diff --git a/.eslintignore b/.eslintignore index f5df2d394adf..f460657cb3b8 100644 --- a/.eslintignore +++ b/.eslintignore @@ -30,3 +30,7 @@ target /packages/osd-test/src/functional_test_runner/lib/config/__tests__/fixtures/ /packages/osd-ui-framework/dist /packages/osd-ui-shared-deps/flot_charts + +# antlr overrides +/src/plugins/data/public/antlr/opensearch_sql/.generated/* +/src/plugins/data/public/antlr/opensearch_sql/grammar/**/* diff --git a/.eslintrc.js b/.eslintrc.js index ad6cea998136..5ab6fdc41483 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -272,26 +272,22 @@ module.exports = { basePath: __dirname, zones: [ { - target: ['(src)/**/*', '!src/core/**/*'], + target: ['src/**/*', '!src/core/**/*'], from: ['src/core/utils/**/*'], errorMessage: `Plugins may only import from src/core/server and src/core/public.`, }, { - target: ['(src)/plugins/*/server/**/*'], - from: ['(src)/plugins/*/public/**/*'], + target: ['src/plugins/*/server/**/*'], + from: ['src/plugins/*/public/**/*'], errorMessage: `Server code can not import from public, use a common directory.`, }, { - target: ['(src)/plugins/*/common/**/*'], - from: ['(src)/plugins/*/(server|public)/**/*'], + target: ['src/plugins/*/common/**/*'], + from: ['src/plugins/*/(server|public)/**/*'], errorMessage: `Common code can not import from server or public, use a common directory.`, }, { - target: [ - 'src/legacy/**/*', - '(src)/plugins/**/(public|server)/**/*', - 'examples/**/*', - ], + target: ['src/legacy/**/*', 'src/plugins/**/(public|server)/**/*', 'examples/**/*'], from: [ 'src/core/public/**/*', '!src/core/public/index.ts', // relative import @@ -320,22 +316,22 @@ module.exports = { { target: [ 'src/legacy/**/*', - '(src)/plugins/**/(public|server)/**/*', + 'src/plugins/**/(public|server)/**/*', 'examples/**/*', - '!(src)/**/*.test.*', + '!src/**/*.test.*', ], from: [ - '(src)/plugins/**/(public|server)/**/*', - '!(src)/plugins/**/(public|server)/mocks/index.{js,mjs,ts}', - '!(src)/plugins/**/(public|server)/(index|mocks).{js,mjs,ts,tsx}', + 'src/plugins/**/(public|server)/**/*', + '!src/plugins/**/(public|server)/mocks/index.{js,mjs,ts}', + '!src/plugins/**/(public|server)/(index|mocks).{js,mjs,ts,tsx}', ], allowSameFolder: true, errorMessage: 'Plugins may only import from top-level public and server modules.', }, { target: [ - '(src)/plugins/**/*', - '!(src)/plugins/**/server/**/*', + 'src/plugins/**/*', + '!src/plugins/**/server/**/*', 'examples/**/*', '!examples/**/server/**/*', @@ -343,7 +339,7 @@ module.exports = { from: [ 'src/core/server', 'src/core/server/**/*', - '(src)/plugins/*/server/**/*', + 'src/plugins/*/server/**/*', 'examples/**/server/**/*', ], errorMessage: @@ -355,7 +351,7 @@ module.exports = { errorMessage: 'The core cannot depend on any plugins.', }, { - target: ['(src)/plugins/*/public/**/*'], + target: ['src/plugins/*/public/**/*'], from: ['ui/**/*'], errorMessage: 'Plugins cannot import legacy UI code.', }, @@ -755,5 +751,14 @@ module.exports = { 'no-undef': 'off', }, }, + { + files: [ + 'src/plugins/data/public/antlr/opensearch_sql/.generated/*', + 'src/plugins/data/public/antlr/opensearch_sql/grammar/**/*', + ], + rules: { + 'filenames/match-regex': 'off', + }, + }, ], }; diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index bf58f69e1c83..891c858aa1a5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @ananzh @kavilla @AMoo-Miki @ashwin-pc @joshuarrrr @abbyhu2000 @zengyan-amazon @zhongnansu @manasvinibs @ZilongX @Flyingliuhub @curq @bandinib-amzn @SuZhou-Joe @ruanyl @BionIT @xinruiba @zhyuanqi @mengweieric +* @ananzh @kavilla @AMoo-Miki @ashwin-pc @joshuarrrr @abbyhu2000 @zengyan-amazon @zhongnansu @manasvinibs @ZilongX @Flyingliuhub @curq @bandinib-amzn @SuZhou-Joe @ruanyl @BionIT @xinruiba @zhyuanqi @mengweieric @LDrago27 @virajsanghvi @sejli @joshuali925 diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 000000000000..52064a31d8bc --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,37 @@ +# .github/release.yml + +changelog: + exclude: + labels: + - ignore-for-release + authors: + - amazon-auto + categories: + - title: 💥 Breaking Changes + labels: + - breaking + - title: 📈 Features/Enhancements + labels: + - enhancement + - title: 🐛 Bug Fixes + labels: + - bug + - title: 🛡 Security + labels: + - cve + - security vulnerability + - title: 🚞 Infrastructure + labels: + - infra + - title: 📝 Documentation + labels: + - docs + - title: 🛠 Maintenance + labels: + - maintenance + - title: 🪛 Refactoring + labels: + - refactor + - title: 🔩 Tests + labels: + - test \ No newline at end of file diff --git a/.github/workflows/build_and_test_workflow.yml b/.github/workflows/build_and_test_workflow.yml index efa86d7dc0fa..da7b5950dd97 100644 --- a/.github/workflows/build_and_test_workflow.yml +++ b/.github/workflows/build_and_test_workflow.yml @@ -11,6 +11,7 @@ on: - '**/*.md' - 'docs/**' - '.lycheeignore' + - 'CODEOWNERS' - 'changelogs/fragments/**' pull_request: branches: [ '**', '!feature/**' ] @@ -18,6 +19,7 @@ on: - '**/*.md' - 'docs/**' - '.lycheeignore' + - 'CODEOWNERS' - 'changelogs/fragments/**' env: diff --git a/.github/workflows/cypress_workflow.yml b/.github/workflows/cypress_workflow.yml index 669f596df898..b404f9c46240 100644 --- a/.github/workflows/cypress_workflow.yml +++ b/.github/workflows/cypress_workflow.yml @@ -32,7 +32,7 @@ env: TEST_REPO: ${{ inputs.test_repo != '' && inputs.test_repo || 'opensearch-project/opensearch-dashboards-functional-test' }} TEST_BRANCH: "${{ inputs.test_branch != '' && inputs.test_branch || github.base_ref }}" FTR_PATH: 'ftr' - START_CMD: 'node ../scripts/opensearch_dashboards --dev --no-base-path --no-watch --savedObjects.maxImportPayloadBytes=10485760 --server.maxPayloadBytes=1759977 --logging.json=false --data.search.aggs.shardDelay.enabled=true --csp.warnLegacyBrowsers=false' + START_CMD: 'node ../scripts/opensearch_dashboards --dev --no-base-path --no-watch --savedObjects.maxImportPayloadBytes=10485760 --server.maxPayloadBytes=1759977 --logging.json=false --data.search.aggs.shardDelay.enabled=true --csp.warnLegacyBrowsers=false --uiSettings.overrides["query:enhancements:enabled"]=false' OPENSEARCH_SNAPSHOT_CMD: 'node ../scripts/opensearch snapshot -E cluster.routing.allocation.disk.threshold_enabled=false' CYPRESS_BROWSER: 'chromium' CYPRESS_VISBUILDER_ENABLED: true diff --git a/.gitignore b/.gitignore index d72d66a6d17d..f62e798ad6a5 100644 --- a/.gitignore +++ b/.gitignore @@ -68,3 +68,6 @@ snapshots.js # Yarn local mirror content .yarn-local-mirror + +# Ignore the generated antlr files +/src/plugins/data/public/antlr/**/grammar/.antlr/ \ No newline at end of file diff --git a/.lycheeignore b/.lycheeignore index 89b3c520d87d..e5a73cf9480c 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -88,4 +88,5 @@ https://unpkg.com/@elastic/ https://codeload.github.com/ https://www.quandl.com/api/v1/datasets/ https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi +http:/adomas.org/javascript-mouse-wheel/ site.com diff --git a/CHANGELOG.md b/CHANGELOG.md index 1eb066f49315..7654e95535ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,210 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] +## [2.16.0-2024-07-30](https://github.com/opensearch-project/OpenSearch-Dashboards/releases/tag/2.16.0) + +### 💥 Breaking Changes + +### Deprecations + + - Remove data enhancements config and readonly flag. Removes dead url link, ([#7291](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7291)) + +### 🛡 Security + + - [CVE-2024-28863] Bump tar from 6.1.11 to 6.2.1 ([#6492](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6492)) + - [CVE-2024-33883] Bump ejs from `3.1.7` to `3.1.10` ([#6770](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6770)) + - [CVE-2024-4067][CVE-2024-4068] Bump packages dependent on `braces` versions lower than 3.0.3 ([#6911](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6911)) + - [GHSA-x565-32qp-m3vf] Bump `jimp` to remove phin dependency ([#6977](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6977)) + - [SNYK-JS-AXIOS-6144788] Bump axios to `1.7.2` ([#7149](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7149)) + - [CVE-2024-37890] Bump ws from `8.5.0` to `8.17.1` and from `7.5.7` to `7.5.10` ([#7153](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7153)) + +### 📈 Features/Enhancements + + - Make theme and dark mode settings user/device specific (in local storage), with opt-out ([#5652](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5652)) + - [Workspace]Import sample data to current workspace ([#6105](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6105)) + - [Data Explorer] Allow render from View directly, not from Data Explorer ([#6167](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6167)) + - [MDS] Allow querying from data sources in Timeline visualizations ([#6385](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6385)) + - [MDS] Prevent importing of data source object when MDS is not enabled ([#6395](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6395)) + - [VisBuilder] Change VisBuilder from experimental to production ([#6436](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6436)) + - Adds `migrations.delete` to delete saved objects by type during a migration ([#6443](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6443)) + - [Workspace] Duplicate selected/all saved objects ([#6478](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6478)) + - [Workspace] Dashboard admin(groups/users) implementation. ([#6554](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6554)) + - Support language selector from the data plugin ([#6613](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6613)) + - Add Server Side Batching for UI Metric Colector ([#6721](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6721)) + - Make Field Name Search Filter Case Insensitive ([#6759](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6759)) + - Add data source selection service to support storing and getting selected data source updates ([#6827](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6827)) + - [Workspace] Only OSD admin can create workspace ([#6831](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6831)) + - [Workspace]Add use cases to workspace form ([#6887](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6887)) + - Add missing aria-label for discover page ([#6898](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6898)) + - Remove endpoint validation for create data source saved object API ([#6899](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6899)) + - [Workspace] Change description field to textarea ([#6907](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6907)) + - Use JSON11 for handling long numerals ([#6915](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6915)) + - [MDS] Allow adding sample data for Timeline visualizations ([#6919](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6919)) + - [Multi DataSource] Add removedComponentIds for data source selection service ([#6920](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6920)) + - [MD]Use placeholder for data source credentials fields when export saved object ([#6928](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6928)) + - Query editor and UI settings toggle ([#7001](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7001)) + - Add search bar extensions ([#7034](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7034)) + - [Workspace] Refactor the UI of workspace picker ([#7045](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7045)) + - Render the datasource selector component conditionally ([#7059](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7059)) + - Introduce new interface for group ([#7060](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7060)) + - Support data source assignment in workspace. ([#7101](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7101)) + - [Workspace] Capabilities service add dashboard admin flag ([#7103](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7103)) + - Onboard dataframes support to MDS and create dataframe before request ([#7106](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7106)) + - Enhance Drag & Drop functionality in Vis Builder ([#7107](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7107)) + - Comply `recent items` with workspace ([#7115](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7115)) + - [Navigation-next] Add register nav group updater in chrome service ([#7117](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7117)) + - [Workspace] Refactor workspace form UI ([#7133](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7133)) + - [MDS] Observability Datasource Plugin migration with MDS support ([#7143](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7143)) + - Add description field in App. ([#7152](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7152)) + - Query editor and dataframes datasources container ([#7157](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7157)) + - [Workspace] Delete the virtual global workspace ([#7165](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7165)) + - 1. Add current nav group into chrome service 2. Prepend current nav group into breadcrumb ([#7166](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7166)) + - [QueryEditorExtensions] change `isEnabled` to an observable ([#7183](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7183)) + - Support workspace level default data source ([#7188](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7188)) + - Introduced an new plugin contentManagement for dynamic content rendering ([#7201](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7201)) + - Address styling of non-primary buttons by making secondary/empty ([#7211](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7211)) + - Add query enhancements plugin as a core plugin ([#7212](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7212)) + - Hide select data source panel for non dashboard admin in workspace create/edit page ([#7213](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7213)) + - [DataSource] Restrict to edit data source on the DSM UI. ([#7214](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7214)) + - Use registered nav group as workspace use case ([#7221](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7221)) + - [navigation-next] Add new left navigation ([#7230](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7230)) + - Add all use case ([#7235](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7235)) + - [navigation-next] add recent works in new homepage ([#7237](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7237)) + - [Workspace] Support workspace detail page ([#7241](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7241)) + - [Workspace] Register workspace settings under setup and settings ([#7242](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7242)) + - Register workspace list card into home page ([#7247](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7247)) + - Add recent items popup in top navigation ([#7257](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7257)) + - [navigation-next] Add new category ([#7275](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7275)) + - Enable landing page for settings and data administration ([#7282](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7282)) + - Support PPL in vega visualization ([#7285](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7285)) + - [VisBuilder] Add Capability to generate dynamic vega ([#7288](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7288)) + - Recover data source management in workspace ([#7296](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7296)) + - Disable certain routes when data_source.manageableBy is none ([#7298](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7298)) + - [navigation-next] fix: redirect to standard index pattern applications while nav group is enabled ([#7305](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7305)) + - Disable inputs in edit data source screen when data_source.manageableBy is none ([#7307](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7307)) + - Update query enhancement UI ([#7309](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7309)) + - [Workspace]Add "All use case" option to workspace form ([#7318](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7318)) + - [MDS] Data Connection details page with MDS support ([#7323](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7323)) + - Use compressed DataSourceSelector ([#7329](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7329)) + - [Workspace] Register four get started cards in home page ([#7333](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7333)) + - [Auto Suggest] OpenSearch SQL autosuggest with ANTLR ([#7336](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7336)) + - [navigation-next] update category ([#7339](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7339)) + - Add home page static list card ([#7351](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7351)) + - [Workspace]Hide create workspace button for non dashboard admin ([#7357](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7357)) + - Enrich breadcrumbs by workspace and use case ([#7360](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7360)) + - Bump OUI to 1.8.0 ([#7363](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7363)) + - [MDS] Observability Datasource Plugin migration with MDS support for Data Connection Table ([#7371](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7371)) + - Add MDS support along with a few cleanup and tests update ([#7463](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7463)) + - Add back data set navigator to control state issues ([#7492](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7492)) + - Fix discover options' location ([#7581](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7581)) + +### 🐛 Bug Fixes + + - [VisBuilder][BUG] Flat render structure in Metric and Table Vis ([#6674](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6674)) + - [MDS] Add a new message to data source components when there are no compatible datasources ([#6678](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6678)) + - Adjust the padding size for aggregated view ([#6715](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6715)) + - Add more test for icon and aggregated view ([#6729](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6729)) + - [OSD Availability] Prevent OSD process crashes when disk is full ([#6733](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6733)) + - Add test for edit data source form ([#6742](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6742)) + - Add test for data_source_error_menu, data_source_item, data_source_multi_selectable ([#6752](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6752)) + - Add test for toast button and validation form ([#6755](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6755)) + - Show error toast when fail to delete saved objects ([#6756](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6756)) + - Lint checker failure fix ([#6771](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6771)) + - Fix workspace name duplication check ([#6776](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6776)) + - Error message is not formatted in vis_type_vega url parser. ([#6777](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6777)) + - [Discover][Bug] Migrate global state from legacy URL ([#6780](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6780)) + - Quickrange selection fix ([#6782](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6782)) + - Bug Fixes for Vis Builder ([#6811](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6811)) + - Fix endpoint validation by passing in request when creating datasource client ([#6822](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6822)) + - Update index pattern references with data source when import sample data ([#6851](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6851)) + - Remove unused import and property which broke compilation ([#6879](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6879)) + - Fix not setting the default data source when creating data source bug ([#6908](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6908)) + - Close any open system flyout when changing view mode of the dashboard ([#6923](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6923)) + - Add TSVB Support for adding sample data ([#6940](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6940)) + - Fix web log sample visualization & vis-builder not rendering with data source issue ([#6948](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6948)) + - [MDS] Include data source name when importing a timeline visualization ([#6954](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6954)) + - Update z-index of sidecar container to make it more than mask, from 1000 to 1001. ([#6964](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6964)) + - [Discover] Check if the timestamp is already included to remove duplicate col ([#6983](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6983)) + - Highlight the anchor row in surrounding doc view ([#7025](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7025)) + - [MDS] Add data source engine type to data source saved object ([#7026](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7026)) + - Fix colors of the visualizations with more than 10 items ([#7051](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7051)) + - [BUG][NewHomePage] Temp Solution to avoid crash for anonymous user with no write permission ([#7054](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7054)) + - [Discover] Allow the last column of a table wider than the window to show up properly ([#7058](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7058)) + - Update error message in timeline visualization when MDS disabled ([#7069](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7069)) + - Fix object empty check and minor perf issue in query editor extensions ([#7077](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7077)) + - Remove angular related comment and code ([#7087](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7087)) + - [MDS][Version Decoupling] Add support of Version Decoupling in Index Patterns Dashboards Plugin ([#7100](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7100)) + - [Workspace]Restrict saved objects finding when workspace enabled ([#7125](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7125)) + - [MDS][Version Decoupling] Add support of required backend plugins check on data sources ([#7146](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7146)) + - [MDS] Fix the dsm plugin setup when mds feature flag is disabled ([#7163](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7163)) + - [MDS][Version Decoupling] Add dataSourceVersion' and 'installedPlugins in viewer returns ([#7172](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7172)) + - Break new lines in table cell in legacy discover ([#7207](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7207)) + - [Sample Data] Updates sample dashboard title in sample web logs data ([#7233](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7233)) + - Discover page status stuck in loading State ([#7252](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7252)) + - Unassign data source before deleteByWorkspace ([#7279](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7279)) + - Unused config setting and remove data sources as a required plugin. ([#7314](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7314)) + - Fix wrapping of labels in filter by type popover ([#7327](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7327)) + - [Navigation] Update dev tools tab css for new left navigation ([#7328](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7328)) + - Data source selector in dev tools tab moved to left ([#7347](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7347)) + - [navigation-next] Fix issues. ([#7356](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7356)) + - [DataSource] No restriction on setting default data source ([#7396](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7396)) + - Make breadcrumb of 4 new added applications comply with BrowserRouter. ([#7401](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7401)) + - [Bug][Workspace] Navigate to detail page when clicking all use case workspace ([#7405](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7405)) + - [Version Decoupling] Add data source version and installed plugins in data source viewer returns ([#7420](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7420)) + - [Bug][Workspace] Add permission validation at workspace detail page ([#7435](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7435)) + - [Bug][Data Source] Move data source manageable feature flag to DSM plugin ([#7440](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7440)) + - Update recent items icon from SVG to react component ([#7478](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7478)) + - [MDS] Fix the hide local cluster config ([#7497](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7497)) + - Update icon of recent items from OUI library to enable dark mode ([#7508](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7508)) + - Fix data source picker trigger local cluster call by default ([#7528](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7528)) + - Fix babel error ([#7541](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7541)) + - Fix tables not displaying in navigator and add local cluster to datasources ([#7542](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7542)) + - Fixes Discover next styling ([#7546](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7546)) + - [navigation]feat: redirect user to home in global when workspace is enabled ([#7551](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7551)) + - [Workspace]Add workspaces and permissions fields into saved objects _bulk_get response ([#7565](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7565)) + - Fixes databases not being displayed upon success ([#7567](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7567)) + +### 🚞 Infrastructure + +### 📝 Documentation + + - Add zhyuanqi as maintainer ([#6788](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6788)) + - Move @BSFishy to emeritus maintainer ([#6790](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6790)) + - Add mengweieric as maintainer ([#6798](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6798)) + - Add OpenAPI specification for GET and CREATE saved object API ([#6799](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6799)) + - Add example for saved object creation part for openapi doc. ([#6855](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6855)) + - Add openAPI doc for saved_object find api ([#6856](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6856)) + - Add OpenAPI specification for bulk create and bulk update saved object APIs ([#6859](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6859)) + - Add OpenAPI specification for bulk_get saved object APIs ([#6860](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6860)) + - Add OpenAPI specification for update, delete and migrate saved object API ([#6864](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6864)) + - Add OpenAPI specification for import and export saved object api ([#6872](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6872)) + - Add OpenAPI specifications for resolve import errors api ([#6885](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6885)) + - Add Suchit as maintainer ([#6980](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6980)) + - Add Viraj as maintainer ([#7196](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7196)) + - Add OpenAPI specification for API for retrieving fields of index patterns ([#7270](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7270)) + - Add Sean as maintainer ([#7458](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7458)) + - Add Joshua as maintainer ([#7553](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7553)) + +### 🛠 Maintenance + + - Skip running tests for updates in CODEOWNERS ([#7197](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7197)) + +### 🪛 Refactoring + + - Unify getDefaultDataSourceId and export ([#6843](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6843)) + - [MDS] Refactor error handling in data source management plugin to use DataSourceError ([#6903](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6903)) + - [Look&Feel] Refactor to use semantic headers for page, modal & flyout ([#7192](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7192)) + - [Look&Feel] Consistency of Plus Icons ([#7195](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7195)) + - [Look&Feel] Update Popover Padding Size ([#7200](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7200)) + - [Look&Feel] Replace browser tooltip usage with OUI tooltip ([#7231](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7231)) + - [Look&Feel] Use small EuiTabs and EuiTabbedContent across the board ([#7232](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7232)) + - Density and consistency changes for discover and query bar ([#7299](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7299)) + - [Look&Feel] Apply guidance for visBuilder ([#7341](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7341)) + - [Look&Feel] Apply small popover padding and add Oui tooltips ([#7523](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7523)) + - [Look&Feel] Discover and Query Management fix ([#7530](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7530)) + +### 🔩 Tests + ### 💥 Breaking Changes ### Deprecations @@ -16,6 +220,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ### 📈 Features/Enhancements - [Multiple Datasource] Add TLS configuration for multiple data sources ([#6171](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6171)) +- [Multiple DataSource] Do not support import data source object to Local cluster when not enable data source ([#6395](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6395)) ### 🐛 Bug Fixes @@ -65,7 +270,6 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - [Multiple Datasource] Able to Hide "Local Cluster" option from datasource DropDown ([#5827](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5827)) - [Multiple Datasource] Add api registry and allow it to be added into client config in data source plugin ([#5895](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5895)) - [Multiple Datasource] Refactor client and legacy client to use authentication registry ([#5881](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5881)) -- [Theme] Make theme and dark mode settings user/device specific (in local storage), with opt-out ([#5652](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5652)) ### 🐛 Bug Fixes diff --git a/MAINTAINERS.md b/MAINTAINERS.md index 7f7dff5d37fe..321d9ab02794 100644 --- a/MAINTAINERS.md +++ b/MAINTAINERS.md @@ -25,6 +25,10 @@ This document contains a list of maintainers in this repo. See [opensearch-proje | Xinrui Bai | [xinruiba](https://github.com/xinruiba) | Amazon | | Ella Zhu | [zhyuanqi](https://github.com/zhyuanqi) | Amazon | | Eric Wei | [mengweieric](https://github.com/mengweieric) | Amazon | +| Suchit Sahoo | [LDrago27](https://github.com/LDrago27) | Amazon | +| Viraj Sanghvi | [virajsanghvi](https://github.com/virajsanghvi) | Amazon | +| Sean Li | [sejli](https://github.com/sejli) | Amazon | +| Joshua Li | [joshuali925](https://github.com/joshuali925) | Amazon | ## Emeritus diff --git a/changelogs/fragments/5652.yml b/changelogs/fragments/5652.yml deleted file mode 100644 index fe6ed9dc1da3..000000000000 --- a/changelogs/fragments/5652.yml +++ /dev/null @@ -1,2 +0,0 @@ -feat: -- Make theme and dark mode settings user/device specific (in local storage), with opt-out ([#5652](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5652)) \ No newline at end of file diff --git a/changelogs/fragments/6105.yml b/changelogs/fragments/6105.yml deleted file mode 100644 index 30038aad59c3..000000000000 --- a/changelogs/fragments/6105.yml +++ /dev/null @@ -1,2 +0,0 @@ -feat: -- [Workspace]Import sample data to current workspace ([#6105](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6105)) \ No newline at end of file diff --git a/changelogs/fragments/6443.yml b/changelogs/fragments/6443.yml deleted file mode 100644 index 2de7191e0d09..000000000000 --- a/changelogs/fragments/6443.yml +++ /dev/null @@ -1,2 +0,0 @@ -feat: -- Adds `migrations.delete` to delete saved objects by type during a migration ([#6443](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6443)) \ No newline at end of file diff --git a/changelogs/fragments/6492.yml b/changelogs/fragments/6492.yml deleted file mode 100644 index 1212133215c5..000000000000 --- a/changelogs/fragments/6492.yml +++ /dev/null @@ -1,2 +0,0 @@ -security: -- [CVE-2024-28863] Bump tar from 6.1.11 to 6.2.1 ([#6492](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6492)) \ No newline at end of file diff --git a/changelogs/fragments/6554.yml b/changelogs/fragments/6554.yml deleted file mode 100644 index b3944b967773..000000000000 --- a/changelogs/fragments/6554.yml +++ /dev/null @@ -1,2 +0,0 @@ -feat: -- [Workspace] Dashboard admin(groups/users) implementation. ([#6554](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6554)) \ No newline at end of file diff --git a/changelogs/fragments/6613.yml b/changelogs/fragments/6613.yml deleted file mode 100644 index 49cd01f52fdd..000000000000 --- a/changelogs/fragments/6613.yml +++ /dev/null @@ -1,2 +0,0 @@ -feat: -- Support language selector from the data plugin ([#6613](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6613)) \ No newline at end of file diff --git a/changelogs/fragments/6678.yml b/changelogs/fragments/6678.yml deleted file mode 100644 index 46e9568f5653..000000000000 --- a/changelogs/fragments/6678.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- [MDS] Add a new message to data source components when there are no compatible datasources ([#6678](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6678)) \ No newline at end of file diff --git a/changelogs/fragments/6715.yml b/changelogs/fragments/6715.yml deleted file mode 100644 index 4d5c07f57bd5..000000000000 --- a/changelogs/fragments/6715.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Adjust the padding size for aggregated view ([#6715](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6715)) \ No newline at end of file diff --git a/changelogs/fragments/6721.yml b/changelogs/fragments/6721.yml deleted file mode 100644 index 25d2a25b2fbb..000000000000 --- a/changelogs/fragments/6721.yml +++ /dev/null @@ -1,2 +0,0 @@ -feat: -- Add Server Side Batching for UI Metric Colector ([#6721](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6721)) \ No newline at end of file diff --git a/changelogs/fragments/6729.yml b/changelogs/fragments/6729.yml deleted file mode 100644 index 8a564e684ffc..000000000000 --- a/changelogs/fragments/6729.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Add more test for icon and aggregated view ([#6729](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6729)) \ No newline at end of file diff --git a/changelogs/fragments/6733.yml b/changelogs/fragments/6733.yml deleted file mode 100644 index 2021bce6c9dc..000000000000 --- a/changelogs/fragments/6733.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- [OSD Availability] Prevent OSD process crashes when disk is full ([#6733](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6733)) \ No newline at end of file diff --git a/changelogs/fragments/6742.yml b/changelogs/fragments/6742.yml deleted file mode 100644 index 390210dff311..000000000000 --- a/changelogs/fragments/6742.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Add test for edit data source form ([#6742](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6742)) \ No newline at end of file diff --git a/changelogs/fragments/6752.yml b/changelogs/fragments/6752.yml deleted file mode 100644 index f43473bea2b8..000000000000 --- a/changelogs/fragments/6752.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Add test for data_source_error_menu, data_source_item, data_source_multi_selectable ([#6752](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6752)) \ No newline at end of file diff --git a/changelogs/fragments/6755.yml b/changelogs/fragments/6755.yml deleted file mode 100644 index 8e06db889066..000000000000 --- a/changelogs/fragments/6755.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Add test for toast button and validation form ([#6755](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6755)) \ No newline at end of file diff --git a/changelogs/fragments/6756.yml b/changelogs/fragments/6756.yml deleted file mode 100644 index 9cad7bffc99d..000000000000 --- a/changelogs/fragments/6756.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Show error toast when fail to delete saved objects ([#6756](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6756)) \ No newline at end of file diff --git a/changelogs/fragments/6759.yml b/changelogs/fragments/6759.yml deleted file mode 100644 index ef7e5cddddda..000000000000 --- a/changelogs/fragments/6759.yml +++ /dev/null @@ -1,2 +0,0 @@ -feat: -- Make Field Name Search Filter Case Insensitive ([#6759](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6759)) \ No newline at end of file diff --git a/changelogs/fragments/6770.yml b/changelogs/fragments/6770.yml deleted file mode 100644 index 19dee5d37b46..000000000000 --- a/changelogs/fragments/6770.yml +++ /dev/null @@ -1,2 +0,0 @@ -security: -- [CVE-2024-33883] Bump ejs from `3.1.7` to `3.1.10` ([#6770](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6770)) \ No newline at end of file diff --git a/changelogs/fragments/6771.yml b/changelogs/fragments/6771.yml deleted file mode 100644 index 454dd0d17565..000000000000 --- a/changelogs/fragments/6771.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Lint checker failure fix ([#6771](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6771)) \ No newline at end of file diff --git a/changelogs/fragments/6776.yml b/changelogs/fragments/6776.yml deleted file mode 100644 index eb976800efa4..000000000000 --- a/changelogs/fragments/6776.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Fix workspace name duplication check ([#6776](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6776)) \ No newline at end of file diff --git a/changelogs/fragments/6777.yml b/changelogs/fragments/6777.yml deleted file mode 100644 index 9e3a63925baa..000000000000 --- a/changelogs/fragments/6777.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Error message is not formatted in vis_type_vega url parser. ([#6777](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6777)) \ No newline at end of file diff --git a/changelogs/fragments/6780.yml b/changelogs/fragments/6780.yml deleted file mode 100644 index 033cd26aec1e..000000000000 --- a/changelogs/fragments/6780.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- [Discover][Bug] Migrate global state from legacy URL ([#6780](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6780)) \ No newline at end of file diff --git a/changelogs/fragments/6782.yml b/changelogs/fragments/6782.yml deleted file mode 100644 index 19349a8de101..000000000000 --- a/changelogs/fragments/6782.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Quickrange selection fix ([#6782](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6782)) \ No newline at end of file diff --git a/changelogs/fragments/6788.yml b/changelogs/fragments/6788.yml deleted file mode 100644 index a032e911c446..000000000000 --- a/changelogs/fragments/6788.yml +++ /dev/null @@ -1,2 +0,0 @@ -doc: -- Add zhyuanqi as maintainer ([#6788](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6788)) \ No newline at end of file diff --git a/changelogs/fragments/6790.yml b/changelogs/fragments/6790.yml deleted file mode 100644 index afd05b60c082..000000000000 --- a/changelogs/fragments/6790.yml +++ /dev/null @@ -1,2 +0,0 @@ -doc: -- Move @BSFishy to emeritus maintainer ([#6790](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6790)) \ No newline at end of file diff --git a/changelogs/fragments/6798.yml b/changelogs/fragments/6798.yml deleted file mode 100644 index de03d466154b..000000000000 --- a/changelogs/fragments/6798.yml +++ /dev/null @@ -1,2 +0,0 @@ -doc: -- Add mengweieric as maintainer ([#6798](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6798)) \ No newline at end of file diff --git a/changelogs/fragments/6799.yml b/changelogs/fragments/6799.yml deleted file mode 100644 index 0fc28064724d..000000000000 --- a/changelogs/fragments/6799.yml +++ /dev/null @@ -1,2 +0,0 @@ -doc: -- Add OpenAPI specification for GET and CREATE saved object API ([#6799](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6799)) \ No newline at end of file diff --git a/changelogs/fragments/6811.yml b/changelogs/fragments/6811.yml deleted file mode 100644 index b7901437ca61..000000000000 --- a/changelogs/fragments/6811.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Bug Fixes for Vis Builder ([#6811](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6811)) \ No newline at end of file diff --git a/changelogs/fragments/6822.yml b/changelogs/fragments/6822.yml deleted file mode 100644 index 83df4b59675c..000000000000 --- a/changelogs/fragments/6822.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Fix endpoint validation by passing in request when creating datasource client ([#6822](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6822)) \ No newline at end of file diff --git a/changelogs/fragments/6827.yml b/changelogs/fragments/6827.yml deleted file mode 100644 index ce47fd32b7bd..000000000000 --- a/changelogs/fragments/6827.yml +++ /dev/null @@ -1,2 +0,0 @@ -feat: -- Add data source selection service to support storing and getting selected data source updates ([#6827](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6827)) \ No newline at end of file diff --git a/changelogs/fragments/6831.yml b/changelogs/fragments/6831.yml deleted file mode 100644 index a7dc8210023b..000000000000 --- a/changelogs/fragments/6831.yml +++ /dev/null @@ -1,2 +0,0 @@ -feat: -- [Workspace] Only OSD admin can create workspace ([#6831](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6831)) \ No newline at end of file diff --git a/changelogs/fragments/6843.yml b/changelogs/fragments/6843.yml deleted file mode 100644 index 2c483392bc2d..000000000000 --- a/changelogs/fragments/6843.yml +++ /dev/null @@ -1,2 +0,0 @@ -refactor: -- Unify getDefaultDataSourceId and export ([#6843](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6843)) \ No newline at end of file diff --git a/changelogs/fragments/6851.yml b/changelogs/fragments/6851.yml deleted file mode 100644 index c4e131e73c9d..000000000000 --- a/changelogs/fragments/6851.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Update index pattern references with data source when import sample data ([#6851](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6851)) \ No newline at end of file diff --git a/changelogs/fragments/6855.yml b/changelogs/fragments/6855.yml deleted file mode 100644 index cc281d82b4a4..000000000000 --- a/changelogs/fragments/6855.yml +++ /dev/null @@ -1,2 +0,0 @@ -doc: -- Add example for saved object creation part for openapi doc. ([#6855](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6855)) \ No newline at end of file diff --git a/changelogs/fragments/6856.yml b/changelogs/fragments/6856.yml deleted file mode 100644 index c3897a3f81fd..000000000000 --- a/changelogs/fragments/6856.yml +++ /dev/null @@ -1,2 +0,0 @@ -doc: -- Add openAPI doc for saved_object find api ([#6856](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6856)) \ No newline at end of file diff --git a/changelogs/fragments/6859.yml b/changelogs/fragments/6859.yml deleted file mode 100644 index 499f2ccc80ff..000000000000 --- a/changelogs/fragments/6859.yml +++ /dev/null @@ -1,2 +0,0 @@ -doc: -- Add OpenAPI specification for bulk create and bulk update saved object APIs ([#6859](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6859)) \ No newline at end of file diff --git a/changelogs/fragments/6860.yml b/changelogs/fragments/6860.yml deleted file mode 100644 index 8390c9302386..000000000000 --- a/changelogs/fragments/6860.yml +++ /dev/null @@ -1,2 +0,0 @@ -doc: -- Add OpenAPI specification for bulk_get saved object APIs ([#6860](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6860)) \ No newline at end of file diff --git a/changelogs/fragments/6864.yml b/changelogs/fragments/6864.yml deleted file mode 100644 index aaab0a92be3a..000000000000 --- a/changelogs/fragments/6864.yml +++ /dev/null @@ -1,2 +0,0 @@ -doc: -- Add OpenAPI specification for update, delete and migrate saved object API ([#6864](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6864)) \ No newline at end of file diff --git a/changelogs/fragments/6879.yml b/changelogs/fragments/6879.yml deleted file mode 100644 index 554e3eaeeeff..000000000000 --- a/changelogs/fragments/6879.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Remove unused import and property which broke compilation ([#6879](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6879)) \ No newline at end of file diff --git a/changelogs/fragments/6885.yml b/changelogs/fragments/6885.yml deleted file mode 100644 index a375c6812aff..000000000000 --- a/changelogs/fragments/6885.yml +++ /dev/null @@ -1,2 +0,0 @@ -doc: -- Add OpenAPI specifications for resolve import errors api ([#6885](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6885)) \ No newline at end of file diff --git a/changelogs/fragments/6899.yml b/changelogs/fragments/6899.yml deleted file mode 100644 index fe930956760c..000000000000 --- a/changelogs/fragments/6899.yml +++ /dev/null @@ -1,2 +0,0 @@ -feat: -- Remove endpoint validation for create data source saved object API ([#6899](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6899)) \ No newline at end of file diff --git a/changelogs/fragments/6908.yml b/changelogs/fragments/6908.yml deleted file mode 100644 index 025eba9b10c2..000000000000 --- a/changelogs/fragments/6908.yml +++ /dev/null @@ -1,2 +0,0 @@ -fix: -- Fix not setting the default data source when creating data source bug ([#6908](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6908)) \ No newline at end of file diff --git a/changelogs/fragments/6928.yml b/changelogs/fragments/6928.yml deleted file mode 100644 index 66ba2e4d9e86..000000000000 --- a/changelogs/fragments/6928.yml +++ /dev/null @@ -1,2 +0,0 @@ -feat: -- [MD]Use placeholder for data source credentials fields when export saved object ([#6928](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6928)) \ No newline at end of file diff --git a/changelogs/fragments/7391.yml b/changelogs/fragments/7391.yml new file mode 100644 index 000000000000..7be3d1f7eaa9 --- /dev/null +++ b/changelogs/fragments/7391.yml @@ -0,0 +1,2 @@ +feat: +- DQL Autocomplete ([#7391](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7391)) \ No newline at end of file diff --git a/changelogs/fragments/7501.yml b/changelogs/fragments/7501.yml new file mode 100644 index 000000000000..3987a2e580fb --- /dev/null +++ b/changelogs/fragments/7501.yml @@ -0,0 +1,2 @@ +refactor: +- [Workspace] Support getting workspaces client from coreStart ([#7501](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7501)) \ No newline at end of file diff --git a/changelogs/fragments/7503.yml b/changelogs/fragments/7503.yml new file mode 100644 index 000000000000..5f33fce038f7 --- /dev/null +++ b/changelogs/fragments/7503.yml @@ -0,0 +1,2 @@ +feat: +- Provide new embeddable option to hide embeddable panel action button ([#7503](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7503)) \ No newline at end of file diff --git a/changelogs/fragments/7512.yml b/changelogs/fragments/7512.yml new file mode 100644 index 000000000000..a248d7644293 --- /dev/null +++ b/changelogs/fragments/7512.yml @@ -0,0 +1,2 @@ +fix: +- [Workspace]add workspace name blank/empty check ([#7512](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7512)) \ No newline at end of file diff --git a/changelogs/fragments/7516.yml b/changelogs/fragments/7516.yml new file mode 100644 index 000000000000..3ef007d8fbf2 --- /dev/null +++ b/changelogs/fragments/7516.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace]Optimize workspace permission validation for bulk operations ([#7516](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7516)) \ No newline at end of file diff --git a/changelogs/fragments/7527.yml b/changelogs/fragments/7527.yml new file mode 100644 index 000000000000..b3bebe022ac2 --- /dev/null +++ b/changelogs/fragments/7527.yml @@ -0,0 +1,2 @@ +fix: +- Not highlighting Droppable Areas while dragging a field ([#7527](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7527)) \ No newline at end of file diff --git a/changelogs/fragments/7529.yml b/changelogs/fragments/7529.yml new file mode 100644 index 000000000000..b2031779e48c --- /dev/null +++ b/changelogs/fragments/7529.yml @@ -0,0 +1,2 @@ +feat: +- [VisBuilder-Next] Migration of legacy visualizations to VisBuilder by constructing the URL. ([#7529](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7529)) \ No newline at end of file diff --git a/changelogs/fragments/7547.yml b/changelogs/fragments/7547.yml new file mode 100644 index 000000000000..8921d0ba4d14 --- /dev/null +++ b/changelogs/fragments/7547.yml @@ -0,0 +1,2 @@ +fix: +- [Workspace] updating workspace-list-card and home-list-card ([#7547](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7547)) \ No newline at end of file diff --git a/changelogs/fragments/7550.yml b/changelogs/fragments/7550.yml new file mode 100644 index 000000000000..83cb4d9f0979 --- /dev/null +++ b/changelogs/fragments/7550.yml @@ -0,0 +1,2 @@ +fix: +- Resolve some browser warnings ([#7550](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7550)) \ No newline at end of file diff --git a/changelogs/fragments/7575.yml b/changelogs/fragments/7575.yml new file mode 100644 index 000000000000..3f1335de1ecc --- /dev/null +++ b/changelogs/fragments/7575.yml @@ -0,0 +1,2 @@ +doc: +- Add documentation for dynamic page creation ([#7575](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7575)) \ No newline at end of file diff --git a/changelogs/fragments/7585.yml b/changelogs/fragments/7585.yml new file mode 100644 index 000000000000..55e0de4ab9f1 --- /dev/null +++ b/changelogs/fragments/7585.yml @@ -0,0 +1,2 @@ +fix: +- Do not show surround doc links for PPL ([#7585](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7585)) \ No newline at end of file diff --git a/changelogs/fragments/7593.yml b/changelogs/fragments/7593.yml new file mode 100644 index 000000000000..d08a386a3978 --- /dev/null +++ b/changelogs/fragments/7593.yml @@ -0,0 +1,2 @@ +fix: +- Update DQL Autocomplete in code and functionality ([#7593](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7593)) \ No newline at end of file diff --git a/changelogs/fragments/7596.yml b/changelogs/fragments/7596.yml new file mode 100644 index 000000000000..273a8b5c1e23 --- /dev/null +++ b/changelogs/fragments/7596.yml @@ -0,0 +1,2 @@ +fix: +- Add validation for data source in get and bulk_get methods ([#7596](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7596)) \ No newline at end of file diff --git a/changelogs/fragments/7598.yml b/changelogs/fragments/7598.yml new file mode 100644 index 000000000000..85bcda12a9d4 --- /dev/null +++ b/changelogs/fragments/7598.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace] Refactor workspace detail page ([#7598](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7598)) \ No newline at end of file diff --git a/changelogs/fragments/7600.yml b/changelogs/fragments/7600.yml new file mode 100644 index 000000000000..1a295f1963c9 --- /dev/null +++ b/changelogs/fragments/7600.yml @@ -0,0 +1,2 @@ +feat: +- Add a util function to generate the relative redirectUrl. ([#7600](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7600)) \ No newline at end of file diff --git a/changelogs/fragments/7603.yml b/changelogs/fragments/7603.yml new file mode 100644 index 000000000000..6ca9213c4fcd --- /dev/null +++ b/changelogs/fragments/7603.yml @@ -0,0 +1,2 @@ +Refactor: +- [Look&Feel] Update paragraph text sizes across remaining OSD ([#7603](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7603)) \ No newline at end of file diff --git a/changelogs/fragments/7612.yml b/changelogs/fragments/7612.yml new file mode 100644 index 000000000000..16e3601f9389 --- /dev/null +++ b/changelogs/fragments/7612.yml @@ -0,0 +1,2 @@ +feat: +- Only allow essential use case when creating workspace if all data sources are serverless ([#7612](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7612)) \ No newline at end of file diff --git a/changelogs/fragments/7613.yml b/changelogs/fragments/7613.yml new file mode 100644 index 000000000000..ff6c8f6c521a --- /dev/null +++ b/changelogs/fragments/7613.yml @@ -0,0 +1,2 @@ +fix: +- [navigation] add sample data to left navigation ([#7613](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7613)) \ No newline at end of file diff --git a/changelogs/fragments/7616.yml b/changelogs/fragments/7616.yml new file mode 100644 index 000000000000..5a7980f4ebbe --- /dev/null +++ b/changelogs/fragments/7616.yml @@ -0,0 +1,2 @@ +Refactor: +- [Look&Feel] Use semantic headers for page, modal, & flyouts across the board ([#7616](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7616)) \ No newline at end of file diff --git a/changelogs/fragments/7618.yml b/changelogs/fragments/7618.yml new file mode 100644 index 000000000000..342203fbb45f --- /dev/null +++ b/changelogs/fragments/7618.yml @@ -0,0 +1,2 @@ +feat: +- Use essentials as the nav group name ([#7618](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7618)) \ No newline at end of file diff --git a/changelogs/fragments/7619.yml b/changelogs/fragments/7619.yml new file mode 100644 index 000000000000..4ad2ddcee19b --- /dev/null +++ b/changelogs/fragments/7619.yml @@ -0,0 +1,2 @@ +feat: +- Make parent item unclickable and fix duplicate items in landing page. ([#7619](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7619)) \ No newline at end of file diff --git a/changelogs/fragments/7624.yml b/changelogs/fragments/7624.yml new file mode 100644 index 000000000000..cc72302bd5ca --- /dev/null +++ b/changelogs/fragments/7624.yml @@ -0,0 +1,2 @@ +fix: +- [contentManagement] display cards by specifying a column size or display all cards in one row ([#7624](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7624)) \ No newline at end of file diff --git a/changelogs/fragments/7625.yml b/changelogs/fragments/7625.yml new file mode 100644 index 000000000000..e4ce4fbe7e57 --- /dev/null +++ b/changelogs/fragments/7625.yml @@ -0,0 +1,5 @@ +refactor: +- Simplify theme configuration and defaulting ([#7625](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7625)) + +deprecate: +- Deprecating `CssDistFilename` exports in favor of `themeCssDistFilenames` in `@osd/ui-shared-deps` ([#7625](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7625)) \ No newline at end of file diff --git a/changelogs/fragments/7627.yml b/changelogs/fragments/7627.yml new file mode 100644 index 000000000000..8b5a7e830062 --- /dev/null +++ b/changelogs/fragments/7627.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace] Set default color for workspace create form ([#7627](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7627)) \ No newline at end of file diff --git a/changelogs/fragments/7633.yml b/changelogs/fragments/7633.yml new file mode 100644 index 000000000000..685398726fe0 --- /dev/null +++ b/changelogs/fragments/7633.yml @@ -0,0 +1,2 @@ +feat: +- Register section and content with the same id will not throw error but overrides the exist one ([#7633](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7633)) \ No newline at end of file diff --git a/changelogs/fragments/7636.yml b/changelogs/fragments/7636.yml new file mode 100644 index 000000000000..e154dc07a596 --- /dev/null +++ b/changelogs/fragments/7636.yml @@ -0,0 +1,2 @@ +fix: +- [Workspace] Move set default source order to avoid dev server crash ([#7636](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7636)) \ No newline at end of file diff --git a/changelogs/fragments/7637.yml b/changelogs/fragments/7637.yml new file mode 100644 index 000000000000..02db1c24d3d2 --- /dev/null +++ b/changelogs/fragments/7637.yml @@ -0,0 +1,2 @@ +feat: +- Introduce the redesign page and applications headers behind a switch ([#7637](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7637)) diff --git a/changelogs/fragments/7640.yml b/changelogs/fragments/7640.yml new file mode 100644 index 000000000000..5a93e3bdb5d9 --- /dev/null +++ b/changelogs/fragments/7640.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace] Update workspace list page table ([#7640](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7640)) \ No newline at end of file diff --git a/changelogs/fragments/7651.yml b/changelogs/fragments/7651.yml new file mode 100644 index 000000000000..451ec7d8c444 --- /dev/null +++ b/changelogs/fragments/7651.yml @@ -0,0 +1,2 @@ +feat: +- [contentManagement] allow to update section input after page rendered ([#7651](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7651)) \ No newline at end of file diff --git a/changelogs/fragments/7652.yml b/changelogs/fragments/7652.yml new file mode 100644 index 000000000000..132afc64c12c --- /dev/null +++ b/changelogs/fragments/7652.yml @@ -0,0 +1,2 @@ +feat: +- Update permission settings appearance ([#7652](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7652)) \ No newline at end of file diff --git a/changelogs/fragments/7655.yml b/changelogs/fragments/7655.yml new file mode 100644 index 000000000000..70a921f4e6df --- /dev/null +++ b/changelogs/fragments/7655.yml @@ -0,0 +1,2 @@ +feat: +- [navigation] Left navigation collective ([#7655](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7655)) \ No newline at end of file diff --git a/changelogs/fragments/7656.yml b/changelogs/fragments/7656.yml new file mode 100644 index 000000000000..0447cdf6e893 --- /dev/null +++ b/changelogs/fragments/7656.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace]Add name and description characters limitation ([#7656](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7656)) \ No newline at end of file diff --git a/changelogs/fragments/7671.yml b/changelogs/fragments/7671.yml new file mode 100644 index 000000000000..8d88fec48eee --- /dev/null +++ b/changelogs/fragments/7671.yml @@ -0,0 +1,2 @@ +fix: +- [Workspace]Fix page crash caused by invalid workspace color ([#7671](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7671)) \ No newline at end of file diff --git a/changelogs/fragments/7673.yml b/changelogs/fragments/7673.yml new file mode 100644 index 000000000000..f0a84647bad2 --- /dev/null +++ b/changelogs/fragments/7673.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace]Essential/Analytics(All) use case overview page ([#7673](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7673)) \ No newline at end of file diff --git a/changelogs/fragments/7686.yml b/changelogs/fragments/7686.yml new file mode 100644 index 000000000000..f446ed68764c --- /dev/null +++ b/changelogs/fragments/7686.yml @@ -0,0 +1,2 @@ +feat: +- Change the locale dynamically by adding &i18n-locale to URL ([#7686](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7686)) \ No newline at end of file diff --git a/changelogs/fragments/7687.yml b/changelogs/fragments/7687.yml new file mode 100644 index 000000000000..4577e74e4d4d --- /dev/null +++ b/changelogs/fragments/7687.yml @@ -0,0 +1,2 @@ +refactor: +- Refactor search bar & filters to conditionally render new look with application header ([#7687](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7687)) \ No newline at end of file diff --git a/changelogs/fragments/7691.yml b/changelogs/fragments/7691.yml new file mode 100644 index 000000000000..e42b7eb3ce88 --- /dev/null +++ b/changelogs/fragments/7691.yml @@ -0,0 +1,2 @@ +feat: +- Allow customizing `restrictWidth` and `paddingSize` of `TableListView` ([#7691](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7691)) \ No newline at end of file diff --git a/changelogs/fragments/7697.yml b/changelogs/fragments/7697.yml new file mode 100644 index 000000000000..df490d5ed68a --- /dev/null +++ b/changelogs/fragments/7697.yml @@ -0,0 +1,2 @@ +feat: +- Integrate new page header for workspace pages ([#7697](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7697)) \ No newline at end of file diff --git a/changelogs/fragments/7699.yml b/changelogs/fragments/7699.yml new file mode 100644 index 000000000000..24554fb44461 --- /dev/null +++ b/changelogs/fragments/7699.yml @@ -0,0 +1,2 @@ +feat: +- Add a unit test case to indicate React is anti-xss ([#7699](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7699)) \ No newline at end of file diff --git a/changelogs/fragments/7702.yml b/changelogs/fragments/7702.yml new file mode 100644 index 000000000000..fe4b48169a40 --- /dev/null +++ b/changelogs/fragments/7702.yml @@ -0,0 +1,2 @@ +feat: +- Refractor the homepage assets list section ([#7702](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7702)) \ No newline at end of file diff --git a/changelogs/fragments/7703.yml b/changelogs/fragments/7703.yml new file mode 100644 index 000000000000..721972dab4db --- /dev/null +++ b/changelogs/fragments/7703.yml @@ -0,0 +1,2 @@ +feat: +- [Workspaces]Add features in use case card and preselect first use case ([#7703](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7703)) \ No newline at end of file diff --git a/changelogs/fragments/7708.yml b/changelogs/fragments/7708.yml new file mode 100644 index 000000000000..56ead27a3644 --- /dev/null +++ b/changelogs/fragments/7708.yml @@ -0,0 +1,2 @@ +feat: +- Support workspace initial page ([#7708](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7708)) \ No newline at end of file diff --git a/changelogs/fragments/7712.yml b/changelogs/fragments/7712.yml new file mode 100644 index 000000000000..faba262bcc6b --- /dev/null +++ b/changelogs/fragments/7712.yml @@ -0,0 +1,2 @@ +feat: +- Add New Page Header to Visualize ([#7712](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7712)) \ No newline at end of file diff --git a/changelogs/fragments/7716.yml b/changelogs/fragments/7716.yml new file mode 100644 index 000000000000..d1b91ff51d89 --- /dev/null +++ b/changelogs/fragments/7716.yml @@ -0,0 +1,2 @@ +feat: +- Display workspace picker content when outside workspace ([#7716](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7716)) \ No newline at end of file diff --git a/changelogs/fragments/7721.yml b/changelogs/fragments/7721.yml new file mode 100644 index 000000000000..1963c6ea4755 --- /dev/null +++ b/changelogs/fragments/7721.yml @@ -0,0 +1,2 @@ +feat: +- Allow `screenTitle` to be present when SearchBar is not in Application header ([#7721](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7721)) \ No newline at end of file diff --git a/changelogs/fragments/7723.yml b/changelogs/fragments/7723.yml new file mode 100644 index 000000000000..6f38d9e804be --- /dev/null +++ b/changelogs/fragments/7723.yml @@ -0,0 +1,2 @@ +feat: +- Simplify `TopNavControlDescriptionData` to to be followed by links ([#7723](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7723)) \ No newline at end of file diff --git a/changelogs/fragments/7731.yml b/changelogs/fragments/7731.yml new file mode 100644 index 000000000000..cab14565e6b7 --- /dev/null +++ b/changelogs/fragments/7731.yml @@ -0,0 +1,2 @@ +refactor: +- Multi-datasources and multi-query languages features to use generic structured types, abstract data querying, and the language service ([#7731](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7731)) \ No newline at end of file diff --git a/changelogs/fragments/7744.yml b/changelogs/fragments/7744.yml new file mode 100644 index 000000000000..a9af55e1c43b --- /dev/null +++ b/changelogs/fragments/7744.yml @@ -0,0 +1,2 @@ +refactor: +- Update page header for settings, objects and index pattern page ([#7744](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7744)) \ No newline at end of file diff --git a/changelogs/fragments/7748.yml b/changelogs/fragments/7748.yml new file mode 100644 index 000000000000..a62a59be3178 --- /dev/null +++ b/changelogs/fragments/7748.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace]Fix click on workspace name not navigates to use case overview page ([#7748](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7748)) \ No newline at end of file diff --git a/changelogs/fragments/7749.yml b/changelogs/fragments/7749.yml new file mode 100644 index 000000000000..acc4198d0d4a --- /dev/null +++ b/changelogs/fragments/7749.yml @@ -0,0 +1,2 @@ +fix: +- Breadcrumb is not correct when clicking inspect / edit in Assets page ([#7749](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7749)) \ No newline at end of file diff --git a/changelogs/fragments/7750.yml b/changelogs/fragments/7750.yml new file mode 100644 index 000000000000..a9ec74c2cdc9 --- /dev/null +++ b/changelogs/fragments/7750.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace]Add right sidebar to workspace create form ([#7750](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7750)) \ No newline at end of file diff --git a/changelogs/fragments/7757.yml b/changelogs/fragments/7757.yml new file mode 100644 index 000000000000..0466de62dc13 --- /dev/null +++ b/changelogs/fragments/7757.yml @@ -0,0 +1,2 @@ +feat: +- Add v9 theme (preview) ([#7757](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7757)) \ No newline at end of file diff --git a/changelogs/fragments/7758.yml b/changelogs/fragments/7758.yml new file mode 100644 index 000000000000..ecc1f7b1defa --- /dev/null +++ b/changelogs/fragments/7758.yml @@ -0,0 +1,2 @@ +feat: +- Minor interface change and move suggestion provider registration location ([#7758](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7758)) \ No newline at end of file diff --git a/changelogs/fragments/7768.yml b/changelogs/fragments/7768.yml new file mode 100644 index 000000000000..ac0418eb42b0 --- /dev/null +++ b/changelogs/fragments/7768.yml @@ -0,0 +1,2 @@ +fix: +- Fix the parameter misalignment in the workspace_detail_page ([#7768](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7768)) \ No newline at end of file diff --git a/changelogs/fragments/7771.yml b/changelogs/fragments/7771.yml new file mode 100644 index 000000000000..1c9eccd32254 --- /dev/null +++ b/changelogs/fragments/7771.yml @@ -0,0 +1,2 @@ +refactor: +- [Workspace] Refactor: workspace detail page header ([#7771](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7771)) \ No newline at end of file diff --git a/changelogs/fragments/7777.yml b/changelogs/fragments/7777.yml new file mode 100644 index 000000000000..465c80e318c3 --- /dev/null +++ b/changelogs/fragments/7777.yml @@ -0,0 +1,2 @@ +refactor: +- [Look & Feel] Appearance Popover Button Change ([#7777](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7777)) \ No newline at end of file diff --git a/changelogs/fragments/7785.yml b/changelogs/fragments/7785.yml new file mode 100644 index 000000000000..219d33e5d831 --- /dev/null +++ b/changelogs/fragments/7785.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace] Add workspace navigation for default route ([#7785](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7785)) \ No newline at end of file diff --git a/changelogs/fragments/7795.yml b/changelogs/fragments/7795.yml new file mode 100644 index 000000000000..daad28b14214 --- /dev/null +++ b/changelogs/fragments/7795.yml @@ -0,0 +1,2 @@ +feat: +- Adjust the appearance of collaborator panel ([#7795](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7795)) \ No newline at end of file diff --git a/changelogs/fragments/7796.yml b/changelogs/fragments/7796.yml new file mode 100644 index 000000000000..1e17933facf9 --- /dev/null +++ b/changelogs/fragments/7796.yml @@ -0,0 +1,2 @@ +fix: +- Fix new header allowing their single-child's overflowing ([#7796](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7796)) \ No newline at end of file diff --git a/changelogs/fragments/7799.yml b/changelogs/fragments/7799.yml new file mode 100644 index 000000000000..15d74ab2efde --- /dev/null +++ b/changelogs/fragments/7799.yml @@ -0,0 +1,4 @@ +feat: +- Add external icon to `TopNavControlButtonData` and `TopNavControlLinkData` with `target: '_blank'` ([#7799](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7799)) +- Add `iconGap` to `TopNavControlButtonData` and `TopNavControlLinkData` ([#7799](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7799)) +- Bump OUI to 1.11.0 ([#7799](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7799)) \ No newline at end of file diff --git a/changelogs/fragments/7801.yml b/changelogs/fragments/7801.yml new file mode 100644 index 000000000000..c4e0b608d6f6 --- /dev/null +++ b/changelogs/fragments/7801.yml @@ -0,0 +1,2 @@ +feat: +- Add `flush` to `TopNavControlLinkData` ([#7801](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7801)) \ No newline at end of file diff --git a/changelogs/fragments/7802.yml b/changelogs/fragments/7802.yml new file mode 100644 index 000000000000..2937e55931ae --- /dev/null +++ b/changelogs/fragments/7802.yml @@ -0,0 +1,2 @@ +feat: +- Add home icon in left bottom ([#7802](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7802)) \ No newline at end of file diff --git a/changelogs/fragments/7804.yml b/changelogs/fragments/7804.yml new file mode 100644 index 000000000000..cda968bd641b --- /dev/null +++ b/changelogs/fragments/7804.yml @@ -0,0 +1,2 @@ +fix: +- Fix query assistant fetching agent bug ([#7804](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7804)) \ No newline at end of file diff --git a/changelogs/fragments/7810.yml b/changelogs/fragments/7810.yml new file mode 100644 index 000000000000..104d2d23e27c --- /dev/null +++ b/changelogs/fragments/7810.yml @@ -0,0 +1,2 @@ +feat: +- Add OpenSearch PPL autocomplete to discover 2.0 with query enhancements ([#7810](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7810)) \ No newline at end of file diff --git a/changelogs/fragments/7812.yml b/changelogs/fragments/7812.yml new file mode 100644 index 000000000000..153357ca4c58 --- /dev/null +++ b/changelogs/fragments/7812.yml @@ -0,0 +1,2 @@ +fix: +- Correct size of dashboard panel options icon button ([#7812](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7812)) \ No newline at end of file diff --git a/changelogs/fragments/7817.yml b/changelogs/fragments/7817.yml new file mode 100644 index 000000000000..1b2a2607f264 --- /dev/null +++ b/changelogs/fragments/7817.yml @@ -0,0 +1,2 @@ +fix: +- [Workspace] maximum call stack error in use case service ([#7817](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7817)) \ No newline at end of file diff --git a/changelogs/fragments/7841.yml b/changelogs/fragments/7841.yml new file mode 100644 index 000000000000..06fc241b193d --- /dev/null +++ b/changelogs/fragments/7841.yml @@ -0,0 +1,2 @@ +feat: +- [Workspace]Remove default appended features ([#7841](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7841)) \ No newline at end of file diff --git a/changelogs/fragments/7842.yml b/changelogs/fragments/7842.yml new file mode 100644 index 000000000000..210ae0638c64 --- /dev/null +++ b/changelogs/fragments/7842.yml @@ -0,0 +1,2 @@ +refactor: +- [Workspace] Use small button, small padding and compressed. ([#7842](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7842)) \ No newline at end of file diff --git a/changelogs/fragments/7857.yml b/changelogs/fragments/7857.yml new file mode 100644 index 000000000000..2e462a9cdac0 --- /dev/null +++ b/changelogs/fragments/7857.yml @@ -0,0 +1,2 @@ +refactor: +- [Workspace] workspace initial page ([#7857](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7857)) \ No newline at end of file diff --git a/changelogs/fragments/7858.yml b/changelogs/fragments/7858.yml new file mode 100644 index 000000000000..6145c80780a7 --- /dev/null +++ b/changelogs/fragments/7858.yml @@ -0,0 +1,2 @@ +fix: +- [Workspace] Revert new home page ui setting for workspace default route ([#7858](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7858)) \ No newline at end of file diff --git a/changelogs/fragments/7865.yml b/changelogs/fragments/7865.yml new file mode 100644 index 000000000000..13c993e56f94 --- /dev/null +++ b/changelogs/fragments/7865.yml @@ -0,0 +1,2 @@ +chore: +- Update oui to 1.12 ([#7865](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7865)) \ No newline at end of file diff --git a/changelogs/fragments/7866.yml b/changelogs/fragments/7866.yml new file mode 100644 index 000000000000..9cc5d4863d55 --- /dev/null +++ b/changelogs/fragments/7866.yml @@ -0,0 +1,2 @@ +feat: +- Query editor UI changes ([#7866](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7866)) \ No newline at end of file diff --git a/changelogs/fragments/7870.yml b/changelogs/fragments/7870.yml new file mode 100644 index 000000000000..cb0c33dfcb39 --- /dev/null +++ b/changelogs/fragments/7870.yml @@ -0,0 +1,2 @@ +fix: +- Clean up language search interceptors and fix aggs for PPL ([#7870](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7870)) \ No newline at end of file diff --git a/changelogs/fragments/7871.yml b/changelogs/fragments/7871.yml new file mode 100644 index 000000000000..f25eb51445d1 --- /dev/null +++ b/changelogs/fragments/7871.yml @@ -0,0 +1,2 @@ +feat: +- Support injecting `DataStructureMeta` from `QueryEditorExtensions` for Query Assist ([#7871](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7871)) \ No newline at end of file diff --git a/changelogs/fragments/7873.yml b/changelogs/fragments/7873.yml new file mode 100644 index 000000000000..ebf0b9e814ed --- /dev/null +++ b/changelogs/fragments/7873.yml @@ -0,0 +1,2 @@ +feat: +- Align essentials use case id ([#7873](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7873)) \ No newline at end of file diff --git a/changelogs/fragments/7879.yml b/changelogs/fragments/7879.yml new file mode 100644 index 000000000000..68a7f88ad045 --- /dev/null +++ b/changelogs/fragments/7879.yml @@ -0,0 +1,2 @@ +feat: +- Update the collaborator input from a combobox to a text field ([#7879](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7879)) \ No newline at end of file diff --git a/changelogs/fragments/7896.yml b/changelogs/fragments/7896.yml new file mode 100644 index 000000000000..cfac5d806ff4 --- /dev/null +++ b/changelogs/fragments/7896.yml @@ -0,0 +1,2 @@ +fix: +- Query editor UI clean up ([#7896](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7896)) \ No newline at end of file diff --git a/changelogs/fragments/7913.yml b/changelogs/fragments/7913.yml new file mode 100644 index 000000000000..1ab0ce89f7f1 --- /dev/null +++ b/changelogs/fragments/7913.yml @@ -0,0 +1,2 @@ +fix: +- Refactor the style for the work list table ([#7913](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7913)) \ No newline at end of file diff --git a/changelogs/fragments/7917.yml b/changelogs/fragments/7917.yml new file mode 100644 index 000000000000..60262ad04c44 --- /dev/null +++ b/changelogs/fragments/7917.yml @@ -0,0 +1,2 @@ +feat: +- Add S3 data exploration for connections, databases, and tables ([#7917](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7917)) \ No newline at end of file diff --git a/config/opensearch_dashboards.yml b/config/opensearch_dashboards.yml index 8dd9c3805b64..eb80d20854c4 100644 --- a/config/opensearch_dashboards.yml +++ b/config/opensearch_dashboards.yml @@ -88,6 +88,22 @@ # To disregard the validity of SSL certificates, change this setting's value to 'none'. #opensearch.ssl.verificationMode: full +# Allowed cipher suites list can be configured below, in preference order. +# Cipher suite names should be in OpenSSL format (https://www.openssl.org/docs/man1.1.1/man1/ciphers.html#CIPHER-SUITE-NAMES) +# Default set of cipher suites for your system can be checked by running the following command in your terminal: +# echo 'console.log(require("crypto").constants.defaultCoreCipherList.split(":"))' | node - +# Below you can find an example of cipher suites considered to be secure +# Keep in mind that older clients might not support them +# Refer to https://ciphersuite.info/ and https://ssl-config.mozilla.org/ for more details and recommendations about cipher suites +# Keep in mind that restricting cipher suites might disable older versions of TLS, as not all cipher suites are supported by them +#server.ssl.cipherSuites: +# - TLS_AES_256_GCM_SHA384 +# - TLS_CHACHA20_POLY1305_SHA256 +# - TLS_AES_128_GCM_SHA256 +# - ECDHE-ECDSA-AES256-GCM-SHA384 +# - ECDHE-ECDSA-CHACHA20-POLY1305 +# - ECDHE-ECDSA-AES128-GCM-SHA256 + # Time in milliseconds to wait for OpenSearch to respond to pings. Defaults to the value of # the opensearch.requestTimeout setting. #opensearch.pingTimeout: 1500 @@ -305,6 +321,12 @@ # AWSSigV4: # enabled: true +# Optional setting that controls the permissions of data source to create, update and delete. +# "none": The data source is readonly for all users. +# "dashboard_admin": The data source can only be managed by dashboard admin. +# "all": The data source can be managed by all users. Default to "all". +# data_source_management.manageableBy: "all" + # Set the value of this setting to false to hide the help menu link to the OpenSearch Dashboards user survey # opensearchDashboards.survey.url: "https://survey.opensearch.org" @@ -321,7 +343,11 @@ # Set the value to true to enable workspace feature # Please note, workspace will not work with multi-tenancy. To enable workspace feature, you need to disable multi-tenancy first with `opensearch_security.multitenancy.enabled: false` +# Please uncomment below uiSettings to enable new home/navigation experience when workspace is enabled # workspace.enabled: false +# uiSettings: +# overrides: +# "home:useNewHomePage": true # Optional settings to specify saved object types to be deleted during migration. # This feature can help address compatibility issues that may arise during the migration of saved objects, such as types defined by legacy applications. @@ -342,4 +368,4 @@ # Set the backend roles in groups or users, whoever has the backend roles or exactly match the user ids defined in this config will be regard as dashboard admin. # Dashboard admin will have the access to all the workspaces(workspace.enabled: true) and objects inside OpenSearch Dashboards. # opensearchDashboards.dashboardAdmin.groups: ["dashboard_admin"] -# opensearchDashboards.dashboardAdmin.users: ["dashboard_admin"] +# opensearchDashboards.dashboardAdmin.users: ["dashboard_admin"] \ No newline at end of file diff --git a/docs/openapi/README.md b/docs/openapi/README.md index a19b2a9a830a..92779f2220b1 100644 --- a/docs/openapi/README.md +++ b/docs/openapi/README.md @@ -6,4 +6,9 @@ The OpenAPI (https://swagger.io/specification/) Specification defines a standard When generated, OpenAPI definition can then be used by documentation generation tools to display the API such as swagger UI, code generation tools to generate servers and clients in various programming languages, testing tools, and many other use cases. ### Starting Up the Swagger UI Locally -To start up the swagger UI locally for development or validation purposes, you can simply start a server in the directory where the index.html file is located. `npx serve` is a simple way to start a server. \ No newline at end of file +To start up the swagger UI locally for development or validation purposes, you can simply start a server in the directory where the index.html file is located. `npx serve` is a simple way to start a server. + +### API Docs + +- [Saved Objects](https://opensearch-project.github.io/OpenSearch-Dashboards/docs/openapi/saved_objects/) +- [Index Patterns](https://opensearch-project.github.io/OpenSearch-Dashboards/docs/openapi/index_patterns/)https://github.com/ashwin-pc/OpenSearch-Dashboards/blob/26b8c38cf4b9f6cf9a809dad370d26cf37b72571/docs/openapi/README.md \ No newline at end of file diff --git a/docs/openapi/index_patterns/index.html b/docs/openapi/index_patterns/index.html new file mode 100644 index 000000000000..cdd9cff94d2a --- /dev/null +++ b/docs/openapi/index_patterns/index.html @@ -0,0 +1,29 @@ + + + + + + + + Index Patterns API + + + +
+ + + + + + \ No newline at end of file diff --git a/docs/openapi/index_patterns/index_patterns.yml b/docs/openapi/index_patterns/index_patterns.yml new file mode 100644 index 000000000000..7a20e5bf4862 --- /dev/null +++ b/docs/openapi/index_patterns/index_patterns.yml @@ -0,0 +1,83 @@ +openapi: 3.0.3 +info: + version: v1 + title: OpenSearch Dashboards Index Patterns API + contact: + name: OpenSearch Dashboards Team + description: |- + OpenAPI schema for OpenSearch Dashboards Index Patterns API +tags: + - name: index patterns +paths: + /api/index_patterns/_fields_for_wildcard: + get: + tags: + - index patterns + summary: + parameters: + - in: query + name: pattern + description: The index pattern used to retrieve fields. + required: true + schema: + type: string + example: my-index* + - in: query + name: meta_fields + description: The list of metadata fields which will be included in the response, it usually contains "_source", "_id", "_type", "_index" and "_score". + schema: + oneOf: + - type: string + - type: array + default: [] + example: _source + - in: query + name: data_source + description: The data source of index patterns and indices. + schema: + type: string + responses: + '200': + description: Fetching fields for index pattern is successful. + content: + application/json: + schema: + type: object + properties: + fields: + type: array + description: Retrieved fields based on wildcard pattern. + items: + type: object + '400': + description: Bad request + content: + application/json: + schema: + $ref: '#/components/schemas/400_bad_request' + '404': + description: Not found + content: + application/json: + schema: + type: object +components: + schemas: + 400_bad_request: + title: Bad request + type: object + required: + - error + - message + - statusCode + properties: + error: + type: string + enum: + - Bad Request + message: + type: string + statusCode: + type: integer + enum: + - 400 \ No newline at end of file diff --git a/docs/theme.md b/docs/theme.md index 293c65661c04..cf85667e1d2d 100644 --- a/docs/theme.md +++ b/docs/theme.md @@ -6,7 +6,7 @@ Themes are defined in OUI via https://github.com/opensearch-project/oui/blob/main/src/themes/themes.ts. When Building OUI, there are several theming artifacts generated (beyond the react components) for each mode (light/dark) of each theme: -1. Theme compiled stylesheets (e.g. `@elastic/eui/dist/eui_theme_dark.css`). Consumed as entry files in [/packages/osd-ui-shared-deps/webpack.config.js](/packages/osd-ui-shared-deps/webpack.config.js) and republished by `osd-ui-shared-deps` (e.g. [UiSharedDeps.darkCssDistFilename](/packages/osd-ui-shared-deps/index.js)). +1. Theme compiled stylesheets (e.g. `@elastic/eui/dist/eui_theme_dark.css`). Consumed as entry files in [/packages/osd-ui-shared-deps/webpack.config.js](/packages/osd-ui-shared-deps/webpack.config.js) and republished by `osd-ui-shared-deps` (e.g. [UiSharedDeps.themeCssDistFilenames](/packages/osd-ui-shared-deps/index.js)). 2. Theme compiled and minified stylesheets (e.g. `@elastic/eui/dist/eui_theme_dark.min.css`). These appear unused by OpenSearch Dashboards 3. Theme computed SASS variables as JSON (e.g. `@elastic/eui/dist/eui_theme_dark.json`). Consumed by [/packages/osd-ui-shared-deps/theme.ts](/packages/osd-ui-shared-deps/theme.ts) and made available to other components via the mode and theme aware `euiThemeVars`. In general, these should not be consumed by any other component directly. 4. Theme type definition file for SASS variables as JSON (e.g. `@elastic/eui/dist/eui_theme_dark.json.d.ts`) @@ -129,3 +129,38 @@ Component styles are not loaded as stylesheets. 4. Used by `src/core/server/rendering/views/theme.ts` to inject values into `src/core/server/rendering/views/styles.tsx` 5. Used (incorrectly) to style a badge color in `src/plugins/index_pattern_management/public/components/create_button/create_button.tsx` 6. Used by `src/plugins/opensearch_dashboards_react/public/code_editor/editor_theme.ts` to create Monaco theme styles + +## Theme Management + +### Change default theme + +Update `DEFAULT_THEME_VERSION` in `src/core/server/ui_settings/ui_settings_config.ts` to point to the desired theme version. + +### Adding a new theme + +1. Add a [a new theme to OUI](https://github.com/opensearch-project/oui/blob/main/wiki/theming.md) and publish new OUI version +2. Update OSD to consume new OUI version +3. Make the following changes in OSD: + 1. Load your theme by creating sass files in `src/core/public/core_app/styles` + 2. Update [webpack config](packages/osd-ui-shared-deps/webpack.config.js) to create css files for your theme + 2. Add kui css files: + 1. Create kui sass files for your theme in `packages/osd-ui-framework/src/` + 2. Update `packages/osd-ui-framework/Gruntfile.js` to build these files + 3. Generate the files by running `npx grunt compileCss` from this package root + 3. Add fonts to OSD: + 1. Make sure your theme fonts are in [/src/core/server/core_app/assets/fonts](/src/core/server/core_app/assets/fonts/readme.md) + 2. Update `src/core/server/rendering/views/fonts.tsx` to reference those files + 3. Update src/core/server/core_app/assets/fonts/readme.md to reference the fonts + 4. Update `packages/osd-ui-shared-deps/theme_config.js`: + 1. Add version and label for version to `THEME_VERSION_LABEL_MAP` + 2. Update `kuiCssDistFilenames` map for new theme + 3. Update `ThemeTag` type in corresponding definition file (`theme_config.d.ts`) + 5. Load variables for new theme in `packages/osd-ui-shared-deps/theme.ts'` + 6. Update `src/legacy/ui/ui_render/ui_render_mixin.js': + 1. Load variables for your theme in `THEME_SOURCES` + 2. Define the text font for your theme in `fontText` + 3. Define the code font for your theme in `fontCode` + 7. If on a branch without user specific themes: + 1. Update `THEME_SOURCES` in `src/core/server/rendering/views/theme.ts` + 2. Update `fontText` and `fontCode` in `src/core/server/rendering/views/fonts.tsx` + \ No newline at end of file diff --git a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx index dd81d612183a..46872c1f8e4f 100644 --- a/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx +++ b/examples/embeddable_examples/public/multi_task_todo/multi_task_todo_component.tsx @@ -72,6 +72,7 @@ function renderTasks(tasks: MultiTaskTodoInput['tasks'], search?: string) { key={task} data-test-subj="multiTaskTodoTask" label={wrapSearchTerms(task, search)} + size="s" /> )); } diff --git a/package.json b/package.json index 59b2f89d0198..56bef8bb1c94 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "dashboarding" ], "private": true, - "version": "2.15.0", + "version": "2.17.0", "branch": "2.x", "types": "./opensearch_dashboards.d.ts", "tsdocMetadata": "./build/tsdoc-metadata.json", @@ -63,6 +63,7 @@ "start": "scripts/use_node scripts/opensearch_dashboards --dev", "start:docker": "scripts/use_node scripts/opensearch_dashboards --dev --opensearch.hosts=$OPENSEARCH_HOSTS --opensearch.ignoreVersionMismatch=true --server.host=$SERVER_HOST", "start:security": "scripts/use_node scripts/opensearch_dashboards --dev --security", + "start:enhancements": "scripts/use_node scripts/opensearch_dashboards --dev --uiSettings.overrides['query:enhancements:enabled']=true --uiSettings.overrides['home:useNewHomePage']=true", "debug": "scripts/use_node --nolazy --inspect scripts/opensearch_dashboards --dev", "debug-break": "scripts/use_node --nolazy --inspect-brk scripts/opensearch_dashboards --dev", "lint": "yarn run lint:es && yarn run lint:style", @@ -76,17 +77,24 @@ "osd:bootstrap": "scripts/use_node scripts/build_ts_refs && scripts/use_node scripts/register_git_hook", "spec_to_console": "scripts/use_node scripts/spec_to_console", "pkg-version": "scripts/use_node -e \"console.log(require('./package.json').version)\"", - "release_note:generate": "scripts/use_node scripts/generate_release_note" + "release_note:generate": "scripts/use_node scripts/generate_release_note", + "cypress:run-without-security": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --headless --env SECURITY_ENABLED=false", + "cypress:run-with-security": "env TZ=America/Los_Angeles NO_COLOR=1 cypress run --headless --env SECURITY_ENABLED=true,openSearchUrl=https://localhost:9200,WAIT_FOR_LOADER_BUFFER_MS=500", + "osd:ciGroup10": "echo \"dashboard_sanity_test_spec.js\"", + "osd:ciGroup11": "echo \"apps/vis_builder/*.js\"", + "generate:opensearchsqlantlr": "./node_modules/antlr4ng-cli/index.js -Dlanguage=TypeScript -o ./src/plugins/data/public/antlr/opensearch_sql/.generated -visitor -no-listener -Xexact-output-dir ./src/plugins/data/public/antlr/opensearch_sql/grammar/OpenSearchSQLLexer.g4 ./src/plugins/data/public/antlr/opensearch_sql/grammar/OpenSearchSQLParser.g4", + "generate:opensearchpplantlr": "./node_modules/antlr4ng-cli/index.js -Dlanguage=TypeScript -o ./src/plugins/data/public/antlr/opensearch_ppl/.generated -visitor -no-listener -Xexact-output-dir ./src/plugins/data/public/antlr/opensearch_ppl/grammar/OpenSearchPPLLexer.g4 ./src/plugins/data/public/antlr/opensearch_ppl/grammar/OpenSearchPPLParser.g4" }, "repository": { "type": "git", "url": "https://github.com/opensearch-project/opensearch-dashboards.git" }, "resolutions": { - "**/@babel/traverse": "^7.23.2", + "**/@babel/traverse": "^7.25.0", "**/@types/node": "~18.7.0", "**/ansi-regex": "^5.0.1", "**/async": "^3.2.3", + "**/cpy/globby": "^10.0.1", "**/d3-color": "^3.1.0", "**/flat": "^5.0.2", "**/elasticsearch/agentkeepalive": "^4.5.0", @@ -97,24 +105,26 @@ "**/jest-config": "npm:@amoo-miki/jest-config@27.5.1", "**/jest-jasmine2": "npm:@amoo-miki/jest-jasmine2@27.5.1", "**/joi/hoek": "npm:@amoo-miki/hoek@6.1.3", + "**/json11": "^1.1.2", "**/json-schema": "^0.4.0", "**/kind-of": ">=6.0.3", + "**/load-bmfont/phin": "^3.7.1", "**/loader-utils": "^2.0.4", "**/node-jose": "^2.2.0", "**/nth-check": "^2.0.1", "**/qs": "^6.11.0", "**/semver": "^7.5.3", "**/set-value": "^4.1.0", - "**/tar":"^6.2.1", + "**/tar": "^6.2.1", "**/topo/hoek": "npm:@amoo-miki/hoek@6.1.3", "**/trim": "^0.0.3", "**/typescript": "4.0.2", "**/unset-value": "^2.0.1", + "**/watchpack-chokidar2/chokidar": "^3.5.3", "**/minimatch": "^3.0.5", "**/eslint-plugin-mocha-next/mocha": "npm:mocha@^10.1.0", "**/xml2js": "^0.5.0", "**/yaml": "^2.2.2" - }, "workspaces": { "packages": [ @@ -136,7 +146,7 @@ "dependencies": { "@aws-crypto/client-node": "^3.1.1", "@elastic/datemath": "5.0.3", - "@elastic/eui": "npm:@opensearch-project/oui@1.5.1", + "@elastic/eui": "npm:@opensearch-project/oui@1.12.0", "@elastic/good": "^9.0.1-kibana3", "@elastic/numeral": "npm:@amoo-miki/numeral@2.6.0", "@elastic/request-crypto": "2.0.0", @@ -153,7 +163,7 @@ "@hapi/vision": "^6.1.0", "@hapi/wreck": "^17.1.0", "@opensearch-project/opensearch": "^1.1.0", - "@opensearch-project/opensearch-next": "npm:@opensearch-project/opensearch@^2.6.0", + "@opensearch-project/opensearch-next": "npm:@opensearch-project/opensearch@^2.9.0", "@opensearch/datemath": "5.0.3", "@osd/ace": "1.0.0", "@osd/analytics": "1.0.0", @@ -173,6 +183,8 @@ "JSONStream": "1.3.5", "abortcontroller-polyfill": "^1.4.0", "ajv": "^8.11.0", + "antlr4-c3": "^3.4.1", + "antlr4ng": "^3.0.4", "bluebird": "3.5.5", "chalk": "^4.1.0", "chokidar": "^3.4.2", @@ -238,12 +250,13 @@ "type-detect": "^4.0.8", "uuid": "3.3.2", "whatwg-fetch": "^3.0.0", - "yauzl": "^2.10.0" - + "yauzl": "^2.10.0", + "@opensearch-dashboards-test/opensearch-dashboards-test-library": "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.6.tar.gz" }, "devDependencies": { "@babel/core": "^7.22.9", "@babel/parser": "^7.22.9", + "@babel/plugin-transform-class-static-block": "^7.24.7", "@babel/register": "^7.22.9", "@babel/types": "^7.22.9", "@elastic/apm-rum": "^5.6.1", @@ -296,7 +309,6 @@ "@types/getopts": "^2.0.1", "@types/getos": "^3.0.0", "@types/glob": "^7.1.3", - "@types/globby": "^8.0.0", "@types/hapi__cookie": "^10.1.4", "@types/hapi__h2o2": "^8.3.3", "@types/hapi__hapi": "^20.0.10", @@ -359,6 +371,7 @@ "@types/zen-observable": "^0.8.0", "@typescript-eslint/eslint-plugin": "^3.10.0", "@typescript-eslint/parser": "^3.10.0", + "antlr4ng-cli": "^2.0.0", "archiver": "^5.3.0", "axe-core": "^4.0.2", "babel-eslint": "^10.0.3", @@ -414,10 +427,10 @@ "jest": "^27.5.1", "jest-canvas-mock": "^2.5.1", "jest-raw-loader": "^1.0.1", - "jimp": "^0.14.0", + "jimp": "^0.22.12", "jquery": "^3.5.0", "json-stringify-pretty-compact": "1.2.0", - "json5": "^1.0.1", + "json5": "^2.2.3", "leaflet": "1.5.1", "leaflet-draw": "0.4.14", "leaflet-responsive-popup": "0.6.4", @@ -459,6 +472,7 @@ "regenerate": "^1.4.0", "reselect": "^4.0.0", "resize-observer-polyfill": "^1.5.1", + "rimraf": "^5.0.7", "selenium-webdriver": "^4.0.0-alpha.7", "simple-git": "^3.16.0", "sinon": "^7.4.2", diff --git a/packages/osd-ace/package.json b/packages/osd-ace/package.json index ae6c3126e99f..0778cbc40a12 100644 --- a/packages/osd-ace/package.json +++ b/packages/osd-ace/package.json @@ -14,7 +14,7 @@ "@osd/babel-preset": "1.0.0", "raw-loader": "^4.0.2", "typescript": "4.0.2", - "webpack": "npm:@amoo-miki/webpack@4.46.0-rc.2" + "webpack": "npm:@amoo-miki/webpack@4.46.0-xxhash.1" }, "@osd/pm": { "web": true diff --git a/packages/osd-babel-preset/webpack_preset.js b/packages/osd-babel-preset/webpack_preset.js index 9d0a75a042c5..62b764f82941 100644 --- a/packages/osd-babel-preset/webpack_preset.js +++ b/packages/osd-babel-preset/webpack_preset.js @@ -55,6 +55,12 @@ module.exports = () => { fileName: false, }, ], + [ + require.resolve('@babel/plugin-transform-class-static-block'), + { + fileName: false, + }, + ], ], env: { production: { diff --git a/packages/osd-eslint-import-resolver-opensearch-dashboards/package.json b/packages/osd-eslint-import-resolver-opensearch-dashboards/package.json index 2dfa00a516ba..fe6b8a19bb73 100755 --- a/packages/osd-eslint-import-resolver-opensearch-dashboards/package.json +++ b/packages/osd-eslint-import-resolver-opensearch-dashboards/package.json @@ -19,6 +19,6 @@ "glob-all": "^3.2.1", "lru-cache": "^4.1.5", "resolve": "^1.7.1", - "webpack": "npm:@amoo-miki/webpack@4.46.0-rc.2" + "webpack": "npm:@amoo-miki/webpack@4.46.0-xxhash.1" } } diff --git a/packages/osd-eslint-plugin-eslint/package.json b/packages/osd-eslint-plugin-eslint/package.json index 0e82fed704c6..85d21d02de14 100644 --- a/packages/osd-eslint-plugin-eslint/package.json +++ b/packages/osd-eslint-plugin-eslint/package.json @@ -11,7 +11,7 @@ "babel-eslint": "^10.0.3" }, "dependencies": { - "micromatch": "3.1.10", + "micromatch": "^4.0.7", "dedent": "^0.7.0", "eslint-module-utils": "2.5.0" } diff --git a/packages/osd-eslint-plugin-eslint/rules/no_restricted_paths.test.js b/packages/osd-eslint-plugin-eslint/rules/no_restricted_paths.test.js index 96251e8fe55c..0cc452043b45 100644 --- a/packages/osd-eslint-plugin-eslint/rules/no_restricted_paths.test.js +++ b/packages/osd-eslint-plugin-eslint/rules/no_restricted_paths.test.js @@ -370,7 +370,7 @@ ruleTester.run('@osd/eslint/no-restricted-paths', rule, { }, { - // Does not allow to import deeply within Core, using "src/core/..." Webpack alias. + // Does not allow to require deeply within Core, using "src/core/..." Webpack alias. code: 'const d = require("src/core/server/saved_objects")', filename: path.join(__dirname, './testfiles/no_restricted_paths/client/a.js'), options: [ @@ -393,6 +393,32 @@ ruleTester.run('@osd/eslint/no-restricted-paths', rule, { ], }, + { + // Does not allow to import deeply within Core, using "src/core/...". + code: ` + import { X as XX } from 'src/core/public'; + import { X as XY } from 'src/core/server';`, + filename: path.join(__dirname, './testfiles/no_restricted_paths/client/a.js'), + options: [ + { + basePath: __dirname, + zones: [ + { + target: ['**/testfiles/**/*', '!**/testfiles/**/server/**/*'], + from: ['src/core/server', 'src/core/server/**/*'], + }, + ], + }, + ], + errors: [ + { + message: 'Unexpected path "src/core/server" imported in restricted zone.', + line: 3, + column: 31, + }, + ], + }, + { // Does not allow to import "ui/kfetch". code: 'const d = require("ui/kfetch")', diff --git a/packages/osd-i18n/src/core/i18n.test.ts b/packages/osd-i18n/src/core/i18n.test.ts index 0ee114c78c95..ebfd546f8561 100644 --- a/packages/osd-i18n/src/core/i18n.test.ts +++ b/packages/osd-i18n/src/core/i18n.test.ts @@ -899,8 +899,17 @@ describe('I18n engine', () => { describe('load', () => { let mockFetch: jest.SpyInstance; + let originalWindow: any; + beforeEach(() => { mockFetch = jest.spyOn(global as any, 'fetch').mockImplementation(); + originalWindow = global.window; + global.window = { ...originalWindow }; + }); + + afterEach(() => { + global.window = originalWindow; + delete (window as any).__i18nWarning; // Clear the warning after each test }); test('fails if server returns >= 300 status code', async () => { @@ -928,7 +937,7 @@ describe('I18n engine', () => { mockFetch.mockResolvedValue({ status: 200, - json: jest.fn().mockResolvedValue(translations), + json: jest.fn().mockResolvedValue({ translations }), }); await expect(i18n.load('some-url')).resolves.toBeUndefined(); @@ -938,5 +947,28 @@ describe('I18n engine', () => { expect(i18n.getTranslation()).toEqual(translations); }); + + test('sets warning on window when present in response', async () => { + const warning = { title: 'Warning', text: 'This is a warning' }; + mockFetch.mockResolvedValue({ + status: 200, + json: jest.fn().mockResolvedValue({ translations: { locale: 'en' }, warning }), + }); + + await i18n.load('some-url'); + + expect((window as any).__i18nWarning).toEqual(warning); + }); + + test('does not set warning on window when not present in response', async () => { + mockFetch.mockResolvedValue({ + status: 200, + json: jest.fn().mockResolvedValue({ translations: { locale: 'en' } }), + }); + + await i18n.load('some-url'); + + expect((window as any).__i18nWarning).toBeUndefined(); + }); }); }); diff --git a/packages/osd-i18n/src/core/i18n.ts b/packages/osd-i18n/src/core/i18n.ts index 3268fae5079f..65da4931ef13 100644 --- a/packages/osd-i18n/src/core/i18n.ts +++ b/packages/osd-i18n/src/core/i18n.ts @@ -261,5 +261,12 @@ export async function load(translationsUrl: string) { throw new Error(`Translations request failed with status code: ${response.status}`); } - init(await response.json()); + const data = await response.json(); + + if (data.warning) { + // Store the warning to be displayed after core system setup + (window as any).__i18nWarning = data.warning; + } + + init(data.translations); } diff --git a/packages/osd-i18n/src/core/locales.js b/packages/osd-i18n/src/core/locales.js index a837462b7408..14dfe95507a9 100644 --- a/packages/osd-i18n/src/core/locales.js +++ b/packages/osd-i18n/src/core/locales.js @@ -21,6 +21,8 @@ function addLocaleData(localeData) { IntlRelativeFormat.__addLocaleData(localeData); } +addLocaleData({ locale: "pl", pluralRuleFunction: function (n, ord) { var s = String(n).split("."), v0 = !s[1], t0 = Number(s[0]) == n, n10 = t0 && s[0].slice(-1), n100 = t0 && s[0].slice(-2); if (ord) return n10 == 1 && n100 != 11 ? "jeden" : n10 == 2 && n100 != 12 ? "dwa" : n10 == 3 && n100 != 13 ? "kilka" : "inny"; return n == 1 && v0 ? "jeden" : "inny" }, "fields": { "rok": { "displayName": "rok", "relative": { "0": "ten rok", "1": "następny rok", "-1": "poprzedni rok" }, "relativeTime": { "future": { "jeden": "za {0} rok", "other": "za {0} lat" }, "past": { "jeden": "{0} rok temu", "other": "{0} lata temu" } } }, "year-short": { "displayName": "rok", "relative": { "0": "ten rok", "1": "następny rok", "-1": "poprzedni rok" }, "relativeTime": { "future": { "one": "za {0} r.", "other": "za {0} l." }, "past": { "one": "{0} r. temu", "other": "{0} l. temu" } } }, "month": { "displayName": "miesiąc", "relative": { "0": "ten miesiąc", "1": "następny miesiąc", "-1": "poprzedni miesiąc" }, "relativeTime": { "future": { "one": "w {0} miesiącu", "other": "w {0} miesiącach" }, "past": { "one": "{0} miesiąc temu", "other": "{0} miesięcy temu" } } }, "month-short": { "displayName": "miesiąc", "relative": { "0": "ten miesiąc", "1": "następny miesiąc", "-1": "poprzedni miesiąc" }, "relativeTime": { "future": { "one": "z {0} miesiąc", "other": "za {0} miesięcy" }, "past": { "one": "{0} miesiąc temu", "other": "{0} miesięcy temu" } } }, "day": { "displayName": "dzień", "relative": { "0": "dzisiaj", "1": "jutro", "-1": "wczoraj" }, "relativeTime": { "future": { "one": "za {0} dzień", "other": "za {0} dni" }, "past": { "one": "{0} dzień temu", "other": "{0} dni temu" } } }, "day-short": { "displayName": "dzień", "relative": { "0": "dzisiaj", "1": "jutro", "-1": "wczoraj" }, "relativeTime": { "future": { "one": "za {0} dzień", "other": "za {0} dni" }, "past": { "one": "{0} dzień temu", "other": "{0} dni temu" } } }, "hour": { "displayName": "godzina", "relative": { "0": "ta godzina" }, "relativeTime": { "future": { "one": "za {0} godzinę", "other": "za {0} godzin" }, "past": { "one": "{0} godzina temu", "other": "{0} godzin temu" } } }, "hour-short": { "displayName": "godz.", "relative": { "0": "ta godzina" }, "relativeTime": { "future": { "one": "za {0} godz.", "other": "za {0} godz." }, "past": { "one": "{0} godz. temu", "other": "{0} godz. temu" } } }, "minute": { "displayName": "minuta", "relative": { "0": "ta minuta" }, "relativeTime": { "future": { "one": "za {0} minutę", "other": "za {0} minut" }, "past": { "one": "{0} minuta temu", "other": "{0} minut temu" } } }, "minute-short": { "displayName": "min.", "relative": { "0": "ta min." }, "relativeTime": { "future": { "one": "za {0} min.", "other": "za {0} min." }, "past": { "one": "{0} min. temu", "other": "{0} min. temu" } } }, "second": { "displayName": "sekunda", "relative": { "0": "teraz" }, "relativeTime": { "future": { "one": "za {0} sekundę", "other": "za {0} sekund" }, "past": { "one": "{0} sekundę temu", "other": "{0} sekund temu" } } }, "second-short": { "displayName": "sek.", "relative": { "0": "teraz" }, "relativeTime": { "future": { "one": "za {0} sek.", "other": "za {0} sek." }, "past": { "one": "{0} sek. temu", "other": "{0} sek. temu" } } } } }); +addLocaleData({ locale: "pl-PL", parentLocale: "pl" }); addLocaleData({ locale: "en", pluralRuleFunction: function (n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return n10==1&&n100!=11?"one":n10==2&&n100!=12?"two":n10==3&&n100!=13?"few":"other";return n==1&&v0?"one":"other"},"fields":{"year":{"displayName":"year","relative":{"0":"this year","1":"next year","-1":"last year"},"relativeTime":{"future":{"one":"in {0} year","other":"in {0} years"},"past":{"one":"{0} year ago","other":"{0} years ago"}}},"year-short":{"displayName":"yr.","relative":{"0":"this yr.","1":"next yr.","-1":"last yr."},"relativeTime":{"future":{"one":"in {0} yr.","other":"in {0} yr."},"past":{"one":"{0} yr. ago","other":"{0} yr. ago"}}},"month":{"displayName":"month","relative":{"0":"this month","1":"next month","-1":"last month"},"relativeTime":{"future":{"one":"in {0} month","other":"in {0} months"},"past":{"one":"{0} month ago","other":"{0} months ago"}}},"month-short":{"displayName":"mo.","relative":{"0":"this mo.","1":"next mo.","-1":"last mo."},"relativeTime":{"future":{"one":"in {0} mo.","other":"in {0} mo."},"past":{"one":"{0} mo. ago","other":"{0} mo. ago"}}},"day":{"displayName":"day","relative":{"0":"today","1":"tomorrow","-1":"yesterday"},"relativeTime":{"future":{"one":"in {0} day","other":"in {0} days"},"past":{"one":"{0} day ago","other":"{0} days ago"}}},"day-short":{"displayName":"day","relative":{"0":"today","1":"tomorrow","-1":"yesterday"},"relativeTime":{"future":{"one":"in {0} day","other":"in {0} days"},"past":{"one":"{0} day ago","other":"{0} days ago"}}},"hour":{"displayName":"hour","relative":{"0":"this hour"},"relativeTime":{"future":{"one":"in {0} hour","other":"in {0} hours"},"past":{"one":"{0} hour ago","other":"{0} hours ago"}}},"hour-short":{"displayName":"hr.","relative":{"0":"this hour"},"relativeTime":{"future":{"one":"in {0} hr.","other":"in {0} hr."},"past":{"one":"{0} hr. ago","other":"{0} hr. ago"}}},"minute":{"displayName":"minute","relative":{"0":"this minute"},"relativeTime":{"future":{"one":"in {0} minute","other":"in {0} minutes"},"past":{"one":"{0} minute ago","other":"{0} minutes ago"}}},"minute-short":{"displayName":"min.","relative":{"0":"this minute"},"relativeTime":{"future":{"one":"in {0} min.","other":"in {0} min."},"past":{"one":"{0} min. ago","other":"{0} min. ago"}}},"second":{"displayName":"second","relative":{"0":"now"},"relativeTime":{"future":{"one":"in {0} second","other":"in {0} seconds"},"past":{"one":"{0} second ago","other":"{0} seconds ago"}}},"second-short":{"displayName":"sec.","relative":{"0":"now"},"relativeTime":{"future":{"one":"in {0} sec.","other":"in {0} sec."},"past":{"one":"{0} sec. ago","other":"{0} sec. ago"}}}} }); addLocaleData({ locale: "en-US", parentLocale: "en" }); addLocaleData({ locale: "en-xa", pluralRuleFunction: function (n,ord){var s=String(n).split("."),v0=!s[1],t0=Number(s[0])==n,n10=t0&&s[0].slice(-1),n100=t0&&s[0].slice(-2);if(ord)return n10==1&&n100!=11?"one":n10==2&&n100!=12?"two":n10==3&&n100!=13?"few":"other";return n==1&&v0?"one":"other"}, "fields":{"year":{"displayName":"ýéààŕ","relative":{"0":"ţĥîîš ýééàŕ","1":"ñéẋẋţ ýééàŕ","-1":"ļàššţ ýééàŕ"},"relativeTime":{"future":{"one":"îñ {0} ýýéàŕŕ","other":"îñ {0} ýýéàŕŕš"},"past":{"one":"{0} ýéààŕ àĝĝô","other":"{0} ýéààŕš ààĝô"}}},"year-short":{"displayName":"ýŕ.","relative":{"0":"ţĥîîš ýŕŕ.","1":"ñéẋẋţ ýŕŕ.","-1":"ļàššţ ýŕŕ."},"relativeTime":{"future":{"one":"îñ {0} ýýŕ.","other":"îñ {0} ýýŕ."},"past":{"one":"{0} ýŕ. ààĝô","other":"{0} ýŕ. ààĝô"}}},"month":{"displayName":"ɱôññţĥ","relative":{"0":"ţĥîîš ɱôôñţĥĥ","1":"ñéẋẋţ ɱôôñţĥĥ","-1":"ļàššţ ɱôôñţĥĥ"},"relativeTime":{"future":{"one":"îñ {0} ɱɱôñţţĥ","other":"îñ {0} ɱɱôñţţĥš"},"past":{"one":"{0} ɱôññţĥ ààĝô","other":"{0} ɱôññţĥšš àĝôô"}}},"month-short":{"displayName":"ɱô.","relative":{"0":"ţĥîîš ɱôô.","1":"ñéẋẋţ ɱôô.","-1":"ļàššţ ɱôô."},"relativeTime":{"future":{"one":"îñ {0} ɱɱô.","other":"îñ {0} ɱɱô."},"past":{"one":"{0} ɱô. ààĝô","other":"{0} ɱô. ààĝô"}}},"day":{"displayName":"ðàýý","relative":{"0":"ţôððàý","1":"ţôɱɱôŕŕŕôŵ","-1":"ýéššţéŕŕðàýý"},"relativeTime":{"future":{"one":"îñ {0} ððàý","other":"îñ {0} ððàýšš"},"past":{"one":"{0} ðàýý àĝôô","other":"{0} ðàýýš àĝĝô"}}},"day-short":{"displayName":"ðàýý","relative":{"0":"ţôððàý","1":"ţôɱɱôŕŕŕôŵ","-1":"ýéššţéŕŕðàýý"},"relativeTime":{"future":{"one":"îñ {0} ððàý","other":"îñ {0} ððàýšš"},"past":{"one":"{0} ðàýý àĝôô","other":"{0} ðàýýš àĝĝô"}}},"hour":{"displayName":"ĥôûûŕ","relative":{"0":"ţĥîîš ĥôôûŕ"},"relativeTime":{"future":{"one":"îñ {0} ĥĥôûŕŕ","other":"îñ {0} ĥĥôûŕŕš"},"past":{"one":"{0} ĥôûûŕ àĝĝô","other":"{0} ĥôûûŕš ààĝô"}}},"hour-short":{"displayName":"ĥŕ.","relative":{"0":"ţĥîîš ĥôôûŕ"},"relativeTime":{"future":{"one":"îñ {0} ĥĥŕ.","other":"îñ {0} ĥĥŕ."},"past":{"one":"{0} ĥŕ. ààĝô","other":"{0} ĥŕ. ààĝô"}}},"minute":{"displayName":"ɱîññûţéé","relative":{"0":"ţĥîîš ɱîîñûţţé"},"relativeTime":{"future":{"one":"îñ {0} ɱɱîñûûţé","other":"îñ {0} ɱɱîñûûţéšš"},"past":{"one":"{0} ɱîññûţéé àĝôô","other":"{0} ɱîññûţééš àĝĝô"}}},"minute-short":{"displayName":"ɱîññ.","relative":{"0":"ţĥîîš ɱîîñûţţé"},"relativeTime":{"future":{"one":"îñ {0} ɱɱîñ.","other":"îñ {0} ɱɱîñ."},"past":{"one":"{0} ɱîññ. àĝôô","other":"{0} ɱîññ. àĝôô"}}},"second":{"displayName":"šéççôñðð","relative":{"0":"ñôŵŵ"},"relativeTime":{"future":{"one":"îñ {0} ššéçôôñð","other":"îñ {0} ššéçôôñðšš"},"past":{"one":"{0} šéççôñðð àĝôô","other":"{0} šéççôñððš àĝĝô"}}},"second-short":{"displayName":"šéçç.","relative":{"0":"ñôŵŵ"},"relativeTime":{"future":{"one":"îñ {0} ššéç.","other":"îñ {0} ššéç."},"past":{"one":"{0} šéçç. àĝôô","other":"{0} šéçç. àĝôô"}}}} }); diff --git a/packages/osd-interpreter/package.json b/packages/osd-interpreter/package.json index 34bcad460244..4bf4b61d9835 100644 --- a/packages/osd-interpreter/package.json +++ b/packages/osd-interpreter/package.json @@ -31,7 +31,7 @@ "style-loader": "^1.1.3", "supports-color": "^7.0.0", "url-loader": "^2.2.0", - "webpack": "npm:@amoo-miki/webpack@4.46.0-rc.2", + "webpack": "npm:@amoo-miki/webpack@4.46.0-xxhash.1", "webpack-cli": "^4.9.2" } } diff --git a/packages/osd-monaco/package.json b/packages/osd-monaco/package.json index ae9cfaddc53a..0c9a459d129b 100644 --- a/packages/osd-monaco/package.json +++ b/packages/osd-monaco/package.json @@ -22,7 +22,7 @@ "raw-loader": "^4.0.2", "supports-color": "^7.0.0", "typescript": "4.0.2", - "webpack": "npm:@amoo-miki/webpack@4.46.0-rc.2", + "webpack": "npm:@amoo-miki/webpack@4.46.0-xxhash.1", "webpack-cli": "^4.9.2" } } diff --git a/packages/osd-monaco/src/xjson/lexer_rules/index.ts b/packages/osd-monaco/src/xjson/lexer_rules/index.ts index b0ed41a63801..1e836eb4fed7 100644 --- a/packages/osd-monaco/src/xjson/lexer_rules/index.ts +++ b/packages/osd-monaco/src/xjson/lexer_rules/index.ts @@ -33,6 +33,8 @@ import * as monaco from 'monaco-editor/esm/vs/editor/editor.api'; import * as xJson from './xjson'; import * as opensearchql from './opensearchql'; import * as painless from './painless'; +import * as opensearchsql from './opensearchsql'; +import * as opensearchppl from './opensearchppl'; export const registerLexerRules = (m: typeof monaco) => { m.languages.register({ id: xJson.ID }); @@ -41,4 +43,9 @@ export const registerLexerRules = (m: typeof monaco) => { m.languages.setMonarchTokensProvider(painless.ID, painless.lexerRules); m.languages.register({ id: opensearchql.ID }); m.languages.setMonarchTokensProvider(opensearchql.ID, opensearchql.lexerRules); + m.languages.register({ id: opensearchsql.ID }); + m.languages.setMonarchTokensProvider(opensearchsql.ID, opensearchsql.lexerRules); + m.languages.register({ id: opensearchppl.ID }); + m.languages.setMonarchTokensProvider(opensearchppl.ID, opensearchppl.lexerRules); + m.languages.register({ id: 'kuery' }); }; diff --git a/packages/osd-monaco/src/xjson/lexer_rules/opensearchppl.ts b/packages/osd-monaco/src/xjson/lexer_rules/opensearchppl.ts new file mode 100644 index 000000000000..f303c8fa9e77 --- /dev/null +++ b/packages/osd-monaco/src/xjson/lexer_rules/opensearchppl.ts @@ -0,0 +1,529 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { monaco } from '../../monaco'; + +export const ID = 'PPL'; + +const brackets = [ + { open: '[', close: ']', token: 'delimiter.square' }, + { open: '(', close: ')', token: 'delimiter.parenthesis' }, +]; + +const keywords = [ + // COMMAND KEYWORDS + 'SEARCH', + 'DESCRIBE', + 'SHOW', + 'FROM', + 'WHERE', + 'FIELDS', + 'RENAME', + 'STATS', + 'DEDUP', + 'SORT', + 'EVAL', + 'HEAD', + 'TOP', + 'RARE', + 'PARSE', + 'METHOD', + 'REGEX', + 'PUNCT', + 'GROK', + 'PATTERN', + 'PATTERNS', + 'NEW_FIELD', + 'KMEANS', + 'AD', + 'ML', + + // COMMAND ASSIST KEYWORDS + 'AS', + 'BY', + 'SOURCE', + 'INDEX', + 'D', + 'DESC', + 'DATASOURCES', + + // CLAUSE KEYWORDS + 'SORTBY', + + // FIELD KEYWORDS + 'AUTO', + 'STR', + 'IP', + 'NUM', + + // ARGUMENT KEYWORDS + 'KEEPEMPTY', + 'CONSECUTIVE', + 'DEDUP_SPLITVALUES', + 'PARTITIONS', + 'ALLNUM', + 'DELIM', + 'CENTROIDS', + 'ITERATIONS', + 'DISTANCE_TYPE', + 'NUMBER_OF_TREES', + 'SHINGLE_SIZE', + 'SAMPLE_SIZE', + 'OUTPUT_AFTER', + 'TIME_DECAY', + 'ANOMALY_RATE', + 'CATEGORY_FIELD', + 'TIME_FIELD', + 'TIME_ZONE', + 'TRAINING_DATA_SIZE', + 'ANOMALY_SCORE_THRESHOLD', + + // COMPARISON FUNCTION KEYWORDS + 'CASE', + 'IN', + + // LOGICAL KEYWORDS + 'NOT', + 'OR', + 'AND', + 'XOR', + 'TRUE', + 'FALSE', + 'REGEXP', + + // DATETIME, INTERVAL AND UNIT KEYWORDS + 'CONVERT_TZ', + 'DATETIME', + 'DAY', + 'DAY_HOUR', + 'DAY_MICROSECOND', + 'DAY_MINUTE', + 'DAY_OF_YEAR', + 'DAY_SECOND', + 'HOUR', + 'HOUR_MICROSECOND', + 'HOUR_MINUTE', + 'HOUR_OF_DAY', + 'HOUR_SECOND', + 'INTERVAL', + 'MICROSECOND', + 'MILLISECOND', + 'MINUTE', + 'MINUTE_MICROSECOND', + 'MINUTE_OF_DAY', + 'MINUTE_OF_HOUR', + 'MINUTE_SECOND', + 'MONTH', + 'MONTH_OF_YEAR', + 'QUARTER', + 'SECOND', + 'SECOND_MICROSECOND', + 'SECOND_OF_MINUTE', + 'WEEK', + 'WEEK_OF_YEAR', + 'YEAR', + 'YEAR_MONTH', + + // DATASET TYPES + 'DATAMODEL', + 'LOOKUP', + 'SAVEDSEARCH', + + // CONVERTED DATA TYPES + 'INT', + 'INTEGER', + 'DOUBLE', + 'LONG', + 'FLOAT', + 'STRING', + 'BOOLEAN', + + // SPECIAL CHARACTERS AND OPERATORS + 'PIPE', + 'COMMA', + 'DOT', + 'EQUAL', + 'GREATER', + 'LESS', + 'NOT_GREATER', + 'NOT_LESS', + 'NOT_EQUAL', + 'PLUS', + 'MINUS', + 'STAR', + 'DIVIDE', + 'MODULE', + 'EXCLAMATION_SYMBOL', + 'COLON', + 'LT_PRTHS', + 'RT_PRTHS', + 'LT_SQR_PRTHS', + 'RT_SQR_PRTHS', + 'SINGLE_QUOTE', + 'DOUBLE_QUOTE', + 'BACKTICK', + + // AGGREGATIONS + 'AVG', + 'COUNT', + 'DISTINCT_COUNT', + 'ESTDC', + 'ESTDC_ERROR', + 'MAX', + 'MEAN', + 'MEDIAN', + 'MIN', + 'MODE', + 'RANGE', + 'STDEV', + 'STDEVP', + 'SUM', + 'SUMSQ', + 'VAR_SAMP', + 'VAR_POP', + 'STDDEV_SAMP', + 'STDDEV_POP', + 'PERCENTILE', + 'TAKE', + 'FIRST', + 'LAST', + 'LIST', + 'VALUES', + 'EARLIEST', + 'EARLIEST_TIME', + 'LATEST', + 'LATEST_TIME', + 'PER_DAY', + 'PER_HOUR', + 'PER_MINUTE', + 'PER_SECOND', + 'RATE', + 'SPARKLINE', + 'C', + 'DC', + + // BASIC FUNCTIONS + 'ABS', + 'CBRT', + 'CEIL', + 'CEILING', + 'CONV', + 'CRC32', + 'E', + 'EXP', + 'FLOOR', + 'LN', + 'LOG', + 'LOG10', + 'LOG2', + 'MOD', + 'PI', + 'POSITION', + 'POW', + 'POWER', + 'RAND', + 'ROUND', + 'SIGN', + 'SQRT', + 'TRUNCATE', + + // TRIGONOMETRIC FUNCTIONS + 'ACOS', + 'ASIN', + 'ATAN', + 'ATAN2', + 'COS', + 'COT', + 'DEGREES', + 'RADIANS', + 'SIN', + 'TAN', + + // DATE AND TIME FUNCTIONS + 'ADDDATE', + 'ADDTIME', + 'CURDATE', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURTIME', + 'DATE', + 'DATEDIFF', + 'DATE_ADD', + 'DATE_FORMAT', + 'DATE_SUB', + 'DAYNAME', + 'DAYOFMONTH', + 'DAYOFWEEK', + 'DAYOFYEAR', + 'DAY_OF_MONTH', + 'DAY_OF_WEEK', + 'DAY_OF_YEAR', + 'EXTRACT', + 'FROM_DAYS', + 'FROM_UNIXTIME', + 'GET_FORMAT', + 'LAST_DAY', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'MAKEDATE', + 'MAKETIME', + 'MONTHNAME', + 'NOW', + 'PERIOD_ADD', + 'PERIOD_DIFF', + 'SEC_TO_TIME', + 'STR_TO_DATE', + 'SUBDATE', + 'SUBTIME', + 'SYSDATE', + 'TIME', + 'TIMEDIFF', + 'TIMESTAMP', + 'TIMESTAMPADD', + 'TIMESTAMPDIFF', + 'TIME_FORMAT', + 'TIME_TO_SEC', + 'TO_DAYS', + 'TO_SECONDS', + 'UNIX_TIMESTAMP', + 'UTC_DATE', + 'UTC_TIME', + 'UTC_TIMESTAMP', + 'WEEKDAY', + 'YEARWEEK', + + // TEXT FUNCTIONS + 'SUBSTR', + 'SUBSTRING', + 'LTRIM', + 'RTRIM', + 'TRIM', + 'TO', + 'LOWER', + 'UPPER', + 'CONCAT', + 'CONCAT_WS', + 'LENGTH', + 'STRCMP', + 'RIGHT', + 'LEFT', + 'ASCII', + 'LOCATE', + 'REPLACE', + 'REVERSE', + 'CAST', + + // BOOL FUNCTIONS + 'LIKE', + 'ISNULL', + 'ISNOTNULL', + + // FLOWCONTROL FUNCTIONS + 'IFNULL', + 'NULLIF', + 'IF', + 'TYPEOF', + + // RELEVANCE FUNCTIONS AND PARAMETERS + 'MATCH', + 'MATCH_PHRASE', + 'MATCH_PHRASE_PREFIX', + 'MATCH_BOOL_PREFIX', + 'SIMPLE_QUERY_STRING', + 'MULTI_MATCH', + 'QUERY_STRING', + 'ALLOW_LEADING_WILDCARD', + 'ANALYZE_WILDCARD', + 'ANALYZER', + 'AUTO_GENERATE_SYNONYMS_PHRASE_QUERY', + 'BOOST', + 'CUTOFF_FREQUENCY', + 'DEFAULT_FIELD', + 'DEFAULT_OPERATOR', + 'ENABLE_POSITION_INCREMENTS', + 'ESCAPE', + 'FLAGS', + 'FUZZY_MAX_EXPANSIONS', + 'FUZZY_PREFIX_LENGTH', + 'FUZZY_TRANSPOSITIONS', + 'FUZZY_REWRITE', + 'FUZZINESS', + 'LENIENT', + 'LOW_FREQ_OPERATOR', + 'MAX_DETERMINIZED_STATES', + 'MAX_EXPANSIONS', + 'MINIMUM_SHOULD_MATCH', + 'OPERATOR', + 'PHRASE_SLOP', + 'PREFIX_LENGTH', + 'QUOTE_ANALYZER', + 'QUOTE_FIELD_SUFFIX', + 'REWRITE', + 'SLOP', + 'TIE_BREAKER', + 'TYPE', + 'ZERO_TERMS_QUERY', + + // SPAN KEYWORDS + 'SPAN', + 'MS', + 'S', + 'M', + 'H', + 'W', + 'Q', + 'Y', +]; + +const builtinFunctions = [ + 'ABS', + 'CBRT', + 'CEIL', + 'CEILING', + 'CONV', + 'CRC32', + 'E', + 'EXP', + 'FLOOR', + 'LN', + 'LOG', + 'LOG10', + 'LOG2', + 'MOD', + 'PI', + 'POSITION', + 'POW', + 'POWER', + 'RAND', + 'ROUND', + 'SIGN', + 'SQRT', + 'TRUNCATE', + 'ACOS', + 'ASIN', + 'ATAN', + 'ATAN2', + 'COS', + 'COT', + 'DEGREES', + 'RADIANS', + 'SIN', + 'TAN', + 'ADDDATE', + 'ADDTIME', + 'CURDATE', + 'CURRENT_DATE', + 'CURRENT_TIME', + 'CURRENT_TIMESTAMP', + 'CURTIME', + 'DATE', + 'DATEDIFF', + 'DATE_ADD', + 'DATE_FORMAT', + 'DATE_SUB', + 'DAYNAME', + 'DAYOFMONTH', + 'DAYOFWEEK', + 'DAYOFYEAR', + 'DAY_OF_MONTH', + 'DAY_OF_WEEK', + 'DAY_OF_YEAR', + 'EXTRACT', + 'FROM_DAYS', + 'FROM_UNIXTIME', + 'GET_FORMAT', + 'LAST_DAY', + 'LOCALTIME', + 'LOCALTIMESTAMP', + 'MAKEDATE', + 'MAKETIME', + 'MONTHNAME', + 'NOW', + 'PERIOD_ADD', + 'PERIOD_DIFF', + 'SEC_TO_TIME', + 'STR_TO_DATE', + 'SUBDATE', + 'SUBTIME', + 'SYSDATE', + 'TIME', + 'TIMEDIFF', + 'TIMESTAMP', + 'TIMESTAMPADD', + 'TIMESTAMPDIFF', + 'TIME_FORMAT', + 'TIME_TO_SEC', + 'TO_DAYS', + 'TO_SECONDS', + 'UNIX_TIMESTAMP', + 'UTC_DATE', + 'UTC_TIME', + 'UTC_TIMESTAMP', + 'WEEKDAY', + 'YEARWEEK', +]; + +export const lexerRules = { + defaultToken: 'invalid', + ignoreCase: true, + tokenPostfix: '', + keywords, + builtinFunctions, + brackets, + tokenizer: { + root: [ + [ + /[a-zA-Z_$][a-zA-Z0-9_$]*\b/, + { + cases: { + '@keywords': 'keyword', + '@builtinFunctions': 'identifier', + '@default': 'identifier', + }, + }, + ], + [/[()]/, '@brackets'], + [/--.*$/, 'comment'], + [/\/\*/, 'comment', '@comment'], + [/\/.*$/, 'comment'], + + [/".*?"/, 'string'], + [/'.*?'/, 'constant'], + [/`.*?`/, 'string'], + + // whitespace + [/[ \t\r\n]+/, { token: '@whitespace' }], + [/[+-]?\d+(?:(?:\.\d*)?(?:[eE][+-]?\d+)?)?\b/, 'number'], + [/⇐|<⇒|\*|\.|\:\:|\+|\-|\/|\/\/|%|&|\^|~|<|>|<=|=>|==|!=|<>|=/, 'keyword.operator'], + [/[\(]/, 'paren.lparen'], + [/[\)]/, 'paren.rparen'], + [/\s+/, 'text'], + ], + numbers: [ + [/0[xX][0-9a-fA-F]*/, 'number'], + [/[$][+-]*\d*(\.\d*)?/, 'number'], + [/((\d+(\.\d*)?)|(\.\d+))([eE][\-+]?\d+)?/, 'number'], + ], + strings: [ + [/N'/, { token: 'string', next: '@string' }], + [/'/, { token: 'string', next: '@string' }], + ], + string: [ + [/[^']+/, 'string'], + [/''/, 'string'], + [/'/, { token: 'string', next: '@pop' }], + ], + comment: [ + [/[^\/*]+/, 'comment'], + [/\*\//, 'comment', '@pop'], + [/[\/*]/, 'comment'], + ], + }, +} as monaco.languages.IMonarchLanguage; + +monaco.languages.register({ + id: ID, +}); diff --git a/packages/osd-monaco/src/xjson/lexer_rules/opensearchsql.ts b/packages/osd-monaco/src/xjson/lexer_rules/opensearchsql.ts new file mode 100644 index 000000000000..0ff29b71c09d --- /dev/null +++ b/packages/osd-monaco/src/xjson/lexer_rules/opensearchsql.ts @@ -0,0 +1,161 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { monaco } from '../../monaco'; + +export const ID = 'SQL'; + +const keywords = [ + 'ALL', + 'AND', + 'AS', + 'ASC', + 'BOOLEAN', + 'BETWEEN', + 'BY', + 'CASE', + 'CAST', + 'CROSS', + 'COLUMNS', + 'DATETIME', + 'DELETE', + 'DESC', + 'DESCRIBE', + 'DISTINCT', + 'DOUBLE', + 'ELSE', + 'EXISTS', + 'FALSE', + 'FLOAT', + 'FIRST', + 'FROM', + 'GROUP', + 'HAVING', + 'IN', + 'INNER', + 'INT', + 'INTEGER', + 'IS', + 'JOIN', + 'LAST', + 'LEFT', + 'LIKE', + 'LIMIT', + 'LONG', + 'MATCH', + 'NATURAL', + 'NOT', + 'NULL', + 'NULLS', + 'ON', + 'OR', + 'ORDER', + 'OUTER', + 'OVER', + 'PARTITION', + 'REGEXP', + 'RIGHT', + 'SELECT', + 'SHOW', + 'STRING', + 'THEN', + 'TRUE', + 'UNION', + 'USING', + 'WHEN', + 'WHERE', + 'EXCEPT', +]; + +const functions = [ + 'AVG', + 'COUNT', + 'MAX', + 'MIN', + 'SUM', + 'VAR_POP', + 'VAR_SAMP', + 'VARIANCE', + 'STD', + 'STDDEV', + 'STDDEV_POP', + 'STDDEV_SAMP', + 'SUBSTRING', + 'TRIM', +]; + +const operators = [ + '=', + '>', + '<', + '!', + '~', + '\\|', + '&', + '\\^', + '\\*', + '/', + '%', + '\\+', + '-', + 'DIV', + 'MOD', +]; + +const brackets = [ + { open: '(', close: ')', token: 'delimiter.parenthesis' }, + { open: '[', close: ']', token: 'delimiter.square' }, +]; + +export const lexerRules = { + defaultToken: 'invalid', + ignoreCase: true, + tokenPostfix: '', + keywords, + functions, + operators, + brackets, + tokenizer: { + root: [ + [ + /[a-zA-Z_$][a-zA-Z0-9_$]*/, + { + cases: { + '@keywords': 'keyword', + '@functions': 'function', + '@default': 'identifier', + }, + }, + ], + { include: '@whitespace' }, + [/[()]/, '@brackets'], + [new RegExp(operators.join('|')), 'operator'], + [/[0-9]+(\.[0-9]+)?/, 'number'], + [/'([^'\\]|\\.)*$/, 'string.invalid'], // non-terminated string + [/'/, 'string', '@string'], + [/"/, 'string', '@string'], + ], + whitespace: [ + [/[ \t\r\n]+/, 'white'], + [/\/\*/, 'comment', '@comment'], + [/--.*$/, 'comment'], + ], + string: [ + [/[^'\\]+/, 'string'], + [/\\./, 'string.escape'], + [/'/, 'string', '@pop'], + [/"/, 'string', '@pop'], + ], + comment: [ + [/[^/*]+/, 'comment'], + [/\*\//, 'comment', '@pop'], + [/[\/*]/, 'comment'], + ], + }, +} as monaco.languages.IMonarchLanguage; + +monaco.languages.register({ + id: ID, +}); diff --git a/packages/osd-optimizer/README.md b/packages/osd-optimizer/README.md index c10f639a38fc..e737f881f125 100644 --- a/packages/osd-optimizer/README.md +++ b/packages/osd-optimizer/README.md @@ -26,7 +26,7 @@ We only limit the number of workers we will start at any given time. If we start ### Caching -Bundles built by the the optimizer include a cache file which describes the information needed to determine if the bundle needs to be rebuilt when the optimizer is restarted. Caching is enabled by default and is very aggressive about invalidating the cache output, but if you need to disable caching you can pass `--no-cache` to `node scripts/build_opensearch_dashboards_platform_plugins`, or set the `OSD_OPTIMIZER_NO_CACHE` environment variable to anything (env overrides everything). +Bundles built by the optimizer include a cache file which describes the information needed to determine if the bundle needs to be rebuilt when the optimizer is restarted. Caching is enabled by default and is very aggressive about invalidating the cache output, but if you need to disable caching you can pass `--no-cache` to `node scripts/build_opensearch_dashboards_platform_plugins`, or set the `OSD_OPTIMIZER_NO_CACHE` environment variable to anything (env overrides everything). When a bundle is determined to be up-to-date a worker is not started for the bundle. If running the optimizer with the `--dev/--watch` flag, then all the files referenced by cached bundles are watched for changes. Once a change is detected in any of the files referenced by the built bundle a worker is started. If a file is changed that is referenced by several bundles then workers will be started for each bundle, combining workers together to respect the worker limit. diff --git a/packages/osd-optimizer/package.json b/packages/osd-optimizer/package.json index b1477f176358..853075556e96 100644 --- a/packages/osd-optimizer/package.json +++ b/packages/osd-optimizer/package.json @@ -45,7 +45,7 @@ "@types/compression-webpack-plugin": "^6.0.6", "@types/loader-utils": "^1.1.3", "@types/source-map-support": "^0.5.3", - "@types/watchpack": "^1.1.6", + "@types/watchpack": "^2.4.4", "@types/webpack": "^4.41.31", "babel-loader": "^8.2.3", "comment-stripper": "^0.0.4", @@ -59,6 +59,6 @@ "style-loader": "^1.1.3", "url-loader": "^2.2.0", "val-loader": "^2.1.2", - "webpack": "npm:@amoo-miki/webpack@4.46.0-rc.2" + "webpack": "npm:@amoo-miki/webpack@4.46.0-xxhash.1" } } diff --git a/packages/osd-optimizer/src/__fixtures__/mock_repo/src/core/public/core_app/styles/_globals_v9dark.scss b/packages/osd-optimizer/src/__fixtures__/mock_repo/src/core/public/core_app/styles/_globals_v9dark.scss new file mode 100644 index 000000000000..e5f1ee131499 --- /dev/null +++ b/packages/osd-optimizer/src/__fixtures__/mock_repo/src/core/public/core_app/styles/_globals_v9dark.scss @@ -0,0 +1 @@ +$globalStyleConstant: 14; diff --git a/packages/osd-optimizer/src/__fixtures__/mock_repo/src/core/public/core_app/styles/_globals_v9light.scss b/packages/osd-optimizer/src/__fixtures__/mock_repo/src/core/public/core_app/styles/_globals_v9light.scss new file mode 100644 index 000000000000..45d508715da0 --- /dev/null +++ b/packages/osd-optimizer/src/__fixtures__/mock_repo/src/core/public/core_app/styles/_globals_v9light.scss @@ -0,0 +1 @@ +$globalStyleConstant: 15; diff --git a/packages/osd-optimizer/src/common/theme_tags.test.ts b/packages/osd-optimizer/src/common/theme_tags.test.ts index 791734e9cfb6..ae8775484e7d 100644 --- a/packages/osd-optimizer/src/common/theme_tags.test.ts +++ b/packages/osd-optimizer/src/common/theme_tags.test.ts @@ -37,6 +37,8 @@ it('returns default tags when passed undefined', () => { "v7light", "v8dark", "v8light", + "v9dark", + "v9light", ] `); }); @@ -48,6 +50,8 @@ it('returns all tags when passed *', () => { "v7light", "v8dark", "v8light", + "v9dark", + "v9light", ] `); }); @@ -81,13 +85,13 @@ it('returns specific tags when passed an array', () => { it('throws when an invalid tag is in the array', () => { expect(() => parseThemeTags(['v8light', 'v7light', 'bar'])).toThrowErrorMatchingInlineSnapshot( - `"Invalid theme tags [bar], options: [v7dark, v7light, v8dark, v8light]"` + `"Invalid theme tags [bar], options: [v7dark, v7light, v8dark, v8light, v9dark, v9light]"` ); }); it('throws when an invalid tags in comma separated list', () => { expect(() => parseThemeTags('v8light ,v7light,bar,box ')).toThrowErrorMatchingInlineSnapshot( - `"Invalid theme tags [bar, box], options: [v7dark, v7light, v8dark, v8light]"` + `"Invalid theme tags [bar, box], options: [v7dark, v7light, v8dark, v8light, v9dark, v9light]"` ); }); diff --git a/packages/osd-optimizer/src/common/theme_tags.ts b/packages/osd-optimizer/src/common/theme_tags.ts index 8170c6bcab69..3078a9c6dc06 100644 --- a/packages/osd-optimizer/src/common/theme_tags.ts +++ b/packages/osd-optimizer/src/common/theme_tags.ts @@ -28,6 +28,8 @@ * under the License. */ +import { themeTags as THEME_TAGS } from '@osd/ui-shared-deps'; +import type { ThemeTag, ThemeTags } from '@osd/ui-shared-deps'; import { ascending } from './array_helpers'; const tags = (...themeTags: string[]) => @@ -37,10 +39,9 @@ const validTag = (tag: any): tag is ThemeTag => ALL_THEMES.includes(tag); const isArrayOfStrings = (input: unknown): input is string[] => Array.isArray(input) && input.every((v) => typeof v === 'string'); -export type ThemeTags = readonly ThemeTag[]; -export type ThemeTag = 'v7light' | 'v7dark' | 'v8light' | 'v8dark'; -export const DEFAULT_THEMES = tags('v7light', 'v7dark', 'v8light', 'v8dark'); -export const ALL_THEMES = tags('v7light', 'v7dark', 'v8light', 'v8dark'); +export type { ThemeTag, ThemeTags }; +export const DEFAULT_THEMES = tags(...THEME_TAGS); +export const ALL_THEMES = tags(...THEME_TAGS); export function parseThemeTags(input?: any): ThemeTags { if (!input) { diff --git a/packages/osd-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap b/packages/osd-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap index dbbb5bad9bf3..776298aa496c 100644 --- a/packages/osd-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap +++ b/packages/osd-optimizer/src/integration_tests/__snapshots__/basic_optimization.test.ts.snap @@ -71,12 +71,14 @@ OptimizerConfig { "v7light", "v8dark", "v8light", + "v9dark", + "v9light", ], "watch": false, } `; -exports[`prepares assets for distribution: bar bundle 1`] = `"(function(modules){var installedModules={};function __webpack_require__(moduleId){if(installedModules[moduleId]){return installedModules[moduleId].exports}var module=installedModules[moduleId]={i:moduleId,l:false,exports:{}};modules[moduleId].call(module.exports,module,module.exports,__webpack_require__);module.l=true;return module.exports}__webpack_require__.m=modules;__webpack_require__.c=installedModules;__webpack_require__.d=function(exports,name,getter){if(!__webpack_require__.o(exports,name)){Object.defineProperty(exports,name,{enumerable:true,get:getter})}};__webpack_require__.r=function(exports){if(typeof Symbol!==\\"undefined\\"&&Symbol.toStringTag){Object.defineProperty(exports,Symbol.toStringTag,{value:\\"Module\\"})}Object.defineProperty(exports,\\"__esModule\\",{value:true})};__webpack_require__.t=function(value,mode){if(mode&1)value=__webpack_require__(value);if(mode&8)return value;if(mode&4&&typeof value===\\"object\\"&&value&&value.__esModule)return value;var ns=Object.create(null);__webpack_require__.r(ns);Object.defineProperty(ns,\\"default\\",{enumerable:true,value:value});if(mode&2&&typeof value!=\\"string\\")for(var key in value)__webpack_require__.d(ns,key,function(key){return value[key]}.bind(null,key));return ns};__webpack_require__.n=function(module){var getter=module&&module.__esModule?function getDefault(){return module[\\"default\\"]}:function getModuleExports(){return module};__webpack_require__.d(getter,\\"a\\",getter);return getter};__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)};__webpack_require__.p=\\"\\";return __webpack_require__(__webpack_require__.s=3)})([function(module,exports,__webpack_require__){\\"use strict\\";module.exports=function(cssWithMappingToString){var list=[];list.toString=function toString(){return this.map((function(item){var content=cssWithMappingToString(item);if(item[2]){return\\"@media \\".concat(item[2],\\" {\\").concat(content,\\"}\\")}return content})).join(\\"\\")};list.i=function(modules,mediaQuery,dedupe){if(typeof modules===\\"string\\"){modules=[[null,modules,\\"\\"]]}var alreadyImportedModules={};if(dedupe){for(var i=0;i { await cpy('**/*', MOCK_REPO_DIR, { cwd: MOCK_REPO_SRC, parents: true, - deep: true, + deep: Infinity, }); }); @@ -176,7 +176,7 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { bar.cache.refresh(); expect(bar.cache.getModuleCount()).toBe( // code + styles + style/css-loader runtimes + public path updater - 25 + 33 ); expect(bar.cache.getReferencedFiles()?.map(absolutePathSerializer.serialize).sort()) @@ -195,6 +195,8 @@ it('builds expected bundles, saves bundle counts to metadata', async () => { "/packages/osd-optimizer/src/__fixtures__/__tmp__/mock_repo/src/core/public/core_app/styles/_globals_v7light.scss", "/packages/osd-optimizer/src/__fixtures__/__tmp__/mock_repo/src/core/public/core_app/styles/_globals_v8dark.scss", "/packages/osd-optimizer/src/__fixtures__/__tmp__/mock_repo/src/core/public/core_app/styles/_globals_v8light.scss", + "/packages/osd-optimizer/src/__fixtures__/__tmp__/mock_repo/src/core/public/core_app/styles/_globals_v9dark.scss", + "/packages/osd-optimizer/src/__fixtures__/__tmp__/mock_repo/src/core/public/core_app/styles/_globals_v9light.scss", "/packages/osd-optimizer/target/worker/entry_point_creator.js", "/packages/osd-ui-shared-deps/public_path_module_creator.js", ] diff --git a/packages/osd-optimizer/src/integration_tests/bundle_cache.test.ts b/packages/osd-optimizer/src/integration_tests/bundle_cache.test.ts index 24ddfd14a832..83a503a21e7e 100644 --- a/packages/osd-optimizer/src/integration_tests/bundle_cache.test.ts +++ b/packages/osd-optimizer/src/integration_tests/bundle_cache.test.ts @@ -54,7 +54,7 @@ beforeEach(async () => { await cpy('**/*', MOCK_REPO_DIR, { cwd: MOCK_REPO_SRC, parents: true, - deep: true, + deep: Infinity, }); }); diff --git a/packages/osd-optimizer/src/optimizer/cache_keys.test.ts b/packages/osd-optimizer/src/optimizer/cache_keys.test.ts index 5ca5c76a566f..95cd4b4b1d32 100644 --- a/packages/osd-optimizer/src/optimizer/cache_keys.test.ts +++ b/packages/osd-optimizer/src/optimizer/cache_keys.test.ts @@ -103,6 +103,8 @@ describe('getOptimizerCacheKey()', () => { "v7light", "v8dark", "v8light", + "v9dark", + "v9light", ], }, } diff --git a/packages/osd-optimizer/src/optimizer/watcher.ts b/packages/osd-optimizer/src/optimizer/watcher.ts index cf5faadb33d8..87f2ba2b8894 100644 --- a/packages/osd-optimizer/src/optimizer/watcher.ts +++ b/packages/osd-optimizer/src/optimizer/watcher.ts @@ -94,7 +94,7 @@ export class Watcher { take(1) ), - // call watchpack.watch after listerners are setup + // call watchpack.watch after listeners are set up Rx.defer(() => { const watchPaths: string[] = []; @@ -104,7 +104,11 @@ export class Watcher { } } - this.watchpack.watch(watchPaths, [], startTime); + this.watchpack.watch({ + files: watchPaths, + startTime, + }); + return Rx.EMPTY; }) ); diff --git a/packages/osd-optimizer/src/worker/webpack.config.ts b/packages/osd-optimizer/src/worker/webpack.config.ts index 1bfb1f186af7..4c3ba9291ee5 100644 --- a/packages/osd-optimizer/src/worker/webpack.config.ts +++ b/packages/osd-optimizer/src/worker/webpack.config.ts @@ -256,6 +256,16 @@ export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: loader: 'raw-loader', }, }, + { + test: /\.cjs$/, + include: /node_modules/, + use: { + loader: 'babel-loader', + options: { + presets: [BABEL_PRESET_PATH], + }, + }, + }, ], }, diff --git a/packages/osd-plugin-generator/template/public/components/app.tsx.ejs b/packages/osd-plugin-generator/template/public/components/app.tsx.ejs index 876b3f8c5e75..2029a69dd8db 100644 --- a/packages/osd-plugin-generator/template/public/components/app.tsx.ejs +++ b/packages/osd-plugin-generator/template/public/components/app.tsx.ejs @@ -4,7 +4,7 @@ import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; import { BrowserRouter as Router } from 'react-router-dom'; import { -EuiButton, +EuiSmallButton, EuiHorizontalRule, EuiPage, EuiPageBody, diff --git a/packages/osd-pm/dist/index.js b/packages/osd-pm/dist/index.js index 458aacd2256c..e3c3250acb68 100644 --- a/packages/osd-pm/dist/index.js +++ b/packages/osd-pm/dist/index.js @@ -735,245 +735,245 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__importDefault", function() { return __importDefault; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__classPrivateFieldGet", function() { return __classPrivateFieldGet; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__classPrivateFieldSet", function() { return __classPrivateFieldSet; }); -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -***************************************************************************** */ -/* global Reflect, Promise */ - -var extendStatics = function(d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; - return extendStatics(d, b); -}; - -function __extends(d, b) { - if (typeof b !== "function" && b !== null) - throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); -} - -var __assign = function() { - __assign = Object.assign || function __assign(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; - } - return t; - } - return __assign.apply(this, arguments); -} - -function __rest(s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -} - -function __decorate(decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -} - -function __param(paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } -} - -function __metadata(metadataKey, metadataValue) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); -} - -function __awaiter(thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -} - -function __generator(thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -} - -var __createBinding = Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -}); - -function __exportStar(m, o) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p); -} - -function __values(o) { - var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; - if (m) return m.call(o); - if (o && typeof o.length === "number") return { - next: function () { - if (o && i >= o.length) o = void 0; - return { value: o && o[i++], done: !o }; - } - }; - throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); -} - -function __read(o, n) { - var m = typeof Symbol === "function" && o[Symbol.iterator]; - if (!m) return o; - var i = m.call(o), r, ar = [], e; - try { - while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); - } - catch (error) { e = { error: error }; } - finally { - try { - if (r && !r.done && (m = i["return"])) m.call(i); - } - finally { if (e) throw e.error; } - } - return ar; -} - -/** @deprecated */ -function __spread() { - for (var ar = [], i = 0; i < arguments.length; i++) - ar = ar.concat(__read(arguments[i])); - return ar; -} - -/** @deprecated */ -function __spreadArrays() { - for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; - for (var r = Array(s), k = 0, i = 0; i < il; i++) - for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) - r[k] = a[j]; - return r; -} - -function __spreadArray(to, from, pack) { - if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { - if (ar || !(i in from)) { - if (!ar) ar = Array.prototype.slice.call(from, 0, i); - ar[i] = from[i]; - } - } - return to.concat(ar || Array.prototype.slice.call(from)); -} - -function __await(v) { - return this instanceof __await ? (this.v = v, this) : new __await(v); -} - -function __asyncGenerator(thisArg, _arguments, generator) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var g = generator.apply(thisArg, _arguments || []), i, q = []; - return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; - function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } - function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } - function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } - function fulfill(value) { resume("next", value); } - function reject(value) { resume("throw", value); } - function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } -} - -function __asyncDelegator(o) { - var i, p; - return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; - function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } -} - -function __asyncValues(o) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var m = o[Symbol.asyncIterator], i; - return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); - function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } - function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } -} - -function __makeTemplateObject(cooked, raw) { - if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } - return cooked; -}; - -var __setModuleDefault = Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}; - -function __importStar(mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -} - -function __importDefault(mod) { - return (mod && mod.__esModule) ? mod : { default: mod }; -} - -function __classPrivateFieldGet(receiver, state, kind, f) { - if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); - if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); - return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); -} - -function __classPrivateFieldSet(receiver, state, value, kind, f) { - if (kind === "m") throw new TypeError("Private method is not writable"); - if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); - if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); - return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; -} +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +/* global Reflect, Promise */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + if (typeof b !== "function" && b !== null) + throw new TypeError("Class extends value " + String(b) + " is not a constructor or null"); + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + } + return __assign.apply(this, arguments); +} + +function __rest(s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +} + +function __decorate(decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +} + +function __param(paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +} + +function __metadata(metadataKey, metadataValue) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); +} + +function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +function __generator(thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +} + +var __createBinding = Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +}); + +function __exportStar(m, o) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p); +} + +function __values(o) { + var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; + if (m) return m.call(o); + if (o && typeof o.length === "number") return { + next: function () { + if (o && i >= o.length) o = void 0; + return { value: o && o[i++], done: !o }; + } + }; + throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); +} + +function __read(o, n) { + var m = typeof Symbol === "function" && o[Symbol.iterator]; + if (!m) return o; + var i = m.call(o), r, ar = [], e; + try { + while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); + } + catch (error) { e = { error: error }; } + finally { + try { + if (r && !r.done && (m = i["return"])) m.call(i); + } + finally { if (e) throw e.error; } + } + return ar; +} + +/** @deprecated */ +function __spread() { + for (var ar = [], i = 0; i < arguments.length; i++) + ar = ar.concat(__read(arguments[i])); + return ar; +} + +/** @deprecated */ +function __spreadArrays() { + for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; + for (var r = Array(s), k = 0, i = 0; i < il; i++) + for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) + r[k] = a[j]; + return r; +} + +function __spreadArray(to, from, pack) { + if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { + if (ar || !(i in from)) { + if (!ar) ar = Array.prototype.slice.call(from, 0, i); + ar[i] = from[i]; + } + } + return to.concat(ar || Array.prototype.slice.call(from)); +} + +function __await(v) { + return this instanceof __await ? (this.v = v, this) : new __await(v); +} + +function __asyncGenerator(thisArg, _arguments, generator) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var g = generator.apply(thisArg, _arguments || []), i, q = []; + return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; + function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } + function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } + function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } + function fulfill(value) { resume("next", value); } + function reject(value) { resume("throw", value); } + function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } +} + +function __asyncDelegator(o) { + var i, p; + return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; + function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } +} + +function __asyncValues(o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], i; + return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); + function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } + function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } +} + +function __makeTemplateObject(cooked, raw) { + if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } + return cooked; +}; + +var __setModuleDefault = Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}; + +function __importStar(mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +} + +function __importDefault(mod) { + return (mod && mod.__esModule) ? mod : { default: mod }; +} + +function __classPrivateFieldGet(receiver, state, kind, f) { + if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); + if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); + return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); +} + +function __classPrivateFieldSet(receiver, state, value, kind, f) { + if (kind === "m") throw new TypeError("Private method is not writable"); + if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); + if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); + return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; +} /***/ }), @@ -1653,224 +1653,224 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__importDefault", function() { return __importDefault; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__classPrivateFieldGet", function() { return __classPrivateFieldGet; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__classPrivateFieldSet", function() { return __classPrivateFieldSet; }); -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -PERFORMANCE OF THIS SOFTWARE. -***************************************************************************** */ -/* global Reflect, Promise */ - -var extendStatics = function(d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); -}; - -function __extends(d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); -} - -var __assign = function() { - __assign = Object.assign || function __assign(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; - } - return t; - } - return __assign.apply(this, arguments); -} - -function __rest(s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { - if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) - t[p[i]] = s[p[i]]; - } - return t; -} - -function __decorate(decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; -} - -function __param(paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } -} - -function __metadata(metadataKey, metadataValue) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); -} - -function __awaiter(thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); -} - -function __generator(thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } -} - -function __createBinding(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -} - -function __exportStar(m, exports) { - for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) exports[p] = m[p]; -} - -function __values(o) { - var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; - if (m) return m.call(o); - if (o && typeof o.length === "number") return { - next: function () { - if (o && i >= o.length) o = void 0; - return { value: o && o[i++], done: !o }; - } - }; - throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); -} - -function __read(o, n) { - var m = typeof Symbol === "function" && o[Symbol.iterator]; - if (!m) return o; - var i = m.call(o), r, ar = [], e; - try { - while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); - } - catch (error) { e = { error: error }; } - finally { - try { - if (r && !r.done && (m = i["return"])) m.call(i); - } - finally { if (e) throw e.error; } - } - return ar; -} - -function __spread() { - for (var ar = [], i = 0; i < arguments.length; i++) - ar = ar.concat(__read(arguments[i])); - return ar; -} - -function __spreadArrays() { - for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; - for (var r = Array(s), k = 0, i = 0; i < il; i++) - for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) - r[k] = a[j]; - return r; -}; - -function __await(v) { - return this instanceof __await ? (this.v = v, this) : new __await(v); -} - -function __asyncGenerator(thisArg, _arguments, generator) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var g = generator.apply(thisArg, _arguments || []), i, q = []; - return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; - function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } - function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } - function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } - function fulfill(value) { resume("next", value); } - function reject(value) { resume("throw", value); } - function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } -} - -function __asyncDelegator(o) { - var i, p; - return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; - function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } -} - -function __asyncValues(o) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var m = o[Symbol.asyncIterator], i; - return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); - function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } - function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } -} - -function __makeTemplateObject(cooked, raw) { - if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } - return cooked; -}; - -function __importStar(mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result.default = mod; - return result; -} - -function __importDefault(mod) { - return (mod && mod.__esModule) ? mod : { default: mod }; -} - -function __classPrivateFieldGet(receiver, privateMap) { - if (!privateMap.has(receiver)) { - throw new TypeError("attempted to get private field on non-instance"); - } - return privateMap.get(receiver); -} - -function __classPrivateFieldSet(receiver, privateMap, value) { - if (!privateMap.has(receiver)) { - throw new TypeError("attempted to set private field on non-instance"); - } - privateMap.set(receiver, value); - return value; -} +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */ +/* global Reflect, Promise */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + } + return __assign.apply(this, arguments); +} + +function __rest(s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { + if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) + t[p[i]] = s[p[i]]; + } + return t; +} + +function __decorate(decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +} + +function __param(paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +} + +function __metadata(metadataKey, metadataValue) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); +} + +function __awaiter(thisArg, _arguments, P, generator) { + function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +function __generator(thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +} + +function __createBinding(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +} + +function __exportStar(m, exports) { + for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) exports[p] = m[p]; +} + +function __values(o) { + var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0; + if (m) return m.call(o); + if (o && typeof o.length === "number") return { + next: function () { + if (o && i >= o.length) o = void 0; + return { value: o && o[i++], done: !o }; + } + }; + throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined."); +} + +function __read(o, n) { + var m = typeof Symbol === "function" && o[Symbol.iterator]; + if (!m) return o; + var i = m.call(o), r, ar = [], e; + try { + while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); + } + catch (error) { e = { error: error }; } + finally { + try { + if (r && !r.done && (m = i["return"])) m.call(i); + } + finally { if (e) throw e.error; } + } + return ar; +} + +function __spread() { + for (var ar = [], i = 0; i < arguments.length; i++) + ar = ar.concat(__read(arguments[i])); + return ar; +} + +function __spreadArrays() { + for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; + for (var r = Array(s), k = 0, i = 0; i < il; i++) + for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) + r[k] = a[j]; + return r; +}; + +function __await(v) { + return this instanceof __await ? (this.v = v, this) : new __await(v); +} + +function __asyncGenerator(thisArg, _arguments, generator) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var g = generator.apply(thisArg, _arguments || []), i, q = []; + return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; + function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } + function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } + function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } + function fulfill(value) { resume("next", value); } + function reject(value) { resume("throw", value); } + function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } +} + +function __asyncDelegator(o) { + var i, p; + return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; + function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } +} + +function __asyncValues(o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], i; + return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); + function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } + function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } +} + +function __makeTemplateObject(cooked, raw) { + if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } + return cooked; +}; + +function __importStar(mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result.default = mod; + return result; +} + +function __importDefault(mod) { + return (mod && mod.__esModule) ? mod : { default: mod }; +} + +function __classPrivateFieldGet(receiver, privateMap) { + if (!privateMap.has(receiver)) { + throw new TypeError("attempted to get private field on non-instance"); + } + return privateMap.get(receiver); +} + +function __classPrivateFieldSet(receiver, privateMap, value) { + if (!privateMap.has(receiver)) { + throw new TypeError("attempted to set private field on non-instance"); + } + privateMap.set(receiver, value); + return value; +} /***/ }), @@ -8252,158 +8252,158 @@ convert.rgb.gray = function (rgb) { /***/ (function(module, exports, __webpack_require__) { "use strict"; - - -module.exports = { - "aliceblue": [240, 248, 255], - "antiquewhite": [250, 235, 215], - "aqua": [0, 255, 255], - "aquamarine": [127, 255, 212], - "azure": [240, 255, 255], - "beige": [245, 245, 220], - "bisque": [255, 228, 196], - "black": [0, 0, 0], - "blanchedalmond": [255, 235, 205], - "blue": [0, 0, 255], - "blueviolet": [138, 43, 226], - "brown": [165, 42, 42], - "burlywood": [222, 184, 135], - "cadetblue": [95, 158, 160], - "chartreuse": [127, 255, 0], - "chocolate": [210, 105, 30], - "coral": [255, 127, 80], - "cornflowerblue": [100, 149, 237], - "cornsilk": [255, 248, 220], - "crimson": [220, 20, 60], - "cyan": [0, 255, 255], - "darkblue": [0, 0, 139], - "darkcyan": [0, 139, 139], - "darkgoldenrod": [184, 134, 11], - "darkgray": [169, 169, 169], - "darkgreen": [0, 100, 0], - "darkgrey": [169, 169, 169], - "darkkhaki": [189, 183, 107], - "darkmagenta": [139, 0, 139], - "darkolivegreen": [85, 107, 47], - "darkorange": [255, 140, 0], - "darkorchid": [153, 50, 204], - "darkred": [139, 0, 0], - "darksalmon": [233, 150, 122], - "darkseagreen": [143, 188, 143], - "darkslateblue": [72, 61, 139], - "darkslategray": [47, 79, 79], - "darkslategrey": [47, 79, 79], - "darkturquoise": [0, 206, 209], - "darkviolet": [148, 0, 211], - "deeppink": [255, 20, 147], - "deepskyblue": [0, 191, 255], - "dimgray": [105, 105, 105], - "dimgrey": [105, 105, 105], - "dodgerblue": [30, 144, 255], - "firebrick": [178, 34, 34], - "floralwhite": [255, 250, 240], - "forestgreen": [34, 139, 34], - "fuchsia": [255, 0, 255], - "gainsboro": [220, 220, 220], - "ghostwhite": [248, 248, 255], - "gold": [255, 215, 0], - "goldenrod": [218, 165, 32], - "gray": [128, 128, 128], - "green": [0, 128, 0], - "greenyellow": [173, 255, 47], - "grey": [128, 128, 128], - "honeydew": [240, 255, 240], - "hotpink": [255, 105, 180], - "indianred": [205, 92, 92], - "indigo": [75, 0, 130], - "ivory": [255, 255, 240], - "khaki": [240, 230, 140], - "lavender": [230, 230, 250], - "lavenderblush": [255, 240, 245], - "lawngreen": [124, 252, 0], - "lemonchiffon": [255, 250, 205], - "lightblue": [173, 216, 230], - "lightcoral": [240, 128, 128], - "lightcyan": [224, 255, 255], - "lightgoldenrodyellow": [250, 250, 210], - "lightgray": [211, 211, 211], - "lightgreen": [144, 238, 144], - "lightgrey": [211, 211, 211], - "lightpink": [255, 182, 193], - "lightsalmon": [255, 160, 122], - "lightseagreen": [32, 178, 170], - "lightskyblue": [135, 206, 250], - "lightslategray": [119, 136, 153], - "lightslategrey": [119, 136, 153], - "lightsteelblue": [176, 196, 222], - "lightyellow": [255, 255, 224], - "lime": [0, 255, 0], - "limegreen": [50, 205, 50], - "linen": [250, 240, 230], - "magenta": [255, 0, 255], - "maroon": [128, 0, 0], - "mediumaquamarine": [102, 205, 170], - "mediumblue": [0, 0, 205], - "mediumorchid": [186, 85, 211], - "mediumpurple": [147, 112, 219], - "mediumseagreen": [60, 179, 113], - "mediumslateblue": [123, 104, 238], - "mediumspringgreen": [0, 250, 154], - "mediumturquoise": [72, 209, 204], - "mediumvioletred": [199, 21, 133], - "midnightblue": [25, 25, 112], - "mintcream": [245, 255, 250], - "mistyrose": [255, 228, 225], - "moccasin": [255, 228, 181], - "navajowhite": [255, 222, 173], - "navy": [0, 0, 128], - "oldlace": [253, 245, 230], - "olive": [128, 128, 0], - "olivedrab": [107, 142, 35], - "orange": [255, 165, 0], - "orangered": [255, 69, 0], - "orchid": [218, 112, 214], - "palegoldenrod": [238, 232, 170], - "palegreen": [152, 251, 152], - "paleturquoise": [175, 238, 238], - "palevioletred": [219, 112, 147], - "papayawhip": [255, 239, 213], - "peachpuff": [255, 218, 185], - "peru": [205, 133, 63], - "pink": [255, 192, 203], - "plum": [221, 160, 221], - "powderblue": [176, 224, 230], - "purple": [128, 0, 128], - "rebeccapurple": [102, 51, 153], - "red": [255, 0, 0], - "rosybrown": [188, 143, 143], - "royalblue": [65, 105, 225], - "saddlebrown": [139, 69, 19], - "salmon": [250, 128, 114], - "sandybrown": [244, 164, 96], - "seagreen": [46, 139, 87], - "seashell": [255, 245, 238], - "sienna": [160, 82, 45], - "silver": [192, 192, 192], - "skyblue": [135, 206, 235], - "slateblue": [106, 90, 205], - "slategray": [112, 128, 144], - "slategrey": [112, 128, 144], - "snow": [255, 250, 250], - "springgreen": [0, 255, 127], - "steelblue": [70, 130, 180], - "tan": [210, 180, 140], - "teal": [0, 128, 128], - "thistle": [216, 191, 216], - "tomato": [255, 99, 71], - "turquoise": [64, 224, 208], - "violet": [238, 130, 238], - "wheat": [245, 222, 179], - "white": [255, 255, 255], - "whitesmoke": [245, 245, 245], - "yellow": [255, 255, 0], - "yellowgreen": [154, 205, 50] -}; + + +module.exports = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; /***/ }), @@ -10741,58 +10741,58 @@ mkdirP.sync = function sync (p, opts, made) { /* 142 */ /***/ (function(module, exports) { -exports.replaceDollarWithPercentPair = replaceDollarWithPercentPair -exports.convertToSetCommand = convertToSetCommand -exports.convertToSetCommands = convertToSetCommands - -function convertToSetCommand(key, value) { - var line = "" - key = key || "" - key = key.trim() - value = value || "" - value = value.trim() - if(key && value && value.length > 0) { - line = "@SET " + key + "=" + replaceDollarWithPercentPair(value) + "\r\n" - } - return line -} - -function extractVariableValuePairs(declarations) { - var pairs = {} - declarations.map(function(declaration) { - var split = declaration.split("=") - pairs[split[0]]=split[1] - }) - return pairs -} - -function convertToSetCommands(variableString) { - var variableValuePairs = extractVariableValuePairs(variableString.split(" ")) - var variableDeclarationsAsBatch = "" - Object.keys(variableValuePairs).forEach(function (key) { - variableDeclarationsAsBatch += convertToSetCommand(key, variableValuePairs[key]) - }) - return variableDeclarationsAsBatch -} - -function replaceDollarWithPercentPair(value) { - var dollarExpressions = /\$\{?([^\$@#\?\- \t{}:]+)\}?/g - var result = "" - var startIndex = 0 - value = value || "" - do { - var match = dollarExpressions.exec(value) - if(match) { - var betweenMatches = value.substring(startIndex, match.index) || "" - result += betweenMatches + "%" + match[1] + "%" - startIndex = dollarExpressions.lastIndex - } - } while (dollarExpressions.lastIndex > 0) - result += value.substr(startIndex) - return result -} - - +exports.replaceDollarWithPercentPair = replaceDollarWithPercentPair +exports.convertToSetCommand = convertToSetCommand +exports.convertToSetCommands = convertToSetCommands + +function convertToSetCommand(key, value) { + var line = "" + key = key || "" + key = key.trim() + value = value || "" + value = value.trim() + if(key && value && value.length > 0) { + line = "@SET " + key + "=" + replaceDollarWithPercentPair(value) + "\r\n" + } + return line +} + +function extractVariableValuePairs(declarations) { + var pairs = {} + declarations.map(function(declaration) { + var split = declaration.split("=") + pairs[split[0]]=split[1] + }) + return pairs +} + +function convertToSetCommands(variableString) { + var variableValuePairs = extractVariableValuePairs(variableString.split(" ")) + var variableDeclarationsAsBatch = "" + Object.keys(variableValuePairs).forEach(function (key) { + variableDeclarationsAsBatch += convertToSetCommand(key, variableValuePairs[key]) + }) + return variableDeclarationsAsBatch +} + +function replaceDollarWithPercentPair(value) { + var dollarExpressions = /\$\{?([^\$@#\?\- \t{}:]+)\}?/g + var result = "" + var startIndex = 0 + value = value || "" + do { + var match = dollarExpressions.exec(value) + if(match) { + var betweenMatches = value.substring(startIndex, match.index) || "" + result += betweenMatches + "%" + match[1] + "%" + startIndex = dollarExpressions.lastIndex + } + } while (dollarExpressions.lastIndex > 0) + result += value.substr(startIndex) + return result +} + + /***/ }), @@ -17527,158 +17527,158 @@ convert.rgb.gray = function (rgb) { /***/ (function(module, exports, __webpack_require__) { "use strict"; - - -module.exports = { - "aliceblue": [240, 248, 255], - "antiquewhite": [250, 235, 215], - "aqua": [0, 255, 255], - "aquamarine": [127, 255, 212], - "azure": [240, 255, 255], - "beige": [245, 245, 220], - "bisque": [255, 228, 196], - "black": [0, 0, 0], - "blanchedalmond": [255, 235, 205], - "blue": [0, 0, 255], - "blueviolet": [138, 43, 226], - "brown": [165, 42, 42], - "burlywood": [222, 184, 135], - "cadetblue": [95, 158, 160], - "chartreuse": [127, 255, 0], - "chocolate": [210, 105, 30], - "coral": [255, 127, 80], - "cornflowerblue": [100, 149, 237], - "cornsilk": [255, 248, 220], - "crimson": [220, 20, 60], - "cyan": [0, 255, 255], - "darkblue": [0, 0, 139], - "darkcyan": [0, 139, 139], - "darkgoldenrod": [184, 134, 11], - "darkgray": [169, 169, 169], - "darkgreen": [0, 100, 0], - "darkgrey": [169, 169, 169], - "darkkhaki": [189, 183, 107], - "darkmagenta": [139, 0, 139], - "darkolivegreen": [85, 107, 47], - "darkorange": [255, 140, 0], - "darkorchid": [153, 50, 204], - "darkred": [139, 0, 0], - "darksalmon": [233, 150, 122], - "darkseagreen": [143, 188, 143], - "darkslateblue": [72, 61, 139], - "darkslategray": [47, 79, 79], - "darkslategrey": [47, 79, 79], - "darkturquoise": [0, 206, 209], - "darkviolet": [148, 0, 211], - "deeppink": [255, 20, 147], - "deepskyblue": [0, 191, 255], - "dimgray": [105, 105, 105], - "dimgrey": [105, 105, 105], - "dodgerblue": [30, 144, 255], - "firebrick": [178, 34, 34], - "floralwhite": [255, 250, 240], - "forestgreen": [34, 139, 34], - "fuchsia": [255, 0, 255], - "gainsboro": [220, 220, 220], - "ghostwhite": [248, 248, 255], - "gold": [255, 215, 0], - "goldenrod": [218, 165, 32], - "gray": [128, 128, 128], - "green": [0, 128, 0], - "greenyellow": [173, 255, 47], - "grey": [128, 128, 128], - "honeydew": [240, 255, 240], - "hotpink": [255, 105, 180], - "indianred": [205, 92, 92], - "indigo": [75, 0, 130], - "ivory": [255, 255, 240], - "khaki": [240, 230, 140], - "lavender": [230, 230, 250], - "lavenderblush": [255, 240, 245], - "lawngreen": [124, 252, 0], - "lemonchiffon": [255, 250, 205], - "lightblue": [173, 216, 230], - "lightcoral": [240, 128, 128], - "lightcyan": [224, 255, 255], - "lightgoldenrodyellow": [250, 250, 210], - "lightgray": [211, 211, 211], - "lightgreen": [144, 238, 144], - "lightgrey": [211, 211, 211], - "lightpink": [255, 182, 193], - "lightsalmon": [255, 160, 122], - "lightseagreen": [32, 178, 170], - "lightskyblue": [135, 206, 250], - "lightslategray": [119, 136, 153], - "lightslategrey": [119, 136, 153], - "lightsteelblue": [176, 196, 222], - "lightyellow": [255, 255, 224], - "lime": [0, 255, 0], - "limegreen": [50, 205, 50], - "linen": [250, 240, 230], - "magenta": [255, 0, 255], - "maroon": [128, 0, 0], - "mediumaquamarine": [102, 205, 170], - "mediumblue": [0, 0, 205], - "mediumorchid": [186, 85, 211], - "mediumpurple": [147, 112, 219], - "mediumseagreen": [60, 179, 113], - "mediumslateblue": [123, 104, 238], - "mediumspringgreen": [0, 250, 154], - "mediumturquoise": [72, 209, 204], - "mediumvioletred": [199, 21, 133], - "midnightblue": [25, 25, 112], - "mintcream": [245, 255, 250], - "mistyrose": [255, 228, 225], - "moccasin": [255, 228, 181], - "navajowhite": [255, 222, 173], - "navy": [0, 0, 128], - "oldlace": [253, 245, 230], - "olive": [128, 128, 0], - "olivedrab": [107, 142, 35], - "orange": [255, 165, 0], - "orangered": [255, 69, 0], - "orchid": [218, 112, 214], - "palegoldenrod": [238, 232, 170], - "palegreen": [152, 251, 152], - "paleturquoise": [175, 238, 238], - "palevioletred": [219, 112, 147], - "papayawhip": [255, 239, 213], - "peachpuff": [255, 218, 185], - "peru": [205, 133, 63], - "pink": [255, 192, 203], - "plum": [221, 160, 221], - "powderblue": [176, 224, 230], - "purple": [128, 0, 128], - "rebeccapurple": [102, 51, 153], - "red": [255, 0, 0], - "rosybrown": [188, 143, 143], - "royalblue": [65, 105, 225], - "saddlebrown": [139, 69, 19], - "salmon": [250, 128, 114], - "sandybrown": [244, 164, 96], - "seagreen": [46, 139, 87], - "seashell": [255, 245, 238], - "sienna": [160, 82, 45], - "silver": [192, 192, 192], - "skyblue": [135, 206, 235], - "slateblue": [106, 90, 205], - "slategray": [112, 128, 144], - "slategrey": [112, 128, 144], - "snow": [255, 250, 250], - "springgreen": [0, 255, 127], - "steelblue": [70, 130, 180], - "tan": [210, 180, 140], - "teal": [0, 128, 128], - "thistle": [216, 191, 216], - "tomato": [255, 99, 71], - "turquoise": [64, 224, 208], - "violet": [238, 130, 238], - "wheat": [245, 222, 179], - "white": [255, 255, 255], - "whitesmoke": [245, 245, 245], - "yellow": [255, 255, 0], - "yellowgreen": [154, 205, 50] -}; + + +module.exports = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; /***/ }), @@ -24189,7 +24189,7 @@ module.exports = function (x, opts) { "use strict"; -var has = __webpack_require__(263); +var hasOwn = __webpack_require__(263); function specifierIncluded(current, specifier) { var nodeParts = current.split('.'); @@ -24254,7 +24254,7 @@ function versionIncluded(nodeVersion, specifierValue) { var data = __webpack_require__(266); module.exports = function isCore(x, nodeVersion) { - return has(data, x) && versionIncluded(nodeVersion, data[x]); + return hasOwn(data, x) && versionIncluded(nodeVersion, data[x]); }; @@ -24265,9 +24265,12 @@ module.exports = function isCore(x, nodeVersion) { "use strict"; +var call = Function.prototype.call; +var $hasOwn = Object.prototype.hasOwnProperty; var bind = __webpack_require__(264); -module.exports = bind.call(Function.call, Object.prototype.hasOwnProperty); +/** @type {(o: {}, p: PropertyKey) => p is keyof o} */ +module.exports = bind.call(call, $hasOwn); /***/ }), @@ -24292,43 +24295,75 @@ module.exports = Function.prototype.bind || implementation; /* eslint no-invalid-this: 1 */ var ERROR_MESSAGE = 'Function.prototype.bind called on incompatible '; -var slice = Array.prototype.slice; var toStr = Object.prototype.toString; +var max = Math.max; var funcType = '[object Function]'; +var concatty = function concatty(a, b) { + var arr = []; + + for (var i = 0; i < a.length; i += 1) { + arr[i] = a[i]; + } + for (var j = 0; j < b.length; j += 1) { + arr[j + a.length] = b[j]; + } + + return arr; +}; + +var slicy = function slicy(arrLike, offset) { + var arr = []; + for (var i = offset || 0, j = 0; i < arrLike.length; i += 1, j += 1) { + arr[j] = arrLike[i]; + } + return arr; +}; + +var joiny = function (arr, joiner) { + var str = ''; + for (var i = 0; i < arr.length; i += 1) { + str += arr[i]; + if (i + 1 < arr.length) { + str += joiner; + } + } + return str; +}; + module.exports = function bind(that) { var target = this; - if (typeof target !== 'function' || toStr.call(target) !== funcType) { + if (typeof target !== 'function' || toStr.apply(target) !== funcType) { throw new TypeError(ERROR_MESSAGE + target); } - var args = slice.call(arguments, 1); + var args = slicy(arguments, 1); var bound; var binder = function () { if (this instanceof bound) { var result = target.apply( this, - args.concat(slice.call(arguments)) + concatty(args, arguments) ); if (Object(result) === result) { return result; } return this; - } else { - return target.apply( - that, - args.concat(slice.call(arguments)) - ); } + return target.apply( + that, + concatty(args, arguments) + ); + }; - var boundLength = Math.max(0, target.length - args.length); + var boundLength = max(0, target.length - args.length); var boundArgs = []; for (var i = 0; i < boundLength; i++) { - boundArgs.push('$' + i); + boundArgs[i] = '$' + i; } - bound = Function('binder', 'return function (' + boundArgs.join(',') + '){ return binder.apply(this,arguments); }')(binder); + bound = Function('binder', 'return function (' + joiny(boundArgs, ',') + '){ return binder.apply(this,arguments); }')(binder); if (target.prototype) { var Empty = function Empty() {}; @@ -24345,7 +24380,7 @@ module.exports = function bind(that) { /* 266 */ /***/ (function(module) { -module.exports = JSON.parse("{\"assert\":true,\"node:assert\":[\">= 14.18 && < 15\",\">= 16\"],\"assert/strict\":\">= 15\",\"node:assert/strict\":\">= 16\",\"async_hooks\":\">= 8\",\"node:async_hooks\":[\">= 14.18 && < 15\",\">= 16\"],\"buffer_ieee754\":\">= 0.5 && < 0.9.7\",\"buffer\":true,\"node:buffer\":[\">= 14.18 && < 15\",\">= 16\"],\"child_process\":true,\"node:child_process\":[\">= 14.18 && < 15\",\">= 16\"],\"cluster\":\">= 0.5\",\"node:cluster\":[\">= 14.18 && < 15\",\">= 16\"],\"console\":true,\"node:console\":[\">= 14.18 && < 15\",\">= 16\"],\"constants\":true,\"node:constants\":[\">= 14.18 && < 15\",\">= 16\"],\"crypto\":true,\"node:crypto\":[\">= 14.18 && < 15\",\">= 16\"],\"_debug_agent\":\">= 1 && < 8\",\"_debugger\":\"< 8\",\"dgram\":true,\"node:dgram\":[\">= 14.18 && < 15\",\">= 16\"],\"diagnostics_channel\":[\">= 14.17 && < 15\",\">= 15.1\"],\"node:diagnostics_channel\":[\">= 14.18 && < 15\",\">= 16\"],\"dns\":true,\"node:dns\":[\">= 14.18 && < 15\",\">= 16\"],\"dns/promises\":\">= 15\",\"node:dns/promises\":\">= 16\",\"domain\":\">= 0.7.12\",\"node:domain\":[\">= 14.18 && < 15\",\">= 16\"],\"events\":true,\"node:events\":[\">= 14.18 && < 15\",\">= 16\"],\"freelist\":\"< 6\",\"fs\":true,\"node:fs\":[\">= 14.18 && < 15\",\">= 16\"],\"fs/promises\":[\">= 10 && < 10.1\",\">= 14\"],\"node:fs/promises\":[\">= 14.18 && < 15\",\">= 16\"],\"_http_agent\":\">= 0.11.1\",\"node:_http_agent\":[\">= 14.18 && < 15\",\">= 16\"],\"_http_client\":\">= 0.11.1\",\"node:_http_client\":[\">= 14.18 && < 15\",\">= 16\"],\"_http_common\":\">= 0.11.1\",\"node:_http_common\":[\">= 14.18 && < 15\",\">= 16\"],\"_http_incoming\":\">= 0.11.1\",\"node:_http_incoming\":[\">= 14.18 && < 15\",\">= 16\"],\"_http_outgoing\":\">= 0.11.1\",\"node:_http_outgoing\":[\">= 14.18 && < 15\",\">= 16\"],\"_http_server\":\">= 0.11.1\",\"node:_http_server\":[\">= 14.18 && < 15\",\">= 16\"],\"http\":true,\"node:http\":[\">= 14.18 && < 15\",\">= 16\"],\"http2\":\">= 8.8\",\"node:http2\":[\">= 14.18 && < 15\",\">= 16\"],\"https\":true,\"node:https\":[\">= 14.18 && < 15\",\">= 16\"],\"inspector\":\">= 8\",\"node:inspector\":[\">= 14.18 && < 15\",\">= 16\"],\"inspector/promises\":[\">= 19\"],\"node:inspector/promises\":[\">= 19\"],\"_linklist\":\"< 8\",\"module\":true,\"node:module\":[\">= 14.18 && < 15\",\">= 16\"],\"net\":true,\"node:net\":[\">= 14.18 && < 15\",\">= 16\"],\"node-inspect/lib/_inspect\":\">= 7.6 && < 12\",\"node-inspect/lib/internal/inspect_client\":\">= 7.6 && < 12\",\"node-inspect/lib/internal/inspect_repl\":\">= 7.6 && < 12\",\"os\":true,\"node:os\":[\">= 14.18 && < 15\",\">= 16\"],\"path\":true,\"node:path\":[\">= 14.18 && < 15\",\">= 16\"],\"path/posix\":\">= 15.3\",\"node:path/posix\":\">= 16\",\"path/win32\":\">= 15.3\",\"node:path/win32\":\">= 16\",\"perf_hooks\":\">= 8.5\",\"node:perf_hooks\":[\">= 14.18 && < 15\",\">= 16\"],\"process\":\">= 1\",\"node:process\":[\">= 14.18 && < 15\",\">= 16\"],\"punycode\":\">= 0.5\",\"node:punycode\":[\">= 14.18 && < 15\",\">= 16\"],\"querystring\":true,\"node:querystring\":[\">= 14.18 && < 15\",\">= 16\"],\"readline\":true,\"node:readline\":[\">= 14.18 && < 15\",\">= 16\"],\"readline/promises\":\">= 17\",\"node:readline/promises\":\">= 17\",\"repl\":true,\"node:repl\":[\">= 14.18 && < 15\",\">= 16\"],\"smalloc\":\">= 0.11.5 && < 3\",\"_stream_duplex\":\">= 0.9.4\",\"node:_stream_duplex\":[\">= 14.18 && < 15\",\">= 16\"],\"_stream_transform\":\">= 0.9.4\",\"node:_stream_transform\":[\">= 14.18 && < 15\",\">= 16\"],\"_stream_wrap\":\">= 1.4.1\",\"node:_stream_wrap\":[\">= 14.18 && < 15\",\">= 16\"],\"_stream_passthrough\":\">= 0.9.4\",\"node:_stream_passthrough\":[\">= 14.18 && < 15\",\">= 16\"],\"_stream_readable\":\">= 0.9.4\",\"node:_stream_readable\":[\">= 14.18 && < 15\",\">= 16\"],\"_stream_writable\":\">= 0.9.4\",\"node:_stream_writable\":[\">= 14.18 && < 15\",\">= 16\"],\"stream\":true,\"node:stream\":[\">= 14.18 && < 15\",\">= 16\"],\"stream/consumers\":\">= 16.7\",\"node:stream/consumers\":\">= 16.7\",\"stream/promises\":\">= 15\",\"node:stream/promises\":\">= 16\",\"stream/web\":\">= 16.5\",\"node:stream/web\":\">= 16.5\",\"string_decoder\":true,\"node:string_decoder\":[\">= 14.18 && < 15\",\">= 16\"],\"sys\":[\">= 0.4 && < 0.7\",\">= 0.8\"],\"node:sys\":[\">= 14.18 && < 15\",\">= 16\"],\"node:test\":[\">= 16.17 && < 17\",\">= 18\"],\"timers\":true,\"node:timers\":[\">= 14.18 && < 15\",\">= 16\"],\"timers/promises\":\">= 15\",\"node:timers/promises\":\">= 16\",\"_tls_common\":\">= 0.11.13\",\"node:_tls_common\":[\">= 14.18 && < 15\",\">= 16\"],\"_tls_legacy\":\">= 0.11.3 && < 10\",\"_tls_wrap\":\">= 0.11.3\",\"node:_tls_wrap\":[\">= 14.18 && < 15\",\">= 16\"],\"tls\":true,\"node:tls\":[\">= 14.18 && < 15\",\">= 16\"],\"trace_events\":\">= 10\",\"node:trace_events\":[\">= 14.18 && < 15\",\">= 16\"],\"tty\":true,\"node:tty\":[\">= 14.18 && < 15\",\">= 16\"],\"url\":true,\"node:url\":[\">= 14.18 && < 15\",\">= 16\"],\"util\":true,\"node:util\":[\">= 14.18 && < 15\",\">= 16\"],\"util/types\":\">= 15.3\",\"node:util/types\":\">= 16\",\"v8/tools/arguments\":\">= 10 && < 12\",\"v8/tools/codemap\":[\">= 4.4 && < 5\",\">= 5.2 && < 12\"],\"v8/tools/consarray\":[\">= 4.4 && < 5\",\">= 5.2 && < 12\"],\"v8/tools/csvparser\":[\">= 4.4 && < 5\",\">= 5.2 && < 12\"],\"v8/tools/logreader\":[\">= 4.4 && < 5\",\">= 5.2 && < 12\"],\"v8/tools/profile_view\":[\">= 4.4 && < 5\",\">= 5.2 && < 12\"],\"v8/tools/splaytree\":[\">= 4.4 && < 5\",\">= 5.2 && < 12\"],\"v8\":\">= 1\",\"node:v8\":[\">= 14.18 && < 15\",\">= 16\"],\"vm\":true,\"node:vm\":[\">= 14.18 && < 15\",\">= 16\"],\"wasi\":\">= 13.4 && < 13.5\",\"worker_threads\":\">= 11.7\",\"node:worker_threads\":[\">= 14.18 && < 15\",\">= 16\"],\"zlib\":\">= 0.5\",\"node:zlib\":[\">= 14.18 && < 15\",\">= 16\"]}"); +module.exports = JSON.parse("{\"assert\":true,\"node:assert\":[\">= 14.18 && < 15\",\">= 16\"],\"assert/strict\":\">= 15\",\"node:assert/strict\":\">= 16\",\"async_hooks\":\">= 8\",\"node:async_hooks\":[\">= 14.18 && < 15\",\">= 16\"],\"buffer_ieee754\":\">= 0.5 && < 0.9.7\",\"buffer\":true,\"node:buffer\":[\">= 14.18 && < 15\",\">= 16\"],\"child_process\":true,\"node:child_process\":[\">= 14.18 && < 15\",\">= 16\"],\"cluster\":\">= 0.5\",\"node:cluster\":[\">= 14.18 && < 15\",\">= 16\"],\"console\":true,\"node:console\":[\">= 14.18 && < 15\",\">= 16\"],\"constants\":true,\"node:constants\":[\">= 14.18 && < 15\",\">= 16\"],\"crypto\":true,\"node:crypto\":[\">= 14.18 && < 15\",\">= 16\"],\"_debug_agent\":\">= 1 && < 8\",\"_debugger\":\"< 8\",\"dgram\":true,\"node:dgram\":[\">= 14.18 && < 15\",\">= 16\"],\"diagnostics_channel\":[\">= 14.17 && < 15\",\">= 15.1\"],\"node:diagnostics_channel\":[\">= 14.18 && < 15\",\">= 16\"],\"dns\":true,\"node:dns\":[\">= 14.18 && < 15\",\">= 16\"],\"dns/promises\":\">= 15\",\"node:dns/promises\":\">= 16\",\"domain\":\">= 0.7.12\",\"node:domain\":[\">= 14.18 && < 15\",\">= 16\"],\"events\":true,\"node:events\":[\">= 14.18 && < 15\",\">= 16\"],\"freelist\":\"< 6\",\"fs\":true,\"node:fs\":[\">= 14.18 && < 15\",\">= 16\"],\"fs/promises\":[\">= 10 && < 10.1\",\">= 14\"],\"node:fs/promises\":[\">= 14.18 && < 15\",\">= 16\"],\"_http_agent\":\">= 0.11.1\",\"node:_http_agent\":[\">= 14.18 && < 15\",\">= 16\"],\"_http_client\":\">= 0.11.1\",\"node:_http_client\":[\">= 14.18 && < 15\",\">= 16\"],\"_http_common\":\">= 0.11.1\",\"node:_http_common\":[\">= 14.18 && < 15\",\">= 16\"],\"_http_incoming\":\">= 0.11.1\",\"node:_http_incoming\":[\">= 14.18 && < 15\",\">= 16\"],\"_http_outgoing\":\">= 0.11.1\",\"node:_http_outgoing\":[\">= 14.18 && < 15\",\">= 16\"],\"_http_server\":\">= 0.11.1\",\"node:_http_server\":[\">= 14.18 && < 15\",\">= 16\"],\"http\":true,\"node:http\":[\">= 14.18 && < 15\",\">= 16\"],\"http2\":\">= 8.8\",\"node:http2\":[\">= 14.18 && < 15\",\">= 16\"],\"https\":true,\"node:https\":[\">= 14.18 && < 15\",\">= 16\"],\"inspector\":\">= 8\",\"node:inspector\":[\">= 14.18 && < 15\",\">= 16\"],\"inspector/promises\":[\">= 19\"],\"node:inspector/promises\":[\">= 19\"],\"_linklist\":\"< 8\",\"module\":true,\"node:module\":[\">= 14.18 && < 15\",\">= 16\"],\"net\":true,\"node:net\":[\">= 14.18 && < 15\",\">= 16\"],\"node-inspect/lib/_inspect\":\">= 7.6 && < 12\",\"node-inspect/lib/internal/inspect_client\":\">= 7.6 && < 12\",\"node-inspect/lib/internal/inspect_repl\":\">= 7.6 && < 12\",\"os\":true,\"node:os\":[\">= 14.18 && < 15\",\">= 16\"],\"path\":true,\"node:path\":[\">= 14.18 && < 15\",\">= 16\"],\"path/posix\":\">= 15.3\",\"node:path/posix\":\">= 16\",\"path/win32\":\">= 15.3\",\"node:path/win32\":\">= 16\",\"perf_hooks\":\">= 8.5\",\"node:perf_hooks\":[\">= 14.18 && < 15\",\">= 16\"],\"process\":\">= 1\",\"node:process\":[\">= 14.18 && < 15\",\">= 16\"],\"punycode\":\">= 0.5\",\"node:punycode\":[\">= 14.18 && < 15\",\">= 16\"],\"querystring\":true,\"node:querystring\":[\">= 14.18 && < 15\",\">= 16\"],\"readline\":true,\"node:readline\":[\">= 14.18 && < 15\",\">= 16\"],\"readline/promises\":\">= 17\",\"node:readline/promises\":\">= 17\",\"repl\":true,\"node:repl\":[\">= 14.18 && < 15\",\">= 16\"],\"smalloc\":\">= 0.11.5 && < 3\",\"_stream_duplex\":\">= 0.9.4\",\"node:_stream_duplex\":[\">= 14.18 && < 15\",\">= 16\"],\"_stream_transform\":\">= 0.9.4\",\"node:_stream_transform\":[\">= 14.18 && < 15\",\">= 16\"],\"_stream_wrap\":\">= 1.4.1\",\"node:_stream_wrap\":[\">= 14.18 && < 15\",\">= 16\"],\"_stream_passthrough\":\">= 0.9.4\",\"node:_stream_passthrough\":[\">= 14.18 && < 15\",\">= 16\"],\"_stream_readable\":\">= 0.9.4\",\"node:_stream_readable\":[\">= 14.18 && < 15\",\">= 16\"],\"_stream_writable\":\">= 0.9.4\",\"node:_stream_writable\":[\">= 14.18 && < 15\",\">= 16\"],\"stream\":true,\"node:stream\":[\">= 14.18 && < 15\",\">= 16\"],\"stream/consumers\":\">= 16.7\",\"node:stream/consumers\":\">= 16.7\",\"stream/promises\":\">= 15\",\"node:stream/promises\":\">= 16\",\"stream/web\":\">= 16.5\",\"node:stream/web\":\">= 16.5\",\"string_decoder\":true,\"node:string_decoder\":[\">= 14.18 && < 15\",\">= 16\"],\"sys\":[\">= 0.4 && < 0.7\",\">= 0.8\"],\"node:sys\":[\">= 14.18 && < 15\",\">= 16\"],\"test/reporters\":\">= 19.9 && < 20.2\",\"node:test/reporters\":[\">= 18.17 && < 19\",\">= 19.9\",\">= 20\"],\"node:test\":[\">= 16.17 && < 17\",\">= 18\"],\"timers\":true,\"node:timers\":[\">= 14.18 && < 15\",\">= 16\"],\"timers/promises\":\">= 15\",\"node:timers/promises\":\">= 16\",\"_tls_common\":\">= 0.11.13\",\"node:_tls_common\":[\">= 14.18 && < 15\",\">= 16\"],\"_tls_legacy\":\">= 0.11.3 && < 10\",\"_tls_wrap\":\">= 0.11.3\",\"node:_tls_wrap\":[\">= 14.18 && < 15\",\">= 16\"],\"tls\":true,\"node:tls\":[\">= 14.18 && < 15\",\">= 16\"],\"trace_events\":\">= 10\",\"node:trace_events\":[\">= 14.18 && < 15\",\">= 16\"],\"tty\":true,\"node:tty\":[\">= 14.18 && < 15\",\">= 16\"],\"url\":true,\"node:url\":[\">= 14.18 && < 15\",\">= 16\"],\"util\":true,\"node:util\":[\">= 14.18 && < 15\",\">= 16\"],\"util/types\":\">= 15.3\",\"node:util/types\":\">= 16\",\"v8/tools/arguments\":\">= 10 && < 12\",\"v8/tools/codemap\":[\">= 4.4 && < 5\",\">= 5.2 && < 12\"],\"v8/tools/consarray\":[\">= 4.4 && < 5\",\">= 5.2 && < 12\"],\"v8/tools/csvparser\":[\">= 4.4 && < 5\",\">= 5.2 && < 12\"],\"v8/tools/logreader\":[\">= 4.4 && < 5\",\">= 5.2 && < 12\"],\"v8/tools/profile_view\":[\">= 4.4 && < 5\",\">= 5.2 && < 12\"],\"v8/tools/splaytree\":[\">= 4.4 && < 5\",\">= 5.2 && < 12\"],\"v8\":\">= 1\",\"node:v8\":[\">= 14.18 && < 15\",\">= 16\"],\"vm\":true,\"node:vm\":[\">= 14.18 && < 15\",\">= 16\"],\"wasi\":[\">= 13.4 && < 13.5\",\">= 18.17 && < 19\",\">= 20\"],\"node:wasi\":[\">= 18.17 && < 19\",\">= 20\"],\"worker_threads\":\">= 11.7\",\"node:worker_threads\":[\">= 14.18 && < 15\",\">= 16\"],\"zlib\":\">= 0.5\",\"node:zlib\":[\">= 14.18 && < 15\",\">= 16\"]}"); /***/ }), /* 267 */ @@ -25991,7 +26026,7 @@ const YARN_EXEC = process.env.npm_execpath || 'yarn'; * Install all dependencies in the given directory */ async function installInDir(directory, extraArgs = [], useAdd = false) { - const options = [useAdd ? 'add' : 'install', '--non-interactive', ...extraArgs]; + const options = [useAdd ? 'add' : 'install', '--non-interactive', '--ignore-engines', ...extraArgs]; // We pass the mutex flag to ensure only one instance of yarn runs at any // given time (e.g. to avoid conflicts). @@ -42996,8 +43031,8 @@ const braces = (input, options = {}) => { let output = []; if (Array.isArray(input)) { - for (let pattern of input) { - let result = braces.create(pattern, options); + for (const pattern of input) { + const result = braces.create(pattern, options); if (Array.isArray(result)) { output.push(...result); } else { @@ -43131,7 +43166,7 @@ braces.create = (input, options = {}) => { return [input]; } - return options.expand !== true + return options.expand !== true ? braces.compile(input, options) : braces.expand(input, options); }; @@ -43153,9 +43188,9 @@ module.exports = braces; const utils = __webpack_require__(369); module.exports = (ast, options = {}) => { - let stringify = (node, parent = {}) => { - let invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent); - let invalidNode = node.invalid === true && options.escapeInvalid === true; + const stringify = (node, parent = {}) => { + const invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent); + const invalidNode = node.invalid === true && options.escapeInvalid === true; let output = ''; if (node.value) { @@ -43170,7 +43205,7 @@ module.exports = (ast, options = {}) => { } if (node.nodes) { - for (let child of node.nodes) { + for (const child of node.nodes) { output += stringify(child); } } @@ -43220,7 +43255,7 @@ exports.exceedsLimit = (min, max, step = 1, limit) => { */ exports.escapeNode = (block, n = 0, type) => { - let node = block.nodes[n]; + const node = block.nodes[n]; if (!node) return; if ((type && node.type === type) || node.type === 'open' || node.type === 'close') { @@ -43289,13 +43324,23 @@ exports.reduce = nodes => nodes.reduce((acc, node) => { exports.flatten = (...args) => { const result = []; + const flat = arr => { for (let i = 0; i < arr.length; i++) { - let ele = arr[i]; - Array.isArray(ele) ? flat(ele, result) : ele !== void 0 && result.push(ele); + const ele = arr[i]; + + if (Array.isArray(ele)) { + flat(ele); + continue; + } + + if (ele !== undefined) { + result.push(ele); + } } return result; }; + flat(args); return result; }; @@ -43312,30 +43357,32 @@ const fill = __webpack_require__(371); const utils = __webpack_require__(369); const compile = (ast, options = {}) => { - let walk = (node, parent = {}) => { - let invalidBlock = utils.isInvalidBrace(parent); - let invalidNode = node.invalid === true && options.escapeInvalid === true; - let invalid = invalidBlock === true || invalidNode === true; - let prefix = options.escapeInvalid === true ? '\\' : ''; + const walk = (node, parent = {}) => { + const invalidBlock = utils.isInvalidBrace(parent); + const invalidNode = node.invalid === true && options.escapeInvalid === true; + const invalid = invalidBlock === true || invalidNode === true; + const prefix = options.escapeInvalid === true ? '\\' : ''; let output = ''; if (node.isOpen === true) { return prefix + node.value; } + if (node.isClose === true) { + console.log('node.isClose', prefix, node.value); return prefix + node.value; } if (node.type === 'open') { - return invalid ? (prefix + node.value) : '('; + return invalid ? prefix + node.value : '('; } if (node.type === 'close') { - return invalid ? (prefix + node.value) : ')'; + return invalid ? prefix + node.value : ')'; } if (node.type === 'comma') { - return node.prev.type === 'comma' ? '' : (invalid ? node.value : '|'); + return node.prev.type === 'comma' ? '' : invalid ? node.value : '|'; } if (node.value) { @@ -43343,8 +43390,8 @@ const compile = (ast, options = {}) => { } if (node.nodes && node.ranges > 0) { - let args = utils.reduce(node.nodes); - let range = fill(...args, { ...options, wrap: false, toRegex: true }); + const args = utils.reduce(node.nodes); + const range = fill(...args, { ...options, wrap: false, toRegex: true, strictZeros: true }); if (range.length !== 0) { return args.length > 1 && range.length > 1 ? `(${range})` : range; @@ -43352,10 +43399,11 @@ const compile = (ast, options = {}) => { } if (node.nodes) { - for (let child of node.nodes) { + for (const child of node.nodes) { output += walk(child, node); } } + return output; }; @@ -43432,7 +43480,7 @@ const toMaxLen = (input, maxLength) => { return negative ? ('-' + input) : input; }; -const toSequence = (parts, options) => { +const toSequence = (parts, options, maxLen) => { parts.negatives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); parts.positives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); @@ -43442,11 +43490,11 @@ const toSequence = (parts, options) => { let result; if (parts.positives.length) { - positives = parts.positives.join('|'); + positives = parts.positives.map(v => toMaxLen(String(v), maxLen)).join('|'); } if (parts.negatives.length) { - negatives = `-(${prefix}${parts.negatives.join('|')})`; + negatives = `-(${prefix}${parts.negatives.map(v => toMaxLen(String(v), maxLen)).join('|')})`; } if (positives && negatives) { @@ -43544,7 +43592,7 @@ const fillNumbers = (start, end, step = 1, options = {}) => { if (options.toRegex === true) { return step > 1 - ? toSequence(parts, options) + ? toSequence(parts, options, maxLen) : toRegex(range, null, { wrap: false, ...options }); } @@ -43556,7 +43604,6 @@ const fillLetters = (start, end, step = 1, options = {}) => { return invalidRange(start, end, options); } - let format = options.transform || (val => String.fromCharCode(val)); let a = `${start}`.charCodeAt(0); let b = `${end}`.charCodeAt(0); @@ -43953,7 +44000,7 @@ const stringify = __webpack_require__(368); const utils = __webpack_require__(369); const append = (queue = '', stash = '', enclose = false) => { - let result = []; + const result = []; queue = [].concat(queue); stash = [].concat(stash); @@ -43963,15 +44010,15 @@ const append = (queue = '', stash = '', enclose = false) => { return enclose ? utils.flatten(stash).map(ele => `{${ele}}`) : stash; } - for (let item of queue) { + for (const item of queue) { if (Array.isArray(item)) { - for (let value of item) { + for (const value of item) { result.push(append(value, stash, enclose)); } } else { for (let ele of stash) { if (enclose === true && typeof ele === 'string') ele = `{${ele}}`; - result.push(Array.isArray(ele) ? append(item, ele, enclose) : (item + ele)); + result.push(Array.isArray(ele) ? append(item, ele, enclose) : item + ele); } } } @@ -43979,9 +44026,9 @@ const append = (queue = '', stash = '', enclose = false) => { }; const expand = (ast, options = {}) => { - let rangeLimit = options.rangeLimit === void 0 ? 1000 : options.rangeLimit; + const rangeLimit = options.rangeLimit === undefined ? 1000 : options.rangeLimit; - let walk = (node, parent = {}) => { + const walk = (node, parent = {}) => { node.queue = []; let p = parent; @@ -44003,7 +44050,7 @@ const expand = (ast, options = {}) => { } if (node.nodes && node.ranges > 0) { - let args = utils.reduce(node.nodes); + const args = utils.reduce(node.nodes); if (utils.exceedsLimit(...args, options.step, rangeLimit)) { throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.'); @@ -44019,7 +44066,7 @@ const expand = (ast, options = {}) => { return; } - let enclose = utils.encloseBrace(node); + const enclose = utils.encloseBrace(node); let queue = node.queue; let block = node; @@ -44029,7 +44076,7 @@ const expand = (ast, options = {}) => { } for (let i = 0; i < node.nodes.length; i++) { - let child = node.nodes[i]; + const child = node.nodes[i]; if (child.type === 'comma' && node.type === 'brace') { if (i === 1) queue.push(''); @@ -44101,22 +44148,21 @@ const parse = (input, options = {}) => { throw new TypeError('Expected a string'); } - let opts = options || {}; - let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + const opts = options || {}; + const max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; if (input.length > max) { throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`); } - let ast = { type: 'root', input, nodes: [] }; - let stack = [ast]; + const ast = { type: 'root', input, nodes: [] }; + const stack = [ast]; let block = ast; let prev = ast; let brackets = 0; - let length = input.length; + const length = input.length; let index = 0; let depth = 0; let value; - let memo = {}; /** * Helpers @@ -44179,7 +44225,6 @@ const parse = (input, options = {}) => { if (value === CHAR_LEFT_SQUARE_BRACKET) { brackets++; - let closed = true; let next; while (index < length && (next = advance())) { @@ -44235,7 +44280,7 @@ const parse = (input, options = {}) => { */ if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) { - let open = value; + const open = value; let next; if (options.keepQuotes !== true) { @@ -44267,8 +44312,8 @@ const parse = (input, options = {}) => { if (value === CHAR_LEFT_CURLY_BRACE) { depth++; - let dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true; - let brace = { + const dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true; + const brace = { type: 'brace', open: true, close: false, @@ -44295,7 +44340,7 @@ const parse = (input, options = {}) => { continue; } - let type = 'close'; + const type = 'close'; block = stack.pop(); block.close = true; @@ -44313,7 +44358,7 @@ const parse = (input, options = {}) => { if (value === CHAR_COMMA && depth > 0) { if (block.ranges > 0) { block.ranges = 0; - let open = block.nodes.shift(); + const open = block.nodes.shift(); block.nodes = [open, { type: 'text', value: stringify(block) }]; } @@ -44327,7 +44372,7 @@ const parse = (input, options = {}) => { */ if (value === CHAR_DOT && depth > 0 && block.commas === 0) { - let siblings = block.nodes; + const siblings = block.nodes; if (depth === 0 || siblings.length === 0) { push({ type: 'text', value }); @@ -44354,7 +44399,7 @@ const parse = (input, options = {}) => { if (prev.type === 'range') { siblings.pop(); - let before = siblings[siblings.length - 1]; + const before = siblings[siblings.length - 1]; before.value += prev.value + value; prev = before; block.ranges--; @@ -44387,8 +44432,8 @@ const parse = (input, options = {}) => { }); // get the location of the block on parent.nodes (block's siblings) - let parent = stack[stack.length - 1]; - let index = parent.nodes.indexOf(block); + const parent = stack[stack.length - 1]; + const index = parent.nodes.indexOf(block); // replace the (invalid) block with it's nodes parent.nodes.splice(index, 1, ...block.nodes); } @@ -44409,7 +44454,7 @@ module.exports = parse; module.exports = { - MAX_LENGTH: 1024 * 64, + MAX_LENGTH: 10000, // Digits CHAR_0: '0', /* 0 */ @@ -59089,7 +59134,6 @@ async function copyToBuild(project, opensearchDashboardsRoot, buildRoot) { await (0, _cpy.default)(['**/*', '!node_modules/**'], buildProjectPath, { cwd: project.getIntermediateBuildDirectory(), dot: true, - nodir: true, parents: true }); @@ -59116,11 +59160,11 @@ const os = __webpack_require__(121); const pMap = __webpack_require__(571); const arrify = __webpack_require__(567); const globby = __webpack_require__(572); -const hasGlob = __webpack_require__(752); -const cpFile = __webpack_require__(754); -const junk = __webpack_require__(763); -const pFilter = __webpack_require__(764); -const CpyError = __webpack_require__(766); +const hasGlob = __webpack_require__(575); +const cpFile = __webpack_require__(577); +const junk = __webpack_require__(586); +const pFilter = __webpack_require__(587); +const CpyError = __webpack_require__(589); const defaultOptions = { ignoreJunk: true @@ -59365,28 +59409,43 @@ module.exports = async ( "use strict"; const fs = __webpack_require__(134); -const arrayUnion = __webpack_require__(573); +const arrayUnion = __webpack_require__(353); +const merge2 = __webpack_require__(354); const glob = __webpack_require__(148); -const fastGlob = __webpack_require__(575); -const dirGlob = __webpack_require__(746); -const gitignore = __webpack_require__(749); +const fastGlob = __webpack_require__(355); +const dirGlob = __webpack_require__(427); +const gitignore = __webpack_require__(573); +const {FilterStream, UniqueStream} = __webpack_require__(574); const DEFAULT_FILTER = () => false; const isNegative = pattern => pattern[0] === '!'; const assertPatternsInput = patterns => { - if (!patterns.every(x => typeof x === 'string')) { + if (!patterns.every(pattern => typeof pattern === 'string')) { throw new TypeError('Patterns must be a string or an array of strings'); } }; -const checkCwdOption = options => { - if (options && options.cwd && !fs.statSync(options.cwd).isDirectory()) { +const checkCwdOption = (options = {}) => { + if (!options.cwd) { + return; + } + + let stat; + try { + stat = fs.statSync(options.cwd); + } catch (_) { + return; + } + + if (!stat.isDirectory()) { throw new Error('The `cwd` option must be a path to a directory'); } }; +const getPathString = p => p.stats instanceof fs.Stats ? p.path : p; + const generateGlobTasks = (patterns, taskOptions) => { patterns = arrayUnion([].concat(patterns)); assertPatternsInput(patterns); @@ -59394,27 +59453,29 @@ const generateGlobTasks = (patterns, taskOptions) => { const globTasks = []; - taskOptions = Object.assign({ + taskOptions = { ignore: [], - expandDirectories: true - }, taskOptions); + expandDirectories: true, + ...taskOptions + }; - patterns.forEach((pattern, i) => { + for (const [index, pattern] of patterns.entries()) { if (isNegative(pattern)) { - return; + continue; } const ignore = patterns - .slice(i) + .slice(index) .filter(isNegative) .map(pattern => pattern.slice(1)); - const options = Object.assign({}, taskOptions, { + const options = { + ...taskOptions, ignore: taskOptions.ignore.concat(ignore) - }); + }; globTasks.push({pattern, options}); - }); + } return globTasks; }; @@ -59426,9 +59487,15 @@ const globDirs = (task, fn) => { } if (Array.isArray(task.options.expandDirectories)) { - options = Object.assign(options, {files: task.options.expandDirectories}); + options = { + ...options, + files: task.options.expandDirectories + }; } else if (typeof task.options.expandDirectories === 'object') { - options = Object.assign(options, task.options.expandDirectories); + options = { + ...options, + ...task.options.expandDirectories + }; } return fn(task.pattern, options); @@ -59436,6 +59503,12 @@ const globDirs = (task, fn) => { const getPattern = (task, fn) => task.options.expandDirectories ? globDirs(task, fn) : [task.pattern]; +const getFilterSync = options => { + return options && options.gitignore ? + gitignore.sync({cwd: options.cwd, ignore: options.ignore}) : + DEFAULT_FILTER; +}; + const globToTask = task => glob => { const {options} = task; if (options.ignore && Array.isArray(options.ignore) && options.expandDirectories) { @@ -59448,60 +59521,61 @@ const globToTask = task => glob => { }; }; -const globby = (patterns, options) => { - let globTasks; +module.exports = async (patterns, options) => { + const globTasks = generateGlobTasks(patterns, options); - try { - globTasks = generateGlobTasks(patterns, options); - } catch (error) { - return Promise.reject(error); - } + const getFilter = async () => { + return options && options.gitignore ? + gitignore({cwd: options.cwd, ignore: options.ignore}) : + DEFAULT_FILTER; + }; - const getTasks = Promise.all(globTasks.map(task => Promise.resolve(getPattern(task, dirGlob)) - .then(globs => Promise.all(globs.map(globToTask(task)))) - )) - .then(tasks => arrayUnion(...tasks)); + const getTasks = async () => { + const tasks = await Promise.all(globTasks.map(async task => { + const globs = await getPattern(task, dirGlob); + return Promise.all(globs.map(globToTask(task))); + })); - const getFilter = () => { - return Promise.resolve( - options && options.gitignore ? - gitignore({cwd: options.cwd, ignore: options.ignore}) : - DEFAULT_FILTER - ); + return arrayUnion(...tasks); }; - return getFilter() - .then(filter => { - return getTasks - .then(tasks => Promise.all(tasks.map(task => fastGlob(task.pattern, task.options)))) - .then(paths => arrayUnion(...paths)) - .then(paths => paths.filter(p => !filter(p))); - }); -}; + const [filter, tasks] = await Promise.all([getFilter(), getTasks()]); + const paths = await Promise.all(tasks.map(task => fastGlob(task.pattern, task.options))); -module.exports = globby; -// TODO: Remove this for the next major release -module.exports.default = globby; + return arrayUnion(...paths).filter(path_ => !filter(getPathString(path_))); +}; module.exports.sync = (patterns, options) => { const globTasks = generateGlobTasks(patterns, options); - const getFilter = () => { - return options && options.gitignore ? - gitignore.sync({cwd: options.cwd, ignore: options.ignore}) : - DEFAULT_FILTER; - }; - const tasks = globTasks.reduce((tasks, task) => { const newTask = getPattern(task, dirGlob.sync).map(globToTask(task)); return tasks.concat(newTask); }, []); - const filter = getFilter(); + const filter = getFilterSync(options); + return tasks.reduce( (matches, task) => arrayUnion(matches, fastGlob.sync(task.pattern, task.options)), [] - ).filter(p => !filter(p)); + ).filter(path_ => !filter(path_)); +}; + +module.exports.stream = (patterns, options) => { + const globTasks = generateGlobTasks(patterns, options); + + const tasks = globTasks.reduce((tasks, task) => { + const newTask = getPattern(task, dirGlob.sync).map(globToTask(task)); + return tasks.concat(newTask); + }, []); + + const filter = getFilterSync(options); + const filterStream = new FilterStream(p => !filter(p)); + const uniqueStream = new UniqueStream(); + + return merge2(tasks.map(task => fastGlob.stream(task.pattern, task.options))) + .pipe(filterStream) + .pipe(uniqueStream); }; module.exports.generateGlobTasks = generateGlobTasks; @@ -59519,23684 +59593,226 @@ module.exports.gitignore = gitignore; "use strict"; -var arrayUniq = __webpack_require__(574); +const {promisify} = __webpack_require__(112); +const fs = __webpack_require__(134); +const path = __webpack_require__(4); +const fastGlob = __webpack_require__(355); +const gitIgnore = __webpack_require__(430); +const slash = __webpack_require__(431); -module.exports = function () { - return arrayUniq([].concat.apply([], arguments)); -}; +const DEFAULT_IGNORE = [ + '**/node_modules/**', + '**/flow-typed/**', + '**/coverage/**', + '**/.git' +]; +const readFileP = promisify(fs.readFile); -/***/ }), -/* 574 */ -/***/ (function(module, exports, __webpack_require__) { +const mapGitIgnorePatternTo = base => ignore => { + if (ignore.startsWith('!')) { + return '!' + path.posix.join(base, ignore.slice(1)); + } -"use strict"; + return path.posix.join(base, ignore); +}; +const parseGitIgnore = (content, options) => { + const base = slash(path.relative(options.cwd, path.dirname(options.fileName))); -// there's 3 implementations written in increasing order of efficiency + return content + .split(/\r?\n/) + .filter(Boolean) + .filter(line => !line.startsWith('#')) + .map(mapGitIgnorePatternTo(base)); +}; -// 1 - no Set type is defined -function uniqNoSet(arr) { - var ret = []; +const reduceIgnore = files => { + return files.reduce((ignores, file) => { + ignores.add(parseGitIgnore(file.content, { + cwd: file.cwd, + fileName: file.filePath + })); + return ignores; + }, gitIgnore()); +}; - for (var i = 0; i < arr.length; i++) { - if (ret.indexOf(arr[i]) === -1) { - ret.push(arr[i]); +const ensureAbsolutePathForCwd = (cwd, p) => { + if (path.isAbsolute(p)) { + if (p.startsWith(cwd)) { + return p; } - } - return ret; -} + throw new Error(`Path ${p} is not in cwd ${cwd}`); + } -// 2 - a simple Set type is defined -function uniqSet(arr) { - var seen = new Set(); - return arr.filter(function (el) { - if (!seen.has(el)) { - seen.add(el); - return true; - } + return path.join(cwd, p); +}; - return false; - }); -} +const getIsIgnoredPredecate = (ignores, cwd) => { + return p => ignores.ignores(slash(path.relative(cwd, ensureAbsolutePathForCwd(cwd, p)))); +}; -// 3 - a standard Set type is defined and it has a forEach method -function uniqSetWithForEach(arr) { - var ret = []; +const getFile = async (file, cwd) => { + const filePath = path.join(cwd, file); + const content = await readFileP(filePath, 'utf8'); - (new Set(arr)).forEach(function (el) { - ret.push(el); - }); + return { + cwd, + filePath, + content + }; +}; - return ret; -} +const getFileSync = (file, cwd) => { + const filePath = path.join(cwd, file); + const content = fs.readFileSync(filePath, 'utf8'); -// V8 currently has a broken implementation -// https://github.com/joyent/node/issues/8449 -function doesForEachActuallyWork() { - var ret = false; + return { + cwd, + filePath, + content + }; +}; - (new Set([true])).forEach(function (el) { - ret = el; - }); +const normalizeOptions = ({ + ignore = [], + cwd = slash(process.cwd()) +} = {}) => { + return {ignore, cwd}; +}; - return ret === true; -} +module.exports = async options => { + options = normalizeOptions(options); -if ('Set' in global) { - if (typeof Set.prototype.forEach === 'function' && doesForEachActuallyWork()) { - module.exports = uniqSetWithForEach; - } else { - module.exports = uniqSet; - } -} else { - module.exports = uniqNoSet; -} + const paths = await fastGlob('**/.gitignore', { + ignore: DEFAULT_IGNORE.concat(options.ignore), + cwd: options.cwd + }); + const files = await Promise.all(paths.map(file => getFile(file, options.cwd))); + const ignores = reduceIgnore(files); -/***/ }), -/* 575 */ -/***/ (function(module, exports, __webpack_require__) { + return getIsIgnoredPredecate(ignores, options.cwd); +}; -const pkg = __webpack_require__(576); +module.exports.sync = options => { + options = normalizeOptions(options); -module.exports = pkg.async; -module.exports.default = pkg.async; + const paths = fastGlob.sync('**/.gitignore', { + ignore: DEFAULT_IGNORE.concat(options.ignore), + cwd: options.cwd + }); -module.exports.async = pkg.async; -module.exports.sync = pkg.sync; -module.exports.stream = pkg.stream; + const files = paths.map(file => getFileSync(file, options.cwd)); + const ignores = reduceIgnore(files); -module.exports.generateTasks = pkg.generateTasks; + return getIsIgnoredPredecate(ignores, options.cwd); +}; /***/ }), -/* 576 */ +/* 574 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -var optionsManager = __webpack_require__(577); -var taskManager = __webpack_require__(578); -var reader_async_1 = __webpack_require__(717); -var reader_stream_1 = __webpack_require__(741); -var reader_sync_1 = __webpack_require__(742); -var arrayUtils = __webpack_require__(744); -var streamUtils = __webpack_require__(745); -/** - * Synchronous API. - */ -function sync(source, opts) { - assertPatternsInput(source); - var works = getWorks(source, reader_sync_1.default, opts); - return arrayUtils.flatten(works); -} -exports.sync = sync; -/** - * Asynchronous API. - */ -function async(source, opts) { - try { - assertPatternsInput(source); - } - catch (error) { - return Promise.reject(error); - } - var works = getWorks(source, reader_async_1.default, opts); - return Promise.all(works).then(arrayUtils.flatten); -} -exports.async = async; -/** - * Stream API. - */ -function stream(source, opts) { - assertPatternsInput(source); - var works = getWorks(source, reader_stream_1.default, opts); - return streamUtils.merge(works); -} -exports.stream = stream; -/** - * Return a set of tasks based on provided patterns. - */ -function generateTasks(source, opts) { - assertPatternsInput(source); - var patterns = [].concat(source); - var options = optionsManager.prepare(opts); - return taskManager.generate(patterns, options); -} -exports.generateTasks = generateTasks; -/** - * Returns a set of works based on provided tasks and class of the reader. - */ -function getWorks(source, _Reader, opts) { - var patterns = [].concat(source); - var options = optionsManager.prepare(opts); - var tasks = taskManager.generate(patterns, options); - var reader = new _Reader(options); - return tasks.map(reader.read, reader); -} -function assertPatternsInput(source) { - if ([].concat(source).every(isString)) { - return; - } - throw new TypeError('Patterns must be a string or an array of strings'); -} -function isString(source) { - /* tslint:disable-next-line strict-type-predicates */ - return typeof source === 'string'; -} +const {Transform} = __webpack_require__(138); -/***/ }), -/* 577 */ -/***/ (function(module, exports, __webpack_require__) { +class ObjectTransform extends Transform { + constructor() { + super({ + objectMode: true + }); + } +} -"use strict"; - -var __assign = (this && this.__assign) || function () { - __assign = Object.assign || function(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) - t[p] = s[p]; - } - return t; - }; - return __assign.apply(this, arguments); -}; -Object.defineProperty(exports, "__esModule", { value: true }); -function prepare(options) { - var opts = __assign({ cwd: process.cwd(), deep: true, ignore: [], dot: false, stats: false, onlyFiles: true, onlyDirectories: false, followSymlinkedDirectories: true, unique: true, markDirectories: false, absolute: false, nobrace: false, brace: true, noglobstar: false, globstar: true, noext: false, extension: true, nocase: false, case: true, matchBase: false, transform: null }, options); - if (opts.onlyDirectories) { - opts.onlyFiles = false; - } - opts.brace = !opts.nobrace; - opts.globstar = !opts.noglobstar; - opts.extension = !opts.noext; - opts.case = !opts.nocase; - if (options) { - opts.brace = ('brace' in options ? options.brace : opts.brace); - opts.globstar = ('globstar' in options ? options.globstar : opts.globstar); - opts.extension = ('extension' in options ? options.extension : opts.extension); - opts.case = ('case' in options ? options.case : opts.case); - } - return opts; -} -exports.prepare = prepare; +class FilterStream extends ObjectTransform { + constructor(filter) { + super(); + this._filter = filter; + } + _transform(data, encoding, callback) { + if (this._filter(data)) { + this.push(data); + } -/***/ }), -/* 578 */ -/***/ (function(module, exports, __webpack_require__) { + callback(); + } +} -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(579); -/** - * Generate tasks based on parent directory of each pattern. - */ -function generate(patterns, options) { - var unixPatterns = patterns.map(patternUtils.unixifyPattern); - var unixIgnore = options.ignore.map(patternUtils.unixifyPattern); - var positivePatterns = getPositivePatterns(unixPatterns); - var negativePatterns = getNegativePatternsAsPositive(unixPatterns, unixIgnore); - /** - * When the `case` option is disabled, all patterns must be marked as dynamic, because we cannot check filepath - * directly (without read directory). - */ - var staticPatterns = !options.case ? [] : positivePatterns.filter(patternUtils.isStaticPattern); - var dynamicPatterns = !options.case ? positivePatterns : positivePatterns.filter(patternUtils.isDynamicPattern); - var staticTasks = convertPatternsToTasks(staticPatterns, negativePatterns, /* dynamic */ false); - var dynamicTasks = convertPatternsToTasks(dynamicPatterns, negativePatterns, /* dynamic */ true); - return staticTasks.concat(dynamicTasks); -} -exports.generate = generate; -/** - * Convert patterns to tasks based on parent directory of each pattern. - */ -function convertPatternsToTasks(positive, negative, dynamic) { - var positivePatternsGroup = groupPatternsByBaseDirectory(positive); - // When we have a global group – there is no reason to divide the patterns into independent tasks. - // In this case, the global task covers the rest. - if ('.' in positivePatternsGroup) { - var task = convertPatternGroupToTask('.', positive, negative, dynamic); - return [task]; - } - return convertPatternGroupsToTasks(positivePatternsGroup, negative, dynamic); -} -exports.convertPatternsToTasks = convertPatternsToTasks; -/** - * Return only positive patterns. - */ -function getPositivePatterns(patterns) { - return patternUtils.getPositivePatterns(patterns); -} -exports.getPositivePatterns = getPositivePatterns; -/** - * Return only negative patterns. - */ -function getNegativePatternsAsPositive(patterns, ignore) { - var negative = patternUtils.getNegativePatterns(patterns).concat(ignore); - var positive = negative.map(patternUtils.convertToPositivePattern); - return positive; -} -exports.getNegativePatternsAsPositive = getNegativePatternsAsPositive; -/** - * Group patterns by base directory of each pattern. - */ -function groupPatternsByBaseDirectory(patterns) { - return patterns.reduce(function (collection, pattern) { - var base = patternUtils.getBaseDirectory(pattern); - if (base in collection) { - collection[base].push(pattern); - } - else { - collection[base] = [pattern]; - } - return collection; - }, {}); -} -exports.groupPatternsByBaseDirectory = groupPatternsByBaseDirectory; -/** - * Convert group of patterns to tasks. - */ -function convertPatternGroupsToTasks(positive, negative, dynamic) { - return Object.keys(positive).map(function (base) { - return convertPatternGroupToTask(base, positive[base], negative, dynamic); - }); -} -exports.convertPatternGroupsToTasks = convertPatternGroupsToTasks; -/** - * Create a task for positive and negative patterns. - */ -function convertPatternGroupToTask(base, positive, negative, dynamic) { - return { - base: base, - dynamic: dynamic, - positive: positive, - negative: negative, - patterns: [].concat(positive, negative.map(patternUtils.convertToNegativePattern)) - }; -} -exports.convertPatternGroupToTask = convertPatternGroupToTask; +class UniqueStream extends ObjectTransform { + constructor() { + super(); + this._pushed = new Set(); + } + _transform(data, encoding, callback) { + if (!this._pushed.has(data)) { + this.push(data); + this._pushed.add(data); + } -/***/ }), -/* 579 */ -/***/ (function(module, exports, __webpack_require__) { + callback(); + } +} -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -var path = __webpack_require__(4); -var globParent = __webpack_require__(363); -var isGlob = __webpack_require__(364); -var micromatch = __webpack_require__(580); -var GLOBSTAR = '**'; -/** - * Return true for static pattern. - */ -function isStaticPattern(pattern) { - return !isDynamicPattern(pattern); -} -exports.isStaticPattern = isStaticPattern; -/** - * Return true for pattern that looks like glob. - */ -function isDynamicPattern(pattern) { - return isGlob(pattern, { strict: false }); -} -exports.isDynamicPattern = isDynamicPattern; -/** - * Convert a windows «path» to a unix-style «path». - */ -function unixifyPattern(pattern) { - return pattern.replace(/\\/g, '/'); -} -exports.unixifyPattern = unixifyPattern; -/** - * Returns negative pattern as positive pattern. - */ -function convertToPositivePattern(pattern) { - return isNegativePattern(pattern) ? pattern.slice(1) : pattern; -} -exports.convertToPositivePattern = convertToPositivePattern; -/** - * Returns positive pattern as negative pattern. - */ -function convertToNegativePattern(pattern) { - return '!' + pattern; -} -exports.convertToNegativePattern = convertToNegativePattern; -/** - * Return true if provided pattern is negative pattern. - */ -function isNegativePattern(pattern) { - return pattern.startsWith('!') && pattern[1] !== '('; -} -exports.isNegativePattern = isNegativePattern; -/** - * Return true if provided pattern is positive pattern. - */ -function isPositivePattern(pattern) { - return !isNegativePattern(pattern); -} -exports.isPositivePattern = isPositivePattern; -/** - * Extracts negative patterns from array of patterns. - */ -function getNegativePatterns(patterns) { - return patterns.filter(isNegativePattern); -} -exports.getNegativePatterns = getNegativePatterns; -/** - * Extracts positive patterns from array of patterns. - */ -function getPositivePatterns(patterns) { - return patterns.filter(isPositivePattern); -} -exports.getPositivePatterns = getPositivePatterns; -/** - * Extract base directory from provided pattern. - */ -function getBaseDirectory(pattern) { - return globParent(pattern); -} -exports.getBaseDirectory = getBaseDirectory; -/** - * Return true if provided pattern has globstar. - */ -function hasGlobStar(pattern) { - return pattern.indexOf(GLOBSTAR) !== -1; -} -exports.hasGlobStar = hasGlobStar; -/** - * Return true if provided pattern ends with slash and globstar. - */ -function endsWithSlashGlobStar(pattern) { - return pattern.endsWith('/' + GLOBSTAR); -} -exports.endsWithSlashGlobStar = endsWithSlashGlobStar; -/** - * Returns «true» when pattern ends with a slash and globstar or the last partial of the pattern is static pattern. - */ -function isAffectDepthOfReadingPattern(pattern) { - var basename = path.basename(pattern); - return endsWithSlashGlobStar(pattern) || isStaticPattern(basename); -} -exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern; -/** - * Return naive depth of provided pattern without depth of the base directory. - */ -function getNaiveDepth(pattern) { - var base = getBaseDirectory(pattern); - var patternDepth = pattern.split('/').length; - var patternBaseDepth = base.split('/').length; - /** - * This is a hack for pattern that has no base directory. - * - * This is related to the `*\something\*` pattern. - */ - if (base === '.') { - return patternDepth - patternBaseDepth; - } - return patternDepth - patternBaseDepth - 1; -} -exports.getNaiveDepth = getNaiveDepth; -/** - * Return max naive depth of provided patterns without depth of the base directory. - */ -function getMaxNaivePatternsDepth(patterns) { - return patterns.reduce(function (max, pattern) { - var depth = getNaiveDepth(pattern); - return depth > max ? depth : max; - }, 0); -} -exports.getMaxNaivePatternsDepth = getMaxNaivePatternsDepth; -/** - * Make RegExp for provided pattern. - */ -function makeRe(pattern, options) { - return micromatch.makeRe(pattern, options); -} -exports.makeRe = makeRe; -/** - * Convert patterns to regexps. - */ -function convertPatternsToRe(patterns, options) { - return patterns.map(function (pattern) { return makeRe(pattern, options); }); -} -exports.convertPatternsToRe = convertPatternsToRe; -/** - * Returns true if the entry match any of the given RegExp's. - */ -function matchAny(entry, patternsRe) { - return patternsRe.some(function (patternRe) { return patternRe.test(entry); }); -} -exports.matchAny = matchAny; +module.exports = { + FilterStream, + UniqueStream +}; /***/ }), -/* 580 */ +/* 575 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +/*! + * has-glob + * + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. + */ -/** - * Module dependencies - */ -var util = __webpack_require__(112); -var braces = __webpack_require__(581); -var toRegex = __webpack_require__(582); -var extend = __webpack_require__(595); +var isGlob = __webpack_require__(576); -/** - * Local dependencies - */ +module.exports = function hasGlob(val) { + if (val == null) return false; + if (typeof val === 'string') { + return isGlob(val); + } + if (Array.isArray(val)) { + var len = val.length; + while (len--) { + if (isGlob(val[len])) { + return true; + } + } + } + return false; +}; -var compilers = __webpack_require__(684); -var parsers = __webpack_require__(713); -var cache = __webpack_require__(714); -var utils = __webpack_require__(715); -var MAX_LENGTH = 1024 * 64; -/** - * The main function takes a list of strings and one or more - * glob patterns to use for matching. - * - * ```js - * var mm = require('micromatch'); - * mm(list, patterns[, options]); +/***/ }), +/* 576 */ +/***/ (function(module, exports, __webpack_require__) { + +/*! + * is-glob * - * console.log(mm(['a.js', 'a.txt'], ['*.js'])); - * //=> [ 'a.js' ] - * ``` - * @param {Array} `list` A list of strings to match - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Array} Returns an array of matches - * @summary false - * @api public + * Copyright (c) 2014-2016, Jon Schlinkert. + * Licensed under the MIT License. */ -function micromatch(list, patterns, options) { - patterns = utils.arrayify(patterns); - list = utils.arrayify(list); +var isExtglob = __webpack_require__(365); - var len = patterns.length; - if (list.length === 0 || len === 0) { - return []; - } - - if (len === 1) { - return micromatch.match(list, patterns[0], options); - } - - var omit = []; - var keep = []; - var idx = -1; - - while (++idx < len) { - var pattern = patterns[idx]; - - if (typeof pattern === 'string' && pattern.charCodeAt(0) === 33 /* ! */) { - omit.push.apply(omit, micromatch.match(list, pattern.slice(1), options)); - } else { - keep.push.apply(keep, micromatch.match(list, pattern, options)); - } - } - - var matches = utils.diff(keep, omit); - if (!options || options.nodupes !== false) { - return utils.unique(matches); - } - - return matches; -} - -/** - * Similar to the main function, but `pattern` must be a string. - * - * ```js - * var mm = require('micromatch'); - * mm.match(list, pattern[, options]); - * - * console.log(mm.match(['a.a', 'a.aa', 'a.b', 'a.c'], '*.a')); - * //=> ['a.a', 'a.aa'] - * ``` - * @param {Array} `list` Array of strings to match - * @param {String} `pattern` Glob pattern to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Array} Returns an array of matches - * @api public - */ - -micromatch.match = function(list, pattern, options) { - if (Array.isArray(pattern)) { - throw new TypeError('expected pattern to be a string'); - } - - var unixify = utils.unixify(options); - var isMatch = memoize('match', pattern, options, micromatch.matcher); - var matches = []; - - list = utils.arrayify(list); - var len = list.length; - var idx = -1; - - while (++idx < len) { - var ele = list[idx]; - if (ele === pattern || isMatch(ele)) { - matches.push(utils.value(ele, unixify, options)); - } - } - - // if no options were passed, uniquify results and return - if (typeof options === 'undefined') { - return utils.unique(matches); - } - - if (matches.length === 0) { - if (options.failglob === true) { - throw new Error('no matches found for "' + pattern + '"'); - } - if (options.nonull === true || options.nullglob === true) { - return [options.unescape ? utils.unescape(pattern) : pattern]; - } - } - - // if `opts.ignore` was defined, diff ignored list - if (options.ignore) { - matches = micromatch.not(matches, options.ignore, options); - } - - return options.nodupes !== false ? utils.unique(matches) : matches; -}; - -/** - * Returns true if the specified `string` matches the given glob `pattern`. - * - * ```js - * var mm = require('micromatch'); - * mm.isMatch(string, pattern[, options]); - * - * console.log(mm.isMatch('a.a', '*.a')); - * //=> true - * console.log(mm.isMatch('a.b', '*.a')); - * //=> false - * ``` - * @param {String} `string` String to match - * @param {String} `pattern` Glob pattern to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if the string matches the glob pattern. - * @api public - */ - -micromatch.isMatch = function(str, pattern, options) { - if (typeof str !== 'string') { - throw new TypeError('expected a string: "' + util.inspect(str) + '"'); - } - - if (isEmptyString(str) || isEmptyString(pattern)) { - return false; - } - - var equals = utils.equalsPattern(options); - if (equals(str)) { - return true; - } - - var isMatch = memoize('isMatch', pattern, options, micromatch.matcher); - return isMatch(str); -}; - -/** - * Returns true if some of the strings in the given `list` match any of the - * given glob `patterns`. - * - * ```js - * var mm = require('micromatch'); - * mm.some(list, patterns[, options]); - * - * console.log(mm.some(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); - * // true - * console.log(mm.some(['foo.js'], ['*.js', '!foo.js'])); - * // false - * ``` - * @param {String|Array} `list` The string or array of strings to test. Returns as soon as the first match is found. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ - -micromatch.some = function(list, patterns, options) { - if (typeof list === 'string') { - list = [list]; - } - for (var i = 0; i < list.length; i++) { - if (micromatch(list[i], patterns, options).length === 1) { - return true; - } - } - return false; -}; - -/** - * Returns true if every string in the given `list` matches - * any of the given glob `patterns`. - * - * ```js - * var mm = require('micromatch'); - * mm.every(list, patterns[, options]); - * - * console.log(mm.every('foo.js', ['foo.js'])); - * // true - * console.log(mm.every(['foo.js', 'bar.js'], ['*.js'])); - * // true - * console.log(mm.every(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); - * // false - * console.log(mm.every(['foo.js'], ['*.js', '!foo.js'])); - * // false - * ``` - * @param {String|Array} `list` The string or array of strings to test. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ - -micromatch.every = function(list, patterns, options) { - if (typeof list === 'string') { - list = [list]; - } - for (var i = 0; i < list.length; i++) { - if (micromatch(list[i], patterns, options).length !== 1) { - return false; - } - } - return true; -}; - -/** - * Returns true if **any** of the given glob `patterns` - * match the specified `string`. - * - * ```js - * var mm = require('micromatch'); - * mm.any(string, patterns[, options]); - * - * console.log(mm.any('a.a', ['b.*', '*.a'])); - * //=> true - * console.log(mm.any('a.a', 'b.*')); - * //=> false - * ``` - * @param {String|Array} `str` The string to test. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ - -micromatch.any = function(str, patterns, options) { - if (typeof str !== 'string') { - throw new TypeError('expected a string: "' + util.inspect(str) + '"'); - } - - if (isEmptyString(str) || isEmptyString(patterns)) { - return false; - } - - if (typeof patterns === 'string') { - patterns = [patterns]; - } - - for (var i = 0; i < patterns.length; i++) { - if (micromatch.isMatch(str, patterns[i], options)) { - return true; - } - } - return false; -}; - -/** - * Returns true if **all** of the given `patterns` match - * the specified string. - * - * ```js - * var mm = require('micromatch'); - * mm.all(string, patterns[, options]); - * - * console.log(mm.all('foo.js', ['foo.js'])); - * // true - * - * console.log(mm.all('foo.js', ['*.js', '!foo.js'])); - * // false - * - * console.log(mm.all('foo.js', ['*.js', 'foo.js'])); - * // true - * - * console.log(mm.all('foo.js', ['*.js', 'f*', '*o*', '*o.js'])); - * // true - * ``` - * @param {String|Array} `str` The string to test. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ - -micromatch.all = function(str, patterns, options) { - if (typeof str !== 'string') { - throw new TypeError('expected a string: "' + util.inspect(str) + '"'); - } - if (typeof patterns === 'string') { - patterns = [patterns]; - } - for (var i = 0; i < patterns.length; i++) { - if (!micromatch.isMatch(str, patterns[i], options)) { - return false; - } - } - return true; -}; - -/** - * Returns a list of strings that _**do not match any**_ of the given `patterns`. - * - * ```js - * var mm = require('micromatch'); - * mm.not(list, patterns[, options]); - * - * console.log(mm.not(['a.a', 'b.b', 'c.c'], '*.a')); - * //=> ['b.b', 'c.c'] - * ``` - * @param {Array} `list` Array of strings to match. - * @param {String|Array} `patterns` One or more glob pattern to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Array} Returns an array of strings that **do not match** the given patterns. - * @api public - */ - -micromatch.not = function(list, patterns, options) { - var opts = extend({}, options); - var ignore = opts.ignore; - delete opts.ignore; - - var unixify = utils.unixify(opts); - list = utils.arrayify(list).map(unixify); - - var matches = utils.diff(list, micromatch(list, patterns, opts)); - if (ignore) { - matches = utils.diff(matches, micromatch(list, ignore)); - } - - return opts.nodupes !== false ? utils.unique(matches) : matches; -}; - -/** - * Returns true if the given `string` contains the given pattern. Similar - * to [.isMatch](#isMatch) but the pattern can match any part of the string. - * - * ```js - * var mm = require('micromatch'); - * mm.contains(string, pattern[, options]); - * - * console.log(mm.contains('aa/bb/cc', '*b')); - * //=> true - * console.log(mm.contains('aa/bb/cc', '*d')); - * //=> false - * ``` - * @param {String} `str` The string to match. - * @param {String|Array} `patterns` Glob pattern to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if the patter matches any part of `str`. - * @api public - */ - -micromatch.contains = function(str, patterns, options) { - if (typeof str !== 'string') { - throw new TypeError('expected a string: "' + util.inspect(str) + '"'); - } - - if (typeof patterns === 'string') { - if (isEmptyString(str) || isEmptyString(patterns)) { - return false; - } - - var equals = utils.equalsPattern(patterns, options); - if (equals(str)) { - return true; - } - var contains = utils.containsPattern(patterns, options); - if (contains(str)) { - return true; - } - } - - var opts = extend({}, options, {contains: true}); - return micromatch.any(str, patterns, opts); -}; - -/** - * Returns true if the given pattern and options should enable - * the `matchBase` option. - * @return {Boolean} - * @api private - */ - -micromatch.matchBase = function(pattern, options) { - if (pattern && pattern.indexOf('/') !== -1 || !options) return false; - return options.basename === true || options.matchBase === true; -}; - -/** - * Filter the keys of the given object with the given `glob` pattern - * and `options`. Does not attempt to match nested keys. If you need this feature, - * use [glob-object][] instead. - * - * ```js - * var mm = require('micromatch'); - * mm.matchKeys(object, patterns[, options]); - * - * var obj = { aa: 'a', ab: 'b', ac: 'c' }; - * console.log(mm.matchKeys(obj, '*b')); - * //=> { ab: 'b' } - * ``` - * @param {Object} `object` The object with keys to filter. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Object} Returns an object with only keys that match the given patterns. - * @api public - */ - -micromatch.matchKeys = function(obj, patterns, options) { - if (!utils.isObject(obj)) { - throw new TypeError('expected the first argument to be an object'); - } - var keys = micromatch(Object.keys(obj), patterns, options); - return utils.pick(obj, keys); -}; - -/** - * Returns a memoized matcher function from the given glob `pattern` and `options`. - * The returned function takes a string to match as its only argument and returns - * true if the string is a match. - * - * ```js - * var mm = require('micromatch'); - * mm.matcher(pattern[, options]); - * - * var isMatch = mm.matcher('*.!(*a)'); - * console.log(isMatch('a.a')); - * //=> false - * console.log(isMatch('a.b')); - * //=> true - * ``` - * @param {String} `pattern` Glob pattern - * @param {Object} `options` See available [options](#options) for changing how matches are performed. - * @return {Function} Returns a matcher function. - * @api public - */ - -micromatch.matcher = function matcher(pattern, options) { - if (Array.isArray(pattern)) { - return compose(pattern, options, matcher); - } - - // if pattern is a regex - if (pattern instanceof RegExp) { - return test(pattern); - } - - // if pattern is invalid - if (!utils.isString(pattern)) { - throw new TypeError('expected pattern to be an array, string or regex'); - } - - // if pattern is a non-glob string - if (!utils.hasSpecialChars(pattern)) { - if (options && options.nocase === true) { - pattern = pattern.toLowerCase(); - } - return utils.matchPath(pattern, options); - } - - // if pattern is a glob string - var re = micromatch.makeRe(pattern, options); - - // if `options.matchBase` or `options.basename` is defined - if (micromatch.matchBase(pattern, options)) { - return utils.matchBasename(re, options); - } - - function test(regex) { - var equals = utils.equalsPattern(options); - var unixify = utils.unixify(options); - - return function(str) { - if (equals(str)) { - return true; - } - - if (regex.test(unixify(str))) { - return true; - } - return false; - }; - } - - var fn = test(re); - Object.defineProperty(fn, 'result', { - configurable: true, - enumerable: false, - value: re.result - }); - return fn; -}; - -/** - * Returns an array of matches captured by `pattern` in `string, or `null` if the pattern did not match. - * - * ```js - * var mm = require('micromatch'); - * mm.capture(pattern, string[, options]); - * - * console.log(mm.capture('test/*.js', 'test/foo.js')); - * //=> ['foo'] - * console.log(mm.capture('test/*.js', 'foo/bar.css')); - * //=> null - * ``` - * @param {String} `pattern` Glob pattern to use for matching. - * @param {String} `string` String to match - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns an array of captures if the string matches the glob pattern, otherwise `null`. - * @api public - */ - -micromatch.capture = function(pattern, str, options) { - var re = micromatch.makeRe(pattern, extend({capture: true}, options)); - var unixify = utils.unixify(options); - - function match() { - return function(string) { - var match = re.exec(unixify(string)); - if (!match) { - return null; - } - - return match.slice(1); - }; - } - - var capture = memoize('capture', pattern, options, match); - return capture(str); -}; - -/** - * Create a regular expression from the given glob `pattern`. - * - * ```js - * var mm = require('micromatch'); - * mm.makeRe(pattern[, options]); - * - * console.log(mm.makeRe('*.js')); - * //=> /^(?:(\.[\\\/])?(?!\.)(?=.)[^\/]*?\.js)$/ - * ``` - * @param {String} `pattern` A glob pattern to convert to regex. - * @param {Object} `options` See available [options](#options) for changing how matches are performed. - * @return {RegExp} Returns a regex created from the given pattern. - * @api public - */ - -micromatch.makeRe = function(pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected pattern to be a string'); - } - - if (pattern.length > MAX_LENGTH) { - throw new Error('expected pattern to be less than ' + MAX_LENGTH + ' characters'); - } - - function makeRe() { - var result = micromatch.create(pattern, options); - var ast_array = []; - var output = result.map(function(obj) { - obj.ast.state = obj.state; - ast_array.push(obj.ast); - return obj.output; - }); - - var regex = toRegex(output.join('|'), options); - Object.defineProperty(regex, 'result', { - configurable: true, - enumerable: false, - value: ast_array - }); - return regex; - } - - return memoize('makeRe', pattern, options, makeRe); -}; - -/** - * Expand the given brace `pattern`. - * - * ```js - * var mm = require('micromatch'); - * console.log(mm.braces('foo/{a,b}/bar')); - * //=> ['foo/(a|b)/bar'] - * - * console.log(mm.braces('foo/{a,b}/bar', {expand: true})); - * //=> ['foo/(a|b)/bar'] - * ``` - * @param {String} `pattern` String with brace pattern to expand. - * @param {Object} `options` Any [options](#options) to change how expansion is performed. See the [braces][] library for all available options. - * @return {Array} - * @api public - */ - -micromatch.braces = function(pattern, options) { - if (typeof pattern !== 'string' && !Array.isArray(pattern)) { - throw new TypeError('expected pattern to be an array or string'); - } - - function expand() { - if (options && options.nobrace === true || !/\{.*\}/.test(pattern)) { - return utils.arrayify(pattern); - } - return braces(pattern, options); - } - - return memoize('braces', pattern, options, expand); -}; - -/** - * Proxy to the [micromatch.braces](#method), for parity with - * minimatch. - */ - -micromatch.braceExpand = function(pattern, options) { - var opts = extend({}, options, {expand: true}); - return micromatch.braces(pattern, opts); -}; - -/** - * Parses the given glob `pattern` and returns an array of abstract syntax - * trees (ASTs), with the compiled `output` and optional source `map` on - * each AST. - * - * ```js - * var mm = require('micromatch'); - * mm.create(pattern[, options]); - * - * console.log(mm.create('abc/*.js')); - * // [{ options: { source: 'string', sourcemap: true }, - * // state: {}, - * // compilers: - * // { ... }, - * // output: '(\\.[\\\\\\/])?abc\\/(?!\\.)(?=.)[^\\/]*?\\.js', - * // ast: - * // { type: 'root', - * // errors: [], - * // nodes: - * // [ ... ], - * // dot: false, - * // input: 'abc/*.js' }, - * // parsingErrors: [], - * // map: - * // { version: 3, - * // sources: [ 'string' ], - * // names: [], - * // mappings: 'AAAA,GAAG,EAAC,kBAAC,EAAC,EAAE', - * // sourcesContent: [ 'abc/*.js' ] }, - * // position: { line: 1, column: 28 }, - * // content: {}, - * // files: {}, - * // idx: 6 }] - * ``` - * @param {String} `pattern` Glob pattern to parse and compile. - * @param {Object} `options` Any [options](#options) to change how parsing and compiling is performed. - * @return {Object} Returns an object with the parsed AST, compiled string and optional source map. - * @api public - */ - -micromatch.create = function(pattern, options) { - return memoize('create', pattern, options, function() { - function create(str, opts) { - return micromatch.compile(micromatch.parse(str, opts), opts); - } - - pattern = micromatch.braces(pattern, options); - var len = pattern.length; - var idx = -1; - var res = []; - - while (++idx < len) { - res.push(create(pattern[idx], options)); - } - return res; - }); -}; - -/** - * Parse the given `str` with the given `options`. - * - * ```js - * var mm = require('micromatch'); - * mm.parse(pattern[, options]); - * - * var ast = mm.parse('a/{b,c}/d'); - * console.log(ast); - * // { type: 'root', - * // errors: [], - * // input: 'a/{b,c}/d', - * // nodes: - * // [ { type: 'bos', val: '' }, - * // { type: 'text', val: 'a/' }, - * // { type: 'brace', - * // nodes: - * // [ { type: 'brace.open', val: '{' }, - * // { type: 'text', val: 'b,c' }, - * // { type: 'brace.close', val: '}' } ] }, - * // { type: 'text', val: '/d' }, - * // { type: 'eos', val: '' } ] } - * ``` - * @param {String} `str` - * @param {Object} `options` - * @return {Object} Returns an AST - * @api public - */ - -micromatch.parse = function(pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected a string'); - } - - function parse() { - var snapdragon = utils.instantiate(null, options); - parsers(snapdragon, options); - - var ast = snapdragon.parse(pattern, options); - utils.define(ast, 'snapdragon', snapdragon); - ast.input = pattern; - return ast; - } - - return memoize('parse', pattern, options, parse); -}; - -/** - * Compile the given `ast` or string with the given `options`. - * - * ```js - * var mm = require('micromatch'); - * mm.compile(ast[, options]); - * - * var ast = mm.parse('a/{b,c}/d'); - * console.log(mm.compile(ast)); - * // { options: { source: 'string' }, - * // state: {}, - * // compilers: - * // { eos: [Function], - * // noop: [Function], - * // bos: [Function], - * // brace: [Function], - * // 'brace.open': [Function], - * // text: [Function], - * // 'brace.close': [Function] }, - * // output: [ 'a/(b|c)/d' ], - * // ast: - * // { ... }, - * // parsingErrors: [] } - * ``` - * @param {Object|String} `ast` - * @param {Object} `options` - * @return {Object} Returns an object that has an `output` property with the compiled string. - * @api public - */ - -micromatch.compile = function(ast, options) { - if (typeof ast === 'string') { - ast = micromatch.parse(ast, options); - } - - return memoize('compile', ast.input, options, function() { - var snapdragon = utils.instantiate(ast, options); - compilers(snapdragon, options); - return snapdragon.compile(ast, options); - }); -}; - -/** - * Clear the regex cache. - * - * ```js - * mm.clearCache(); - * ``` - * @api public - */ - -micromatch.clearCache = function() { - micromatch.cache.caches = {}; -}; - -/** - * Returns true if the given value is effectively an empty string - */ - -function isEmptyString(val) { - return String(val) === '' || String(val) === './'; -} - -/** - * Compose a matcher function with the given patterns. - * This allows matcher functions to be compiled once and - * called multiple times. - */ - -function compose(patterns, options, matcher) { - var matchers; - - return memoize('compose', String(patterns), options, function() { - return function(file) { - // delay composition until it's invoked the first time, - // after that it won't be called again - if (!matchers) { - matchers = []; - for (var i = 0; i < patterns.length; i++) { - matchers.push(matcher(patterns[i], options)); - } - } - - var len = matchers.length; - while (len--) { - if (matchers[len](file) === true) { - return true; - } - } - return false; - }; - }); -} - -/** - * Memoize a generated regex or function. A unique key is generated - * from the `type` (usually method name), the `pattern`, and - * user-defined options. - */ - -function memoize(type, pattern, options, fn) { - var key = utils.createKey(type + '=' + pattern, options); - - if (options && options.cache === false) { - return fn(pattern, options); - } - - if (cache.has(type, key)) { - return cache.get(type, key); - } - - var val = fn(pattern, options); - cache.set(type, key, val); - return val; -} - -/** - * Expose compiler, parser and cache on `micromatch` - */ - -micromatch.compilers = compilers; -micromatch.parsers = parsers; -micromatch.caches = cache.caches; - -/** - * Expose `micromatch` - * @type {Function} - */ - -module.exports = micromatch; - - -/***/ }), -/* 581 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/** - * Module dependencies - */ - -var toRegex = __webpack_require__(582); -var unique = __webpack_require__(600); -var extend = __webpack_require__(601); - -/** - * Local dependencies - */ - -var compilers = __webpack_require__(603); -var parsers = __webpack_require__(613); -var Braces = __webpack_require__(617); -var utils = __webpack_require__(604); -var MAX_LENGTH = 1024 * 64; -var cache = {}; - -/** - * Convert the given `braces` pattern into a regex-compatible string. By default, only one string is generated for every input string. Set `options.expand` to true to return an array of patterns (similar to Bash or minimatch. Before using `options.expand`, it's recommended that you read the [performance notes](#performance)). - * - * ```js - * var braces = require('braces'); - * console.log(braces('{a,b,c}')); - * //=> ['(a|b|c)'] - * - * console.log(braces('{a,b,c}', {expand: true})); - * //=> ['a', 'b', 'c'] - * ``` - * @param {String} `str` - * @param {Object} `options` - * @return {String} - * @api public - */ - -function braces(pattern, options) { - var key = utils.createKey(String(pattern), options); - var arr = []; - - var disabled = options && options.cache === false; - if (!disabled && cache.hasOwnProperty(key)) { - return cache[key]; - } - - if (Array.isArray(pattern)) { - for (var i = 0; i < pattern.length; i++) { - arr.push.apply(arr, braces.create(pattern[i], options)); - } - } else { - arr = braces.create(pattern, options); - } - - if (options && options.nodupes === true) { - arr = unique(arr); - } - - if (!disabled) { - cache[key] = arr; - } - return arr; -} - -/** - * Expands a brace pattern into an array. This method is called by the main [braces](#braces) function when `options.expand` is true. Before using this method it's recommended that you read the [performance notes](#performance)) and advantages of using [.optimize](#optimize) instead. - * - * ```js - * var braces = require('braces'); - * console.log(braces.expand('a/{b,c}/d')); - * //=> ['a/b/d', 'a/c/d']; - * ``` - * @param {String} `pattern` Brace pattern - * @param {Object} `options` - * @return {Array} Returns an array of expanded values. - * @api public - */ - -braces.expand = function(pattern, options) { - return braces.create(pattern, extend({}, options, {expand: true})); -}; - -/** - * Expands a brace pattern into a regex-compatible, optimized string. This method is called by the main [braces](#braces) function by default. - * - * ```js - * var braces = require('braces'); - * console.log(braces.expand('a/{b,c}/d')); - * //=> ['a/(b|c)/d'] - * ``` - * @param {String} `pattern` Brace pattern - * @param {Object} `options` - * @return {Array} Returns an array of expanded values. - * @api public - */ - -braces.optimize = function(pattern, options) { - return braces.create(pattern, options); -}; - -/** - * Processes a brace pattern and returns either an expanded array (if `options.expand` is true), a highly optimized regex-compatible string. This method is called by the main [braces](#braces) function. - * - * ```js - * var braces = require('braces'); - * console.log(braces.create('user-{200..300}/project-{a,b,c}-{1..10}')) - * //=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)' - * ``` - * @param {String} `pattern` Brace pattern - * @param {Object} `options` - * @return {Array} Returns an array of expanded values. - * @api public - */ - -braces.create = function(pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected a string'); - } - - var maxLength = (options && options.maxLength) || MAX_LENGTH; - if (pattern.length >= maxLength) { - throw new Error('expected pattern to be less than ' + maxLength + ' characters'); - } - - function create() { - if (pattern === '' || pattern.length < 3) { - return [pattern]; - } - - if (utils.isEmptySets(pattern)) { - return []; - } - - if (utils.isQuotedString(pattern)) { - return [pattern.slice(1, -1)]; - } - - var proto = new Braces(options); - var result = !options || options.expand !== true - ? proto.optimize(pattern, options) - : proto.expand(pattern, options); - - // get the generated pattern(s) - var arr = result.output; - - // filter out empty strings if specified - if (options && options.noempty === true) { - arr = arr.filter(Boolean); - } - - // filter out duplicates if specified - if (options && options.nodupes === true) { - arr = unique(arr); - } - - Object.defineProperty(arr, 'result', { - enumerable: false, - value: result - }); - - return arr; - } - - return memoize('create', pattern, options, create); -}; - -/** - * Create a regular expression from the given string `pattern`. - * - * ```js - * var braces = require('braces'); - * - * console.log(braces.makeRe('id-{200..300}')); - * //=> /^(?:id-(20[0-9]|2[1-9][0-9]|300))$/ - * ``` - * @param {String} `pattern` The pattern to convert to regex. - * @param {Object} `options` - * @return {RegExp} - * @api public - */ - -braces.makeRe = function(pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected a string'); - } - - var maxLength = (options && options.maxLength) || MAX_LENGTH; - if (pattern.length >= maxLength) { - throw new Error('expected pattern to be less than ' + maxLength + ' characters'); - } - - function makeRe() { - var arr = braces(pattern, options); - var opts = extend({strictErrors: false}, options); - return toRegex(arr, opts); - } - - return memoize('makeRe', pattern, options, makeRe); -}; - -/** - * Parse the given `str` with the given `options`. - * - * ```js - * var braces = require('braces'); - * var ast = braces.parse('a/{b,c}/d'); - * console.log(ast); - * // { type: 'root', - * // errors: [], - * // input: 'a/{b,c}/d', - * // nodes: - * // [ { type: 'bos', val: '' }, - * // { type: 'text', val: 'a/' }, - * // { type: 'brace', - * // nodes: - * // [ { type: 'brace.open', val: '{' }, - * // { type: 'text', val: 'b,c' }, - * // { type: 'brace.close', val: '}' } ] }, - * // { type: 'text', val: '/d' }, - * // { type: 'eos', val: '' } ] } - * ``` - * @param {String} `pattern` Brace pattern to parse - * @param {Object} `options` - * @return {Object} Returns an AST - * @api public - */ - -braces.parse = function(pattern, options) { - var proto = new Braces(options); - return proto.parse(pattern, options); -}; - -/** - * Compile the given `ast` or string with the given `options`. - * - * ```js - * var braces = require('braces'); - * var ast = braces.parse('a/{b,c}/d'); - * console.log(braces.compile(ast)); - * // { options: { source: 'string' }, - * // state: {}, - * // compilers: - * // { eos: [Function], - * // noop: [Function], - * // bos: [Function], - * // brace: [Function], - * // 'brace.open': [Function], - * // text: [Function], - * // 'brace.close': [Function] }, - * // output: [ 'a/(b|c)/d' ], - * // ast: - * // { ... }, - * // parsingErrors: [] } - * ``` - * @param {Object|String} `ast` AST from [.parse](#parse). If a string is passed it will be parsed first. - * @param {Object} `options` - * @return {Object} Returns an object that has an `output` property with the compiled string. - * @api public - */ - -braces.compile = function(ast, options) { - var proto = new Braces(options); - return proto.compile(ast, options); -}; - -/** - * Clear the regex cache. - * - * ```js - * braces.clearCache(); - * ``` - * @api public - */ - -braces.clearCache = function() { - cache = braces.cache = {}; -}; - -/** - * Memoize a generated regex or function. A unique key is generated - * from the method name, pattern, and user-defined options. Set - * options.memoize to false to disable. - */ - -function memoize(type, pattern, options, fn) { - var key = utils.createKey(type + ':' + pattern, options); - var disabled = options && options.cache === false; - if (disabled) { - braces.clearCache(); - return fn(pattern, options); - } - - if (cache.hasOwnProperty(key)) { - return cache[key]; - } - - var res = fn(pattern, options); - cache[key] = res; - return res; -} - -/** - * Expose `Braces` constructor and methods - * @type {Function} - */ - -braces.Braces = Braces; -braces.compilers = compilers; -braces.parsers = parsers; -braces.cache = cache; - -/** - * Expose `braces` - * @type {Function} - */ - -module.exports = braces; - - -/***/ }), -/* 582 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var safe = __webpack_require__(583); -var define = __webpack_require__(589); -var extend = __webpack_require__(595); -var not = __webpack_require__(599); -var MAX_LENGTH = 1024 * 64; - -/** - * Session cache - */ - -var cache = {}; - -/** - * Create a regular expression from the given `pattern` string. - * - * @param {String|RegExp} `pattern` Pattern can be a string or regular expression. - * @param {Object} `options` - * @return {RegExp} - * @api public - */ - -module.exports = function(patterns, options) { - if (!Array.isArray(patterns)) { - return makeRe(patterns, options); - } - return makeRe(patterns.join('|'), options); -}; - -/** - * Create a regular expression from the given `pattern` string. - * - * @param {String|RegExp} `pattern` Pattern can be a string or regular expression. - * @param {Object} `options` - * @return {RegExp} - * @api public - */ - -function makeRe(pattern, options) { - if (pattern instanceof RegExp) { - return pattern; - } - - if (typeof pattern !== 'string') { - throw new TypeError('expected a string'); - } - - if (pattern.length > MAX_LENGTH) { - throw new Error('expected pattern to be less than ' + MAX_LENGTH + ' characters'); - } - - var key = pattern; - // do this before shallow cloning options, it's a lot faster - if (!options || (options && options.cache !== false)) { - key = createKey(pattern, options); - - if (cache.hasOwnProperty(key)) { - return cache[key]; - } - } - - var opts = extend({}, options); - if (opts.contains === true) { - if (opts.negate === true) { - opts.strictNegate = false; - } else { - opts.strict = false; - } - } - - if (opts.strict === false) { - opts.strictOpen = false; - opts.strictClose = false; - } - - var open = opts.strictOpen !== false ? '^' : ''; - var close = opts.strictClose !== false ? '$' : ''; - var flags = opts.flags || ''; - var regex; - - if (opts.nocase === true && !/i/.test(flags)) { - flags += 'i'; - } - - try { - if (opts.negate || typeof opts.strictNegate === 'boolean') { - pattern = not.create(pattern, opts); - } - - var str = open + '(?:' + pattern + ')' + close; - regex = new RegExp(str, flags); - - if (opts.safe === true && safe(regex) === false) { - throw new Error('potentially unsafe regular expression: ' + regex.source); - } - - } catch (err) { - if (opts.strictErrors === true || opts.safe === true) { - err.key = key; - err.pattern = pattern; - err.originalOptions = options; - err.createdOptions = opts; - throw err; - } - - try { - regex = new RegExp('^' + pattern.replace(/(\W)/g, '\\$1') + '$'); - } catch (err) { - regex = /.^/; //<= match nothing - } - } - - if (opts.cache !== false) { - memoize(regex, key, pattern, opts); - } - return regex; -} - -/** - * Memoize generated regex. This can result in dramatic speed improvements - * and simplify debugging by adding options and pattern to the regex. It can be - * disabled by passing setting `options.cache` to false. - */ - -function memoize(regex, key, pattern, options) { - define(regex, 'cached', true); - define(regex, 'pattern', pattern); - define(regex, 'options', options); - define(regex, 'key', key); - cache[key] = regex; -} - -/** - * Create the key to use for memoization. The key is generated - * by iterating over the options and concatenating key-value pairs - * to the pattern string. - */ - -function createKey(pattern, options) { - if (!options) return pattern; - var key = pattern; - for (var prop in options) { - if (options.hasOwnProperty(prop)) { - key += ';' + prop + '=' + String(options[prop]); - } - } - return key; -} - -/** - * Expose `makeRe` - */ - -module.exports.makeRe = makeRe; - - -/***/ }), -/* 583 */ -/***/ (function(module, exports, __webpack_require__) { - -var parse = __webpack_require__(584); -var types = parse.types; - -module.exports = function (re, opts) { - if (!opts) opts = {}; - var replimit = opts.limit === undefined ? 25 : opts.limit; - - if (isRegExp(re)) re = re.source; - else if (typeof re !== 'string') re = String(re); - - try { re = parse(re) } - catch (err) { return false } - - var reps = 0; - return (function walk (node, starHeight) { - if (node.type === types.REPETITION) { - starHeight ++; - reps ++; - if (starHeight > 1) return false; - if (reps > replimit) return false; - } - - if (node.options) { - for (var i = 0, len = node.options.length; i < len; i++) { - var ok = walk({ stack: node.options[i] }, starHeight); - if (!ok) return false; - } - } - var stack = node.stack || (node.value && node.value.stack); - if (!stack) return true; - - for (var i = 0; i < stack.length; i++) { - var ok = walk(stack[i], starHeight); - if (!ok) return false; - } - - return true; - })(re, 0); -}; - -function isRegExp (x) { - return {}.toString.call(x) === '[object RegExp]'; -} - - -/***/ }), -/* 584 */ -/***/ (function(module, exports, __webpack_require__) { - -var util = __webpack_require__(585); -var types = __webpack_require__(586); -var sets = __webpack_require__(587); -var positions = __webpack_require__(588); - - -module.exports = function(regexpStr) { - var i = 0, l, c, - start = { type: types.ROOT, stack: []}, - - // Keep track of last clause/group and stack. - lastGroup = start, - last = start.stack, - groupStack = []; - - - var repeatErr = function(i) { - util.error(regexpStr, 'Nothing to repeat at column ' + (i - 1)); - }; - - // Decode a few escaped characters. - var str = util.strToChars(regexpStr); - l = str.length; - - // Iterate through each character in string. - while (i < l) { - c = str[i++]; - - switch (c) { - // Handle escaped characters, inclues a few sets. - case '\\': - c = str[i++]; - - switch (c) { - case 'b': - last.push(positions.wordBoundary()); - break; - - case 'B': - last.push(positions.nonWordBoundary()); - break; - - case 'w': - last.push(sets.words()); - break; - - case 'W': - last.push(sets.notWords()); - break; - - case 'd': - last.push(sets.ints()); - break; - - case 'D': - last.push(sets.notInts()); - break; - - case 's': - last.push(sets.whitespace()); - break; - - case 'S': - last.push(sets.notWhitespace()); - break; - - default: - // Check if c is integer. - // In which case it's a reference. - if (/\d/.test(c)) { - last.push({ type: types.REFERENCE, value: parseInt(c, 10) }); - - // Escaped character. - } else { - last.push({ type: types.CHAR, value: c.charCodeAt(0) }); - } - } - - break; - - - // Positionals. - case '^': - last.push(positions.begin()); - break; - - case '$': - last.push(positions.end()); - break; - - - // Handle custom sets. - case '[': - // Check if this class is 'anti' i.e. [^abc]. - var not; - if (str[i] === '^') { - not = true; - i++; - } else { - not = false; - } - - // Get all the characters in class. - var classTokens = util.tokenizeClass(str.slice(i), regexpStr); - - // Increase index by length of class. - i += classTokens[1]; - last.push({ - type: types.SET, - set: classTokens[0], - not: not, - }); - - break; - - - // Class of any character except \n. - case '.': - last.push(sets.anyChar()); - break; - - - // Push group onto stack. - case '(': - // Create group. - var group = { - type: types.GROUP, - stack: [], - remember: true, - }; - - c = str[i]; - - // If if this is a special kind of group. - if (c === '?') { - c = str[i + 1]; - i += 2; - - // Match if followed by. - if (c === '=') { - group.followedBy = true; - - // Match if not followed by. - } else if (c === '!') { - group.notFollowedBy = true; - - } else if (c !== ':') { - util.error(regexpStr, - 'Invalid group, character \'' + c + - '\' after \'?\' at column ' + (i - 1)); - } - - group.remember = false; - } - - // Insert subgroup into current group stack. - last.push(group); - - // Remember the current group for when the group closes. - groupStack.push(lastGroup); - - // Make this new group the current group. - lastGroup = group; - last = group.stack; - break; - - - // Pop group out of stack. - case ')': - if (groupStack.length === 0) { - util.error(regexpStr, 'Unmatched ) at column ' + (i - 1)); - } - lastGroup = groupStack.pop(); - - // Check if this group has a PIPE. - // To get back the correct last stack. - last = lastGroup.options ? - lastGroup.options[lastGroup.options.length - 1] : lastGroup.stack; - break; - - - // Use pipe character to give more choices. - case '|': - // Create array where options are if this is the first PIPE - // in this clause. - if (!lastGroup.options) { - lastGroup.options = [lastGroup.stack]; - delete lastGroup.stack; - } - - // Create a new stack and add to options for rest of clause. - var stack = []; - lastGroup.options.push(stack); - last = stack; - break; - - - // Repetition. - // For every repetition, remove last element from last stack - // then insert back a RANGE object. - // This design is chosen because there could be more than - // one repetition symbols in a regex i.e. `a?+{2,3}`. - case '{': - var rs = /^(\d+)(,(\d+)?)?\}/.exec(str.slice(i)), min, max; - if (rs !== null) { - if (last.length === 0) { - repeatErr(i); - } - min = parseInt(rs[1], 10); - max = rs[2] ? rs[3] ? parseInt(rs[3], 10) : Infinity : min; - i += rs[0].length; - - last.push({ - type: types.REPETITION, - min: min, - max: max, - value: last.pop(), - }); - } else { - last.push({ - type: types.CHAR, - value: 123, - }); - } - break; - - case '?': - if (last.length === 0) { - repeatErr(i); - } - last.push({ - type: types.REPETITION, - min: 0, - max: 1, - value: last.pop(), - }); - break; - - case '+': - if (last.length === 0) { - repeatErr(i); - } - last.push({ - type: types.REPETITION, - min: 1, - max: Infinity, - value: last.pop(), - }); - break; - - case '*': - if (last.length === 0) { - repeatErr(i); - } - last.push({ - type: types.REPETITION, - min: 0, - max: Infinity, - value: last.pop(), - }); - break; - - - // Default is a character that is not `\[](){}?+*^$`. - default: - last.push({ - type: types.CHAR, - value: c.charCodeAt(0), - }); - } - - } - - // Check if any groups have not been closed. - if (groupStack.length !== 0) { - util.error(regexpStr, 'Unterminated group'); - } - - return start; -}; - -module.exports.types = types; - - -/***/ }), -/* 585 */ -/***/ (function(module, exports, __webpack_require__) { - -var types = __webpack_require__(586); -var sets = __webpack_require__(587); - - -// All of these are private and only used by randexp. -// It's assumed that they will always be called with the correct input. - -var CTRL = '@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^ ?'; -var SLSH = { '0': 0, 't': 9, 'n': 10, 'v': 11, 'f': 12, 'r': 13 }; - -/** - * Finds character representations in str and convert all to - * their respective characters - * - * @param {String} str - * @return {String} - */ -exports.strToChars = function(str) { - /* jshint maxlen: false */ - var chars_regex = /(\[\\b\])|(\\)?\\(?:u([A-F0-9]{4})|x([A-F0-9]{2})|(0?[0-7]{2})|c([@A-Z\[\\\]\^?])|([0tnvfr]))/g; - str = str.replace(chars_regex, function(s, b, lbs, a16, b16, c8, dctrl, eslsh) { - if (lbs) { - return s; - } - - var code = b ? 8 : - a16 ? parseInt(a16, 16) : - b16 ? parseInt(b16, 16) : - c8 ? parseInt(c8, 8) : - dctrl ? CTRL.indexOf(dctrl) : - SLSH[eslsh]; - - var c = String.fromCharCode(code); - - // Escape special regex characters. - if (/[\[\]{}\^$.|?*+()]/.test(c)) { - c = '\\' + c; - } - - return c; - }); - - return str; -}; - - -/** - * turns class into tokens - * reads str until it encounters a ] not preceeded by a \ - * - * @param {String} str - * @param {String} regexpStr - * @return {Array., Number>} - */ -exports.tokenizeClass = function(str, regexpStr) { - /* jshint maxlen: false */ - var tokens = []; - var regexp = /\\(?:(w)|(d)|(s)|(W)|(D)|(S))|((?:(?:\\)(.)|([^\]\\]))-(?:\\)?([^\]]))|(\])|(?:\\)?(.)/g; - var rs, c; - - - while ((rs = regexp.exec(str)) != null) { - if (rs[1]) { - tokens.push(sets.words()); - - } else if (rs[2]) { - tokens.push(sets.ints()); - - } else if (rs[3]) { - tokens.push(sets.whitespace()); - - } else if (rs[4]) { - tokens.push(sets.notWords()); - - } else if (rs[5]) { - tokens.push(sets.notInts()); - - } else if (rs[6]) { - tokens.push(sets.notWhitespace()); - - } else if (rs[7]) { - tokens.push({ - type: types.RANGE, - from: (rs[8] || rs[9]).charCodeAt(0), - to: rs[10].charCodeAt(0), - }); - - } else if (c = rs[12]) { - tokens.push({ - type: types.CHAR, - value: c.charCodeAt(0), - }); - - } else { - return [tokens, regexp.lastIndex]; - } - } - - exports.error(regexpStr, 'Unterminated character class'); -}; - - -/** - * Shortcut to throw errors. - * - * @param {String} regexp - * @param {String} msg - */ -exports.error = function(regexp, msg) { - throw new SyntaxError('Invalid regular expression: /' + regexp + '/: ' + msg); -}; - - -/***/ }), -/* 586 */ -/***/ (function(module, exports) { - -module.exports = { - ROOT : 0, - GROUP : 1, - POSITION : 2, - SET : 3, - RANGE : 4, - REPETITION : 5, - REFERENCE : 6, - CHAR : 7, -}; - - -/***/ }), -/* 587 */ -/***/ (function(module, exports, __webpack_require__) { - -var types = __webpack_require__(586); - -var INTS = function() { - return [{ type: types.RANGE , from: 48, to: 57 }]; -}; - -var WORDS = function() { - return [ - { type: types.CHAR, value: 95 }, - { type: types.RANGE, from: 97, to: 122 }, - { type: types.RANGE, from: 65, to: 90 } - ].concat(INTS()); -}; - -var WHITESPACE = function() { - return [ - { type: types.CHAR, value: 9 }, - { type: types.CHAR, value: 10 }, - { type: types.CHAR, value: 11 }, - { type: types.CHAR, value: 12 }, - { type: types.CHAR, value: 13 }, - { type: types.CHAR, value: 32 }, - { type: types.CHAR, value: 160 }, - { type: types.CHAR, value: 5760 }, - { type: types.CHAR, value: 6158 }, - { type: types.CHAR, value: 8192 }, - { type: types.CHAR, value: 8193 }, - { type: types.CHAR, value: 8194 }, - { type: types.CHAR, value: 8195 }, - { type: types.CHAR, value: 8196 }, - { type: types.CHAR, value: 8197 }, - { type: types.CHAR, value: 8198 }, - { type: types.CHAR, value: 8199 }, - { type: types.CHAR, value: 8200 }, - { type: types.CHAR, value: 8201 }, - { type: types.CHAR, value: 8202 }, - { type: types.CHAR, value: 8232 }, - { type: types.CHAR, value: 8233 }, - { type: types.CHAR, value: 8239 }, - { type: types.CHAR, value: 8287 }, - { type: types.CHAR, value: 12288 }, - { type: types.CHAR, value: 65279 } - ]; -}; - -var NOTANYCHAR = function() { - return [ - { type: types.CHAR, value: 10 }, - { type: types.CHAR, value: 13 }, - { type: types.CHAR, value: 8232 }, - { type: types.CHAR, value: 8233 }, - ]; -}; - -// Predefined class objects. -exports.words = function() { - return { type: types.SET, set: WORDS(), not: false }; -}; - -exports.notWords = function() { - return { type: types.SET, set: WORDS(), not: true }; -}; - -exports.ints = function() { - return { type: types.SET, set: INTS(), not: false }; -}; - -exports.notInts = function() { - return { type: types.SET, set: INTS(), not: true }; -}; - -exports.whitespace = function() { - return { type: types.SET, set: WHITESPACE(), not: false }; -}; - -exports.notWhitespace = function() { - return { type: types.SET, set: WHITESPACE(), not: true }; -}; - -exports.anyChar = function() { - return { type: types.SET, set: NOTANYCHAR(), not: true }; -}; - - -/***/ }), -/* 588 */ -/***/ (function(module, exports, __webpack_require__) { - -var types = __webpack_require__(586); - -exports.wordBoundary = function() { - return { type: types.POSITION, value: 'b' }; -}; - -exports.nonWordBoundary = function() { - return { type: types.POSITION, value: 'B' }; -}; - -exports.begin = function() { - return { type: types.POSITION, value: '^' }; -}; - -exports.end = function() { - return { type: types.POSITION, value: '$' }; -}; - - -/***/ }), -/* 589 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * define-property - * - * Copyright (c) 2015-2018, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var isobject = __webpack_require__(590); -var isDescriptor = __webpack_require__(591); -var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) - ? Reflect.defineProperty - : Object.defineProperty; - -module.exports = function defineProperty(obj, key, val) { - if (!isobject(obj) && typeof obj !== 'function' && !Array.isArray(obj)) { - throw new TypeError('expected an object, function, or array'); - } - - if (typeof key !== 'string') { - throw new TypeError('expected "key" to be a string'); - } - - if (isDescriptor(val)) { - define(obj, key, val); - return obj; - } - - define(obj, key, { - configurable: true, - enumerable: false, - writable: true, - value: val - }); - - return obj; -}; - - -/***/ }), -/* 590 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * isobject - * - * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -module.exports = function isObject(val) { - return val != null && typeof val === 'object' && Array.isArray(val) === false; -}; - - -/***/ }), -/* 591 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * is-descriptor - * - * Copyright (c) 2015-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var typeOf = __webpack_require__(592); -var isAccessor = __webpack_require__(593); -var isData = __webpack_require__(594); - -module.exports = function isDescriptor(obj, key) { - if (typeOf(obj) !== 'object') { - return false; - } - if ('get' in obj) { - return isAccessor(obj, key); - } - return isData(obj, key); -}; - - -/***/ }), -/* 592 */ -/***/ (function(module, exports) { - -var toString = Object.prototype.toString; - -module.exports = function kindOf(val) { - if (val === void 0) return 'undefined'; - if (val === null) return 'null'; - - var type = typeof val; - if (type === 'boolean') return 'boolean'; - if (type === 'string') return 'string'; - if (type === 'number') return 'number'; - if (type === 'symbol') return 'symbol'; - if (type === 'function') { - return isGeneratorFn(val) ? 'generatorfunction' : 'function'; - } - - if (isArray(val)) return 'array'; - if (isBuffer(val)) return 'buffer'; - if (isArguments(val)) return 'arguments'; - if (isDate(val)) return 'date'; - if (isError(val)) return 'error'; - if (isRegexp(val)) return 'regexp'; - - switch (ctorName(val)) { - case 'Symbol': return 'symbol'; - case 'Promise': return 'promise'; - - // Set, Map, WeakSet, WeakMap - case 'WeakMap': return 'weakmap'; - case 'WeakSet': return 'weakset'; - case 'Map': return 'map'; - case 'Set': return 'set'; - - // 8-bit typed arrays - case 'Int8Array': return 'int8array'; - case 'Uint8Array': return 'uint8array'; - case 'Uint8ClampedArray': return 'uint8clampedarray'; - - // 16-bit typed arrays - case 'Int16Array': return 'int16array'; - case 'Uint16Array': return 'uint16array'; - - // 32-bit typed arrays - case 'Int32Array': return 'int32array'; - case 'Uint32Array': return 'uint32array'; - case 'Float32Array': return 'float32array'; - case 'Float64Array': return 'float64array'; - } - - if (isGeneratorObj(val)) { - return 'generator'; - } - - // Non-plain objects - type = toString.call(val); - switch (type) { - case '[object Object]': return 'object'; - // iterators - case '[object Map Iterator]': return 'mapiterator'; - case '[object Set Iterator]': return 'setiterator'; - case '[object String Iterator]': return 'stringiterator'; - case '[object Array Iterator]': return 'arrayiterator'; - } - - // other - return type.slice(8, -1).toLowerCase().replace(/\s/g, ''); -}; - -function ctorName(val) { - return typeof val.constructor === 'function' ? val.constructor.name : null; -} - -function isArray(val) { - if (Array.isArray) return Array.isArray(val); - return val instanceof Array; -} - -function isError(val) { - return val instanceof Error || (typeof val.message === 'string' && val.constructor && typeof val.constructor.stackTraceLimit === 'number'); -} - -function isDate(val) { - if (val instanceof Date) return true; - return typeof val.toDateString === 'function' - && typeof val.getDate === 'function' - && typeof val.setDate === 'function'; -} - -function isRegexp(val) { - if (val instanceof RegExp) return true; - return typeof val.flags === 'string' - && typeof val.ignoreCase === 'boolean' - && typeof val.multiline === 'boolean' - && typeof val.global === 'boolean'; -} - -function isGeneratorFn(name, val) { - return ctorName(name) === 'GeneratorFunction'; -} - -function isGeneratorObj(val) { - return typeof val.throw === 'function' - && typeof val.return === 'function' - && typeof val.next === 'function'; -} - -function isArguments(val) { - try { - if (typeof val.length === 'number' && typeof val.callee === 'function') { - return true; - } - } catch (err) { - if (err.message.indexOf('callee') !== -1) { - return true; - } - } - return false; -} - -/** - * If you need to support Safari 5-7 (8-10 yr-old browser), - * take a look at https://github.com/feross/is-buffer - */ - -function isBuffer(val) { - if (val.constructor && typeof val.constructor.isBuffer === 'function') { - return val.constructor.isBuffer(val); - } - return false; -} - - -/***/ }), -/* 593 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * is-accessor-descriptor - * - * Copyright (c) 2015-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var typeOf = __webpack_require__(592); - -// accessor descriptor properties -var accessor = { - get: 'function', - set: 'function', - configurable: 'boolean', - enumerable: 'boolean' -}; - -function isAccessorDescriptor(obj, prop) { - if (typeof prop === 'string') { - var val = Object.getOwnPropertyDescriptor(obj, prop); - return typeof val !== 'undefined'; - } - - if (typeOf(obj) !== 'object') { - return false; - } - - if (has(obj, 'value') || has(obj, 'writable')) { - return false; - } - - if (!has(obj, 'get') || typeof obj.get !== 'function') { - return false; - } - - // tldr: it's valid to have "set" be undefined - // "set" might be undefined if `Object.getOwnPropertyDescriptor` - // was used to get the value, and only `get` was defined by the user - if (has(obj, 'set') && typeof obj[key] !== 'function' && typeof obj[key] !== 'undefined') { - return false; - } - - for (var key in obj) { - if (!accessor.hasOwnProperty(key)) { - continue; - } - - if (typeOf(obj[key]) === accessor[key]) { - continue; - } - - if (typeof obj[key] !== 'undefined') { - return false; - } - } - return true; -} - -function has(obj, key) { - return {}.hasOwnProperty.call(obj, key); -} - -/** - * Expose `isAccessorDescriptor` - */ - -module.exports = isAccessorDescriptor; - - -/***/ }), -/* 594 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * is-data-descriptor - * - * Copyright (c) 2015-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var typeOf = __webpack_require__(592); - -module.exports = function isDataDescriptor(obj, prop) { - // data descriptor properties - var data = { - configurable: 'boolean', - enumerable: 'boolean', - writable: 'boolean' - }; - - if (typeOf(obj) !== 'object') { - return false; - } - - if (typeof prop === 'string') { - var val = Object.getOwnPropertyDescriptor(obj, prop); - return typeof val !== 'undefined'; - } - - if (!('value' in obj) && !('writable' in obj)) { - return false; - } - - for (var key in obj) { - if (key === 'value') continue; - - if (!data.hasOwnProperty(key)) { - continue; - } - - if (typeOf(obj[key]) === data[key]) { - continue; - } - - if (typeof obj[key] !== 'undefined') { - return false; - } - } - return true; -}; - - -/***/ }), -/* 595 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var isExtendable = __webpack_require__(596); -var assignSymbols = __webpack_require__(598); - -module.exports = Object.assign || function(obj/*, objects*/) { - if (obj === null || typeof obj === 'undefined') { - throw new TypeError('Cannot convert undefined or null to object'); - } - if (!isObject(obj)) { - obj = {}; - } - for (var i = 1; i < arguments.length; i++) { - var val = arguments[i]; - if (isString(val)) { - val = toObject(val); - } - if (isObject(val)) { - assign(obj, val); - assignSymbols(obj, val); - } - } - return obj; -}; - -function assign(a, b) { - for (var key in b) { - if (hasOwn(b, key)) { - a[key] = b[key]; - } - } -} - -function isString(val) { - return (val && typeof val === 'string'); -} - -function toObject(str) { - var obj = {}; - for (var i in str) { - obj[i] = str[i]; - } - return obj; -} - -function isObject(val) { - return (val && typeof val === 'object') || isExtendable(val); -} - -/** - * Returns true if the given `key` is an own property of `obj`. - */ - -function hasOwn(obj, key) { - return Object.prototype.hasOwnProperty.call(obj, key); -} - -function isEnum(obj, key) { - return Object.prototype.propertyIsEnumerable.call(obj, key); -} - - -/***/ }), -/* 596 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * is-extendable - * - * Copyright (c) 2015-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var isPlainObject = __webpack_require__(597); - -module.exports = function isExtendable(val) { - return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); -}; - - -/***/ }), -/* 597 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * is-plain-object - * - * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var isObject = __webpack_require__(590); - -function isObjectObject(o) { - return isObject(o) === true - && Object.prototype.toString.call(o) === '[object Object]'; -} - -module.exports = function isPlainObject(o) { - var ctor,prot; - - if (isObjectObject(o) === false) return false; - - // If has modified constructor - ctor = o.constructor; - if (typeof ctor !== 'function') return false; - - // If has modified prototype - prot = ctor.prototype; - if (isObjectObject(prot) === false) return false; - - // If constructor does not have an Object-specific method - if (prot.hasOwnProperty('isPrototypeOf') === false) { - return false; - } - - // Most likely a plain Object - return true; -}; - - -/***/ }), -/* 598 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * assign-symbols - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -module.exports = function(receiver, objects) { - if (receiver === null || typeof receiver === 'undefined') { - throw new TypeError('expected first argument to be an object.'); - } - - if (typeof objects === 'undefined' || typeof Symbol === 'undefined') { - return receiver; - } - - if (typeof Object.getOwnPropertySymbols !== 'function') { - return receiver; - } - - var isEnumerable = Object.prototype.propertyIsEnumerable; - var target = Object(receiver); - var len = arguments.length, i = 0; - - while (++i < len) { - var provider = Object(arguments[i]); - var names = Object.getOwnPropertySymbols(provider); - - for (var j = 0; j < names.length; j++) { - var key = names[j]; - - if (isEnumerable.call(provider, key)) { - target[key] = provider[key]; - } - } - } - return target; -}; - - -/***/ }), -/* 599 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var extend = __webpack_require__(595); -var safe = __webpack_require__(583); - -/** - * The main export is a function that takes a `pattern` string and an `options` object. - * - * ```js - & var not = require('regex-not'); - & console.log(not('foo')); - & //=> /^(?:(?!^(?:foo)$).)*$/ - * ``` - * - * @param {String} `pattern` - * @param {Object} `options` - * @return {RegExp} Converts the given `pattern` to a regex using the specified `options`. - * @api public - */ - -function toRegex(pattern, options) { - return new RegExp(toRegex.create(pattern, options)); -} - -/** - * Create a regex-compatible string from the given `pattern` and `options`. - * - * ```js - & var not = require('regex-not'); - & console.log(not.create('foo')); - & //=> '^(?:(?!^(?:foo)$).)*$' - * ``` - * @param {String} `pattern` - * @param {Object} `options` - * @return {String} - * @api public - */ - -toRegex.create = function(pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected a string'); - } - - var opts = extend({}, options); - if (opts.contains === true) { - opts.strictNegate = false; - } - - var open = opts.strictOpen !== false ? '^' : ''; - var close = opts.strictClose !== false ? '$' : ''; - var endChar = opts.endChar ? opts.endChar : '+'; - var str = pattern; - - if (opts.strictNegate === false) { - str = '(?:(?!(?:' + pattern + ')).)' + endChar; - } else { - str = '(?:(?!^(?:' + pattern + ')$).)' + endChar; - } - - var res = open + str + close; - if (opts.safe === true && safe(res) === false) { - throw new Error('potentially unsafe regular expression: ' + res); - } - - return res; -}; - -/** - * Expose `toRegex` - */ - -module.exports = toRegex; - - -/***/ }), -/* 600 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * array-unique - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -module.exports = function unique(arr) { - if (!Array.isArray(arr)) { - throw new TypeError('array-unique expects an array.'); - } - - var len = arr.length; - var i = -1; - - while (i++ < len) { - var j = i + 1; - - for (; j < arr.length; ++j) { - if (arr[i] === arr[j]) { - arr.splice(j--, 1); - } - } - } - return arr; -}; - -module.exports.immutable = function uniqueImmutable(arr) { - if (!Array.isArray(arr)) { - throw new TypeError('array-unique expects an array.'); - } - - var arrLen = arr.length; - var newArr = new Array(arrLen); - - for (var i = 0; i < arrLen; i++) { - newArr[i] = arr[i]; - } - - return module.exports(newArr); -}; - - -/***/ }), -/* 601 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var isObject = __webpack_require__(602); - -module.exports = function extend(o/*, objects*/) { - if (!isObject(o)) { o = {}; } - - var len = arguments.length; - for (var i = 1; i < len; i++) { - var obj = arguments[i]; - - if (isObject(obj)) { - assign(o, obj); - } - } - return o; -}; - -function assign(a, b) { - for (var key in b) { - if (hasOwn(b, key)) { - a[key] = b[key]; - } - } -} - -/** - * Returns true if the given `key` is an own property of `obj`. - */ - -function hasOwn(obj, key) { - return Object.prototype.hasOwnProperty.call(obj, key); -} - - -/***/ }), -/* 602 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * is-extendable - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -module.exports = function isExtendable(val) { - return typeof val !== 'undefined' && val !== null - && (typeof val === 'object' || typeof val === 'function'); -}; - - -/***/ }), -/* 603 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var utils = __webpack_require__(604); - -module.exports = function(braces, options) { - braces.compiler - - /** - * bos - */ - - .set('bos', function() { - if (this.output) return; - this.ast.queue = isEscaped(this.ast) ? [this.ast.val] : []; - this.ast.count = 1; - }) - - /** - * Square brackets - */ - - .set('bracket', function(node) { - var close = node.close; - var open = !node.escaped ? '[' : '\\['; - var negated = node.negated; - var inner = node.inner; - - inner = inner.replace(/\\(?=[\\\w]|$)/g, '\\\\'); - if (inner === ']-') { - inner = '\\]\\-'; - } - - if (negated && inner.indexOf('.') === -1) { - inner += '.'; - } - if (negated && inner.indexOf('/') === -1) { - inner += '/'; - } - - var val = open + negated + inner + close; - var queue = node.parent.queue; - var last = utils.arrayify(queue.pop()); - - queue.push(utils.join(last, val)); - queue.push.apply(queue, []); - }) - - /** - * Brace - */ - - .set('brace', function(node) { - node.queue = isEscaped(node) ? [node.val] : []; - node.count = 1; - return this.mapVisit(node.nodes); - }) - - /** - * Open - */ - - .set('brace.open', function(node) { - node.parent.open = node.val; - }) - - /** - * Inner - */ - - .set('text', function(node) { - var queue = node.parent.queue; - var escaped = node.escaped; - var segs = [node.val]; - - if (node.optimize === false) { - options = utils.extend({}, options, {optimize: false}); - } - - if (node.multiplier > 1) { - node.parent.count *= node.multiplier; - } - - if (options.quantifiers === true && utils.isQuantifier(node.val)) { - escaped = true; - - } else if (node.val.length > 1) { - if (isType(node.parent, 'brace') && !isEscaped(node)) { - var expanded = utils.expand(node.val, options); - segs = expanded.segs; - - if (expanded.isOptimized) { - node.parent.isOptimized = true; - } - - // if nothing was expanded, we probably have a literal brace - if (!segs.length) { - var val = (expanded.val || node.val); - if (options.unescape !== false) { - // unescape unexpanded brace sequence/set separators - val = val.replace(/\\([,.])/g, '$1'); - // strip quotes - val = val.replace(/["'`]/g, ''); - } - - segs = [val]; - escaped = true; - } - } - - } else if (node.val === ',') { - if (options.expand) { - node.parent.queue.push(['']); - segs = ['']; - } else { - segs = ['|']; - } - } else { - escaped = true; - } - - if (escaped && isType(node.parent, 'brace')) { - if (node.parent.nodes.length <= 4 && node.parent.count === 1) { - node.parent.escaped = true; - } else if (node.parent.length <= 3) { - node.parent.escaped = true; - } - } - - if (!hasQueue(node.parent)) { - node.parent.queue = segs; - return; - } - - var last = utils.arrayify(queue.pop()); - if (node.parent.count > 1 && options.expand) { - last = multiply(last, node.parent.count); - node.parent.count = 1; - } - - queue.push(utils.join(utils.flatten(last), segs.shift())); - queue.push.apply(queue, segs); - }) - - /** - * Close - */ - - .set('brace.close', function(node) { - var queue = node.parent.queue; - var prev = node.parent.parent; - var last = prev.queue.pop(); - var open = node.parent.open; - var close = node.val; - - if (open && close && isOptimized(node, options)) { - open = '('; - close = ')'; - } - - // if a close brace exists, and the previous segment is one character - // don't wrap the result in braces or parens - var ele = utils.last(queue); - if (node.parent.count > 1 && options.expand) { - ele = multiply(queue.pop(), node.parent.count); - node.parent.count = 1; - queue.push(ele); - } - - if (close && typeof ele === 'string' && ele.length === 1) { - open = ''; - close = ''; - } - - if ((isLiteralBrace(node, options) || noInner(node)) && !node.parent.hasEmpty) { - queue.push(utils.join(open, queue.pop() || '')); - queue = utils.flatten(utils.join(queue, close)); - } - - if (typeof last === 'undefined') { - prev.queue = [queue]; - } else { - prev.queue.push(utils.flatten(utils.join(last, queue))); - } - }) - - /** - * eos - */ - - .set('eos', function(node) { - if (this.input) return; - - if (options.optimize !== false) { - this.output = utils.last(utils.flatten(this.ast.queue)); - } else if (Array.isArray(utils.last(this.ast.queue))) { - this.output = utils.flatten(this.ast.queue.pop()); - } else { - this.output = utils.flatten(this.ast.queue); - } - - if (node.parent.count > 1 && options.expand) { - this.output = multiply(this.output, node.parent.count); - } - - this.output = utils.arrayify(this.output); - this.ast.queue = []; - }); - -}; - -/** - * Multiply the segments in the current brace level - */ - -function multiply(queue, n, options) { - return utils.flatten(utils.repeat(utils.arrayify(queue), n)); -} - -/** - * Return true if `node` is escaped - */ - -function isEscaped(node) { - return node.escaped === true; -} - -/** - * Returns true if regex parens should be used for sets. If the parent `type` - * is not `brace`, then we're on a root node, which means we should never - * expand segments and open/close braces should be `{}` (since this indicates - * a brace is missing from the set) - */ - -function isOptimized(node, options) { - if (node.parent.isOptimized) return true; - return isType(node.parent, 'brace') - && !isEscaped(node.parent) - && options.expand !== true; -} - -/** - * Returns true if the value in `node` should be wrapped in a literal brace. - * @return {Boolean} - */ - -function isLiteralBrace(node, options) { - return isEscaped(node.parent) || options.optimize !== false; -} - -/** - * Returns true if the given `node` does not have an inner value. - * @return {Boolean} - */ - -function noInner(node, type) { - if (node.parent.queue.length === 1) { - return true; - } - var nodes = node.parent.nodes; - return nodes.length === 3 - && isType(nodes[0], 'brace.open') - && !isType(nodes[1], 'text') - && isType(nodes[2], 'brace.close'); -} - -/** - * Returns true if the given `node` is the given `type` - * @return {Boolean} - */ - -function isType(node, type) { - return typeof node !== 'undefined' && node.type === type; -} - -/** - * Returns true if the given `node` has a non-empty queue. - * @return {Boolean} - */ - -function hasQueue(node) { - return Array.isArray(node.queue) && node.queue.length; -} - - -/***/ }), -/* 604 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var splitString = __webpack_require__(605); -var utils = module.exports; - -/** - * Module dependencies - */ - -utils.extend = __webpack_require__(601); -utils.flatten = __webpack_require__(606); -utils.isObject = __webpack_require__(590); -utils.fillRange = __webpack_require__(607); -utils.repeat = __webpack_require__(612); -utils.unique = __webpack_require__(600); - -utils.define = function(obj, key, val) { - Object.defineProperty(obj, key, { - writable: true, - configurable: true, - enumerable: false, - value: val - }); -}; - -/** - * Returns true if the given string contains only empty brace sets. - */ - -utils.isEmptySets = function(str) { - return /^(?:\{,\})+$/.test(str); -}; - -/** - * Returns true if the given string contains only empty brace sets. - */ - -utils.isQuotedString = function(str) { - var open = str.charAt(0); - if (open === '\'' || open === '"' || open === '`') { - return str.slice(-1) === open; - } - return false; -}; - -/** - * Create the key to use for memoization. The unique key is generated - * by iterating over the options and concatenating key-value pairs - * to the pattern string. - */ - -utils.createKey = function(pattern, options) { - var id = pattern; - if (typeof options === 'undefined') { - return id; - } - var keys = Object.keys(options); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - id += ';' + key + '=' + String(options[key]); - } - return id; -}; - -/** - * Normalize options - */ - -utils.createOptions = function(options) { - var opts = utils.extend.apply(null, arguments); - if (typeof opts.expand === 'boolean') { - opts.optimize = !opts.expand; - } - if (typeof opts.optimize === 'boolean') { - opts.expand = !opts.optimize; - } - if (opts.optimize === true) { - opts.makeRe = true; - } - return opts; -}; - -/** - * Join patterns in `a` to patterns in `b` - */ - -utils.join = function(a, b, options) { - options = options || {}; - a = utils.arrayify(a); - b = utils.arrayify(b); - - if (!a.length) return b; - if (!b.length) return a; - - var len = a.length; - var idx = -1; - var arr = []; - - while (++idx < len) { - var val = a[idx]; - if (Array.isArray(val)) { - for (var i = 0; i < val.length; i++) { - val[i] = utils.join(val[i], b, options); - } - arr.push(val); - continue; - } - - for (var j = 0; j < b.length; j++) { - var bval = b[j]; - - if (Array.isArray(bval)) { - arr.push(utils.join(val, bval, options)); - } else { - arr.push(val + bval); - } - } - } - return arr; -}; - -/** - * Split the given string on `,` if not escaped. - */ - -utils.split = function(str, options) { - var opts = utils.extend({sep: ','}, options); - if (typeof opts.keepQuotes !== 'boolean') { - opts.keepQuotes = true; - } - if (opts.unescape === false) { - opts.keepEscaping = true; - } - return splitString(str, opts, utils.escapeBrackets(opts)); -}; - -/** - * Expand ranges or sets in the given `pattern`. - * - * @param {String} `str` - * @param {Object} `options` - * @return {Object} - */ - -utils.expand = function(str, options) { - var opts = utils.extend({rangeLimit: 10000}, options); - var segs = utils.split(str, opts); - var tok = { segs: segs }; - - if (utils.isQuotedString(str)) { - return tok; - } - - if (opts.rangeLimit === true) { - opts.rangeLimit = 10000; - } - - if (segs.length > 1) { - if (opts.optimize === false) { - tok.val = segs[0]; - return tok; - } - - tok.segs = utils.stringifyArray(tok.segs); - } else if (segs.length === 1) { - var arr = str.split('..'); - - if (arr.length === 1) { - tok.val = tok.segs[tok.segs.length - 1] || tok.val || str; - tok.segs = []; - return tok; - } - - if (arr.length === 2 && arr[0] === arr[1]) { - tok.escaped = true; - tok.val = arr[0]; - tok.segs = []; - return tok; - } - - if (arr.length > 1) { - if (opts.optimize !== false) { - opts.optimize = true; - delete opts.expand; - } - - if (opts.optimize !== true) { - var min = Math.min(arr[0], arr[1]); - var max = Math.max(arr[0], arr[1]); - var step = arr[2] || 1; - - if (opts.rangeLimit !== false && ((max - min) / step >= opts.rangeLimit)) { - throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.'); - } - } - - arr.push(opts); - tok.segs = utils.fillRange.apply(null, arr); - - if (!tok.segs.length) { - tok.escaped = true; - tok.val = str; - return tok; - } - - if (opts.optimize === true) { - tok.segs = utils.stringifyArray(tok.segs); - } - - if (tok.segs === '') { - tok.val = str; - } else { - tok.val = tok.segs[0]; - } - return tok; - } - } else { - tok.val = str; - } - return tok; -}; - -/** - * Ensure commas inside brackets and parens are not split. - * @param {Object} `tok` Token from the `split-string` module - * @return {undefined} - */ - -utils.escapeBrackets = function(options) { - return function(tok) { - if (tok.escaped && tok.val === 'b') { - tok.val = '\\b'; - return; - } - - if (tok.val !== '(' && tok.val !== '[') return; - var opts = utils.extend({}, options); - var brackets = []; - var parens = []; - var stack = []; - var val = tok.val; - var str = tok.str; - var i = tok.idx - 1; - - while (++i < str.length) { - var ch = str[i]; - - if (ch === '\\') { - val += (opts.keepEscaping === false ? '' : ch) + str[++i]; - continue; - } - - if (ch === '(') { - parens.push(ch); - stack.push(ch); - } - - if (ch === '[') { - brackets.push(ch); - stack.push(ch); - } - - if (ch === ')') { - parens.pop(); - stack.pop(); - if (!stack.length) { - val += ch; - break; - } - } - - if (ch === ']') { - brackets.pop(); - stack.pop(); - if (!stack.length) { - val += ch; - break; - } - } - val += ch; - } - - tok.split = false; - tok.val = val.slice(1); - tok.idx = i; - }; -}; - -/** - * Returns true if the given string looks like a regex quantifier - * @return {Boolean} - */ - -utils.isQuantifier = function(str) { - return /^(?:[0-9]?,[0-9]|[0-9],)$/.test(str); -}; - -/** - * Cast `val` to an array. - * @param {*} `val` - */ - -utils.stringifyArray = function(arr) { - return [utils.arrayify(arr).join('|')]; -}; - -/** - * Cast `val` to an array. - * @param {*} `val` - */ - -utils.arrayify = function(arr) { - if (typeof arr === 'undefined') { - return []; - } - if (typeof arr === 'string') { - return [arr]; - } - return arr; -}; - -/** - * Returns true if the given `str` is a non-empty string - * @return {Boolean} - */ - -utils.isString = function(str) { - return str != null && typeof str === 'string'; -}; - -/** - * Get the last element from `array` - * @param {Array} `array` - * @return {*} - */ - -utils.last = function(arr, n) { - return arr[arr.length - (n || 1)]; -}; - -utils.escapeRegex = function(str) { - return str.replace(/\\?([!^*?()[\]{}+?/])/g, '\\$1'); -}; - - -/***/ }), -/* 605 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * split-string - * - * Copyright (c) 2015-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var extend = __webpack_require__(595); - -module.exports = function(str, options, fn) { - if (typeof str !== 'string') { - throw new TypeError('expected a string'); - } - - if (typeof options === 'function') { - fn = options; - options = null; - } - - // allow separator to be defined as a string - if (typeof options === 'string') { - options = { sep: options }; - } - - var opts = extend({sep: '.'}, options); - var quotes = opts.quotes || ['"', "'", '`']; - var brackets; - - if (opts.brackets === true) { - brackets = { - '<': '>', - '(': ')', - '[': ']', - '{': '}' - }; - } else if (opts.brackets) { - brackets = opts.brackets; - } - - var tokens = []; - var stack = []; - var arr = ['']; - var sep = opts.sep; - var len = str.length; - var idx = -1; - var closeIdx; - - function expected() { - if (brackets && stack.length) { - return brackets[stack[stack.length - 1]]; - } - } - - while (++idx < len) { - var ch = str[idx]; - var next = str[idx + 1]; - var tok = { val: ch, idx: idx, arr: arr, str: str }; - tokens.push(tok); - - if (ch === '\\') { - tok.val = keepEscaping(opts, str, idx) === true ? (ch + next) : next; - tok.escaped = true; - if (typeof fn === 'function') { - fn(tok); - } - arr[arr.length - 1] += tok.val; - idx++; - continue; - } - - if (brackets && brackets[ch]) { - stack.push(ch); - var e = expected(); - var i = idx + 1; - - if (str.indexOf(e, i + 1) !== -1) { - while (stack.length && i < len) { - var s = str[++i]; - if (s === '\\') { - s++; - continue; - } - - if (quotes.indexOf(s) !== -1) { - i = getClosingQuote(str, s, i + 1); - continue; - } - - e = expected(); - if (stack.length && str.indexOf(e, i + 1) === -1) { - break; - } - - if (brackets[s]) { - stack.push(s); - continue; - } - - if (e === s) { - stack.pop(); - } - } - } - - closeIdx = i; - if (closeIdx === -1) { - arr[arr.length - 1] += ch; - continue; - } - - ch = str.slice(idx, closeIdx + 1); - tok.val = ch; - tok.idx = idx = closeIdx; - } - - if (quotes.indexOf(ch) !== -1) { - closeIdx = getClosingQuote(str, ch, idx + 1); - if (closeIdx === -1) { - arr[arr.length - 1] += ch; - continue; - } - - if (keepQuotes(ch, opts) === true) { - ch = str.slice(idx, closeIdx + 1); - } else { - ch = str.slice(idx + 1, closeIdx); - } - - tok.val = ch; - tok.idx = idx = closeIdx; - } - - if (typeof fn === 'function') { - fn(tok, tokens); - ch = tok.val; - idx = tok.idx; - } - - if (tok.val === sep && tok.split !== false) { - arr.push(''); - continue; - } - - arr[arr.length - 1] += tok.val; - } - - return arr; -}; - -function getClosingQuote(str, ch, i, brackets) { - var idx = str.indexOf(ch, i); - if (str.charAt(idx - 1) === '\\') { - return getClosingQuote(str, ch, idx + 1); - } - return idx; -} - -function keepQuotes(ch, opts) { - if (opts.keepDoubleQuotes === true && ch === '"') return true; - if (opts.keepSingleQuotes === true && ch === "'") return true; - return opts.keepQuotes; -} - -function keepEscaping(opts, str, idx) { - if (typeof opts.keepEscaping === 'function') { - return opts.keepEscaping(str, idx); - } - return opts.keepEscaping === true || str[idx + 1] === '\\'; -} - - -/***/ }), -/* 606 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * arr-flatten - * - * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -module.exports = function (arr) { - return flat(arr, []); -}; - -function flat(arr, res) { - var i = 0, cur; - var len = arr.length; - for (; i < len; i++) { - cur = arr[i]; - Array.isArray(cur) ? flat(cur, res) : res.push(cur); - } - return res; -} - - -/***/ }), -/* 607 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * fill-range - * - * Copyright (c) 2014-2015, 2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var util = __webpack_require__(112); -var isNumber = __webpack_require__(608); -var extend = __webpack_require__(609); -var repeat = __webpack_require__(610); -var toRegex = __webpack_require__(611); - -/** - * Return a range of numbers or letters. - * - * @param {String} `start` Start of the range - * @param {String} `stop` End of the range - * @param {String} `step` Increment or decrement to use. - * @param {Function} `fn` Custom function to modify each element in the range. - * @return {Array} - */ - -function fillRange(start, stop, step, options) { - if (typeof start === 'undefined') { - return []; - } - - if (typeof stop === 'undefined' || start === stop) { - // special case, for handling negative zero - var isString = typeof start === 'string'; - if (isNumber(start) && !toNumber(start)) { - return [isString ? '0' : 0]; - } - return [start]; - } - - if (typeof step !== 'number' && typeof step !== 'string') { - options = step; - step = undefined; - } - - if (typeof options === 'function') { - options = { transform: options }; - } - - var opts = extend({step: step}, options); - if (opts.step && !isValidNumber(opts.step)) { - if (opts.strictRanges === true) { - throw new TypeError('expected options.step to be a number'); - } - return []; - } - - opts.isNumber = isValidNumber(start) && isValidNumber(stop); - if (!opts.isNumber && !isValid(start, stop)) { - if (opts.strictRanges === true) { - throw new RangeError('invalid range arguments: ' + util.inspect([start, stop])); - } - return []; - } - - opts.isPadded = isPadded(start) || isPadded(stop); - opts.toString = opts.stringify - || typeof opts.step === 'string' - || typeof start === 'string' - || typeof stop === 'string' - || !opts.isNumber; - - if (opts.isPadded) { - opts.maxLength = Math.max(String(start).length, String(stop).length); - } - - // support legacy minimatch/fill-range options - if (typeof opts.optimize === 'boolean') opts.toRegex = opts.optimize; - if (typeof opts.makeRe === 'boolean') opts.toRegex = opts.makeRe; - return expand(start, stop, opts); -} - -function expand(start, stop, options) { - var a = options.isNumber ? toNumber(start) : start.charCodeAt(0); - var b = options.isNumber ? toNumber(stop) : stop.charCodeAt(0); - - var step = Math.abs(toNumber(options.step)) || 1; - if (options.toRegex && step === 1) { - return toRange(a, b, start, stop, options); - } - - var zero = {greater: [], lesser: []}; - var asc = a < b; - var arr = new Array(Math.round((asc ? b - a : a - b) / step)); - var idx = 0; - - while (asc ? a <= b : a >= b) { - var val = options.isNumber ? a : String.fromCharCode(a); - if (options.toRegex && (val >= 0 || !options.isNumber)) { - zero.greater.push(val); - } else { - zero.lesser.push(Math.abs(val)); - } - - if (options.isPadded) { - val = zeros(val, options); - } - - if (options.toString) { - val = String(val); - } - - if (typeof options.transform === 'function') { - arr[idx++] = options.transform(val, a, b, step, idx, arr, options); - } else { - arr[idx++] = val; - } - - if (asc) { - a += step; - } else { - a -= step; - } - } - - if (options.toRegex === true) { - return toSequence(arr, zero, options); - } - return arr; -} - -function toRange(a, b, start, stop, options) { - if (options.isPadded) { - return toRegex(start, stop, options); - } - - if (options.isNumber) { - return toRegex(Math.min(a, b), Math.max(a, b), options); - } - - var start = String.fromCharCode(Math.min(a, b)); - var stop = String.fromCharCode(Math.max(a, b)); - return '[' + start + '-' + stop + ']'; -} - -function toSequence(arr, zeros, options) { - var greater = '', lesser = ''; - if (zeros.greater.length) { - greater = zeros.greater.join('|'); - } - if (zeros.lesser.length) { - lesser = '-(' + zeros.lesser.join('|') + ')'; - } - var res = greater && lesser - ? greater + '|' + lesser - : greater || lesser; - - if (options.capture) { - return '(' + res + ')'; - } - return res; -} - -function zeros(val, options) { - if (options.isPadded) { - var str = String(val); - var len = str.length; - var dash = ''; - if (str.charAt(0) === '-') { - dash = '-'; - str = str.slice(1); - } - var diff = options.maxLength - len; - var pad = repeat('0', diff); - val = (dash + pad + str); - } - if (options.stringify) { - return String(val); - } - return val; -} - -function toNumber(val) { - return Number(val) || 0; -} - -function isPadded(str) { - return /^-?0\d/.test(str); -} - -function isValid(min, max) { - return (isValidNumber(min) || isValidLetter(min)) - && (isValidNumber(max) || isValidLetter(max)); -} - -function isValidLetter(ch) { - return typeof ch === 'string' && ch.length === 1 && /^\w+$/.test(ch); -} - -function isValidNumber(n) { - return isNumber(n) && !/\./.test(n); -} - -/** - * Expose `fillRange` - * @type {Function} - */ - -module.exports = fillRange; - - -/***/ }), -/* 608 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * is-number - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -var typeOf = __webpack_require__(592); - -module.exports = function isNumber(num) { - var type = typeOf(num); - - if (type === 'string') { - if (!num.trim()) return false; - } else if (type !== 'number') { - return false; - } - - return (num - num + 1) >= 0; -}; - - -/***/ }), -/* 609 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var isObject = __webpack_require__(602); - -module.exports = function extend(o/*, objects*/) { - if (!isObject(o)) { o = {}; } - - var len = arguments.length; - for (var i = 1; i < len; i++) { - var obj = arguments[i]; - - if (isObject(obj)) { - assign(o, obj); - } - } - return o; -}; - -function assign(a, b) { - for (var key in b) { - if (hasOwn(b, key)) { - a[key] = b[key]; - } - } -} - -/** - * Returns true if the given `key` is an own property of `obj`. - */ - -function hasOwn(obj, key) { - return Object.prototype.hasOwnProperty.call(obj, key); -} - - -/***/ }), -/* 610 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * repeat-string - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -/** - * Results cache - */ - -var res = ''; -var cache; - -/** - * Expose `repeat` - */ - -module.exports = repeat; - -/** - * Repeat the given `string` the specified `number` - * of times. - * - * **Example:** - * - * ```js - * var repeat = require('repeat-string'); - * repeat('A', 5); - * //=> AAAAA - * ``` - * - * @param {String} `string` The string to repeat - * @param {Number} `number` The number of times to repeat the string - * @return {String} Repeated string - * @api public - */ - -function repeat(str, num) { - if (typeof str !== 'string') { - throw new TypeError('expected a string'); - } - - // cover common, quick use cases - if (num === 1) return str; - if (num === 2) return str + str; - - var max = str.length * num; - if (cache !== str || typeof cache === 'undefined') { - cache = str; - res = ''; - } else if (res.length >= max) { - return res.substr(0, max); - } - - while (max > res.length && num > 1) { - if (num & 1) { - res += str; - } - - num >>= 1; - str += str; - } - - res += str; - res = res.substr(0, max); - return res; -} - - -/***/ }), -/* 611 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * to-regex-range - * - * Copyright (c) 2015, 2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var repeat = __webpack_require__(610); -var isNumber = __webpack_require__(608); -var cache = {}; - -function toRegexRange(min, max, options) { - if (isNumber(min) === false) { - throw new RangeError('toRegexRange: first argument is invalid.'); - } - - if (typeof max === 'undefined' || min === max) { - return String(min); - } - - if (isNumber(max) === false) { - throw new RangeError('toRegexRange: second argument is invalid.'); - } - - options = options || {}; - var relax = String(options.relaxZeros); - var shorthand = String(options.shorthand); - var capture = String(options.capture); - var key = min + ':' + max + '=' + relax + shorthand + capture; - if (cache.hasOwnProperty(key)) { - return cache[key].result; - } - - var a = Math.min(min, max); - var b = Math.max(min, max); - - if (Math.abs(a - b) === 1) { - var result = min + '|' + max; - if (options.capture) { - return '(' + result + ')'; - } - return result; - } - - var isPadded = padding(min) || padding(max); - var positives = []; - var negatives = []; - - var tok = {min: min, max: max, a: a, b: b}; - if (isPadded) { - tok.isPadded = isPadded; - tok.maxLen = String(tok.max).length; - } - - if (a < 0) { - var newMin = b < 0 ? Math.abs(b) : 1; - var newMax = Math.abs(a); - negatives = splitToPatterns(newMin, newMax, tok, options); - a = tok.a = 0; - } - - if (b >= 0) { - positives = splitToPatterns(a, b, tok, options); - } - - tok.negatives = negatives; - tok.positives = positives; - tok.result = siftPatterns(negatives, positives, options); - - if (options.capture && (positives.length + negatives.length) > 1) { - tok.result = '(' + tok.result + ')'; - } - - cache[key] = tok; - return tok.result; -} - -function siftPatterns(neg, pos, options) { - var onlyNegative = filterPatterns(neg, pos, '-', false, options) || []; - var onlyPositive = filterPatterns(pos, neg, '', false, options) || []; - var intersected = filterPatterns(neg, pos, '-?', true, options) || []; - var subpatterns = onlyNegative.concat(intersected).concat(onlyPositive); - return subpatterns.join('|'); -} - -function splitToRanges(min, max) { - min = Number(min); - max = Number(max); - - var nines = 1; - var stops = [max]; - var stop = +countNines(min, nines); - - while (min <= stop && stop <= max) { - stops = push(stops, stop); - nines += 1; - stop = +countNines(min, nines); - } - - var zeros = 1; - stop = countZeros(max + 1, zeros) - 1; - - while (min < stop && stop <= max) { - stops = push(stops, stop); - zeros += 1; - stop = countZeros(max + 1, zeros) - 1; - } - - stops.sort(compare); - return stops; -} - -/** - * Convert a range to a regex pattern - * @param {Number} `start` - * @param {Number} `stop` - * @return {String} - */ - -function rangeToPattern(start, stop, options) { - if (start === stop) { - return {pattern: String(start), digits: []}; - } - - var zipped = zip(String(start), String(stop)); - var len = zipped.length, i = -1; - - var pattern = ''; - var digits = 0; - - while (++i < len) { - var numbers = zipped[i]; - var startDigit = numbers[0]; - var stopDigit = numbers[1]; - - if (startDigit === stopDigit) { - pattern += startDigit; - - } else if (startDigit !== '0' || stopDigit !== '9') { - pattern += toCharacterClass(startDigit, stopDigit); - - } else { - digits += 1; - } - } - - if (digits) { - pattern += options.shorthand ? '\\d' : '[0-9]'; - } - - return { pattern: pattern, digits: [digits] }; -} - -function splitToPatterns(min, max, tok, options) { - var ranges = splitToRanges(min, max); - var len = ranges.length; - var idx = -1; - - var tokens = []; - var start = min; - var prev; - - while (++idx < len) { - var range = ranges[idx]; - var obj = rangeToPattern(start, range, options); - var zeros = ''; - - if (!tok.isPadded && prev && prev.pattern === obj.pattern) { - if (prev.digits.length > 1) { - prev.digits.pop(); - } - prev.digits.push(obj.digits[0]); - prev.string = prev.pattern + toQuantifier(prev.digits); - start = range + 1; - continue; - } - - if (tok.isPadded) { - zeros = padZeros(range, tok); - } - - obj.string = zeros + obj.pattern + toQuantifier(obj.digits); - tokens.push(obj); - start = range + 1; - prev = obj; - } - - return tokens; -} - -function filterPatterns(arr, comparison, prefix, intersection, options) { - var res = []; - - for (var i = 0; i < arr.length; i++) { - var tok = arr[i]; - var ele = tok.string; - - if (options.relaxZeros !== false) { - if (prefix === '-' && ele.charAt(0) === '0') { - if (ele.charAt(1) === '{') { - ele = '0*' + ele.replace(/^0\{\d+\}/, ''); - } else { - ele = '0*' + ele.slice(1); - } - } - } - - if (!intersection && !contains(comparison, 'string', ele)) { - res.push(prefix + ele); - } - - if (intersection && contains(comparison, 'string', ele)) { - res.push(prefix + ele); - } - } - return res; -} - -/** - * Zip strings (`for in` can be used on string characters) - */ - -function zip(a, b) { - var arr = []; - for (var ch in a) arr.push([a[ch], b[ch]]); - return arr; -} - -function compare(a, b) { - return a > b ? 1 : b > a ? -1 : 0; -} - -function push(arr, ele) { - if (arr.indexOf(ele) === -1) arr.push(ele); - return arr; -} - -function contains(arr, key, val) { - for (var i = 0; i < arr.length; i++) { - if (arr[i][key] === val) { - return true; - } - } - return false; -} - -function countNines(min, len) { - return String(min).slice(0, -len) + repeat('9', len); -} - -function countZeros(integer, zeros) { - return integer - (integer % Math.pow(10, zeros)); -} - -function toQuantifier(digits) { - var start = digits[0]; - var stop = digits[1] ? (',' + digits[1]) : ''; - if (!stop && (!start || start === 1)) { - return ''; - } - return '{' + start + stop + '}'; -} - -function toCharacterClass(a, b) { - return '[' + a + ((b - a === 1) ? '' : '-') + b + ']'; -} - -function padding(str) { - return /^-?(0+)\d/.exec(str); -} - -function padZeros(val, tok) { - if (tok.isPadded) { - var diff = Math.abs(tok.maxLen - String(val).length); - switch (diff) { - case 0: - return ''; - case 1: - return '0'; - default: { - return '0{' + diff + '}'; - } - } - } - return val; -} - -/** - * Expose `toRegexRange` - */ - -module.exports = toRegexRange; - - -/***/ }), -/* 612 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * repeat-element - * - * Copyright (c) 2015-present, Jon Schlinkert. - * Licensed under the MIT license. - */ - - - -module.exports = function repeat(ele, num) { - if (Array.prototype.fill) { - return new Array(num).fill(ele); - } - - var arr = new Array(num); - - for (var i = 0; i < num; i++) { - arr[i] = ele; - } - - return arr; -}; - - -/***/ }), -/* 613 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var Node = __webpack_require__(614); -var utils = __webpack_require__(604); - -/** - * Braces parsers - */ - -module.exports = function(braces, options) { - braces.parser - .set('bos', function() { - if (!this.parsed) { - this.ast = this.nodes[0] = new Node(this.ast); - } - }) - - /** - * Character parsers - */ - - .set('escape', function() { - var pos = this.position(); - var m = this.match(/^(?:\\(.)|\$\{)/); - if (!m) return; - - var prev = this.prev(); - var last = utils.last(prev.nodes); - - var node = pos(new Node({ - type: 'text', - multiplier: 1, - val: m[0] - })); - - if (node.val === '\\\\') { - return node; - } - - if (node.val === '${') { - var str = this.input; - var idx = -1; - var ch; - - while ((ch = str[++idx])) { - this.consume(1); - node.val += ch; - if (ch === '\\') { - node.val += str[++idx]; - continue; - } - if (ch === '}') { - break; - } - } - } - - if (this.options.unescape !== false) { - node.val = node.val.replace(/\\([{}])/g, '$1'); - } - - if (last.val === '"' && this.input.charAt(0) === '"') { - last.val = node.val; - this.consume(1); - return; - } - - return concatNodes.call(this, pos, node, prev, options); - }) - - /** - * Brackets: "[...]" (basic, this is overridden by - * other parsers in more advanced implementations) - */ - - .set('bracket', function() { - var isInside = this.isInside('brace'); - var pos = this.position(); - var m = this.match(/^(?:\[([!^]?)([^\]]{2,}|\]-)(\]|[^*+?]+)|\[)/); - if (!m) return; - - var prev = this.prev(); - var val = m[0]; - var negated = m[1] ? '^' : ''; - var inner = m[2] || ''; - var close = m[3] || ''; - - if (isInside && prev.type === 'brace') { - prev.text = prev.text || ''; - prev.text += val; - } - - var esc = this.input.slice(0, 2); - if (inner === '' && esc === '\\]') { - inner += esc; - this.consume(2); - - var str = this.input; - var idx = -1; - var ch; - - while ((ch = str[++idx])) { - this.consume(1); - if (ch === ']') { - close = ch; - break; - } - inner += ch; - } - } - - return pos(new Node({ - type: 'bracket', - val: val, - escaped: close !== ']', - negated: negated, - inner: inner, - close: close - })); - }) - - /** - * Empty braces (we capture these early to - * speed up processing in the compiler) - */ - - .set('multiplier', function() { - var isInside = this.isInside('brace'); - var pos = this.position(); - var m = this.match(/^\{((?:,|\{,+\})+)\}/); - if (!m) return; - - this.multiplier = true; - var prev = this.prev(); - var val = m[0]; - - if (isInside && prev.type === 'brace') { - prev.text = prev.text || ''; - prev.text += val; - } - - var node = pos(new Node({ - type: 'text', - multiplier: 1, - match: m, - val: val - })); - - return concatNodes.call(this, pos, node, prev, options); - }) - - /** - * Open - */ - - .set('brace.open', function() { - var pos = this.position(); - var m = this.match(/^\{(?!(?:[^\\}]?|,+)\})/); - if (!m) return; - - var prev = this.prev(); - var last = utils.last(prev.nodes); - - // if the last parsed character was an extglob character - // we need to _not optimize_ the brace pattern because - // it might be mistaken for an extglob by a downstream parser - if (last && last.val && isExtglobChar(last.val.slice(-1))) { - last.optimize = false; - } - - var open = pos(new Node({ - type: 'brace.open', - val: m[0] - })); - - var node = pos(new Node({ - type: 'brace', - nodes: [] - })); - - node.push(open); - prev.push(node); - this.push('brace', node); - }) - - /** - * Close - */ - - .set('brace.close', function() { - var pos = this.position(); - var m = this.match(/^\}/); - if (!m || !m[0]) return; - - var brace = this.pop('brace'); - var node = pos(new Node({ - type: 'brace.close', - val: m[0] - })); - - if (!this.isType(brace, 'brace')) { - if (this.options.strict) { - throw new Error('missing opening "{"'); - } - node.type = 'text'; - node.multiplier = 0; - node.escaped = true; - return node; - } - - var prev = this.prev(); - var last = utils.last(prev.nodes); - if (last.text) { - var lastNode = utils.last(last.nodes); - if (lastNode.val === ')' && /[!@*?+]\(/.test(last.text)) { - var open = last.nodes[0]; - var text = last.nodes[1]; - if (open.type === 'brace.open' && text && text.type === 'text') { - text.optimize = false; - } - } - } - - if (brace.nodes.length > 2) { - var first = brace.nodes[1]; - if (first.type === 'text' && first.val === ',') { - brace.nodes.splice(1, 1); - brace.nodes.push(first); - } - } - - brace.push(node); - }) - - /** - * Capture boundary characters - */ - - .set('boundary', function() { - var pos = this.position(); - var m = this.match(/^[$^](?!\{)/); - if (!m) return; - return pos(new Node({ - type: 'text', - val: m[0] - })); - }) - - /** - * One or zero, non-comma characters wrapped in braces - */ - - .set('nobrace', function() { - var isInside = this.isInside('brace'); - var pos = this.position(); - var m = this.match(/^\{[^,]?\}/); - if (!m) return; - - var prev = this.prev(); - var val = m[0]; - - if (isInside && prev.type === 'brace') { - prev.text = prev.text || ''; - prev.text += val; - } - - return pos(new Node({ - type: 'text', - multiplier: 0, - val: val - })); - }) - - /** - * Text - */ - - .set('text', function() { - var isInside = this.isInside('brace'); - var pos = this.position(); - var m = this.match(/^((?!\\)[^${}[\]])+/); - if (!m) return; - - var prev = this.prev(); - var val = m[0]; - - if (isInside && prev.type === 'brace') { - prev.text = prev.text || ''; - prev.text += val; - } - - var node = pos(new Node({ - type: 'text', - multiplier: 1, - val: val - })); - - return concatNodes.call(this, pos, node, prev, options); - }); -}; - -/** - * Returns true if the character is an extglob character. - */ - -function isExtglobChar(ch) { - return ch === '!' || ch === '@' || ch === '*' || ch === '?' || ch === '+'; -} - -/** - * Combine text nodes, and calculate empty sets (`{,,}`) - * @param {Function} `pos` Function to calculate node position - * @param {Object} `node` AST node - * @return {Object} - */ - -function concatNodes(pos, node, parent, options) { - node.orig = node.val; - var prev = this.prev(); - var last = utils.last(prev.nodes); - var isEscaped = false; - - if (node.val.length > 1) { - var a = node.val.charAt(0); - var b = node.val.slice(-1); - - isEscaped = (a === '"' && b === '"') - || (a === "'" && b === "'") - || (a === '`' && b === '`'); - } - - if (isEscaped && options.unescape !== false) { - node.val = node.val.slice(1, node.val.length - 1); - node.escaped = true; - } - - if (node.match) { - var match = node.match[1]; - if (!match || match.indexOf('}') === -1) { - match = node.match[0]; - } - - // replace each set with a single "," - var val = match.replace(/\{/g, ',').replace(/\}/g, ''); - node.multiplier *= val.length; - node.val = ''; - } - - var simpleText = last.type === 'text' - && last.multiplier === 1 - && node.multiplier === 1 - && node.val; - - if (simpleText) { - last.val += node.val; - return; - } - - prev.push(node); -} - - -/***/ }), -/* 614 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var isObject = __webpack_require__(590); -var define = __webpack_require__(615); -var utils = __webpack_require__(616); -var ownNames; - -/** - * Create a new AST `Node` with the given `val` and `type`. - * - * ```js - * var node = new Node('*', 'Star'); - * var node = new Node({type: 'star', val: '*'}); - * ``` - * @name Node - * @param {String|Object} `val` Pass a matched substring, or an object to merge onto the node. - * @param {String} `type` The node type to use when `val` is a string. - * @return {Object} node instance - * @api public - */ - -function Node(val, type, parent) { - if (typeof type !== 'string') { - parent = type; - type = null; - } - - define(this, 'parent', parent); - define(this, 'isNode', true); - define(this, 'expect', null); - - if (typeof type !== 'string' && isObject(val)) { - lazyKeys(); - var keys = Object.keys(val); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - if (ownNames.indexOf(key) === -1) { - this[key] = val[key]; - } - } - } else { - this.type = type; - this.val = val; - } -} - -/** - * Returns true if the given value is a node. - * - * ```js - * var Node = require('snapdragon-node'); - * var node = new Node({type: 'foo'}); - * console.log(Node.isNode(node)); //=> true - * console.log(Node.isNode({})); //=> false - * ``` - * @param {Object} `node` - * @returns {Boolean} - * @api public - */ - -Node.isNode = function(node) { - return utils.isNode(node); -}; - -/** - * Define a non-enumberable property on the node instance. - * Useful for adding properties that shouldn't be extended - * or visible during debugging. - * - * ```js - * var node = new Node(); - * node.define('foo', 'something non-enumerable'); - * ``` - * @param {String} `name` - * @param {any} `val` - * @return {Object} returns the node instance - * @api public - */ - -Node.prototype.define = function(name, val) { - define(this, name, val); - return this; -}; - -/** - * Returns true if `node.val` is an empty string, or `node.nodes` does - * not contain any non-empty text nodes. - * - * ```js - * var node = new Node({type: 'text'}); - * node.isEmpty(); //=> true - * node.val = 'foo'; - * node.isEmpty(); //=> false - * ``` - * @param {Function} `fn` (optional) Filter function that is called on `node` and/or child nodes. `isEmpty` will return false immediately when the filter function returns false on any nodes. - * @return {Boolean} - * @api public - */ - -Node.prototype.isEmpty = function(fn) { - return utils.isEmpty(this, fn); -}; - -/** - * Given node `foo` and node `bar`, push node `bar` onto `foo.nodes`, and - * set `foo` as `bar.parent`. - * - * ```js - * var foo = new Node({type: 'foo'}); - * var bar = new Node({type: 'bar'}); - * foo.push(bar); - * ``` - * @param {Object} `node` - * @return {Number} Returns the length of `node.nodes` - * @api public - */ - -Node.prototype.push = function(node) { - assert(Node.isNode(node), 'expected node to be an instance of Node'); - define(node, 'parent', this); - - this.nodes = this.nodes || []; - return this.nodes.push(node); -}; - -/** - * Given node `foo` and node `bar`, unshift node `bar` onto `foo.nodes`, and - * set `foo` as `bar.parent`. - * - * ```js - * var foo = new Node({type: 'foo'}); - * var bar = new Node({type: 'bar'}); - * foo.unshift(bar); - * ``` - * @param {Object} `node` - * @return {Number} Returns the length of `node.nodes` - * @api public - */ - -Node.prototype.unshift = function(node) { - assert(Node.isNode(node), 'expected node to be an instance of Node'); - define(node, 'parent', this); - - this.nodes = this.nodes || []; - return this.nodes.unshift(node); -}; - -/** - * Pop a node from `node.nodes`. - * - * ```js - * var node = new Node({type: 'foo'}); - * node.push(new Node({type: 'a'})); - * node.push(new Node({type: 'b'})); - * node.push(new Node({type: 'c'})); - * node.push(new Node({type: 'd'})); - * console.log(node.nodes.length); - * //=> 4 - * node.pop(); - * console.log(node.nodes.length); - * //=> 3 - * ``` - * @return {Number} Returns the popped `node` - * @api public - */ - -Node.prototype.pop = function() { - return this.nodes && this.nodes.pop(); -}; - -/** - * Shift a node from `node.nodes`. - * - * ```js - * var node = new Node({type: 'foo'}); - * node.push(new Node({type: 'a'})); - * node.push(new Node({type: 'b'})); - * node.push(new Node({type: 'c'})); - * node.push(new Node({type: 'd'})); - * console.log(node.nodes.length); - * //=> 4 - * node.shift(); - * console.log(node.nodes.length); - * //=> 3 - * ``` - * @return {Object} Returns the shifted `node` - * @api public - */ - -Node.prototype.shift = function() { - return this.nodes && this.nodes.shift(); -}; - -/** - * Remove `node` from `node.nodes`. - * - * ```js - * node.remove(childNode); - * ``` - * @param {Object} `node` - * @return {Object} Returns the removed node. - * @api public - */ - -Node.prototype.remove = function(node) { - assert(Node.isNode(node), 'expected node to be an instance of Node'); - this.nodes = this.nodes || []; - var idx = node.index; - if (idx !== -1) { - node.index = -1; - return this.nodes.splice(idx, 1); - } - return null; -}; - -/** - * Get the first child node from `node.nodes` that matches the given `type`. - * If `type` is a number, the child node at that index is returned. - * - * ```js - * var child = node.find(1); //<= index of the node to get - * var child = node.find('foo'); //<= node.type of a child node - * var child = node.find(/^(foo|bar)$/); //<= regex to match node.type - * var child = node.find(['foo', 'bar']); //<= array of node.type(s) - * ``` - * @param {String} `type` - * @return {Object} Returns a child node or undefined. - * @api public - */ - -Node.prototype.find = function(type) { - return utils.findNode(this.nodes, type); -}; - -/** - * Return true if the node is the given `type`. - * - * ```js - * var node = new Node({type: 'bar'}); - * cosole.log(node.isType('foo')); // false - * cosole.log(node.isType(/^(foo|bar)$/)); // true - * cosole.log(node.isType(['foo', 'bar'])); // true - * ``` - * @param {String} `type` - * @return {Boolean} - * @api public - */ - -Node.prototype.isType = function(type) { - return utils.isType(this, type); -}; - -/** - * Return true if the `node.nodes` has the given `type`. - * - * ```js - * var foo = new Node({type: 'foo'}); - * var bar = new Node({type: 'bar'}); - * foo.push(bar); - * - * cosole.log(foo.hasType('qux')); // false - * cosole.log(foo.hasType(/^(qux|bar)$/)); // true - * cosole.log(foo.hasType(['qux', 'bar'])); // true - * ``` - * @param {String} `type` - * @return {Boolean} - * @api public - */ - -Node.prototype.hasType = function(type) { - return utils.hasType(this, type); -}; - -/** - * Get the siblings array, or `null` if it doesn't exist. - * - * ```js - * var foo = new Node({type: 'foo'}); - * var bar = new Node({type: 'bar'}); - * var baz = new Node({type: 'baz'}); - * foo.push(bar); - * foo.push(baz); - * - * console.log(bar.siblings.length) // 2 - * console.log(baz.siblings.length) // 2 - * ``` - * @return {Array} - * @api public - */ - -Object.defineProperty(Node.prototype, 'siblings', { - set: function() { - throw new Error('node.siblings is a getter and cannot be defined'); - }, - get: function() { - return this.parent ? this.parent.nodes : null; - } -}); - -/** - * Get the node's current index from `node.parent.nodes`. - * This should always be correct, even when the parent adds nodes. - * - * ```js - * var foo = new Node({type: 'foo'}); - * var bar = new Node({type: 'bar'}); - * var baz = new Node({type: 'baz'}); - * var qux = new Node({type: 'qux'}); - * foo.push(bar); - * foo.push(baz); - * foo.unshift(qux); - * - * console.log(bar.index) // 1 - * console.log(baz.index) // 2 - * console.log(qux.index) // 0 - * ``` - * @return {Number} - * @api public - */ - -Object.defineProperty(Node.prototype, 'index', { - set: function(index) { - define(this, 'idx', index); - }, - get: function() { - if (!Array.isArray(this.siblings)) { - return -1; - } - var tok = this.idx !== -1 ? this.siblings[this.idx] : null; - if (tok !== this) { - this.idx = this.siblings.indexOf(this); - } - return this.idx; - } -}); - -/** - * Get the previous node from the siblings array or `null`. - * - * ```js - * var foo = new Node({type: 'foo'}); - * var bar = new Node({type: 'bar'}); - * var baz = new Node({type: 'baz'}); - * foo.push(bar); - * foo.push(baz); - * - * console.log(baz.prev.type) // 'bar' - * ``` - * @return {Object} - * @api public - */ - -Object.defineProperty(Node.prototype, 'prev', { - set: function() { - throw new Error('node.prev is a getter and cannot be defined'); - }, - get: function() { - if (Array.isArray(this.siblings)) { - return this.siblings[this.index - 1] || this.parent.prev; - } - return null; - } -}); - -/** - * Get the siblings array, or `null` if it doesn't exist. - * - * ```js - * var foo = new Node({type: 'foo'}); - * var bar = new Node({type: 'bar'}); - * var baz = new Node({type: 'baz'}); - * foo.push(bar); - * foo.push(baz); - * - * console.log(bar.siblings.length) // 2 - * console.log(baz.siblings.length) // 2 - * ``` - * @return {Object} - * @api public - */ - -Object.defineProperty(Node.prototype, 'next', { - set: function() { - throw new Error('node.next is a getter and cannot be defined'); - }, - get: function() { - if (Array.isArray(this.siblings)) { - return this.siblings[this.index + 1] || this.parent.next; - } - return null; - } -}); - -/** - * Get the first node from `node.nodes`. - * - * ```js - * var foo = new Node({type: 'foo'}); - * var bar = new Node({type: 'bar'}); - * var baz = new Node({type: 'baz'}); - * var qux = new Node({type: 'qux'}); - * foo.push(bar); - * foo.push(baz); - * foo.push(qux); - * - * console.log(foo.first.type) // 'bar' - * ``` - * @return {Object} The first node, or undefiend - * @api public - */ - -Object.defineProperty(Node.prototype, 'first', { - get: function() { - return this.nodes ? this.nodes[0] : null; - } -}); - -/** - * Get the last node from `node.nodes`. - * - * ```js - * var foo = new Node({type: 'foo'}); - * var bar = new Node({type: 'bar'}); - * var baz = new Node({type: 'baz'}); - * var qux = new Node({type: 'qux'}); - * foo.push(bar); - * foo.push(baz); - * foo.push(qux); - * - * console.log(foo.last.type) // 'qux' - * ``` - * @return {Object} The last node, or undefiend - * @api public - */ - -Object.defineProperty(Node.prototype, 'last', { - get: function() { - return this.nodes ? utils.last(this.nodes) : null; - } -}); - -/** - * Get the last node from `node.nodes`. - * - * ```js - * var foo = new Node({type: 'foo'}); - * var bar = new Node({type: 'bar'}); - * var baz = new Node({type: 'baz'}); - * var qux = new Node({type: 'qux'}); - * foo.push(bar); - * foo.push(baz); - * foo.push(qux); - * - * console.log(foo.last.type) // 'qux' - * ``` - * @return {Object} The last node, or undefiend - * @api public - */ - -Object.defineProperty(Node.prototype, 'scope', { - get: function() { - if (this.isScope !== true) { - return this.parent ? this.parent.scope : this; - } - return this; - } -}); - -/** - * Get own property names from Node prototype, but only the - * first time `Node` is instantiated - */ - -function lazyKeys() { - if (!ownNames) { - ownNames = Object.getOwnPropertyNames(Node.prototype); - } -} - -/** - * Simplified assertion. Throws an error is `val` is falsey. - */ - -function assert(val, message) { - if (!val) throw new Error(message); -} - -/** - * Expose `Node` - */ - -exports = module.exports = Node; - - -/***/ }), -/* 615 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * define-property - * - * Copyright (c) 2015, 2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var isDescriptor = __webpack_require__(591); - -module.exports = function defineProperty(obj, prop, val) { - if (typeof obj !== 'object' && typeof obj !== 'function') { - throw new TypeError('expected an object or function.'); - } - - if (typeof prop !== 'string') { - throw new TypeError('expected `prop` to be a string.'); - } - - if (isDescriptor(val) && ('set' in val || 'get' in val)) { - return Object.defineProperty(obj, prop, val); - } - - return Object.defineProperty(obj, prop, { - configurable: true, - enumerable: false, - writable: true, - value: val - }); -}; - - -/***/ }), -/* 616 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var typeOf = __webpack_require__(592); -var utils = module.exports; - -/** - * Returns true if the given value is a node. - * - * ```js - * var Node = require('snapdragon-node'); - * var node = new Node({type: 'foo'}); - * console.log(utils.isNode(node)); //=> true - * console.log(utils.isNode({})); //=> false - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @returns {Boolean} - * @api public - */ - -utils.isNode = function(node) { - return typeOf(node) === 'object' && node.isNode === true; -}; - -/** - * Emit an empty string for the given `node`. - * - * ```js - * // do nothing for beginning-of-string - * snapdragon.compiler.set('bos', utils.noop); - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @returns {undefined} - * @api public - */ - -utils.noop = function(node) { - append(this, '', node); -}; - -/** - * Appdend `node.val` to `compiler.output`, exactly as it was created - * by the parser. - * - * ```js - * snapdragon.compiler.set('text', utils.identity); - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @returns {undefined} - * @api public - */ - -utils.identity = function(node) { - append(this, node.val, node); -}; - -/** - * Previously named `.emit`, this method appends the given `val` - * to `compiler.output` for the given node. Useful when you know - * what value should be appended advance, regardless of the actual - * value of `node.val`. - * - * ```js - * snapdragon.compiler - * .set('i', function(node) { - * this.mapVisit(node); - * }) - * .set('i.open', utils.append('')) - * .set('i.close', utils.append('')) - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @returns {Function} Returns a compiler middleware function. - * @api public - */ - -utils.append = function(val) { - return function(node) { - append(this, val, node); - }; -}; - -/** - * Used in compiler middleware, this onverts an AST node into - * an empty `text` node and deletes `node.nodes` if it exists. - * The advantage of this method is that, as opposed to completely - * removing the node, indices will not need to be re-calculated - * in sibling nodes, and nothing is appended to the output. - * - * ```js - * utils.toNoop(node); - * // convert `node.nodes` to the given value instead of deleting it - * utils.toNoop(node, []); - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @param {Array} `nodes` Optionally pass a new `nodes` value, to replace the existing `node.nodes` array. - * @api public - */ - -utils.toNoop = function(node, nodes) { - if (nodes) { - node.nodes = nodes; - } else { - delete node.nodes; - node.type = 'text'; - node.val = ''; - } -}; - -/** - * Visit `node` with the given `fn`. The built-in `.visit` method in snapdragon - * automatically calls registered compilers, this allows you to pass a visitor - * function. - * - * ```js - * snapdragon.compiler.set('i', function(node) { - * utils.visit(node, function(childNode) { - * // do stuff with "childNode" - * return childNode; - * }); - * }); - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @param {Function} `fn` - * @return {Object} returns the node after recursively visiting all child nodes. - * @api public - */ - -utils.visit = function(node, fn) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - assert(isFunction(fn), 'expected a visitor function'); - fn(node); - return node.nodes ? utils.mapVisit(node, fn) : node; -}; - -/** - * Map [visit](#visit) the given `fn` over `node.nodes`. This is called by - * [visit](#visit), use this method if you do not want `fn` to be called on - * the first node. - * - * ```js - * snapdragon.compiler.set('i', function(node) { - * utils.mapVisit(node, function(childNode) { - * // do stuff with "childNode" - * return childNode; - * }); - * }); - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @param {Object} `options` - * @param {Function} `fn` - * @return {Object} returns the node - * @api public - */ - -utils.mapVisit = function(node, fn) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - assert(isArray(node.nodes), 'expected node.nodes to be an array'); - assert(isFunction(fn), 'expected a visitor function'); - - for (var i = 0; i < node.nodes.length; i++) { - utils.visit(node.nodes[i], fn); - } - return node; -}; - -/** - * Unshift an `*.open` node onto `node.nodes`. - * - * ```js - * var Node = require('snapdragon-node'); - * snapdragon.parser.set('brace', function(node) { - * var match = this.match(/^{/); - * if (match) { - * var parent = new Node({type: 'brace'}); - * utils.addOpen(parent, Node); - * console.log(parent.nodes[0]): - * // { type: 'brace.open', val: '' }; - * - * // push the parent "brace" node onto the stack - * this.push(parent); - * - * // return the parent node, so it's also added to the AST - * return brace; - * } - * }); - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @param {Function} `Node` (required) Node constructor function from [snapdragon-node][]. - * @param {Function} `filter` Optionaly specify a filter function to exclude the node. - * @return {Object} Returns the created opening node. - * @api public - */ - -utils.addOpen = function(node, Node, val, filter) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - assert(isFunction(Node), 'expected Node to be a constructor function'); - - if (typeof val === 'function') { - filter = val; - val = ''; - } - - if (typeof filter === 'function' && !filter(node)) return; - var open = new Node({ type: node.type + '.open', val: val}); - var unshift = node.unshift || node.unshiftNode; - if (typeof unshift === 'function') { - unshift.call(node, open); - } else { - utils.unshiftNode(node, open); - } - return open; -}; - -/** - * Push a `*.close` node onto `node.nodes`. - * - * ```js - * var Node = require('snapdragon-node'); - * snapdragon.parser.set('brace', function(node) { - * var match = this.match(/^}/); - * if (match) { - * var parent = this.parent(); - * if (parent.type !== 'brace') { - * throw new Error('missing opening: ' + '}'); - * } - * - * utils.addClose(parent, Node); - * console.log(parent.nodes[parent.nodes.length - 1]): - * // { type: 'brace.close', val: '' }; - * - * // no need to return a node, since the parent - * // was already added to the AST - * return; - * } - * }); - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @param {Function} `Node` (required) Node constructor function from [snapdragon-node][]. - * @param {Function} `filter` Optionaly specify a filter function to exclude the node. - * @return {Object} Returns the created closing node. - * @api public - */ - -utils.addClose = function(node, Node, val, filter) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - assert(isFunction(Node), 'expected Node to be a constructor function'); - - if (typeof val === 'function') { - filter = val; - val = ''; - } - - if (typeof filter === 'function' && !filter(node)) return; - var close = new Node({ type: node.type + '.close', val: val}); - var push = node.push || node.pushNode; - if (typeof push === 'function') { - push.call(node, close); - } else { - utils.pushNode(node, close); - } - return close; -}; - -/** - * Wraps the given `node` with `*.open` and `*.close` nodes. - * - * @param {Object} `node` Instance of [snapdragon-node][] - * @param {Function} `Node` (required) Node constructor function from [snapdragon-node][]. - * @param {Function} `filter` Optionaly specify a filter function to exclude the node. - * @return {Object} Returns the node - * @api public - */ - -utils.wrapNodes = function(node, Node, filter) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - assert(isFunction(Node), 'expected Node to be a constructor function'); - - utils.addOpen(node, Node, filter); - utils.addClose(node, Node, filter); - return node; -}; - -/** - * Push the given `node` onto `parent.nodes`, and set `parent` as `node.parent. - * - * ```js - * var parent = new Node({type: 'foo'}); - * var node = new Node({type: 'bar'}); - * utils.pushNode(parent, node); - * console.log(parent.nodes[0].type) // 'bar' - * console.log(node.parent.type) // 'foo' - * ``` - * @param {Object} `parent` - * @param {Object} `node` Instance of [snapdragon-node][] - * @return {Object} Returns the child node - * @api public - */ - -utils.pushNode = function(parent, node) { - assert(utils.isNode(parent), 'expected parent node to be an instance of Node'); - assert(utils.isNode(node), 'expected node to be an instance of Node'); - - node.define('parent', parent); - parent.nodes = parent.nodes || []; - parent.nodes.push(node); - return node; -}; - -/** - * Unshift `node` onto `parent.nodes`, and set `parent` as `node.parent. - * - * ```js - * var parent = new Node({type: 'foo'}); - * var node = new Node({type: 'bar'}); - * utils.unshiftNode(parent, node); - * console.log(parent.nodes[0].type) // 'bar' - * console.log(node.parent.type) // 'foo' - * ``` - * @param {Object} `parent` - * @param {Object} `node` Instance of [snapdragon-node][] - * @return {undefined} - * @api public - */ - -utils.unshiftNode = function(parent, node) { - assert(utils.isNode(parent), 'expected parent node to be an instance of Node'); - assert(utils.isNode(node), 'expected node to be an instance of Node'); - - node.define('parent', parent); - parent.nodes = parent.nodes || []; - parent.nodes.unshift(node); -}; - -/** - * Pop the last `node` off of `parent.nodes`. The advantage of - * using this method is that it checks for `node.nodes` and works - * with any version of `snapdragon-node`. - * - * ```js - * var parent = new Node({type: 'foo'}); - * utils.pushNode(parent, new Node({type: 'foo'})); - * utils.pushNode(parent, new Node({type: 'bar'})); - * utils.pushNode(parent, new Node({type: 'baz'})); - * console.log(parent.nodes.length); //=> 3 - * utils.popNode(parent); - * console.log(parent.nodes.length); //=> 2 - * ``` - * @param {Object} `parent` - * @param {Object} `node` Instance of [snapdragon-node][] - * @return {Number|Undefined} Returns the length of `node.nodes` or undefined. - * @api public - */ - -utils.popNode = function(node) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - if (typeof node.pop === 'function') { - return node.pop(); - } - return node.nodes && node.nodes.pop(); -}; - -/** - * Shift the first `node` off of `parent.nodes`. The advantage of - * using this method is that it checks for `node.nodes` and works - * with any version of `snapdragon-node`. - * - * ```js - * var parent = new Node({type: 'foo'}); - * utils.pushNode(parent, new Node({type: 'foo'})); - * utils.pushNode(parent, new Node({type: 'bar'})); - * utils.pushNode(parent, new Node({type: 'baz'})); - * console.log(parent.nodes.length); //=> 3 - * utils.shiftNode(parent); - * console.log(parent.nodes.length); //=> 2 - * ``` - * @param {Object} `parent` - * @param {Object} `node` Instance of [snapdragon-node][] - * @return {Number|Undefined} Returns the length of `node.nodes` or undefined. - * @api public - */ - -utils.shiftNode = function(node) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - if (typeof node.shift === 'function') { - return node.shift(); - } - return node.nodes && node.nodes.shift(); -}; - -/** - * Remove the specified `node` from `parent.nodes`. - * - * ```js - * var parent = new Node({type: 'abc'}); - * var foo = new Node({type: 'foo'}); - * utils.pushNode(parent, foo); - * utils.pushNode(parent, new Node({type: 'bar'})); - * utils.pushNode(parent, new Node({type: 'baz'})); - * console.log(parent.nodes.length); //=> 3 - * utils.removeNode(parent, foo); - * console.log(parent.nodes.length); //=> 2 - * ``` - * @param {Object} `parent` - * @param {Object} `node` Instance of [snapdragon-node][] - * @return {Object|undefined} Returns the removed node, if successful, or undefined if it does not exist on `parent.nodes`. - * @api public - */ - -utils.removeNode = function(parent, node) { - assert(utils.isNode(parent), 'expected parent.node to be an instance of Node'); - assert(utils.isNode(node), 'expected node to be an instance of Node'); - - if (!parent.nodes) { - return null; - } - - if (typeof parent.remove === 'function') { - return parent.remove(node); - } - - var idx = parent.nodes.indexOf(node); - if (idx !== -1) { - return parent.nodes.splice(idx, 1); - } -}; - -/** - * Returns true if `node.type` matches the given `type`. Throws a - * `TypeError` if `node` is not an instance of `Node`. - * - * ```js - * var Node = require('snapdragon-node'); - * var node = new Node({type: 'foo'}); - * console.log(utils.isType(node, 'foo')); // false - * console.log(utils.isType(node, 'bar')); // true - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @param {String} `type` - * @return {Boolean} - * @api public - */ - -utils.isType = function(node, type) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - switch (typeOf(type)) { - case 'array': - var types = type.slice(); - for (var i = 0; i < types.length; i++) { - if (utils.isType(node, types[i])) { - return true; - } - } - return false; - case 'string': - return node.type === type; - case 'regexp': - return type.test(node.type); - default: { - throw new TypeError('expected "type" to be an array, string or regexp'); - } - } -}; - -/** - * Returns true if the given `node` has the given `type` in `node.nodes`. - * Throws a `TypeError` if `node` is not an instance of `Node`. - * - * ```js - * var Node = require('snapdragon-node'); - * var node = new Node({ - * type: 'foo', - * nodes: [ - * new Node({type: 'bar'}), - * new Node({type: 'baz'}) - * ] - * }); - * console.log(utils.hasType(node, 'xyz')); // false - * console.log(utils.hasType(node, 'baz')); // true - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @param {String} `type` - * @return {Boolean} - * @api public - */ - -utils.hasType = function(node, type) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - if (!Array.isArray(node.nodes)) return false; - for (var i = 0; i < node.nodes.length; i++) { - if (utils.isType(node.nodes[i], type)) { - return true; - } - } - return false; -}; - -/** - * Returns the first node from `node.nodes` of the given `type` - * - * ```js - * var node = new Node({ - * type: 'foo', - * nodes: [ - * new Node({type: 'text', val: 'abc'}), - * new Node({type: 'text', val: 'xyz'}) - * ] - * }); - * - * var textNode = utils.firstOfType(node.nodes, 'text'); - * console.log(textNode.val); - * //=> 'abc' - * ``` - * @param {Array} `nodes` - * @param {String} `type` - * @return {Object|undefined} Returns the first matching node or undefined. - * @api public - */ - -utils.firstOfType = function(nodes, type) { - for (var i = 0; i < nodes.length; i++) { - var node = nodes[i]; - if (utils.isType(node, type)) { - return node; - } - } -}; - -/** - * Returns the node at the specified index, or the first node of the - * given `type` from `node.nodes`. - * - * ```js - * var node = new Node({ - * type: 'foo', - * nodes: [ - * new Node({type: 'text', val: 'abc'}), - * new Node({type: 'text', val: 'xyz'}) - * ] - * }); - * - * var nodeOne = utils.findNode(node.nodes, 'text'); - * console.log(nodeOne.val); - * //=> 'abc' - * - * var nodeTwo = utils.findNode(node.nodes, 1); - * console.log(nodeTwo.val); - * //=> 'xyz' - * ``` - * - * @param {Array} `nodes` - * @param {String|Number} `type` Node type or index. - * @return {Object} Returns a node or undefined. - * @api public - */ - -utils.findNode = function(nodes, type) { - if (!Array.isArray(nodes)) { - return null; - } - if (typeof type === 'number') { - return nodes[type]; - } - return utils.firstOfType(nodes, type); -}; - -/** - * Returns true if the given node is an "*.open" node. - * - * ```js - * var Node = require('snapdragon-node'); - * var brace = new Node({type: 'brace'}); - * var open = new Node({type: 'brace.open'}); - * var close = new Node({type: 'brace.close'}); - * - * console.log(utils.isOpen(brace)); // false - * console.log(utils.isOpen(open)); // true - * console.log(utils.isOpen(close)); // false - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @return {Boolean} - * @api public - */ - -utils.isOpen = function(node) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - return node.type.slice(-5) === '.open'; -}; - -/** - * Returns true if the given node is a "*.close" node. - * - * ```js - * var Node = require('snapdragon-node'); - * var brace = new Node({type: 'brace'}); - * var open = new Node({type: 'brace.open'}); - * var close = new Node({type: 'brace.close'}); - * - * console.log(utils.isClose(brace)); // false - * console.log(utils.isClose(open)); // false - * console.log(utils.isClose(close)); // true - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @return {Boolean} - * @api public - */ - -utils.isClose = function(node) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - return node.type.slice(-6) === '.close'; -}; - -/** - * Returns true if `node.nodes` **has** an `.open` node - * - * ```js - * var Node = require('snapdragon-node'); - * var brace = new Node({ - * type: 'brace', - * nodes: [] - * }); - * - * var open = new Node({type: 'brace.open'}); - * console.log(utils.hasOpen(brace)); // false - * - * brace.pushNode(open); - * console.log(utils.hasOpen(brace)); // true - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @return {Boolean} - * @api public - */ - -utils.hasOpen = function(node) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - var first = node.first || node.nodes ? node.nodes[0] : null; - if (utils.isNode(first)) { - return first.type === node.type + '.open'; - } - return false; -}; - -/** - * Returns true if `node.nodes` **has** a `.close` node - * - * ```js - * var Node = require('snapdragon-node'); - * var brace = new Node({ - * type: 'brace', - * nodes: [] - * }); - * - * var close = new Node({type: 'brace.close'}); - * console.log(utils.hasClose(brace)); // false - * - * brace.pushNode(close); - * console.log(utils.hasClose(brace)); // true - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @return {Boolean} - * @api public - */ - -utils.hasClose = function(node) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - var last = node.last || node.nodes ? node.nodes[node.nodes.length - 1] : null; - if (utils.isNode(last)) { - return last.type === node.type + '.close'; - } - return false; -}; - -/** - * Returns true if `node.nodes` has both `.open` and `.close` nodes - * - * ```js - * var Node = require('snapdragon-node'); - * var brace = new Node({ - * type: 'brace', - * nodes: [] - * }); - * - * var open = new Node({type: 'brace.open'}); - * var close = new Node({type: 'brace.close'}); - * console.log(utils.hasOpen(brace)); // false - * console.log(utils.hasClose(brace)); // false - * - * brace.pushNode(open); - * brace.pushNode(close); - * console.log(utils.hasOpen(brace)); // true - * console.log(utils.hasClose(brace)); // true - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @return {Boolean} - * @api public - */ - -utils.hasOpenAndClose = function(node) { - return utils.hasOpen(node) && utils.hasClose(node); -}; - -/** - * Push the given `node` onto the `state.inside` array for the - * given type. This array is used as a specialized "stack" for - * only the given `node.type`. - * - * ```js - * var state = { inside: {}}; - * var node = new Node({type: 'brace'}); - * utils.addType(state, node); - * console.log(state.inside); - * //=> { brace: [{type: 'brace'}] } - * ``` - * @param {Object} `state` The `compiler.state` object or custom state object. - * @param {Object} `node` Instance of [snapdragon-node][] - * @return {Array} Returns the `state.inside` stack for the given type. - * @api public - */ - -utils.addType = function(state, node) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - assert(isObject(state), 'expected state to be an object'); - - var type = node.parent - ? node.parent.type - : node.type.replace(/\.open$/, ''); - - if (!state.hasOwnProperty('inside')) { - state.inside = {}; - } - if (!state.inside.hasOwnProperty(type)) { - state.inside[type] = []; - } - - var arr = state.inside[type]; - arr.push(node); - return arr; -}; - -/** - * Remove the given `node` from the `state.inside` array for the - * given type. This array is used as a specialized "stack" for - * only the given `node.type`. - * - * ```js - * var state = { inside: {}}; - * var node = new Node({type: 'brace'}); - * utils.addType(state, node); - * console.log(state.inside); - * //=> { brace: [{type: 'brace'}] } - * utils.removeType(state, node); - * //=> { brace: [] } - * ``` - * @param {Object} `state` The `compiler.state` object or custom state object. - * @param {Object} `node` Instance of [snapdragon-node][] - * @return {Array} Returns the `state.inside` stack for the given type. - * @api public - */ - -utils.removeType = function(state, node) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - assert(isObject(state), 'expected state to be an object'); - - var type = node.parent - ? node.parent.type - : node.type.replace(/\.close$/, ''); - - if (state.inside.hasOwnProperty(type)) { - return state.inside[type].pop(); - } -}; - -/** - * Returns true if `node.val` is an empty string, or `node.nodes` does - * not contain any non-empty text nodes. - * - * ```js - * var node = new Node({type: 'text'}); - * utils.isEmpty(node); //=> true - * node.val = 'foo'; - * utils.isEmpty(node); //=> false - * ``` - * @param {Object} `node` Instance of [snapdragon-node][] - * @param {Function} `fn` - * @return {Boolean} - * @api public - */ - -utils.isEmpty = function(node, fn) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - - if (!Array.isArray(node.nodes)) { - if (node.type !== 'text') { - return true; - } - if (typeof fn === 'function') { - return fn(node, node.parent); - } - return !utils.trim(node.val); - } - - for (var i = 0; i < node.nodes.length; i++) { - var child = node.nodes[i]; - if (utils.isOpen(child) || utils.isClose(child)) { - continue; - } - if (!utils.isEmpty(child, fn)) { - return false; - } - } - - return true; -}; - -/** - * Returns true if the `state.inside` stack for the given type exists - * and has one or more nodes on it. - * - * ```js - * var state = { inside: {}}; - * var node = new Node({type: 'brace'}); - * console.log(utils.isInsideType(state, 'brace')); //=> false - * utils.addType(state, node); - * console.log(utils.isInsideType(state, 'brace')); //=> true - * utils.removeType(state, node); - * console.log(utils.isInsideType(state, 'brace')); //=> false - * ``` - * @param {Object} `state` - * @param {String} `type` - * @return {Boolean} - * @api public - */ - -utils.isInsideType = function(state, type) { - assert(isObject(state), 'expected state to be an object'); - assert(isString(type), 'expected type to be a string'); - - if (!state.hasOwnProperty('inside')) { - return false; - } - - if (!state.inside.hasOwnProperty(type)) { - return false; - } - - return state.inside[type].length > 0; -}; - -/** - * Returns true if `node` is either a child or grand-child of the given `type`, - * or `state.inside[type]` is a non-empty array. - * - * ```js - * var state = { inside: {}}; - * var node = new Node({type: 'brace'}); - * var open = new Node({type: 'brace.open'}); - * console.log(utils.isInside(state, open, 'brace')); //=> false - * utils.pushNode(node, open); - * console.log(utils.isInside(state, open, 'brace')); //=> true - * ``` - * @param {Object} `state` Either the `compiler.state` object, if it exists, or a user-supplied state object. - * @param {Object} `node` Instance of [snapdragon-node][] - * @param {String} `type` The `node.type` to check for. - * @return {Boolean} - * @api public - */ - -utils.isInside = function(state, node, type) { - assert(utils.isNode(node), 'expected node to be an instance of Node'); - assert(isObject(state), 'expected state to be an object'); - - if (Array.isArray(type)) { - for (var i = 0; i < type.length; i++) { - if (utils.isInside(state, node, type[i])) { - return true; - } - } - return false; - } - - var parent = node.parent; - if (typeof type === 'string') { - return (parent && parent.type === type) || utils.isInsideType(state, type); - } - - if (typeOf(type) === 'regexp') { - if (parent && parent.type && type.test(parent.type)) { - return true; - } - - var keys = Object.keys(state.inside); - var len = keys.length; - var idx = -1; - while (++idx < len) { - var key = keys[idx]; - var val = state.inside[key]; - - if (Array.isArray(val) && val.length !== 0 && type.test(key)) { - return true; - } - } - } - return false; -}; - -/** - * Get the last `n` element from the given `array`. Used for getting - * a node from `node.nodes.` - * - * @param {Array} `array` - * @param {Number} `n` - * @return {undefined} - * @api public - */ - -utils.last = function(arr, n) { - return arr[arr.length - (n || 1)]; -}; - -/** - * Cast the given `val` to an array. - * - * ```js - * console.log(utils.arrayify('')); - * //=> [] - * console.log(utils.arrayify('foo')); - * //=> ['foo'] - * console.log(utils.arrayify(['foo'])); - * //=> ['foo'] - * ``` - * @param {any} `val` - * @return {Array} - * @api public - */ - -utils.arrayify = function(val) { - if (typeof val === 'string' && val !== '') { - return [val]; - } - if (!Array.isArray(val)) { - return []; - } - return val; -}; - -/** - * Convert the given `val` to a string by joining with `,`. Useful - * for creating a cheerio/CSS/DOM-style selector from a list of strings. - * - * @param {any} `val` - * @return {Array} - * @api public - */ - -utils.stringify = function(val) { - return utils.arrayify(val).join(','); -}; - -/** - * Ensure that the given value is a string and call `.trim()` on it, - * or return an empty string. - * - * @param {String} `str` - * @return {String} - * @api public - */ - -utils.trim = function(str) { - return typeof str === 'string' ? str.trim() : ''; -}; - -/** - * Return true if val is an object - */ - -function isObject(val) { - return typeOf(val) === 'object'; -} - -/** - * Return true if val is a string - */ - -function isString(val) { - return typeof val === 'string'; -} - -/** - * Return true if val is a function - */ - -function isFunction(val) { - return typeof val === 'function'; -} - -/** - * Return true if val is an array - */ - -function isArray(val) { - return Array.isArray(val); -} - -/** - * Shim to ensure the `.append` methods work with any version of snapdragon - */ - -function append(compiler, val, node) { - if (typeof compiler.append !== 'function') { - return compiler.emit(val, node); - } - return compiler.append(val, node); -} - -/** - * Simplified assertion. Throws an error is `val` is falsey. - */ - -function assert(val, message) { - if (!val) throw new Error(message); -} - - -/***/ }), -/* 617 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var extend = __webpack_require__(601); -var Snapdragon = __webpack_require__(618); -var compilers = __webpack_require__(603); -var parsers = __webpack_require__(613); -var utils = __webpack_require__(604); - -/** - * Customize Snapdragon parser and renderer - */ - -function Braces(options) { - this.options = extend({}, options); -} - -/** - * Initialize braces - */ - -Braces.prototype.init = function(options) { - if (this.isInitialized) return; - this.isInitialized = true; - var opts = utils.createOptions({}, this.options, options); - this.snapdragon = this.options.snapdragon || new Snapdragon(opts); - this.compiler = this.snapdragon.compiler; - this.parser = this.snapdragon.parser; - - compilers(this.snapdragon, opts); - parsers(this.snapdragon, opts); - - /** - * Call Snapdragon `.parse` method. When AST is returned, we check to - * see if any unclosed braces are left on the stack and, if so, we iterate - * over the stack and correct the AST so that compilers are called in the correct - * order and unbalance braces are properly escaped. - */ - - utils.define(this.snapdragon, 'parse', function(pattern, options) { - var parsed = Snapdragon.prototype.parse.apply(this, arguments); - this.parser.ast.input = pattern; - - var stack = this.parser.stack; - while (stack.length) { - addParent({type: 'brace.close', val: ''}, stack.pop()); - } - - function addParent(node, parent) { - utils.define(node, 'parent', parent); - parent.nodes.push(node); - } - - // add non-enumerable parser reference - utils.define(parsed, 'parser', this.parser); - return parsed; - }); -}; - -/** - * Decorate `.parse` method - */ - -Braces.prototype.parse = function(ast, options) { - if (ast && typeof ast === 'object' && ast.nodes) return ast; - this.init(options); - return this.snapdragon.parse(ast, options); -}; - -/** - * Decorate `.compile` method - */ - -Braces.prototype.compile = function(ast, options) { - if (typeof ast === 'string') { - ast = this.parse(ast, options); - } else { - this.init(options); - } - return this.snapdragon.compile(ast, options); -}; - -/** - * Expand - */ - -Braces.prototype.expand = function(pattern) { - var ast = this.parse(pattern, {expand: true}); - return this.compile(ast, {expand: true}); -}; - -/** - * Optimize - */ - -Braces.prototype.optimize = function(pattern) { - var ast = this.parse(pattern, {optimize: true}); - return this.compile(ast, {optimize: true}); -}; - -/** - * Expose `Braces` - */ - -module.exports = Braces; - - -/***/ }), -/* 618 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var Base = __webpack_require__(619); -var define = __webpack_require__(645); -var Compiler = __webpack_require__(652); -var Parser = __webpack_require__(681); -var utils = __webpack_require__(660); -var regexCache = {}; -var cache = {}; - -/** - * Create a new instance of `Snapdragon` with the given `options`. - * - * ```js - * var snapdragon = new Snapdragon(); - * ``` - * - * @param {Object} `options` - * @api public - */ - -function Snapdragon(options) { - Base.call(this, null, options); - this.options = utils.extend({source: 'string'}, this.options); - this.compiler = new Compiler(this.options); - this.parser = new Parser(this.options); - - Object.defineProperty(this, 'compilers', { - get: function() { - return this.compiler.compilers; - } - }); - - Object.defineProperty(this, 'parsers', { - get: function() { - return this.parser.parsers; - } - }); - - Object.defineProperty(this, 'regex', { - get: function() { - return this.parser.regex; - } - }); -} - -/** - * Inherit Base - */ - -Base.extend(Snapdragon); - -/** - * Add a parser to `snapdragon.parsers` for capturing the given `type` using - * the specified regex or parser function. A function is useful if you need - * to customize how the token is created and/or have access to the parser - * instance to check options, etc. - * - * ```js - * snapdragon - * .capture('slash', /^\//) - * .capture('dot', function() { - * var pos = this.position(); - * var m = this.match(/^\./); - * if (!m) return; - * return pos({ - * type: 'dot', - * val: m[0] - * }); - * }); - * ``` - * @param {String} `type` - * @param {RegExp|Function} `regex` - * @return {Object} Returns the parser instance for chaining - * @api public - */ - -Snapdragon.prototype.capture = function() { - return this.parser.capture.apply(this.parser, arguments); -}; - -/** - * Register a plugin `fn`. - * - * ```js - * var snapdragon = new Snapdgragon([options]); - * snapdragon.use(function() { - * console.log(this); //<= snapdragon instance - * console.log(this.parser); //<= parser instance - * console.log(this.compiler); //<= compiler instance - * }); - * ``` - * @param {Object} `fn` - * @api public - */ - -Snapdragon.prototype.use = function(fn) { - fn.call(this, this); - return this; -}; - -/** - * Parse the given `str`. - * - * ```js - * var snapdragon = new Snapdgragon([options]); - * // register parsers - * snapdragon.parser.use(function() {}); - * - * // parse - * var ast = snapdragon.parse('foo/bar'); - * console.log(ast); - * ``` - * @param {String} `str` - * @param {Object} `options` Set `options.sourcemap` to true to enable source maps. - * @return {Object} Returns an AST. - * @api public - */ - -Snapdragon.prototype.parse = function(str, options) { - this.options = utils.extend({}, this.options, options); - var parsed = this.parser.parse(str, this.options); - - // add non-enumerable parser reference - define(parsed, 'parser', this.parser); - return parsed; -}; - -/** - * Compile the given `AST`. - * - * ```js - * var snapdragon = new Snapdgragon([options]); - * // register plugins - * snapdragon.use(function() {}); - * // register parser plugins - * snapdragon.parser.use(function() {}); - * // register compiler plugins - * snapdragon.compiler.use(function() {}); - * - * // parse - * var ast = snapdragon.parse('foo/bar'); - * - * // compile - * var res = snapdragon.compile(ast); - * console.log(res.output); - * ``` - * @param {Object} `ast` - * @param {Object} `options` - * @return {Object} Returns an object with an `output` property with the rendered string. - * @api public - */ - -Snapdragon.prototype.compile = function(ast, options) { - this.options = utils.extend({}, this.options, options); - var compiled = this.compiler.compile(ast, this.options); - - // add non-enumerable compiler reference - define(compiled, 'compiler', this.compiler); - return compiled; -}; - -/** - * Expose `Snapdragon` - */ - -module.exports = Snapdragon; - -/** - * Expose `Parser` and `Compiler` - */ - -module.exports.Compiler = Compiler; -module.exports.Parser = Parser; - - -/***/ }), -/* 619 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var util = __webpack_require__(112); -var define = __webpack_require__(620); -var CacheBase = __webpack_require__(621); -var Emitter = __webpack_require__(622); -var isObject = __webpack_require__(590); -var merge = __webpack_require__(640); -var pascal = __webpack_require__(643); -var cu = __webpack_require__(644); - -/** - * Optionally define a custom `cache` namespace to use. - */ - -function namespace(name) { - var Cache = name ? CacheBase.namespace(name) : CacheBase; - var fns = []; - - /** - * Create an instance of `Base` with the given `config` and `options`. - * - * ```js - * // initialize with `config` and `options` - * var app = new Base({isApp: true}, {abc: true}); - * app.set('foo', 'bar'); - * - * // values defined with the given `config` object will be on the root of the instance - * console.log(app.baz); //=> undefined - * console.log(app.foo); //=> 'bar' - * // or use `.get` - * console.log(app.get('isApp')); //=> true - * console.log(app.get('foo')); //=> 'bar' - * - * // values defined with the given `options` object will be on `app.options - * console.log(app.options.abc); //=> true - * ``` - * - * @param {Object} `config` If supplied, this object is passed to [cache-base][] to merge onto the the instance upon instantiation. - * @param {Object} `options` If supplied, this object is used to initialize the `base.options` object. - * @api public - */ - - function Base(config, options) { - if (!(this instanceof Base)) { - return new Base(config, options); - } - Cache.call(this, config); - this.is('base'); - this.initBase(config, options); - } - - /** - * Inherit cache-base - */ - - util.inherits(Base, Cache); - - /** - * Add static emitter methods - */ - - Emitter(Base); - - /** - * Initialize `Base` defaults with the given `config` object - */ - - Base.prototype.initBase = function(config, options) { - this.options = merge({}, this.options, options); - this.cache = this.cache || {}; - this.define('registered', {}); - if (name) this[name] = {}; - - // make `app._callbacks` non-enumerable - this.define('_callbacks', this._callbacks); - if (isObject(config)) { - this.visit('set', config); - } - Base.run(this, 'use', fns); - }; - - /** - * Set the given `name` on `app._name` and `app.is*` properties. Used for doing - * lookups in plugins. - * - * ```js - * app.is('foo'); - * console.log(app._name); - * //=> 'foo' - * console.log(app.isFoo); - * //=> true - * app.is('bar'); - * console.log(app.isFoo); - * //=> true - * console.log(app.isBar); - * //=> true - * console.log(app._name); - * //=> 'bar' - * ``` - * @name .is - * @param {String} `name` - * @return {Boolean} - * @api public - */ - - Base.prototype.is = function(name) { - if (typeof name !== 'string') { - throw new TypeError('expected name to be a string'); - } - this.define('is' + pascal(name), true); - this.define('_name', name); - this.define('_appname', name); - return this; - }; - - /** - * Returns true if a plugin has already been registered on an instance. - * - * Plugin implementors are encouraged to use this first thing in a plugin - * to prevent the plugin from being called more than once on the same - * instance. - * - * ```js - * var base = new Base(); - * base.use(function(app) { - * if (app.isRegistered('myPlugin')) return; - * // do stuff to `app` - * }); - * - * // to also record the plugin as being registered - * base.use(function(app) { - * if (app.isRegistered('myPlugin', true)) return; - * // do stuff to `app` - * }); - * ``` - * @name .isRegistered - * @emits `plugin` Emits the name of the plugin being registered. Useful for unit tests, to ensure plugins are only registered once. - * @param {String} `name` The plugin name. - * @param {Boolean} `register` If the plugin if not already registered, to record it as being registered pass `true` as the second argument. - * @return {Boolean} Returns true if a plugin is already registered. - * @api public - */ - - Base.prototype.isRegistered = function(name, register) { - if (this.registered.hasOwnProperty(name)) { - return true; - } - if (register !== false) { - this.registered[name] = true; - this.emit('plugin', name); - } - return false; - }; - - /** - * Define a plugin function to be called immediately upon init. Plugins are chainable - * and expose the following arguments to the plugin function: - * - * - `app`: the current instance of `Base` - * - `base`: the [first ancestor instance](#base) of `Base` - * - * ```js - * var app = new Base() - * .use(foo) - * .use(bar) - * .use(baz) - * ``` - * @name .use - * @param {Function} `fn` plugin function to call - * @return {Object} Returns the item instance for chaining. - * @api public - */ - - Base.prototype.use = function(fn) { - fn.call(this, this); - return this; - }; - - /** - * The `.define` method is used for adding non-enumerable property on the instance. - * Dot-notation is **not supported** with `define`. - * - * ```js - * // arbitrary `render` function using lodash `template` - * app.define('render', function(str, locals) { - * return _.template(str)(locals); - * }); - * ``` - * @name .define - * @param {String} `key` The name of the property to define. - * @param {any} `value` - * @return {Object} Returns the instance for chaining. - * @api public - */ - - Base.prototype.define = function(key, val) { - if (isObject(key)) { - return this.visit('define', key); - } - define(this, key, val); - return this; - }; - - /** - * Mix property `key` onto the Base prototype. If base is inherited using - * `Base.extend` this method will be overridden by a new `mixin` method that will - * only add properties to the prototype of the inheriting application. - * - * ```js - * app.mixin('foo', function() { - * // do stuff - * }); - * ``` - * @name .mixin - * @param {String} `key` - * @param {Object|Array} `val` - * @return {Object} Returns the `base` instance for chaining. - * @api public - */ - - Base.prototype.mixin = function(key, val) { - Base.prototype[key] = val; - return this; - }; - - /** - * Non-enumberable mixin array, used by the static [Base.mixin]() method. - */ - - Base.prototype.mixins = Base.prototype.mixins || []; - - /** - * Getter/setter used when creating nested instances of `Base`, for storing a reference - * to the first ancestor instance. This works by setting an instance of `Base` on the `parent` - * property of a "child" instance. The `base` property defaults to the current instance if - * no `parent` property is defined. - * - * ```js - * // create an instance of `Base`, this is our first ("base") instance - * var first = new Base(); - * first.foo = 'bar'; // arbitrary property, to make it easier to see what's happening later - * - * // create another instance - * var second = new Base(); - * // create a reference to the first instance (`first`) - * second.parent = first; - * - * // create another instance - * var third = new Base(); - * // create a reference to the previous instance (`second`) - * // repeat this pattern every time a "child" instance is created - * third.parent = second; - * - * // we can always access the first instance using the `base` property - * console.log(first.base.foo); - * //=> 'bar' - * console.log(second.base.foo); - * //=> 'bar' - * console.log(third.base.foo); - * //=> 'bar' - * // and now you know how to get to third base ;) - * ``` - * @name .base - * @api public - */ - - Object.defineProperty(Base.prototype, 'base', { - configurable: true, - get: function() { - return this.parent ? this.parent.base : this; - } - }); - - /** - * Static method for adding global plugin functions that will - * be added to an instance when created. - * - * ```js - * Base.use(function(app) { - * app.foo = 'bar'; - * }); - * var app = new Base(); - * console.log(app.foo); - * //=> 'bar' - * ``` - * @name #use - * @param {Function} `fn` Plugin function to use on each instance. - * @return {Object} Returns the `Base` constructor for chaining - * @api public - */ - - define(Base, 'use', function(fn) { - fns.push(fn); - return Base; - }); - - /** - * Run an array of functions by passing each function - * to a method on the given object specified by the given property. - * - * @param {Object} `obj` Object containing method to use. - * @param {String} `prop` Name of the method on the object to use. - * @param {Array} `arr` Array of functions to pass to the method. - */ - - define(Base, 'run', function(obj, prop, arr) { - var len = arr.length, i = 0; - while (len--) { - obj[prop](arr[i++]); - } - return Base; - }); - - /** - * Static method for inheriting the prototype and static methods of the `Base` class. - * This method greatly simplifies the process of creating inheritance-based applications. - * See [static-extend][] for more details. - * - * ```js - * var extend = cu.extend(Parent); - * Parent.extend(Child); - * - * // optional methods - * Parent.extend(Child, { - * foo: function() {}, - * bar: function() {} - * }); - * ``` - * @name #extend - * @param {Function} `Ctor` constructor to extend - * @param {Object} `methods` Optional prototype properties to mix in. - * @return {Object} Returns the `Base` constructor for chaining - * @api public - */ - - define(Base, 'extend', cu.extend(Base, function(Ctor, Parent) { - Ctor.prototype.mixins = Ctor.prototype.mixins || []; - - define(Ctor, 'mixin', function(fn) { - var mixin = fn(Ctor.prototype, Ctor); - if (typeof mixin === 'function') { - Ctor.prototype.mixins.push(mixin); - } - return Ctor; - }); - - define(Ctor, 'mixins', function(Child) { - Base.run(Child, 'mixin', Ctor.prototype.mixins); - return Ctor; - }); - - Ctor.prototype.mixin = function(key, value) { - Ctor.prototype[key] = value; - return this; - }; - return Base; - })); - - /** - * Used for adding methods to the `Base` prototype, and/or to the prototype of child instances. - * When a mixin function returns a function, the returned function is pushed onto the `.mixins` - * array, making it available to be used on inheriting classes whenever `Base.mixins()` is - * called (e.g. `Base.mixins(Child)`). - * - * ```js - * Base.mixin(function(proto) { - * proto.foo = function(msg) { - * return 'foo ' + msg; - * }; - * }); - * ``` - * @name #mixin - * @param {Function} `fn` Function to call - * @return {Object} Returns the `Base` constructor for chaining - * @api public - */ - - define(Base, 'mixin', function(fn) { - var mixin = fn(Base.prototype, Base); - if (typeof mixin === 'function') { - Base.prototype.mixins.push(mixin); - } - return Base; - }); - - /** - * Static method for running global mixin functions against a child constructor. - * Mixins must be registered before calling this method. - * - * ```js - * Base.extend(Child); - * Base.mixins(Child); - * ``` - * @name #mixins - * @param {Function} `Child` Constructor function of a child class - * @return {Object} Returns the `Base` constructor for chaining - * @api public - */ - - define(Base, 'mixins', function(Child) { - Base.run(Child, 'mixin', Base.prototype.mixins); - return Base; - }); - - /** - * Similar to `util.inherit`, but copies all static properties, prototype properties, and - * getters/setters from `Provider` to `Receiver`. See [class-utils][]{#inherit} for more details. - * - * ```js - * Base.inherit(Foo, Bar); - * ``` - * @name #inherit - * @param {Function} `Receiver` Receiving (child) constructor - * @param {Function} `Provider` Providing (parent) constructor - * @return {Object} Returns the `Base` constructor for chaining - * @api public - */ - - define(Base, 'inherit', cu.inherit); - define(Base, 'bubble', cu.bubble); - return Base; -} - -/** - * Expose `Base` with default settings - */ - -module.exports = namespace(); - -/** - * Allow users to define a namespace - */ - -module.exports.namespace = namespace; - - -/***/ }), -/* 620 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * define-property - * - * Copyright (c) 2015, 2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var isDescriptor = __webpack_require__(591); - -module.exports = function defineProperty(obj, prop, val) { - if (typeof obj !== 'object' && typeof obj !== 'function') { - throw new TypeError('expected an object or function.'); - } - - if (typeof prop !== 'string') { - throw new TypeError('expected `prop` to be a string.'); - } - - if (isDescriptor(val) && ('set' in val || 'get' in val)) { - return Object.defineProperty(obj, prop, val); - } - - return Object.defineProperty(obj, prop, { - configurable: true, - enumerable: false, - writable: true, - value: val - }); -}; - - -/***/ }), -/* 621 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var isObject = __webpack_require__(590); -var Emitter = __webpack_require__(622); -var visit = __webpack_require__(623); -var toPath = __webpack_require__(626); -var union = __webpack_require__(627); -var del = __webpack_require__(632); -var get = __webpack_require__(629); -var has = __webpack_require__(638); -var set = __webpack_require__(630); - -/** - * Create a `Cache` constructor that when instantiated will - * store values on the given `prop`. - * - * ```js - * var Cache = require('cache-base').namespace('data'); - * var cache = new Cache(); - * - * cache.set('foo', 'bar'); - * //=> {data: {foo: 'bar'}} - * ``` - * @param {String} `prop` The property name to use for storing values. - * @return {Function} Returns a custom `Cache` constructor - * @api public - */ - -function namespace(prop) { - - /** - * Create a new `Cache`. Internally the `Cache` constructor is created using - * the `namespace` function, with `cache` defined as the storage object. - * - * ```js - * var app = new Cache(); - * ``` - * @param {Object} `cache` Optionally pass an object to initialize with. - * @constructor - * @api public - */ - - function Cache(cache) { - if (prop) { - this[prop] = {}; - } - if (cache) { - this.set(cache); - } - } - - /** - * Inherit Emitter - */ - - Emitter(Cache.prototype); - - /** - * Assign `value` to `key`. Also emits `set` with - * the key and value. - * - * ```js - * app.on('set', function(key, val) { - * // do something when `set` is emitted - * }); - * - * app.set(key, value); - * - * // also takes an object or array - * app.set({name: 'Halle'}); - * app.set([{foo: 'bar'}, {baz: 'quux'}]); - * console.log(app); - * //=> {name: 'Halle', foo: 'bar', baz: 'quux'} - * ``` - * - * @name .set - * @emits `set` with `key` and `value` as arguments. - * @param {String} `key` - * @param {any} `value` - * @return {Object} Returns the instance for chaining. - * @api public - */ - - Cache.prototype.set = function(key, val) { - if (Array.isArray(key) && arguments.length === 2) { - key = toPath(key); - } - if (isObject(key) || Array.isArray(key)) { - this.visit('set', key); - } else { - set(prop ? this[prop] : this, key, val); - this.emit('set', key, val); - } - return this; - }; - - /** - * Union `array` to `key`. Also emits `set` with - * the key and value. - * - * ```js - * app.union('a.b', ['foo']); - * app.union('a.b', ['bar']); - * console.log(app.get('a')); - * //=> {b: ['foo', 'bar']} - * ``` - * @name .union - * @param {String} `key` - * @param {any} `value` - * @return {Object} Returns the instance for chaining. - * @api public - */ - - Cache.prototype.union = function(key, val) { - if (Array.isArray(key) && arguments.length === 2) { - key = toPath(key); - } - var ctx = prop ? this[prop] : this; - union(ctx, key, arrayify(val)); - this.emit('union', val); - return this; - }; - - /** - * Return the value of `key`. Dot notation may be used - * to get [nested property values][get-value]. - * - * ```js - * app.set('a.b.c', 'd'); - * app.get('a.b'); - * //=> {c: 'd'} - * - * app.get(['a', 'b']); - * //=> {c: 'd'} - * ``` - * - * @name .get - * @emits `get` with `key` and `value` as arguments. - * @param {String} `key` The name of the property to get. Dot-notation may be used. - * @return {any} Returns the value of `key` - * @api public - */ - - Cache.prototype.get = function(key) { - key = toPath(arguments); - - var ctx = prop ? this[prop] : this; - var val = get(ctx, key); - - this.emit('get', key, val); - return val; - }; - - /** - * Return true if app has a stored value for `key`, - * false only if value is `undefined`. - * - * ```js - * app.set('foo', 'bar'); - * app.has('foo'); - * //=> true - * ``` - * - * @name .has - * @emits `has` with `key` and true or false as arguments. - * @param {String} `key` - * @return {Boolean} - * @api public - */ - - Cache.prototype.has = function(key) { - key = toPath(arguments); - - var ctx = prop ? this[prop] : this; - var val = get(ctx, key); - - var has = typeof val !== 'undefined'; - this.emit('has', key, has); - return has; - }; - - /** - * Delete one or more properties from the instance. - * - * ```js - * app.del(); // delete all - * // or - * app.del('foo'); - * // or - * app.del(['foo', 'bar']); - * ``` - * @name .del - * @emits `del` with the `key` as the only argument. - * @param {String|Array} `key` Property name or array of property names. - * @return {Object} Returns the instance for chaining. - * @api public - */ - - Cache.prototype.del = function(key) { - if (Array.isArray(key)) { - this.visit('del', key); - } else { - del(prop ? this[prop] : this, key); - this.emit('del', key); - } - return this; - }; - - /** - * Reset the entire cache to an empty object. - * - * ```js - * app.clear(); - * ``` - * @api public - */ - - Cache.prototype.clear = function() { - if (prop) { - this[prop] = {}; - } - }; - - /** - * Visit `method` over the properties in the given object, or map - * visit over the object-elements in an array. - * - * @name .visit - * @param {String} `method` The name of the `base` method to call. - * @param {Object|Array} `val` The object or array to iterate over. - * @return {Object} Returns the instance for chaining. - * @api public - */ - - Cache.prototype.visit = function(method, val) { - visit(this, method, val); - return this; - }; - - return Cache; -} - -/** - * Cast val to an array - */ - -function arrayify(val) { - return val ? (Array.isArray(val) ? val : [val]) : []; -} - -/** - * Expose `Cache` - */ - -module.exports = namespace(); - -/** - * Expose `Cache.namespace` - */ - -module.exports.namespace = namespace; - - -/***/ }), -/* 622 */ -/***/ (function(module, exports, __webpack_require__) { - - -/** - * Expose `Emitter`. - */ - -if (true) { - module.exports = Emitter; -} - -/** - * Initialize a new `Emitter`. - * - * @api public - */ - -function Emitter(obj) { - if (obj) return mixin(obj); -}; - -/** - * Mixin the emitter properties. - * - * @param {Object} obj - * @return {Object} - * @api private - */ - -function mixin(obj) { - for (var key in Emitter.prototype) { - obj[key] = Emitter.prototype[key]; - } - return obj; -} - -/** - * Listen on the given `event` with `fn`. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - -Emitter.prototype.on = -Emitter.prototype.addEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - (this._callbacks['$' + event] = this._callbacks['$' + event] || []) - .push(fn); - return this; -}; - -/** - * Adds an `event` listener that will be invoked a single - * time then automatically removed. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - -Emitter.prototype.once = function(event, fn){ - function on() { - this.off(event, on); - fn.apply(this, arguments); - } - - on.fn = fn; - this.on(event, on); - return this; -}; - -/** - * Remove the given callback for `event` or all - * registered callbacks. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - -Emitter.prototype.off = -Emitter.prototype.removeListener = -Emitter.prototype.removeAllListeners = -Emitter.prototype.removeEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - - // all - if (0 == arguments.length) { - this._callbacks = {}; - return this; - } - - // specific event - var callbacks = this._callbacks['$' + event]; - if (!callbacks) return this; - - // remove all handlers - if (1 == arguments.length) { - delete this._callbacks['$' + event]; - return this; - } - - // remove specific handler - var cb; - for (var i = 0; i < callbacks.length; i++) { - cb = callbacks[i]; - if (cb === fn || cb.fn === fn) { - callbacks.splice(i, 1); - break; - } - } - - // Remove event specific arrays for event types that no - // one is subscribed for to avoid memory leak. - if (callbacks.length === 0) { - delete this._callbacks['$' + event]; - } - - return this; -}; - -/** - * Emit `event` with the given args. - * - * @param {String} event - * @param {Mixed} ... - * @return {Emitter} - */ - -Emitter.prototype.emit = function(event){ - this._callbacks = this._callbacks || {}; - - var args = new Array(arguments.length - 1) - , callbacks = this._callbacks['$' + event]; - - for (var i = 1; i < arguments.length; i++) { - args[i - 1] = arguments[i]; - } - - if (callbacks) { - callbacks = callbacks.slice(0); - for (var i = 0, len = callbacks.length; i < len; ++i) { - callbacks[i].apply(this, args); - } - } - - return this; -}; - -/** - * Return array of callbacks for `event`. - * - * @param {String} event - * @return {Array} - * @api public - */ - -Emitter.prototype.listeners = function(event){ - this._callbacks = this._callbacks || {}; - return this._callbacks['$' + event] || []; -}; - -/** - * Check if this emitter has `event` handlers. - * - * @param {String} event - * @return {Boolean} - * @api public - */ - -Emitter.prototype.hasListeners = function(event){ - return !! this.listeners(event).length; -}; - - -/***/ }), -/* 623 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * collection-visit - * - * Copyright (c) 2015, 2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var visit = __webpack_require__(624); -var mapVisit = __webpack_require__(625); - -module.exports = function(collection, method, val) { - var result; - - if (typeof val === 'string' && (method in collection)) { - var args = [].slice.call(arguments, 2); - result = collection[method].apply(collection, args); - } else if (Array.isArray(val)) { - result = mapVisit.apply(null, arguments); - } else { - result = visit.apply(null, arguments); - } - - if (typeof result !== 'undefined') { - return result; - } - - return collection; -}; - - -/***/ }), -/* 624 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * object-visit - * - * Copyright (c) 2015, 2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var isObject = __webpack_require__(590); - -module.exports = function visit(thisArg, method, target, val) { - if (!isObject(thisArg) && typeof thisArg !== 'function') { - throw new Error('object-visit expects `thisArg` to be an object.'); - } - - if (typeof method !== 'string') { - throw new Error('object-visit expects `method` name to be a string'); - } - - if (typeof thisArg[method] !== 'function') { - return thisArg; - } - - var args = [].slice.call(arguments, 3); - target = target || {}; - - for (var key in target) { - var arr = [key, target[key]].concat(args); - thisArg[method].apply(thisArg, arr); - } - return thisArg; -}; - - -/***/ }), -/* 625 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var util = __webpack_require__(112); -var visit = __webpack_require__(624); - -/** - * Map `visit` over an array of objects. - * - * @param {Object} `collection` The context in which to invoke `method` - * @param {String} `method` Name of the method to call on `collection` - * @param {Object} `arr` Array of objects. - */ - -module.exports = function mapVisit(collection, method, val) { - if (isObject(val)) { - return visit.apply(null, arguments); - } - - if (!Array.isArray(val)) { - throw new TypeError('expected an array: ' + util.inspect(val)); - } - - var args = [].slice.call(arguments, 3); - - for (var i = 0; i < val.length; i++) { - var ele = val[i]; - if (isObject(ele)) { - visit.apply(null, [collection, method, ele].concat(args)); - } else { - collection[method].apply(collection, [ele].concat(args)); - } - } -}; - -function isObject(val) { - return val && (typeof val === 'function' || (!Array.isArray(val) && typeof val === 'object')); -} - - -/***/ }), -/* 626 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * to-object-path - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -var typeOf = __webpack_require__(592); - -module.exports = function toPath(args) { - if (typeOf(args) !== 'arguments') { - args = arguments; - } - return filter(args).join('.'); -}; - -function filter(arr) { - var len = arr.length; - var idx = -1; - var res = []; - - while (++idx < len) { - var ele = arr[idx]; - if (typeOf(ele) === 'arguments' || Array.isArray(ele)) { - res.push.apply(res, filter(ele)); - } else if (typeof ele === 'string') { - res.push(ele); - } - } - return res; -} - - -/***/ }), -/* 627 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var isObject = __webpack_require__(602); -var union = __webpack_require__(628); -var get = __webpack_require__(629); -var set = __webpack_require__(630); - -module.exports = function unionValue(obj, prop, value) { - if (!isObject(obj)) { - throw new TypeError('union-value expects the first argument to be an object.'); - } - - if (typeof prop !== 'string') { - throw new TypeError('union-value expects `prop` to be a string.'); - } - - var arr = arrayify(get(obj, prop)); - set(obj, prop, union(arr, arrayify(value))); - return obj; -}; - -function arrayify(val) { - if (val === null || typeof val === 'undefined') { - return []; - } - if (Array.isArray(val)) { - return val; - } - return [val]; -} - - -/***/ }), -/* 628 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -module.exports = function union(init) { - if (!Array.isArray(init)) { - throw new TypeError('arr-union expects the first argument to be an array.'); - } - - var len = arguments.length; - var i = 0; - - while (++i < len) { - var arg = arguments[i]; - if (!arg) continue; - - if (!Array.isArray(arg)) { - arg = [arg]; - } - - for (var j = 0; j < arg.length; j++) { - var ele = arg[j]; - - if (init.indexOf(ele) >= 0) { - continue; - } - init.push(ele); - } - } - return init; -}; - - -/***/ }), -/* 629 */ -/***/ (function(module, exports) { - -/*! - * get-value - * - * Copyright (c) 2014-2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -module.exports = function(obj, prop, a, b, c) { - if (!isObject(obj) || !prop) { - return obj; - } - - prop = toString(prop); - - // allowing for multiple properties to be passed as - // a string or array, but much faster (3-4x) than doing - // `[].slice.call(arguments)` - if (a) prop += '.' + toString(a); - if (b) prop += '.' + toString(b); - if (c) prop += '.' + toString(c); - - if (prop in obj) { - return obj[prop]; - } - - var segs = prop.split('.'); - var len = segs.length; - var i = -1; - - while (obj && (++i < len)) { - var key = segs[i]; - while (key[key.length - 1] === '\\') { - key = key.slice(0, -1) + '.' + segs[++i]; - } - obj = obj[key]; - } - return obj; -}; - -function isObject(val) { - return val !== null && (typeof val === 'object' || typeof val === 'function'); -} - -function toString(val) { - if (!val) return ''; - if (Array.isArray(val)) { - return val.join('.'); - } - return val; -} - - -/***/ }), -/* 630 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * set-value - * - * Copyright (c) Jon Schlinkert (https://github.com/jonschlinkert). - * Released under the MIT License. - */ - - - -const { deleteProperty } = Reflect; -const isPrimitive = __webpack_require__(631); -const isPlainObject = __webpack_require__(597); - -const isObject = value => { - return (typeof value === 'object' && value !== null) || typeof value === 'function'; -}; - -const isUnsafeKey = key => { - return key === '__proto__' || key === 'constructor' || key === 'prototype'; -}; - -const validateKey = key => { - if (!isPrimitive(key)) { - throw new TypeError('Object keys must be strings or symbols'); - } - - if (isUnsafeKey(key)) { - throw new Error(`Cannot set unsafe key: "${key}"`); - } -}; - -const toStringKey = input => { - return Array.isArray(input) ? input.flat().map(String).join(',') : input; -}; - -const createMemoKey = (input, options) => { - if (typeof input !== 'string' || !options) return input; - let key = input + ';'; - if (options.arrays !== undefined) key += `arrays=${options.arrays};`; - if (options.separator !== undefined) key += `separator=${options.separator};`; - if (options.split !== undefined) key += `split=${options.split};`; - if (options.merge !== undefined) key += `merge=${options.merge};`; - if (options.preservePaths !== undefined) key += `preservePaths=${options.preservePaths};`; - return key; -}; - -const memoize = (input, options, fn) => { - const key = toStringKey(options ? createMemoKey(input, options) : input); - validateKey(key); - - const value = setValue.cache.get(key) || fn(); - setValue.cache.set(key, value); - return value; -}; - -const splitString = (input, options = {}) => { - const sep = options.separator || '.'; - const preserve = sep === '/' ? false : options.preservePaths; - - if (typeof input === 'string' && preserve !== false && /\//.test(input)) { - return [input]; - } - - const parts = []; - let part = ''; - - const push = part => { - let number; - if (part.trim() !== '' && Number.isInteger((number = Number(part)))) { - parts.push(number); - } else { - parts.push(part); - } - }; - - for (let i = 0; i < input.length; i++) { - const value = input[i]; - - if (value === '\\') { - part += input[++i]; - continue; - } - - if (value === sep) { - push(part); - part = ''; - continue; - } - - part += value; - } - - if (part) { - push(part); - } - - return parts; -}; - -const split = (input, options) => { - if (options && typeof options.split === 'function') return options.split(input); - if (typeof input === 'symbol') return [input]; - if (Array.isArray(input)) return input; - return memoize(input, options, () => splitString(input, options)); -}; - -const assignProp = (obj, prop, value, options) => { - validateKey(prop); - - // Delete property when "value" is undefined - if (value === undefined) { - deleteProperty(obj, prop); - - } else if (options && options.merge) { - const merge = options.merge === 'function' ? options.merge : Object.assign; - - // Only merge plain objects - if (merge && isPlainObject(obj[prop]) && isPlainObject(value)) { - obj[prop] = merge(obj[prop], value); - } else { - obj[prop] = value; - } - - } else { - obj[prop] = value; - } - - return obj; -}; - -const setValue = (target, path, value, options) => { - if (!path || !isObject(target)) return target; - - const keys = split(path, options); - let obj = target; - - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - const next = keys[i + 1]; - - validateKey(key); - - if (next === undefined) { - assignProp(obj, key, value, options); - break; - } - - if (typeof next === 'number' && !Array.isArray(obj[key])) { - obj = obj[key] = []; - continue; - } - - if (!isObject(obj[key])) { - obj[key] = {}; - } - - obj = obj[key]; - } - - return target; -}; - -setValue.split = split; -setValue.cache = new Map(); -setValue.clear = () => { - setValue.cache = new Map(); -}; - -module.exports = setValue; - - -/***/ }), -/* 631 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * is-primitive - * - * Copyright (c) 2014-present, Jon Schlinkert. - * Released under the MIT License. - */ - - - -module.exports = function isPrimitive(val) { - if (typeof val === 'object') { - return val === null; - } - return typeof val !== 'function'; -}; - - -/***/ }), -/* 632 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * unset-value - * - * Copyright (c) 2015, 2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var isObject = __webpack_require__(633); -var has = __webpack_require__(634); - -const isUnsafeKey = key => { - return key === '__proto__' || key === 'constructor' || key === 'prototype'; -}; - -const validateKey = key => { - if (isUnsafeKey(key)) { - throw new Error(`Cannot set unsafe key: "${key}"`); - } -}; - -module.exports = function unset(obj, prop) { - if (!isObject(obj)) { - throw new TypeError('expected an object.'); - } - - var isArray = Array.isArray(prop); - - if (!isArray && obj.hasOwnProperty(prop)) { - delete obj[prop]; - return true; - } - - if (has(obj, prop)) { - var segs = isArray ? prop.slice() : prop.split('.'); - var last = segs.pop(); - while (segs.length && segs[segs.length - 1].slice(-1) === '\\') { - last = segs.pop().slice(0, -1) + '.' + last; - } - while (segs.length) { - prop = segs.shift(); - validateKey(prop); - obj = obj[prop]; - } - return (delete obj[last]); - } - return true; -}; - - -/***/ }), -/* 633 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "default", function() { return isObject; }); -/*! - * isobject - * - * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. - */ - -function isObject(val) { - return val != null && typeof val === 'object' && Array.isArray(val) === false; -}; - - -/***/ }), -/* 634 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * has-value - * - * Copyright (c) 2014-2018, Jon Schlinkert. - * Released under the MIT License. - */ - - - -const get = __webpack_require__(635); -const has = __webpack_require__(637); - -module.exports = function(obj, path, options) { - if (isObject(obj) && (typeof path === 'string' || Array.isArray(path))) { - return has(get(obj, path, options)); - } - return false; -}; - -function isObject(val) { - return val != null && (typeof val === 'object' || typeof val === 'function' || Array.isArray(val)); -} - - -/***/ }), -/* 635 */ -/***/ (function(module, exports, __webpack_require__) { - -/*! - * get-value - * - * Copyright (c) 2014-2018, Jon Schlinkert. - * Released under the MIT License. - */ - -const isObject = __webpack_require__(636); - -module.exports = function(target, path, options) { - if (!isObject(options)) { - options = { default: options }; - } - - if (!isValidObject(target)) { - return typeof options.default !== 'undefined' ? options.default : target; - } - - if (typeof path === 'number') { - path = String(path); - } - - const isArray = Array.isArray(path); - const isString = typeof path === 'string'; - const splitChar = options.separator || '.'; - const joinChar = options.joinChar || (typeof splitChar === 'string' ? splitChar : '.'); - - if (!isString && !isArray) { - return target; - } - - if (isString && path in target) { - return isValid(path, target, options) ? target[path] : options.default; - } - - let segs = isArray ? path : split(path, splitChar, options); - let len = segs.length; - let idx = 0; - - do { - let prop = segs[idx]; - if (typeof prop === 'number') { - prop = String(prop); - } - - while (prop && prop.slice(-1) === '\\') { - prop = join([prop.slice(0, -1), segs[++idx] || ''], joinChar, options); - } - - if (prop in target) { - if (!isValid(prop, target, options)) { - return options.default; - } - - target = target[prop]; - } else { - let hasProp = false; - let n = idx + 1; - - while (n < len) { - prop = join([prop, segs[n++]], joinChar, options); - - if ((hasProp = prop in target)) { - if (!isValid(prop, target, options)) { - return options.default; - } - - target = target[prop]; - idx = n - 1; - break; - } - } - - if (!hasProp) { - return options.default; - } - } - } while (++idx < len && isValidObject(target)); - - if (idx === len) { - return target; - } - - return options.default; -}; - -function join(segs, joinChar, options) { - if (typeof options.join === 'function') { - return options.join(segs); - } - return segs[0] + joinChar + segs[1]; -} - -function split(path, splitChar, options) { - if (typeof options.split === 'function') { - return options.split(path); - } - return path.split(splitChar); -} - -function isValid(key, target, options) { - if (typeof options.isValid === 'function') { - return options.isValid(key, target); - } - return true; -} - -function isValidObject(val) { - return isObject(val) || Array.isArray(val) || typeof val === 'function'; -} - - -/***/ }), -/* 636 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * isobject - * - * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -module.exports = function isObject(val) { - return val != null && typeof val === 'object' && Array.isArray(val) === false; -}; - - -/***/ }), -/* 637 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * has-values - * - * Copyright (c) 2014-2018, Jon Schlinkert. - * Released under the MIT License. - */ - - - -const typeOf = __webpack_require__(592); - -module.exports = function has(val) { - switch (typeOf(val)) { - case 'boolean': - case 'date': - case 'function': - case 'null': - case 'number': - return true; - case 'undefined': - return false; - case 'regexp': - return val.source !== '(?:)' && val.source !== ''; - case 'buffer': - return val.toString() !== ''; - case 'error': - return val.message !== ''; - case 'string': - case 'arguments': - return val.length !== 0; - case 'file': - case 'map': - case 'set': - return val.size !== 0; - case 'array': - case 'object': - for (const key of Object.keys(val)) { - if (has(val[key])) { - return true; - } - } - return false; - - // everything else - default: { - return true; - } - } -}; - - -/***/ }), -/* 638 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * has-value - * - * Copyright (c) 2014-2017, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -var isObject = __webpack_require__(590); -var hasValues = __webpack_require__(639); -var get = __webpack_require__(629); - -module.exports = function(val, prop) { - return hasValues(isObject(val) && prop ? get(val, prop) : val); -}; - - -/***/ }), -/* 639 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * has-values - * - * Copyright (c) 2014-2015, 2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var typeOf = __webpack_require__(592); -var isNumber = __webpack_require__(608); - -module.exports = function hasValue(val) { - // is-number checks for NaN and other edge cases - if (isNumber(val)) { - return true; - } - - switch (typeOf(val)) { - case 'null': - case 'boolean': - case 'function': - return true; - case 'string': - case 'arguments': - return val.length !== 0; - case 'error': - return val.message !== ''; - case 'array': - var len = val.length; - if (len === 0) { - return false; - } - for (var i = 0; i < len; i++) { - if (hasValue(val[i])) { - return true; - } - } - return false; - case 'file': - case 'map': - case 'set': - return val.size !== 0; - case 'object': - var keys = Object.keys(val); - if (keys.length === 0) { - return false; - } - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - if (hasValue(val[key])) { - return true; - } - } - return false; - default: { - return false; - } - } -}; - - -/***/ }), -/* 640 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var isExtendable = __webpack_require__(641); -var forIn = __webpack_require__(642); - -function mixinDeep(target, objects) { - var len = arguments.length, i = 0; - while (++i < len) { - var obj = arguments[i]; - if (isObject(obj)) { - forIn(obj, copy, target); - } - } - return target; -} - -/** - * Copy properties from the source object to the - * target object. - * - * @param {*} `val` - * @param {String} `key` - */ - -function copy(val, key) { - if (!isValidKey(key)) { - return; - } - - var obj = this[key]; - if (isObject(val) && isObject(obj)) { - mixinDeep(obj, val); - } else { - this[key] = val; - } -} - -/** - * Returns true if `val` is an object or function. - * - * @param {any} val - * @return {Boolean} - */ - -function isObject(val) { - return isExtendable(val) && !Array.isArray(val); -} - -/** - * Returns true if `key` is a valid key to use when extending objects. - * - * @param {String} `key` - * @return {Boolean} - */ - -function isValidKey(key) { - return key !== '__proto__' && key !== 'constructor' && key !== 'prototype'; -}; - -/** - * Expose `mixinDeep` - */ - -module.exports = mixinDeep; - - -/***/ }), -/* 641 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * is-extendable - * - * Copyright (c) 2015-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var isPlainObject = __webpack_require__(597); - -module.exports = function isExtendable(val) { - return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); -}; - - -/***/ }), -/* 642 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * for-in - * - * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -module.exports = function forIn(obj, fn, thisArg) { - for (var key in obj) { - if (fn.call(thisArg, obj[key], key, obj) === false) { - break; - } - } -}; - - -/***/ }), -/* 643 */ -/***/ (function(module, exports) { - -/*! - * pascalcase - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - -function pascalcase(str) { - if (typeof str !== 'string') { - throw new TypeError('expected a string.'); - } - str = str.replace(/([A-Z])/g, ' $1'); - if (str.length === 1) { return str.toUpperCase(); } - str = str.replace(/^[\W_]+|[\W_]+$/g, '').toLowerCase(); - str = str.charAt(0).toUpperCase() + str.slice(1); - return str.replace(/[\W_]+(\w|$)/g, function (_, ch) { - return ch.toUpperCase(); - }); -} - -module.exports = pascalcase; - - -/***/ }), -/* 644 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var util = __webpack_require__(112); -var union = __webpack_require__(628); -var define = __webpack_require__(645); -var staticExtend = __webpack_require__(649); -var isObj = __webpack_require__(590); - -/** - * Expose class utils - */ - -var cu = module.exports; - -/** - * Expose class utils: `cu` - */ - -cu.isObject = function isObject(val) { - return isObj(val) || typeof val === 'function'; -}; - -/** - * Returns true if an array has any of the given elements, or an - * object has any of the give keys. - * - * ```js - * cu.has(['a', 'b', 'c'], 'c'); - * //=> true - * - * cu.has(['a', 'b', 'c'], ['c', 'z']); - * //=> true - * - * cu.has({a: 'b', c: 'd'}, ['c', 'z']); - * //=> true - * ``` - * @param {Object} `obj` - * @param {String|Array} `val` - * @return {Boolean} - * @api public - */ - -cu.has = function has(obj, val) { - val = cu.arrayify(val); - var len = val.length; - - if (cu.isObject(obj)) { - for (var key in obj) { - if (val.indexOf(key) > -1) { - return true; - } - } - - var keys = cu.nativeKeys(obj); - return cu.has(keys, val); - } - - if (Array.isArray(obj)) { - var arr = obj; - while (len--) { - if (arr.indexOf(val[len]) > -1) { - return true; - } - } - return false; - } - - throw new TypeError('expected an array or object.'); -}; - -/** - * Returns true if an array or object has all of the given values. - * - * ```js - * cu.hasAll(['a', 'b', 'c'], 'c'); - * //=> true - * - * cu.hasAll(['a', 'b', 'c'], ['c', 'z']); - * //=> false - * - * cu.hasAll({a: 'b', c: 'd'}, ['c', 'z']); - * //=> false - * ``` - * @param {Object|Array} `val` - * @param {String|Array} `values` - * @return {Boolean} - * @api public - */ - -cu.hasAll = function hasAll(val, values) { - values = cu.arrayify(values); - var len = values.length; - while (len--) { - if (!cu.has(val, values[len])) { - return false; - } - } - return true; -}; - -/** - * Cast the given value to an array. - * - * ```js - * cu.arrayify('foo'); - * //=> ['foo'] - * - * cu.arrayify(['foo']); - * //=> ['foo'] - * ``` - * - * @param {String|Array} `val` - * @return {Array} - * @api public - */ - -cu.arrayify = function arrayify(val) { - return val ? (Array.isArray(val) ? val : [val]) : []; -}; - -/** - * Noop - */ - -cu.noop = function noop() { - return; -}; - -/** - * Returns the first argument passed to the function. - */ - -cu.identity = function identity(val) { - return val; -}; - -/** - * Returns true if a value has a `contructor` - * - * ```js - * cu.hasConstructor({}); - * //=> true - * - * cu.hasConstructor(Object.create(null)); - * //=> false - * ``` - * @param {Object} `value` - * @return {Boolean} - * @api public - */ - -cu.hasConstructor = function hasConstructor(val) { - return cu.isObject(val) && typeof val.constructor !== 'undefined'; -}; - -/** - * Get the native `ownPropertyNames` from the constructor of the - * given `object`. An empty array is returned if the object does - * not have a constructor. - * - * ```js - * cu.nativeKeys({a: 'b', b: 'c', c: 'd'}) - * //=> ['a', 'b', 'c'] - * - * cu.nativeKeys(function(){}) - * //=> ['length', 'caller'] - * ``` - * - * @param {Object} `obj` Object that has a `constructor`. - * @return {Array} Array of keys. - * @api public - */ - -cu.nativeKeys = function nativeKeys(val) { - if (!cu.hasConstructor(val)) return []; - var keys = Object.getOwnPropertyNames(val); - if ('caller' in val) keys.push('caller'); - return keys; -}; - -/** - * Returns property descriptor `key` if it's an "own" property - * of the given object. - * - * ```js - * function App() {} - * Object.defineProperty(App.prototype, 'count', { - * get: function() { - * return Object.keys(this).length; - * } - * }); - * cu.getDescriptor(App.prototype, 'count'); - * // returns: - * // { - * // get: [Function], - * // set: undefined, - * // enumerable: false, - * // configurable: false - * // } - * ``` - * - * @param {Object} `obj` - * @param {String} `key` - * @return {Object} Returns descriptor `key` - * @api public - */ - -cu.getDescriptor = function getDescriptor(obj, key) { - if (!cu.isObject(obj)) { - throw new TypeError('expected an object.'); - } - if (typeof key !== 'string') { - throw new TypeError('expected key to be a string.'); - } - return Object.getOwnPropertyDescriptor(obj, key); -}; - -/** - * Copy a descriptor from one object to another. - * - * ```js - * function App() {} - * Object.defineProperty(App.prototype, 'count', { - * get: function() { - * return Object.keys(this).length; - * } - * }); - * var obj = {}; - * cu.copyDescriptor(obj, App.prototype, 'count'); - * ``` - * @param {Object} `receiver` - * @param {Object} `provider` - * @param {String} `name` - * @return {Object} - * @api public - */ - -cu.copyDescriptor = function copyDescriptor(receiver, provider, name) { - if (!cu.isObject(receiver)) { - throw new TypeError('expected receiving object to be an object.'); - } - if (!cu.isObject(provider)) { - throw new TypeError('expected providing object to be an object.'); - } - if (typeof name !== 'string') { - throw new TypeError('expected name to be a string.'); - } - - var val = cu.getDescriptor(provider, name); - if (val) Object.defineProperty(receiver, name, val); -}; - -/** - * Copy static properties, prototype properties, and descriptors - * from one object to another. - * - * @param {Object} `receiver` - * @param {Object} `provider` - * @param {String|Array} `omit` One or more properties to omit - * @return {Object} - * @api public - */ - -cu.copy = function copy(receiver, provider, omit) { - if (!cu.isObject(receiver)) { - throw new TypeError('expected receiving object to be an object.'); - } - if (!cu.isObject(provider)) { - throw new TypeError('expected providing object to be an object.'); - } - var props = Object.getOwnPropertyNames(provider); - var keys = Object.keys(provider); - var len = props.length, - key; - omit = cu.arrayify(omit); - - while (len--) { - key = props[len]; - - if (cu.has(keys, key)) { - define(receiver, key, provider[key]); - } else if (!(key in receiver) && !cu.has(omit, key)) { - cu.copyDescriptor(receiver, provider, key); - } - } -}; - -/** - * Inherit the static properties, prototype properties, and descriptors - * from of an object. - * - * @param {Object} `receiver` - * @param {Object} `provider` - * @param {String|Array} `omit` One or more properties to omit - * @return {Object} - * @api public - */ - -cu.inherit = function inherit(receiver, provider, omit) { - if (!cu.isObject(receiver)) { - throw new TypeError('expected receiving object to be an object.'); - } - if (!cu.isObject(provider)) { - throw new TypeError('expected providing object to be an object.'); - } - - var keys = []; - for (var key in provider) { - keys.push(key); - receiver[key] = provider[key]; - } - - keys = keys.concat(cu.arrayify(omit)); - - var a = provider.prototype || provider; - var b = receiver.prototype || receiver; - cu.copy(b, a, keys); -}; - -/** - * Returns a function for extending the static properties, - * prototype properties, and descriptors from the `Parent` - * constructor onto `Child` constructors. - * - * ```js - * var extend = cu.extend(Parent); - * Parent.extend(Child); - * - * // optional methods - * Parent.extend(Child, { - * foo: function() {}, - * bar: function() {} - * }); - * ``` - * @param {Function} `Parent` Parent ctor - * @param {Function} `extend` Optional extend function to handle custom extensions. Useful when updating methods that require a specific prototype. - * @param {Function} `Child` Child ctor - * @param {Object} `proto` Optionally pass additional prototype properties to inherit. - * @return {Object} - * @api public - */ - -cu.extend = function() { - // keep it lazy, instead of assigning to `cu.extend` - return staticExtend.apply(null, arguments); -}; - -/** - * Bubble up events emitted from static methods on the Parent ctor. - * - * @param {Object} `Parent` - * @param {Array} `events` Event names to bubble up - * @api public - */ - -cu.bubble = function(Parent, events) { - events = events || []; - Parent.bubble = function(Child, arr) { - if (Array.isArray(arr)) { - events = union([], events, arr); - } - var len = events.length; - var idx = -1; - while (++idx < len) { - var name = events[idx]; - Parent.on(name, Child.emit.bind(Child, name)); - } - cu.bubble(Child, events); - }; -}; - - -/***/ }), -/* 645 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * define-property - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -var isDescriptor = __webpack_require__(646); - -module.exports = function defineProperty(obj, prop, val) { - if (typeof obj !== 'object' && typeof obj !== 'function') { - throw new TypeError('expected an object or function.'); - } - - if (typeof prop !== 'string') { - throw new TypeError('expected `prop` to be a string.'); - } - - if (isDescriptor(val) && ('set' in val || 'get' in val)) { - return Object.defineProperty(obj, prop, val); - } - - return Object.defineProperty(obj, prop, { - configurable: true, - enumerable: false, - writable: true, - value: val - }); -}; - - -/***/ }), -/* 646 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * is-descriptor - * - * Copyright (c) 2015-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var typeOf = __webpack_require__(592); -var isAccessor = __webpack_require__(647); -var isData = __webpack_require__(648); - -module.exports = function isDescriptor(obj, key) { - if (typeOf(obj) !== 'object') { - return false; - } - if ('get' in obj) { - return isAccessor(obj, key); - } - return isData(obj, key); -}; - - -/***/ }), -/* 647 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * is-accessor-descriptor - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -var typeOf = __webpack_require__(592); - -// accessor descriptor properties -var accessor = { - get: 'function', - set: 'function', - configurable: 'boolean', - enumerable: 'boolean' -}; - -function isAccessorDescriptor(obj, prop) { - if (typeof prop === 'string') { - var val = Object.getOwnPropertyDescriptor(obj, prop); - return typeof val !== 'undefined'; - } - - if (typeOf(obj) !== 'object') { - return false; - } - - if (has(obj, 'value') || has(obj, 'writable')) { - return false; - } - - if (!has(obj, 'get') || typeof obj.get !== 'function') { - return false; - } - - // tldr: it's valid to have "set" be undefined - // "set" might be undefined if `Object.getOwnPropertyDescriptor` - // was used to get the value, and only `get` was defined by the user - if (has(obj, 'set') && typeof obj[key] !== 'function' && typeof obj[key] !== 'undefined') { - return false; - } - - for (var key in obj) { - if (!accessor.hasOwnProperty(key)) { - continue; - } - - if (typeOf(obj[key]) === accessor[key]) { - continue; - } - - if (typeof obj[key] !== 'undefined') { - return false; - } - } - return true; -} - -function has(obj, key) { - return {}.hasOwnProperty.call(obj, key); -} - -/** - * Expose `isAccessorDescriptor` - */ - -module.exports = isAccessorDescriptor; - - -/***/ }), -/* 648 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * is-data-descriptor - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -var typeOf = __webpack_require__(592); - -// data descriptor properties -var data = { - configurable: 'boolean', - enumerable: 'boolean', - writable: 'boolean' -}; - -function isDataDescriptor(obj, prop) { - if (typeOf(obj) !== 'object') { - return false; - } - - if (typeof prop === 'string') { - var val = Object.getOwnPropertyDescriptor(obj, prop); - return typeof val !== 'undefined'; - } - - if (!('value' in obj) && !('writable' in obj)) { - return false; - } - - for (var key in obj) { - if (key === 'value') continue; - - if (!data.hasOwnProperty(key)) { - continue; - } - - if (typeOf(obj[key]) === data[key]) { - continue; - } - - if (typeof obj[key] !== 'undefined') { - return false; - } - } - return true; -} - -/** - * Expose `isDataDescriptor` - */ - -module.exports = isDataDescriptor; - - -/***/ }), -/* 649 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * static-extend - * - * Copyright (c) 2016, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -var copy = __webpack_require__(650); -var define = __webpack_require__(645); -var util = __webpack_require__(112); - -/** - * Returns a function for extending the static properties, - * prototype properties, and descriptors from the `Parent` - * constructor onto `Child` constructors. - * - * ```js - * var extend = require('static-extend'); - * Parent.extend = extend(Parent); - * - * // optionally pass a custom merge function as the second arg - * Parent.extend = extend(Parent, function(Child) { - * Child.prototype.mixin = function(key, val) { - * Child.prototype[key] = val; - * }; - * }); - * - * // extend "child" constructors - * Parent.extend(Child); - * - * // optionally define prototype methods as the second arg - * Parent.extend(Child, { - * foo: function() {}, - * bar: function() {} - * }); - * ``` - * @param {Function} `Parent` Parent ctor - * @param {Function} `extendFn` Optional extend function for handling any necessary custom merging. Useful when updating methods that require a specific prototype. - * @param {Function} `Child` Child ctor - * @param {Object} `proto` Optionally pass additional prototype properties to inherit. - * @return {Object} - * @api public - */ - -function extend(Parent, extendFn) { - if (typeof Parent !== 'function') { - throw new TypeError('expected Parent to be a function.'); - } - - return function(Ctor, proto) { - if (typeof Ctor !== 'function') { - throw new TypeError('expected Ctor to be a function.'); - } - - util.inherits(Ctor, Parent); - copy(Ctor, Parent); - - // proto can be null or a plain object - if (typeof proto === 'object') { - var obj = Object.create(proto); - - for (var k in obj) { - Ctor.prototype[k] = obj[k]; - } - } - - // keep a reference to the parent prototype - define(Ctor.prototype, '_parent_', { - configurable: true, - set: function() {}, - get: function() { - return Parent.prototype; - } - }); - - if (typeof extendFn === 'function') { - extendFn(Ctor, Parent); - } - - Ctor.extend = extend(Ctor, extendFn); - }; -}; - -/** - * Expose `extend` - */ - -module.exports = extend; - - -/***/ }), -/* 650 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var typeOf = __webpack_require__(592); -var copyDescriptor = __webpack_require__(651); -var define = __webpack_require__(645); - -/** - * Copy static properties, prototype properties, and descriptors from one object to another. - * - * ```js - * function App() {} - * var proto = App.prototype; - * App.prototype.set = function() {}; - * App.prototype.get = function() {}; - * - * var obj = {}; - * copy(obj, proto); - * ``` - * @param {Object} `receiver` - * @param {Object} `provider` - * @param {String|Array} `omit` One or more properties to omit - * @return {Object} - * @api public - */ - -function copy(receiver, provider, omit) { - if (!isObject(receiver)) { - throw new TypeError('expected receiving object to be an object.'); - } - if (!isObject(provider)) { - throw new TypeError('expected providing object to be an object.'); - } - - var props = nativeKeys(provider); - var keys = Object.keys(provider); - var len = props.length; - omit = arrayify(omit); - - while (len--) { - var key = props[len]; - - if (has(keys, key)) { - define(receiver, key, provider[key]); - } else if (!(key in receiver) && !has(omit, key)) { - copyDescriptor(receiver, provider, key); - } - } -}; - -/** - * Return true if the given value is an object or function - */ - -function isObject(val) { - return typeOf(val) === 'object' || typeof val === 'function'; -} - -/** - * Returns true if an array has any of the given elements, or an - * object has any of the give keys. - * - * ```js - * has(['a', 'b', 'c'], 'c'); - * //=> true - * - * has(['a', 'b', 'c'], ['c', 'z']); - * //=> true - * - * has({a: 'b', c: 'd'}, ['c', 'z']); - * //=> true - * ``` - * @param {Object} `obj` - * @param {String|Array} `val` - * @return {Boolean} - */ - -function has(obj, val) { - val = arrayify(val); - var len = val.length; - - if (isObject(obj)) { - for (var key in obj) { - if (val.indexOf(key) > -1) { - return true; - } - } - - var keys = nativeKeys(obj); - return has(keys, val); - } - - if (Array.isArray(obj)) { - var arr = obj; - while (len--) { - if (arr.indexOf(val[len]) > -1) { - return true; - } - } - return false; - } - - throw new TypeError('expected an array or object.'); -} - -/** - * Cast the given value to an array. - * - * ```js - * arrayify('foo'); - * //=> ['foo'] - * - * arrayify(['foo']); - * //=> ['foo'] - * ``` - * - * @param {String|Array} `val` - * @return {Array} - */ - -function arrayify(val) { - return val ? (Array.isArray(val) ? val : [val]) : []; -} - -/** - * Returns true if a value has a `contructor` - * - * ```js - * hasConstructor({}); - * //=> true - * - * hasConstructor(Object.create(null)); - * //=> false - * ``` - * @param {Object} `value` - * @return {Boolean} - */ - -function hasConstructor(val) { - return isObject(val) && typeof val.constructor !== 'undefined'; -} - -/** - * Get the native `ownPropertyNames` from the constructor of the - * given `object`. An empty array is returned if the object does - * not have a constructor. - * - * ```js - * nativeKeys({a: 'b', b: 'c', c: 'd'}) - * //=> ['a', 'b', 'c'] - * - * nativeKeys(function(){}) - * //=> ['length', 'caller'] - * ``` - * - * @param {Object} `obj` Object that has a `constructor`. - * @return {Array} Array of keys. - */ - -function nativeKeys(val) { - if (!hasConstructor(val)) return []; - return Object.getOwnPropertyNames(val); -} - -/** - * Expose `copy` - */ - -module.exports = copy; - -/** - * Expose `copy.has` for tests - */ - -module.exports.has = has; - - -/***/ }), -/* 651 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * copy-descriptor - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -/** - * Copy a descriptor from one object to another. - * - * ```js - * function App() { - * this.cache = {}; - * } - * App.prototype.set = function(key, val) { - * this.cache[key] = val; - * return this; - * }; - * Object.defineProperty(App.prototype, 'count', { - * get: function() { - * return Object.keys(this.cache).length; - * } - * }); - * - * copy(App.prototype, 'count', 'len'); - * - * // create an instance - * var app = new App(); - * - * app.set('a', true); - * app.set('b', true); - * app.set('c', true); - * - * console.log(app.count); - * //=> 3 - * console.log(app.len); - * //=> 3 - * ``` - * @name copy - * @param {Object} `receiver` The target object - * @param {Object} `provider` The provider object - * @param {String} `from` The key to copy on provider. - * @param {String} `to` Optionally specify a new key name to use. - * @return {Object} - * @api public - */ - -module.exports = function copyDescriptor(receiver, provider, from, to) { - if (!isObject(provider) && typeof provider !== 'function') { - to = from; - from = provider; - provider = receiver; - } - if (!isObject(receiver) && typeof receiver !== 'function') { - throw new TypeError('expected the first argument to be an object'); - } - if (!isObject(provider) && typeof provider !== 'function') { - throw new TypeError('expected provider to be an object'); - } - - if (typeof to !== 'string') { - to = from; - } - if (typeof from !== 'string') { - throw new TypeError('expected key to be a string'); - } - - if (!(from in provider)) { - throw new Error('property "' + from + '" does not exist'); - } - - var val = Object.getOwnPropertyDescriptor(provider, from); - if (val) Object.defineProperty(receiver, to, val); -}; - -function isObject(val) { - return {}.toString.call(val) === '[object Object]'; -} - - - -/***/ }), -/* 652 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var use = __webpack_require__(653); -var define = __webpack_require__(645); -var debug = __webpack_require__(654)('snapdragon:compiler'); -var utils = __webpack_require__(660); - -/** - * Create a new `Compiler` with the given `options`. - * @param {Object} `options` - */ - -function Compiler(options, state) { - debug('initializing', __filename); - this.options = utils.extend({source: 'string'}, options); - this.state = state || {}; - this.compilers = {}; - this.output = ''; - this.set('eos', function(node) { - return this.emit(node.val, node); - }); - this.set('noop', function(node) { - return this.emit(node.val, node); - }); - this.set('bos', function(node) { - return this.emit(node.val, node); - }); - use(this); -} - -/** - * Prototype methods - */ - -Compiler.prototype = { - - /** - * Throw an error message with details including the cursor position. - * @param {String} `msg` Message to use in the Error. - */ - - error: function(msg, node) { - var pos = node.position || {start: {column: 0}}; - var message = this.options.source + ' column:' + pos.start.column + ': ' + msg; - - var err = new Error(message); - err.reason = msg; - err.column = pos.start.column; - err.source = this.pattern; - - if (this.options.silent) { - this.errors.push(err); - } else { - throw err; - } - }, - - /** - * Define a non-enumberable property on the `Compiler` instance. - * - * ```js - * compiler.define('foo', 'bar'); - * ``` - * @name .define - * @param {String} `key` propery name - * @param {any} `val` property value - * @return {Object} Returns the Compiler instance for chaining. - * @api public - */ - - define: function(key, val) { - define(this, key, val); - return this; - }, - - /** - * Emit `node.val` - */ - - emit: function(str, node) { - this.output += str; - return str; - }, - - /** - * Add a compiler `fn` with the given `name` - */ - - set: function(name, fn) { - this.compilers[name] = fn; - return this; - }, - - /** - * Get compiler `name`. - */ - - get: function(name) { - return this.compilers[name]; - }, - - /** - * Get the previous AST node. - */ - - prev: function(n) { - return this.ast.nodes[this.idx - (n || 1)] || { type: 'bos', val: '' }; - }, - - /** - * Get the next AST node. - */ - - next: function(n) { - return this.ast.nodes[this.idx + (n || 1)] || { type: 'eos', val: '' }; - }, - - /** - * Visit `node`. - */ - - visit: function(node, nodes, i) { - var fn = this.compilers[node.type]; - this.idx = i; - - if (typeof fn !== 'function') { - throw this.error('compiler "' + node.type + '" is not registered', node); - } - return fn.call(this, node, nodes, i); - }, - - /** - * Map visit over array of `nodes`. - */ - - mapVisit: function(nodes) { - if (!Array.isArray(nodes)) { - throw new TypeError('expected an array'); - } - var len = nodes.length; - var idx = -1; - while (++idx < len) { - this.visit(nodes[idx], nodes, idx); - } - return this; - }, - - /** - * Compile `ast`. - */ - - compile: function(ast, options) { - var opts = utils.extend({}, this.options, options); - this.ast = ast; - this.parsingErrors = this.ast.errors; - this.output = ''; - - // source map support - if (opts.sourcemap) { - var sourcemaps = __webpack_require__(680); - sourcemaps(this); - this.mapVisit(this.ast.nodes); - this.applySourceMaps(); - this.map = opts.sourcemap === 'generator' ? this.map : this.map.toJSON(); - return this; - } - - this.mapVisit(this.ast.nodes); - return this; - } -}; - -/** - * Expose `Compiler` - */ - -module.exports = Compiler; - - -/***/ }), -/* 653 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * use - * - * Copyright (c) 2015-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -module.exports = function base(app, options) { - if (!isObject(app) && typeof app !== 'function') { - throw new TypeError('expected an object or function'); - } - - var opts = isObject(options) ? options : {}; - var prop = typeof opts.prop === 'string' ? opts.prop : 'fns'; - if (!Array.isArray(app[prop])) { - define(app, prop, []); - } - - /** - * Define a plugin function to be passed to use. The only - * parameter exposed to the plugin is `app`, the object or function. - * passed to `use(app)`. `app` is also exposed as `this` in plugins. - * - * Additionally, **if a plugin returns a function, the function will - * be pushed onto the `fns` array**, allowing the plugin to be - * called at a later point by the `run` method. - * - * ```js - * var use = require('use'); - * - * // define a plugin - * function foo(app) { - * // do stuff - * } - * - * var app = function(){}; - * use(app); - * - * // register plugins - * app.use(foo); - * app.use(bar); - * app.use(baz); - * ``` - * @name .use - * @param {Function} `fn` plugin function to call - * @api public - */ - - define(app, 'use', use); - - /** - * Run all plugins on `fns`. Any plugin that returns a function - * when called by `use` is pushed onto the `fns` array. - * - * ```js - * var config = {}; - * app.run(config); - * ``` - * @name .run - * @param {Object} `value` Object to be modified by plugins. - * @return {Object} Returns the object passed to `run` - * @api public - */ - - define(app, 'run', function(val) { - if (!isObject(val)) return; - - if (!val.use || !val.run) { - define(val, prop, val[prop] || []); - define(val, 'use', use); - } - - if (!val[prop] || val[prop].indexOf(base) === -1) { - val.use(base); - } - - var self = this || app; - var fns = self[prop]; - var len = fns.length; - var idx = -1; - - while (++idx < len) { - val.use(fns[idx]); - } - return val; - }); - - /** - * Call plugin `fn`. If a function is returned push it into the - * `fns` array to be called by the `run` method. - */ - - function use(type, fn, options) { - var offset = 1; - - if (typeof type === 'string' || Array.isArray(type)) { - fn = wrap(type, fn); - offset++; - } else { - options = fn; - fn = type; - } - - if (typeof fn !== 'function') { - throw new TypeError('expected a function'); - } - - var self = this || app; - var fns = self[prop]; - - var args = [].slice.call(arguments, offset); - args.unshift(self); - - if (typeof opts.hook === 'function') { - opts.hook.apply(self, args); - } - - var val = fn.apply(self, args); - if (typeof val === 'function' && fns.indexOf(val) === -1) { - fns.push(val); - } - return self; - } - - /** - * Wrap a named plugin function so that it's only called on objects of the - * given `type` - * - * @param {String} `type` - * @param {Function} `fn` Plugin function - * @return {Function} - */ - - function wrap(type, fn) { - return function plugin() { - return this.type === type ? fn.apply(this, arguments) : plugin; - }; - } - - return app; -}; - -function isObject(val) { - return val && typeof val === 'object' && !Array.isArray(val); -} - -function define(obj, key, val) { - Object.defineProperty(obj, key, { - configurable: true, - writable: true, - value: val - }); -} - - -/***/ }), -/* 654 */ -/***/ (function(module, exports, __webpack_require__) { - -/** - * Detect Electron renderer process, which is node, but we should - * treat as a browser. - */ - -if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(655); -} else { - module.exports = __webpack_require__(658); -} - - -/***/ }), -/* 655 */ -/***/ (function(module, exports, __webpack_require__) { - -/** - * This is the web browser implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = __webpack_require__(656); -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; -exports.storage = 'undefined' != typeof chrome - && 'undefined' != typeof chrome.storage - ? chrome.storage.local - : localstorage(); - -/** - * Colors. - */ - -exports.colors = [ - 'lightseagreen', - 'forestgreen', - 'goldenrod', - 'dodgerblue', - 'darkorchid', - 'crimson' -]; - -/** - * Currently only WebKit-based Web Inspectors, Firefox >= v31, - * and the Firebug extension (any Firefox version) are known - * to support "%c" CSS customizations. - * - * TODO: add a `localStorage` variable to explicitly enable/disable colors - */ - -function useColors() { - // NB: In an Electron preload script, document will be defined but not fully - // initialized. Since we know we're in Chrome, we'll just detect this case - // explicitly - if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { - return true; - } - - // is webkit? http://stackoverflow.com/a/16459606/376773 - // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 - return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || - // is firebug? http://stackoverflow.com/a/398120/376773 - (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || - // is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || - // double check webkit in userAgent just in case we are in a worker - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); -} - -/** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ - -exports.formatters.j = function(v) { - try { - return JSON.stringify(v); - } catch (err) { - return '[UnexpectedJSONParseError]: ' + err.message; - } -}; - - -/** - * Colorize log arguments if enabled. - * - * @api public - */ - -function formatArgs(args) { - var useColors = this.useColors; - - args[0] = (useColors ? '%c' : '') - + this.namespace - + (useColors ? ' %c' : ' ') - + args[0] - + (useColors ? '%c ' : ' ') - + '+' + exports.humanize(this.diff); - - if (!useColors) return; - - var c = 'color: ' + this.color; - args.splice(1, 0, c, 'color: inherit') - - // the final "%c" is somewhat tricky, because there could be other - // arguments passed either before or after the %c, so we need to - // figure out the correct index to insert the CSS into - var index = 0; - var lastC = 0; - args[0].replace(/%[a-zA-Z%]/g, function(match) { - if ('%%' === match) return; - index++; - if ('%c' === match) { - // we only are interested in the *last* %c - // (the user may have provided their own) - lastC = index; - } - }); - - args.splice(lastC, 0, c); -} - -/** - * Invokes `console.log()` when available. - * No-op when `console.log` is not a "function". - * - * @api public - */ - -function log() { - // this hackery is required for IE8/9, where - // the `console.log` function doesn't have 'apply' - return 'object' === typeof console - && console.log - && Function.prototype.apply.call(console.log, console, arguments); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - -function save(namespaces) { - try { - if (null == namespaces) { - exports.storage.removeItem('debug'); - } else { - exports.storage.debug = namespaces; - } - } catch(e) {} -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - -function load() { - var r; - try { - r = exports.storage.debug; - } catch(e) {} - - // If debug isn't set in LS, and we're in Electron, try to load $DEBUG - if (!r && typeof process !== 'undefined' && 'env' in process) { - r = process.env.DEBUG; - } - - return r; -} - -/** - * Enable namespaces listed in `localStorage.debug` initially. - */ - -exports.enable(load()); - -/** - * Localstorage attempts to return the localstorage. - * - * This is necessary because safari throws - * when a user disables cookies/localstorage - * and you attempt to access it. - * - * @return {LocalStorage} - * @api private - */ - -function localstorage() { - try { - return window.localStorage; - } catch (e) {} -} - - -/***/ }), -/* 656 */ -/***/ (function(module, exports, __webpack_require__) { - - -/** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; -exports.coerce = coerce; -exports.disable = disable; -exports.enable = enable; -exports.enabled = enabled; -exports.humanize = __webpack_require__(657); - -/** - * The currently active debug mode names, and names to skip. - */ - -exports.names = []; -exports.skips = []; - -/** - * Map of special "%n" handling functions, for the debug "format" argument. - * - * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". - */ - -exports.formatters = {}; - -/** - * Previous log timestamp. - */ - -var prevTime; - -/** - * Select a color. - * @param {String} namespace - * @return {Number} - * @api private - */ - -function selectColor(namespace) { - var hash = 0, i; - - for (i in namespace) { - hash = ((hash << 5) - hash) + namespace.charCodeAt(i); - hash |= 0; // Convert to 32bit integer - } - - return exports.colors[Math.abs(hash) % exports.colors.length]; -} - -/** - * Create a debugger with the given `namespace`. - * - * @param {String} namespace - * @return {Function} - * @api public - */ - -function createDebug(namespace) { - - function debug() { - // disabled? - if (!debug.enabled) return; - - var self = debug; - - // set `diff` timestamp - var curr = +new Date(); - var ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; - - // turn the `arguments` into a proper Array - var args = new Array(arguments.length); - for (var i = 0; i < args.length; i++) { - args[i] = arguments[i]; - } - - args[0] = exports.coerce(args[0]); - - if ('string' !== typeof args[0]) { - // anything else let's inspect with %O - args.unshift('%O'); - } - - // apply any `formatters` transformations - var index = 0; - args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { - // if we encounter an escaped % then don't increase the array index - if (match === '%%') return match; - index++; - var formatter = exports.formatters[format]; - if ('function' === typeof formatter) { - var val = args[index]; - match = formatter.call(self, val); - - // now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; - } - return match; - }); - - // apply env-specific formatting (colors, etc.) - exports.formatArgs.call(self, args); - - var logFn = debug.log || exports.log || console.log.bind(console); - logFn.apply(self, args); - } - - debug.namespace = namespace; - debug.enabled = exports.enabled(namespace); - debug.useColors = exports.useColors(); - debug.color = selectColor(namespace); - - // env-specific initialization logic for debug instances - if ('function' === typeof exports.init) { - exports.init(debug); - } - - return debug; -} - -/** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. - * - * @param {String} namespaces - * @api public - */ - -function enable(namespaces) { - exports.save(namespaces); - - exports.names = []; - exports.skips = []; - - var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); - var len = split.length; - - for (var i = 0; i < len; i++) { - if (!split[i]) continue; // ignore empty strings - namespaces = split[i].replace(/\*/g, '.*?'); - if (namespaces[0] === '-') { - exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); - } else { - exports.names.push(new RegExp('^' + namespaces + '$')); - } - } -} - -/** - * Disable debug output. - * - * @api public - */ - -function disable() { - exports.enable(''); -} - -/** - * Returns true if the given mode name is enabled, false otherwise. - * - * @param {String} name - * @return {Boolean} - * @api public - */ - -function enabled(name) { - var i, len; - for (i = 0, len = exports.skips.length; i < len; i++) { - if (exports.skips[i].test(name)) { - return false; - } - } - for (i = 0, len = exports.names.length; i < len; i++) { - if (exports.names[i].test(name)) { - return true; - } - } - return false; -} - -/** - * Coerce `val`. - * - * @param {Mixed} val - * @return {Mixed} - * @api private - */ - -function coerce(val) { - if (val instanceof Error) return val.stack || val.message; - return val; -} - - -/***/ }), -/* 657 */ -/***/ (function(module, exports) { - -/** - * Helpers. - */ - -var s = 1000; -var m = s * 60; -var h = m * 60; -var d = h * 24; -var y = d * 365.25; - -/** - * Parse or format the given `val`. - * - * Options: - * - * - `long` verbose formatting [false] - * - * @param {String|Number} val - * @param {Object} [options] - * @throws {Error} throw an error if val is not a non-empty string or a number - * @return {String|Number} - * @api public - */ - -module.exports = function(val, options) { - options = options || {}; - var type = typeof val; - if (type === 'string' && val.length > 0) { - return parse(val); - } else if (type === 'number' && isNaN(val) === false) { - return options.long ? fmtLong(val) : fmtShort(val); - } - throw new Error( - 'val is not a non-empty string or a valid number. val=' + - JSON.stringify(val) - ); -}; - -/** - * Parse the given `str` and return milliseconds. - * - * @param {String} str - * @return {Number} - * @api private - */ - -function parse(str) { - str = String(str); - if (str.length > 100) { - return; - } - var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( - str - ); - if (!match) { - return; - } - var n = parseFloat(match[1]); - var type = (match[2] || 'ms').toLowerCase(); - switch (type) { - case 'years': - case 'year': - case 'yrs': - case 'yr': - case 'y': - return n * y; - case 'days': - case 'day': - case 'd': - return n * d; - case 'hours': - case 'hour': - case 'hrs': - case 'hr': - case 'h': - return n * h; - case 'minutes': - case 'minute': - case 'mins': - case 'min': - case 'm': - return n * m; - case 'seconds': - case 'second': - case 'secs': - case 'sec': - case 's': - return n * s; - case 'milliseconds': - case 'millisecond': - case 'msecs': - case 'msec': - case 'ms': - return n; - default: - return undefined; - } -} - -/** - * Short format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtShort(ms) { - if (ms >= d) { - return Math.round(ms / d) + 'd'; - } - if (ms >= h) { - return Math.round(ms / h) + 'h'; - } - if (ms >= m) { - return Math.round(ms / m) + 'm'; - } - if (ms >= s) { - return Math.round(ms / s) + 's'; - } - return ms + 'ms'; -} - -/** - * Long format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtLong(ms) { - return plural(ms, d, 'day') || - plural(ms, h, 'hour') || - plural(ms, m, 'minute') || - plural(ms, s, 'second') || - ms + ' ms'; -} - -/** - * Pluralization helper. - */ - -function plural(ms, n, name) { - if (ms < n) { - return; - } - if (ms < n * 1.5) { - return Math.floor(ms / n) + ' ' + name; - } - return Math.ceil(ms / n) + ' ' + name + 's'; -} - - -/***/ }), -/* 658 */ -/***/ (function(module, exports, __webpack_require__) { - -/** - * Module dependencies. - */ - -var tty = __webpack_require__(122); -var util = __webpack_require__(112); - -/** - * This is the Node.js implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = __webpack_require__(656); -exports.init = init; -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; - -/** - * Colors. - */ - -exports.colors = [6, 2, 3, 4, 5, 1]; - -/** - * Build up the default `inspectOpts` object from the environment variables. - * - * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js - */ - -exports.inspectOpts = Object.keys(process.env).filter(function (key) { - return /^debug_/i.test(key); -}).reduce(function (obj, key) { - // camel-case - var prop = key - .substring(6) - .toLowerCase() - .replace(/_([a-z])/g, function (_, k) { return k.toUpperCase() }); - - // coerce string value into JS value - var val = process.env[key]; - if (/^(yes|on|true|enabled)$/i.test(val)) val = true; - else if (/^(no|off|false|disabled)$/i.test(val)) val = false; - else if (val === 'null') val = null; - else val = Number(val); - - obj[prop] = val; - return obj; -}, {}); - -/** - * The file descriptor to write the `debug()` calls to. - * Set the `DEBUG_FD` env variable to override with another value. i.e.: - * - * $ DEBUG_FD=3 node script.js 3>debug.log - */ - -var fd = parseInt(process.env.DEBUG_FD, 10) || 2; - -if (1 !== fd && 2 !== fd) { - util.deprecate(function(){}, 'except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)')() -} - -var stream = 1 === fd ? process.stdout : - 2 === fd ? process.stderr : - createWritableStdioStream(fd); - -/** - * Is stdout a TTY? Colored output is enabled when `true`. - */ - -function useColors() { - return 'colors' in exports.inspectOpts - ? Boolean(exports.inspectOpts.colors) - : tty.isatty(fd); -} - -/** - * Map %o to `util.inspect()`, all on a single line. - */ - -exports.formatters.o = function(v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts) - .split('\n').map(function(str) { - return str.trim() - }).join(' '); -}; - -/** - * Map %o to `util.inspect()`, allowing multiple lines if needed. - */ - -exports.formatters.O = function(v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts); -}; - -/** - * Adds ANSI color escape codes if enabled. - * - * @api public - */ - -function formatArgs(args) { - var name = this.namespace; - var useColors = this.useColors; - - if (useColors) { - var c = this.color; - var prefix = ' \u001b[3' + c + ';1m' + name + ' ' + '\u001b[0m'; - - args[0] = prefix + args[0].split('\n').join('\n' + prefix); - args.push('\u001b[3' + c + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); - } else { - args[0] = new Date().toUTCString() - + ' ' + name + ' ' + args[0]; - } -} - -/** - * Invokes `util.format()` with the specified arguments and writes to `stream`. - */ - -function log() { - return stream.write(util.format.apply(util, arguments) + '\n'); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - -function save(namespaces) { - if (null == namespaces) { - // If you set a process.env field to null or undefined, it gets cast to the - // string 'null' or 'undefined'. Just delete instead. - delete process.env.DEBUG; - } else { - process.env.DEBUG = namespaces; - } -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - -function load() { - return process.env.DEBUG; -} - -/** - * Copied from `node/src/node.js`. - * - * XXX: It's lame that node doesn't expose this API out-of-the-box. It also - * relies on the undocumented `tty_wrap.guessHandleType()` which is also lame. - */ - -function createWritableStdioStream (fd) { - var stream; - var tty_wrap = process.binding('tty_wrap'); - - // Note stream._type is used for test-module-load-list.js - - switch (tty_wrap.guessHandleType(fd)) { - case 'TTY': - stream = new tty.WriteStream(fd); - stream._type = 'tty'; - - // Hack to have stream not keep the event loop alive. - // See https://github.com/joyent/node/issues/1726 - if (stream._handle && stream._handle.unref) { - stream._handle.unref(); - } - break; - - case 'FILE': - var fs = __webpack_require__(134); - stream = new fs.SyncWriteStream(fd, { autoClose: false }); - stream._type = 'fs'; - break; - - case 'PIPE': - case 'TCP': - var net = __webpack_require__(659); - stream = new net.Socket({ - fd: fd, - readable: false, - writable: true - }); - - // FIXME Should probably have an option in net.Socket to create a - // stream from an existing fd which is writable only. But for now - // we'll just add this hack and set the `readable` member to false. - // Test: ./node test/fixtures/echo.js < /etc/passwd - stream.readable = false; - stream.read = null; - stream._type = 'pipe'; - - // FIXME Hack to have stream not keep the event loop alive. - // See https://github.com/joyent/node/issues/1726 - if (stream._handle && stream._handle.unref) { - stream._handle.unref(); - } - break; - - default: - // Probably an error on in uv_guess_handle() - throw new Error('Implement me. Unknown stream file type!'); - } - - // For supporting legacy API we put the FD here. - stream.fd = fd; - - stream._isStdio = true; - - return stream; -} - -/** - * Init logic for `debug` instances. - * - * Create a new `inspectOpts` object in case `useColors` is set - * differently for a particular `debug` instance. - */ - -function init (debug) { - debug.inspectOpts = {}; - - var keys = Object.keys(exports.inspectOpts); - for (var i = 0; i < keys.length; i++) { - debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; - } -} - -/** - * Enable namespaces listed in `process.env.DEBUG` initially. - */ - -exports.enable(load()); - - -/***/ }), -/* 659 */ -/***/ (function(module, exports) { - -module.exports = require("net"); - -/***/ }), -/* 660 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/** - * Module dependencies - */ - -exports.extend = __webpack_require__(661); -exports.SourceMap = __webpack_require__(662); -exports.sourceMapResolve = __webpack_require__(673); - -/** - * Convert backslash in the given string to forward slashes - */ - -exports.unixify = function(fp) { - return fp.split(/\\+/).join('/'); -}; - -/** - * Return true if `val` is a non-empty string - * - * @param {String} `str` - * @return {Boolean} - */ - -exports.isString = function(str) { - return str && typeof str === 'string'; -}; - -/** - * Cast `val` to an array - * @return {Array} - */ - -exports.arrayify = function(val) { - if (typeof val === 'string') return [val]; - return val ? (Array.isArray(val) ? val : [val]) : []; -}; - -/** - * Get the last `n` element from the given `array` - * @param {Array} `array` - * @return {*} - */ - -exports.last = function(arr, n) { - return arr[arr.length - (n || 1)]; -}; - - -/***/ }), -/* 661 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var isObject = __webpack_require__(602); - -module.exports = function extend(o/*, objects*/) { - if (!isObject(o)) { o = {}; } - - var len = arguments.length; - for (var i = 1; i < len; i++) { - var obj = arguments[i]; - - if (isObject(obj)) { - assign(o, obj); - } - } - return o; -}; - -function assign(a, b) { - for (var key in b) { - if (hasOwn(b, key)) { - a[key] = b[key]; - } - } -} - -/** - * Returns true if the given `key` is an own property of `obj`. - */ - -function hasOwn(obj, key) { - return Object.prototype.hasOwnProperty.call(obj, key); -} - - -/***/ }), -/* 662 */ -/***/ (function(module, exports, __webpack_require__) { - -/* - * Copyright 2009-2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE.txt or: - * http://opensource.org/licenses/BSD-3-Clause - */ -exports.SourceMapGenerator = __webpack_require__(663).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(669).SourceMapConsumer; -exports.SourceNode = __webpack_require__(672).SourceNode; - - -/***/ }), -/* 663 */ -/***/ (function(module, exports, __webpack_require__) { - -/* -*- Mode: js; js-indent-level: 2; -*- */ -/* - * Copyright 2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -var base64VLQ = __webpack_require__(664); -var util = __webpack_require__(666); -var ArraySet = __webpack_require__(667).ArraySet; -var MappingList = __webpack_require__(668).MappingList; - -/** - * An instance of the SourceMapGenerator represents a source map which is - * being built incrementally. You may pass an object with the following - * properties: - * - * - file: The filename of the generated source. - * - sourceRoot: A root for all relative URLs in this source map. - */ -function SourceMapGenerator(aArgs) { - if (!aArgs) { - aArgs = {}; - } - this._file = util.getArg(aArgs, 'file', null); - this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null); - this._skipValidation = util.getArg(aArgs, 'skipValidation', false); - this._sources = new ArraySet(); - this._names = new ArraySet(); - this._mappings = new MappingList(); - this._sourcesContents = null; -} - -SourceMapGenerator.prototype._version = 3; - -/** - * Creates a new SourceMapGenerator based on a SourceMapConsumer - * - * @param aSourceMapConsumer The SourceMap. - */ -SourceMapGenerator.fromSourceMap = - function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) { - var sourceRoot = aSourceMapConsumer.sourceRoot; - var generator = new SourceMapGenerator({ - file: aSourceMapConsumer.file, - sourceRoot: sourceRoot - }); - aSourceMapConsumer.eachMapping(function (mapping) { - var newMapping = { - generated: { - line: mapping.generatedLine, - column: mapping.generatedColumn - } - }; - - if (mapping.source != null) { - newMapping.source = mapping.source; - if (sourceRoot != null) { - newMapping.source = util.relative(sourceRoot, newMapping.source); - } - - newMapping.original = { - line: mapping.originalLine, - column: mapping.originalColumn - }; - - if (mapping.name != null) { - newMapping.name = mapping.name; - } - } - - generator.addMapping(newMapping); - }); - aSourceMapConsumer.sources.forEach(function (sourceFile) { - var content = aSourceMapConsumer.sourceContentFor(sourceFile); - if (content != null) { - generator.setSourceContent(sourceFile, content); - } - }); - return generator; - }; - -/** - * Add a single mapping from original source line and column to the generated - * source's line and column for this source map being created. The mapping - * object should have the following properties: - * - * - generated: An object with the generated line and column positions. - * - original: An object with the original line and column positions. - * - source: The original source file (relative to the sourceRoot). - * - name: An optional original token name for this mapping. - */ -SourceMapGenerator.prototype.addMapping = - function SourceMapGenerator_addMapping(aArgs) { - var generated = util.getArg(aArgs, 'generated'); - var original = util.getArg(aArgs, 'original', null); - var source = util.getArg(aArgs, 'source', null); - var name = util.getArg(aArgs, 'name', null); - - if (!this._skipValidation) { - this._validateMapping(generated, original, source, name); - } - - if (source != null) { - source = String(source); - if (!this._sources.has(source)) { - this._sources.add(source); - } - } - - if (name != null) { - name = String(name); - if (!this._names.has(name)) { - this._names.add(name); - } - } - - this._mappings.add({ - generatedLine: generated.line, - generatedColumn: generated.column, - originalLine: original != null && original.line, - originalColumn: original != null && original.column, - source: source, - name: name - }); - }; - -/** - * Set the source content for a source file. - */ -SourceMapGenerator.prototype.setSourceContent = - function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) { - var source = aSourceFile; - if (this._sourceRoot != null) { - source = util.relative(this._sourceRoot, source); - } - - if (aSourceContent != null) { - // Add the source content to the _sourcesContents map. - // Create a new _sourcesContents map if the property is null. - if (!this._sourcesContents) { - this._sourcesContents = Object.create(null); - } - this._sourcesContents[util.toSetString(source)] = aSourceContent; - } else if (this._sourcesContents) { - // Remove the source file from the _sourcesContents map. - // If the _sourcesContents map is empty, set the property to null. - delete this._sourcesContents[util.toSetString(source)]; - if (Object.keys(this._sourcesContents).length === 0) { - this._sourcesContents = null; - } - } - }; - -/** - * Applies the mappings of a sub-source-map for a specific source file to the - * source map being generated. Each mapping to the supplied source file is - * rewritten using the supplied source map. Note: The resolution for the - * resulting mappings is the minimium of this map and the supplied map. - * - * @param aSourceMapConsumer The source map to be applied. - * @param aSourceFile Optional. The filename of the source file. - * If omitted, SourceMapConsumer's file property will be used. - * @param aSourceMapPath Optional. The dirname of the path to the source map - * to be applied. If relative, it is relative to the SourceMapConsumer. - * This parameter is needed when the two source maps aren't in the same - * directory, and the source map to be applied contains relative source - * paths. If so, those relative source paths need to be rewritten - * relative to the SourceMapGenerator. - */ -SourceMapGenerator.prototype.applySourceMap = - function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) { - var sourceFile = aSourceFile; - // If aSourceFile is omitted, we will use the file property of the SourceMap - if (aSourceFile == null) { - if (aSourceMapConsumer.file == null) { - throw new Error( - 'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' + - 'or the source map\'s "file" property. Both were omitted.' - ); - } - sourceFile = aSourceMapConsumer.file; - } - var sourceRoot = this._sourceRoot; - // Make "sourceFile" relative if an absolute Url is passed. - if (sourceRoot != null) { - sourceFile = util.relative(sourceRoot, sourceFile); - } - // Applying the SourceMap can add and remove items from the sources and - // the names array. - var newSources = new ArraySet(); - var newNames = new ArraySet(); - - // Find mappings for the "sourceFile" - this._mappings.unsortedForEach(function (mapping) { - if (mapping.source === sourceFile && mapping.originalLine != null) { - // Check if it can be mapped by the source map, then update the mapping. - var original = aSourceMapConsumer.originalPositionFor({ - line: mapping.originalLine, - column: mapping.originalColumn - }); - if (original.source != null) { - // Copy mapping - mapping.source = original.source; - if (aSourceMapPath != null) { - mapping.source = util.join(aSourceMapPath, mapping.source) - } - if (sourceRoot != null) { - mapping.source = util.relative(sourceRoot, mapping.source); - } - mapping.originalLine = original.line; - mapping.originalColumn = original.column; - if (original.name != null) { - mapping.name = original.name; - } - } - } - - var source = mapping.source; - if (source != null && !newSources.has(source)) { - newSources.add(source); - } - - var name = mapping.name; - if (name != null && !newNames.has(name)) { - newNames.add(name); - } - - }, this); - this._sources = newSources; - this._names = newNames; - - // Copy sourcesContents of applied map. - aSourceMapConsumer.sources.forEach(function (sourceFile) { - var content = aSourceMapConsumer.sourceContentFor(sourceFile); - if (content != null) { - if (aSourceMapPath != null) { - sourceFile = util.join(aSourceMapPath, sourceFile); - } - if (sourceRoot != null) { - sourceFile = util.relative(sourceRoot, sourceFile); - } - this.setSourceContent(sourceFile, content); - } - }, this); - }; - -/** - * A mapping can have one of the three levels of data: - * - * 1. Just the generated position. - * 2. The Generated position, original position, and original source. - * 3. Generated and original position, original source, as well as a name - * token. - * - * To maintain consistency, we validate that any new mapping being added falls - * in to one of these categories. - */ -SourceMapGenerator.prototype._validateMapping = - function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource, - aName) { - // When aOriginal is truthy but has empty values for .line and .column, - // it is most likely a programmer error. In this case we throw a very - // specific error message to try to guide them the right way. - // For example: https://github.com/Polymer/polymer-bundler/pull/519 - if (aOriginal && typeof aOriginal.line !== 'number' && typeof aOriginal.column !== 'number') { - throw new Error( - 'original.line and original.column are not numbers -- you probably meant to omit ' + - 'the original mapping entirely and only map the generated position. If so, pass ' + - 'null for the original mapping instead of an object with empty or null values.' - ); - } - - if (aGenerated && 'line' in aGenerated && 'column' in aGenerated - && aGenerated.line > 0 && aGenerated.column >= 0 - && !aOriginal && !aSource && !aName) { - // Case 1. - return; - } - else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated - && aOriginal && 'line' in aOriginal && 'column' in aOriginal - && aGenerated.line > 0 && aGenerated.column >= 0 - && aOriginal.line > 0 && aOriginal.column >= 0 - && aSource) { - // Cases 2 and 3. - return; - } - else { - throw new Error('Invalid mapping: ' + JSON.stringify({ - generated: aGenerated, - source: aSource, - original: aOriginal, - name: aName - })); - } - }; - -/** - * Serialize the accumulated mappings in to the stream of base 64 VLQs - * specified by the source map format. - */ -SourceMapGenerator.prototype._serializeMappings = - function SourceMapGenerator_serializeMappings() { - var previousGeneratedColumn = 0; - var previousGeneratedLine = 1; - var previousOriginalColumn = 0; - var previousOriginalLine = 0; - var previousName = 0; - var previousSource = 0; - var result = ''; - var next; - var mapping; - var nameIdx; - var sourceIdx; - - var mappings = this._mappings.toArray(); - for (var i = 0, len = mappings.length; i < len; i++) { - mapping = mappings[i]; - next = '' - - if (mapping.generatedLine !== previousGeneratedLine) { - previousGeneratedColumn = 0; - while (mapping.generatedLine !== previousGeneratedLine) { - next += ';'; - previousGeneratedLine++; - } - } - else { - if (i > 0) { - if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) { - continue; - } - next += ','; - } - } - - next += base64VLQ.encode(mapping.generatedColumn - - previousGeneratedColumn); - previousGeneratedColumn = mapping.generatedColumn; - - if (mapping.source != null) { - sourceIdx = this._sources.indexOf(mapping.source); - next += base64VLQ.encode(sourceIdx - previousSource); - previousSource = sourceIdx; - - // lines are stored 0-based in SourceMap spec version 3 - next += base64VLQ.encode(mapping.originalLine - 1 - - previousOriginalLine); - previousOriginalLine = mapping.originalLine - 1; - - next += base64VLQ.encode(mapping.originalColumn - - previousOriginalColumn); - previousOriginalColumn = mapping.originalColumn; - - if (mapping.name != null) { - nameIdx = this._names.indexOf(mapping.name); - next += base64VLQ.encode(nameIdx - previousName); - previousName = nameIdx; - } - } - - result += next; - } - - return result; - }; - -SourceMapGenerator.prototype._generateSourcesContent = - function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) { - return aSources.map(function (source) { - if (!this._sourcesContents) { - return null; - } - if (aSourceRoot != null) { - source = util.relative(aSourceRoot, source); - } - var key = util.toSetString(source); - return Object.prototype.hasOwnProperty.call(this._sourcesContents, key) - ? this._sourcesContents[key] - : null; - }, this); - }; - -/** - * Externalize the source map. - */ -SourceMapGenerator.prototype.toJSON = - function SourceMapGenerator_toJSON() { - var map = { - version: this._version, - sources: this._sources.toArray(), - names: this._names.toArray(), - mappings: this._serializeMappings() - }; - if (this._file != null) { - map.file = this._file; - } - if (this._sourceRoot != null) { - map.sourceRoot = this._sourceRoot; - } - if (this._sourcesContents) { - map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot); - } - - return map; - }; - -/** - * Render the source map being generated to a string. - */ -SourceMapGenerator.prototype.toString = - function SourceMapGenerator_toString() { - return JSON.stringify(this.toJSON()); - }; - -exports.SourceMapGenerator = SourceMapGenerator; - - -/***/ }), -/* 664 */ -/***/ (function(module, exports, __webpack_require__) { - -/* -*- Mode: js; js-indent-level: 2; -*- */ -/* - * Copyright 2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE or: - * http://opensource.org/licenses/BSD-3-Clause - * - * Based on the Base 64 VLQ implementation in Closure Compiler: - * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java - * - * Copyright 2011 The Closure Compiler Authors. All rights reserved. - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -var base64 = __webpack_require__(665); - -// A single base 64 digit can contain 6 bits of data. For the base 64 variable -// length quantities we use in the source map spec, the first bit is the sign, -// the next four bits are the actual value, and the 6th bit is the -// continuation bit. The continuation bit tells us whether there are more -// digits in this value following this digit. -// -// Continuation -// | Sign -// | | -// V V -// 101011 - -var VLQ_BASE_SHIFT = 5; - -// binary: 100000 -var VLQ_BASE = 1 << VLQ_BASE_SHIFT; - -// binary: 011111 -var VLQ_BASE_MASK = VLQ_BASE - 1; - -// binary: 100000 -var VLQ_CONTINUATION_BIT = VLQ_BASE; - -/** - * Converts from a two-complement value to a value where the sign bit is - * placed in the least significant bit. For example, as decimals: - * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) - * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) - */ -function toVLQSigned(aValue) { - return aValue < 0 - ? ((-aValue) << 1) + 1 - : (aValue << 1) + 0; -} - -/** - * Converts to a two-complement value from a value where the sign bit is - * placed in the least significant bit. For example, as decimals: - * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 - * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 - */ -function fromVLQSigned(aValue) { - var isNegative = (aValue & 1) === 1; - var shifted = aValue >> 1; - return isNegative - ? -shifted - : shifted; -} - -/** - * Returns the base 64 VLQ encoded value. - */ -exports.encode = function base64VLQ_encode(aValue) { - var encoded = ""; - var digit; - - var vlq = toVLQSigned(aValue); - - do { - digit = vlq & VLQ_BASE_MASK; - vlq >>>= VLQ_BASE_SHIFT; - if (vlq > 0) { - // There are still more digits in this value, so we must make sure the - // continuation bit is marked. - digit |= VLQ_CONTINUATION_BIT; - } - encoded += base64.encode(digit); - } while (vlq > 0); - - return encoded; -}; - -/** - * Decodes the next base 64 VLQ value from the given string and returns the - * value and the rest of the string via the out parameter. - */ -exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { - var strLen = aStr.length; - var result = 0; - var shift = 0; - var continuation, digit; - - do { - if (aIndex >= strLen) { - throw new Error("Expected more digits in base 64 VLQ value."); - } - - digit = base64.decode(aStr.charCodeAt(aIndex++)); - if (digit === -1) { - throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1)); - } - - continuation = !!(digit & VLQ_CONTINUATION_BIT); - digit &= VLQ_BASE_MASK; - result = result + (digit << shift); - shift += VLQ_BASE_SHIFT; - } while (continuation); - - aOutParam.value = fromVLQSigned(result); - aOutParam.rest = aIndex; -}; - - -/***/ }), -/* 665 */ -/***/ (function(module, exports) { - -/* -*- Mode: js; js-indent-level: 2; -*- */ -/* - * Copyright 2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''); - -/** - * Encode an integer in the range of 0 to 63 to a single base 64 digit. - */ -exports.encode = function (number) { - if (0 <= number && number < intToCharMap.length) { - return intToCharMap[number]; - } - throw new TypeError("Must be between 0 and 63: " + number); -}; - -/** - * Decode a single base 64 character code digit to an integer. Returns -1 on - * failure. - */ -exports.decode = function (charCode) { - var bigA = 65; // 'A' - var bigZ = 90; // 'Z' - - var littleA = 97; // 'a' - var littleZ = 122; // 'z' - - var zero = 48; // '0' - var nine = 57; // '9' - - var plus = 43; // '+' - var slash = 47; // '/' - - var littleOffset = 26; - var numberOffset = 52; - - // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ - if (bigA <= charCode && charCode <= bigZ) { - return (charCode - bigA); - } - - // 26 - 51: abcdefghijklmnopqrstuvwxyz - if (littleA <= charCode && charCode <= littleZ) { - return (charCode - littleA + littleOffset); - } - - // 52 - 61: 0123456789 - if (zero <= charCode && charCode <= nine) { - return (charCode - zero + numberOffset); - } - - // 62: + - if (charCode == plus) { - return 62; - } - - // 63: / - if (charCode == slash) { - return 63; - } - - // Invalid base64 digit. - return -1; -}; - - -/***/ }), -/* 666 */ -/***/ (function(module, exports) { - -/* -*- Mode: js; js-indent-level: 2; -*- */ -/* - * Copyright 2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -/** - * This is a helper function for getting values from parameter/options - * objects. - * - * @param args The object we are extracting values from - * @param name The name of the property we are getting. - * @param defaultValue An optional value to return if the property is missing - * from the object. If this is not specified and the property is missing, an - * error will be thrown. - */ -function getArg(aArgs, aName, aDefaultValue) { - if (aName in aArgs) { - return aArgs[aName]; - } else if (arguments.length === 3) { - return aDefaultValue; - } else { - throw new Error('"' + aName + '" is a required argument.'); - } -} -exports.getArg = getArg; - -var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/; -var dataUrlRegexp = /^data:.+\,.+$/; - -function urlParse(aUrl) { - var match = aUrl.match(urlRegexp); - if (!match) { - return null; - } - return { - scheme: match[1], - auth: match[2], - host: match[3], - port: match[4], - path: match[5] - }; -} -exports.urlParse = urlParse; - -function urlGenerate(aParsedUrl) { - var url = ''; - if (aParsedUrl.scheme) { - url += aParsedUrl.scheme + ':'; - } - url += '//'; - if (aParsedUrl.auth) { - url += aParsedUrl.auth + '@'; - } - if (aParsedUrl.host) { - url += aParsedUrl.host; - } - if (aParsedUrl.port) { - url += ":" + aParsedUrl.port - } - if (aParsedUrl.path) { - url += aParsedUrl.path; - } - return url; -} -exports.urlGenerate = urlGenerate; - -/** - * Normalizes a path, or the path portion of a URL: - * - * - Replaces consecutive slashes with one slash. - * - Removes unnecessary '.' parts. - * - Removes unnecessary '/..' parts. - * - * Based on code in the Node.js 'path' core module. - * - * @param aPath The path or url to normalize. - */ -function normalize(aPath) { - var path = aPath; - var url = urlParse(aPath); - if (url) { - if (!url.path) { - return aPath; - } - path = url.path; - } - var isAbsolute = exports.isAbsolute(path); - - var parts = path.split(/\/+/); - for (var part, up = 0, i = parts.length - 1; i >= 0; i--) { - part = parts[i]; - if (part === '.') { - parts.splice(i, 1); - } else if (part === '..') { - up++; - } else if (up > 0) { - if (part === '') { - // The first part is blank if the path is absolute. Trying to go - // above the root is a no-op. Therefore we can remove all '..' parts - // directly after the root. - parts.splice(i + 1, up); - up = 0; - } else { - parts.splice(i, 2); - up--; - } - } - } - path = parts.join('/'); - - if (path === '') { - path = isAbsolute ? '/' : '.'; - } - - if (url) { - url.path = path; - return urlGenerate(url); - } - return path; -} -exports.normalize = normalize; - -/** - * Joins two paths/URLs. - * - * @param aRoot The root path or URL. - * @param aPath The path or URL to be joined with the root. - * - * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a - * scheme-relative URL: Then the scheme of aRoot, if any, is prepended - * first. - * - Otherwise aPath is a path. If aRoot is a URL, then its path portion - * is updated with the result and aRoot is returned. Otherwise the result - * is returned. - * - If aPath is absolute, the result is aPath. - * - Otherwise the two paths are joined with a slash. - * - Joining for example 'http://' and 'www.example.com' is also supported. - */ -function join(aRoot, aPath) { - if (aRoot === "") { - aRoot = "."; - } - if (aPath === "") { - aPath = "."; - } - var aPathUrl = urlParse(aPath); - var aRootUrl = urlParse(aRoot); - if (aRootUrl) { - aRoot = aRootUrl.path || '/'; - } - - // `join(foo, '//www.example.org')` - if (aPathUrl && !aPathUrl.scheme) { - if (aRootUrl) { - aPathUrl.scheme = aRootUrl.scheme; - } - return urlGenerate(aPathUrl); - } - - if (aPathUrl || aPath.match(dataUrlRegexp)) { - return aPath; - } - - // `join('http://', 'www.example.com')` - if (aRootUrl && !aRootUrl.host && !aRootUrl.path) { - aRootUrl.host = aPath; - return urlGenerate(aRootUrl); - } - - var joined = aPath.charAt(0) === '/' - ? aPath - : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath); - - if (aRootUrl) { - aRootUrl.path = joined; - return urlGenerate(aRootUrl); - } - return joined; -} -exports.join = join; - -exports.isAbsolute = function (aPath) { - return aPath.charAt(0) === '/' || !!aPath.match(urlRegexp); -}; - -/** - * Make a path relative to a URL or another path. - * - * @param aRoot The root path or URL. - * @param aPath The path or URL to be made relative to aRoot. - */ -function relative(aRoot, aPath) { - if (aRoot === "") { - aRoot = "."; - } - - aRoot = aRoot.replace(/\/$/, ''); - - // It is possible for the path to be above the root. In this case, simply - // checking whether the root is a prefix of the path won't work. Instead, we - // need to remove components from the root one by one, until either we find - // a prefix that fits, or we run out of components to remove. - var level = 0; - while (aPath.indexOf(aRoot + '/') !== 0) { - var index = aRoot.lastIndexOf("/"); - if (index < 0) { - return aPath; - } - - // If the only part of the root that is left is the scheme (i.e. http://, - // file:///, etc.), one or more slashes (/), or simply nothing at all, we - // have exhausted all components, so the path is not relative to the root. - aRoot = aRoot.slice(0, index); - if (aRoot.match(/^([^\/]+:\/)?\/*$/)) { - return aPath; - } - - ++level; - } - - // Make sure we add a "../" for each component we removed from the root. - return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1); -} -exports.relative = relative; - -var supportsNullProto = (function () { - var obj = Object.create(null); - return !('__proto__' in obj); -}()); - -function identity (s) { - return s; -} - -/** - * Because behavior goes wacky when you set `__proto__` on objects, we - * have to prefix all the strings in our set with an arbitrary character. - * - * See https://github.com/mozilla/source-map/pull/31 and - * https://github.com/mozilla/source-map/issues/30 - * - * @param String aStr - */ -function toSetString(aStr) { - if (isProtoString(aStr)) { - return '$' + aStr; - } - - return aStr; -} -exports.toSetString = supportsNullProto ? identity : toSetString; - -function fromSetString(aStr) { - if (isProtoString(aStr)) { - return aStr.slice(1); - } - - return aStr; -} -exports.fromSetString = supportsNullProto ? identity : fromSetString; - -function isProtoString(s) { - if (!s) { - return false; - } - - var length = s.length; - - if (length < 9 /* "__proto__".length */) { - return false; - } - - if (s.charCodeAt(length - 1) !== 95 /* '_' */ || - s.charCodeAt(length - 2) !== 95 /* '_' */ || - s.charCodeAt(length - 3) !== 111 /* 'o' */ || - s.charCodeAt(length - 4) !== 116 /* 't' */ || - s.charCodeAt(length - 5) !== 111 /* 'o' */ || - s.charCodeAt(length - 6) !== 114 /* 'r' */ || - s.charCodeAt(length - 7) !== 112 /* 'p' */ || - s.charCodeAt(length - 8) !== 95 /* '_' */ || - s.charCodeAt(length - 9) !== 95 /* '_' */) { - return false; - } - - for (var i = length - 10; i >= 0; i--) { - if (s.charCodeAt(i) !== 36 /* '$' */) { - return false; - } - } - - return true; -} - -/** - * Comparator between two mappings where the original positions are compared. - * - * Optionally pass in `true` as `onlyCompareGenerated` to consider two - * mappings with the same original source/line/column, but different generated - * line and column the same. Useful when searching for a mapping with a - * stubbed out mapping. - */ -function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) { - var cmp = mappingA.source - mappingB.source; - if (cmp !== 0) { - return cmp; - } - - cmp = mappingA.originalLine - mappingB.originalLine; - if (cmp !== 0) { - return cmp; - } - - cmp = mappingA.originalColumn - mappingB.originalColumn; - if (cmp !== 0 || onlyCompareOriginal) { - return cmp; - } - - cmp = mappingA.generatedColumn - mappingB.generatedColumn; - if (cmp !== 0) { - return cmp; - } - - cmp = mappingA.generatedLine - mappingB.generatedLine; - if (cmp !== 0) { - return cmp; - } - - return mappingA.name - mappingB.name; -} -exports.compareByOriginalPositions = compareByOriginalPositions; - -/** - * Comparator between two mappings with deflated source and name indices where - * the generated positions are compared. - * - * Optionally pass in `true` as `onlyCompareGenerated` to consider two - * mappings with the same generated line and column, but different - * source/name/original line and column the same. Useful when searching for a - * mapping with a stubbed out mapping. - */ -function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) { - var cmp = mappingA.generatedLine - mappingB.generatedLine; - if (cmp !== 0) { - return cmp; - } - - cmp = mappingA.generatedColumn - mappingB.generatedColumn; - if (cmp !== 0 || onlyCompareGenerated) { - return cmp; - } - - cmp = mappingA.source - mappingB.source; - if (cmp !== 0) { - return cmp; - } - - cmp = mappingA.originalLine - mappingB.originalLine; - if (cmp !== 0) { - return cmp; - } - - cmp = mappingA.originalColumn - mappingB.originalColumn; - if (cmp !== 0) { - return cmp; - } - - return mappingA.name - mappingB.name; -} -exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated; - -function strcmp(aStr1, aStr2) { - if (aStr1 === aStr2) { - return 0; - } - - if (aStr1 > aStr2) { - return 1; - } - - return -1; -} - -/** - * Comparator between two mappings with inflated source and name strings where - * the generated positions are compared. - */ -function compareByGeneratedPositionsInflated(mappingA, mappingB) { - var cmp = mappingA.generatedLine - mappingB.generatedLine; - if (cmp !== 0) { - return cmp; - } - - cmp = mappingA.generatedColumn - mappingB.generatedColumn; - if (cmp !== 0) { - return cmp; - } - - cmp = strcmp(mappingA.source, mappingB.source); - if (cmp !== 0) { - return cmp; - } - - cmp = mappingA.originalLine - mappingB.originalLine; - if (cmp !== 0) { - return cmp; - } - - cmp = mappingA.originalColumn - mappingB.originalColumn; - if (cmp !== 0) { - return cmp; - } - - return strcmp(mappingA.name, mappingB.name); -} -exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated; - - -/***/ }), -/* 667 */ -/***/ (function(module, exports, __webpack_require__) { - -/* -*- Mode: js; js-indent-level: 2; -*- */ -/* - * Copyright 2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -var util = __webpack_require__(666); -var has = Object.prototype.hasOwnProperty; -var hasNativeMap = typeof Map !== "undefined"; - -/** - * A data structure which is a combination of an array and a set. Adding a new - * member is O(1), testing for membership is O(1), and finding the index of an - * element is O(1). Removing elements from the set is not supported. Only - * strings are supported for membership. - */ -function ArraySet() { - this._array = []; - this._set = hasNativeMap ? new Map() : Object.create(null); -} - -/** - * Static method for creating ArraySet instances from an existing array. - */ -ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) { - var set = new ArraySet(); - for (var i = 0, len = aArray.length; i < len; i++) { - set.add(aArray[i], aAllowDuplicates); - } - return set; -}; - -/** - * Return how many unique items are in this ArraySet. If duplicates have been - * added, than those do not count towards the size. - * - * @returns Number - */ -ArraySet.prototype.size = function ArraySet_size() { - return hasNativeMap ? this._set.size : Object.getOwnPropertyNames(this._set).length; -}; - -/** - * Add the given string to this set. - * - * @param String aStr - */ -ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) { - var sStr = hasNativeMap ? aStr : util.toSetString(aStr); - var isDuplicate = hasNativeMap ? this.has(aStr) : has.call(this._set, sStr); - var idx = this._array.length; - if (!isDuplicate || aAllowDuplicates) { - this._array.push(aStr); - } - if (!isDuplicate) { - if (hasNativeMap) { - this._set.set(aStr, idx); - } else { - this._set[sStr] = idx; - } - } -}; - -/** - * Is the given string a member of this set? - * - * @param String aStr - */ -ArraySet.prototype.has = function ArraySet_has(aStr) { - if (hasNativeMap) { - return this._set.has(aStr); - } else { - var sStr = util.toSetString(aStr); - return has.call(this._set, sStr); - } -}; - -/** - * What is the index of the given string in the array? - * - * @param String aStr - */ -ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) { - if (hasNativeMap) { - var idx = this._set.get(aStr); - if (idx >= 0) { - return idx; - } - } else { - var sStr = util.toSetString(aStr); - if (has.call(this._set, sStr)) { - return this._set[sStr]; - } - } - - throw new Error('"' + aStr + '" is not in the set.'); -}; - -/** - * What is the element at the given index? - * - * @param Number aIdx - */ -ArraySet.prototype.at = function ArraySet_at(aIdx) { - if (aIdx >= 0 && aIdx < this._array.length) { - return this._array[aIdx]; - } - throw new Error('No element indexed by ' + aIdx); -}; - -/** - * Returns the array representation of this set (which has the proper indices - * indicated by indexOf). Note that this is a copy of the internal array used - * for storing the members so that no one can mess with internal state. - */ -ArraySet.prototype.toArray = function ArraySet_toArray() { - return this._array.slice(); -}; - -exports.ArraySet = ArraySet; - - -/***/ }), -/* 668 */ -/***/ (function(module, exports, __webpack_require__) { - -/* -*- Mode: js; js-indent-level: 2; -*- */ -/* - * Copyright 2014 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -var util = __webpack_require__(666); - -/** - * Determine whether mappingB is after mappingA with respect to generated - * position. - */ -function generatedPositionAfter(mappingA, mappingB) { - // Optimized for most common case - var lineA = mappingA.generatedLine; - var lineB = mappingB.generatedLine; - var columnA = mappingA.generatedColumn; - var columnB = mappingB.generatedColumn; - return lineB > lineA || lineB == lineA && columnB >= columnA || - util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0; -} - -/** - * A data structure to provide a sorted view of accumulated mappings in a - * performance conscious manner. It trades a neglibable overhead in general - * case for a large speedup in case of mappings being added in order. - */ -function MappingList() { - this._array = []; - this._sorted = true; - // Serves as infimum - this._last = {generatedLine: -1, generatedColumn: 0}; -} - -/** - * Iterate through internal items. This method takes the same arguments that - * `Array.prototype.forEach` takes. - * - * NOTE: The order of the mappings is NOT guaranteed. - */ -MappingList.prototype.unsortedForEach = - function MappingList_forEach(aCallback, aThisArg) { - this._array.forEach(aCallback, aThisArg); - }; - -/** - * Add the given source mapping. - * - * @param Object aMapping - */ -MappingList.prototype.add = function MappingList_add(aMapping) { - if (generatedPositionAfter(this._last, aMapping)) { - this._last = aMapping; - this._array.push(aMapping); - } else { - this._sorted = false; - this._array.push(aMapping); - } -}; - -/** - * Returns the flat, sorted array of mappings. The mappings are sorted by - * generated position. - * - * WARNING: This method returns internal data without copying, for - * performance. The return value must NOT be mutated, and should be treated as - * an immutable borrow. If you want to take ownership, you must make your own - * copy. - */ -MappingList.prototype.toArray = function MappingList_toArray() { - if (!this._sorted) { - this._array.sort(util.compareByGeneratedPositionsInflated); - this._sorted = true; - } - return this._array; -}; - -exports.MappingList = MappingList; - - -/***/ }), -/* 669 */ -/***/ (function(module, exports, __webpack_require__) { - -/* -*- Mode: js; js-indent-level: 2; -*- */ -/* - * Copyright 2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -var util = __webpack_require__(666); -var binarySearch = __webpack_require__(670); -var ArraySet = __webpack_require__(667).ArraySet; -var base64VLQ = __webpack_require__(664); -var quickSort = __webpack_require__(671).quickSort; - -function SourceMapConsumer(aSourceMap) { - var sourceMap = aSourceMap; - if (typeof aSourceMap === 'string') { - sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); - } - - return sourceMap.sections != null - ? new IndexedSourceMapConsumer(sourceMap) - : new BasicSourceMapConsumer(sourceMap); -} - -SourceMapConsumer.fromSourceMap = function(aSourceMap) { - return BasicSourceMapConsumer.fromSourceMap(aSourceMap); -} - -/** - * The version of the source mapping spec that we are consuming. - */ -SourceMapConsumer.prototype._version = 3; - -// `__generatedMappings` and `__originalMappings` are arrays that hold the -// parsed mapping coordinates from the source map's "mappings" attribute. They -// are lazily instantiated, accessed via the `_generatedMappings` and -// `_originalMappings` getters respectively, and we only parse the mappings -// and create these arrays once queried for a source location. We jump through -// these hoops because there can be many thousands of mappings, and parsing -// them is expensive, so we only want to do it if we must. -// -// Each object in the arrays is of the form: -// -// { -// generatedLine: The line number in the generated code, -// generatedColumn: The column number in the generated code, -// source: The path to the original source file that generated this -// chunk of code, -// originalLine: The line number in the original source that -// corresponds to this chunk of generated code, -// originalColumn: The column number in the original source that -// corresponds to this chunk of generated code, -// name: The name of the original symbol which generated this chunk of -// code. -// } -// -// All properties except for `generatedLine` and `generatedColumn` can be -// `null`. -// -// `_generatedMappings` is ordered by the generated positions. -// -// `_originalMappings` is ordered by the original positions. - -SourceMapConsumer.prototype.__generatedMappings = null; -Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', { - get: function () { - if (!this.__generatedMappings) { - this._parseMappings(this._mappings, this.sourceRoot); - } - - return this.__generatedMappings; - } -}); - -SourceMapConsumer.prototype.__originalMappings = null; -Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', { - get: function () { - if (!this.__originalMappings) { - this._parseMappings(this._mappings, this.sourceRoot); - } - - return this.__originalMappings; - } -}); - -SourceMapConsumer.prototype._charIsMappingSeparator = - function SourceMapConsumer_charIsMappingSeparator(aStr, index) { - var c = aStr.charAt(index); - return c === ";" || c === ","; - }; - -/** - * Parse the mappings in a string in to a data structure which we can easily - * query (the ordered arrays in the `this.__generatedMappings` and - * `this.__originalMappings` properties). - */ -SourceMapConsumer.prototype._parseMappings = - function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { - throw new Error("Subclasses must implement _parseMappings"); - }; - -SourceMapConsumer.GENERATED_ORDER = 1; -SourceMapConsumer.ORIGINAL_ORDER = 2; - -SourceMapConsumer.GREATEST_LOWER_BOUND = 1; -SourceMapConsumer.LEAST_UPPER_BOUND = 2; - -/** - * Iterate over each mapping between an original source/line/column and a - * generated line/column in this source map. - * - * @param Function aCallback - * The function that is called with each mapping. - * @param Object aContext - * Optional. If specified, this object will be the value of `this` every - * time that `aCallback` is called. - * @param aOrder - * Either `SourceMapConsumer.GENERATED_ORDER` or - * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to - * iterate over the mappings sorted by the generated file's line/column - * order or the original's source/line/column order, respectively. Defaults to - * `SourceMapConsumer.GENERATED_ORDER`. - */ -SourceMapConsumer.prototype.eachMapping = - function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) { - var context = aContext || null; - var order = aOrder || SourceMapConsumer.GENERATED_ORDER; - - var mappings; - switch (order) { - case SourceMapConsumer.GENERATED_ORDER: - mappings = this._generatedMappings; - break; - case SourceMapConsumer.ORIGINAL_ORDER: - mappings = this._originalMappings; - break; - default: - throw new Error("Unknown order of iteration."); - } - - var sourceRoot = this.sourceRoot; - mappings.map(function (mapping) { - var source = mapping.source === null ? null : this._sources.at(mapping.source); - if (source != null && sourceRoot != null) { - source = util.join(sourceRoot, source); - } - return { - source: source, - generatedLine: mapping.generatedLine, - generatedColumn: mapping.generatedColumn, - originalLine: mapping.originalLine, - originalColumn: mapping.originalColumn, - name: mapping.name === null ? null : this._names.at(mapping.name) - }; - }, this).forEach(aCallback, context); - }; - -/** - * Returns all generated line and column information for the original source, - * line, and column provided. If no column is provided, returns all mappings - * corresponding to a either the line we are searching for or the next - * closest line that has any mappings. Otherwise, returns all mappings - * corresponding to the given line and either the column we are searching for - * or the next closest column that has any offsets. - * - * The only argument is an object with the following properties: - * - * - source: The filename of the original source. - * - line: The line number in the original source. - * - column: Optional. the column number in the original source. - * - * and an array of objects is returned, each with the following properties: - * - * - line: The line number in the generated source, or null. - * - column: The column number in the generated source, or null. - */ -SourceMapConsumer.prototype.allGeneratedPositionsFor = - function SourceMapConsumer_allGeneratedPositionsFor(aArgs) { - var line = util.getArg(aArgs, 'line'); - - // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping - // returns the index of the closest mapping less than the needle. By - // setting needle.originalColumn to 0, we thus find the last mapping for - // the given line, provided such a mapping exists. - var needle = { - source: util.getArg(aArgs, 'source'), - originalLine: line, - originalColumn: util.getArg(aArgs, 'column', 0) - }; - - if (this.sourceRoot != null) { - needle.source = util.relative(this.sourceRoot, needle.source); - } - if (!this._sources.has(needle.source)) { - return []; - } - needle.source = this._sources.indexOf(needle.source); - - var mappings = []; - - var index = this._findMapping(needle, - this._originalMappings, - "originalLine", - "originalColumn", - util.compareByOriginalPositions, - binarySearch.LEAST_UPPER_BOUND); - if (index >= 0) { - var mapping = this._originalMappings[index]; - - if (aArgs.column === undefined) { - var originalLine = mapping.originalLine; - - // Iterate until either we run out of mappings, or we run into - // a mapping for a different line than the one we found. Since - // mappings are sorted, this is guaranteed to find all mappings for - // the line we found. - while (mapping && mapping.originalLine === originalLine) { - mappings.push({ - line: util.getArg(mapping, 'generatedLine', null), - column: util.getArg(mapping, 'generatedColumn', null), - lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) - }); - - mapping = this._originalMappings[++index]; - } - } else { - var originalColumn = mapping.originalColumn; - - // Iterate until either we run out of mappings, or we run into - // a mapping for a different line than the one we were searching for. - // Since mappings are sorted, this is guaranteed to find all mappings for - // the line we are searching for. - while (mapping && - mapping.originalLine === line && - mapping.originalColumn == originalColumn) { - mappings.push({ - line: util.getArg(mapping, 'generatedLine', null), - column: util.getArg(mapping, 'generatedColumn', null), - lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) - }); - - mapping = this._originalMappings[++index]; - } - } - } - - return mappings; - }; - -exports.SourceMapConsumer = SourceMapConsumer; - -/** - * A BasicSourceMapConsumer instance represents a parsed source map which we can - * query for information about the original file positions by giving it a file - * position in the generated source. - * - * The only parameter is the raw source map (either as a JSON string, or - * already parsed to an object). According to the spec, source maps have the - * following attributes: - * - * - version: Which version of the source map spec this map is following. - * - sources: An array of URLs to the original source files. - * - names: An array of identifiers which can be referrenced by individual mappings. - * - sourceRoot: Optional. The URL root from which all sources are relative. - * - sourcesContent: Optional. An array of contents of the original source files. - * - mappings: A string of base64 VLQs which contain the actual mappings. - * - file: Optional. The generated file this source map is associated with. - * - * Here is an example source map, taken from the source map spec[0]: - * - * { - * version : 3, - * file: "out.js", - * sourceRoot : "", - * sources: ["foo.js", "bar.js"], - * names: ["src", "maps", "are", "fun"], - * mappings: "AA,AB;;ABCDE;" - * } - * - * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1# - */ -function BasicSourceMapConsumer(aSourceMap) { - var sourceMap = aSourceMap; - if (typeof aSourceMap === 'string') { - sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); - } - - var version = util.getArg(sourceMap, 'version'); - var sources = util.getArg(sourceMap, 'sources'); - // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which - // requires the array) to play nice here. - var names = util.getArg(sourceMap, 'names', []); - var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null); - var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null); - var mappings = util.getArg(sourceMap, 'mappings'); - var file = util.getArg(sourceMap, 'file', null); - - // Once again, Sass deviates from the spec and supplies the version as a - // string rather than a number, so we use loose equality checking here. - if (version != this._version) { - throw new Error('Unsupported version: ' + version); - } - - sources = sources - .map(String) - // Some source maps produce relative source paths like "./foo.js" instead of - // "foo.js". Normalize these first so that future comparisons will succeed. - // See bugzil.la/1090768. - .map(util.normalize) - // Always ensure that absolute sources are internally stored relative to - // the source root, if the source root is absolute. Not doing this would - // be particularly problematic when the source root is a prefix of the - // source (valid, but why??). See github issue #199 and bugzil.la/1188982. - .map(function (source) { - return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source) - ? util.relative(sourceRoot, source) - : source; - }); - - // Pass `true` below to allow duplicate names and sources. While source maps - // are intended to be compressed and deduplicated, the TypeScript compiler - // sometimes generates source maps with duplicates in them. See Github issue - // #72 and bugzil.la/889492. - this._names = ArraySet.fromArray(names.map(String), true); - this._sources = ArraySet.fromArray(sources, true); - - this.sourceRoot = sourceRoot; - this.sourcesContent = sourcesContent; - this._mappings = mappings; - this.file = file; -} - -BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); -BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer; - -/** - * Create a BasicSourceMapConsumer from a SourceMapGenerator. - * - * @param SourceMapGenerator aSourceMap - * The source map that will be consumed. - * @returns BasicSourceMapConsumer - */ -BasicSourceMapConsumer.fromSourceMap = - function SourceMapConsumer_fromSourceMap(aSourceMap) { - var smc = Object.create(BasicSourceMapConsumer.prototype); - - var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true); - var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true); - smc.sourceRoot = aSourceMap._sourceRoot; - smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(), - smc.sourceRoot); - smc.file = aSourceMap._file; - - // Because we are modifying the entries (by converting string sources and - // names to indices into the sources and names ArraySets), we have to make - // a copy of the entry or else bad things happen. Shared mutable state - // strikes again! See github issue #191. - - var generatedMappings = aSourceMap._mappings.toArray().slice(); - var destGeneratedMappings = smc.__generatedMappings = []; - var destOriginalMappings = smc.__originalMappings = []; - - for (var i = 0, length = generatedMappings.length; i < length; i++) { - var srcMapping = generatedMappings[i]; - var destMapping = new Mapping; - destMapping.generatedLine = srcMapping.generatedLine; - destMapping.generatedColumn = srcMapping.generatedColumn; - - if (srcMapping.source) { - destMapping.source = sources.indexOf(srcMapping.source); - destMapping.originalLine = srcMapping.originalLine; - destMapping.originalColumn = srcMapping.originalColumn; - - if (srcMapping.name) { - destMapping.name = names.indexOf(srcMapping.name); - } - - destOriginalMappings.push(destMapping); - } - - destGeneratedMappings.push(destMapping); - } - - quickSort(smc.__originalMappings, util.compareByOriginalPositions); - - return smc; - }; - -/** - * The version of the source mapping spec that we are consuming. - */ -BasicSourceMapConsumer.prototype._version = 3; - -/** - * The list of original sources. - */ -Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', { - get: function () { - return this._sources.toArray().map(function (s) { - return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s; - }, this); - } -}); - -/** - * Provide the JIT with a nice shape / hidden class. - */ -function Mapping() { - this.generatedLine = 0; - this.generatedColumn = 0; - this.source = null; - this.originalLine = null; - this.originalColumn = null; - this.name = null; -} - -/** - * Parse the mappings in a string in to a data structure which we can easily - * query (the ordered arrays in the `this.__generatedMappings` and - * `this.__originalMappings` properties). - */ -BasicSourceMapConsumer.prototype._parseMappings = - function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { - var generatedLine = 1; - var previousGeneratedColumn = 0; - var previousOriginalLine = 0; - var previousOriginalColumn = 0; - var previousSource = 0; - var previousName = 0; - var length = aStr.length; - var index = 0; - var cachedSegments = {}; - var temp = {}; - var originalMappings = []; - var generatedMappings = []; - var mapping, str, segment, end, value; - - while (index < length) { - if (aStr.charAt(index) === ';') { - generatedLine++; - index++; - previousGeneratedColumn = 0; - } - else if (aStr.charAt(index) === ',') { - index++; - } - else { - mapping = new Mapping(); - mapping.generatedLine = generatedLine; - - // Because each offset is encoded relative to the previous one, - // many segments often have the same encoding. We can exploit this - // fact by caching the parsed variable length fields of each segment, - // allowing us to avoid a second parse if we encounter the same - // segment again. - for (end = index; end < length; end++) { - if (this._charIsMappingSeparator(aStr, end)) { - break; - } - } - str = aStr.slice(index, end); - - segment = cachedSegments[str]; - if (segment) { - index += str.length; - } else { - segment = []; - while (index < end) { - base64VLQ.decode(aStr, index, temp); - value = temp.value; - index = temp.rest; - segment.push(value); - } - - if (segment.length === 2) { - throw new Error('Found a source, but no line and column'); - } - - if (segment.length === 3) { - throw new Error('Found a source and line, but no column'); - } - - cachedSegments[str] = segment; - } - - // Generated column. - mapping.generatedColumn = previousGeneratedColumn + segment[0]; - previousGeneratedColumn = mapping.generatedColumn; - - if (segment.length > 1) { - // Original source. - mapping.source = previousSource + segment[1]; - previousSource += segment[1]; - - // Original line. - mapping.originalLine = previousOriginalLine + segment[2]; - previousOriginalLine = mapping.originalLine; - // Lines are stored 0-based - mapping.originalLine += 1; - - // Original column. - mapping.originalColumn = previousOriginalColumn + segment[3]; - previousOriginalColumn = mapping.originalColumn; - - if (segment.length > 4) { - // Original name. - mapping.name = previousName + segment[4]; - previousName += segment[4]; - } - } - - generatedMappings.push(mapping); - if (typeof mapping.originalLine === 'number') { - originalMappings.push(mapping); - } - } - } - - quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated); - this.__generatedMappings = generatedMappings; - - quickSort(originalMappings, util.compareByOriginalPositions); - this.__originalMappings = originalMappings; - }; - -/** - * Find the mapping that best matches the hypothetical "needle" mapping that - * we are searching for in the given "haystack" of mappings. - */ -BasicSourceMapConsumer.prototype._findMapping = - function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName, - aColumnName, aComparator, aBias) { - // To return the position we are searching for, we must first find the - // mapping for the given position and then return the opposite position it - // points to. Because the mappings are sorted, we can use binary search to - // find the best mapping. - - if (aNeedle[aLineName] <= 0) { - throw new TypeError('Line must be greater than or equal to 1, got ' - + aNeedle[aLineName]); - } - if (aNeedle[aColumnName] < 0) { - throw new TypeError('Column must be greater than or equal to 0, got ' - + aNeedle[aColumnName]); - } - - return binarySearch.search(aNeedle, aMappings, aComparator, aBias); - }; - -/** - * Compute the last column for each generated mapping. The last column is - * inclusive. - */ -BasicSourceMapConsumer.prototype.computeColumnSpans = - function SourceMapConsumer_computeColumnSpans() { - for (var index = 0; index < this._generatedMappings.length; ++index) { - var mapping = this._generatedMappings[index]; - - // Mappings do not contain a field for the last generated columnt. We - // can come up with an optimistic estimate, however, by assuming that - // mappings are contiguous (i.e. given two consecutive mappings, the - // first mapping ends where the second one starts). - if (index + 1 < this._generatedMappings.length) { - var nextMapping = this._generatedMappings[index + 1]; - - if (mapping.generatedLine === nextMapping.generatedLine) { - mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1; - continue; - } - } - - // The last mapping for each line spans the entire line. - mapping.lastGeneratedColumn = Infinity; - } - }; - -/** - * Returns the original source, line, and column information for the generated - * source's line and column positions provided. The only argument is an object - * with the following properties: - * - * - line: The line number in the generated source. - * - column: The column number in the generated source. - * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or - * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the - * closest element that is smaller than or greater than the one we are - * searching for, respectively, if the exact element cannot be found. - * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. - * - * and an object is returned with the following properties: - * - * - source: The original source file, or null. - * - line: The line number in the original source, or null. - * - column: The column number in the original source, or null. - * - name: The original identifier, or null. - */ -BasicSourceMapConsumer.prototype.originalPositionFor = - function SourceMapConsumer_originalPositionFor(aArgs) { - var needle = { - generatedLine: util.getArg(aArgs, 'line'), - generatedColumn: util.getArg(aArgs, 'column') - }; - - var index = this._findMapping( - needle, - this._generatedMappings, - "generatedLine", - "generatedColumn", - util.compareByGeneratedPositionsDeflated, - util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) - ); - - if (index >= 0) { - var mapping = this._generatedMappings[index]; - - if (mapping.generatedLine === needle.generatedLine) { - var source = util.getArg(mapping, 'source', null); - if (source !== null) { - source = this._sources.at(source); - if (this.sourceRoot != null) { - source = util.join(this.sourceRoot, source); - } - } - var name = util.getArg(mapping, 'name', null); - if (name !== null) { - name = this._names.at(name); - } - return { - source: source, - line: util.getArg(mapping, 'originalLine', null), - column: util.getArg(mapping, 'originalColumn', null), - name: name - }; - } - } - - return { - source: null, - line: null, - column: null, - name: null - }; - }; - -/** - * Return true if we have the source content for every source in the source - * map, false otherwise. - */ -BasicSourceMapConsumer.prototype.hasContentsOfAllSources = - function BasicSourceMapConsumer_hasContentsOfAllSources() { - if (!this.sourcesContent) { - return false; - } - return this.sourcesContent.length >= this._sources.size() && - !this.sourcesContent.some(function (sc) { return sc == null; }); - }; - -/** - * Returns the original source content. The only argument is the url of the - * original source file. Returns null if no original source content is - * available. - */ -BasicSourceMapConsumer.prototype.sourceContentFor = - function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { - if (!this.sourcesContent) { - return null; - } - - if (this.sourceRoot != null) { - aSource = util.relative(this.sourceRoot, aSource); - } - - if (this._sources.has(aSource)) { - return this.sourcesContent[this._sources.indexOf(aSource)]; - } - - var url; - if (this.sourceRoot != null - && (url = util.urlParse(this.sourceRoot))) { - // XXX: file:// URIs and absolute paths lead to unexpected behavior for - // many users. We can help them out when they expect file:// URIs to - // behave like it would if they were running a local HTTP server. See - // https://bugzilla.mozilla.org/show_bug.cgi?id=885597. - var fileUriAbsPath = aSource.replace(/^file:\/\//, ""); - if (url.scheme == "file" - && this._sources.has(fileUriAbsPath)) { - return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)] - } - - if ((!url.path || url.path == "/") - && this._sources.has("/" + aSource)) { - return this.sourcesContent[this._sources.indexOf("/" + aSource)]; - } - } - - // This function is used recursively from - // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we - // don't want to throw if we can't find the source - we just want to - // return null, so we provide a flag to exit gracefully. - if (nullOnMissing) { - return null; - } - else { - throw new Error('"' + aSource + '" is not in the SourceMap.'); - } - }; - -/** - * Returns the generated line and column information for the original source, - * line, and column positions provided. The only argument is an object with - * the following properties: - * - * - source: The filename of the original source. - * - line: The line number in the original source. - * - column: The column number in the original source. - * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or - * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the - * closest element that is smaller than or greater than the one we are - * searching for, respectively, if the exact element cannot be found. - * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. - * - * and an object is returned with the following properties: - * - * - line: The line number in the generated source, or null. - * - column: The column number in the generated source, or null. - */ -BasicSourceMapConsumer.prototype.generatedPositionFor = - function SourceMapConsumer_generatedPositionFor(aArgs) { - var source = util.getArg(aArgs, 'source'); - if (this.sourceRoot != null) { - source = util.relative(this.sourceRoot, source); - } - if (!this._sources.has(source)) { - return { - line: null, - column: null, - lastColumn: null - }; - } - source = this._sources.indexOf(source); - - var needle = { - source: source, - originalLine: util.getArg(aArgs, 'line'), - originalColumn: util.getArg(aArgs, 'column') - }; - - var index = this._findMapping( - needle, - this._originalMappings, - "originalLine", - "originalColumn", - util.compareByOriginalPositions, - util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) - ); - - if (index >= 0) { - var mapping = this._originalMappings[index]; - - if (mapping.source === needle.source) { - return { - line: util.getArg(mapping, 'generatedLine', null), - column: util.getArg(mapping, 'generatedColumn', null), - lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) - }; - } - } - - return { - line: null, - column: null, - lastColumn: null - }; - }; - -exports.BasicSourceMapConsumer = BasicSourceMapConsumer; - -/** - * An IndexedSourceMapConsumer instance represents a parsed source map which - * we can query for information. It differs from BasicSourceMapConsumer in - * that it takes "indexed" source maps (i.e. ones with a "sections" field) as - * input. - * - * The only parameter is a raw source map (either as a JSON string, or already - * parsed to an object). According to the spec for indexed source maps, they - * have the following attributes: - * - * - version: Which version of the source map spec this map is following. - * - file: Optional. The generated file this source map is associated with. - * - sections: A list of section definitions. - * - * Each value under the "sections" field has two fields: - * - offset: The offset into the original specified at which this section - * begins to apply, defined as an object with a "line" and "column" - * field. - * - map: A source map definition. This source map could also be indexed, - * but doesn't have to be. - * - * Instead of the "map" field, it's also possible to have a "url" field - * specifying a URL to retrieve a source map from, but that's currently - * unsupported. - * - * Here's an example source map, taken from the source map spec[0], but - * modified to omit a section which uses the "url" field. - * - * { - * version : 3, - * file: "app.js", - * sections: [{ - * offset: {line:100, column:10}, - * map: { - * version : 3, - * file: "section.js", - * sources: ["foo.js", "bar.js"], - * names: ["src", "maps", "are", "fun"], - * mappings: "AAAA,E;;ABCDE;" - * } - * }], - * } - * - * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt - */ -function IndexedSourceMapConsumer(aSourceMap) { - var sourceMap = aSourceMap; - if (typeof aSourceMap === 'string') { - sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); - } - - var version = util.getArg(sourceMap, 'version'); - var sections = util.getArg(sourceMap, 'sections'); - - if (version != this._version) { - throw new Error('Unsupported version: ' + version); - } - - this._sources = new ArraySet(); - this._names = new ArraySet(); - - var lastOffset = { - line: -1, - column: 0 - }; - this._sections = sections.map(function (s) { - if (s.url) { - // The url field will require support for asynchronicity. - // See https://github.com/mozilla/source-map/issues/16 - throw new Error('Support for url field in sections not implemented.'); - } - var offset = util.getArg(s, 'offset'); - var offsetLine = util.getArg(offset, 'line'); - var offsetColumn = util.getArg(offset, 'column'); - - if (offsetLine < lastOffset.line || - (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) { - throw new Error('Section offsets must be ordered and non-overlapping.'); - } - lastOffset = offset; - - return { - generatedOffset: { - // The offset fields are 0-based, but we use 1-based indices when - // encoding/decoding from VLQ. - generatedLine: offsetLine + 1, - generatedColumn: offsetColumn + 1 - }, - consumer: new SourceMapConsumer(util.getArg(s, 'map')) - } - }); -} - -IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); -IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer; - -/** - * The version of the source mapping spec that we are consuming. - */ -IndexedSourceMapConsumer.prototype._version = 3; - -/** - * The list of original sources. - */ -Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', { - get: function () { - var sources = []; - for (var i = 0; i < this._sections.length; i++) { - for (var j = 0; j < this._sections[i].consumer.sources.length; j++) { - sources.push(this._sections[i].consumer.sources[j]); - } - } - return sources; - } -}); - -/** - * Returns the original source, line, and column information for the generated - * source's line and column positions provided. The only argument is an object - * with the following properties: - * - * - line: The line number in the generated source. - * - column: The column number in the generated source. - * - * and an object is returned with the following properties: - * - * - source: The original source file, or null. - * - line: The line number in the original source, or null. - * - column: The column number in the original source, or null. - * - name: The original identifier, or null. - */ -IndexedSourceMapConsumer.prototype.originalPositionFor = - function IndexedSourceMapConsumer_originalPositionFor(aArgs) { - var needle = { - generatedLine: util.getArg(aArgs, 'line'), - generatedColumn: util.getArg(aArgs, 'column') - }; - - // Find the section containing the generated position we're trying to map - // to an original position. - var sectionIndex = binarySearch.search(needle, this._sections, - function(needle, section) { - var cmp = needle.generatedLine - section.generatedOffset.generatedLine; - if (cmp) { - return cmp; - } - - return (needle.generatedColumn - - section.generatedOffset.generatedColumn); - }); - var section = this._sections[sectionIndex]; - - if (!section) { - return { - source: null, - line: null, - column: null, - name: null - }; - } - - return section.consumer.originalPositionFor({ - line: needle.generatedLine - - (section.generatedOffset.generatedLine - 1), - column: needle.generatedColumn - - (section.generatedOffset.generatedLine === needle.generatedLine - ? section.generatedOffset.generatedColumn - 1 - : 0), - bias: aArgs.bias - }); - }; - -/** - * Return true if we have the source content for every source in the source - * map, false otherwise. - */ -IndexedSourceMapConsumer.prototype.hasContentsOfAllSources = - function IndexedSourceMapConsumer_hasContentsOfAllSources() { - return this._sections.every(function (s) { - return s.consumer.hasContentsOfAllSources(); - }); - }; - -/** - * Returns the original source content. The only argument is the url of the - * original source file. Returns null if no original source content is - * available. - */ -IndexedSourceMapConsumer.prototype.sourceContentFor = - function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { - for (var i = 0; i < this._sections.length; i++) { - var section = this._sections[i]; - - var content = section.consumer.sourceContentFor(aSource, true); - if (content) { - return content; - } - } - if (nullOnMissing) { - return null; - } - else { - throw new Error('"' + aSource + '" is not in the SourceMap.'); - } - }; - -/** - * Returns the generated line and column information for the original source, - * line, and column positions provided. The only argument is an object with - * the following properties: - * - * - source: The filename of the original source. - * - line: The line number in the original source. - * - column: The column number in the original source. - * - * and an object is returned with the following properties: - * - * - line: The line number in the generated source, or null. - * - column: The column number in the generated source, or null. - */ -IndexedSourceMapConsumer.prototype.generatedPositionFor = - function IndexedSourceMapConsumer_generatedPositionFor(aArgs) { - for (var i = 0; i < this._sections.length; i++) { - var section = this._sections[i]; - - // Only consider this section if the requested source is in the list of - // sources of the consumer. - if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) { - continue; - } - var generatedPosition = section.consumer.generatedPositionFor(aArgs); - if (generatedPosition) { - var ret = { - line: generatedPosition.line + - (section.generatedOffset.generatedLine - 1), - column: generatedPosition.column + - (section.generatedOffset.generatedLine === generatedPosition.line - ? section.generatedOffset.generatedColumn - 1 - : 0) - }; - return ret; - } - } - - return { - line: null, - column: null - }; - }; - -/** - * Parse the mappings in a string in to a data structure which we can easily - * query (the ordered arrays in the `this.__generatedMappings` and - * `this.__originalMappings` properties). - */ -IndexedSourceMapConsumer.prototype._parseMappings = - function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) { - this.__generatedMappings = []; - this.__originalMappings = []; - for (var i = 0; i < this._sections.length; i++) { - var section = this._sections[i]; - var sectionMappings = section.consumer._generatedMappings; - for (var j = 0; j < sectionMappings.length; j++) { - var mapping = sectionMappings[j]; - - var source = section.consumer._sources.at(mapping.source); - if (section.consumer.sourceRoot !== null) { - source = util.join(section.consumer.sourceRoot, source); - } - this._sources.add(source); - source = this._sources.indexOf(source); - - var name = section.consumer._names.at(mapping.name); - this._names.add(name); - name = this._names.indexOf(name); - - // The mappings coming from the consumer for the section have - // generated positions relative to the start of the section, so we - // need to offset them to be relative to the start of the concatenated - // generated file. - var adjustedMapping = { - source: source, - generatedLine: mapping.generatedLine + - (section.generatedOffset.generatedLine - 1), - generatedColumn: mapping.generatedColumn + - (section.generatedOffset.generatedLine === mapping.generatedLine - ? section.generatedOffset.generatedColumn - 1 - : 0), - originalLine: mapping.originalLine, - originalColumn: mapping.originalColumn, - name: name - }; - - this.__generatedMappings.push(adjustedMapping); - if (typeof adjustedMapping.originalLine === 'number') { - this.__originalMappings.push(adjustedMapping); - } - } - } - - quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated); - quickSort(this.__originalMappings, util.compareByOriginalPositions); - }; - -exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; - - -/***/ }), -/* 670 */ -/***/ (function(module, exports) { - -/* -*- Mode: js; js-indent-level: 2; -*- */ -/* - * Copyright 2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -exports.GREATEST_LOWER_BOUND = 1; -exports.LEAST_UPPER_BOUND = 2; - -/** - * Recursive implementation of binary search. - * - * @param aLow Indices here and lower do not contain the needle. - * @param aHigh Indices here and higher do not contain the needle. - * @param aNeedle The element being searched for. - * @param aHaystack The non-empty array being searched. - * @param aCompare Function which takes two elements and returns -1, 0, or 1. - * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or - * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the - * closest element that is smaller than or greater than the one we are - * searching for, respectively, if the exact element cannot be found. - */ -function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) { - // This function terminates when one of the following is true: - // - // 1. We find the exact element we are looking for. - // - // 2. We did not find the exact element, but we can return the index of - // the next-closest element. - // - // 3. We did not find the exact element, and there is no next-closest - // element than the one we are searching for, so we return -1. - var mid = Math.floor((aHigh - aLow) / 2) + aLow; - var cmp = aCompare(aNeedle, aHaystack[mid], true); - if (cmp === 0) { - // Found the element we are looking for. - return mid; - } - else if (cmp > 0) { - // Our needle is greater than aHaystack[mid]. - if (aHigh - mid > 1) { - // The element is in the upper half. - return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias); - } - - // The exact needle element was not found in this haystack. Determine if - // we are in termination case (3) or (2) and return the appropriate thing. - if (aBias == exports.LEAST_UPPER_BOUND) { - return aHigh < aHaystack.length ? aHigh : -1; - } else { - return mid; - } - } - else { - // Our needle is less than aHaystack[mid]. - if (mid - aLow > 1) { - // The element is in the lower half. - return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias); - } - - // we are in termination case (3) or (2) and return the appropriate thing. - if (aBias == exports.LEAST_UPPER_BOUND) { - return mid; - } else { - return aLow < 0 ? -1 : aLow; - } - } -} - -/** - * This is an implementation of binary search which will always try and return - * the index of the closest element if there is no exact hit. This is because - * mappings between original and generated line/col pairs are single points, - * and there is an implicit region between each of them, so a miss just means - * that you aren't on the very start of a region. - * - * @param aNeedle The element you are looking for. - * @param aHaystack The array that is being searched. - * @param aCompare A function which takes the needle and an element in the - * array and returns -1, 0, or 1 depending on whether the needle is less - * than, equal to, or greater than the element, respectively. - * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or - * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the - * closest element that is smaller than or greater than the one we are - * searching for, respectively, if the exact element cannot be found. - * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'. - */ -exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { - if (aHaystack.length === 0) { - return -1; - } - - var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack, - aCompare, aBias || exports.GREATEST_LOWER_BOUND); - if (index < 0) { - return -1; - } - - // We have found either the exact element, or the next-closest element than - // the one we are searching for. However, there may be more than one such - // element. Make sure we always return the smallest of these. - while (index - 1 >= 0) { - if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) { - break; - } - --index; - } - - return index; -}; - - -/***/ }), -/* 671 */ -/***/ (function(module, exports) { - -/* -*- Mode: js; js-indent-level: 2; -*- */ -/* - * Copyright 2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -// It turns out that some (most?) JavaScript engines don't self-host -// `Array.prototype.sort`. This makes sense because C++ will likely remain -// faster than JS when doing raw CPU-intensive sorting. However, when using a -// custom comparator function, calling back and forth between the VM's C++ and -// JIT'd JS is rather slow *and* loses JIT type information, resulting in -// worse generated code for the comparator function than would be optimal. In -// fact, when sorting with a comparator, these costs outweigh the benefits of -// sorting in C++. By using our own JS-implemented Quick Sort (below), we get -// a ~3500ms mean speed-up in `bench/bench.html`. - -/** - * Swap the elements indexed by `x` and `y` in the array `ary`. - * - * @param {Array} ary - * The array. - * @param {Number} x - * The index of the first item. - * @param {Number} y - * The index of the second item. - */ -function swap(ary, x, y) { - var temp = ary[x]; - ary[x] = ary[y]; - ary[y] = temp; -} - -/** - * Returns a random integer within the range `low .. high` inclusive. - * - * @param {Number} low - * The lower bound on the range. - * @param {Number} high - * The upper bound on the range. - */ -function randomIntInRange(low, high) { - return Math.round(low + (Math.random() * (high - low))); -} - -/** - * The Quick Sort algorithm. - * - * @param {Array} ary - * An array to sort. - * @param {function} comparator - * Function to use to compare two items. - * @param {Number} p - * Start index of the array - * @param {Number} r - * End index of the array - */ -function doQuickSort(ary, comparator, p, r) { - // If our lower bound is less than our upper bound, we (1) partition the - // array into two pieces and (2) recurse on each half. If it is not, this is - // the empty array and our base case. - - if (p < r) { - // (1) Partitioning. - // - // The partitioning chooses a pivot between `p` and `r` and moves all - // elements that are less than or equal to the pivot to the before it, and - // all the elements that are greater than it after it. The effect is that - // once partition is done, the pivot is in the exact place it will be when - // the array is put in sorted order, and it will not need to be moved - // again. This runs in O(n) time. - - // Always choose a random pivot so that an input array which is reverse - // sorted does not cause O(n^2) running time. - var pivotIndex = randomIntInRange(p, r); - var i = p - 1; - - swap(ary, pivotIndex, r); - var pivot = ary[r]; - - // Immediately after `j` is incremented in this loop, the following hold - // true: - // - // * Every element in `ary[p .. i]` is less than or equal to the pivot. - // - // * Every element in `ary[i+1 .. j-1]` is greater than the pivot. - for (var j = p; j < r; j++) { - if (comparator(ary[j], pivot) <= 0) { - i += 1; - swap(ary, i, j); - } - } - - swap(ary, i + 1, j); - var q = i + 1; - - // (2) Recurse on each half. - - doQuickSort(ary, comparator, p, q - 1); - doQuickSort(ary, comparator, q + 1, r); - } -} - -/** - * Sort the given array in-place with the given comparator function. - * - * @param {Array} ary - * An array to sort. - * @param {function} comparator - * Function to use to compare two items. - */ -exports.quickSort = function (ary, comparator) { - doQuickSort(ary, comparator, 0, ary.length - 1); -}; - - -/***/ }), -/* 672 */ -/***/ (function(module, exports, __webpack_require__) { - -/* -*- Mode: js; js-indent-level: 2; -*- */ -/* - * Copyright 2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE or: - * http://opensource.org/licenses/BSD-3-Clause - */ - -var SourceMapGenerator = __webpack_require__(663).SourceMapGenerator; -var util = __webpack_require__(666); - -// Matches a Windows-style `\r\n` newline or a `\n` newline used by all other -// operating systems these days (capturing the result). -var REGEX_NEWLINE = /(\r?\n)/; - -// Newline character code for charCodeAt() comparisons -var NEWLINE_CODE = 10; - -// Private symbol for identifying `SourceNode`s when multiple versions of -// the source-map library are loaded. This MUST NOT CHANGE across -// versions! -var isSourceNode = "$$$isSourceNode$$$"; - -/** - * SourceNodes provide a way to abstract over interpolating/concatenating - * snippets of generated JavaScript source code while maintaining the line and - * column information associated with the original source code. - * - * @param aLine The original line number. - * @param aColumn The original column number. - * @param aSource The original source's filename. - * @param aChunks Optional. An array of strings which are snippets of - * generated JS, or other SourceNodes. - * @param aName The original identifier. - */ -function SourceNode(aLine, aColumn, aSource, aChunks, aName) { - this.children = []; - this.sourceContents = {}; - this.line = aLine == null ? null : aLine; - this.column = aColumn == null ? null : aColumn; - this.source = aSource == null ? null : aSource; - this.name = aName == null ? null : aName; - this[isSourceNode] = true; - if (aChunks != null) this.add(aChunks); -} - -/** - * Creates a SourceNode from generated code and a SourceMapConsumer. - * - * @param aGeneratedCode The generated code - * @param aSourceMapConsumer The SourceMap for the generated code - * @param aRelativePath Optional. The path that relative sources in the - * SourceMapConsumer should be relative to. - */ -SourceNode.fromStringWithSourceMap = - function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) { - // The SourceNode we want to fill with the generated code - // and the SourceMap - var node = new SourceNode(); - - // All even indices of this array are one line of the generated code, - // while all odd indices are the newlines between two adjacent lines - // (since `REGEX_NEWLINE` captures its match). - // Processed fragments are accessed by calling `shiftNextLine`. - var remainingLines = aGeneratedCode.split(REGEX_NEWLINE); - var remainingLinesIndex = 0; - var shiftNextLine = function() { - var lineContents = getNextLine(); - // The last line of a file might not have a newline. - var newLine = getNextLine() || ""; - return lineContents + newLine; - - function getNextLine() { - return remainingLinesIndex < remainingLines.length ? - remainingLines[remainingLinesIndex++] : undefined; - } - }; - - // We need to remember the position of "remainingLines" - var lastGeneratedLine = 1, lastGeneratedColumn = 0; - - // The generate SourceNodes we need a code range. - // To extract it current and last mapping is used. - // Here we store the last mapping. - var lastMapping = null; - - aSourceMapConsumer.eachMapping(function (mapping) { - if (lastMapping !== null) { - // We add the code from "lastMapping" to "mapping": - // First check if there is a new line in between. - if (lastGeneratedLine < mapping.generatedLine) { - // Associate first line with "lastMapping" - addMappingWithCode(lastMapping, shiftNextLine()); - lastGeneratedLine++; - lastGeneratedColumn = 0; - // The remaining code is added without mapping - } else { - // There is no new line in between. - // Associate the code between "lastGeneratedColumn" and - // "mapping.generatedColumn" with "lastMapping" - var nextLine = remainingLines[remainingLinesIndex]; - var code = nextLine.substr(0, mapping.generatedColumn - - lastGeneratedColumn); - remainingLines[remainingLinesIndex] = nextLine.substr(mapping.generatedColumn - - lastGeneratedColumn); - lastGeneratedColumn = mapping.generatedColumn; - addMappingWithCode(lastMapping, code); - // No more remaining code, continue - lastMapping = mapping; - return; - } - } - // We add the generated code until the first mapping - // to the SourceNode without any mapping. - // Each line is added as separate string. - while (lastGeneratedLine < mapping.generatedLine) { - node.add(shiftNextLine()); - lastGeneratedLine++; - } - if (lastGeneratedColumn < mapping.generatedColumn) { - var nextLine = remainingLines[remainingLinesIndex]; - node.add(nextLine.substr(0, mapping.generatedColumn)); - remainingLines[remainingLinesIndex] = nextLine.substr(mapping.generatedColumn); - lastGeneratedColumn = mapping.generatedColumn; - } - lastMapping = mapping; - }, this); - // We have processed all mappings. - if (remainingLinesIndex < remainingLines.length) { - if (lastMapping) { - // Associate the remaining code in the current line with "lastMapping" - addMappingWithCode(lastMapping, shiftNextLine()); - } - // and add the remaining lines without any mapping - node.add(remainingLines.splice(remainingLinesIndex).join("")); - } - - // Copy sourcesContent into SourceNode - aSourceMapConsumer.sources.forEach(function (sourceFile) { - var content = aSourceMapConsumer.sourceContentFor(sourceFile); - if (content != null) { - if (aRelativePath != null) { - sourceFile = util.join(aRelativePath, sourceFile); - } - node.setSourceContent(sourceFile, content); - } - }); - - return node; - - function addMappingWithCode(mapping, code) { - if (mapping === null || mapping.source === undefined) { - node.add(code); - } else { - var source = aRelativePath - ? util.join(aRelativePath, mapping.source) - : mapping.source; - node.add(new SourceNode(mapping.originalLine, - mapping.originalColumn, - source, - code, - mapping.name)); - } - } - }; - -/** - * Add a chunk of generated JS to this source node. - * - * @param aChunk A string snippet of generated JS code, another instance of - * SourceNode, or an array where each member is one of those things. - */ -SourceNode.prototype.add = function SourceNode_add(aChunk) { - if (Array.isArray(aChunk)) { - aChunk.forEach(function (chunk) { - this.add(chunk); - }, this); - } - else if (aChunk[isSourceNode] || typeof aChunk === "string") { - if (aChunk) { - this.children.push(aChunk); - } - } - else { - throw new TypeError( - "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk - ); - } - return this; -}; - -/** - * Add a chunk of generated JS to the beginning of this source node. - * - * @param aChunk A string snippet of generated JS code, another instance of - * SourceNode, or an array where each member is one of those things. - */ -SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) { - if (Array.isArray(aChunk)) { - for (var i = aChunk.length-1; i >= 0; i--) { - this.prepend(aChunk[i]); - } - } - else if (aChunk[isSourceNode] || typeof aChunk === "string") { - this.children.unshift(aChunk); - } - else { - throw new TypeError( - "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk - ); - } - return this; -}; - -/** - * Walk over the tree of JS snippets in this node and its children. The - * walking function is called once for each snippet of JS and is passed that - * snippet and the its original associated source's line/column location. - * - * @param aFn The traversal function. - */ -SourceNode.prototype.walk = function SourceNode_walk(aFn) { - var chunk; - for (var i = 0, len = this.children.length; i < len; i++) { - chunk = this.children[i]; - if (chunk[isSourceNode]) { - chunk.walk(aFn); - } - else { - if (chunk !== '') { - aFn(chunk, { source: this.source, - line: this.line, - column: this.column, - name: this.name }); - } - } - } -}; - -/** - * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between - * each of `this.children`. - * - * @param aSep The separator. - */ -SourceNode.prototype.join = function SourceNode_join(aSep) { - var newChildren; - var i; - var len = this.children.length; - if (len > 0) { - newChildren = []; - for (i = 0; i < len-1; i++) { - newChildren.push(this.children[i]); - newChildren.push(aSep); - } - newChildren.push(this.children[i]); - this.children = newChildren; - } - return this; -}; - -/** - * Call String.prototype.replace on the very right-most source snippet. Useful - * for trimming whitespace from the end of a source node, etc. - * - * @param aPattern The pattern to replace. - * @param aReplacement The thing to replace the pattern with. - */ -SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) { - var lastChild = this.children[this.children.length - 1]; - if (lastChild[isSourceNode]) { - lastChild.replaceRight(aPattern, aReplacement); - } - else if (typeof lastChild === 'string') { - this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement); - } - else { - this.children.push(''.replace(aPattern, aReplacement)); - } - return this; -}; - -/** - * Set the source content for a source file. This will be added to the SourceMapGenerator - * in the sourcesContent field. - * - * @param aSourceFile The filename of the source file - * @param aSourceContent The content of the source file - */ -SourceNode.prototype.setSourceContent = - function SourceNode_setSourceContent(aSourceFile, aSourceContent) { - this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent; - }; - -/** - * Walk over the tree of SourceNodes. The walking function is called for each - * source file content and is passed the filename and source content. - * - * @param aFn The traversal function. - */ -SourceNode.prototype.walkSourceContents = - function SourceNode_walkSourceContents(aFn) { - for (var i = 0, len = this.children.length; i < len; i++) { - if (this.children[i][isSourceNode]) { - this.children[i].walkSourceContents(aFn); - } - } - - var sources = Object.keys(this.sourceContents); - for (var i = 0, len = sources.length; i < len; i++) { - aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]); - } - }; - -/** - * Return the string representation of this source node. Walks over the tree - * and concatenates all the various snippets together to one string. - */ -SourceNode.prototype.toString = function SourceNode_toString() { - var str = ""; - this.walk(function (chunk) { - str += chunk; - }); - return str; -}; - -/** - * Returns the string representation of this source node along with a source - * map. - */ -SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) { - var generated = { - code: "", - line: 1, - column: 0 - }; - var map = new SourceMapGenerator(aArgs); - var sourceMappingActive = false; - var lastOriginalSource = null; - var lastOriginalLine = null; - var lastOriginalColumn = null; - var lastOriginalName = null; - this.walk(function (chunk, original) { - generated.code += chunk; - if (original.source !== null - && original.line !== null - && original.column !== null) { - if(lastOriginalSource !== original.source - || lastOriginalLine !== original.line - || lastOriginalColumn !== original.column - || lastOriginalName !== original.name) { - map.addMapping({ - source: original.source, - original: { - line: original.line, - column: original.column - }, - generated: { - line: generated.line, - column: generated.column - }, - name: original.name - }); - } - lastOriginalSource = original.source; - lastOriginalLine = original.line; - lastOriginalColumn = original.column; - lastOriginalName = original.name; - sourceMappingActive = true; - } else if (sourceMappingActive) { - map.addMapping({ - generated: { - line: generated.line, - column: generated.column - } - }); - lastOriginalSource = null; - sourceMappingActive = false; - } - for (var idx = 0, length = chunk.length; idx < length; idx++) { - if (chunk.charCodeAt(idx) === NEWLINE_CODE) { - generated.line++; - generated.column = 0; - // Mappings end at eol - if (idx + 1 === length) { - lastOriginalSource = null; - sourceMappingActive = false; - } else if (sourceMappingActive) { - map.addMapping({ - source: original.source, - original: { - line: original.line, - column: original.column - }, - generated: { - line: generated.line, - column: generated.column - }, - name: original.name - }); - } - } else { - generated.column++; - } - } - }); - this.walkSourceContents(function (sourceFile, sourceContent) { - map.setSourceContent(sourceFile, sourceContent); - }); - - return { code: generated.code, map: map }; -}; - -exports.SourceNode = SourceNode; - - -/***/ }), -/* 673 */ -/***/ (function(module, exports, __webpack_require__) { - -var sourceMappingURL = __webpack_require__(674) - -var resolveUrl = __webpack_require__(675) -var decodeUriComponent = __webpack_require__(676) -var urix = __webpack_require__(678) -var atob = __webpack_require__(679) - - - -function callbackAsync(callback, error, result) { - setImmediate(function() { callback(error, result) }) -} - -function parseMapToJSON(string, data) { - try { - return JSON.parse(string.replace(/^\)\]\}'/, "")) - } catch (error) { - error.sourceMapData = data - throw error - } -} - -function readSync(read, url, data) { - var readUrl = decodeUriComponent(url) - try { - return String(read(readUrl)) - } catch (error) { - error.sourceMapData = data - throw error - } -} - - - -function resolveSourceMap(code, codeUrl, read, callback) { - var mapData - try { - mapData = resolveSourceMapHelper(code, codeUrl) - } catch (error) { - return callbackAsync(callback, error) - } - if (!mapData || mapData.map) { - return callbackAsync(callback, null, mapData) - } - var readUrl = decodeUriComponent(mapData.url) - read(readUrl, function(error, result) { - if (error) { - error.sourceMapData = mapData - return callback(error) - } - mapData.map = String(result) - try { - mapData.map = parseMapToJSON(mapData.map, mapData) - } catch (error) { - return callback(error) - } - callback(null, mapData) - }) -} - -function resolveSourceMapSync(code, codeUrl, read) { - var mapData = resolveSourceMapHelper(code, codeUrl) - if (!mapData || mapData.map) { - return mapData - } - mapData.map = readSync(read, mapData.url, mapData) - mapData.map = parseMapToJSON(mapData.map, mapData) - return mapData -} - -var dataUriRegex = /^data:([^,;]*)(;[^,;]*)*(?:,(.*))?$/ - -/** - * The media type for JSON text is application/json. - * - * {@link https://tools.ietf.org/html/rfc8259#section-11 | IANA Considerations } - * - * `text/json` is non-standard media type - */ -var jsonMimeTypeRegex = /^(?:application|text)\/json$/ - -/** - * JSON text exchanged between systems that are not part of a closed ecosystem - * MUST be encoded using UTF-8. - * - * {@link https://tools.ietf.org/html/rfc8259#section-8.1 | Character Encoding} - */ -var jsonCharacterEncoding = "utf-8" - -function base64ToBuf(b64) { - var binStr = atob(b64) - var len = binStr.length - var arr = new Uint8Array(len) - for (var i = 0; i < len; i++) { - arr[i] = binStr.charCodeAt(i) - } - return arr -} - -function decodeBase64String(b64) { - if (typeof TextDecoder === "undefined" || typeof Uint8Array === "undefined") { - return atob(b64) - } - var buf = base64ToBuf(b64); - // Note: `decoder.decode` method will throw a `DOMException` with the - // `"EncodingError"` value when an coding error is found. - var decoder = new TextDecoder(jsonCharacterEncoding, {fatal: true}) - return decoder.decode(buf); -} - -function resolveSourceMapHelper(code, codeUrl) { - codeUrl = urix(codeUrl) - - var url = sourceMappingURL.getFrom(code) - if (!url) { - return null - } - - var dataUri = url.match(dataUriRegex) - if (dataUri) { - var mimeType = dataUri[1] || "text/plain" - var lastParameter = dataUri[2] || "" - var encoded = dataUri[3] || "" - var data = { - sourceMappingURL: url, - url: null, - sourcesRelativeTo: codeUrl, - map: encoded - } - if (!jsonMimeTypeRegex.test(mimeType)) { - var error = new Error("Unuseful data uri mime type: " + mimeType) - error.sourceMapData = data - throw error - } - try { - data.map = parseMapToJSON( - lastParameter === ";base64" ? decodeBase64String(encoded) : decodeURIComponent(encoded), - data - ) - } catch (error) { - error.sourceMapData = data - throw error - } - return data - } - - var mapUrl = resolveUrl(codeUrl, url) - return { - sourceMappingURL: url, - url: mapUrl, - sourcesRelativeTo: mapUrl, - map: null - } -} - - - -function resolveSources(map, mapUrl, read, options, callback) { - if (typeof options === "function") { - callback = options - options = {} - } - var pending = map.sources ? map.sources.length : 0 - var result = { - sourcesResolved: [], - sourcesContent: [] - } - - if (pending === 0) { - callbackAsync(callback, null, result) - return - } - - var done = function() { - pending-- - if (pending === 0) { - callback(null, result) - } - } - - resolveSourcesHelper(map, mapUrl, options, function(fullUrl, sourceContent, index) { - result.sourcesResolved[index] = fullUrl - if (typeof sourceContent === "string") { - result.sourcesContent[index] = sourceContent - callbackAsync(done, null) - } else { - var readUrl = decodeUriComponent(fullUrl) - read(readUrl, function(error, source) { - result.sourcesContent[index] = error ? error : String(source) - done() - }) - } - }) -} - -function resolveSourcesSync(map, mapUrl, read, options) { - var result = { - sourcesResolved: [], - sourcesContent: [] - } - - if (!map.sources || map.sources.length === 0) { - return result - } - - resolveSourcesHelper(map, mapUrl, options, function(fullUrl, sourceContent, index) { - result.sourcesResolved[index] = fullUrl - if (read !== null) { - if (typeof sourceContent === "string") { - result.sourcesContent[index] = sourceContent - } else { - var readUrl = decodeUriComponent(fullUrl) - try { - result.sourcesContent[index] = String(read(readUrl)) - } catch (error) { - result.sourcesContent[index] = error - } - } - } - }) - - return result -} - -var endingSlash = /\/?$/ - -function resolveSourcesHelper(map, mapUrl, options, fn) { - options = options || {} - mapUrl = urix(mapUrl) - var fullUrl - var sourceContent - var sourceRoot - for (var index = 0, len = map.sources.length; index < len; index++) { - sourceRoot = null - if (typeof options.sourceRoot === "string") { - sourceRoot = options.sourceRoot - } else if (typeof map.sourceRoot === "string" && options.sourceRoot !== false) { - sourceRoot = map.sourceRoot - } - // If the sourceRoot is the empty string, it is equivalent to not setting - // the property at all. - if (sourceRoot === null || sourceRoot === '') { - fullUrl = resolveUrl(mapUrl, map.sources[index]) - } else { - // Make sure that the sourceRoot ends with a slash, so that `/scripts/subdir` becomes - // `/scripts/subdir/`, not `/scripts/`. Pointing to a file as source root - // does not make sense. - fullUrl = resolveUrl(mapUrl, sourceRoot.replace(endingSlash, "/"), map.sources[index]) - } - sourceContent = (map.sourcesContent || [])[index] - fn(fullUrl, sourceContent, index) - } -} - - - -function resolve(code, codeUrl, read, options, callback) { - if (typeof options === "function") { - callback = options - options = {} - } - if (code === null) { - var mapUrl = codeUrl - var data = { - sourceMappingURL: null, - url: mapUrl, - sourcesRelativeTo: mapUrl, - map: null - } - var readUrl = decodeUriComponent(mapUrl) - read(readUrl, function(error, result) { - if (error) { - error.sourceMapData = data - return callback(error) - } - data.map = String(result) - try { - data.map = parseMapToJSON(data.map, data) - } catch (error) { - return callback(error) - } - _resolveSources(data) - }) - } else { - resolveSourceMap(code, codeUrl, read, function(error, mapData) { - if (error) { - return callback(error) - } - if (!mapData) { - return callback(null, null) - } - _resolveSources(mapData) - }) - } - - function _resolveSources(mapData) { - resolveSources(mapData.map, mapData.sourcesRelativeTo, read, options, function(error, result) { - if (error) { - return callback(error) - } - mapData.sourcesResolved = result.sourcesResolved - mapData.sourcesContent = result.sourcesContent - callback(null, mapData) - }) - } -} - -function resolveSync(code, codeUrl, read, options) { - var mapData - if (code === null) { - var mapUrl = codeUrl - mapData = { - sourceMappingURL: null, - url: mapUrl, - sourcesRelativeTo: mapUrl, - map: null - } - mapData.map = readSync(read, mapUrl, mapData) - mapData.map = parseMapToJSON(mapData.map, mapData) - } else { - mapData = resolveSourceMapSync(code, codeUrl, read) - if (!mapData) { - return null - } - } - var result = resolveSourcesSync(mapData.map, mapData.sourcesRelativeTo, read, options) - mapData.sourcesResolved = result.sourcesResolved - mapData.sourcesContent = result.sourcesContent - return mapData -} - - - -module.exports = { - resolveSourceMap: resolveSourceMap, - resolveSourceMapSync: resolveSourceMapSync, - resolveSources: resolveSources, - resolveSourcesSync: resolveSourcesSync, - resolve: resolve, - resolveSync: resolveSync, - parseMapToJSON: parseMapToJSON -} - - -/***/ }), -/* 674 */ -/***/ (function(module, exports, __webpack_require__) { - -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell -// X11 (“MIT”) Licensed. (See LICENSE.) - -void (function(root, factory) { - if (true) { - !(__WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.call(exports, __webpack_require__, exports, module)) : - __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)) - } else {} -}(this, function() { - - var innerRegex = /[#@] sourceMappingURL=([^\s'"]*)/ - - var regex = RegExp( - "(?:" + - "/\\*" + - "(?:\\s*\r?\n(?://)?)?" + - "(?:" + innerRegex.source + ")" + - "\\s*" + - "\\*/" + - "|" + - "//(?:" + innerRegex.source + ")" + - ")" + - "\\s*" - ) - - return { - - regex: regex, - _innerRegex: innerRegex, - - getFrom: function(code) { - var match = code.match(regex) - return (match ? match[1] || match[2] || "" : null) - }, - - existsIn: function(code) { - return regex.test(code) - }, - - removeFrom: function(code) { - return code.replace(regex, "") - }, - - insertBefore: function(code, string) { - var match = code.match(regex) - if (match) { - return code.slice(0, match.index) + string + code.slice(match.index) - } else { - return code + string - } - } - } - -})); - - -/***/ }), -/* 675 */ -/***/ (function(module, exports, __webpack_require__) { - -var url = __webpack_require__(252) - -function resolveUrl(/* ...urls */) { - return Array.prototype.reduce.call(arguments, function(resolved, nextUrl) { - return url.resolve(resolved, nextUrl) - }) -} - -module.exports = resolveUrl - - -/***/ }), -/* 676 */ -/***/ (function(module, exports, __webpack_require__) { - -var decodeUriComponent = __webpack_require__(677) - -function customDecodeUriComponent(string) { - // `decodeUriComponent` turns `+` into ` `, but that's not wanted. - return decodeUriComponent(string.replace(/\+/g, "%2B")) -} - -module.exports = customDecodeUriComponent - - -/***/ }), -/* 677 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -var token = '%[a-f0-9]{2}'; -var singleMatcher = new RegExp('(' + token + ')|([^%]+?)', 'gi'); -var multiMatcher = new RegExp('(' + token + ')+', 'gi'); - -function decodeComponents(components, split) { - try { - // Try to decode the entire string first - return [decodeURIComponent(components.join(''))]; - } catch (err) { - // Do nothing - } - - if (components.length === 1) { - return components; - } - - split = split || 1; - - // Split the array in 2 parts - var left = components.slice(0, split); - var right = components.slice(split); - - return Array.prototype.concat.call([], decodeComponents(left), decodeComponents(right)); -} - -function decode(input) { - try { - return decodeURIComponent(input); - } catch (err) { - var tokens = input.match(singleMatcher) || []; - - for (var i = 1; i < tokens.length; i++) { - input = decodeComponents(tokens, i).join(''); - - tokens = input.match(singleMatcher) || []; - } - - return input; - } -} - -function customDecodeURIComponent(input) { - // Keep track of all the replacements and prefill the map with the `BOM` - var replaceMap = { - '%FE%FF': '\uFFFD\uFFFD', - '%FF%FE': '\uFFFD\uFFFD' - }; - - var match = multiMatcher.exec(input); - while (match) { - try { - // Decode as big chunks as possible - replaceMap[match[0]] = decodeURIComponent(match[0]); - } catch (err) { - var result = decode(match[0]); - - if (result !== match[0]) { - replaceMap[match[0]] = result; - } - } - - match = multiMatcher.exec(input); - } - - // Add `%C2` at the end of the map to make sure it does not replace the combinator before everything else - replaceMap['%C2'] = '\uFFFD'; - - var entries = Object.keys(replaceMap); - - for (var i = 0; i < entries.length; i++) { - // Replace all decoded components - var key = entries[i]; - input = input.replace(new RegExp(key, 'g'), replaceMap[key]); - } - - return input; -} - -module.exports = function (encodedURI) { - if (typeof encodedURI !== 'string') { - throw new TypeError('Expected `encodedURI` to be of type `string`, got `' + typeof encodedURI + '`'); - } - - try { - encodedURI = encodedURI.replace(/\+/g, ' '); - - // Try the built in decoder first - return decodeURIComponent(encodedURI); - } catch (err) { - // Fallback to a more advanced decoder - return customDecodeURIComponent(encodedURI); - } -}; - - -/***/ }), -/* 678 */ -/***/ (function(module, exports, __webpack_require__) { - -// Copyright 2014 Simon Lydell -// X11 (“MIT”) Licensed. (See LICENSE.) - -var path = __webpack_require__(4) - -"use strict" - -function urix(aPath) { - if (path.sep === "\\") { - return aPath - .replace(/\\/g, "/") - .replace(/^[a-z]:\/?/i, "/") - } - return aPath -} - -module.exports = urix - - -/***/ }), -/* 679 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -function atob(str) { - return Buffer.from(str, 'base64').toString('binary'); -} - -module.exports = atob.atob = atob; - - -/***/ }), -/* 680 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var fs = __webpack_require__(134); -var path = __webpack_require__(4); -var define = __webpack_require__(645); -var utils = __webpack_require__(660); - -/** - * Expose `mixin()`. - * This code is based on `source-maps-support.js` in reworkcss/css - * https://github.com/reworkcss/css/blob/master/lib/stringify/source-map-support.js - * Copyright (c) 2012 TJ Holowaychuk - */ - -module.exports = mixin; - -/** - * Mixin source map support into `compiler`. - * - * @param {Object} `compiler` - * @api public - */ - -function mixin(compiler) { - define(compiler, '_comment', compiler.comment); - compiler.map = new utils.SourceMap.SourceMapGenerator(); - compiler.position = { line: 1, column: 1 }; - compiler.content = {}; - compiler.files = {}; - - for (var key in exports) { - define(compiler, key, exports[key]); - } -} - -/** - * Update position. - * - * @param {String} str - */ - -exports.updatePosition = function(str) { - var lines = str.match(/\n/g); - if (lines) this.position.line += lines.length; - var i = str.lastIndexOf('\n'); - this.position.column = ~i ? str.length - i : this.position.column + str.length; -}; - -/** - * Emit `str` with `position`. - * - * @param {String} str - * @param {Object} [pos] - * @return {String} - */ - -exports.emit = function(str, node) { - var position = node.position || {}; - var source = position.source; - if (source) { - if (position.filepath) { - source = utils.unixify(position.filepath); - } - - this.map.addMapping({ - source: source, - generated: { - line: this.position.line, - column: Math.max(this.position.column - 1, 0) - }, - original: { - line: position.start.line, - column: position.start.column - 1 - } - }); - - if (position.content) { - this.addContent(source, position); - } - if (position.filepath) { - this.addFile(source, position); - } - - this.updatePosition(str); - this.output += str; - } - return str; -}; - -/** - * Adds a file to the source map output if it has not already been added - * @param {String} `file` - * @param {Object} `pos` - */ - -exports.addFile = function(file, position) { - if (typeof position.content !== 'string') return; - if (Object.prototype.hasOwnProperty.call(this.files, file)) return; - this.files[file] = position.content; -}; - -/** - * Adds a content source to the source map output if it has not already been added - * @param {String} `source` - * @param {Object} `position` - */ - -exports.addContent = function(source, position) { - if (typeof position.content !== 'string') return; - if (Object.prototype.hasOwnProperty.call(this.content, source)) return; - this.map.setSourceContent(source, position.content); -}; - -/** - * Applies any original source maps to the output and embeds the source file - * contents in the source map. - */ - -exports.applySourceMaps = function() { - Object.keys(this.files).forEach(function(file) { - var content = this.files[file]; - this.map.setSourceContent(file, content); - - if (this.options.inputSourcemaps === true) { - var originalMap = utils.sourceMapResolve.resolveSync(content, file, fs.readFileSync); - if (originalMap) { - var map = new utils.SourceMap.SourceMapConsumer(originalMap.map); - var relativeTo = originalMap.sourcesRelativeTo; - this.map.applySourceMap(map, file, utils.unixify(path.dirname(relativeTo))); - } - } - }, this); -}; - -/** - * Process comments, drops sourceMap comments. - * @param {Object} node - */ - -exports.comment = function(node) { - if (/^# sourceMappingURL=/.test(node.comment)) { - return this.emit('', node.position); - } - return this._comment(node); -}; - - -/***/ }), -/* 681 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var use = __webpack_require__(653); -var util = __webpack_require__(112); -var Cache = __webpack_require__(682); -var define = __webpack_require__(645); -var debug = __webpack_require__(654)('snapdragon:parser'); -var Position = __webpack_require__(683); -var utils = __webpack_require__(660); - -/** - * Create a new `Parser` with the given `input` and `options`. - * @param {String} `input` - * @param {Object} `options` - * @api public - */ - -function Parser(options) { - debug('initializing', __filename); - this.options = utils.extend({source: 'string'}, options); - this.init(this.options); - use(this); -} - -/** - * Prototype methods - */ - -Parser.prototype = { - constructor: Parser, - - init: function(options) { - this.orig = ''; - this.input = ''; - this.parsed = ''; - - this.column = 1; - this.line = 1; - - this.regex = new Cache(); - this.errors = this.errors || []; - this.parsers = this.parsers || {}; - this.types = this.types || []; - this.sets = this.sets || {}; - this.fns = this.fns || []; - this.currentType = 'root'; - - var pos = this.position(); - this.bos = pos({type: 'bos', val: ''}); - - this.ast = { - type: 'root', - errors: this.errors, - nodes: [this.bos] - }; - - define(this.bos, 'parent', this.ast); - this.nodes = [this.ast]; - - this.count = 0; - this.setCount = 0; - this.stack = []; - }, - - /** - * Throw a formatted error with the cursor column and `msg`. - * @param {String} `msg` Message to use in the Error. - */ - - error: function(msg, node) { - var pos = node.position || {start: {column: 0, line: 0}}; - var line = pos.start.line; - var column = pos.start.column; - var source = this.options.source; - - var message = source + ' : ' + msg; - var err = new Error(message); - err.source = source; - err.reason = msg; - err.pos = pos; - - if (this.options.silent) { - this.errors.push(err); - } else { - throw err; - } - }, - - /** - * Define a non-enumberable property on the `Parser` instance. - * - * ```js - * parser.define('foo', 'bar'); - * ``` - * @name .define - * @param {String} `key` propery name - * @param {any} `val` property value - * @return {Object} Returns the Parser instance for chaining. - * @api public - */ - - define: function(key, val) { - define(this, key, val); - return this; - }, - - /** - * Mark position and patch `node.position`. - */ - - position: function() { - var start = { line: this.line, column: this.column }; - var self = this; - - return function(node) { - define(node, 'position', new Position(start, self)); - return node; - }; - }, - - /** - * Set parser `name` with the given `fn` - * @param {String} `name` - * @param {Function} `fn` - * @api public - */ - - set: function(type, fn) { - if (this.types.indexOf(type) === -1) { - this.types.push(type); - } - this.parsers[type] = fn.bind(this); - return this; - }, - - /** - * Get parser `name` - * @param {String} `name` - * @api public - */ - - get: function(name) { - return this.parsers[name]; - }, - - /** - * Push a `token` onto the `type` stack. - * - * @param {String} `type` - * @return {Object} `token` - * @api public - */ - - push: function(type, token) { - this.sets[type] = this.sets[type] || []; - this.count++; - this.stack.push(token); - return this.sets[type].push(token); - }, - - /** - * Pop a token off of the `type` stack - * @param {String} `type` - * @returns {Object} Returns a token - * @api public - */ - - pop: function(type) { - this.sets[type] = this.sets[type] || []; - this.count--; - this.stack.pop(); - return this.sets[type].pop(); - }, - - /** - * Return true if inside a `stack` node. Types are `braces`, `parens` or `brackets`. - * - * @param {String} `type` - * @return {Boolean} - * @api public - */ - - isInside: function(type) { - this.sets[type] = this.sets[type] || []; - return this.sets[type].length > 0; - }, - - /** - * Return true if `node` is the given `type`. - * - * ```js - * parser.isType(node, 'brace'); - * ``` - * @param {Object} `node` - * @param {String} `type` - * @return {Boolean} - * @api public - */ - - isType: function(node, type) { - return node && node.type === type; - }, - - /** - * Get the previous AST node - * @return {Object} - */ - - prev: function(n) { - return this.stack.length > 0 - ? utils.last(this.stack, n) - : utils.last(this.nodes, n); - }, - - /** - * Update line and column based on `str`. - */ - - consume: function(len) { - this.input = this.input.substr(len); - }, - - /** - * Update column based on `str`. - */ - - updatePosition: function(str, len) { - var lines = str.match(/\n/g); - if (lines) this.line += lines.length; - var i = str.lastIndexOf('\n'); - this.column = ~i ? len - i : this.column + len; - this.parsed += str; - this.consume(len); - }, - - /** - * Match `regex`, return captures, and update the cursor position by `match[0]` length. - * @param {RegExp} `regex` - * @return {Object} - */ - - match: function(regex) { - var m = regex.exec(this.input); - if (m) { - this.updatePosition(m[0], m[0].length); - return m; - } - }, - - /** - * Capture `type` with the given regex. - * @param {String} `type` - * @param {RegExp} `regex` - * @return {Function} - */ - - capture: function(type, regex) { - if (typeof regex === 'function') { - return this.set.apply(this, arguments); - } - - this.regex.set(type, regex); - this.set(type, function() { - var parsed = this.parsed; - var pos = this.position(); - var m = this.match(regex); - if (!m || !m[0]) return; - - var prev = this.prev(); - var node = pos({ - type: type, - val: m[0], - parsed: parsed, - rest: this.input - }); - - if (m[1]) { - node.inner = m[1]; - } - - define(node, 'inside', this.stack.length > 0); - define(node, 'parent', prev); - prev.nodes.push(node); - }.bind(this)); - return this; - }, - - /** - * Create a parser with open and close for parens, - * brackets or braces - */ - - capturePair: function(type, openRegex, closeRegex, fn) { - this.sets[type] = this.sets[type] || []; - - /** - * Open - */ - - this.set(type + '.open', function() { - var parsed = this.parsed; - var pos = this.position(); - var m = this.match(openRegex); - if (!m || !m[0]) return; - - var val = m[0]; - this.setCount++; - this.specialChars = true; - var open = pos({ - type: type + '.open', - val: val, - rest: this.input - }); - - if (typeof m[1] !== 'undefined') { - open.inner = m[1]; - } - - var prev = this.prev(); - var node = pos({ - type: type, - nodes: [open] - }); - - define(node, 'rest', this.input); - define(node, 'parsed', parsed); - define(node, 'prefix', m[1]); - define(node, 'parent', prev); - define(open, 'parent', node); - - if (typeof fn === 'function') { - fn.call(this, open, node); - } - - this.push(type, node); - prev.nodes.push(node); - }); - - /** - * Close - */ - - this.set(type + '.close', function() { - var pos = this.position(); - var m = this.match(closeRegex); - if (!m || !m[0]) return; - - var parent = this.pop(type); - var node = pos({ - type: type + '.close', - rest: this.input, - suffix: m[1], - val: m[0] - }); - - if (!this.isType(parent, type)) { - if (this.options.strict) { - throw new Error('missing opening "' + type + '"'); - } - - this.setCount--; - node.escaped = true; - return node; - } - - if (node.suffix === '\\') { - parent.escaped = true; - node.escaped = true; - } - - parent.nodes.push(node); - define(node, 'parent', parent); - }); - - return this; - }, - - /** - * Capture end-of-string - */ - - eos: function() { - var pos = this.position(); - if (this.input) return; - var prev = this.prev(); - - while (prev.type !== 'root' && !prev.visited) { - if (this.options.strict === true) { - throw new SyntaxError('invalid syntax:' + util.inspect(prev, null, 2)); - } - - if (!hasDelims(prev)) { - prev.parent.escaped = true; - prev.escaped = true; - } - - visit(prev, function(node) { - if (!hasDelims(node.parent)) { - node.parent.escaped = true; - node.escaped = true; - } - }); - - prev = prev.parent; - } - - var tok = pos({ - type: 'eos', - val: this.append || '' - }); - - define(tok, 'parent', this.ast); - return tok; - }, - - /** - * Run parsers to advance the cursor position - */ - - next: function() { - var parsed = this.parsed; - var len = this.types.length; - var idx = -1; - var tok; - - while (++idx < len) { - if ((tok = this.parsers[this.types[idx]].call(this))) { - define(tok, 'rest', this.input); - define(tok, 'parsed', parsed); - this.last = tok; - return tok; - } - } - }, - - /** - * Parse the given string. - * @return {Array} - */ - - parse: function(input) { - if (typeof input !== 'string') { - throw new TypeError('expected a string'); - } - - this.init(this.options); - this.orig = input; - this.input = input; - var self = this; - - function parse() { - // check input before calling `.next()` - input = self.input; - - // get the next AST ndoe - var node = self.next(); - if (node) { - var prev = self.prev(); - if (prev) { - define(node, 'parent', prev); - if (prev.nodes) { - prev.nodes.push(node); - } - } - - if (self.sets.hasOwnProperty(prev.type)) { - self.currentType = prev.type; - } - } - - // if we got here but input is not changed, throw an error - if (self.input && input === self.input) { - throw new Error('no parsers registered for: "' + self.input.slice(0, 5) + '"'); - } - } - - while (this.input) parse(); - if (this.stack.length && this.options.strict) { - var node = this.stack.pop(); - throw this.error('missing opening ' + node.type + ': "' + this.orig + '"'); - } - - var eos = this.eos(); - var tok = this.prev(); - if (tok.type !== 'eos') { - this.ast.nodes.push(eos); - } - - return this.ast; - } -}; - -/** - * Visit `node` with the given `fn` - */ - -function visit(node, fn) { - if (!node.visited) { - define(node, 'visited', true); - return node.nodes ? mapVisit(node.nodes, fn) : fn(node); - } - return node; -} - -/** - * Map visit over array of `nodes`. - */ - -function mapVisit(nodes, fn) { - var len = nodes.length; - var idx = -1; - while (++idx < len) { - visit(nodes[idx], fn); - } -} - -function hasOpen(node) { - return node.nodes && node.nodes[0].type === (node.type + '.open'); -} - -function hasClose(node) { - return node.nodes && utils.last(node.nodes).type === (node.type + '.close'); -} - -function hasDelims(node) { - return hasOpen(node) && hasClose(node); -} - -/** - * Expose `Parser` - */ - -module.exports = Parser; - - -/***/ }), -/* 682 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * map-cache - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -var hasOwn = Object.prototype.hasOwnProperty; - -/** - * Expose `MapCache` - */ - -module.exports = MapCache; - -/** - * Creates a cache object to store key/value pairs. - * - * ```js - * var cache = new MapCache(); - * ``` - * - * @api public - */ - -function MapCache(data) { - this.__data__ = data || {}; -} - -/** - * Adds `value` to `key` on the cache. - * - * ```js - * cache.set('foo', 'bar'); - * ``` - * - * @param {String} `key` The key of the value to cache. - * @param {*} `value` The value to cache. - * @returns {Object} Returns the `Cache` object for chaining. - * @api public - */ - -MapCache.prototype.set = function mapSet(key, value) { - if (key !== '__proto__') { - this.__data__[key] = value; - } - return this; -}; - -/** - * Gets the cached value for `key`. - * - * ```js - * cache.get('foo'); - * //=> 'bar' - * ``` - * - * @param {String} `key` The key of the value to get. - * @returns {*} Returns the cached value. - * @api public - */ - -MapCache.prototype.get = function mapGet(key) { - return key === '__proto__' ? undefined : this.__data__[key]; -}; - -/** - * Checks if a cached value for `key` exists. - * - * ```js - * cache.has('foo'); - * //=> true - * ``` - * - * @param {String} `key` The key of the entry to check. - * @returns {Boolean} Returns `true` if an entry for `key` exists, else `false`. - * @api public - */ - -MapCache.prototype.has = function mapHas(key) { - return key !== '__proto__' && hasOwn.call(this.__data__, key); -}; - -/** - * Removes `key` and its value from the cache. - * - * ```js - * cache.del('foo'); - * ``` - * @title .del - * @param {String} `key` The key of the value to remove. - * @returns {Boolean} Returns `true` if the entry was removed successfully, else `false`. - * @api public - */ - -MapCache.prototype.del = function mapDelete(key) { - return this.has(key) && delete this.__data__[key]; -}; - - -/***/ }), -/* 683 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var define = __webpack_require__(645); - -/** - * Store position for a node - */ - -module.exports = function Position(start, parser) { - this.start = start; - this.end = { line: parser.line, column: parser.column }; - define(this, 'content', parser.orig); - define(this, 'source', parser.options.source); -}; - - -/***/ }), -/* 684 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var nanomatch = __webpack_require__(685); -var extglob = __webpack_require__(695); - -module.exports = function(snapdragon) { - var compilers = snapdragon.compiler.compilers; - var opts = snapdragon.options; - - // register nanomatch compilers - snapdragon.use(nanomatch.compilers); - - // get references to some specific nanomatch compilers before they - // are overridden by the extglob and/or custom compilers - var escape = compilers.escape; - var qmark = compilers.qmark; - var slash = compilers.slash; - var star = compilers.star; - var text = compilers.text; - var plus = compilers.plus; - var dot = compilers.dot; - - // register extglob compilers or escape exglobs if disabled - if (opts.extglob === false || opts.noext === true) { - snapdragon.compiler.use(escapeExtglobs); - } else { - snapdragon.use(extglob.compilers); - } - - snapdragon.use(function() { - this.options.star = this.options.star || function(/*node*/) { - return '[^\\\\/]*?'; - }; - }); - - // custom micromatch compilers - snapdragon.compiler - - // reset referenced compiler - .set('dot', dot) - .set('escape', escape) - .set('plus', plus) - .set('slash', slash) - .set('qmark', qmark) - .set('star', star) - .set('text', text); -}; - -function escapeExtglobs(compiler) { - compiler.set('paren', function(node) { - var val = ''; - visit(node, function(tok) { - if (tok.val) val += (/^\W/.test(tok.val) ? '\\' : '') + tok.val; - }); - return this.emit(val, node); - }); - - /** - * Visit `node` with the given `fn` - */ - - function visit(node, fn) { - return node.nodes ? mapVisit(node.nodes, fn) : fn(node); - } - - /** - * Map visit over array of `nodes`. - */ - - function mapVisit(nodes, fn) { - var len = nodes.length; - var idx = -1; - while (++idx < len) { - visit(nodes[idx], fn); - } - } -} - - -/***/ }), -/* 685 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/** - * Module dependencies - */ - -var util = __webpack_require__(112); -var toRegex = __webpack_require__(582); -var extend = __webpack_require__(595); - -/** - * Local dependencies - */ - -var compilers = __webpack_require__(686); -var parsers = __webpack_require__(687); -var cache = __webpack_require__(688); -var utils = __webpack_require__(690); -var MAX_LENGTH = 1024 * 64; - -/** - * The main function takes a list of strings and one or more - * glob patterns to use for matching. - * - * ```js - * var nm = require('nanomatch'); - * nm(list, patterns[, options]); - * - * console.log(nm(['a.js', 'a.txt'], ['*.js'])); - * //=> [ 'a.js' ] - * ``` - * @param {Array} `list` A list of strings to match - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Array} Returns an array of matches - * @summary false - * @api public - */ - -function nanomatch(list, patterns, options) { - patterns = utils.arrayify(patterns); - list = utils.arrayify(list); - - var len = patterns.length; - if (list.length === 0 || len === 0) { - return []; - } - - if (len === 1) { - return nanomatch.match(list, patterns[0], options); - } - - var negated = false; - var omit = []; - var keep = []; - var idx = -1; - - while (++idx < len) { - var pattern = patterns[idx]; - - if (typeof pattern === 'string' && pattern.charCodeAt(0) === 33 /* ! */) { - omit.push.apply(omit, nanomatch.match(list, pattern.slice(1), options)); - negated = true; - } else { - keep.push.apply(keep, nanomatch.match(list, pattern, options)); - } - } - - // minimatch.match parity - if (negated && keep.length === 0) { - if (options && options.unixify === false) { - keep = list.slice(); - } else { - var unixify = utils.unixify(options); - for (var i = 0; i < list.length; i++) { - keep.push(unixify(list[i])); - } - } - } - - var matches = utils.diff(keep, omit); - if (!options || options.nodupes !== false) { - return utils.unique(matches); - } - - return matches; -} - -/** - * Similar to the main function, but `pattern` must be a string. - * - * ```js - * var nm = require('nanomatch'); - * nm.match(list, pattern[, options]); - * - * console.log(nm.match(['a.a', 'a.aa', 'a.b', 'a.c'], '*.a')); - * //=> ['a.a', 'a.aa'] - * ``` - * @param {Array} `list` Array of strings to match - * @param {String} `pattern` Glob pattern to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Array} Returns an array of matches - * @api public - */ - -nanomatch.match = function(list, pattern, options) { - if (Array.isArray(pattern)) { - throw new TypeError('expected pattern to be a string'); - } - - var unixify = utils.unixify(options); - var isMatch = memoize('match', pattern, options, nanomatch.matcher); - var matches = []; - - list = utils.arrayify(list); - var len = list.length; - var idx = -1; - - while (++idx < len) { - var ele = list[idx]; - if (ele === pattern || isMatch(ele)) { - matches.push(utils.value(ele, unixify, options)); - } - } - - // if no options were passed, uniquify results and return - if (typeof options === 'undefined') { - return utils.unique(matches); - } - - if (matches.length === 0) { - if (options.failglob === true) { - throw new Error('no matches found for "' + pattern + '"'); - } - if (options.nonull === true || options.nullglob === true) { - return [options.unescape ? utils.unescape(pattern) : pattern]; - } - } - - // if `opts.ignore` was defined, diff ignored list - if (options.ignore) { - matches = nanomatch.not(matches, options.ignore, options); - } - - return options.nodupes !== false ? utils.unique(matches) : matches; -}; - -/** - * Returns true if the specified `string` matches the given glob `pattern`. - * - * ```js - * var nm = require('nanomatch'); - * nm.isMatch(string, pattern[, options]); - * - * console.log(nm.isMatch('a.a', '*.a')); - * //=> true - * console.log(nm.isMatch('a.b', '*.a')); - * //=> false - * ``` - * @param {String} `string` String to match - * @param {String} `pattern` Glob pattern to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if the string matches the glob pattern. - * @api public - */ - -nanomatch.isMatch = function(str, pattern, options) { - if (typeof str !== 'string') { - throw new TypeError('expected a string: "' + util.inspect(str) + '"'); - } - - if (utils.isEmptyString(str) || utils.isEmptyString(pattern)) { - return false; - } - - var equals = utils.equalsPattern(options); - if (equals(str)) { - return true; - } - - var isMatch = memoize('isMatch', pattern, options, nanomatch.matcher); - return isMatch(str); -}; - -/** - * Returns true if some of the elements in the given `list` match any of the - * given glob `patterns`. - * - * ```js - * var nm = require('nanomatch'); - * nm.some(list, patterns[, options]); - * - * console.log(nm.some(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); - * // true - * console.log(nm.some(['foo.js'], ['*.js', '!foo.js'])); - * // false - * ``` - * @param {String|Array} `list` The string or array of strings to test. Returns as soon as the first match is found. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ - -nanomatch.some = function(list, patterns, options) { - if (typeof list === 'string') { - list = [list]; - } - - for (var i = 0; i < list.length; i++) { - if (nanomatch(list[i], patterns, options).length === 1) { - return true; - } - } - - return false; -}; - -/** - * Returns true if every element in the given `list` matches - * at least one of the given glob `patterns`. - * - * ```js - * var nm = require('nanomatch'); - * nm.every(list, patterns[, options]); - * - * console.log(nm.every('foo.js', ['foo.js'])); - * // true - * console.log(nm.every(['foo.js', 'bar.js'], ['*.js'])); - * // true - * console.log(nm.every(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); - * // false - * console.log(nm.every(['foo.js'], ['*.js', '!foo.js'])); - * // false - * ``` - * @param {String|Array} `list` The string or array of strings to test. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ - -nanomatch.every = function(list, patterns, options) { - if (typeof list === 'string') { - list = [list]; - } - - for (var i = 0; i < list.length; i++) { - if (nanomatch(list[i], patterns, options).length !== 1) { - return false; - } - } - - return true; -}; - -/** - * Returns true if **any** of the given glob `patterns` - * match the specified `string`. - * - * ```js - * var nm = require('nanomatch'); - * nm.any(string, patterns[, options]); - * - * console.log(nm.any('a.a', ['b.*', '*.a'])); - * //=> true - * console.log(nm.any('a.a', 'b.*')); - * //=> false - * ``` - * @param {String|Array} `str` The string to test. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ - -nanomatch.any = function(str, patterns, options) { - if (typeof str !== 'string') { - throw new TypeError('expected a string: "' + util.inspect(str) + '"'); - } - - if (utils.isEmptyString(str) || utils.isEmptyString(patterns)) { - return false; - } - - if (typeof patterns === 'string') { - patterns = [patterns]; - } - - for (var i = 0; i < patterns.length; i++) { - if (nanomatch.isMatch(str, patterns[i], options)) { - return true; - } - } - return false; -}; - -/** - * Returns true if **all** of the given `patterns` - * match the specified string. - * - * ```js - * var nm = require('nanomatch'); - * nm.all(string, patterns[, options]); - * - * console.log(nm.all('foo.js', ['foo.js'])); - * // true - * - * console.log(nm.all('foo.js', ['*.js', '!foo.js'])); - * // false - * - * console.log(nm.all('foo.js', ['*.js', 'foo.js'])); - * // true - * - * console.log(nm.all('foo.js', ['*.js', 'f*', '*o*', '*o.js'])); - * // true - * ``` - * @param {String|Array} `str` The string to test. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ - -nanomatch.all = function(str, patterns, options) { - if (typeof str !== 'string') { - throw new TypeError('expected a string: "' + util.inspect(str) + '"'); - } - - if (typeof patterns === 'string') { - patterns = [patterns]; - } - - for (var i = 0; i < patterns.length; i++) { - if (!nanomatch.isMatch(str, patterns[i], options)) { - return false; - } - } - return true; -}; - -/** - * Returns a list of strings that _**do not match any**_ of the given `patterns`. - * - * ```js - * var nm = require('nanomatch'); - * nm.not(list, patterns[, options]); - * - * console.log(nm.not(['a.a', 'b.b', 'c.c'], '*.a')); - * //=> ['b.b', 'c.c'] - * ``` - * @param {Array} `list` Array of strings to match. - * @param {String|Array} `patterns` One or more glob pattern to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Array} Returns an array of strings that **do not match** the given patterns. - * @api public - */ - -nanomatch.not = function(list, patterns, options) { - var opts = extend({}, options); - var ignore = opts.ignore; - delete opts.ignore; - - list = utils.arrayify(list); - - var matches = utils.diff(list, nanomatch(list, patterns, opts)); - if (ignore) { - matches = utils.diff(matches, nanomatch(list, ignore)); - } - - return opts.nodupes !== false ? utils.unique(matches) : matches; -}; - -/** - * Returns true if the given `string` contains the given pattern. Similar - * to [.isMatch](#isMatch) but the pattern can match any part of the string. - * - * ```js - * var nm = require('nanomatch'); - * nm.contains(string, pattern[, options]); - * - * console.log(nm.contains('aa/bb/cc', '*b')); - * //=> true - * console.log(nm.contains('aa/bb/cc', '*d')); - * //=> false - * ``` - * @param {String} `str` The string to match. - * @param {String|Array} `patterns` Glob pattern to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if the patter matches any part of `str`. - * @api public - */ - -nanomatch.contains = function(str, patterns, options) { - if (typeof str !== 'string') { - throw new TypeError('expected a string: "' + util.inspect(str) + '"'); - } - - if (typeof patterns === 'string') { - if (utils.isEmptyString(str) || utils.isEmptyString(patterns)) { - return false; - } - - var equals = utils.equalsPattern(patterns, options); - if (equals(str)) { - return true; - } - var contains = utils.containsPattern(patterns, options); - if (contains(str)) { - return true; - } - } - - var opts = extend({}, options, {contains: true}); - return nanomatch.any(str, patterns, opts); -}; - -/** - * Returns true if the given pattern and options should enable - * the `matchBase` option. - * @return {Boolean} - * @api private - */ - -nanomatch.matchBase = function(pattern, options) { - if (pattern && pattern.indexOf('/') !== -1 || !options) return false; - return options.basename === true || options.matchBase === true; -}; - -/** - * Filter the keys of the given object with the given `glob` pattern - * and `options`. Does not attempt to match nested keys. If you need this feature, - * use [glob-object][] instead. - * - * ```js - * var nm = require('nanomatch'); - * nm.matchKeys(object, patterns[, options]); - * - * var obj = { aa: 'a', ab: 'b', ac: 'c' }; - * console.log(nm.matchKeys(obj, '*b')); - * //=> { ab: 'b' } - * ``` - * @param {Object} `object` The object with keys to filter. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Object} Returns an object with only keys that match the given patterns. - * @api public - */ - -nanomatch.matchKeys = function(obj, patterns, options) { - if (!utils.isObject(obj)) { - throw new TypeError('expected the first argument to be an object'); - } - var keys = nanomatch(Object.keys(obj), patterns, options); - return utils.pick(obj, keys); -}; - -/** - * Returns a memoized matcher function from the given glob `pattern` and `options`. - * The returned function takes a string to match as its only argument and returns - * true if the string is a match. - * - * ```js - * var nm = require('nanomatch'); - * nm.matcher(pattern[, options]); - * - * var isMatch = nm.matcher('*.!(*a)'); - * console.log(isMatch('a.a')); - * //=> false - * console.log(isMatch('a.b')); - * //=> true - * ``` - * @param {String} `pattern` Glob pattern - * @param {Object} `options` See available [options](#options) for changing how matches are performed. - * @return {Function} Returns a matcher function. - * @api public - */ - -nanomatch.matcher = function matcher(pattern, options) { - if (utils.isEmptyString(pattern)) { - return function() { - return false; - }; - } - - if (Array.isArray(pattern)) { - return compose(pattern, options, matcher); - } - - // if pattern is a regex - if (pattern instanceof RegExp) { - return test(pattern); - } - - // if pattern is invalid - if (!utils.isString(pattern)) { - throw new TypeError('expected pattern to be an array, string or regex'); - } - - // if pattern is a non-glob string - if (!utils.hasSpecialChars(pattern)) { - if (options && options.nocase === true) { - pattern = pattern.toLowerCase(); - } - return utils.matchPath(pattern, options); - } - - // if pattern is a glob string - var re = nanomatch.makeRe(pattern, options); - - // if `options.matchBase` or `options.basename` is defined - if (nanomatch.matchBase(pattern, options)) { - return utils.matchBasename(re, options); - } - - function test(regex) { - var equals = utils.equalsPattern(options); - var unixify = utils.unixify(options); - - return function(str) { - if (equals(str)) { - return true; - } - - if (regex.test(unixify(str))) { - return true; - } - return false; - }; - } - - // create matcher function - var matcherFn = test(re); - // set result object from compiler on matcher function, - // as a non-enumerable property. useful for debugging - utils.define(matcherFn, 'result', re.result); - return matcherFn; -}; - -/** - * Returns an array of matches captured by `pattern` in `string, or - * `null` if the pattern did not match. - * - * ```js - * var nm = require('nanomatch'); - * nm.capture(pattern, string[, options]); - * - * console.log(nm.capture('test/*.js', 'test/foo.js')); - * //=> ['foo'] - * console.log(nm.capture('test/*.js', 'foo/bar.css')); - * //=> null - * ``` - * @param {String} `pattern` Glob pattern to use for matching. - * @param {String} `string` String to match - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns an array of captures if the string matches the glob pattern, otherwise `null`. - * @api public - */ - -nanomatch.capture = function(pattern, str, options) { - var re = nanomatch.makeRe(pattern, extend({capture: true}, options)); - var unixify = utils.unixify(options); - - function match() { - return function(string) { - var match = re.exec(unixify(string)); - if (!match) { - return null; - } - - return match.slice(1); - }; - } - - var capture = memoize('capture', pattern, options, match); - return capture(str); -}; - -/** - * Create a regular expression from the given glob `pattern`. - * - * ```js - * var nm = require('nanomatch'); - * nm.makeRe(pattern[, options]); - * - * console.log(nm.makeRe('*.js')); - * //=> /^(?:(\.[\\\/])?(?!\.)(?=.)[^\/]*?\.js)$/ - * ``` - * @param {String} `pattern` A glob pattern to convert to regex. - * @param {Object} `options` See available [options](#options) for changing how matches are performed. - * @return {RegExp} Returns a regex created from the given pattern. - * @api public - */ - -nanomatch.makeRe = function(pattern, options) { - if (pattern instanceof RegExp) { - return pattern; - } - - if (typeof pattern !== 'string') { - throw new TypeError('expected pattern to be a string'); - } - - if (pattern.length > MAX_LENGTH) { - throw new Error('expected pattern to be less than ' + MAX_LENGTH + ' characters'); - } - - function makeRe() { - var opts = utils.extend({wrap: false}, options); - var result = nanomatch.create(pattern, opts); - var regex = toRegex(result.output, opts); - utils.define(regex, 'result', result); - return regex; - } - - return memoize('makeRe', pattern, options, makeRe); -}; - -/** - * Parses the given glob `pattern` and returns an object with the compiled `output` - * and optional source `map`. - * - * ```js - * var nm = require('nanomatch'); - * nm.create(pattern[, options]); - * - * console.log(nm.create('abc/*.js')); - * // { options: { source: 'string', sourcemap: true }, - * // state: {}, - * // compilers: - * // { ... }, - * // output: '(\\.[\\\\\\/])?abc\\/(?!\\.)(?=.)[^\\/]*?\\.js', - * // ast: - * // { type: 'root', - * // errors: [], - * // nodes: - * // [ ... ], - * // dot: false, - * // input: 'abc/*.js' }, - * // parsingErrors: [], - * // map: - * // { version: 3, - * // sources: [ 'string' ], - * // names: [], - * // mappings: 'AAAA,GAAG,EAAC,kBAAC,EAAC,EAAE', - * // sourcesContent: [ 'abc/*.js' ] }, - * // position: { line: 1, column: 28 }, - * // content: {}, - * // files: {}, - * // idx: 6 } - * ``` - * @param {String} `pattern` Glob pattern to parse and compile. - * @param {Object} `options` Any [options](#options) to change how parsing and compiling is performed. - * @return {Object} Returns an object with the parsed AST, compiled string and optional source map. - * @api public - */ - -nanomatch.create = function(pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected a string'); - } - function create() { - return nanomatch.compile(nanomatch.parse(pattern, options), options); - } - return memoize('create', pattern, options, create); -}; - -/** - * Parse the given `str` with the given `options`. - * - * ```js - * var nm = require('nanomatch'); - * nm.parse(pattern[, options]); - * - * var ast = nm.parse('a/{b,c}/d'); - * console.log(ast); - * // { type: 'root', - * // errors: [], - * // input: 'a/{b,c}/d', - * // nodes: - * // [ { type: 'bos', val: '' }, - * // { type: 'text', val: 'a/' }, - * // { type: 'brace', - * // nodes: - * // [ { type: 'brace.open', val: '{' }, - * // { type: 'text', val: 'b,c' }, - * // { type: 'brace.close', val: '}' } ] }, - * // { type: 'text', val: '/d' }, - * // { type: 'eos', val: '' } ] } - * ``` - * @param {String} `str` - * @param {Object} `options` - * @return {Object} Returns an AST - * @api public - */ - -nanomatch.parse = function(pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected a string'); - } - - function parse() { - var snapdragon = utils.instantiate(null, options); - parsers(snapdragon, options); - - var ast = snapdragon.parse(pattern, options); - utils.define(ast, 'snapdragon', snapdragon); - ast.input = pattern; - return ast; - } - - return memoize('parse', pattern, options, parse); -}; - -/** - * Compile the given `ast` or string with the given `options`. - * - * ```js - * var nm = require('nanomatch'); - * nm.compile(ast[, options]); - * - * var ast = nm.parse('a/{b,c}/d'); - * console.log(nm.compile(ast)); - * // { options: { source: 'string' }, - * // state: {}, - * // compilers: - * // { eos: [Function], - * // noop: [Function], - * // bos: [Function], - * // brace: [Function], - * // 'brace.open': [Function], - * // text: [Function], - * // 'brace.close': [Function] }, - * // output: [ 'a/(b|c)/d' ], - * // ast: - * // { ... }, - * // parsingErrors: [] } - * ``` - * @param {Object|String} `ast` - * @param {Object} `options` - * @return {Object} Returns an object that has an `output` property with the compiled string. - * @api public - */ - -nanomatch.compile = function(ast, options) { - if (typeof ast === 'string') { - ast = nanomatch.parse(ast, options); - } - - function compile() { - var snapdragon = utils.instantiate(ast, options); - compilers(snapdragon, options); - return snapdragon.compile(ast, options); - } - - return memoize('compile', ast.input, options, compile); -}; - -/** - * Clear the regex cache. - * - * ```js - * nm.clearCache(); - * ``` - * @api public - */ - -nanomatch.clearCache = function() { - nanomatch.cache.__data__ = {}; -}; - -/** - * Compose a matcher function with the given patterns. - * This allows matcher functions to be compiled once and - * called multiple times. - */ - -function compose(patterns, options, matcher) { - var matchers; - - return memoize('compose', String(patterns), options, function() { - return function(file) { - // delay composition until it's invoked the first time, - // after that it won't be called again - if (!matchers) { - matchers = []; - for (var i = 0; i < patterns.length; i++) { - matchers.push(matcher(patterns[i], options)); - } - } - - var len = matchers.length; - while (len--) { - if (matchers[len](file) === true) { - return true; - } - } - return false; - }; - }); -} - -/** - * Memoize a generated regex or function. A unique key is generated - * from the `type` (usually method name), the `pattern`, and - * user-defined options. - */ - -function memoize(type, pattern, options, fn) { - var key = utils.createKey(type + '=' + pattern, options); - - if (options && options.cache === false) { - return fn(pattern, options); - } - - if (cache.has(type, key)) { - return cache.get(type, key); - } - - var val = fn(pattern, options); - cache.set(type, key, val); - return val; -} - -/** - * Expose compiler, parser and cache on `nanomatch` - */ - -nanomatch.compilers = compilers; -nanomatch.parsers = parsers; -nanomatch.cache = cache; - -/** - * Expose `nanomatch` - * @type {Function} - */ - -module.exports = nanomatch; - - -/***/ }), -/* 686 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/** -* Nanomatch compilers -*/ - -module.exports = function(nanomatch, options) { - function slash() { - if (options && typeof options.slash === 'string') { - return options.slash; - } - if (options && typeof options.slash === 'function') { - return options.slash.call(nanomatch); - } - return '\\\\/'; - } - - function star() { - if (options && typeof options.star === 'string') { - return options.star; - } - if (options && typeof options.star === 'function') { - return options.star.call(nanomatch); - } - return '[^' + slash() + ']*?'; - } - - var ast = nanomatch.ast = nanomatch.parser.ast; - ast.state = nanomatch.parser.state; - nanomatch.compiler.state = ast.state; - nanomatch.compiler - - /** - * Negation / escaping - */ - - .set('not', function(node) { - var prev = this.prev(); - if (this.options.nonegate === true || prev.type !== 'bos') { - return this.emit('\\' + node.val, node); - } - return this.emit(node.val, node); - }) - .set('escape', function(node) { - if (this.options.unescape && /^[-\w_.]/.test(node.val)) { - return this.emit(node.val, node); - } - return this.emit('\\' + node.val, node); - }) - .set('quoted', function(node) { - return this.emit(node.val, node); - }) - - /** - * Regex - */ - - .set('dollar', function(node) { - if (node.parent.type === 'bracket') { - return this.emit(node.val, node); - } - return this.emit('\\' + node.val, node); - }) - - /** - * Dot: "." - */ - - .set('dot', function(node) { - if (node.dotfiles === true) this.dotfiles = true; - return this.emit('\\' + node.val, node); - }) - - /** - * Slashes: "/" and "\" - */ - - .set('backslash', function(node) { - return this.emit(node.val, node); - }) - .set('slash', function(node, nodes, i) { - var val = '[' + slash() + ']'; - var parent = node.parent; - var prev = this.prev(); - - // set "node.hasSlash" to true on all ancestor parens nodes - while (parent.type === 'paren' && !parent.hasSlash) { - parent.hasSlash = true; - parent = parent.parent; - } - - if (prev.addQmark) { - val += '?'; - } - - // word boundary - if (node.rest.slice(0, 2) === '\\b') { - return this.emit(val, node); - } - - // globstars - if (node.parsed === '**' || node.parsed === './**') { - this.output = '(?:' + this.output; - return this.emit(val + ')?', node); - } - - // negation - if (node.parsed === '!**' && this.options.nonegate !== true) { - return this.emit(val + '?\\b', node); - } - return this.emit(val, node); - }) - - /** - * Square brackets - */ - - .set('bracket', function(node) { - var close = node.close; - var open = !node.escaped ? '[' : '\\['; - var negated = node.negated; - var inner = node.inner; - var val = node.val; - - if (node.escaped === true) { - inner = inner.replace(/\\?(\W)/g, '\\$1'); - negated = ''; - } - - if (inner === ']-') { - inner = '\\]\\-'; - } - - if (negated && inner.indexOf('.') === -1) { - inner += '.'; - } - if (negated && inner.indexOf('/') === -1) { - inner += '/'; - } - - val = open + negated + inner + close; - return this.emit(val, node); - }) - - /** - * Square: "[.]" (only matches a single character in brackets) - */ - - .set('square', function(node) { - var val = (/^\W/.test(node.val) ? '\\' : '') + node.val; - return this.emit(val, node); - }) - - /** - * Question mark: "?" - */ - - .set('qmark', function(node) { - var prev = this.prev(); - // don't use "slash" variable so that we always avoid - // matching backslashes and slashes with a qmark - var val = '[^.\\\\/]'; - if (this.options.dot || (prev.type !== 'bos' && prev.type !== 'slash')) { - val = '[^\\\\/]'; - } - - if (node.parsed.slice(-1) === '(') { - var ch = node.rest.charAt(0); - if (ch === '!' || ch === '=' || ch === ':') { - return this.emit(node.val, node); - } - } - - if (node.val.length > 1) { - val += '{' + node.val.length + '}'; - } - return this.emit(val, node); - }) - - /** - * Plus - */ - - .set('plus', function(node) { - var prev = node.parsed.slice(-1); - if (prev === ']' || prev === ')') { - return this.emit(node.val, node); - } - if (!this.output || (/[?*+]/.test(ch) && node.parent.type !== 'bracket')) { - return this.emit('\\+', node); - } - var ch = this.output.slice(-1); - if (/\w/.test(ch) && !node.inside) { - return this.emit('+\\+?', node); - } - return this.emit('+', node); - }) - - /** - * globstar: '**' - */ - - .set('globstar', function(node, nodes, i) { - if (!this.output) { - this.state.leadingGlobstar = true; - } - - var prev = this.prev(); - var before = this.prev(2); - var next = this.next(); - var after = this.next(2); - var type = prev.type; - var val = node.val; - - if (prev.type === 'slash' && next.type === 'slash') { - if (before.type === 'text') { - this.output += '?'; - - if (after.type !== 'text') { - this.output += '\\b'; - } - } - } - - var parsed = node.parsed; - if (parsed.charAt(0) === '!') { - parsed = parsed.slice(1); - } - - var isInside = node.isInside.paren || node.isInside.brace; - if (parsed && type !== 'slash' && type !== 'bos' && !isInside) { - val = star(); - } else { - val = this.options.dot !== true - ? '(?:(?!(?:[' + slash() + ']|^)\\.).)*?' - : '(?:(?!(?:[' + slash() + ']|^)(?:\\.{1,2})($|[' + slash() + ']))(?!\\.{2}).)*?'; - } - - if ((type === 'slash' || type === 'bos') && this.options.dot !== true) { - val = '(?!\\.)' + val; - } - - if (prev.type === 'slash' && next.type === 'slash' && before.type !== 'text') { - if (after.type === 'text' || after.type === 'star') { - node.addQmark = true; - } - } - - if (this.options.capture) { - val = '(' + val + ')'; - } - - return this.emit(val, node); - }) - - /** - * Star: "*" - */ - - .set('star', function(node, nodes, i) { - var prior = nodes[i - 2] || {}; - var prev = this.prev(); - var next = this.next(); - var type = prev.type; - - function isStart(n) { - return n.type === 'bos' || n.type === 'slash'; - } - - if (this.output === '' && this.options.contains !== true) { - this.output = '(?![' + slash() + '])'; - } - - if (type === 'bracket' && this.options.bash === false) { - var str = next && next.type === 'bracket' ? star() : '*?'; - if (!prev.nodes || prev.nodes[1].type !== 'posix') { - return this.emit(str, node); - } - } - - var prefix = !this.dotfiles && type !== 'text' && type !== 'escape' - ? (this.options.dot ? '(?!(?:^|[' + slash() + '])\\.{1,2}(?:$|[' + slash() + ']))' : '(?!\\.)') - : ''; - - if (isStart(prev) || (isStart(prior) && type === 'not')) { - if (prefix !== '(?!\\.)') { - prefix += '(?!(\\.{2}|\\.[' + slash() + ']))(?=.)'; - } else { - prefix += '(?=.)'; - } - } else if (prefix === '(?!\\.)') { - prefix = ''; - } - - if (prev.type === 'not' && prior.type === 'bos' && this.options.dot === true) { - this.output = '(?!\\.)' + this.output; - } - - var output = prefix + star(); - if (this.options.capture) { - output = '(' + output + ')'; - } - - return this.emit(output, node); - }) - - /** - * Text - */ - - .set('text', function(node) { - return this.emit(node.val, node); - }) - - /** - * End-of-string - */ - - .set('eos', function(node) { - var prev = this.prev(); - var val = node.val; - - this.output = '(?:\\.[' + slash() + '](?=.))?' + this.output; - if (this.state.metachar && prev.type !== 'qmark' && prev.type !== 'slash') { - val += (this.options.contains ? '[' + slash() + ']?' : '(?:[' + slash() + ']|$)'); - } - - return this.emit(val, node); - }); - - /** - * Allow custom compilers to be passed on options - */ - - if (options && typeof options.compilers === 'function') { - options.compilers(nanomatch.compiler); - } -}; - - - -/***/ }), -/* 687 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var regexNot = __webpack_require__(599); -var toRegex = __webpack_require__(582); - -/** - * Characters to use in negation regex (we want to "not" match - * characters that are matched by other parsers) - */ - -var cached; -var NOT_REGEX = '[\\[!*+?$^"\'.\\\\/]+'; -var not = createTextRegex(NOT_REGEX); - -/** - * Nanomatch parsers - */ - -module.exports = function(nanomatch, options) { - var parser = nanomatch.parser; - var opts = parser.options; - - parser.state = { - slashes: 0, - paths: [] - }; - - parser.ast.state = parser.state; - parser - - /** - * Beginning-of-string - */ - - .capture('prefix', function() { - if (this.parsed) return; - var m = this.match(/^\.[\\/]/); - if (!m) return; - this.state.strictOpen = !!this.options.strictOpen; - this.state.addPrefix = true; - }) - - /** - * Escape: "\\." - */ - - .capture('escape', function() { - if (this.isInside('bracket')) return; - var pos = this.position(); - var m = this.match(/^(?:\\(.)|([$^]))/); - if (!m) return; - - return pos({ - type: 'escape', - val: m[2] || m[1] - }); - }) - - /** - * Quoted strings - */ - - .capture('quoted', function() { - var pos = this.position(); - var m = this.match(/^["']/); - if (!m) return; - - var quote = m[0]; - if (this.input.indexOf(quote) === -1) { - return pos({ - type: 'escape', - val: quote - }); - } - - var tok = advanceTo(this.input, quote); - this.consume(tok.len); - - return pos({ - type: 'quoted', - val: tok.esc - }); - }) - - /** - * Negations: "!" - */ - - .capture('not', function() { - var parsed = this.parsed; - var pos = this.position(); - var m = this.match(this.notRegex || /^!+/); - if (!m) return; - var val = m[0]; - - var isNegated = (val.length % 2) === 1; - if (parsed === '' && !isNegated) { - val = ''; - } - - // if nothing has been parsed, we know `!` is at the start, - // so we need to wrap the result in a negation regex - if (parsed === '' && isNegated && this.options.nonegate !== true) { - this.bos.val = '(?!^(?:'; - this.append = ')$).*'; - val = ''; - } - return pos({ - type: 'not', - val: val - }); - }) - - /** - * Dot: "." - */ - - .capture('dot', function() { - var parsed = this.parsed; - var pos = this.position(); - var m = this.match(/^\.+/); - if (!m) return; - - var val = m[0]; - this.state.dot = val === '.' && (parsed === '' || parsed.slice(-1) === '/'); - - return pos({ - type: 'dot', - dotfiles: this.state.dot, - val: val - }); - }) - - /** - * Plus: "+" - */ - - .capture('plus', /^\+(?!\()/) - - /** - * Question mark: "?" - */ - - .capture('qmark', function() { - var parsed = this.parsed; - var pos = this.position(); - var m = this.match(/^\?+(?!\()/); - if (!m) return; - - this.state.metachar = true; - this.state.qmark = true; - - return pos({ - type: 'qmark', - parsed: parsed, - val: m[0] - }); - }) - - /** - * Globstar: "**" - */ - - .capture('globstar', function() { - var parsed = this.parsed; - var pos = this.position(); - var m = this.match(/^\*{2}(?![*(])(?=[,)/]|$)/); - if (!m) return; - - var type = opts.noglobstar !== true ? 'globstar' : 'star'; - var node = pos({type: type, parsed: parsed}); - this.state.metachar = true; - - while (this.input.slice(0, 4) === '/**/') { - this.input = this.input.slice(3); - } - - node.isInside = { - brace: this.isInside('brace'), - paren: this.isInside('paren') - }; - - if (type === 'globstar') { - this.state.globstar = true; - node.val = '**'; - - } else { - this.state.star = true; - node.val = '*'; - } - - return node; - }) - - /** - * Star: "*" - */ - - .capture('star', function() { - var pos = this.position(); - var starRe = /^(?:\*(?![*(])|[*]{3,}(?!\()|[*]{2}(?![(/]|$)|\*(?=\*\())/; - var m = this.match(starRe); - if (!m) return; - - this.state.metachar = true; - this.state.star = true; - return pos({ - type: 'star', - val: m[0] - }); - }) - - /** - * Slash: "/" - */ - - .capture('slash', function() { - var pos = this.position(); - var m = this.match(/^\//); - if (!m) return; - - this.state.slashes++; - return pos({ - type: 'slash', - val: m[0] - }); - }) - - /** - * Backslash: "\\" - */ - - .capture('backslash', function() { - var pos = this.position(); - var m = this.match(/^\\(?![*+?(){}[\]'"])/); - if (!m) return; - - var val = m[0]; - - if (this.isInside('bracket')) { - val = '\\'; - } else if (val.length > 1) { - val = '\\\\'; - } - - return pos({ - type: 'backslash', - val: val - }); - }) - - /** - * Square: "[.]" - */ - - .capture('square', function() { - if (this.isInside('bracket')) return; - var pos = this.position(); - var m = this.match(/^\[([^!^\\])\]/); - if (!m) return; - - return pos({ - type: 'square', - val: m[1] - }); - }) - - /** - * Brackets: "[...]" (basic, this can be overridden by other parsers) - */ - - .capture('bracket', function() { - var pos = this.position(); - var m = this.match(/^(?:\[([!^]?)([^\]]+|\]-)(\]|[^*+?]+)|\[)/); - if (!m) return; - - var val = m[0]; - var negated = m[1] ? '^' : ''; - var inner = (m[2] || '').replace(/\\\\+/, '\\\\'); - var close = m[3] || ''; - - if (m[2] && inner.length < m[2].length) { - val = val.replace(/\\\\+/, '\\\\'); - } - - var esc = this.input.slice(0, 2); - if (inner === '' && esc === '\\]') { - inner += esc; - this.consume(2); - - var str = this.input; - var idx = -1; - var ch; - - while ((ch = str[++idx])) { - this.consume(1); - if (ch === ']') { - close = ch; - break; - } - inner += ch; - } - } - - return pos({ - type: 'bracket', - val: val, - escaped: close !== ']', - negated: negated, - inner: inner, - close: close - }); - }) - - /** - * Text - */ - - .capture('text', function() { - if (this.isInside('bracket')) return; - var pos = this.position(); - var m = this.match(not); - if (!m || !m[0]) return; - - return pos({ - type: 'text', - val: m[0] - }); - }); - - /** - * Allow custom parsers to be passed on options - */ - - if (options && typeof options.parsers === 'function') { - options.parsers(nanomatch.parser); - } -}; - -/** - * Advance to the next non-escaped character - */ - -function advanceTo(input, endChar) { - var ch = input.charAt(0); - var tok = { len: 1, val: '', esc: '' }; - var idx = 0; - - function advance() { - if (ch !== '\\') { - tok.esc += '\\' + ch; - tok.val += ch; - } - - ch = input.charAt(++idx); - tok.len++; - - if (ch === '\\') { - advance(); - advance(); - } - } - - while (ch && ch !== endChar) { - advance(); - } - return tok; -} - -/** - * Create text regex - */ - -function createTextRegex(pattern) { - if (cached) return cached; - var opts = {contains: true, strictClose: false}; - var not = regexNot.create(pattern, opts); - var re = toRegex('^(?:[*]\\((?=.)|' + not + ')', opts); - return (cached = re); -} - -/** - * Expose negation string - */ - -module.exports.not = NOT_REGEX; - - -/***/ }), -/* 688 */ -/***/ (function(module, exports, __webpack_require__) { - -module.exports = new (__webpack_require__(689))(); - - -/***/ }), -/* 689 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * fragment-cache - * - * Copyright (c) 2016-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var MapCache = __webpack_require__(682); - -/** - * Create a new `FragmentCache` with an optional object to use for `caches`. - * - * ```js - * var fragment = new FragmentCache(); - * ``` - * @name FragmentCache - * @param {String} `cacheName` - * @return {Object} Returns the [map-cache][] instance. - * @api public - */ - -function FragmentCache(caches) { - this.caches = caches || {}; -} - -/** - * Prototype - */ - -FragmentCache.prototype = { - - /** - * Get cache `name` from the `fragment.caches` object. Creates a new - * `MapCache` if it doesn't already exist. - * - * ```js - * var cache = fragment.cache('files'); - * console.log(fragment.caches.hasOwnProperty('files')); - * //=> true - * ``` - * @name .cache - * @param {String} `cacheName` - * @return {Object} Returns the [map-cache][] instance. - * @api public - */ - - cache: function(cacheName) { - return this.caches[cacheName] || (this.caches[cacheName] = new MapCache()); - }, - - /** - * Set a value for property `key` on cache `name` - * - * ```js - * fragment.set('files', 'somefile.js', new File({path: 'somefile.js'})); - * ``` - * @name .set - * @param {String} `name` - * @param {String} `key` Property name to set - * @param {any} `val` The value of `key` - * @return {Object} The cache instance for chaining - * @api public - */ - - set: function(cacheName, key, val) { - var cache = this.cache(cacheName); - cache.set(key, val); - return cache; - }, - - /** - * Returns true if a non-undefined value is set for `key` on fragment cache `name`. - * - * ```js - * var cache = fragment.cache('files'); - * cache.set('somefile.js'); - * - * console.log(cache.has('somefile.js')); - * //=> true - * - * console.log(cache.has('some-other-file.js')); - * //=> false - * ``` - * @name .has - * @param {String} `name` Cache name - * @param {String} `key` Optionally specify a property to check for on cache `name` - * @return {Boolean} - * @api public - */ - - has: function(cacheName, key) { - return typeof this.get(cacheName, key) !== 'undefined'; - }, - - /** - * Get `name`, or if specified, the value of `key`. Invokes the [cache]() method, - * so that cache `name` will be created it doesn't already exist. If `key` is not passed, - * the entire cache (`name`) is returned. - * - * ```js - * var Vinyl = require('vinyl'); - * var cache = fragment.cache('files'); - * cache.set('somefile.js', new Vinyl({path: 'somefile.js'})); - * console.log(cache.get('somefile.js')); - * //=> - * ``` - * @name .get - * @param {String} `name` - * @return {Object} Returns cache `name`, or the value of `key` if specified - * @api public - */ - - get: function(name, key) { - var cache = this.cache(name); - if (typeof key === 'string') { - return cache.get(key); - } - return cache; - } -}; - -/** - * Expose `FragmentCache` - */ - -exports = module.exports = FragmentCache; - - -/***/ }), -/* 690 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var utils = module.exports; -var path = __webpack_require__(4); - -/** - * Module dependencies - */ - -var isWindows = __webpack_require__(691)(); -var Snapdragon = __webpack_require__(618); -utils.define = __webpack_require__(692); -utils.diff = __webpack_require__(693); -utils.extend = __webpack_require__(595); -utils.pick = __webpack_require__(694); -utils.typeOf = __webpack_require__(592); -utils.unique = __webpack_require__(600); - -/** - * Returns true if the given value is effectively an empty string - */ - -utils.isEmptyString = function(val) { - return String(val) === '' || String(val) === './'; -}; - -/** - * Returns true if the platform is windows, or `path.sep` is `\\`. - * This is defined as a function to allow `path.sep` to be set in unit tests, - * or by the user, if there is a reason to do so. - * @return {Boolean} - */ - -utils.isWindows = function() { - return path.sep === '\\' || isWindows === true; -}; - -/** - * Return the last element from an array - */ - -utils.last = function(arr, n) { - return arr[arr.length - (n || 1)]; -}; - -/** - * Get the `Snapdragon` instance to use - */ - -utils.instantiate = function(ast, options) { - var snapdragon; - // if an instance was created by `.parse`, use that instance - if (utils.typeOf(ast) === 'object' && ast.snapdragon) { - snapdragon = ast.snapdragon; - // if the user supplies an instance on options, use that instance - } else if (utils.typeOf(options) === 'object' && options.snapdragon) { - snapdragon = options.snapdragon; - // create a new instance - } else { - snapdragon = new Snapdragon(options); - } - - utils.define(snapdragon, 'parse', function(str, options) { - var parsed = Snapdragon.prototype.parse.call(this, str, options); - parsed.input = str; - - // escape unmatched brace/bracket/parens - var last = this.parser.stack.pop(); - if (last && this.options.strictErrors !== true) { - var open = last.nodes[0]; - var inner = last.nodes[1]; - if (last.type === 'bracket') { - if (inner.val.charAt(0) === '[') { - inner.val = '\\' + inner.val; - } - - } else { - open.val = '\\' + open.val; - var sibling = open.parent.nodes[1]; - if (sibling.type === 'star') { - sibling.loose = true; - } - } - } - - // add non-enumerable parser reference - utils.define(parsed, 'parser', this.parser); - return parsed; - }); - - return snapdragon; -}; - -/** - * Create the key to use for memoization. The key is generated - * by iterating over the options and concatenating key-value pairs - * to the pattern string. - */ - -utils.createKey = function(pattern, options) { - if (typeof options === 'undefined') { - return pattern; - } - var key = pattern; - for (var prop in options) { - if (options.hasOwnProperty(prop)) { - key += ';' + prop + '=' + String(options[prop]); - } - } - return key; -}; - -/** - * Cast `val` to an array - * @return {Array} - */ - -utils.arrayify = function(val) { - if (typeof val === 'string') return [val]; - return val ? (Array.isArray(val) ? val : [val]) : []; -}; - -/** - * Return true if `val` is a non-empty string - */ - -utils.isString = function(val) { - return typeof val === 'string'; -}; - -/** - * Return true if `val` is a non-empty string - */ - -utils.isRegex = function(val) { - return utils.typeOf(val) === 'regexp'; -}; - -/** - * Return true if `val` is a non-empty string - */ - -utils.isObject = function(val) { - return utils.typeOf(val) === 'object'; -}; - -/** - * Escape regex characters in the given string - */ - -utils.escapeRegex = function(str) { - return str.replace(/[-[\]{}()^$|*+?.\\/\s]/g, '\\$&'); -}; - -/** - * Combines duplicate characters in the provided `input` string. - * @param {String} `input` - * @returns {String} - */ - -utils.combineDupes = function(input, patterns) { - patterns = utils.arrayify(patterns).join('|').split('|'); - patterns = patterns.map(function(s) { - return s.replace(/\\?([+*\\/])/g, '\\$1'); - }); - var substr = patterns.join('|'); - var regex = new RegExp('(' + substr + ')(?=\\1)', 'g'); - return input.replace(regex, ''); -}; - -/** - * Returns true if the given `str` has special characters - */ - -utils.hasSpecialChars = function(str) { - return /(?:(?:(^|\/)[!.])|[*?+()|[\]{}]|[+@]\()/.test(str); -}; - -/** - * Normalize slashes in the given filepath. - * - * @param {String} `filepath` - * @return {String} - */ - -utils.toPosixPath = function(str) { - return str.replace(/\\+/g, '/'); -}; - -/** - * Strip backslashes before special characters in a string. - * - * @param {String} `str` - * @return {String} - */ - -utils.unescape = function(str) { - return utils.toPosixPath(str.replace(/\\(?=[*+?!.])/g, '')); -}; - -/** - * Strip the drive letter from a windows filepath - * @param {String} `fp` - * @return {String} - */ - -utils.stripDrive = function(fp) { - return utils.isWindows() ? fp.replace(/^[a-z]:[\\/]+?/i, '/') : fp; -}; - -/** - * Strip the prefix from a filepath - * @param {String} `fp` - * @return {String} - */ - -utils.stripPrefix = function(str) { - if (str.charAt(0) === '.' && (str.charAt(1) === '/' || str.charAt(1) === '\\')) { - return str.slice(2); - } - return str; -}; - -/** - * Returns true if `str` is a common character that doesn't need - * to be processed to be used for matching. - * @param {String} `str` - * @return {Boolean} - */ - -utils.isSimpleChar = function(str) { - return str.trim() === '' || str === '.'; -}; - -/** - * Returns true if the given str is an escaped or - * unescaped path character - */ - -utils.isSlash = function(str) { - return str === '/' || str === '\\/' || str === '\\' || str === '\\\\'; -}; - -/** - * Returns a function that returns true if the given - * pattern matches or contains a `filepath` - * - * @param {String} `pattern` - * @return {Function} - */ - -utils.matchPath = function(pattern, options) { - return (options && options.contains) - ? utils.containsPattern(pattern, options) - : utils.equalsPattern(pattern, options); -}; - -/** - * Returns true if the given (original) filepath or unixified path are equal - * to the given pattern. - */ - -utils._equals = function(filepath, unixPath, pattern) { - return pattern === filepath || pattern === unixPath; -}; - -/** - * Returns true if the given (original) filepath or unixified path contain - * the given pattern. - */ - -utils._contains = function(filepath, unixPath, pattern) { - return filepath.indexOf(pattern) !== -1 || unixPath.indexOf(pattern) !== -1; -}; - -/** - * Returns a function that returns true if the given - * pattern is the same as a given `filepath` - * - * @param {String} `pattern` - * @return {Function} - */ - -utils.equalsPattern = function(pattern, options) { - var unixify = utils.unixify(options); - options = options || {}; - - return function fn(filepath) { - var equal = utils._equals(filepath, unixify(filepath), pattern); - if (equal === true || options.nocase !== true) { - return equal; - } - var lower = filepath.toLowerCase(); - return utils._equals(lower, unixify(lower), pattern); - }; -}; - -/** - * Returns a function that returns true if the given - * pattern contains a `filepath` - * - * @param {String} `pattern` - * @return {Function} - */ - -utils.containsPattern = function(pattern, options) { - var unixify = utils.unixify(options); - options = options || {}; - - return function(filepath) { - var contains = utils._contains(filepath, unixify(filepath), pattern); - if (contains === true || options.nocase !== true) { - return contains; - } - var lower = filepath.toLowerCase(); - return utils._contains(lower, unixify(lower), pattern); - }; -}; - -/** - * Returns a function that returns true if the given - * regex matches the `filename` of a file path. - * - * @param {RegExp} `re` Matching regex - * @return {Function} - */ - -utils.matchBasename = function(re) { - return function(filepath) { - return re.test(filepath) || re.test(path.basename(filepath)); - }; -}; - -/** - * Returns the given value unchanced. - * @return {any} - */ - -utils.identity = function(val) { - return val; -}; - -/** - * Determines the filepath to return based on the provided options. - * @return {any} - */ - -utils.value = function(str, unixify, options) { - if (options && options.unixify === false) { - return str; - } - if (options && typeof options.unixify === 'function') { - return options.unixify(str); - } - return unixify(str); -}; - -/** - * Returns a function that normalizes slashes in a string to forward - * slashes, strips `./` from beginning of paths, and optionally unescapes - * special characters. - * @return {Function} - */ - -utils.unixify = function(options) { - var opts = options || {}; - return function(filepath) { - if (opts.stripPrefix !== false) { - filepath = utils.stripPrefix(filepath); - } - if (opts.unescape === true) { - filepath = utils.unescape(filepath); - } - if (opts.unixify === true || utils.isWindows()) { - filepath = utils.toPosixPath(filepath); - } - return filepath; - }; -}; - - -/***/ }), -/* 691 */ -/***/ (function(module, exports, __webpack_require__) { - -var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! - * is-windows - * - * Copyright © 2015-2018, Jon Schlinkert. - * Released under the MIT License. - */ - -(function(factory) { - if (exports && typeof exports === 'object' && typeof module !== 'undefined') { - module.exports = factory(); - } else if (true) { - !(__WEBPACK_AMD_DEFINE_ARRAY__ = [], __WEBPACK_AMD_DEFINE_FACTORY__ = (factory), - __WEBPACK_AMD_DEFINE_RESULT__ = (typeof __WEBPACK_AMD_DEFINE_FACTORY__ === 'function' ? - (__WEBPACK_AMD_DEFINE_FACTORY__.apply(exports, __WEBPACK_AMD_DEFINE_ARRAY__)) : __WEBPACK_AMD_DEFINE_FACTORY__), - __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); - } else {} -})(function() { - 'use strict'; - return function isWindows() { - return process && (process.platform === 'win32' || /^(msys|cygwin)$/.test(process.env.OSTYPE)); - }; -}); - - -/***/ }), -/* 692 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * define-property - * - * Copyright (c) 2015-2018, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var isobject = __webpack_require__(590); -var isDescriptor = __webpack_require__(591); -var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) - ? Reflect.defineProperty - : Object.defineProperty; - -module.exports = function defineProperty(obj, key, val) { - if (!isobject(obj) && typeof obj !== 'function' && !Array.isArray(obj)) { - throw new TypeError('expected an object, function, or array'); - } - - if (typeof key !== 'string') { - throw new TypeError('expected "key" to be a string'); - } - - if (isDescriptor(val)) { - define(obj, key, val); - return obj; - } - - define(obj, key, { - configurable: true, - enumerable: false, - writable: true, - value: val - }); - - return obj; -}; - - -/***/ }), -/* 693 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * arr-diff - * - * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -module.exports = function diff(arr/*, arrays*/) { - var len = arguments.length; - var idx = 0; - while (++idx < len) { - arr = diffArray(arr, arguments[idx]); - } - return arr; -}; - -function diffArray(one, two) { - if (!Array.isArray(two)) { - return one.slice(); - } - - var tlen = two.length - var olen = one.length; - var idx = -1; - var arr = []; - - while (++idx < olen) { - var ele = one[idx]; - - var hasEle = false; - for (var i = 0; i < tlen; i++) { - var val = two[i]; - - if (ele === val) { - hasEle = true; - break; - } - } - - if (hasEle === false) { - arr.push(ele); - } - } - return arr; -} - - -/***/ }), -/* 694 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * object.pick - * - * Copyright (c) 2014-2015 Jon Schlinkert, contributors. - * Licensed under the MIT License - */ - - - -var isObject = __webpack_require__(590); - -module.exports = function pick(obj, keys) { - if (!isObject(obj) && typeof obj !== 'function') { - return {}; - } - - var res = {}; - if (typeof keys === 'string') { - if (keys in obj) { - res[keys] = obj[keys]; - } - return res; - } - - var len = keys.length; - var idx = -1; - - while (++idx < len) { - var key = keys[idx]; - if (key in obj) { - res[key] = obj[key]; - } - } - return res; -}; - - -/***/ }), -/* 695 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/** - * Module dependencies - */ - -var extend = __webpack_require__(696); -var unique = __webpack_require__(600); -var toRegex = __webpack_require__(582); - -/** - * Local dependencies - */ - -var compilers = __webpack_require__(697); -var parsers = __webpack_require__(709); -var Extglob = __webpack_require__(712); -var utils = __webpack_require__(711); -var MAX_LENGTH = 1024 * 64; - -/** - * Convert the given `extglob` pattern into a regex-compatible string. Returns - * an object with the compiled result and the parsed AST. - * - * ```js - * var extglob = require('extglob'); - * console.log(extglob('*.!(*a)')); - * //=> '(?!\\.)[^/]*?\\.(?!(?!\\.)[^/]*?a\\b).*?' - * ``` - * @param {String} `pattern` - * @param {Object} `options` - * @return {String} - * @api public - */ - -function extglob(pattern, options) { - return extglob.create(pattern, options).output; -} - -/** - * Takes an array of strings and an extglob pattern and returns a new - * array that contains only the strings that match the pattern. - * - * ```js - * var extglob = require('extglob'); - * console.log(extglob.match(['a.a', 'a.b', 'a.c'], '*.!(*a)')); - * //=> ['a.b', 'a.c'] - * ``` - * @param {Array} `list` Array of strings to match - * @param {String} `pattern` Extglob pattern - * @param {Object} `options` - * @return {Array} Returns an array of matches - * @api public - */ - -extglob.match = function(list, pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected pattern to be a string'); - } - - list = utils.arrayify(list); - var isMatch = extglob.matcher(pattern, options); - var len = list.length; - var idx = -1; - var matches = []; - - while (++idx < len) { - var ele = list[idx]; - - if (isMatch(ele)) { - matches.push(ele); - } - } - - // if no options were passed, uniquify results and return - if (typeof options === 'undefined') { - return unique(matches); - } - - if (matches.length === 0) { - if (options.failglob === true) { - throw new Error('no matches found for "' + pattern + '"'); - } - if (options.nonull === true || options.nullglob === true) { - return [pattern.split('\\').join('')]; - } - } - - return options.nodupes !== false ? unique(matches) : matches; -}; - -/** - * Returns true if the specified `string` matches the given - * extglob `pattern`. - * - * ```js - * var extglob = require('extglob'); - * - * console.log(extglob.isMatch('a.a', '*.!(*a)')); - * //=> false - * console.log(extglob.isMatch('a.b', '*.!(*a)')); - * //=> true - * ``` - * @param {String} `string` String to match - * @param {String} `pattern` Extglob pattern - * @param {String} `options` - * @return {Boolean} - * @api public - */ - -extglob.isMatch = function(str, pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected pattern to be a string'); - } - - if (typeof str !== 'string') { - throw new TypeError('expected a string'); - } - - if (pattern === str) { - return true; - } - - if (pattern === '' || pattern === ' ' || pattern === '.') { - return pattern === str; - } - - var isMatch = utils.memoize('isMatch', pattern, options, extglob.matcher); - return isMatch(str); -}; - -/** - * Returns true if the given `string` contains the given pattern. Similar to `.isMatch` but - * the pattern can match any part of the string. - * - * ```js - * var extglob = require('extglob'); - * console.log(extglob.contains('aa/bb/cc', '*b')); - * //=> true - * console.log(extglob.contains('aa/bb/cc', '*d')); - * //=> false - * ``` - * @param {String} `str` The string to match. - * @param {String} `pattern` Glob pattern to use for matching. - * @param {Object} `options` - * @return {Boolean} Returns true if the patter matches any part of `str`. - * @api public - */ - -extglob.contains = function(str, pattern, options) { - if (typeof str !== 'string') { - throw new TypeError('expected a string'); - } - - if (pattern === '' || pattern === ' ' || pattern === '.') { - return pattern === str; - } - - var opts = extend({}, options, {contains: true}); - opts.strictClose = false; - opts.strictOpen = false; - return extglob.isMatch(str, pattern, opts); -}; - -/** - * Takes an extglob pattern and returns a matcher function. The returned - * function takes the string to match as its only argument. - * - * ```js - * var extglob = require('extglob'); - * var isMatch = extglob.matcher('*.!(*a)'); - * - * console.log(isMatch('a.a')); - * //=> false - * console.log(isMatch('a.b')); - * //=> true - * ``` - * @param {String} `pattern` Extglob pattern - * @param {String} `options` - * @return {Boolean} - * @api public - */ - -extglob.matcher = function(pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected pattern to be a string'); - } - - function matcher() { - var re = extglob.makeRe(pattern, options); - return function(str) { - return re.test(str); - }; - } - - return utils.memoize('matcher', pattern, options, matcher); -}; - -/** - * Convert the given `extglob` pattern into a regex-compatible string. Returns - * an object with the compiled result and the parsed AST. - * - * ```js - * var extglob = require('extglob'); - * console.log(extglob.create('*.!(*a)').output); - * //=> '(?!\\.)[^/]*?\\.(?!(?!\\.)[^/]*?a\\b).*?' - * ``` - * @param {String} `str` - * @param {Object} `options` - * @return {String} - * @api public - */ - -extglob.create = function(pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected pattern to be a string'); - } - - function create() { - var ext = new Extglob(options); - var ast = ext.parse(pattern, options); - return ext.compile(ast, options); - } - - return utils.memoize('create', pattern, options, create); -}; - -/** - * Returns an array of matches captured by `pattern` in `string`, or `null` - * if the pattern did not match. - * - * ```js - * var extglob = require('extglob'); - * extglob.capture(pattern, string[, options]); - * - * console.log(extglob.capture('test/*.js', 'test/foo.js')); - * //=> ['foo'] - * console.log(extglob.capture('test/*.js', 'foo/bar.css')); - * //=> null - * ``` - * @param {String} `pattern` Glob pattern to use for matching. - * @param {String} `string` String to match - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns an array of captures if the string matches the glob pattern, otherwise `null`. - * @api public - */ - -extglob.capture = function(pattern, str, options) { - var re = extglob.makeRe(pattern, extend({capture: true}, options)); - - function match() { - return function(string) { - var match = re.exec(string); - if (!match) { - return null; - } - - return match.slice(1); - }; - } - - var capture = utils.memoize('capture', pattern, options, match); - return capture(str); -}; - -/** - * Create a regular expression from the given `pattern` and `options`. - * - * ```js - * var extglob = require('extglob'); - * var re = extglob.makeRe('*.!(*a)'); - * console.log(re); - * //=> /^[^\/]*?\.(?![^\/]*?a)[^\/]*?$/ - * ``` - * @param {String} `pattern` The pattern to convert to regex. - * @param {Object} `options` - * @return {RegExp} - * @api public - */ - -extglob.makeRe = function(pattern, options) { - if (pattern instanceof RegExp) { - return pattern; - } - - if (typeof pattern !== 'string') { - throw new TypeError('expected pattern to be a string'); - } - - if (pattern.length > MAX_LENGTH) { - throw new Error('expected pattern to be less than ' + MAX_LENGTH + ' characters'); - } - - function makeRe() { - var opts = extend({strictErrors: false}, options); - if (opts.strictErrors === true) opts.strict = true; - var res = extglob.create(pattern, opts); - return toRegex(res.output, opts); - } - - var regex = utils.memoize('makeRe', pattern, options, makeRe); - if (regex.source.length > MAX_LENGTH) { - throw new SyntaxError('potentially malicious regex detected'); - } - - return regex; -}; - -/** - * Cache - */ - -extglob.cache = utils.cache; -extglob.clearCache = function() { - extglob.cache.__data__ = {}; -}; - -/** - * Expose `Extglob` constructor, parsers and compilers - */ - -extglob.Extglob = Extglob; -extglob.compilers = compilers; -extglob.parsers = parsers; - -/** - * Expose `extglob` - * @type {Function} - */ - -module.exports = extglob; - - -/***/ }), -/* 696 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var isObject = __webpack_require__(602); - -module.exports = function extend(o/*, objects*/) { - if (!isObject(o)) { o = {}; } - - var len = arguments.length; - for (var i = 1; i < len; i++) { - var obj = arguments[i]; - - if (isObject(obj)) { - assign(o, obj); - } - } - return o; -}; - -function assign(a, b) { - for (var key in b) { - if (hasOwn(b, key)) { - a[key] = b[key]; - } - } -} - -/** - * Returns true if the given `key` is an own property of `obj`. - */ - -function hasOwn(obj, key) { - return Object.prototype.hasOwnProperty.call(obj, key); -} - - -/***/ }), -/* 697 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var brackets = __webpack_require__(698); - -/** - * Extglob compilers - */ - -module.exports = function(extglob) { - function star() { - if (typeof extglob.options.star === 'function') { - return extglob.options.star.apply(this, arguments); - } - if (typeof extglob.options.star === 'string') { - return extglob.options.star; - } - return '.*?'; - } - - /** - * Use `expand-brackets` compilers - */ - - extglob.use(brackets.compilers); - extglob.compiler - - /** - * Escaped: "\\*" - */ - - .set('escape', function(node) { - return this.emit(node.val, node); - }) - - /** - * Dot: "." - */ - - .set('dot', function(node) { - return this.emit('\\' + node.val, node); - }) - - /** - * Question mark: "?" - */ - - .set('qmark', function(node) { - var val = '[^\\\\/.]'; - var prev = this.prev(); - - if (node.parsed.slice(-1) === '(') { - var ch = node.rest.charAt(0); - if (ch !== '!' && ch !== '=' && ch !== ':') { - return this.emit(val, node); - } - return this.emit(node.val, node); - } - - if (prev.type === 'text' && prev.val) { - return this.emit(val, node); - } - - if (node.val.length > 1) { - val += '{' + node.val.length + '}'; - } - return this.emit(val, node); - }) - - /** - * Plus: "+" - */ - - .set('plus', function(node) { - var prev = node.parsed.slice(-1); - if (prev === ']' || prev === ')') { - return this.emit(node.val, node); - } - var ch = this.output.slice(-1); - if (!this.output || (/[?*+]/.test(ch) && node.parent.type !== 'bracket')) { - return this.emit('\\+', node); - } - if (/\w/.test(ch) && !node.inside) { - return this.emit('+\\+?', node); - } - return this.emit('+', node); - }) - - /** - * Star: "*" - */ - - .set('star', function(node) { - var prev = this.prev(); - var prefix = prev.type !== 'text' && prev.type !== 'escape' - ? '(?!\\.)' - : ''; - - return this.emit(prefix + star.call(this, node), node); - }) - - /** - * Parens - */ - - .set('paren', function(node) { - return this.mapVisit(node.nodes); - }) - .set('paren.open', function(node) { - var capture = this.options.capture ? '(' : ''; - - switch (node.parent.prefix) { - case '!': - case '^': - return this.emit(capture + '(?:(?!(?:', node); - case '*': - case '+': - case '?': - case '@': - return this.emit(capture + '(?:', node); - default: { - var val = node.val; - if (this.options.bash === true) { - val = '\\' + val; - } else if (!this.options.capture && val === '(' && node.parent.rest[0] !== '?') { - val += '?:'; - } - - return this.emit(val, node); - } - } - }) - .set('paren.close', function(node) { - var capture = this.options.capture ? ')' : ''; - - switch (node.prefix) { - case '!': - case '^': - var prefix = /^(\)|$)/.test(node.rest) ? '$' : ''; - var str = star.call(this, node); - - // if the extglob has a slash explicitly defined, we know the user wants - // to match slashes, so we need to ensure the "star" regex allows for it - if (node.parent.hasSlash && !this.options.star && this.options.slash !== false) { - str = '.*?'; - } - - return this.emit(prefix + ('))' + str + ')') + capture, node); - case '*': - case '+': - case '?': - return this.emit(')' + node.prefix + capture, node); - case '@': - return this.emit(')' + capture, node); - default: { - var val = (this.options.bash === true ? '\\' : '') + ')'; - return this.emit(val, node); - } - } - }) - - /** - * Text - */ - - .set('text', function(node) { - var val = node.val.replace(/[\[\]]/g, '\\$&'); - return this.emit(val, node); - }); -}; - - -/***/ }), -/* 698 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/** - * Local dependencies - */ - -var compilers = __webpack_require__(699); -var parsers = __webpack_require__(701); - -/** - * Module dependencies - */ - -var debug = __webpack_require__(703)('expand-brackets'); -var extend = __webpack_require__(708); -var Snapdragon = __webpack_require__(618); -var toRegex = __webpack_require__(582); - -/** - * Parses the given POSIX character class `pattern` and returns a - * string that can be used for creating regular expressions for matching. - * - * @param {String} `pattern` - * @param {Object} `options` - * @return {Object} - * @api public - */ - -function brackets(pattern, options) { - debug('initializing from <%s>', __filename); - var res = brackets.create(pattern, options); - return res.output; -} - -/** - * Takes an array of strings and a POSIX character class pattern, and returns a new - * array with only the strings that matched the pattern. - * - * ```js - * var brackets = require('expand-brackets'); - * console.log(brackets.match(['1', 'a', 'ab'], '[[:alpha:]]')); - * //=> ['a'] - * - * console.log(brackets.match(['1', 'a', 'ab'], '[[:alpha:]]+')); - * //=> ['a', 'ab'] - * ``` - * @param {Array} `arr` Array of strings to match - * @param {String} `pattern` POSIX character class pattern(s) - * @param {Object} `options` - * @return {Array} - * @api public - */ - -brackets.match = function(arr, pattern, options) { - arr = [].concat(arr); - var opts = extend({}, options); - var isMatch = brackets.matcher(pattern, opts); - var len = arr.length; - var idx = -1; - var res = []; - - while (++idx < len) { - var ele = arr[idx]; - if (isMatch(ele)) { - res.push(ele); - } - } - - if (res.length === 0) { - if (opts.failglob === true) { - throw new Error('no matches found for "' + pattern + '"'); - } - - if (opts.nonull === true || opts.nullglob === true) { - return [pattern.split('\\').join('')]; - } - } - return res; -}; - -/** - * Returns true if the specified `string` matches the given - * brackets `pattern`. - * - * ```js - * var brackets = require('expand-brackets'); - * - * console.log(brackets.isMatch('a.a', '[[:alpha:]].[[:alpha:]]')); - * //=> true - * console.log(brackets.isMatch('1.2', '[[:alpha:]].[[:alpha:]]')); - * //=> false - * ``` - * @param {String} `string` String to match - * @param {String} `pattern` Poxis pattern - * @param {String} `options` - * @return {Boolean} - * @api public - */ - -brackets.isMatch = function(str, pattern, options) { - return brackets.matcher(pattern, options)(str); -}; - -/** - * Takes a POSIX character class pattern and returns a matcher function. The returned - * function takes the string to match as its only argument. - * - * ```js - * var brackets = require('expand-brackets'); - * var isMatch = brackets.matcher('[[:lower:]].[[:upper:]]'); - * - * console.log(isMatch('a.a')); - * //=> false - * console.log(isMatch('a.A')); - * //=> true - * ``` - * @param {String} `pattern` Poxis pattern - * @param {String} `options` - * @return {Boolean} - * @api public - */ - -brackets.matcher = function(pattern, options) { - var re = brackets.makeRe(pattern, options); - return function(str) { - return re.test(str); - }; -}; - -/** - * Create a regular expression from the given `pattern`. - * - * ```js - * var brackets = require('expand-brackets'); - * var re = brackets.makeRe('[[:alpha:]]'); - * console.log(re); - * //=> /^(?:[a-zA-Z])$/ - * ``` - * @param {String} `pattern` The pattern to convert to regex. - * @param {Object} `options` - * @return {RegExp} - * @api public - */ - -brackets.makeRe = function(pattern, options) { - var res = brackets.create(pattern, options); - var opts = extend({strictErrors: false}, options); - return toRegex(res.output, opts); -}; - -/** - * Parses the given POSIX character class `pattern` and returns an object - * with the compiled `output` and optional source `map`. - * - * ```js - * var brackets = require('expand-brackets'); - * console.log(brackets('[[:alpha:]]')); - * // { options: { source: 'string' }, - * // input: '[[:alpha:]]', - * // state: {}, - * // compilers: - * // { eos: [Function], - * // noop: [Function], - * // bos: [Function], - * // not: [Function], - * // escape: [Function], - * // text: [Function], - * // posix: [Function], - * // bracket: [Function], - * // 'bracket.open': [Function], - * // 'bracket.inner': [Function], - * // 'bracket.literal': [Function], - * // 'bracket.close': [Function] }, - * // output: '[a-zA-Z]', - * // ast: - * // { type: 'root', - * // errors: [], - * // nodes: [ [Object], [Object], [Object] ] }, - * // parsingErrors: [] } - * ``` - * @param {String} `pattern` - * @param {Object} `options` - * @return {Object} - * @api public - */ - -brackets.create = function(pattern, options) { - var snapdragon = (options && options.snapdragon) || new Snapdragon(options); - compilers(snapdragon); - parsers(snapdragon); - - var ast = snapdragon.parse(pattern, options); - ast.input = pattern; - var res = snapdragon.compile(ast, options); - res.input = pattern; - return res; -}; - -/** - * Expose `brackets` constructor, parsers and compilers - */ - -brackets.compilers = compilers; -brackets.parsers = parsers; - -/** - * Expose `brackets` - * @type {Function} - */ - -module.exports = brackets; - - -/***/ }), -/* 699 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var posix = __webpack_require__(700); - -module.exports = function(brackets) { - brackets.compiler - - /** - * Escaped characters - */ - - .set('escape', function(node) { - return this.emit('\\' + node.val.replace(/^\\/, ''), node); - }) - - /** - * Text - */ - - .set('text', function(node) { - return this.emit(node.val.replace(/([{}])/g, '\\$1'), node); - }) - - /** - * POSIX character classes - */ - - .set('posix', function(node) { - if (node.val === '[::]') { - return this.emit('\\[::\\]', node); - } - - var val = posix[node.inner]; - if (typeof val === 'undefined') { - val = '[' + node.inner + ']'; - } - return this.emit(val, node); - }) - - /** - * Non-posix brackets - */ - - .set('bracket', function(node) { - return this.mapVisit(node.nodes); - }) - .set('bracket.open', function(node) { - return this.emit(node.val, node); - }) - .set('bracket.inner', function(node) { - var inner = node.val; - - if (inner === '[' || inner === ']') { - return this.emit('\\' + node.val, node); - } - if (inner === '^]') { - return this.emit('^\\]', node); - } - if (inner === '^') { - return this.emit('^', node); - } - - if (/-/.test(inner) && !/(\d-\d|\w-\w)/.test(inner)) { - inner = inner.split('-').join('\\-'); - } - - var isNegated = inner.charAt(0) === '^'; - // add slashes to negated brackets, per spec - if (isNegated && inner.indexOf('/') === -1) { - inner += '/'; - } - if (isNegated && inner.indexOf('.') === -1) { - inner += '.'; - } - - // don't unescape `0` (octal literal) - inner = inner.replace(/\\([1-9])/g, '$1'); - return this.emit(inner, node); - }) - .set('bracket.close', function(node) { - var val = node.val.replace(/^\\/, ''); - if (node.parent.escaped === true) { - return this.emit('\\' + val, node); - } - return this.emit(val, node); - }); -}; - - -/***/ }), -/* 700 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/** - * POSIX character classes - */ - -module.exports = { - alnum: 'a-zA-Z0-9', - alpha: 'a-zA-Z', - ascii: '\\x00-\\x7F', - blank: ' \\t', - cntrl: '\\x00-\\x1F\\x7F', - digit: '0-9', - graph: '\\x21-\\x7E', - lower: 'a-z', - print: '\\x20-\\x7E ', - punct: '\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~', - space: ' \\t\\r\\n\\v\\f', - upper: 'A-Z', - word: 'A-Za-z0-9_', - xdigit: 'A-Fa-f0-9' -}; - - -/***/ }), -/* 701 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var utils = __webpack_require__(702); -var define = __webpack_require__(645); - -/** - * Text regex - */ - -var TEXT_REGEX = '(\\[(?=.*\\])|\\])+'; -var not = utils.createRegex(TEXT_REGEX); - -/** - * Brackets parsers - */ - -function parsers(brackets) { - brackets.state = brackets.state || {}; - brackets.parser.sets.bracket = brackets.parser.sets.bracket || []; - brackets.parser - - .capture('escape', function() { - if (this.isInside('bracket')) return; - var pos = this.position(); - var m = this.match(/^\\(.)/); - if (!m) return; - - return pos({ - type: 'escape', - val: m[0] - }); - }) - - /** - * Text parser - */ - - .capture('text', function() { - if (this.isInside('bracket')) return; - var pos = this.position(); - var m = this.match(not); - if (!m || !m[0]) return; - - return pos({ - type: 'text', - val: m[0] - }); - }) - - /** - * POSIX character classes: "[[:alpha:][:digits:]]" - */ - - .capture('posix', function() { - var pos = this.position(); - var m = this.match(/^\[:(.*?):\](?=.*\])/); - if (!m) return; - - var inside = this.isInside('bracket'); - if (inside) { - brackets.posix++; - } - - return pos({ - type: 'posix', - insideBracket: inside, - inner: m[1], - val: m[0] - }); - }) - - /** - * Bracket (noop) - */ - - .capture('bracket', function() {}) - - /** - * Open: '[' - */ - - .capture('bracket.open', function() { - var parsed = this.parsed; - var pos = this.position(); - var m = this.match(/^\[(?=.*\])/); - if (!m) return; - - var prev = this.prev(); - var last = utils.last(prev.nodes); - - if (parsed.slice(-1) === '\\' && !this.isInside('bracket')) { - last.val = last.val.slice(0, last.val.length - 1); - return pos({ - type: 'escape', - val: m[0] - }); - } - - var open = pos({ - type: 'bracket.open', - val: m[0] - }); - - if (last.type === 'bracket.open' || this.isInside('bracket')) { - open.val = '\\' + open.val; - open.type = 'bracket.inner'; - open.escaped = true; - return open; - } - - var node = pos({ - type: 'bracket', - nodes: [open] - }); - - define(node, 'parent', prev); - define(open, 'parent', node); - this.push('bracket', node); - prev.nodes.push(node); - }) - - /** - * Bracket text - */ - - .capture('bracket.inner', function() { - if (!this.isInside('bracket')) return; - var pos = this.position(); - var m = this.match(not); - if (!m || !m[0]) return; - - var next = this.input.charAt(0); - var val = m[0]; - - var node = pos({ - type: 'bracket.inner', - val: val - }); - - if (val === '\\\\') { - return node; - } - - var first = val.charAt(0); - var last = val.slice(-1); - - if (first === '!') { - val = '^' + val.slice(1); - } - - if (last === '\\' || (val === '^' && next === ']')) { - val += this.input[0]; - this.consume(1); - } - - node.val = val; - return node; - }) - - /** - * Close: ']' - */ - - .capture('bracket.close', function() { - var parsed = this.parsed; - var pos = this.position(); - var m = this.match(/^\]/); - if (!m) return; - - var prev = this.prev(); - var last = utils.last(prev.nodes); - - if (parsed.slice(-1) === '\\' && !this.isInside('bracket')) { - last.val = last.val.slice(0, last.val.length - 1); - - return pos({ - type: 'escape', - val: m[0] - }); - } - - var node = pos({ - type: 'bracket.close', - rest: this.input, - val: m[0] - }); - - if (last.type === 'bracket.open') { - node.type = 'bracket.inner'; - node.escaped = true; - return node; - } - - var bracket = this.pop('bracket'); - if (!this.isType(bracket, 'bracket')) { - if (this.options.strict) { - throw new Error('missing opening "["'); - } - node.type = 'bracket.inner'; - node.escaped = true; - return node; - } - - bracket.nodes.push(node); - define(node, 'parent', bracket); - }); -} - -/** - * Brackets parsers - */ - -module.exports = parsers; - -/** - * Expose text regex - */ - -module.exports.TEXT_REGEX = TEXT_REGEX; - - -/***/ }), -/* 702 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var toRegex = __webpack_require__(582); -var regexNot = __webpack_require__(599); -var cached; - -/** - * Get the last element from `array` - * @param {Array} `array` - * @return {*} - */ - -exports.last = function(arr) { - return arr[arr.length - 1]; -}; - -/** - * Create and cache regex to use for text nodes - */ - -exports.createRegex = function(pattern, include) { - if (cached) return cached; - var opts = {contains: true, strictClose: false}; - var not = regexNot.create(pattern, opts); - var re; - - if (typeof include === 'string') { - re = toRegex('^(?:' + include + '|' + not + ')', opts); - } else { - re = toRegex(not, opts); - } - - return (cached = re); -}; - - -/***/ }), -/* 703 */ -/***/ (function(module, exports, __webpack_require__) { - -/** - * Detect Electron renderer process, which is node, but we should - * treat as a browser. - */ - -if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(704); -} else { - module.exports = __webpack_require__(707); -} - - -/***/ }), -/* 704 */ -/***/ (function(module, exports, __webpack_require__) { - -/** - * This is the web browser implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = __webpack_require__(705); -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; -exports.storage = 'undefined' != typeof chrome - && 'undefined' != typeof chrome.storage - ? chrome.storage.local - : localstorage(); - -/** - * Colors. - */ - -exports.colors = [ - 'lightseagreen', - 'forestgreen', - 'goldenrod', - 'dodgerblue', - 'darkorchid', - 'crimson' -]; - -/** - * Currently only WebKit-based Web Inspectors, Firefox >= v31, - * and the Firebug extension (any Firefox version) are known - * to support "%c" CSS customizations. - * - * TODO: add a `localStorage` variable to explicitly enable/disable colors - */ - -function useColors() { - // NB: In an Electron preload script, document will be defined but not fully - // initialized. Since we know we're in Chrome, we'll just detect this case - // explicitly - if (typeof window !== 'undefined' && window.process && window.process.type === 'renderer') { - return true; - } - - // is webkit? http://stackoverflow.com/a/16459606/376773 - // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 - return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || - // is firebug? http://stackoverflow.com/a/398120/376773 - (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || - // is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || - // double check webkit in userAgent just in case we are in a worker - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); -} - -/** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ - -exports.formatters.j = function(v) { - try { - return JSON.stringify(v); - } catch (err) { - return '[UnexpectedJSONParseError]: ' + err.message; - } -}; - - -/** - * Colorize log arguments if enabled. - * - * @api public - */ - -function formatArgs(args) { - var useColors = this.useColors; - - args[0] = (useColors ? '%c' : '') - + this.namespace - + (useColors ? ' %c' : ' ') - + args[0] - + (useColors ? '%c ' : ' ') - + '+' + exports.humanize(this.diff); - - if (!useColors) return; - - var c = 'color: ' + this.color; - args.splice(1, 0, c, 'color: inherit') - - // the final "%c" is somewhat tricky, because there could be other - // arguments passed either before or after the %c, so we need to - // figure out the correct index to insert the CSS into - var index = 0; - var lastC = 0; - args[0].replace(/%[a-zA-Z%]/g, function(match) { - if ('%%' === match) return; - index++; - if ('%c' === match) { - // we only are interested in the *last* %c - // (the user may have provided their own) - lastC = index; - } - }); - - args.splice(lastC, 0, c); -} - -/** - * Invokes `console.log()` when available. - * No-op when `console.log` is not a "function". - * - * @api public - */ - -function log() { - // this hackery is required for IE8/9, where - // the `console.log` function doesn't have 'apply' - return 'object' === typeof console - && console.log - && Function.prototype.apply.call(console.log, console, arguments); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - -function save(namespaces) { - try { - if (null == namespaces) { - exports.storage.removeItem('debug'); - } else { - exports.storage.debug = namespaces; - } - } catch(e) {} -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - -function load() { - var r; - try { - r = exports.storage.debug; - } catch(e) {} - - // If debug isn't set in LS, and we're in Electron, try to load $DEBUG - if (!r && typeof process !== 'undefined' && 'env' in process) { - r = process.env.DEBUG; - } - - return r; -} - -/** - * Enable namespaces listed in `localStorage.debug` initially. - */ - -exports.enable(load()); - -/** - * Localstorage attempts to return the localstorage. - * - * This is necessary because safari throws - * when a user disables cookies/localstorage - * and you attempt to access it. - * - * @return {LocalStorage} - * @api private - */ - -function localstorage() { - try { - return window.localStorage; - } catch (e) {} -} - - -/***/ }), -/* 705 */ -/***/ (function(module, exports, __webpack_require__) { - - -/** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; -exports.coerce = coerce; -exports.disable = disable; -exports.enable = enable; -exports.enabled = enabled; -exports.humanize = __webpack_require__(706); - -/** - * The currently active debug mode names, and names to skip. - */ - -exports.names = []; -exports.skips = []; - -/** - * Map of special "%n" handling functions, for the debug "format" argument. - * - * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". - */ - -exports.formatters = {}; - -/** - * Previous log timestamp. - */ - -var prevTime; - -/** - * Select a color. - * @param {String} namespace - * @return {Number} - * @api private - */ - -function selectColor(namespace) { - var hash = 0, i; - - for (i in namespace) { - hash = ((hash << 5) - hash) + namespace.charCodeAt(i); - hash |= 0; // Convert to 32bit integer - } - - return exports.colors[Math.abs(hash) % exports.colors.length]; -} - -/** - * Create a debugger with the given `namespace`. - * - * @param {String} namespace - * @return {Function} - * @api public - */ - -function createDebug(namespace) { - - function debug() { - // disabled? - if (!debug.enabled) return; - - var self = debug; - - // set `diff` timestamp - var curr = +new Date(); - var ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; - - // turn the `arguments` into a proper Array - var args = new Array(arguments.length); - for (var i = 0; i < args.length; i++) { - args[i] = arguments[i]; - } - - args[0] = exports.coerce(args[0]); - - if ('string' !== typeof args[0]) { - // anything else let's inspect with %O - args.unshift('%O'); - } - - // apply any `formatters` transformations - var index = 0; - args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { - // if we encounter an escaped % then don't increase the array index - if (match === '%%') return match; - index++; - var formatter = exports.formatters[format]; - if ('function' === typeof formatter) { - var val = args[index]; - match = formatter.call(self, val); - - // now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; - } - return match; - }); - - // apply env-specific formatting (colors, etc.) - exports.formatArgs.call(self, args); - - var logFn = debug.log || exports.log || console.log.bind(console); - logFn.apply(self, args); - } - - debug.namespace = namespace; - debug.enabled = exports.enabled(namespace); - debug.useColors = exports.useColors(); - debug.color = selectColor(namespace); - - // env-specific initialization logic for debug instances - if ('function' === typeof exports.init) { - exports.init(debug); - } - - return debug; -} - -/** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. - * - * @param {String} namespaces - * @api public - */ - -function enable(namespaces) { - exports.save(namespaces); - - exports.names = []; - exports.skips = []; - - var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); - var len = split.length; - - for (var i = 0; i < len; i++) { - if (!split[i]) continue; // ignore empty strings - namespaces = split[i].replace(/\*/g, '.*?'); - if (namespaces[0] === '-') { - exports.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); - } else { - exports.names.push(new RegExp('^' + namespaces + '$')); - } - } -} - -/** - * Disable debug output. - * - * @api public - */ - -function disable() { - exports.enable(''); -} - -/** - * Returns true if the given mode name is enabled, false otherwise. - * - * @param {String} name - * @return {Boolean} - * @api public - */ - -function enabled(name) { - var i, len; - for (i = 0, len = exports.skips.length; i < len; i++) { - if (exports.skips[i].test(name)) { - return false; - } - } - for (i = 0, len = exports.names.length; i < len; i++) { - if (exports.names[i].test(name)) { - return true; - } - } - return false; -} - -/** - * Coerce `val`. - * - * @param {Mixed} val - * @return {Mixed} - * @api private - */ - -function coerce(val) { - if (val instanceof Error) return val.stack || val.message; - return val; -} - - -/***/ }), -/* 706 */ -/***/ (function(module, exports) { - -/** - * Helpers. - */ - -var s = 1000; -var m = s * 60; -var h = m * 60; -var d = h * 24; -var y = d * 365.25; - -/** - * Parse or format the given `val`. - * - * Options: - * - * - `long` verbose formatting [false] - * - * @param {String|Number} val - * @param {Object} [options] - * @throws {Error} throw an error if val is not a non-empty string or a number - * @return {String|Number} - * @api public - */ - -module.exports = function(val, options) { - options = options || {}; - var type = typeof val; - if (type === 'string' && val.length > 0) { - return parse(val); - } else if (type === 'number' && isNaN(val) === false) { - return options.long ? fmtLong(val) : fmtShort(val); - } - throw new Error( - 'val is not a non-empty string or a valid number. val=' + - JSON.stringify(val) - ); -}; - -/** - * Parse the given `str` and return milliseconds. - * - * @param {String} str - * @return {Number} - * @api private - */ - -function parse(str) { - str = String(str); - if (str.length > 100) { - return; - } - var match = /^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec( - str - ); - if (!match) { - return; - } - var n = parseFloat(match[1]); - var type = (match[2] || 'ms').toLowerCase(); - switch (type) { - case 'years': - case 'year': - case 'yrs': - case 'yr': - case 'y': - return n * y; - case 'days': - case 'day': - case 'd': - return n * d; - case 'hours': - case 'hour': - case 'hrs': - case 'hr': - case 'h': - return n * h; - case 'minutes': - case 'minute': - case 'mins': - case 'min': - case 'm': - return n * m; - case 'seconds': - case 'second': - case 'secs': - case 'sec': - case 's': - return n * s; - case 'milliseconds': - case 'millisecond': - case 'msecs': - case 'msec': - case 'ms': - return n; - default: - return undefined; - } -} - -/** - * Short format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtShort(ms) { - if (ms >= d) { - return Math.round(ms / d) + 'd'; - } - if (ms >= h) { - return Math.round(ms / h) + 'h'; - } - if (ms >= m) { - return Math.round(ms / m) + 'm'; - } - if (ms >= s) { - return Math.round(ms / s) + 's'; - } - return ms + 'ms'; -} - -/** - * Long format for `ms`. - * - * @param {Number} ms - * @return {String} - * @api private - */ - -function fmtLong(ms) { - return plural(ms, d, 'day') || - plural(ms, h, 'hour') || - plural(ms, m, 'minute') || - plural(ms, s, 'second') || - ms + ' ms'; -} - -/** - * Pluralization helper. - */ - -function plural(ms, n, name) { - if (ms < n) { - return; - } - if (ms < n * 1.5) { - return Math.floor(ms / n) + ' ' + name; - } - return Math.ceil(ms / n) + ' ' + name + 's'; -} - - -/***/ }), -/* 707 */ -/***/ (function(module, exports, __webpack_require__) { - -/** - * Module dependencies. - */ - -var tty = __webpack_require__(122); -var util = __webpack_require__(112); - -/** - * This is the Node.js implementation of `debug()`. - * - * Expose `debug()` as the module. - */ - -exports = module.exports = __webpack_require__(705); -exports.init = init; -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; - -/** - * Colors. - */ - -exports.colors = [6, 2, 3, 4, 5, 1]; - -/** - * Build up the default `inspectOpts` object from the environment variables. - * - * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js - */ - -exports.inspectOpts = Object.keys(process.env).filter(function (key) { - return /^debug_/i.test(key); -}).reduce(function (obj, key) { - // camel-case - var prop = key - .substring(6) - .toLowerCase() - .replace(/_([a-z])/g, function (_, k) { return k.toUpperCase() }); - - // coerce string value into JS value - var val = process.env[key]; - if (/^(yes|on|true|enabled)$/i.test(val)) val = true; - else if (/^(no|off|false|disabled)$/i.test(val)) val = false; - else if (val === 'null') val = null; - else val = Number(val); - - obj[prop] = val; - return obj; -}, {}); - -/** - * The file descriptor to write the `debug()` calls to. - * Set the `DEBUG_FD` env variable to override with another value. i.e.: - * - * $ DEBUG_FD=3 node script.js 3>debug.log - */ - -var fd = parseInt(process.env.DEBUG_FD, 10) || 2; - -if (1 !== fd && 2 !== fd) { - util.deprecate(function(){}, 'except for stderr(2) and stdout(1), any other usage of DEBUG_FD is deprecated. Override debug.log if you want to use a different log function (https://git.io/debug_fd)')() -} - -var stream = 1 === fd ? process.stdout : - 2 === fd ? process.stderr : - createWritableStdioStream(fd); - -/** - * Is stdout a TTY? Colored output is enabled when `true`. - */ - -function useColors() { - return 'colors' in exports.inspectOpts - ? Boolean(exports.inspectOpts.colors) - : tty.isatty(fd); -} - -/** - * Map %o to `util.inspect()`, all on a single line. - */ - -exports.formatters.o = function(v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts) - .split('\n').map(function(str) { - return str.trim() - }).join(' '); -}; - -/** - * Map %o to `util.inspect()`, allowing multiple lines if needed. - */ - -exports.formatters.O = function(v) { - this.inspectOpts.colors = this.useColors; - return util.inspect(v, this.inspectOpts); -}; - -/** - * Adds ANSI color escape codes if enabled. - * - * @api public - */ - -function formatArgs(args) { - var name = this.namespace; - var useColors = this.useColors; - - if (useColors) { - var c = this.color; - var prefix = ' \u001b[3' + c + ';1m' + name + ' ' + '\u001b[0m'; - - args[0] = prefix + args[0].split('\n').join('\n' + prefix); - args.push('\u001b[3' + c + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); - } else { - args[0] = new Date().toUTCString() - + ' ' + name + ' ' + args[0]; - } -} - -/** - * Invokes `util.format()` with the specified arguments and writes to `stream`. - */ - -function log() { - return stream.write(util.format.apply(util, arguments) + '\n'); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ - -function save(namespaces) { - if (null == namespaces) { - // If you set a process.env field to null or undefined, it gets cast to the - // string 'null' or 'undefined'. Just delete instead. - delete process.env.DEBUG; - } else { - process.env.DEBUG = namespaces; - } -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ - -function load() { - return process.env.DEBUG; -} - -/** - * Copied from `node/src/node.js`. - * - * XXX: It's lame that node doesn't expose this API out-of-the-box. It also - * relies on the undocumented `tty_wrap.guessHandleType()` which is also lame. - */ - -function createWritableStdioStream (fd) { - var stream; - var tty_wrap = process.binding('tty_wrap'); - - // Note stream._type is used for test-module-load-list.js - - switch (tty_wrap.guessHandleType(fd)) { - case 'TTY': - stream = new tty.WriteStream(fd); - stream._type = 'tty'; - - // Hack to have stream not keep the event loop alive. - // See https://github.com/joyent/node/issues/1726 - if (stream._handle && stream._handle.unref) { - stream._handle.unref(); - } - break; - - case 'FILE': - var fs = __webpack_require__(134); - stream = new fs.SyncWriteStream(fd, { autoClose: false }); - stream._type = 'fs'; - break; - - case 'PIPE': - case 'TCP': - var net = __webpack_require__(659); - stream = new net.Socket({ - fd: fd, - readable: false, - writable: true - }); - - // FIXME Should probably have an option in net.Socket to create a - // stream from an existing fd which is writable only. But for now - // we'll just add this hack and set the `readable` member to false. - // Test: ./node test/fixtures/echo.js < /etc/passwd - stream.readable = false; - stream.read = null; - stream._type = 'pipe'; - - // FIXME Hack to have stream not keep the event loop alive. - // See https://github.com/joyent/node/issues/1726 - if (stream._handle && stream._handle.unref) { - stream._handle.unref(); - } - break; - - default: - // Probably an error on in uv_guess_handle() - throw new Error('Implement me. Unknown stream file type!'); - } - - // For supporting legacy API we put the FD here. - stream.fd = fd; - - stream._isStdio = true; - - return stream; -} - -/** - * Init logic for `debug` instances. - * - * Create a new `inspectOpts` object in case `useColors` is set - * differently for a particular `debug` instance. - */ - -function init (debug) { - debug.inspectOpts = {}; - - var keys = Object.keys(exports.inspectOpts); - for (var i = 0; i < keys.length; i++) { - debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; - } -} - -/** - * Enable namespaces listed in `process.env.DEBUG` initially. - */ - -exports.enable(load()); - - -/***/ }), -/* 708 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var isObject = __webpack_require__(602); - -module.exports = function extend(o/*, objects*/) { - if (!isObject(o)) { o = {}; } - - var len = arguments.length; - for (var i = 1; i < len; i++) { - var obj = arguments[i]; - - if (isObject(obj)) { - assign(o, obj); - } - } - return o; -}; - -function assign(a, b) { - for (var key in b) { - if (hasOwn(b, key)) { - a[key] = b[key]; - } - } -} - -/** - * Returns true if the given `key` is an own property of `obj`. - */ - -function hasOwn(obj, key) { - return Object.prototype.hasOwnProperty.call(obj, key); -} - - -/***/ }), -/* 709 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var brackets = __webpack_require__(698); -var define = __webpack_require__(710); -var utils = __webpack_require__(711); - -/** - * Characters to use in text regex (we want to "not" match - * characters that are matched by other parsers) - */ - -var TEXT_REGEX = '([!@*?+]?\\(|\\)|[*?.+\\\\]|\\[:?(?=.*\\])|:?\\])+'; -var not = utils.createRegex(TEXT_REGEX); - -/** - * Extglob parsers - */ - -function parsers(extglob) { - extglob.state = extglob.state || {}; - - /** - * Use `expand-brackets` parsers - */ - - extglob.use(brackets.parsers); - extglob.parser.sets.paren = extglob.parser.sets.paren || []; - extglob.parser - - /** - * Extglob open: "*(" - */ - - .capture('paren.open', function() { - var parsed = this.parsed; - var pos = this.position(); - var m = this.match(/^([!@*?+])?\(/); - if (!m) return; - - var prev = this.prev(); - var prefix = m[1]; - var val = m[0]; - - var open = pos({ - type: 'paren.open', - parsed: parsed, - val: val - }); - - var node = pos({ - type: 'paren', - prefix: prefix, - nodes: [open] - }); - - // if nested negation extglobs, just cancel them out to simplify - if (prefix === '!' && prev.type === 'paren' && prev.prefix === '!') { - prev.prefix = '@'; - node.prefix = '@'; - } - - define(node, 'rest', this.input); - define(node, 'parsed', parsed); - define(node, 'parent', prev); - define(open, 'parent', node); - - this.push('paren', node); - prev.nodes.push(node); - }) - - /** - * Extglob close: ")" - */ - - .capture('paren.close', function() { - var parsed = this.parsed; - var pos = this.position(); - var m = this.match(/^\)/); - if (!m) return; - - var parent = this.pop('paren'); - var node = pos({ - type: 'paren.close', - rest: this.input, - parsed: parsed, - val: m[0] - }); - - if (!this.isType(parent, 'paren')) { - if (this.options.strict) { - throw new Error('missing opening paren: "("'); - } - node.escaped = true; - return node; - } - - node.prefix = parent.prefix; - parent.nodes.push(node); - define(node, 'parent', parent); - }) - - /** - * Escape: "\\." - */ - - .capture('escape', function() { - var pos = this.position(); - var m = this.match(/^\\(.)/); - if (!m) return; - - return pos({ - type: 'escape', - val: m[0], - ch: m[1] - }); - }) - - /** - * Question marks: "?" - */ - - .capture('qmark', function() { - var parsed = this.parsed; - var pos = this.position(); - var m = this.match(/^\?+(?!\()/); - if (!m) return; - extglob.state.metachar = true; - return pos({ - type: 'qmark', - rest: this.input, - parsed: parsed, - val: m[0] - }); - }) - - /** - * Character parsers - */ - - .capture('star', /^\*(?!\()/) - .capture('plus', /^\+(?!\()/) - .capture('dot', /^\./) - .capture('text', not); -}; - -/** - * Expose text regex string - */ - -module.exports.TEXT_REGEX = TEXT_REGEX; - -/** - * Extglob parsers - */ - -module.exports = parsers; - - -/***/ }), -/* 710 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * define-property - * - * Copyright (c) 2015, 2017, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var isDescriptor = __webpack_require__(591); - -module.exports = function defineProperty(obj, prop, val) { - if (typeof obj !== 'object' && typeof obj !== 'function') { - throw new TypeError('expected an object or function.'); - } - - if (typeof prop !== 'string') { - throw new TypeError('expected `prop` to be a string.'); - } - - if (isDescriptor(val) && ('set' in val || 'get' in val)) { - return Object.defineProperty(obj, prop, val); - } - - return Object.defineProperty(obj, prop, { - configurable: true, - enumerable: false, - writable: true, - value: val - }); -}; - - -/***/ }), -/* 711 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var regex = __webpack_require__(599); -var Cache = __webpack_require__(689); - -/** - * Utils - */ - -var utils = module.exports; -var cache = utils.cache = new Cache(); - -/** - * Cast `val` to an array - * @return {Array} - */ - -utils.arrayify = function(val) { - if (!Array.isArray(val)) { - return [val]; - } - return val; -}; - -/** - * Memoize a generated regex or function - */ - -utils.memoize = function(type, pattern, options, fn) { - var key = utils.createKey(type + pattern, options); - - if (cache.has(type, key)) { - return cache.get(type, key); - } - - var val = fn(pattern, options); - if (options && options.cache === false) { - return val; - } - - cache.set(type, key, val); - return val; -}; - -/** - * Create the key to use for memoization. The key is generated - * by iterating over the options and concatenating key-value pairs - * to the pattern string. - */ - -utils.createKey = function(pattern, options) { - var key = pattern; - if (typeof options === 'undefined') { - return key; - } - for (var prop in options) { - key += ';' + prop + '=' + String(options[prop]); - } - return key; -}; - -/** - * Create the regex to use for matching text - */ - -utils.createRegex = function(str) { - var opts = {contains: true, strictClose: false}; - return regex(str, opts); -}; - - -/***/ }), -/* 712 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -/** - * Module dependencies - */ - -var Snapdragon = __webpack_require__(618); -var define = __webpack_require__(710); -var extend = __webpack_require__(696); - -/** - * Local dependencies - */ - -var compilers = __webpack_require__(697); -var parsers = __webpack_require__(709); - -/** - * Customize Snapdragon parser and renderer - */ - -function Extglob(options) { - this.options = extend({source: 'extglob'}, options); - this.snapdragon = this.options.snapdragon || new Snapdragon(this.options); - this.snapdragon.patterns = this.snapdragon.patterns || {}; - this.compiler = this.snapdragon.compiler; - this.parser = this.snapdragon.parser; - - compilers(this.snapdragon); - parsers(this.snapdragon); - - /** - * Override Snapdragon `.parse` method - */ - - define(this.snapdragon, 'parse', function(str, options) { - var parsed = Snapdragon.prototype.parse.apply(this, arguments); - parsed.input = str; - - // escape unmatched brace/bracket/parens - var last = this.parser.stack.pop(); - if (last && this.options.strict !== true) { - var node = last.nodes[0]; - node.val = '\\' + node.val; - var sibling = node.parent.nodes[1]; - if (sibling.type === 'star') { - sibling.loose = true; - } - } - - // add non-enumerable parser reference - define(parsed, 'parser', this.parser); - return parsed; - }); - - /** - * Decorate `.parse` method - */ - - define(this, 'parse', function(ast, options) { - return this.snapdragon.parse.apply(this.snapdragon, arguments); - }); - - /** - * Decorate `.compile` method - */ - - define(this, 'compile', function(ast, options) { - return this.snapdragon.compile.apply(this.snapdragon, arguments); - }); - -} - -/** - * Expose `Extglob` - */ - -module.exports = Extglob; - - -/***/ }), -/* 713 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var extglob = __webpack_require__(695); -var nanomatch = __webpack_require__(685); -var regexNot = __webpack_require__(599); -var toRegex = __webpack_require__(582); -var not; - -/** - * Characters to use in negation regex (we want to "not" match - * characters that are matched by other parsers) - */ - -var TEXT = '([!@*?+]?\\(|\\)|\\[:?(?=.*?:?\\])|:?\\]|[*+?!^$.\\\\/])+'; -var createNotRegex = function(opts) { - return not || (not = textRegex(TEXT)); -}; - -/** - * Parsers - */ - -module.exports = function(snapdragon) { - var parsers = snapdragon.parser.parsers; - - // register nanomatch parsers - snapdragon.use(nanomatch.parsers); - - // get references to some specific nanomatch parsers before they - // are overridden by the extglob and/or parsers - var escape = parsers.escape; - var slash = parsers.slash; - var qmark = parsers.qmark; - var plus = parsers.plus; - var star = parsers.star; - var dot = parsers.dot; - - // register extglob parsers - snapdragon.use(extglob.parsers); - - // custom micromatch parsers - snapdragon.parser - .use(function() { - // override "notRegex" created in nanomatch parser - this.notRegex = /^\!+(?!\()/; - }) - // reset the referenced parsers - .capture('escape', escape) - .capture('slash', slash) - .capture('qmark', qmark) - .capture('star', star) - .capture('plus', plus) - .capture('dot', dot) - - /** - * Override `text` parser - */ - - .capture('text', function() { - if (this.isInside('bracket')) return; - var pos = this.position(); - var m = this.match(createNotRegex(this.options)); - if (!m || !m[0]) return; - - // escape regex boundary characters and simple brackets - var val = m[0].replace(/([[\]^$])/g, '\\$1'); - - return pos({ - type: 'text', - val: val - }); - }); -}; - -/** - * Create text regex - */ - -function textRegex(pattern) { - var notStr = regexNot.create(pattern, {contains: true, strictClose: false}); - var prefix = '(?:[\\^]|\\\\|'; - return toRegex(prefix + notStr + ')', {strictClose: false}); -} - - -/***/ }), -/* 714 */ -/***/ (function(module, exports, __webpack_require__) { - -module.exports = new (__webpack_require__(689))(); - - -/***/ }), -/* 715 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var utils = module.exports; -var path = __webpack_require__(4); - -/** - * Module dependencies - */ - -var Snapdragon = __webpack_require__(618); -utils.define = __webpack_require__(716); -utils.diff = __webpack_require__(693); -utils.extend = __webpack_require__(595); -utils.pick = __webpack_require__(694); -utils.typeOf = __webpack_require__(592); -utils.unique = __webpack_require__(600); - -/** - * Returns true if the platform is windows, or `path.sep` is `\\`. - * This is defined as a function to allow `path.sep` to be set in unit tests, - * or by the user, if there is a reason to do so. - * @return {Boolean} - */ - -utils.isWindows = function() { - return path.sep === '\\' || process.platform === 'win32'; -}; - -/** - * Get the `Snapdragon` instance to use - */ - -utils.instantiate = function(ast, options) { - var snapdragon; - // if an instance was created by `.parse`, use that instance - if (utils.typeOf(ast) === 'object' && ast.snapdragon) { - snapdragon = ast.snapdragon; - // if the user supplies an instance on options, use that instance - } else if (utils.typeOf(options) === 'object' && options.snapdragon) { - snapdragon = options.snapdragon; - // create a new instance - } else { - snapdragon = new Snapdragon(options); - } - - utils.define(snapdragon, 'parse', function(str, options) { - var parsed = Snapdragon.prototype.parse.apply(this, arguments); - parsed.input = str; - - // escape unmatched brace/bracket/parens - var last = this.parser.stack.pop(); - if (last && this.options.strictErrors !== true) { - var open = last.nodes[0]; - var inner = last.nodes[1]; - if (last.type === 'bracket') { - if (inner.val.charAt(0) === '[') { - inner.val = '\\' + inner.val; - } - - } else { - open.val = '\\' + open.val; - var sibling = open.parent.nodes[1]; - if (sibling.type === 'star') { - sibling.loose = true; - } - } - } - - // add non-enumerable parser reference - utils.define(parsed, 'parser', this.parser); - return parsed; - }); - - return snapdragon; -}; - -/** - * Create the key to use for memoization. The key is generated - * by iterating over the options and concatenating key-value pairs - * to the pattern string. - */ - -utils.createKey = function(pattern, options) { - if (utils.typeOf(options) !== 'object') { - return pattern; - } - var val = pattern; - var keys = Object.keys(options); - for (var i = 0; i < keys.length; i++) { - var key = keys[i]; - val += ';' + key + '=' + String(options[key]); - } - return val; -}; - -/** - * Cast `val` to an array - * @return {Array} - */ - -utils.arrayify = function(val) { - if (typeof val === 'string') return [val]; - return val ? (Array.isArray(val) ? val : [val]) : []; -}; - -/** - * Return true if `val` is a non-empty string - */ - -utils.isString = function(val) { - return typeof val === 'string'; -}; - -/** - * Return true if `val` is a non-empty string - */ - -utils.isObject = function(val) { - return utils.typeOf(val) === 'object'; -}; - -/** - * Returns true if the given `str` has special characters - */ - -utils.hasSpecialChars = function(str) { - return /(?:(?:(^|\/)[!.])|[*?+()|\[\]{}]|[+@]\()/.test(str); -}; - -/** - * Escape regex characters in the given string - */ - -utils.escapeRegex = function(str) { - return str.replace(/[-[\]{}()^$|*+?.\\\/\s]/g, '\\$&'); -}; - -/** - * Normalize slashes in the given filepath. - * - * @param {String} `filepath` - * @return {String} - */ - -utils.toPosixPath = function(str) { - return str.replace(/\\+/g, '/'); -}; - -/** - * Strip backslashes before special characters in a string. - * - * @param {String} `str` - * @return {String} - */ - -utils.unescape = function(str) { - return utils.toPosixPath(str.replace(/\\(?=[*+?!.])/g, '')); -}; - -/** - * Strip the prefix from a filepath - * @param {String} `fp` - * @return {String} - */ - -utils.stripPrefix = function(str) { - if (str.charAt(0) !== '.') { - return str; - } - var ch = str.charAt(1); - if (utils.isSlash(ch)) { - return str.slice(2); - } - return str; -}; - -/** - * Returns true if the given str is an escaped or - * unescaped path character - */ - -utils.isSlash = function(str) { - return str === '/' || str === '\\/' || str === '\\' || str === '\\\\'; -}; - -/** - * Returns a function that returns true if the given - * pattern matches or contains a `filepath` - * - * @param {String} `pattern` - * @return {Function} - */ - -utils.matchPath = function(pattern, options) { - return (options && options.contains) - ? utils.containsPattern(pattern, options) - : utils.equalsPattern(pattern, options); -}; - -/** - * Returns true if the given (original) filepath or unixified path are equal - * to the given pattern. - */ - -utils._equals = function(filepath, unixPath, pattern) { - return pattern === filepath || pattern === unixPath; -}; - -/** - * Returns true if the given (original) filepath or unixified path contain - * the given pattern. - */ - -utils._contains = function(filepath, unixPath, pattern) { - return filepath.indexOf(pattern) !== -1 || unixPath.indexOf(pattern) !== -1; -}; - -/** - * Returns a function that returns true if the given - * pattern is the same as a given `filepath` - * - * @param {String} `pattern` - * @return {Function} - */ - -utils.equalsPattern = function(pattern, options) { - var unixify = utils.unixify(options); - options = options || {}; - - return function fn(filepath) { - var equal = utils._equals(filepath, unixify(filepath), pattern); - if (equal === true || options.nocase !== true) { - return equal; - } - var lower = filepath.toLowerCase(); - return utils._equals(lower, unixify(lower), pattern); - }; -}; - -/** - * Returns a function that returns true if the given - * pattern contains a `filepath` - * - * @param {String} `pattern` - * @return {Function} - */ - -utils.containsPattern = function(pattern, options) { - var unixify = utils.unixify(options); - options = options || {}; - - return function(filepath) { - var contains = utils._contains(filepath, unixify(filepath), pattern); - if (contains === true || options.nocase !== true) { - return contains; - } - var lower = filepath.toLowerCase(); - return utils._contains(lower, unixify(lower), pattern); - }; -}; - -/** - * Returns a function that returns true if the given - * regex matches the `filename` of a file path. - * - * @param {RegExp} `re` Matching regex - * @return {Function} - */ - -utils.matchBasename = function(re) { - return function(filepath) { - return re.test(path.basename(filepath)); - }; -}; - -/** - * Determines the filepath to return based on the provided options. - * @return {any} - */ - -utils.value = function(str, unixify, options) { - if (options && options.unixify === false) { - return str; - } - return unixify(str); -}; - -/** - * Returns a function that normalizes slashes in a string to forward - * slashes, strips `./` from beginning of paths, and optionally unescapes - * special characters. - * @return {Function} - */ - -utils.unixify = function(options) { - options = options || {}; - return function(filepath) { - if (utils.isWindows() || options.unixify === true) { - filepath = utils.toPosixPath(filepath); - } - if (options.stripPrefix !== false) { - filepath = utils.stripPrefix(filepath); - } - if (options.unescape === true) { - filepath = utils.unescape(filepath); - } - return filepath; - }; -}; - - -/***/ }), -/* 716 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * define-property - * - * Copyright (c) 2015-2018, Jon Schlinkert. - * Released under the MIT License. - */ - - - -var isobject = __webpack_require__(590); -var isDescriptor = __webpack_require__(591); -var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) - ? Reflect.defineProperty - : Object.defineProperty; - -module.exports = function defineProperty(obj, key, val) { - if (!isobject(obj) && typeof obj !== 'function' && !Array.isArray(obj)) { - throw new TypeError('expected an object, function, or array'); - } - - if (typeof key !== 'string') { - throw new TypeError('expected "key" to be a string'); - } - - if (isDescriptor(val)) { - define(obj, key, val); - return obj; - } - - define(obj, key, { - configurable: true, - enumerable: false, - writable: true, - value: val - }); - - return obj; -}; - - -/***/ }), -/* 717 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(718); -var reader_1 = __webpack_require__(731); -var fs_stream_1 = __webpack_require__(735); -var ReaderAsync = /** @class */ (function (_super) { - __extends(ReaderAsync, _super); - function ReaderAsync() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(ReaderAsync.prototype, "fsAdapter", { - /** - * Returns FileSystem adapter. - */ - get: function () { - return new fs_stream_1.default(this.options); - }, - enumerable: true, - configurable: true - }); - /** - * Use async API to read entries for Task. - */ - ReaderAsync.prototype.read = function (task) { - var _this = this; - var root = this.getRootDirectory(task); - var options = this.getReaderOptions(task); - var entries = []; - return new Promise(function (resolve, reject) { - var stream = _this.api(root, task, options); - stream.on('error', function (err) { - _this.isEnoentCodeError(err) ? resolve([]) : reject(err); - stream.pause(); - }); - stream.on('data', function (entry) { return entries.push(_this.transform(entry)); }); - stream.on('end', function () { return resolve(entries); }); - }); - }; - /** - * Returns founded paths. - */ - ReaderAsync.prototype.api = function (root, task, options) { - if (task.dynamic) { - return this.dynamicApi(root, options); - } - return this.staticApi(task, options); - }; - /** - * Api for dynamic tasks. - */ - ReaderAsync.prototype.dynamicApi = function (root, options) { - return readdir.readdirStreamStat(root, options); - }; - /** - * Api for static tasks. - */ - ReaderAsync.prototype.staticApi = function (task, options) { - return this.fsAdapter.read(task.patterns, options.filter); - }; - return ReaderAsync; -}(reader_1.default)); -exports.default = ReaderAsync; - - -/***/ }), -/* 718 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const readdirSync = __webpack_require__(719); -const readdirAsync = __webpack_require__(727); -const readdirStream = __webpack_require__(730); - -module.exports = exports = readdirAsyncPath; -exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; -exports.readdirAsyncStat = exports.async.stat = readdirAsyncStat; -exports.readdirStream = exports.stream = readdirStreamPath; -exports.readdirStreamStat = exports.stream.stat = readdirStreamStat; -exports.readdirSync = exports.sync = readdirSyncPath; -exports.readdirSyncStat = exports.sync.stat = readdirSyncStat; - -/** - * Synchronous readdir that returns an array of string paths. - * - * @param {string} dir - * @param {object} [options] - * @returns {string[]} - */ -function readdirSyncPath (dir, options) { - return readdirSync(dir, options, {}); -} - -/** - * Synchronous readdir that returns results as an array of {@link fs.Stats} objects - * - * @param {string} dir - * @param {object} [options] - * @returns {fs.Stats[]} - */ -function readdirSyncStat (dir, options) { - return readdirSync(dir, options, { stats: true }); -} - -/** - * Aynchronous readdir (accepts an error-first callback or returns a {@link Promise}). - * Results are an array of path strings. - * - * @param {string} dir - * @param {object} [options] - * @param {function} [callback] - * @returns {Promise} - */ -function readdirAsyncPath (dir, options, callback) { - return readdirAsync(dir, options, callback, {}); -} - -/** - * Aynchronous readdir (accepts an error-first callback or returns a {@link Promise}). - * Results are an array of {@link fs.Stats} objects. - * - * @param {string} dir - * @param {object} [options] - * @param {function} [callback] - * @returns {Promise} - */ -function readdirAsyncStat (dir, options, callback) { - return readdirAsync(dir, options, callback, { stats: true }); -} - -/** - * Aynchronous readdir that returns a {@link stream.Readable} (which is also an {@link EventEmitter}). - * All stream data events ("data", "file", "directory", "symlink") are passed a path string. - * - * @param {string} dir - * @param {object} [options] - * @returns {stream.Readable} - */ -function readdirStreamPath (dir, options) { - return readdirStream(dir, options, {}); -} - -/** - * Aynchronous readdir that returns a {@link stream.Readable} (which is also an {@link EventEmitter}) - * All stream data events ("data", "file", "directory", "symlink") are passed an {@link fs.Stats} object. - * - * @param {string} dir - * @param {object} [options] - * @returns {stream.Readable} - */ -function readdirStreamStat (dir, options) { - return readdirStream(dir, options, { stats: true }); -} - - -/***/ }), -/* 719 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -module.exports = readdirSync; - -const DirectoryReader = __webpack_require__(720); - -let syncFacade = { - fs: __webpack_require__(725), - forEach: __webpack_require__(726), - sync: true -}; - -/** - * Returns the buffered output from a synchronous {@link DirectoryReader}. - * - * @param {string} dir - * @param {object} [options] - * @param {object} internalOptions - */ -function readdirSync (dir, options, internalOptions) { - internalOptions.facade = syncFacade; - - let reader = new DirectoryReader(dir, options, internalOptions); - let stream = reader.stream; - - let results = []; - let data = stream.read(); - while (data !== null) { - results.push(data); - data = stream.read(); - } - - return results; -} - - -/***/ }), -/* 720 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const Readable = __webpack_require__(138).Readable; -const EventEmitter = __webpack_require__(157).EventEmitter; -const path = __webpack_require__(4); -const normalizeOptions = __webpack_require__(721); -const stat = __webpack_require__(723); -const call = __webpack_require__(724); - -/** - * Asynchronously reads the contents of a directory and streams the results - * via a {@link stream.Readable}. - */ -class DirectoryReader { - /** - * @param {string} dir - The absolute or relative directory path to read - * @param {object} [options] - User-specified options, if any (see {@link normalizeOptions}) - * @param {object} internalOptions - Internal options that aren't part of the public API - * @class - */ - constructor (dir, options, internalOptions) { - this.options = options = normalizeOptions(options, internalOptions); - - // Indicates whether we should keep reading - // This is set false if stream.Readable.push() returns false. - this.shouldRead = true; - - // The directories to read - // (initialized with the top-level directory) - this.queue = [{ - path: dir, - basePath: options.basePath, - posixBasePath: options.posixBasePath, - depth: 0 - }]; - - // The number of directories that are currently being processed - this.pending = 0; - - // The data that has been read, but not yet emitted - this.buffer = []; - - this.stream = new Readable({ objectMode: true }); - this.stream._read = () => { - // Start (or resume) reading - this.shouldRead = true; - - // If we have data in the buffer, then send the next chunk - if (this.buffer.length > 0) { - this.pushFromBuffer(); - } - - // If we have directories queued, then start processing the next one - if (this.queue.length > 0) { - if (this.options.facade.sync) { - while (this.queue.length > 0) { - this.readNextDirectory(); - } - } - else { - this.readNextDirectory(); - } - } - - this.checkForEOF(); - }; - } - - /** - * Reads the next directory in the queue - */ - readNextDirectory () { - let facade = this.options.facade; - let dir = this.queue.shift(); - this.pending++; - - // Read the directory listing - call.safe(facade.fs.readdir, dir.path, (err, items) => { - if (err) { - // fs.readdir threw an error - this.emit('error', err); - return this.finishedReadingDirectory(); - } - - try { - // Process each item in the directory (simultaneously, if async) - facade.forEach( - items, - this.processItem.bind(this, dir), - this.finishedReadingDirectory.bind(this, dir) - ); - } - catch (err2) { - // facade.forEach threw an error - // (probably because fs.readdir returned an invalid result) - this.emit('error', err2); - this.finishedReadingDirectory(); - } - }); - } - - /** - * This method is called after all items in a directory have been processed. - * - * NOTE: This does not necessarily mean that the reader is finished, since there may still - * be other directories queued or pending. - */ - finishedReadingDirectory () { - this.pending--; - - if (this.shouldRead) { - // If we have directories queued, then start processing the next one - if (this.queue.length > 0 && this.options.facade.async) { - this.readNextDirectory(); - } - - this.checkForEOF(); - } - } - - /** - * Determines whether the reader has finished processing all items in all directories. - * If so, then the "end" event is fired (via {@Readable#push}) - */ - checkForEOF () { - if (this.buffer.length === 0 && // The stuff we've already read - this.pending === 0 && // The stuff we're currently reading - this.queue.length === 0) { // The stuff we haven't read yet - // There's no more stuff! - this.stream.push(null); - } - } - - /** - * Processes a single item in a directory. - * - * If the item is a directory, and `option.deep` is enabled, then the item will be added - * to the directory queue. - * - * If the item meets the filter criteria, then it will be emitted to the reader's stream. - * - * @param {object} dir - A directory object from the queue - * @param {string} item - The name of the item (name only, no path) - * @param {function} done - A callback function that is called after the item has been processed - */ - processItem (dir, item, done) { - let stream = this.stream; - let options = this.options; - - let itemPath = dir.basePath + item; - let posixPath = dir.posixBasePath + item; - let fullPath = path.join(dir.path, item); - - // If `options.deep` is a number, and we've already recursed to the max depth, - // then there's no need to check fs.Stats to know if it's a directory. - // If `options.deep` is a function, then we'll need fs.Stats - let maxDepthReached = dir.depth >= options.recurseDepth; - - // Do we need to call `fs.stat`? - let needStats = - !maxDepthReached || // we need the fs.Stats to know if it's a directory - options.stats || // the user wants fs.Stats objects returned - options.recurseFn || // we need fs.Stats for the recurse function - options.filterFn || // we need fs.Stats for the filter function - EventEmitter.listenerCount(stream, 'file') || // we need the fs.Stats to know if it's a file - EventEmitter.listenerCount(stream, 'directory') || // we need the fs.Stats to know if it's a directory - EventEmitter.listenerCount(stream, 'symlink'); // we need the fs.Stats to know if it's a symlink - - // If we don't need stats, then exit early - if (!needStats) { - if (this.filter(itemPath, posixPath)) { - this.pushOrBuffer({ data: itemPath }); - } - return done(); - } - - // Get the fs.Stats object for this path - stat(options.facade.fs, fullPath, (err, stats) => { - if (err) { - // fs.stat threw an error - this.emit('error', err); - return done(); - } - - try { - // Add the item's path to the fs.Stats object - // The base of this path, and its separators are determined by the options - // (i.e. options.basePath and options.sep) - stats.path = itemPath; - - // Add depth of the path to the fs.Stats object for use this in the filter function - stats.depth = dir.depth; - - if (this.shouldRecurse(stats, posixPath, maxDepthReached)) { - // Add this subdirectory to the queue - this.queue.push({ - path: fullPath, - basePath: itemPath + options.sep, - posixBasePath: posixPath + '/', - depth: dir.depth + 1, - }); - } - - // Determine whether this item matches the filter criteria - if (this.filter(stats, posixPath)) { - this.pushOrBuffer({ - data: options.stats ? stats : itemPath, - file: stats.isFile(), - directory: stats.isDirectory(), - symlink: stats.isSymbolicLink(), - }); - } - - done(); - } - catch (err2) { - // An error occurred while processing the item - // (probably during a user-specified function, such as options.deep, options.filter, etc.) - this.emit('error', err2); - done(); - } - }); - } - - /** - * Pushes the given chunk of data to the stream, or adds it to the buffer, - * depending on the state of the stream. - * - * @param {object} chunk - */ - pushOrBuffer (chunk) { - // Add the chunk to the buffer - this.buffer.push(chunk); - - // If we're still reading, then immediately emit the next chunk in the buffer - // (which may or may not be the chunk that we just added) - if (this.shouldRead) { - this.pushFromBuffer(); - } - } - - /** - * Immediately pushes the next chunk in the buffer to the reader's stream. - * The "data" event will always be fired (via {@link Readable#push}). - * In addition, the "file", "directory", and/or "symlink" events may be fired, - * depending on the type of properties of the chunk. - */ - pushFromBuffer () { - let stream = this.stream; - let chunk = this.buffer.shift(); - - // Stream the data - try { - this.shouldRead = stream.push(chunk.data); - } - catch (err) { - this.emit('error', err); - } - - // Also emit specific events, based on the type of chunk - chunk.file && this.emit('file', chunk.data); - chunk.symlink && this.emit('symlink', chunk.data); - chunk.directory && this.emit('directory', chunk.data); - } - - /** - * Determines whether the given directory meets the user-specified recursion criteria. - * If the user didn't specify recursion criteria, then this function will default to true. - * - * @param {fs.Stats} stats - The directory's {@link fs.Stats} object - * @param {string} posixPath - The item's POSIX path (used for glob matching) - * @param {boolean} maxDepthReached - Whether we've already crawled the user-specified depth - * @returns {boolean} - */ - shouldRecurse (stats, posixPath, maxDepthReached) { - let options = this.options; - - if (maxDepthReached) { - // We've already crawled to the maximum depth. So no more recursion. - return false; - } - else if (!stats.isDirectory()) { - // It's not a directory. So don't try to crawl it. - return false; - } - else if (options.recurseGlob) { - // Glob patterns are always tested against the POSIX path, even on Windows - // https://github.com/isaacs/node-glob#windows - return options.recurseGlob.test(posixPath); - } - else if (options.recurseRegExp) { - // Regular expressions are tested against the normal path - // (based on the OS or options.sep) - return options.recurseRegExp.test(stats.path); - } - else if (options.recurseFn) { - try { - // Run the user-specified recursion criteria - return options.recurseFn.call(null, stats); - } - catch (err) { - // An error occurred in the user's code. - // In Sync and Async modes, this will return an error. - // In Streaming mode, we emit an "error" event, but continue processing - this.emit('error', err); - } - } - else { - // No recursion function was specified, and we're within the maximum depth. - // So crawl this directory. - return true; - } - } - - /** - * Determines whether the given item meets the user-specified filter criteria. - * If the user didn't specify a filter, then this function will always return true. - * - * @param {string|fs.Stats} value - Either the item's path, or the item's {@link fs.Stats} object - * @param {string} posixPath - The item's POSIX path (used for glob matching) - * @returns {boolean} - */ - filter (value, posixPath) { - let options = this.options; - - if (options.filterGlob) { - // Glob patterns are always tested against the POSIX path, even on Windows - // https://github.com/isaacs/node-glob#windows - return options.filterGlob.test(posixPath); - } - else if (options.filterRegExp) { - // Regular expressions are tested against the normal path - // (based on the OS or options.sep) - return options.filterRegExp.test(value.path || value); - } - else if (options.filterFn) { - try { - // Run the user-specified filter function - return options.filterFn.call(null, value); - } - catch (err) { - // An error occurred in the user's code. - // In Sync and Async modes, this will return an error. - // In Streaming mode, we emit an "error" event, but continue processing - this.emit('error', err); - } - } - else { - // No filter was specified, so match everything - return true; - } - } - - /** - * Emits an event. If one of the event listeners throws an error, - * then an "error" event is emitted. - * - * @param {string} eventName - * @param {*} data - */ - emit (eventName, data) { - let stream = this.stream; - - try { - stream.emit(eventName, data); - } - catch (err) { - if (eventName === 'error') { - // Don't recursively emit "error" events. - // If the first one fails, then just throw - throw err; - } - else { - stream.emit('error', err); - } - } - } -} - -module.exports = DirectoryReader; - - -/***/ }), -/* 721 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const path = __webpack_require__(4); -const globToRegExp = __webpack_require__(722); - -module.exports = normalizeOptions; - -let isWindows = /^win/.test(process.platform); - -/** - * @typedef {Object} FSFacade - * @property {fs.readdir} readdir - * @property {fs.stat} stat - * @property {fs.lstat} lstat - */ - -/** - * Validates and normalizes the options argument - * - * @param {object} [options] - User-specified options, if any - * @param {object} internalOptions - Internal options that aren't part of the public API - * - * @param {number|boolean|function} [options.deep] - * The number of directories to recursively traverse. Any falsy value or negative number will - * default to zero, so only the top-level contents will be returned. Set to `true` or `Infinity` - * to traverse all subdirectories. Or provide a function that accepts a {@link fs.Stats} object - * and returns a truthy value if the directory's contents should be crawled. - * - * @param {function|string|RegExp} [options.filter] - * A function that accepts a {@link fs.Stats} object and returns a truthy value if the data should - * be returned. Or a RegExp or glob string pattern, to filter by file name. - * - * @param {string} [options.sep] - * The path separator to use. By default, the OS-specific separator will be used, but this can be - * set to a specific value to ensure consistency across platforms. - * - * @param {string} [options.basePath] - * The base path to prepend to each result. If empty, then all results will be relative to `dir`. - * - * @param {FSFacade} [options.fs] - * Synchronous or asynchronous facades for Node.js File System module - * - * @param {object} [internalOptions.facade] - * Synchronous or asynchronous facades for various methods, including for the Node.js File System module - * - * @param {boolean} [internalOptions.emit] - * Indicates whether the reader should emit "file", "directory", and "symlink" events - * - * @param {boolean} [internalOptions.stats] - * Indicates whether the reader should emit {@link fs.Stats} objects instead of path strings - * - * @returns {object} - */ -function normalizeOptions (options, internalOptions) { - if (options === null || options === undefined) { - options = {}; - } - else if (typeof options !== 'object') { - throw new TypeError('options must be an object'); - } - - let recurseDepth, recurseFn, recurseRegExp, recurseGlob, deep = options.deep; - if (deep === null || deep === undefined) { - recurseDepth = 0; - } - else if (typeof deep === 'boolean') { - recurseDepth = deep ? Infinity : 0; - } - else if (typeof deep === 'number') { - if (deep < 0 || isNaN(deep)) { - throw new Error('options.deep must be a positive number'); - } - else if (Math.floor(deep) !== deep) { - throw new Error('options.deep must be an integer'); - } - else { - recurseDepth = deep; - } - } - else if (typeof deep === 'function') { - recurseDepth = Infinity; - recurseFn = deep; - } - else if (deep instanceof RegExp) { - recurseDepth = Infinity; - recurseRegExp = deep; - } - else if (typeof deep === 'string' && deep.length > 0) { - recurseDepth = Infinity; - recurseGlob = globToRegExp(deep, { extended: true, globstar: true }); - } - else { - throw new TypeError('options.deep must be a boolean, number, function, regular expression, or glob pattern'); - } - - let filterFn, filterRegExp, filterGlob, filter = options.filter; - if (filter !== null && filter !== undefined) { - if (typeof filter === 'function') { - filterFn = filter; - } - else if (filter instanceof RegExp) { - filterRegExp = filter; - } - else if (typeof filter === 'string' && filter.length > 0) { - filterGlob = globToRegExp(filter, { extended: true, globstar: true }); - } - else { - throw new TypeError('options.filter must be a function, regular expression, or glob pattern'); - } - } - - let sep = options.sep; - if (sep === null || sep === undefined) { - sep = path.sep; - } - else if (typeof sep !== 'string') { - throw new TypeError('options.sep must be a string'); - } - - let basePath = options.basePath; - if (basePath === null || basePath === undefined) { - basePath = ''; - } - else if (typeof basePath === 'string') { - // Append a path separator to the basePath, if necessary - if (basePath && basePath.substr(-1) !== sep) { - basePath += sep; - } - } - else { - throw new TypeError('options.basePath must be a string'); - } - - // Convert the basePath to POSIX (forward slashes) - // so that glob pattern matching works consistently, even on Windows - let posixBasePath = basePath; - if (posixBasePath && sep !== '/') { - posixBasePath = posixBasePath.replace(new RegExp('\\' + sep, 'g'), '/'); - - /* istanbul ignore if */ - if (isWindows) { - // Convert Windows root paths (C:\) and UNCs (\\) to POSIX root paths - posixBasePath = posixBasePath.replace(/^([a-zA-Z]\:\/|\/\/)/, '/'); - } - } - - // Determine which facade methods to use - let facade; - if (options.fs === null || options.fs === undefined) { - // The user didn't provide their own facades, so use our internal ones - facade = internalOptions.facade; - } - else if (typeof options.fs === 'object') { - // Merge the internal facade methods with the user-provided `fs` facades - facade = Object.assign({}, internalOptions.facade); - facade.fs = Object.assign({}, internalOptions.facade.fs, options.fs); - } - else { - throw new TypeError('options.fs must be an object'); - } - - return { - recurseDepth, - recurseFn, - recurseRegExp, - recurseGlob, - filterFn, - filterRegExp, - filterGlob, - sep, - basePath, - posixBasePath, - facade, - emit: !!internalOptions.emit, - stats: !!internalOptions.stats, - }; -} - - -/***/ }), -/* 722 */ -/***/ (function(module, exports) { - -module.exports = function (glob, opts) { - if (typeof glob !== 'string') { - throw new TypeError('Expected a string'); - } - - var str = String(glob); - - // The regexp we are building, as a string. - var reStr = ""; - - // Whether we are matching so called "extended" globs (like bash) and should - // support single character matching, matching ranges of characters, group - // matching, etc. - var extended = opts ? !!opts.extended : false; - - // When globstar is _false_ (default), '/foo/*' is translated a regexp like - // '^\/foo\/.*$' which will match any string beginning with '/foo/' - // When globstar is _true_, '/foo/*' is translated to regexp like - // '^\/foo\/[^/]*$' which will match any string beginning with '/foo/' BUT - // which does not have a '/' to the right of it. - // E.g. with '/foo/*' these will match: '/foo/bar', '/foo/bar.txt' but - // these will not '/foo/bar/baz', '/foo/bar/baz.txt' - // Lastely, when globstar is _true_, '/foo/**' is equivelant to '/foo/*' when - // globstar is _false_ - var globstar = opts ? !!opts.globstar : false; - - // If we are doing extended matching, this boolean is true when we are inside - // a group (eg {*.html,*.js}), and false otherwise. - var inGroup = false; - - // RegExp flags (eg "i" ) to pass in to RegExp constructor. - var flags = opts && typeof( opts.flags ) === "string" ? opts.flags : ""; - - var c; - for (var i = 0, len = str.length; i < len; i++) { - c = str[i]; - - switch (c) { - case "\\": - case "/": - case "$": - case "^": - case "+": - case ".": - case "(": - case ")": - case "=": - case "!": - case "|": - reStr += "\\" + c; - break; - - case "?": - if (extended) { - reStr += "."; - break; - } - - case "[": - case "]": - if (extended) { - reStr += c; - break; - } - - case "{": - if (extended) { - inGroup = true; - reStr += "("; - break; - } - - case "}": - if (extended) { - inGroup = false; - reStr += ")"; - break; - } - - case ",": - if (inGroup) { - reStr += "|"; - break; - } - reStr += "\\" + c; - break; - - case "*": - // Move over all consecutive "*"'s. - // Also store the previous and next characters - var prevChar = str[i - 1]; - var starCount = 1; - while(str[i + 1] === "*") { - starCount++; - i++; - } - var nextChar = str[i + 1]; - - if (!globstar) { - // globstar is disabled, so treat any number of "*" as one - reStr += ".*"; - } else { - // globstar is enabled, so determine if this is a globstar segment - var isGlobstar = starCount > 1 // multiple "*"'s - && (prevChar === "/" || prevChar === undefined) // from the start of the segment - && (nextChar === "/" || nextChar === undefined) // to the end of the segment - - if (isGlobstar) { - // it's a globstar, so match zero or more path segments - reStr += "(?:[^/]*(?:\/|$))*"; - i++; // move over the "/" - } else { - // it's not a globstar, so only match one path segment - reStr += "[^/]*"; - } - } - break; - - default: - reStr += c; - } - } - - // When regexp 'g' flag is specified don't - // constrain the regular expression with ^ & $ - if (!flags || !~flags.indexOf('g')) { - reStr = "^" + reStr + "$"; - } - - return new RegExp(reStr, flags); -}; - - -/***/ }), -/* 723 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const call = __webpack_require__(724); - -module.exports = stat; - -/** - * Retrieves the {@link fs.Stats} for the given path. If the path is a symbolic link, - * then the Stats of the symlink's target are returned instead. If the symlink is broken, - * then the Stats of the symlink itself are returned. - * - * @param {object} fs - Synchronous or Asynchronouse facade for the "fs" module - * @param {string} path - The path to return stats for - * @param {function} callback - */ -function stat (fs, path, callback) { - let isSymLink = false; - - call.safe(fs.lstat, path, (err, lstats) => { - if (err) { - // fs.lstat threw an eror - return callback(err); - } - - try { - isSymLink = lstats.isSymbolicLink(); - } - catch (err2) { - // lstats.isSymbolicLink() threw an error - // (probably because fs.lstat returned an invalid result) - return callback(err2); - } - - if (isSymLink) { - // Try to resolve the symlink - symlinkStat(fs, path, lstats, callback); - } - else { - // It's not a symlink, so return the stats as-is - callback(null, lstats); - } - }); -} - -/** - * Retrieves the {@link fs.Stats} for the target of the given symlink. - * If the symlink is broken, then the Stats of the symlink itself are returned. - * - * @param {object} fs - Synchronous or Asynchronouse facade for the "fs" module - * @param {string} path - The path of the symlink to return stats for - * @param {object} lstats - The stats of the symlink - * @param {function} callback - */ -function symlinkStat (fs, path, lstats, callback) { - call.safe(fs.stat, path, (err, stats) => { - if (err) { - // The symlink is broken, so return the stats for the link itself - return callback(null, lstats); - } - - try { - // Return the stats for the resolved symlink target, - // and override the `isSymbolicLink` method to indicate that it's a symlink - stats.isSymbolicLink = () => true; - } - catch (err2) { - // Setting stats.isSymbolicLink threw an error - // (probably because fs.stat returned an invalid result) - return callback(err2); - } - - callback(null, stats); - }); -} - - -/***/ }), -/* 724 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -let call = module.exports = { - safe: safeCall, - once: callOnce, -}; - -/** - * Calls a function with the given arguments, and ensures that the error-first callback is _always_ - * invoked exactly once, even if the function throws an error. - * - * @param {function} fn - The function to invoke - * @param {...*} args - The arguments to pass to the function. The final argument must be a callback function. - */ -function safeCall (fn, args) { - // Get the function arguments as an array - args = Array.prototype.slice.call(arguments, 1); - - // Replace the callback function with a wrapper that ensures it will only be called once - let callback = call.once(args.pop()); - args.push(callback); - - try { - fn.apply(null, args); - } - catch (err) { - callback(err); - } -} - -/** - * Returns a wrapper function that ensures the given callback function is only called once. - * Subsequent calls are ignored, unless the first argument is an Error, in which case the - * error is thrown. - * - * @param {function} fn - The function that should only be called once - * @returns {function} - */ -function callOnce (fn) { - let fulfilled = false; - - return function onceWrapper (err) { - if (!fulfilled) { - fulfilled = true; - return fn.apply(this, arguments); - } - else if (err) { - // The callback has already been called, but now an error has occurred - // (most likely inside the callback function). So re-throw the error, - // so it gets handled further up the call stack - throw err; - } - }; -} - - -/***/ }), -/* 725 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const fs = __webpack_require__(134); -const call = __webpack_require__(724); - -/** - * A facade around {@link fs.readdirSync} that allows it to be called - * the same way as {@link fs.readdir}. - * - * @param {string} dir - * @param {function} callback - */ -exports.readdir = function (dir, callback) { - // Make sure the callback is only called once - callback = call.once(callback); - - try { - let items = fs.readdirSync(dir); - callback(null, items); - } - catch (err) { - callback(err); - } -}; - -/** - * A facade around {@link fs.statSync} that allows it to be called - * the same way as {@link fs.stat}. - * - * @param {string} path - * @param {function} callback - */ -exports.stat = function (path, callback) { - // Make sure the callback is only called once - callback = call.once(callback); - - try { - let stats = fs.statSync(path); - callback(null, stats); - } - catch (err) { - callback(err); - } -}; - -/** - * A facade around {@link fs.lstatSync} that allows it to be called - * the same way as {@link fs.lstat}. - * - * @param {string} path - * @param {function} callback - */ -exports.lstat = function (path, callback) { - // Make sure the callback is only called once - callback = call.once(callback); - - try { - let stats = fs.lstatSync(path); - callback(null, stats); - } - catch (err) { - callback(err); - } -}; - - -/***/ }), -/* 726 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -module.exports = syncForEach; - -/** - * A facade that allows {@link Array.forEach} to be called as though it were asynchronous. - * - * @param {array} array - The array to iterate over - * @param {function} iterator - The function to call for each item in the array - * @param {function} done - The function to call when all iterators have completed - */ -function syncForEach (array, iterator, done) { - array.forEach(item => { - iterator(item, () => { - // Note: No error-handling here because this is currently only ever called - // by DirectoryReader, which never passes an `error` parameter to the callback. - // Instead, DirectoryReader emits an "error" event if an error occurs. - }); - }); - - done(); -} - - -/***/ }), -/* 727 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -module.exports = readdirAsync; - -const maybe = __webpack_require__(728); -const DirectoryReader = __webpack_require__(720); - -let asyncFacade = { - fs: __webpack_require__(134), - forEach: __webpack_require__(729), - async: true -}; - -/** - * Returns the buffered output from an asynchronous {@link DirectoryReader}, - * via an error-first callback or a {@link Promise}. - * - * @param {string} dir - * @param {object} [options] - * @param {function} [callback] - * @param {object} internalOptions - */ -function readdirAsync (dir, options, callback, internalOptions) { - if (typeof options === 'function') { - callback = options; - options = undefined; - } - - return maybe(callback, new Promise(((resolve, reject) => { - let results = []; - - internalOptions.facade = asyncFacade; - - let reader = new DirectoryReader(dir, options, internalOptions); - let stream = reader.stream; - - stream.on('error', err => { - reject(err); - stream.pause(); - }); - stream.on('data', result => { - results.push(result); - }); - stream.on('end', () => { - resolve(results); - }); - }))); -} - - -/***/ }), -/* 728 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var next = (global.process && process.nextTick) || global.setImmediate || function (f) { - setTimeout(f, 0) -} - -module.exports = function maybe (cb, promise) { - if (cb) { - promise - .then(function (result) { - next(function () { cb(null, result) }) - }, function (err) { - next(function () { cb(err) }) - }) - return undefined - } - else { - return promise - } -} - - -/***/ }), -/* 729 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -module.exports = asyncForEach; - -/** - * Simultaneously processes all items in the given array. - * - * @param {array} array - The array to iterate over - * @param {function} iterator - The function to call for each item in the array - * @param {function} done - The function to call when all iterators have completed - */ -function asyncForEach (array, iterator, done) { - if (array.length === 0) { - // NOTE: Normally a bad idea to mix sync and async, but it's safe here because - // of the way that this method is currently used by DirectoryReader. - done(); - return; - } - - // Simultaneously process all items in the array. - let pending = array.length; - array.forEach(item => { - iterator(item, () => { - if (--pending === 0) { - done(); - } - }); - }); -} - - -/***/ }), -/* 730 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -module.exports = readdirStream; - -const DirectoryReader = __webpack_require__(720); - -let streamFacade = { - fs: __webpack_require__(134), - forEach: __webpack_require__(729), - async: true -}; - -/** - * Returns the {@link stream.Readable} of an asynchronous {@link DirectoryReader}. - * - * @param {string} dir - * @param {object} [options] - * @param {object} internalOptions - */ -function readdirStream (dir, options, internalOptions) { - internalOptions.facade = streamFacade; - - let reader = new DirectoryReader(dir, options, internalOptions); - return reader.stream; -} - - -/***/ }), -/* 731 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -var path = __webpack_require__(4); -var deep_1 = __webpack_require__(732); -var entry_1 = __webpack_require__(734); -var pathUtil = __webpack_require__(733); -var Reader = /** @class */ (function () { - function Reader(options) { - this.options = options; - this.micromatchOptions = this.getMicromatchOptions(); - this.entryFilter = new entry_1.default(options, this.micromatchOptions); - this.deepFilter = new deep_1.default(options, this.micromatchOptions); - } - /** - * Returns root path to scanner. - */ - Reader.prototype.getRootDirectory = function (task) { - return path.resolve(this.options.cwd, task.base); - }; - /** - * Returns options for reader. - */ - Reader.prototype.getReaderOptions = function (task) { - return { - basePath: task.base === '.' ? '' : task.base, - filter: this.entryFilter.getFilter(task.positive, task.negative), - deep: this.deepFilter.getFilter(task.positive, task.negative), - sep: '/' - }; - }; - /** - * Returns options for micromatch. - */ - Reader.prototype.getMicromatchOptions = function () { - return { - dot: this.options.dot, - nobrace: !this.options.brace, - noglobstar: !this.options.globstar, - noext: !this.options.extension, - nocase: !this.options.case, - matchBase: this.options.matchBase - }; - }; - /** - * Returns transformed entry. - */ - Reader.prototype.transform = function (entry) { - if (this.options.absolute) { - entry.path = pathUtil.makeAbsolute(this.options.cwd, entry.path); - } - if (this.options.markDirectories && entry.isDirectory()) { - entry.path += '/'; - } - var item = this.options.stats ? entry : entry.path; - if (this.options.transform === null) { - return item; - } - return this.options.transform(item); - }; - /** - * Returns true if error has ENOENT code. - */ - Reader.prototype.isEnoentCodeError = function (err) { - return err.code === 'ENOENT'; - }; - return Reader; -}()); -exports.default = Reader; - - -/***/ }), -/* 732 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(733); -var patternUtils = __webpack_require__(579); -var DeepFilter = /** @class */ (function () { - function DeepFilter(options, micromatchOptions) { - this.options = options; - this.micromatchOptions = micromatchOptions; - } - /** - * Returns filter for directories. - */ - DeepFilter.prototype.getFilter = function (positive, negative) { - var _this = this; - var maxPatternDepth = this.getMaxPatternDepth(positive); - var negativeRe = this.getNegativePatternsRe(negative); - return function (entry) { return _this.filter(entry, negativeRe, maxPatternDepth); }; - }; - /** - * Returns max depth of the provided patterns. - */ - DeepFilter.prototype.getMaxPatternDepth = function (patterns) { - var globstar = patterns.some(patternUtils.hasGlobStar); - return globstar ? Infinity : patternUtils.getMaxNaivePatternsDepth(patterns); - }; - /** - * Returns RegExp's for patterns that can affect the depth of reading. - */ - DeepFilter.prototype.getNegativePatternsRe = function (patterns) { - var affectDepthOfReadingPatterns = patterns.filter(patternUtils.isAffectDepthOfReadingPattern); - return patternUtils.convertPatternsToRe(affectDepthOfReadingPatterns, this.micromatchOptions); - }; - /** - * Returns «true» for directory that should be read. - */ - DeepFilter.prototype.filter = function (entry, negativeRe, maxPatternDepth) { - if (this.isSkippedByDeepOption(entry.depth)) { - return false; - } - if (this.isSkippedByMaxPatternDepth(entry.depth, maxPatternDepth)) { - return false; - } - if (this.isSkippedSymlinkedDirectory(entry)) { - return false; - } - if (this.isSkippedDotDirectory(entry)) { - return false; - } - return this.isSkippedByNegativePatterns(entry, negativeRe); - }; - /** - * Returns «true» when the «deep» option is disabled or number and depth of the entry is greater that the option value. - */ - DeepFilter.prototype.isSkippedByDeepOption = function (entryDepth) { - return !this.options.deep || (typeof this.options.deep === 'number' && entryDepth >= this.options.deep); - }; - /** - * Returns «true» when depth parameter is not an Infinity and entry depth greater that the parameter value. - */ - DeepFilter.prototype.isSkippedByMaxPatternDepth = function (entryDepth, maxPatternDepth) { - return maxPatternDepth !== Infinity && entryDepth >= maxPatternDepth; - }; - /** - * Returns «true» for symlinked directory if the «followSymlinkedDirectories» option is disabled. - */ - DeepFilter.prototype.isSkippedSymlinkedDirectory = function (entry) { - return !this.options.followSymlinkedDirectories && entry.isSymbolicLink(); - }; - /** - * Returns «true» for a directory whose name starts with a period if «dot» option is disabled. - */ - DeepFilter.prototype.isSkippedDotDirectory = function (entry) { - return !this.options.dot && pathUtils.isDotDirectory(entry.path); - }; - /** - * Returns «true» for a directory whose path math to any negative pattern. - */ - DeepFilter.prototype.isSkippedByNegativePatterns = function (entry, negativeRe) { - return !patternUtils.matchAny(entry.path, negativeRe); - }; - return DeepFilter; -}()); -exports.default = DeepFilter; - - -/***/ }), -/* 733 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -var path = __webpack_require__(4); -/** - * Returns «true» if the last partial of the path starting with a period. - */ -function isDotDirectory(filepath) { - return path.basename(filepath).startsWith('.'); -} -exports.isDotDirectory = isDotDirectory; -/** - * Convert a windows-like path to a unix-style path. - */ -function normalize(filepath) { - return filepath.replace(/\\/g, '/'); -} -exports.normalize = normalize; -/** - * Returns normalized absolute path of provided filepath. - */ -function makeAbsolute(cwd, filepath) { - return normalize(path.resolve(cwd, filepath)); -} -exports.makeAbsolute = makeAbsolute; - - -/***/ }), -/* 734 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(733); -var patternUtils = __webpack_require__(579); -var EntryFilter = /** @class */ (function () { - function EntryFilter(options, micromatchOptions) { - this.options = options; - this.micromatchOptions = micromatchOptions; - this.index = new Map(); - } - /** - * Returns filter for directories. - */ - EntryFilter.prototype.getFilter = function (positive, negative) { - var _this = this; - var positiveRe = patternUtils.convertPatternsToRe(positive, this.micromatchOptions); - var negativeRe = patternUtils.convertPatternsToRe(negative, this.micromatchOptions); - return function (entry) { return _this.filter(entry, positiveRe, negativeRe); }; - }; - /** - * Returns true if entry must be added to result. - */ - EntryFilter.prototype.filter = function (entry, positiveRe, negativeRe) { - // Exclude duplicate results - if (this.options.unique) { - if (this.isDuplicateEntry(entry)) { - return false; - } - this.createIndexRecord(entry); - } - // Filter files and directories by options - if (this.onlyFileFilter(entry) || this.onlyDirectoryFilter(entry)) { - return false; - } - if (this.isSkippedByAbsoluteNegativePatterns(entry, negativeRe)) { - return false; - } - return this.isMatchToPatterns(entry.path, positiveRe) && !this.isMatchToPatterns(entry.path, negativeRe); - }; - /** - * Return true if the entry already has in the cross reader index. - */ - EntryFilter.prototype.isDuplicateEntry = function (entry) { - return this.index.has(entry.path); - }; - /** - * Create record in the cross reader index. - */ - EntryFilter.prototype.createIndexRecord = function (entry) { - this.index.set(entry.path, undefined); - }; - /** - * Returns true for non-files if the «onlyFiles» option is enabled. - */ - EntryFilter.prototype.onlyFileFilter = function (entry) { - return this.options.onlyFiles && !entry.isFile(); - }; - /** - * Returns true for non-directories if the «onlyDirectories» option is enabled. - */ - EntryFilter.prototype.onlyDirectoryFilter = function (entry) { - return this.options.onlyDirectories && !entry.isDirectory(); - }; - /** - * Return true when `absolute` option is enabled and matched to the negative patterns. - */ - EntryFilter.prototype.isSkippedByAbsoluteNegativePatterns = function (entry, negativeRe) { - if (!this.options.absolute) { - return false; - } - var fullpath = pathUtils.makeAbsolute(this.options.cwd, entry.path); - return this.isMatchToPatterns(fullpath, negativeRe); - }; - /** - * Return true when entry match to provided patterns. - * - * First, just trying to apply patterns to the path. - * Second, trying to apply patterns to the path with final slash (need to micromatch to support «directory/**» patterns). - */ - EntryFilter.prototype.isMatchToPatterns = function (filepath, patternsRe) { - return patternUtils.matchAny(filepath, patternsRe) || patternUtils.matchAny(filepath + '/', patternsRe); - }; - return EntryFilter; -}()); -exports.default = EntryFilter; - - -/***/ }), -/* 735 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -var stream = __webpack_require__(138); -var fsStat = __webpack_require__(736); -var fs_1 = __webpack_require__(740); -var FileSystemStream = /** @class */ (function (_super) { - __extends(FileSystemStream, _super); - function FileSystemStream() { - return _super !== null && _super.apply(this, arguments) || this; - } - /** - * Use stream API to read entries for Task. - */ - FileSystemStream.prototype.read = function (patterns, filter) { - var _this = this; - var filepaths = patterns.map(this.getFullEntryPath, this); - var transform = new stream.Transform({ objectMode: true }); - transform._transform = function (index, _enc, done) { - return _this.getEntry(filepaths[index], patterns[index]).then(function (entry) { - if (entry !== null && filter(entry)) { - transform.push(entry); - } - if (index === filepaths.length - 1) { - transform.end(); - } - done(); - }); - }; - for (var i = 0; i < filepaths.length; i++) { - transform.write(i); - } - return transform; - }; - /** - * Return entry for the provided path. - */ - FileSystemStream.prototype.getEntry = function (filepath, pattern) { - var _this = this; - return this.getStat(filepath) - .then(function (stat) { return _this.makeEntry(stat, pattern); }) - .catch(function () { return null; }); - }; - /** - * Return fs.Stats for the provided path. - */ - FileSystemStream.prototype.getStat = function (filepath) { - return fsStat.stat(filepath, { throwErrorOnBrokenSymlinks: false }); - }; - return FileSystemStream; -}(fs_1.default)); -exports.default = FileSystemStream; - - -/***/ }), -/* 736 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(737); -const statProvider = __webpack_require__(739); -/** - * Asynchronous API. - */ -function stat(path, opts) { - return new Promise((resolve, reject) => { - statProvider.async(path, optionsManager.prepare(opts), (err, stats) => err ? reject(err) : resolve(stats)); - }); -} -exports.stat = stat; -function statCallback(path, optsOrCallback, callback) { - if (typeof optsOrCallback === 'function') { - callback = optsOrCallback; /* tslint:disable-line: no-parameter-reassignment */ - optsOrCallback = undefined; /* tslint:disable-line: no-parameter-reassignment */ - } - if (typeof callback === 'undefined') { - throw new TypeError('The "callback" argument must be of type Function.'); - } - statProvider.async(path, optionsManager.prepare(optsOrCallback), callback); -} -exports.statCallback = statCallback; -/** - * Synchronous API. - */ -function statSync(path, opts) { - return statProvider.sync(path, optionsManager.prepare(opts)); -} -exports.statSync = statSync; - - -/***/ }), -/* 737 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(738); -function prepare(opts) { - const options = Object.assign({ - fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), - throwErrorOnBrokenSymlinks: true, - followSymlinks: true - }, opts); - return options; -} -exports.prepare = prepare; - - -/***/ }), -/* 738 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(134); -exports.FILE_SYSTEM_ADAPTER = { - lstat: fs.lstat, - stat: fs.stat, - lstatSync: fs.lstatSync, - statSync: fs.statSync -}; -function getFileSystemAdapter(fsMethods) { - if (!fsMethods) { - return exports.FILE_SYSTEM_ADAPTER; - } - return Object.assign({}, exports.FILE_SYSTEM_ADAPTER, fsMethods); -} -exports.getFileSystemAdapter = getFileSystemAdapter; - - -/***/ }), -/* 739 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -function sync(path, options) { - const lstat = options.fs.lstatSync(path); - if (!isFollowedSymlink(lstat, options)) { - return lstat; - } - try { - const stat = options.fs.statSync(path); - stat.isSymbolicLink = () => true; - return stat; - } - catch (err) { - if (!options.throwErrorOnBrokenSymlinks) { - return lstat; - } - throw err; - } -} -exports.sync = sync; -function async(path, options, callback) { - options.fs.lstat(path, (err0, lstat) => { - if (err0) { - return callback(err0, undefined); - } - if (!isFollowedSymlink(lstat, options)) { - return callback(null, lstat); - } - options.fs.stat(path, (err1, stat) => { - if (err1) { - return options.throwErrorOnBrokenSymlinks ? callback(err1) : callback(null, lstat); - } - stat.isSymbolicLink = () => true; - callback(null, stat); - }); - }); -} -exports.async = async; -/** - * Returns `true` for followed symlink. - */ -function isFollowedSymlink(stat, options) { - return stat.isSymbolicLink() && options.followSymlinks; -} -exports.isFollowedSymlink = isFollowedSymlink; - - -/***/ }), -/* 740 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -var path = __webpack_require__(4); -var FileSystem = /** @class */ (function () { - function FileSystem(options) { - this.options = options; - } - /** - * Return full path to entry. - */ - FileSystem.prototype.getFullEntryPath = function (filepath) { - return path.resolve(this.options.cwd, filepath); - }; - /** - * Return an implementation of the Entry interface. - */ - FileSystem.prototype.makeEntry = function (stat, pattern) { - stat.path = pattern; - stat.depth = pattern.split('/').length; - return stat; - }; - return FileSystem; -}()); -exports.default = FileSystem; - - -/***/ }), -/* 741 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -var stream = __webpack_require__(138); -var readdir = __webpack_require__(718); -var reader_1 = __webpack_require__(731); -var fs_stream_1 = __webpack_require__(735); -var TransformStream = /** @class */ (function (_super) { - __extends(TransformStream, _super); - function TransformStream(reader) { - var _this = _super.call(this, { objectMode: true }) || this; - _this.reader = reader; - return _this; - } - TransformStream.prototype._transform = function (entry, _encoding, callback) { - callback(null, this.reader.transform(entry)); - }; - return TransformStream; -}(stream.Transform)); -var ReaderStream = /** @class */ (function (_super) { - __extends(ReaderStream, _super); - function ReaderStream() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(ReaderStream.prototype, "fsAdapter", { - /** - * Returns FileSystem adapter. - */ - get: function () { - return new fs_stream_1.default(this.options); - }, - enumerable: true, - configurable: true - }); - /** - * Use stream API to read entries for Task. - */ - ReaderStream.prototype.read = function (task) { - var _this = this; - var root = this.getRootDirectory(task); - var options = this.getReaderOptions(task); - var transform = new TransformStream(this); - var readable = this.api(root, task, options); - return readable - .on('error', function (err) { return _this.isEnoentCodeError(err) ? null : transform.emit('error', err); }) - .pipe(transform); - }; - /** - * Returns founded paths. - */ - ReaderStream.prototype.api = function (root, task, options) { - if (task.dynamic) { - return this.dynamicApi(root, options); - } - return this.staticApi(task, options); - }; - /** - * Api for dynamic tasks. - */ - ReaderStream.prototype.dynamicApi = function (root, options) { - return readdir.readdirStreamStat(root, options); - }; - /** - * Api for static tasks. - */ - ReaderStream.prototype.staticApi = function (task, options) { - return this.fsAdapter.read(task.patterns, options.filter); - }; - return ReaderStream; -}(reader_1.default)); -exports.default = ReaderStream; - - -/***/ }), -/* 742 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(718); -var reader_1 = __webpack_require__(731); -var fs_sync_1 = __webpack_require__(743); -var ReaderSync = /** @class */ (function (_super) { - __extends(ReaderSync, _super); - function ReaderSync() { - return _super !== null && _super.apply(this, arguments) || this; - } - Object.defineProperty(ReaderSync.prototype, "fsAdapter", { - /** - * Returns FileSystem adapter. - */ - get: function () { - return new fs_sync_1.default(this.options); - }, - enumerable: true, - configurable: true - }); - /** - * Use sync API to read entries for Task. - */ - ReaderSync.prototype.read = function (task) { - var root = this.getRootDirectory(task); - var options = this.getReaderOptions(task); - try { - var entries = this.api(root, task, options); - return entries.map(this.transform, this); - } - catch (err) { - if (this.isEnoentCodeError(err)) { - return []; - } - throw err; - } - }; - /** - * Returns founded paths. - */ - ReaderSync.prototype.api = function (root, task, options) { - if (task.dynamic) { - return this.dynamicApi(root, options); - } - return this.staticApi(task, options); - }; - /** - * Api for dynamic tasks. - */ - ReaderSync.prototype.dynamicApi = function (root, options) { - return readdir.readdirSyncStat(root, options); - }; - /** - * Api for static tasks. - */ - ReaderSync.prototype.staticApi = function (task, options) { - return this.fsAdapter.read(task.patterns, options.filter); - }; - return ReaderSync; -}(reader_1.default)); -exports.default = ReaderSync; - - -/***/ }), -/* 743 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -var __extends = (this && this.__extends) || (function () { - var extendStatics = function (d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); - }; - return function (d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); - }; -})(); -Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(736); -var fs_1 = __webpack_require__(740); -var FileSystemSync = /** @class */ (function (_super) { - __extends(FileSystemSync, _super); - function FileSystemSync() { - return _super !== null && _super.apply(this, arguments) || this; - } - /** - * Use sync API to read entries for Task. - */ - FileSystemSync.prototype.read = function (patterns, filter) { - var _this = this; - var entries = []; - patterns.forEach(function (pattern) { - var filepath = _this.getFullEntryPath(pattern); - var entry = _this.getEntry(filepath, pattern); - if (entry === null || !filter(entry)) { - return; - } - entries.push(entry); - }); - return entries; - }; - /** - * Return entry for the provided path. - */ - FileSystemSync.prototype.getEntry = function (filepath, pattern) { - try { - var stat = this.getStat(filepath); - return this.makeEntry(stat, pattern); - } - catch (err) { - return null; - } - }; - /** - * Return fs.Stats for the provided path. - */ - FileSystemSync.prototype.getStat = function (filepath) { - return fsStat.statSync(filepath, { throwErrorOnBrokenSymlinks: false }); - }; - return FileSystemSync; -}(fs_1.default)); -exports.default = FileSystemSync; - - -/***/ }), -/* 744 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -/** - * Flatten nested arrays (max depth is 2) into a non-nested array of non-array items. - */ -function flatten(items) { - return items.reduce(function (collection, item) { return [].concat(collection, item); }, []); -} -exports.flatten = flatten; - - -/***/ }), -/* 745 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -var merge2 = __webpack_require__(354); -/** - * Merge multiple streams and propagate their errors into one stream in parallel. - */ -function merge(streams) { - var mergedStream = merge2(streams); - streams.forEach(function (stream) { - stream.on('error', function (err) { return mergedStream.emit('error', err); }); - }); - return mergedStream; -} -exports.merge = merge; - - -/***/ }), -/* 746 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const path = __webpack_require__(4); -const pathType = __webpack_require__(747); - -const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; - -const getPath = (filepath, cwd) => { - const pth = filepath[0] === '!' ? filepath.slice(1) : filepath; - return path.isAbsolute(pth) ? pth : path.join(cwd, pth); -}; - -const addExtensions = (file, extensions) => { - if (path.extname(file)) { - return `**/${file}`; - } - - return `**/${file}.${getExtensions(extensions)}`; -}; - -const getGlob = (dir, opts) => { - if (opts.files && !Array.isArray(opts.files)) { - throw new TypeError(`Expected \`files\` to be of type \`Array\` but received type \`${typeof opts.files}\``); - } - - if (opts.extensions && !Array.isArray(opts.extensions)) { - throw new TypeError(`Expected \`extensions\` to be of type \`Array\` but received type \`${typeof opts.extensions}\``); - } - - if (opts.files && opts.extensions) { - return opts.files.map(x => path.join(dir, addExtensions(x, opts.extensions))); - } - - if (opts.files) { - return opts.files.map(x => path.join(dir, `**/${x}`)); - } - - if (opts.extensions) { - return [path.join(dir, `**/*.${getExtensions(opts.extensions)}`)]; - } - - return [path.join(dir, '**')]; -}; - -module.exports = (input, opts) => { - opts = Object.assign({cwd: process.cwd()}, opts); - - if (typeof opts.cwd !== 'string') { - return Promise.reject(new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof opts.cwd}\``)); - } - - return Promise.all([].concat(input).map(x => pathType.dir(getPath(x, opts.cwd)) - .then(isDir => isDir ? getGlob(x, opts) : x))) - .then(globs => [].concat.apply([], globs)); -}; - -module.exports.sync = (input, opts) => { - opts = Object.assign({cwd: process.cwd()}, opts); - - if (typeof opts.cwd !== 'string') { - throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof opts.cwd}\``); - } - - const globs = [].concat(input).map(x => pathType.dirSync(getPath(x, opts.cwd)) ? getGlob(x, opts) : x); - return [].concat.apply([], globs); -}; - - -/***/ }), -/* 747 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const fs = __webpack_require__(134); -const pify = __webpack_require__(748); - -function type(fn, fn2, fp) { - if (typeof fp !== 'string') { - return Promise.reject(new TypeError(`Expected a string, got ${typeof fp}`)); - } - - return pify(fs[fn])(fp) - .then(stats => stats[fn2]()) - .catch(err => { - if (err.code === 'ENOENT') { - return false; - } - - throw err; - }); -} - -function typeSync(fn, fn2, fp) { - if (typeof fp !== 'string') { - throw new TypeError(`Expected a string, got ${typeof fp}`); - } - - try { - return fs[fn](fp)[fn2](); - } catch (err) { - if (err.code === 'ENOENT') { - return false; - } - - throw err; - } -} - -exports.file = type.bind(null, 'stat', 'isFile'); -exports.dir = type.bind(null, 'stat', 'isDirectory'); -exports.symlink = type.bind(null, 'lstat', 'isSymbolicLink'); -exports.fileSync = typeSync.bind(null, 'statSync', 'isFile'); -exports.dirSync = typeSync.bind(null, 'statSync', 'isDirectory'); -exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); - - -/***/ }), -/* 748 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const processFn = (fn, opts) => function () { - const P = opts.promiseModule; - const args = new Array(arguments.length); - - for (let i = 0; i < arguments.length; i++) { - args[i] = arguments[i]; - } - - return new P((resolve, reject) => { - if (opts.errorFirst) { - args.push(function (err, result) { - if (opts.multiArgs) { - const results = new Array(arguments.length - 1); - - for (let i = 1; i < arguments.length; i++) { - results[i - 1] = arguments[i]; - } - - if (err) { - results.unshift(err); - reject(results); - } else { - resolve(results); - } - } else if (err) { - reject(err); - } else { - resolve(result); - } - }); - } else { - args.push(function (result) { - if (opts.multiArgs) { - const results = new Array(arguments.length - 1); - - for (let i = 0; i < arguments.length; i++) { - results[i] = arguments[i]; - } - - resolve(results); - } else { - resolve(result); - } - }); - } - - fn.apply(this, args); - }); -}; - -module.exports = (obj, opts) => { - opts = Object.assign({ - exclude: [/.+(Sync|Stream)$/], - errorFirst: true, - promiseModule: Promise - }, opts); - - const filter = key => { - const match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key); - return opts.include ? opts.include.some(match) : !opts.exclude.some(match); - }; - - let ret; - if (typeof obj === 'function') { - ret = function () { - if (opts.excludeMain) { - return obj.apply(this, arguments); - } - - return processFn(obj, opts).apply(this, arguments); - }; - } else { - ret = Object.create(Object.getPrototypeOf(obj)); - } - - for (const key in obj) { // eslint-disable-line guard-for-in - const x = obj[key]; - ret[key] = typeof x === 'function' && filter(key) ? processFn(x, opts) : x; - } - - return ret; -}; - - -/***/ }), -/* 749 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const fs = __webpack_require__(134); -const path = __webpack_require__(4); -const fastGlob = __webpack_require__(575); -const gitIgnore = __webpack_require__(750); -const pify = __webpack_require__(285); -const slash = __webpack_require__(751); - -const DEFAULT_IGNORE = [ - '**/node_modules/**', - '**/bower_components/**', - '**/flow-typed/**', - '**/coverage/**', - '**/.git' -]; - -const readFileP = pify(fs.readFile); - -const mapGitIgnorePatternTo = base => ignore => { - if (ignore.startsWith('!')) { - return '!' + path.posix.join(base, ignore.slice(1)); - } - - return path.posix.join(base, ignore); -}; - -const parseGitIgnore = (content, options) => { - const base = slash(path.relative(options.cwd, path.dirname(options.fileName))); - - return content - .split(/\r?\n/) - .filter(Boolean) - .filter(line => line.charAt(0) !== '#') - .map(mapGitIgnorePatternTo(base)); -}; - -const reduceIgnore = files => { - return files.reduce((ignores, file) => { - ignores.add(parseGitIgnore(file.content, { - cwd: file.cwd, - fileName: file.filePath - })); - return ignores; - }, gitIgnore()); -}; - -const getIsIgnoredPredecate = (ignores, cwd) => { - return p => ignores.ignores(slash(path.relative(cwd, p))); -}; - -const getFile = (file, cwd) => { - const filePath = path.join(cwd, file); - return readFileP(filePath, 'utf8') - .then(content => ({ - content, - cwd, - filePath - })); -}; - -const getFileSync = (file, cwd) => { - const filePath = path.join(cwd, file); - const content = fs.readFileSync(filePath, 'utf8'); - - return { - content, - cwd, - filePath - }; -}; - -const normalizeOptions = (options = {}) => { - const ignore = options.ignore || []; - const cwd = options.cwd || process.cwd(); - return {ignore, cwd}; -}; - -module.exports = options => { - options = normalizeOptions(options); - - return fastGlob('**/.gitignore', { - ignore: DEFAULT_IGNORE.concat(options.ignore), - cwd: options.cwd - }) - .then(paths => Promise.all(paths.map(file => getFile(file, options.cwd)))) - .then(files => reduceIgnore(files)) - .then(ignores => getIsIgnoredPredecate(ignores, options.cwd)); -}; - -module.exports.sync = options => { - options = normalizeOptions(options); - - const paths = fastGlob.sync('**/.gitignore', { - ignore: DEFAULT_IGNORE.concat(options.ignore), - cwd: options.cwd - }); - const files = paths.map(file => getFileSync(file, options.cwd)); - const ignores = reduceIgnore(files); - - return getIsIgnoredPredecate(ignores, options.cwd); -}; - - -/***/ }), -/* 750 */ -/***/ (function(module, exports) { - -// A simple implementation of make-array -function make_array (subject) { - return Array.isArray(subject) - ? subject - : [subject] -} - -const REGEX_BLANK_LINE = /^\s+$/ -const REGEX_LEADING_EXCAPED_EXCLAMATION = /^\\!/ -const REGEX_LEADING_EXCAPED_HASH = /^\\#/ -const SLASH = '/' -const KEY_IGNORE = typeof Symbol !== 'undefined' - ? Symbol.for('node-ignore') - /* istanbul ignore next */ - : 'node-ignore' - -const define = (object, key, value) => - Object.defineProperty(object, key, {value}) - -const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g - -// Sanitize the range of a regular expression -// The cases are complicated, see test cases for details -const sanitizeRange = range => range.replace( - REGEX_REGEXP_RANGE, - (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) - ? match - // Invalid range (out of order) which is ok for gitignore rules but - // fatal for JavaScript regular expression, so eliminate it. - : '' -) - -// > If the pattern ends with a slash, -// > it is removed for the purpose of the following description, -// > but it would only find a match with a directory. -// > In other words, foo/ will match a directory foo and paths underneath it, -// > but will not match a regular file or a symbolic link foo -// > (this is consistent with the way how pathspec works in general in Git). -// '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`' -// -> ignore-rules will not deal with it, because it costs extra `fs.stat` call -// you could use option `mark: true` with `glob` - -// '`foo/`' should not continue with the '`..`' -const DEFAULT_REPLACER_PREFIX = [ - - // > Trailing spaces are ignored unless they are quoted with backslash ("\") - [ - // (a\ ) -> (a ) - // (a ) -> (a) - // (a \ ) -> (a ) - /\\?\s+$/, - match => match.indexOf('\\') === 0 - ? ' ' - : '' - ], - - // replace (\ ) with ' ' - [ - /\\\s/g, - () => ' ' - ], - - // Escape metacharacters - // which is written down by users but means special for regular expressions. - - // > There are 12 characters with special meanings: - // > - the backslash \, - // > - the caret ^, - // > - the dollar sign $, - // > - the period or dot ., - // > - the vertical bar or pipe symbol |, - // > - the question mark ?, - // > - the asterisk or star *, - // > - the plus sign +, - // > - the opening parenthesis (, - // > - the closing parenthesis ), - // > - and the opening square bracket [, - // > - the opening curly brace {, - // > These special characters are often called "metacharacters". - [ - /[\\^$.|*+(){]/g, - match => `\\${match}` - ], - - [ - // > [abc] matches any character inside the brackets - // > (in this case a, b, or c); - /\[([^\]/]*)($|\])/g, - (match, p1, p2) => p2 === ']' - ? `[${sanitizeRange(p1)}]` - : `\\${match}` - ], - - [ - // > a question mark (?) matches a single character - /(?!\\)\?/g, - () => '[^/]' - ], - - // leading slash - [ - - // > A leading slash matches the beginning of the pathname. - // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". - // A leading slash matches the beginning of the pathname - /^\//, - () => '^' - ], - - // replace special metacharacter slash after the leading slash - [ - /\//g, - () => '\\/' - ], - - [ - // > A leading "**" followed by a slash means match in all directories. - // > For example, "**/foo" matches file or directory "foo" anywhere, - // > the same as pattern "foo". - // > "**/foo/bar" matches file or directory "bar" anywhere that is directly - // > under directory "foo". - // Notice that the '*'s have been replaced as '\\*' - /^\^*\\\*\\\*\\\//, - - // '**/foo' <-> 'foo' - () => '^(?:.*\\/)?' - ] -] - -const DEFAULT_REPLACER_SUFFIX = [ - // starting - [ - // there will be no leading '/' - // (which has been replaced by section "leading slash") - // If starts with '**', adding a '^' to the regular expression also works - /^(?=[^^])/, - function startingReplacer () { - return !/\/(?!$)/.test(this) - // > If the pattern does not contain a slash /, - // > Git treats it as a shell glob pattern - // Actually, if there is only a trailing slash, - // git also treats it as a shell glob pattern - ? '(?:^|\\/)' - - // > Otherwise, Git treats the pattern as a shell glob suitable for - // > consumption by fnmatch(3) - : '^' - } - ], - - // two globstars - [ - // Use lookahead assertions so that we could match more than one `'/**'` - /\\\/\\\*\\\*(?=\\\/|$)/g, - - // Zero, one or several directories - // should not use '*', or it will be replaced by the next replacer - - // Check if it is not the last `'/**'` - (match, index, str) => index + 6 < str.length - - // case: /**/ - // > A slash followed by two consecutive asterisks then a slash matches - // > zero or more directories. - // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on. - // '/**/' - ? '(?:\\/[^\\/]+)*' - - // case: /** - // > A trailing `"/**"` matches everything inside. - - // #21: everything inside but it should not include the current folder - : '\\/.+' - ], - - // intermediate wildcards - [ - // Never replace escaped '*' - // ignore rule '\*' will match the path '*' - - // 'abc.*/' -> go - // 'abc.*' -> skip this rule - /(^|[^\\]+)\\\*(?=.+)/g, - - // '*.js' matches '.js' - // '*.js' doesn't match 'abc' - (match, p1) => `${p1}[^\\/]*` - ], - - // trailing wildcard - [ - /(\^|\\\/)?\\\*$/, - (match, p1) => { - const prefix = p1 - // '\^': - // '/*' does not match '' - // '/*' does not match everything - - // '\\\/': - // 'abc/*' does not match 'abc/' - ? `${p1}[^/]+` - - // 'a*' matches 'a' - // 'a*' matches 'aa' - : '[^/]*' - - return `${prefix}(?=$|\\/$)` - } - ], - - [ - // unescape - /\\\\\\/g, - () => '\\' - ] -] - -const POSITIVE_REPLACERS = [ - ...DEFAULT_REPLACER_PREFIX, - - // 'f' - // matches - // - /f(end) - // - /f/ - // - (start)f(end) - // - (start)f/ - // doesn't match - // - oof - // - foo - // pseudo: - // -> (^|/)f(/|$) - - // ending - [ - // 'js' will not match 'js.' - // 'ab' will not match 'abc' - /(?:[^*/])$/, - - // 'js*' will not match 'a.js' - // 'js/' will not match 'a.js' - // 'js' will match 'a.js' and 'a.js/' - match => `${match}(?=$|\\/)` - ], - - ...DEFAULT_REPLACER_SUFFIX -] - -const NEGATIVE_REPLACERS = [ - ...DEFAULT_REPLACER_PREFIX, - - // #24, #38 - // The MISSING rule of [gitignore docs](https://git-scm.com/docs/gitignore) - // A negative pattern without a trailing wildcard should not - // re-include the things inside that directory. - - // eg: - // ['node_modules/*', '!node_modules'] - // should ignore `node_modules/a.js` - [ - /(?:[^*])$/, - match => `${match}(?=$|\\/$)` - ], - - ...DEFAULT_REPLACER_SUFFIX -] - -// A simple cache, because an ignore rule only has only one certain meaning -const cache = Object.create(null) - -// @param {pattern} -const make_regex = (pattern, negative, ignorecase) => { - const r = cache[pattern] - if (r) { - return r - } - - const replacers = negative - ? NEGATIVE_REPLACERS - : POSITIVE_REPLACERS - - const source = replacers.reduce( - (prev, current) => prev.replace(current[0], current[1].bind(pattern)), - pattern - ) - - return cache[pattern] = ignorecase - ? new RegExp(source, 'i') - : new RegExp(source) -} - -// > A blank line matches no files, so it can serve as a separator for readability. -const checkPattern = pattern => pattern - && typeof pattern === 'string' - && !REGEX_BLANK_LINE.test(pattern) - - // > A line starting with # serves as a comment. - && pattern.indexOf('#') !== 0 - -const createRule = (pattern, ignorecase) => { - const origin = pattern - let negative = false - - // > An optional prefix "!" which negates the pattern; - if (pattern.indexOf('!') === 0) { - negative = true - pattern = pattern.substr(1) - } - - pattern = pattern - // > Put a backslash ("\") in front of the first "!" for patterns that - // > begin with a literal "!", for example, `"\!important!.txt"`. - .replace(REGEX_LEADING_EXCAPED_EXCLAMATION, '!') - // > Put a backslash ("\") in front of the first hash for patterns that - // > begin with a hash. - .replace(REGEX_LEADING_EXCAPED_HASH, '#') - - const regex = make_regex(pattern, negative, ignorecase) - - return { - origin, - pattern, - negative, - regex - } -} - -class IgnoreBase { - constructor ({ - ignorecase = true - } = {}) { - this._rules = [] - this._ignorecase = ignorecase - define(this, KEY_IGNORE, true) - this._initCache() - } - - _initCache () { - this._cache = Object.create(null) - } - - // @param {Array.|string|Ignore} pattern - add (pattern) { - this._added = false - - if (typeof pattern === 'string') { - pattern = pattern.split(/\r?\n/g) - } - - make_array(pattern).forEach(this._addPattern, this) - - // Some rules have just added to the ignore, - // making the behavior changed. - if (this._added) { - this._initCache() - } - - return this - } - - // legacy - addPattern (pattern) { - return this.add(pattern) - } - - _addPattern (pattern) { - // #32 - if (pattern && pattern[KEY_IGNORE]) { - this._rules = this._rules.concat(pattern._rules) - this._added = true - return - } - - if (checkPattern(pattern)) { - const rule = createRule(pattern, this._ignorecase) - this._added = true - this._rules.push(rule) - } - } - - filter (paths) { - return make_array(paths).filter(path => this._filter(path)) - } - - createFilter () { - return path => this._filter(path) - } - - ignores (path) { - return !this._filter(path) - } - - // @returns `Boolean` true if the `path` is NOT ignored - _filter (path, slices) { - if (!path) { - return false - } - - if (path in this._cache) { - return this._cache[path] - } - - if (!slices) { - // path/to/a.js - // ['path', 'to', 'a.js'] - slices = path.split(SLASH) - } - - slices.pop() - - return this._cache[path] = slices.length - // > It is not possible to re-include a file if a parent directory of - // > that file is excluded. - // If the path contains a parent directory, check the parent first - ? this._filter(slices.join(SLASH) + SLASH, slices) - && this._test(path) - - // Or only test the path - : this._test(path) - } - - // @returns {Boolean} true if a file is NOT ignored - _test (path) { - // Explicitly define variable type by setting matched to `0` - let matched = 0 - - this._rules.forEach(rule => { - // if matched = true, then we only test negative rules - // if matched = false, then we test non-negative rules - if (!(matched ^ rule.negative)) { - matched = rule.negative ^ rule.regex.test(path) - } - }) - - return !matched - } -} - -// Windows -// -------------------------------------------------------------- -/* istanbul ignore if */ -if ( - // Detect `process` so that it can run in browsers. - typeof process !== 'undefined' - && ( - process.env && process.env.IGNORE_TEST_WIN32 - || process.platform === 'win32' - ) -) { - const filter = IgnoreBase.prototype._filter - - /* eslint no-control-regex: "off" */ - const make_posix = str => /^\\\\\?\\/.test(str) - || /[^\x00-\x80]+/.test(str) - ? str - : str.replace(/\\/g, '/') - - IgnoreBase.prototype._filter = function filterWin32 (path, slices) { - path = make_posix(path) - return filter.call(this, path, slices) - } -} - -module.exports = options => new IgnoreBase(options) - - -/***/ }), -/* 751 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -module.exports = input => { - const isExtendedLengthPath = /^\\\\\?\\/.test(input); - const hasNonAscii = /[^\u0000-\u0080]+/.test(input); // eslint-disable-line no-control-regex - - if (isExtendedLengthPath || hasNonAscii) { - return input; - } - - return input.replace(/\\/g, '/'); -}; - - -/***/ }), -/* 752 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -/*! - * has-glob - * - * Copyright (c) 2015, Jon Schlinkert. - * Licensed under the MIT License. - */ - - - -var isGlob = __webpack_require__(753); - -module.exports = function hasGlob(val) { - if (val == null) return false; - if (typeof val === 'string') { - return isGlob(val); - } - if (Array.isArray(val)) { - var len = val.length; - while (len--) { - if (isGlob(val[len])) { - return true; - } - } - } - return false; -}; - - -/***/ }), -/* 753 */ -/***/ (function(module, exports, __webpack_require__) { - -/*! - * is-glob - * - * Copyright (c) 2014-2016, Jon Schlinkert. - * Licensed under the MIT License. - */ - -var isExtglob = __webpack_require__(365); - -module.exports = function isGlob(str) { - if (typeof str !== 'string' || str === '') { - return false; +module.exports = function isGlob(str) { + if (typeof str !== 'string' || str === '') { + return false; } if (isExtglob(str)) return true; @@ -83213,17 +59829,17 @@ module.exports = function isGlob(str) { /***/ }), -/* 754 */ +/* 577 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); const {constants: fsConstants} = __webpack_require__(134); -const pEvent = __webpack_require__(755); -const CpFileError = __webpack_require__(758); -const fs = __webpack_require__(760); -const ProgressEmitter = __webpack_require__(762); +const pEvent = __webpack_require__(578); +const CpFileError = __webpack_require__(581); +const fs = __webpack_require__(583); +const ProgressEmitter = __webpack_require__(585); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -83337,12 +59953,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 755 */ +/* 578 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(756); +const pTimeout = __webpack_require__(579); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -83635,13 +60251,13 @@ module.exports.TimeoutError = pTimeout.TimeoutError; /***/ }), -/* 756 */ +/* 579 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(757); +const pFinally = __webpack_require__(580); class TimeoutError extends Error { constructor(message) { @@ -83699,7 +60315,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 757 */ +/* 580 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83721,12 +60337,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 758 */ +/* 581 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(759); +const NestedError = __webpack_require__(582); class CpFileError extends NestedError { constructor(message, nested) { @@ -83740,7 +60356,7 @@ module.exports = CpFileError; /***/ }), -/* 759 */ +/* 582 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(112).inherits; @@ -83796,16 +60412,16 @@ module.exports = NestedError; /***/ }), -/* 760 */ +/* 583 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(112); const fs = __webpack_require__(133); -const makeDir = __webpack_require__(761); -const pEvent = __webpack_require__(755); -const CpFileError = __webpack_require__(758); +const makeDir = __webpack_require__(584); +const pEvent = __webpack_require__(578); +const CpFileError = __webpack_require__(581); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -83902,7 +60518,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 761 */ +/* 584 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84065,7 +60681,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 762 */ +/* 585 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84106,7 +60722,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 763 */ +/* 586 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84152,12 +60768,12 @@ exports.default = module.exports; /***/ }), -/* 764 */ +/* 587 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pMap = __webpack_require__(765); +const pMap = __webpack_require__(588); const pFilter = async (iterable, filterer, options) => { const values = await pMap( @@ -84174,7 +60790,7 @@ module.exports.default = pFilter; /***/ }), -/* 765 */ +/* 588 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84253,12 +60869,12 @@ module.exports.default = pMap; /***/ }), -/* 766 */ +/* 589 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(759); +const NestedError = __webpack_require__(582); class CpyError extends NestedError { constructor(message, nested) { diff --git a/packages/osd-pm/package.json b/packages/osd-pm/package.json index f6c25309e992..226264b828e3 100644 --- a/packages/osd-pm/package.json +++ b/packages/osd-pm/package.json @@ -23,7 +23,6 @@ "@types/dedent": "^0.7.0", "@types/getopts": "^2.0.1", "@types/glob": "^7.1.3", - "@types/globby": "^8.0.0", "@types/has-ansi": "^3.0.0", "@types/lodash": "^4.14.170", "@types/log-symbols": "^2.0.0", @@ -62,7 +61,7 @@ "tempy": "^0.3.0", "typescript": "4.0.2", "unlazy-loader": "^0.1.3", - "webpack": "npm:@amoo-miki/webpack@4.46.0-rc.2", + "webpack": "npm:@amoo-miki/webpack@4.46.0-xxhash.1", "webpack-cli": "^4.9.2", "write-pkg": "^4.0.0" }, diff --git a/packages/osd-pm/src/production/build_production_projects.ts b/packages/osd-pm/src/production/build_production_projects.ts index 534166716f49..3949968da396 100644 --- a/packages/osd-pm/src/production/build_production_projects.ts +++ b/packages/osd-pm/src/production/build_production_projects.ts @@ -130,7 +130,6 @@ async function copyToBuild(project: Project, opensearchDashboardsRoot: string, b await copy(['**/*', '!node_modules/**'], buildProjectPath, { cwd: project.getIntermediateBuildDirectory(), dot: true, - nodir: true, parents: true, }); diff --git a/packages/osd-std/package.json b/packages/osd-std/package.json index 3b1bea69bf8d..d0d859217c08 100644 --- a/packages/osd-std/package.json +++ b/packages/osd-std/package.json @@ -8,6 +8,7 @@ "private": true, "sideEffects": false, "dependencies": { + "json11": "^1.1.2", "lodash": "^4.17.21" }, "devDependencies": { diff --git a/packages/osd-std/src/json.ts b/packages/osd-std/src/json.ts index d8bb27e1eb6a..4dcd3eb03e65 100644 --- a/packages/osd-std/src/json.ts +++ b/packages/osd-std/src/json.ts @@ -3,260 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -/* In JavaScript, a `Number` is a 64-bit floating-point value which can store 16 digits. However, the - * serializer and deserializer will need to cater to numeric values generated by other languages which - * can have up to 19 digits. Native JSON parser and stringifier, incapable of handling the extra - * digits, corrupt the values, making them unusable. - * - * To work around this limitation, the deserializer converts long sequences of digits into strings and - * marks them before applying the parser. During the parsing, string values that begin with the mark - * are converted to `BigInt` values. - * Similarly, during stringification, the serializer converts `BigInt` values to marked strings and - * when done, it replaces them with plain numerals. - * - * `Number.MAX_SAFE_INTEGER`, 9,007,199,254,740,991, is the largest number that the native methods can - * parse and stringify, and any numeral greater than that would need to be translated using the - * workaround; all 17-digits or longer and only tail-end of the 16-digits need translation. It would - * be unfair to all the 16-digit numbers if the translation applied to `\d{16,}` only to cover the - * less than 10%. Hence, a RegExp is created to only match numerals too long to be a number. - * - * To make the explanation simpler, let's assume that MAX_SAFE_INTEGER is 8921 which has 4 digits. - * Starting from the right, we take each digit onwards, `[-9]`: - * 1) 7922 - 7929: 792[2-9]\d{0} - * 2) 7930 - 7999: 79[3-9]\d{1} - * 9) 9 + 1 = 10 which results in a rollover; no need to do anything. - * 8) 9000 - 9999: [9-9]\d{3} - * Finally we add anything 5 digits or longer: `\d{5,} - * - * Note: A better solution would use AST but considering its performance penalty, RegExp is the next - * best thing. - */ -const maxIntAsString = String(Number.MAX_SAFE_INTEGER); -const maxIntLength = maxIntAsString.length; -// Sub-patterns for each digit -const longNumeralMatcherTokens = [`\\d{${maxIntAsString.length + 1},}`]; -for (let i = 0; i < maxIntLength; i++) { - if (maxIntAsString[i] !== '9') { - longNumeralMatcherTokens.push( - maxIntAsString.substring(0, i) + - `[${parseInt(maxIntAsString[i], 10) + 1}-9]` + - `\\d{${maxIntLength - i - 1}}` - ); - } -} - -/* The matcher that looks for `": , ...}` and `[..., , ...]` - * - * The pattern starts by looking for `":` not immediately preceded by a `\`. That should be - * followed by any of the numeric sub-patterns. A comma, end of an array, end of an object, or - * the end of the input are the only acceptable elements after it. - * - * Note: This RegExp can result in false-positive hits on the likes of `{"key": "[ ]"}` and - * those are cleaned out during parsing. - */ -const longNumeralMatcher = new RegExp( - `((?:\\[|,|(? { - // coverage:ignore-line - if (!length || length < 0) return []; - const choices = []; - const arr = markerChars; - const arrLength = arr.length; - const temp = Array(length); - - (function fill(pos, start) { - if (pos === length) return choices.push(temp.join('')); - - for (let i = start; i < arrLength; i++) { - temp[pos] = arr[i]; - fill(pos + 1, i); - } - })(0, 0); - - return choices; -}; - -/* Experiments with different combinations of various lengths, until one is found to not be in - * the input string. - */ -const getMarker = (text: string): { marker: string; length: number } => { - let marker; - let length = 0; - do { - length++; - getMarkerChoices(length).some((markerChoice) => { - if (text.indexOf(markerChoice) === -1) { - marker = markerChoice; - return true; - } - }); - } while (!marker); - - return { - marker, - length, - }; -}; - -const parseStringWithLongNumerals = ( - text: string, - reviver?: ((this: any, key: string, value: any) => any) | null -): any => { - const { marker, length } = getMarker(text); - - let hadException; - let obj; - let markedJSON = text.replace(longNumeralMatcher, `$1"${marker}$2"$3`); - const markedValueMatcher = new RegExp(`^${marker}-?\\d+$`); - - /* Convert marked values to BigInt values. - * The `startsWith` is purely for performance, to avoid running `test` if not needed. - */ - const convertMarkedValues = (val: any) => - typeof val === 'string' && val.startsWith(marker) && markedValueMatcher.test(val) - ? BigInt(val.substring(length)) - : val; - - /* For better performance, instead of testing for existence of `reviver` on each value, two almost - * identical functions are used. - */ - const parseMarkedText = reviver - ? (markedText: string) => - JSON.parse(markedText, function (key, val) { - return reviver.call(this, key, convertMarkedValues(val)); - }) - : (markedText: string) => JSON.parse(markedText, (key, val) => convertMarkedValues(val)); - - /* RegExp cannot replace AST and the process of marking adds quotes. So, any false-positive hit - * will make the JSON string unparseable. - * - * To find those instances, we try to parse and watch for the location of any errors. If an error - * is caused by the marking, we remove that single marking and try again. - */ - try { - do { - try { - hadException = false; - obj = parseMarkedText(markedJSON); - } catch (e) { - hadException = true; - /* There are two types of exception objects that can be raised: - * 1) a textual message with the position that we need to parse - * i. Unexpected [token|string ...] at position ... - * ii. Expected ',' or ... after ... in JSON at position ... - * iii. expected ',' or ... after ... in object at line ... column ... - * 2) a proper object with lineNumber and columnNumber which we can use - * Note: this might refer to the part of the code that threw the exception but - * we will try it anyway; the regex is specific enough to not produce - * false-positives. - */ - let { lineNumber, columnNumber } = e; - - if (typeof e?.message === 'string') { - /* Check for 1-i and 1-ii - * Finding "..."෴1111"..." inside a string value, the extra quotes throw a syntax error - * and the position points to " that is assumed to be the begining of a value. - */ - let match = e.message.match(/^(?:Un)?expected .*at position (\d+)(\D|$)/i); - if (match) { - lineNumber = 1; - // Add 1 to reach the marker - columnNumber = parseInt(match[1], 10) + 1; - } else { - /* Check for 1-iii - * Finding "...,"෴1111"..." inside a string value, the extra quotes throw a syntax error - * and the column number points to the marker after the " that is assumed to be terminating the - * value. - * PS: There are different versions of this error across browsers and platforms. - */ - // ToDo: Add functional tests for this path - match = e.message.match(/expected .*line (\d+) column (\d+)(\D|$)/i); - if (match) { - lineNumber = parseInt(match[1], 10); - columnNumber = parseInt(match[2], 10); - } - } - } - - if (lineNumber < 1 || columnNumber < 2) { - /* The problem is not with this replacement. - * Note: This will never happen because the outer parse would have already thrown. - */ - // coverage:ignore-line - throw e; - } - - /* We need to skip e.lineNumber - 1 number of `\n` occurrences. - * Then, we need to go to e.columnNumber - 2 to look for `"\d+"`; we need to `-1` to - * account for the quote but an additional `-1` is needed because columnNumber starts from 1. - */ - const re = new RegExp( - `^((?:.*\\n){${lineNumber - 1}}[^\\n]{${columnNumber - 2}})"${marker}(-?\\d+)"` - ); - if (!re.test(markedJSON)) { - /* The exception is not caused by adding the marker. - * Note: This will never happen because the outer parse would have already thrown. - */ - // coverage:ignore-line - throw e; - } - - // We have found a bad replacement; let's remove it. - markedJSON = markedJSON.replace(re, '$1$2'); - } - } while (hadException); - } catch (ex) { - // If parsing of marked `text` fails, fallback to parsing the original `text` - obj = JSON.parse(text, reviver || undefined); - } - - return obj; -}; - -const stringifyObjectWithBigInts = ( - obj: any, - candidate: string, - replacer?: ((this: any, key: string, value: any) => any) | null, - space?: string | number -): string => { - const { marker } = getMarker(candidate); - - /* The matcher that looks for "" - * Because we have made sure that `marker` was never present in the original object, we can - * carelessly assume every "" is due to our marking. - */ - const markedBigIntMatcher = new RegExp(`"${marker}(-?\\d+)"`, 'g'); - - /* Convert BigInt values to a string and mark them. - * Can't be bothered with Number values outside the safe range because they are already corrupted. - * - * For better performance, instead of testing for existence of `replacer` on each value, two almost - * identical functions are used. - */ - const addMarkerToBigInts = replacer - ? function (this: any, key: string, val: any) { - // replacer is called before marking because marking changes the type - const newVal = replacer.call(this, key, val); - return typeof newVal === 'bigint' ? `${marker}${newVal.toString()}` : newVal; - } - : (key: string, val: any) => (typeof val === 'bigint' ? `${marker}${val.toString()}` : val); - - return ( - JSON.stringify(obj, addMarkerToBigInts, space) - // Replace marked substrings with just the numerals - .replace(markedBigIntMatcher, '$1') - ); -}; +import JSON11 from 'json11'; export const stringify = ( obj: any, @@ -298,7 +45,14 @@ export const stringify = ( text = JSON.stringify(obj, checkForBigInts, space); if (!numeralsAreNumbers) { - text = stringifyObjectWithBigInts(obj, text, replacer, space); + const temp = JSON11.stringify(obj, { + replacer, + space, + withBigInt: false, + quote: '"', + quoteNames: true, + }); + if (temp) text = temp; } return text; @@ -344,7 +98,8 @@ export const parse = ( obj = JSON.parse(text, checkForLargeNumerals); if (!numeralsAreNumbers) { - obj = parseStringWithLongNumerals(text, reviver); + const temp = JSON11.parse(text, reviver, { withLongNumerals: true }); + if (temp) obj = temp; } return obj; diff --git a/packages/osd-stylelint-config/config/global_selectors.json b/packages/osd-stylelint-config/config/global_selectors.json index 285f11f40a2b..0427337a298b 100644 --- a/packages/osd-stylelint-config/config/global_selectors.json +++ b/packages/osd-stylelint-config/config/global_selectors.json @@ -25,7 +25,10 @@ "packages/osd-ui-framework/src/components/button/button_group/_button_group.scss", "src/plugins/discover/public/application/components/data_grid/data_grid_table_cell_value.scss", "src/plugins/discover/public/application/view_components/canvas/discover_canvas.scss", - "src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss" + "src/plugins/discover/public/application/components/sidebar/discover_sidebar.scss", + "src/plugins/data/public/ui/query_string_input/_query_bar.scss", + "src/plugins/data/public/ui/query_editor/_query_editor.scss", + "src/plugins/data/public/ui/dataset_selector/_dataset_selector.scss" ] } } \ No newline at end of file diff --git a/packages/osd-ui-framework/Gruntfile.js b/packages/osd-ui-framework/Gruntfile.js index a0d8bd0abee8..e6c580f91bab 100644 --- a/packages/osd-ui-framework/Gruntfile.js +++ b/packages/osd-ui-framework/Gruntfile.js @@ -97,6 +97,8 @@ module.exports = function (grunt) { uiFrameworkCompile('src/kui_dark.scss', 'dist/kui_dark.css'), uiFrameworkCompile('src/kui_next_light.scss', 'dist/kui_next_light.css'), uiFrameworkCompile('src/kui_next_dark.scss', 'dist/kui_next_dark.css'), + uiFrameworkCompile('src/kui_v9_light.scss', 'dist/kui_v9_light.css'), + uiFrameworkCompile('src/kui_v9_dark.scss', 'dist/kui_v9_dark.css'), ]).then(done); }); }; diff --git a/packages/osd-ui-framework/dist/kui_v9_dark.css b/packages/osd-ui-framework/dist/kui_v9_dark.css new file mode 100644 index 000000000000..656f7facfc8b --- /dev/null +++ b/packages/osd-ui-framework/dist/kui_v9_dark.css @@ -0,0 +1,1635 @@ +/*! + * 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. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +main { + display: block; } + +.kuiBar { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 30px; } + +.kuiBarSection { + display: flex; + align-items: center; + flex: 1 1 auto; + margin-left: 25px; + margin-right: 25px; +} +.kuiBarSection:not(:first-child):not(:last-child):not(:only-child) { + justify-content: center; } +.kuiBarSection:first-child { + margin-left: 0; +} +.kuiBarSection:last-child { + margin-right: 0; + flex: 0 1 auto; justify-content: flex-end; } +.kuiBarSection:only-child { + margin-left: auto; } +.kuiBarSection > * + * { + margin-left: 10px; } + +.kuiButton { + display: inline-block; appearance: none; + cursor: pointer; + padding: 4px 12px 5px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + height: 30px; + text-decoration: none; + border: none; + border-radius: 4px; +} +.kuiButton:disabled { + cursor: not-allowed; + opacity: 0.5; +} +a.kuiButton.kuiButton-isDisabled { + cursor: not-allowed; + opacity: 0.5; +} + +.kuiButton:enabled:active { + transform: translateY(1px); +} +a.kuiButton:not(.kuiButton-isDisabled):active { transform: translateY(1px); +} + +.kuiButton__inner { + display: flex; align-items: center; } + +.kuiButton--small { + font-size: 12px; + padding: 2px 8px 3px; + height: 22px; +} + +.kuiButton--fullWidth { + width: 100%; + text-align: center; +} + +.kuiButton--iconText .kuiButton__icon:first-child:not(:only-child) { + margin-right: 8px; +} +.kuiButton--iconText .kuiButton__icon:last-child:not(:only-child) { + margin-left: 8px; +} +.kuiButton--iconText.kuiButton--small .kuiButton__icon:first-child:not(:only-child) { + margin-right: 4px; +} +.kuiButton--iconText.kuiButton--small .kuiButton__icon:last-child:not(:only-child) { + margin-left: 4px; +} + +.kuiButton--basic { + color: #DFE5EF; + background-color: #101B25; +} +.kuiButton--basic:not(a):enabled:focus { + color: #DFE5EF; +} +a.kuiButton--basic:not(.kuiButton-isDisabled):focus { color: #DFE5EF; +} + +.kuiButton--basic:enabled:hover { + background-color: #010101 !important; } +a.kuiButton--basic:not(.kuiButton-isDisabled):hover { background-color: #010101 !important; } + +.kuiButton--basic:enabled:active { + background-color: #010101 !important; } +a.kuiButton--basic:not(.kuiButton-isDisabled):active { background-color: #010101 !important; } + +.kuiButton--primary { + color: #FCFEFF; + background-color: #FFC0CB; +} +.kuiButton--primary:not(a):enabled:focus { + color: #FCFEFF; +} +a.kuiButton--primary:not(.kuiButton-isDisabled):focus { color: #FCFEFF; +} + +.kuiButton--primary:enabled:hover { + color: #FCFEFF !important; background-color: #ff8da1; +} +a.kuiButton--primary:not(.kuiButton-isDisabled):hover { color: #FCFEFF !important; background-color: #ff8da1; +} + +.kuiButton--primary:enabled:active { + color: #FCFEFF !important; background-color: #ff8da1; +} +a.kuiButton--primary:not(.kuiButton-isDisabled):active { color: #FCFEFF !important; background-color: #ff8da1; +} + +.kuiButton--success { + color: #FCFEFF; + background-color: #FFC0CB; +} +.kuiButton--success:not(a):enabled:focus { + color: #FCFEFF; +} +a.kuiButton--success:not(.kuiButton-isDisabled):focus { color: #FCFEFF; +} + +.kuiButton--success:enabled:hover { + color: #FCFEFF !important; background-color: #ff8da1; +} +a.kuiButton--success:not(.kuiButton-isDisabled):hover { color: #FCFEFF !important; background-color: #ff8da1; +} + +.kuiButton--success:enabled:active { + color: #FCFEFF !important; background-color: #ff8da1; +} +a.kuiButton--success:not(.kuiButton-isDisabled):active { color: #FCFEFF !important; background-color: #ff8da1; +} + +.kuiButton--danger { + color: #FF6666; + border: solid 1px #FF6666; +} +.kuiButton--danger:not(a):enabled:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #0A121A, 0 0 0 2px #FF6666; color: #FF6666; +} +a.kuiButton--danger:not(.kuiButton-isDisabled):focus { z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #0A121A, 0 0 0 2px #FF6666; color: #FF6666; +} + +.kuiButton--danger:enabled:hover { + color: #ff3333 !important; + border: solid 1px #ff3333; + background-color: rgba(255, 102, 102, 0.1); +} +a.kuiButton--danger:not(.kuiButton-isDisabled):hover { color: #ff3333 !important; + border: solid 1px #ff3333; + background-color: rgba(255, 102, 102, 0.1); +} + +.kuiButton--danger:enabled:active { + color: #ff3333 !important; + border: solid 1px #ff3333; + background-color: rgba(255, 102, 102, 0.1); +} +a.kuiButton--danger:not(.kuiButton-isDisabled):active { color: #ff3333 !important; + border: solid 1px #ff3333; + background-color: rgba(255, 102, 102, 0.1); +} + +.kuiButton--warning { + color: #FCFEFF; + background-color: #FFCE7A; +} +.kuiButton--warning:not(a):enabled:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #0A121A, 0 0 0 2px #FFCE7A; color: #FCFEFF; +} +a.kuiButton--warning:not(.kuiButton-isDisabled):focus { z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #0A121A, 0 0 0 2px #FFCE7A; color: #FCFEFF; +} + +.kuiButton--warning:enabled:hover { + color: #FCFEFF !important; background-color: #ffbb47; +} +a.kuiButton--warning:not(.kuiButton-isDisabled):hover { color: #FCFEFF !important; background-color: #ffbb47; +} + +.kuiButton--warning:enabled:active { + color: #FCFEFF !important; background-color: #ffbb47; +} +a.kuiButton--warning:not(.kuiButton-isDisabled):active { color: #FCFEFF !important; background-color: #ffbb47; +} + +.kuiButton--warning:disabled { + background-color: #ffe1ad; +} +a.kuiButton--warning.kuiButton-isDisabled { + background-color: #ffe1ad; +} + +.kuiButton--hollow { + color: #FFC0CB !important; background-color: transparent; +} +.kuiButton--hollow:enabled:hover { + color: #ff8da1 !important; text-decoration: underline; +} +a.kuiButton--hollow:not(.kuiButton-isDisabled):hover { color: #ff8da1 !important; text-decoration: underline; +} + +.kuiButton--hollow:enabled:active { + color: #ff8da1 !important; text-decoration: underline; +} +a.kuiButton--hollow:not(.kuiButton-isDisabled):active { color: #ff8da1 !important; text-decoration: underline; +} + +.kuiButton--secondary { + color: #FFC0CB !important; border: solid 1px #FFC0CB; +} +.kuiButton--secondary:enabled:hover { + color: #ff8da1 !important; border: solid 1px #ff8da1; + background-color: rgba(255, 192, 203, 0.1); + text-decoration: underline; +} +a.kuiButton--secondary:not(.kuiButton-isDisabled):hover { color: #ff8da1 !important; border: solid 1px #ff8da1; + background-color: rgba(255, 192, 203, 0.1); + text-decoration: underline; +} + +.kuiButton--secondary:enabled:active { + color: #ff8da1 !important; border: solid 1px #ff8da1; + background-color: rgba(255, 192, 203, 0.1); + text-decoration: underline; +} +a.kuiButton--secondary:not(.kuiButton-isDisabled):active { color: #ff8da1 !important; border: solid 1px #ff8da1; + background-color: rgba(255, 192, 203, 0.1); + text-decoration: underline; +} + +.kuiButtonGroup { + display: flex; + align-items: center; +} +.kuiButtonGroup .kuiButton + .kuiButton { + margin-left: 4px; +} + +.kuiButtonGroup--united > .kuiButton:not(:first-child):not(:last-child) { + border-radius: 0; +} +.kuiButtonGroup--united > .kuiButton:first-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.kuiButtonGroup--united > .kuiButton:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.kuiButtonGroup--united > .kuiButton:only-child { + border-radius: 4px 4px 4px 4px; +} +.kuiButtonGroup--united .kuiButton + .kuiButton { + margin-left: 0; +} + +.kuiButtonGroup--fullWidth { + display: flex; +} +.kuiButtonGroup--fullWidth > .kuiButton { + flex: 1 1 auto; + text-align: center; +} + +.eui-textCenter > .kuiButtonGroup, +.text-center > .kuiButtonGroup { + display: inline-block; +} + +.kuiCollapseButton { + appearance: none; + background-color: transparent; + padding: 4px; + border: none; + line-height: 1; + font-size: 16px; + color: #DFE5EF !important; cursor: pointer; + opacity: 0.35; +} +.kuiCollapseButton:hover { + opacity: 1; +} + +.kuiAssistedInput { + display: inline-block; position: relative; +} + +.kuiAssistedInput__assistance { + position: absolute; + right: 12px; + top: 50%; transform: translateY(-50%); } + +.kuiCheckBox { + appearance: none; background-color: #0e1721; + border: 1px solid rgba(252, 254, 255, 0.1); + border-radius: 4px; + width: 16px; + height: 16px; + font: var(--font-text) !important; line-height: 1.5 !important; margin: 0 !important; font-family: var(--font-text) !important; font-size: 10px !important; transition: background-color 0.1s linear; +} +.kuiCheckBox::before { + position: relative; + left: 0.25em; + font-family: FontAwesome, sans-serif; + content: "\f00c"; + font-size: 1em; + opacity: 0; + color: #FCFEFF; + transition: opacity 0.1s linear; +} +.kuiCheckBox:checked { + border-color: #FFC0CB; + background-color: #FFC0CB; +} +.kuiCheckBox:checked::before { + opacity: 1; +} +.kuiCheckBox:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #0A121A, 0 0 0 2px #FFC0CB; } +.kuiCheckBox:disabled { + background-color: #0d161e !important; + border-color: #0d161e !important; + cursor: not-allowed !important; +} + +.kuiCheckBoxLabel { + display: flex; + align-items: center; + font-weight: normal !important; + line-height: 1.5; +} + +.kuiCheckBoxLabel__text { + font-size: 16px; + margin-left: 8px; +} + +.kuiLabel { + font-size: 16px; + line-height: 1.5; + font-weight: bold; + margin-bottom: 0; } + +.kuiSearchInput { + width: 180px; + display: inline-block; + position: relative; + font-size: 16px; + line-height: 1.5; +} +.kuiSearchInput.kuiSearchInput-isInvalid .kuiSearchInput__input { + border-color: #FF6666; +} + +.kuiSearchInput__icon { + position: absolute; + top: 0.5em; + left: 0.7em; + font-size: 1em; + color: #5B6875; +} + +.kuiSearchInput__input { + appearance: none; + font-family: var(--font-text); + padding: 4px 12px 4px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #DFE5EF; + background-color: #0e1721; + border: 1px solid rgba(252, 254, 255, 0.1); + border-radius: 4px; + transition: border-color 0.1s linear; + min-height: 32px; padding-left: 28px; width: 100%; } +.kuiSearchInput__input:invalid { + border-color: #FF6666; +} +.kuiSearchInput__input:focus { + outline: none; + border-color: #FFC0CB; +} +.kuiSearchInput__input:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.kuiSearchInput--small { + width: 60px; +} + +.kuiSearchInput--large { + width: 400px; +} + +.kuiSelect { + appearance: none; + font-family: var(--font-text); + padding: 4px 12px 4px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #DFE5EF; + background-color: #0e1721; + border: 1px solid rgba(252, 254, 255, 0.1); + border-radius: 4px; + transition: border-color 0.1s linear; + min-height: 32px; padding-right: 30px; background-image: url('data:image/svg+xml;utf8,'); background-size: 14px; + background-repeat: no-repeat; + background-position: calc(100% - 8px); } +.kuiSelect:invalid { + border-color: #FF6666; +} +.kuiSelect:focus { + outline: none; + border-color: #FFC0CB; +} +.kuiSelect:disabled { + opacity: 0.4; + cursor: not-allowed; +} +.kuiSelect:-moz-focusring { + text-shadow: 0 0 0; } +.kuiSelect.kuiSelect-isInvalid { + border-color: #FF6666; +} +.kuiSelect:focus { + box-shadow: none; + outline: none; + border-color: #FFC0CB; +} + +.kuiSelect--small { + width: 60px; +} + +.kuiSelect--medium { + width: 180px; +} + +.kuiSelect--large { + width: 400px; +} + +.kuiStaticInput { + width: 180px; + appearance: none; + font-family: var(--font-text); + padding: 4px 12px 4px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #DFE5EF; + border: 1px solid transparent; background-color: transparent; +} + +.kuiTextArea { + width: 180px; + appearance: none; + font-family: var(--font-text); + padding: 4px 12px 4px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #DFE5EF; + background-color: #0e1721; + border: 1px solid rgba(252, 254, 255, 0.1); + border-radius: 4px; + transition: border-color 0.1s linear; + min-height: 32px; } +.kuiTextArea:invalid { + border-color: #FF6666; +} +.kuiTextArea:focus { + outline: none; + border-color: #FFC0CB; +} +.kuiTextArea:disabled { + opacity: 0.4; + cursor: not-allowed; +} +.kuiTextArea:focus { + box-shadow: none; + outline: none; + border-color: #FFC0CB; +} +.kuiTextArea.kuiTextArea-isInvalid { + border-color: #FF6666; +} + +.kuiTextArea--nonResizable { + resize: none; +} + +.kuiTextArea--small { + width: 60px; +} + +.kuiTextArea--large { + width: 400px; +} + +.kuiTextInput { + width: 180px; + appearance: none; + font-family: var(--font-text); + padding: 4px 12px 4px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #DFE5EF; + background-color: #0e1721; + border: 1px solid rgba(252, 254, 255, 0.1); + border-radius: 4px; + transition: border-color 0.1s linear; + min-height: 32px; } +.kuiTextInput:invalid { + border-color: #FF6666; +} +.kuiTextInput:focus { + outline: none; + border-color: #FFC0CB; +} +.kuiTextInput:disabled { + opacity: 0.4; + cursor: not-allowed; +} +.kuiTextInput.kuiTextInput-isInvalid { + border-color: #FF6666; +} + +.kuiTextInput--small { + width: 60px; +} + +.kuiTextInput--large { + width: 400px; +} + +.kuiFieldGroup { + display: flex; + align-items: center; } + +.kuiFieldGroup--alignTop { + align-items: flex-start; +} + +.kuiFieldGroupSection { + line-height: 1.5; +} +.kuiFieldGroupSection + .kuiFieldGroupSection { + margin-left: 10px; +} + +.kuiFieldGroupSection--wide { + flex: 1 1 auto; +} +.kuiFieldGroupSection--wide > * { + width: 100%; +} + +.kuiIcon { + display: inline-block; font: normal normal normal 14px/1 FontAwesome, sans-serif; font-size: inherit; text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } + +.kuiIcon--info { + color: #FFC0CB; +} + +.kuiIcon--success { + color: #FFC0CB; +} + +.kuiIcon--warning { + color: #FFCE7A; +} + +.kuiIcon--error { + color: #FF6666; +} + +.kuiIcon--inactive { + color: #293847; +} + +.kuiIcon--basic { + color: #8D98A3; +} + +.kuiInfoPanel { + padding: 14px 20px 18px; + line-height: 1.5; + border: 2px solid; +} + +.kuiInfoPanel--info { + border-color: rgba(255, 192, 203, 0.25); } + +.kuiInfoPanel--success { + border-color: rgba(255, 192, 203, 0.25); } + +.kuiInfoPanel--warning { + border-color: rgba(255, 206, 122, 0.25); } + +.kuiInfoPanel--error { + border-color: rgba(255, 102, 102, 0.25); } + +.kuiInfoPanelHeader { + display: flex; + align-items: baseline; } + +.kuiInfoPanelHeader__icon { + margin-right: 10px; + font-size: 16px; + line-height: 1.5; +} + +.kuiInfoPanelHeader__title { + font-size: 16px; + line-height: 1.5; + font-weight: 700; +} + +.kuiInfoPanelBody { + margin-top: 8px; +} +.kuiInfoPanelBody > * + * { + margin-top: 8px; +} + +.kuiInfoPanelBody__message { + font-size: 16px; + line-height: 1.5; +} + +.kuiLink { + color: #FFC0CB; + text-decoration: none; + cursor: pointer; appearance: none; background-color: transparent; border: none; font-size: inherit; line-height: inherit; } +.kuiLink:visited, .kuiLink:active { + color: #FFC0CB; +} +.kuiLink:hover { + color: #ff8da1; + text-decoration: underline; +} + +.kuiLocalBreadcrumbs { + display: flex; + align-items: center; + padding: 12px 8px; border-bottom: 1px solid #293847; + flex-grow: 1; + flex-basis: 100%; + background-color: #0A121A; +} + +.kuiLocalBreadcrumb { + font-size: 14px; + margin: 0; + font-weight: normal; +} +.kuiLocalBreadcrumb + .kuiLocalBreadcrumb { + margin-left: 6px; +} +.kuiLocalBreadcrumb + .kuiLocalBreadcrumb::before { + content: "/"; + user-select: none; + margin-right: 4px; + color: #293847; +} + +.kuiLocalBreadcrumb__link { + color: #FFC0CB; + text-decoration: none; + cursor: pointer; appearance: none; background-color: transparent; border: none; font-size: inherit; line-height: inherit; color: #FFC0CB; font-size: 16px; +} +.kuiLocalBreadcrumb__link:visited, .kuiLocalBreadcrumb__link:active { + color: #FFC0CB; +} +.kuiLocalBreadcrumb__link:hover { + color: #ff8da1; + text-decoration: underline; +} + +.kuiLocalBreadcrumb__emphasis { + font-weight: 700; +} + +.kuiDatePicker { + background-color: transparent; + border-collapse: collapse; + border-spacing: 0; + line-height: 1.5; +} + +.kuiDatePickerNavigationCell { + padding: 0; +} + +.kuiDatePickerNavigation { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 4px; +} + +.kuiDatePickerNavigationButton { + appearance: none; + background-color: transparent; + border: none; + font-size: 14px; + color: #DFE5EF; + padding: 3px 6px; + border-radius: 4px; +} +.kuiDatePickerNavigationButton:hover, .kuiDatePickerNavigationButton:active { + cursor: pointer; + color: #101B25; + background-color: #FFC0CB; +} +.kuiDatePickerNavigationButton:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px transparent, 0 0 0 2px #FFC0CB; color: #DFE5EF; } + +.kuiDatePickerHeaderCell { + padding: 9px 0; + color: #DFE5EF; + font-size: 14px; + font-weight: bold; + text-align: center; + line-height: 1.2; +} + +.kuiDatePickerRowCell { + padding: 0; + text-align: center; +} + +.kuiDatePickerRowCellContent { + appearance: none; + background-color: transparent; + width: 100%; + border: 1px solid transparent; + color: #DFE5EF; + font-size: 14px; + padding: 8px; + border-radius: 4px; + line-height: 1.2; +} +.kuiDatePickerRowCellContent:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px transparent, 0 0 0 2px #FFC0CB; color: #DFE5EF; } +.kuiDatePickerRowCellContent:disabled { + pointer-events: none; + opacity: 0.5; +} +.kuiDatePickerRowCellContent.kuiDatePickerRowCellContent-isOtherMonth { + visibility: hidden; + pointer-events: none; +} +.kuiDatePickerRowCellContent.kuiDatePickerRowCellContent-isCurrent { + color: #FFC0CB; +} +.kuiDatePickerRowCellContent.kuiDatePickerRowCellContent-isSelected { + background-color: #8D98A3; + color: #DFE5EF; +} +.kuiDatePickerRowCellContent:hover, .kuiDatePickerRowCellContent:active { + cursor: pointer; + color: #101B25; + background-color: #FFC0CB; +} + +.kuiLocalDropdown { + position: relative; + padding: 10px 8px 14px; + background-color: transparent; + margin-bottom: 10px; + line-height: 20px; +} + +.kuiLocalDropdownCloseButton { + appearance: none; + background-color: transparent; + padding: 4px; + border: none; + line-height: 1; + font-size: 16px; + color: #DFE5EF !important; cursor: pointer; + opacity: 0.35; + position: absolute; + top: 1px; + right: 5px; +} +.kuiLocalDropdownCloseButton:hover { + opacity: 1; +} + +.kuiLocalDropdownPanels { + display: flex; +} + +.kuiLocalDropdownPanel { + flex: 1 1 0%; +} + +.kuiLocalDropdownPanel--left { + margin-right: 30px; +} + +.kuiLocalDropdownPanel--right { + margin-left: 30px; +} + +.kuiLocalDropdownTitle { + margin-top: 0; margin-bottom: 12px; + font-size: 18px; + color: #DFE5EF; +} + +.kuiLocalDropdownSection { + margin-bottom: 16px; +} +.kuiLocalDropdownSection:last-child { + margin-bottom: 0; +} + +.kuiLocalDropdownHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 6px; +} + +.kuiLocalDropdownHeader__label { + font-size: 14px; + font-weight: 700; + margin-bottom: 0; color: #DFE5EF; +} + +.kuiLocalDropdownHeader__actions { + display: flex; +} + +.kuiLocalDropdownHeader__action { + color: #FFC0CB; + font-size: 12px; + text-decoration: none; + cursor: pointer; +} +.kuiLocalDropdownHeader__action + .kuiLocalDropdownHeader__action { + margin-left: 10px; +} +.kuiLocalDropdownHeader__action:hover, .kuiLocalDropdownHeader__action:active { + color: #ff8da1; +} + +.kuiLocalDropdownInput { + display: block; + width: 100%; + margin-bottom: 12px; + padding: 5px 15px; + font-size: 14px; + color: #DFE5EF; + background-color: #0e1721; + border: 1px solid rgba(252, 254, 255, 0.1); + border-radius: 4px; +} +.kuiLocalDropdownInput:focus { + border-color: #FFC0CB; +} + +.kuiLocalDropdownFormNote { + font-size: 14px; + color: #8D98A3; +} + +.kuiLocalDropdownWarning { + margin-bottom: 16px; + padding: 6px 10px; + font-size: 14px; + color: #DFE5EF; + background-color: #0A121A; + border-left: solid 2px #FF6666; +} + +.kuiLocalDropdownHelpText { + margin-bottom: 16px; + font-size: 14px; + color: #8D98A3; +} + +.kuiLocalMenu { + display: flex; + align-items: stretch; + padding-top: 8px; + padding-bottom: 8px; +} + +.kuiLocalMenuItem { + display: flex; + align-items: center; + padding: 2px 8px; + font-size: 16px; + background-color: transparent; + color: #DFE5EF; + border: 0; + cursor: pointer; + border-bottom: solid 2px transparent; +} +.kuiLocalMenuItem:hover, .kuiLocalMenuItem:focus { + background-color: rgba(255, 192, 203, 0.15); + text-decoration: underline; +} +.kuiLocalMenuItem.kuiLocalMenuItem-isSelected { + color: #FFC0CB; + background-color: transparent; + border-color: #FFC0CB; + z-index: 2; +} +.kuiLocalMenuItem.kuiLocalMenuItem-isSelected:hover, .kuiLocalMenuItem.kuiLocalMenuItem-isSelected:focus { + text-decoration: none; +} +.kuiLocalMenuItem.kuiLocalMenuItem-isDisabled { + opacity: 0.5; + cursor: not-allowed; +} +.kuiLocalMenuItem.kuiLocalMenuItem-isDisabled:hover { + background-color: transparent; + text-decoration: none; +} + +.kuiLocalMenuItem__icon { + margin-right: 5px; + margin-bottom: -1px; +} + +.kuiLocalNav { + display: flex; + flex-direction: column; + justify-content: space-between; + min-height: 69px; color: #DFE5EF; + background-color: #0A121A; + line-height: 1.5; + border-bottom: solid 1px #293847; +} + +.kuiLocalNavRow { + display: flex; + align-items: stretch; + justify-content: space-between; +} + +.kuiLocalNavRow__section { + display: flex; + align-items: stretch; +} + +.kuiLocalNavRow--secondary { + padding: 0 8px; align-items: flex-start; } + +.kuiLocalSearch { + display: flex; + width: 100%; + margin-bottom: 8px; +} + +.kuiLocalSearchInput { + appearance: none; + font-family: var(--font-text); + padding: 4px 12px 4px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #DFE5EF; + background-color: #0e1721; + border: 1px solid rgba(252, 254, 255, 0.1); + border-radius: 4px; + transition: border-color 0.1s linear; + min-height: 32px; flex: 1 1 100%; + border-color: #FCFEFF; + border-color: #293847; + border-radius: 4px 0 0 4px; +} +.kuiLocalSearchInput:invalid { + border-color: #FF6666; +} +.kuiLocalSearchInput:focus { + outline: none; + border-color: #FFC0CB; +} +.kuiLocalSearchInput:disabled { + opacity: 0.4; + cursor: not-allowed; +} +.kuiLocalSearchInput:focus { + box-shadow: none; +} +.kuiLocalSearchInput.kuiLocalSearchInput-isInvalid { + border-color: #FF6666; +} + +.kuiLocalSearchInput--secondary { + height: 32px; + flex: 0 0 auto; + border-radius: 0; + border-left-width: 0; +} + +.kuiLocalSearchInput, +.kuiLocalSearchAssistedInput__input { + padding-right: 6em; } + +.kuiLocalSearchAssistedInput__assistance { + position: absolute; + right: 6px; + top: 50%; z-index: 2; + transform: translateY(-50%); } + +.kuiLocalSearchSelect { + appearance: none; + font-family: var(--font-text); + padding: 4px 12px 4px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #DFE5EF; + background-color: #0e1721; + border: 1px solid rgba(252, 254, 255, 0.1); + border-radius: 4px; + transition: border-color 0.1s linear; + min-height: 32px; padding-right: 30px; background-image: url('data:image/svg+xml;utf8,'); background-size: 14px; + background-repeat: no-repeat; + background-position: calc(100% - 8px); border-left-width: 0; + border-radius: 0; +} +.kuiLocalSearchSelect:invalid { + border-color: #FF6666; +} +.kuiLocalSearchSelect:focus { + outline: none; + border-color: #FFC0CB; +} +.kuiLocalSearchSelect:disabled { + opacity: 0.4; + cursor: not-allowed; +} +.kuiLocalSearchSelect:-moz-focusring { + text-shadow: 0 0 0; } + +.kuiLocalSearchButton { + width: 43px; + height: 32px; + font-size: 16px; + line-height: 0; color: #FCFEFF; + background-color: #FFC0CB; + border: 0; + border-radius: 0 4px 4px 0; +} +.kuiLocalSearchButton:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #293847, 0 0 0 2px #FFC0CB; } + +.kuiLocalTabs { + display: flex; + align-items: flex-end; + height: 100%; +} + +.kuiLocalTab { + padding: 5px 0 6px; + font-size: 18px; + color: #DFE3E8; + border-bottom: 2px solid transparent; + text-decoration: none; + cursor: pointer; + margin-top: 0 !important; margin-bottom: 0 !important; } +.kuiLocalTab:hover:not(.kuiLocalTab-isDisabled), .kuiLocalTab:active:not(.kuiLocalTab-isDisabled) { + color: #FFC0CB; +} +.kuiLocalTab.kuiLocalTab-isSelected { + color: #FFC0CB; + border-bottom-color: #FFC0CB; + cursor: default; +} +.kuiLocalTab.kuiLocalTab-isDisabled { + opacity: 0.5; + cursor: default; } +.kuiLocalTab + .kuiLocalTab { + margin-left: 15px; +} + +.kuiLocalTitle { + display: flex; + align-items: center; + padding: 12px 8px; + font-size: 16px; + font-weight: bold; + border-bottom: 1px solid #293847; + flex-grow: 1; + flex-basis: 100%; + background-color: #0A121A; +} +.kuiLocalTitle:empty { + padding: 0; + display: none; +} + +.kuiPager { + display: flex; + align-items: center; +} +.kuiPager > * + * { + margin-left: 10px; } + +.kuiPagerText { + font-size: 16px; + line-height: 1.5; + color: #8D98A3; + white-space: nowrap; } + +.kuiPanel { + box-shadow: 0 2px 2px -1px rgba(0, 0, 0, 0.3), 0 1px 5px -2px rgba(0, 0, 0, 0.3); + background-color: #0A121A; + border: 1px solid #293847; + border-radius: 4px; +} + +.kuiPanel--prompt { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + justify-content: center; + min-height: 300px; +} +.kuiPanel--prompt .kuiPanelBody { + padding: 30px; + max-width: 500px; +} + +.kuiPanel--noBorder { + border: none; +} + +.kuiPanel--withToolBar { + border-top: none; + border-radius: 0; +} + +.kuiPanel--centered { + display: flex; + justify-content: center; + align-items: center; +} + +.kuiPanelHeader { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 30px; padding: 10px; + height: 50px; + border-bottom: 1px solid #293847; +} +.kuiPanelHeader .kuiButton:not(a):enabled:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #293847, 0 0 0 2px #FFC0CB; } +a.kuiPanelHeader .kuiButton:not(.kuiButton-isDisabled):focus { z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #293847, 0 0 0 2px #FFC0CB; } + +.kuiPanelHeader .kuiButton--danger:not(a):enabled:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #293847, 0 0 0 2px #FF6666; } +a.kuiPanelHeader .kuiButton--danger:not(.kuiButton-isDisabled):focus { z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #293847, 0 0 0 2px #FF6666; } + +.kuiPanelHeader .kuiSelect { + border-color: #0e1721; +} +.kuiPanelHeader .kuiSelect:not(a):enabled:focus { + outline: none; + border-color: #FFC0CB; +} +a.kuiPanelHeader .kuiSelect:not(.kuiButton-isDisabled):focus { outline: none; + border-color: #FFC0CB; +} + +.kuiPanelHeader__title { + font-size: 20px; + line-height: 1.5; + margin: 0; } + +.kuiPanelHeaderSection { + display: flex; + align-items: center; + flex: 1 1 auto; + margin-left: 25px; + margin-right: 25px; +} +.kuiPanelHeaderSection:not(:first-child):not(:last-child):not(:only-child) { + justify-content: center; } +.kuiPanelHeaderSection:first-child { + margin-left: 0; +} +.kuiPanelHeaderSection:last-child { + margin-right: 0; + flex: 0 1 auto; justify-content: flex-end; } +.kuiPanelHeaderSection:only-child { + margin-left: auto; } +.kuiPanelHeaderSection > * + * { + margin-left: 10px; } +.kuiPanelHeaderSection:only-child { + margin-left: 0; margin-right: auto; } + +.kuiPanelBody { + padding: 10px; +} + +.kuiEmptyTablePrompt { + display: flex; + flex-direction: column; + align-items: center; + padding: 30px; +} + +.kuiEmptyTablePrompt__message { + font-size: 20px; + color: #8D98A3; + line-height: 1.5; +} + +.kuiEmptyTablePrompt__actions { + margin-top: 10px; +} + +.kuiStatusText { + display: inline-flex; + align-items: baseline; +} + +.kuiStatusText--info { + color: #FFC0CB; +} + +.kuiStatusText--success { + color: #FFC0CB; +} + +.kuiStatusText--warning { + color: #FFCE7A; +} + +.kuiStatusText--error { + color: #FF6666; +} + +.kuiStatusText__icon { + margin-right: 6px; + width: 1.15em; max-height: 1.15em; } + +.kuiControlledTable { + background: #0A121A; +} +.kuiControlledTable .kuiTable { + border-top: none; } +.kuiControlledTable .kuiToolBarFooter { + border-top: none; } +.kuiControlledTable .kuiMenu--contained { + border-top: none; } + +.kuiTable { + width: 100%; + border: 1px solid #293847; + border-collapse: collapse; + background-color: #0A121A; +} + +.kuiTable--fluid { + width: auto; } +.kuiTable--fluid .kuiTableHeaderCell, +.kuiTable--fluid .kuiTableRowCell { + max-width: none; } + +.kuiTableHeaderCell { + font-size: 16px; + font-weight: 400; + text-align: left; + max-width: 20px; line-height: 1.5; + color: #8D98A3; +} + +.kuiTableHeaderCell__liner { + display: inline-block; + padding: 7px 8px 8px; +} + +.kuiTableHeaderCellButton { + user-select: none; cursor: pointer; + width: 100%; + appearance: none; background-color: transparent; border: 0; padding: 0; color: inherit; line-height: inherit; font-size: inherit; text-align: inherit; } +.kuiTableHeaderCellButton:hover .kuiTableSortIcon { + display: block; + opacity: 1; +} +.kuiTableHeaderCellButton .kuiTableHeaderCell__liner { + display: flex; + align-items: center; +} + +.kuiTableHeaderCell--alignRight { + text-align: right; +} + +.kuiTableSortIcon { + display: none; + pointer-events: none; + margin-left: 4px; +} +.kuiTableHeaderCellButton-isSorted .kuiTableSortIcon { + display: block; + opacity: 0.4; +} + +.kuiTableRow:hover .kuiTableRowHoverReveal { + display: inline-block; +} + +.kuiTableRowHoverReveal { + display: none; +} + +.kuiTableRowCell { + font-size: 16px; + font-weight: 400; + text-align: left; + max-width: 20px; color: #DFE5EF; + border-top: 1px solid #293847; + vertical-align: middle; +} + +.kuiTableRowCell__liner { + padding: 7px 8px 8px; line-height: 1.5; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.kuiTableRowCell__liner > * { + vertical-align: middle; } + +.kuiTableRowCell--wrap .kuiTableRowCell__liner { + white-space: normal; +} + +.kuiTableRowCell--overflowingContent .kuiTableRowCell__liner { + overflow: visible; + white-space: normal; +} + +.kuiTableRowCell--expanded { + border-top-color: #0A121A; } + +.kuiTableRowCell--alignRight { + text-align: right; +} +.kuiTableRowCell--alignRight .kuiTableRowCell__liner { + text-align: right; +} + +.kuiTableHeaderCell--checkBox, +.kuiTableRowCell--checkBox { + width: 28px; line-height: 1; } +.kuiTableHeaderCell--checkBox .kuiTableRowCell__liner, +.kuiTableRowCell--checkBox .kuiTableRowCell__liner { + overflow: visible; } +.kuiTableHeaderCell--checkBox .kuiTableHeaderCell__liner, +.kuiTableHeaderCell--checkBox .kuiTableRowCell__liner, +.kuiTableRowCell--checkBox .kuiTableHeaderCell__liner, +.kuiTableRowCell--checkBox .kuiTableRowCell__liner { + padding-right: 0; +} + +.kuiTableInfo { + padding: 30px; + font-size: 20px; + color: #8D98A3; + line-height: 1.5; +} + +.kuiTabs { + display: flex; + border-bottom: 1px solid #293847; +} + +.kuiTab { + appearance: none; cursor: pointer; + padding: 10px 30px; + font-size: 16px; + color: #8D98A3; + background-color: transparent; border: 1px solid #293847; + border-radius: 0; margin-bottom: -1px; } +.kuiTab + .kuiTab { + border-left: none; +} +.kuiTab + .kuiTab:focus:not(.kuiTab-isSelected):not(:active) { + margin-left: -1px; } +.kuiTab:active { + outline: none !important; box-shadow: none; } +.kuiTab:focus { + outline: none; } +.kuiTab:focus:not(.kuiTab-isSelected):not(:active) { + z-index: 1; + color: #FFC0CB; + border: 1px solid #FFC0CB !important; +} +.kuiTab:hover:not(.kuiTab-isSelected) { + color: #ff8da1; + background-color: #101B25; +} +.kuiTab.kuiTab-isSelected { + cursor: default; + color: #DFE5EF; + background-color: transparent; + border-bottom-color: transparent; +} + +.kuiToolBar { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 30px; padding: 10px; + height: 50px; + background-color: transparent; + border: solid 1px #293847; +} +.kuiToolBar .kuiButton:not(a):enabled:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #293847, 0 0 0 2px #FFC0CB; } +a.kuiToolBar .kuiButton:not(.kuiButton-isDisabled):focus { z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #293847, 0 0 0 2px #FFC0CB; } + +.kuiToolBar .kuiButton--danger:not(a):enabled:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #293847, 0 0 0 2px #FF6666; } +a.kuiToolBar .kuiButton--danger:not(.kuiButton-isDisabled):focus { z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #293847, 0 0 0 2px #FF6666; } + +.kuiToolBar .kuiSelect { + border-color: #0e1721; +} +.kuiToolBar .kuiSelect:not(a):enabled:focus { + outline: none; + border-color: #FFC0CB; +} +a.kuiToolBar .kuiSelect:not(.kuiButton-isDisabled):focus { outline: none; + border-color: #FFC0CB; +} + +.kuiToolBarSection { + display: flex; + align-items: center; + flex: 1 1 auto; + margin-left: 25px; + margin-right: 25px; +} +.kuiToolBarSection:not(:first-child):not(:last-child):not(:only-child) { + justify-content: center; } +.kuiToolBarSection:first-child { + margin-left: 0; +} +.kuiToolBarSection:last-child { + margin-right: 0; + flex: 0 1 auto; justify-content: flex-end; } +.kuiToolBarSection:only-child { + margin-left: auto; } +.kuiToolBarSection > * + * { + margin-left: 10px; } + +.kuiToolBar--searchOnly .kuiToolBarSearch { + margin-left: 0 !important; } + +.kuiToolBarFooter { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 30px; padding: 10px; + height: 40px; + background-color: #0A121A; +} + +.kuiToolBarFooterSection { + display: flex; + align-items: center; + flex: 1 1 auto; + margin-left: 25px; + margin-right: 25px; +} +.kuiToolBarFooterSection:not(:first-child):not(:last-child):not(:only-child) { + justify-content: center; } +.kuiToolBarFooterSection:first-child { + margin-left: 0; +} +.kuiToolBarFooterSection:last-child { + margin-right: 0; + flex: 0 1 auto; justify-content: flex-end; } +.kuiToolBarFooterSection:only-child { + margin-left: auto; } +.kuiToolBarFooterSection > * + * { + margin-left: 10px; } + +.kuiToolBarSearch { + display: flex; + align-items: center; + margin-left: 25px; + margin-right: 25px; + flex: 1 1 auto; + max-width: 100%; line-height: 1.5; +} +.kuiToolBarSearch:first-child { + margin-left: 0; +} +.kuiToolBarSearch:last-child { + margin-right: 0; +} +.kuiToolBarSearch > * + * { + margin-left: 10px; } + +.kuiToolBarSearchBox { + flex: 1 1 auto; + position: relative; + font-size: 16px; + max-width: 800px; +} + +.kuiToolBarSearchBox__icon { + position: absolute; + top: 0.5em; + left: 0.7em; + font-size: 1em; + color: #acacac; +} + +.kuiToolBarSearchBox__input { + width: 100%; + min-width: 200px; + padding: 4px 12px 5px 28px; + font-family: var(--font-text); background-color: #0A121A; + color: #DFE5EF; + border-radius: 4px; + font-size: 1em; + border: 1px solid #293847; + line-height: normal; transition: border-color 0.1s linear; +} +.kuiToolBarSearchBox__input:focus { + outline: none; + border-color: #FFC0CB; +} + +.kuiToolBarText { + font-size: 16px; + line-height: 1.5; + color: #8D98A3; + white-space: nowrap; } + +.kuiTitle { + margin: 0; font-weight: 400; font-size: 24px; +} + +.kuiSubTitle { + margin: 0; font-weight: 400; font-size: 20px; +} + +.kuiTextTitle { + margin: 0; font-weight: 700; line-height: 1.5; + font-size: 16px; +} + +.kuiText { + margin: 0; font-weight: 400; line-height: 1.5; + font-size: 16px; +} + +.kuiSubText { + margin: 0; font-weight: 400; line-height: 1.5; + font-size: 14px; +} + +.kuiSubduedText { + color: #8D98A3 !important; +} + +.kuiVerticalRhythm + .kuiVerticalRhythm { + margin-top: 16px; +} + +.kuiVerticalRhythmSmall + .kuiVerticalRhythmSmall { + margin-top: 8px; +} + +.kuiVerticalRhythmLarge + .kuiVerticalRhythmLarge { + margin-top: 24px; +} + +.kuiVerticalRhythmXLarge + .kuiVerticalRhythmXLarge { + margin-top: 32px; +} + +.kuiView { + background-color: #fff; + flex: 1 1 auto; +} + +.kuiViewContent { + padding-top: 20px; + padding-bottom: 20px; + width: 100%; +} + +.kuiViewContent--constrainedWidth { + width: 100%; + max-width: 1100px; + margin-left: auto; + margin-right: auto; +} + +.kuiViewContentItem { + padding-left: 20px; + padding-right: 20px; +} diff --git a/packages/osd-ui-framework/dist/kui_v9_light.css b/packages/osd-ui-framework/dist/kui_v9_light.css new file mode 100644 index 000000000000..f7efab3e9de6 --- /dev/null +++ b/packages/osd-ui-framework/dist/kui_v9_light.css @@ -0,0 +1,1635 @@ +/*! + * 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. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +main { + display: block; } + +.kuiBar { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 30px; } + +.kuiBarSection { + display: flex; + align-items: center; + flex: 1 1 auto; + margin-left: 25px; + margin-right: 25px; +} +.kuiBarSection:not(:first-child):not(:last-child):not(:only-child) { + justify-content: center; } +.kuiBarSection:first-child { + margin-left: 0; +} +.kuiBarSection:last-child { + margin-right: 0; + flex: 0 1 auto; justify-content: flex-end; } +.kuiBarSection:only-child { + margin-left: auto; } +.kuiBarSection > * + * { + margin-left: 10px; } + +.kuiButton { + display: inline-block; appearance: none; + cursor: pointer; + padding: 4px 12px 5px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + height: 30px; + text-decoration: none; + border: none; + border-radius: 4px; +} +.kuiButton:disabled { + cursor: not-allowed; + opacity: 0.5; +} +a.kuiButton.kuiButton-isDisabled { + cursor: not-allowed; + opacity: 0.5; +} + +.kuiButton:enabled:active { + transform: translateY(1px); +} +a.kuiButton:not(.kuiButton-isDisabled):active { transform: translateY(1px); +} + +.kuiButton__inner { + display: flex; align-items: center; } + +.kuiButton--small { + font-size: 12px; + padding: 2px 8px 3px; + height: 22px; +} + +.kuiButton--fullWidth { + width: 100%; + text-align: center; +} + +.kuiButton--iconText .kuiButton__icon:first-child:not(:only-child) { + margin-right: 8px; +} +.kuiButton--iconText .kuiButton__icon:last-child:not(:only-child) { + margin-left: 8px; +} +.kuiButton--iconText.kuiButton--small .kuiButton__icon:first-child:not(:only-child) { + margin-right: 4px; +} +.kuiButton--iconText.kuiButton--small .kuiButton__icon:last-child:not(:only-child) { + margin-left: 4px; +} + +.kuiButton--basic { + color: #343741; + background-color: #F5F7FA; +} +.kuiButton--basic:not(a):enabled:focus { + color: #343741; +} +a.kuiButton--basic:not(.kuiButton-isDisabled):focus { color: #343741; +} + +.kuiButton--basic:enabled:hover { + background-color: #d3dce9 !important; } +a.kuiButton--basic:not(.kuiButton-isDisabled):hover { background-color: #d3dce9 !important; } + +.kuiButton--basic:enabled:active { + background-color: #d3dce9 !important; } +a.kuiButton--basic:not(.kuiButton-isDisabled):active { background-color: #d3dce9 !important; } + +.kuiButton--primary { + color: #FFF; + background-color: #006BB4; +} +.kuiButton--primary:not(a):enabled:focus { + color: #FFF; +} +a.kuiButton--primary:not(.kuiButton-isDisabled):focus { color: #FFF; +} + +.kuiButton--primary:enabled:hover { + color: #FFF !important; background-color: #004d81; +} +a.kuiButton--primary:not(.kuiButton-isDisabled):hover { color: #FFF !important; background-color: #004d81; +} + +.kuiButton--primary:enabled:active { + color: #FFF !important; background-color: #004d81; +} +a.kuiButton--primary:not(.kuiButton-isDisabled):active { color: #FFF !important; background-color: #004d81; +} + +.kuiButton--success { + color: #FFF; + background-color: #017D73; +} +.kuiButton--success:not(a):enabled:focus { + color: #FFF; +} +a.kuiButton--success:not(.kuiButton-isDisabled):focus { color: #FFF; +} + +.kuiButton--success:enabled:hover { + color: #FFF !important; background-color: #014a44; +} +a.kuiButton--success:not(.kuiButton-isDisabled):hover { color: #FFF !important; background-color: #014a44; +} + +.kuiButton--success:enabled:active { + color: #FFF !important; background-color: #014a44; +} +a.kuiButton--success:not(.kuiButton-isDisabled):active { color: #FFF !important; background-color: #014a44; +} + +.kuiButton--danger { + color: #BD271E; + border: solid 1px #BD271E; +} +.kuiButton--danger:not(a):enabled:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #FFF, 0 0 0 2px #BD271E; color: #BD271E; +} +a.kuiButton--danger:not(.kuiButton-isDisabled):focus { z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #FFF, 0 0 0 2px #BD271E; color: #BD271E; +} + +.kuiButton--danger:enabled:hover { + color: #911e17 !important; + border: solid 1px #911e17; + background-color: rgba(189, 39, 30, 0.1); +} +a.kuiButton--danger:not(.kuiButton-isDisabled):hover { color: #911e17 !important; + border: solid 1px #911e17; + background-color: rgba(189, 39, 30, 0.1); +} + +.kuiButton--danger:enabled:active { + color: #911e17 !important; + border: solid 1px #911e17; + background-color: rgba(189, 39, 30, 0.1); +} +a.kuiButton--danger:not(.kuiButton-isDisabled):active { color: #911e17 !important; + border: solid 1px #911e17; + background-color: rgba(189, 39, 30, 0.1); +} + +.kuiButton--warning { + color: #FFF; + background-color: #F5A700; +} +.kuiButton--warning:not(a):enabled:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #FFF, 0 0 0 2px #F5A700; color: #FFF; +} +a.kuiButton--warning:not(.kuiButton-isDisabled):focus { z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #FFF, 0 0 0 2px #F5A700; color: #FFF; +} + +.kuiButton--warning:enabled:hover { + color: #FFF !important; background-color: #c28400; +} +a.kuiButton--warning:not(.kuiButton-isDisabled):hover { color: #FFF !important; background-color: #c28400; +} + +.kuiButton--warning:enabled:active { + color: #FFF !important; background-color: #c28400; +} +a.kuiButton--warning:not(.kuiButton-isDisabled):active { color: #FFF !important; background-color: #c28400; +} + +.kuiButton--warning:disabled { + background-color: #ffbb29; +} +a.kuiButton--warning.kuiButton-isDisabled { + background-color: #ffbb29; +} + +.kuiButton--hollow { + color: #006BB4 !important; background-color: transparent; +} +.kuiButton--hollow:enabled:hover { + color: #004d81 !important; text-decoration: underline; +} +a.kuiButton--hollow:not(.kuiButton-isDisabled):hover { color: #004d81 !important; text-decoration: underline; +} + +.kuiButton--hollow:enabled:active { + color: #004d81 !important; text-decoration: underline; +} +a.kuiButton--hollow:not(.kuiButton-isDisabled):active { color: #004d81 !important; text-decoration: underline; +} + +.kuiButton--secondary { + color: #006BB4 !important; border: solid 1px #006BB4; +} +.kuiButton--secondary:enabled:hover { + color: #004d81 !important; border: solid 1px #004d81; + background-color: rgba(0, 107, 180, 0.1); + text-decoration: underline; +} +a.kuiButton--secondary:not(.kuiButton-isDisabled):hover { color: #004d81 !important; border: solid 1px #004d81; + background-color: rgba(0, 107, 180, 0.1); + text-decoration: underline; +} + +.kuiButton--secondary:enabled:active { + color: #004d81 !important; border: solid 1px #004d81; + background-color: rgba(0, 107, 180, 0.1); + text-decoration: underline; +} +a.kuiButton--secondary:not(.kuiButton-isDisabled):active { color: #004d81 !important; border: solid 1px #004d81; + background-color: rgba(0, 107, 180, 0.1); + text-decoration: underline; +} + +.kuiButtonGroup { + display: flex; + align-items: center; +} +.kuiButtonGroup .kuiButton + .kuiButton { + margin-left: 4px; +} + +.kuiButtonGroup--united > .kuiButton:not(:first-child):not(:last-child) { + border-radius: 0; +} +.kuiButtonGroup--united > .kuiButton:first-child { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.kuiButtonGroup--united > .kuiButton:last-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.kuiButtonGroup--united > .kuiButton:only-child { + border-radius: 4px 4px 4px 4px; +} +.kuiButtonGroup--united .kuiButton + .kuiButton { + margin-left: 0; +} + +.kuiButtonGroup--fullWidth { + display: flex; +} +.kuiButtonGroup--fullWidth > .kuiButton { + flex: 1 1 auto; + text-align: center; +} + +.eui-textCenter > .kuiButtonGroup, +.text-center > .kuiButtonGroup { + display: inline-block; +} + +.kuiCollapseButton { + appearance: none; + background-color: transparent; + padding: 4px; + border: none; + line-height: 1; + font-size: 16px; + color: #343741 !important; cursor: pointer; + opacity: 0.35; +} +.kuiCollapseButton:hover { + opacity: 1; +} + +.kuiAssistedInput { + display: inline-block; position: relative; +} + +.kuiAssistedInput__assistance { + position: absolute; + right: 12px; + top: 50%; transform: translateY(-50%); } + +.kuiCheckBox { + appearance: none; background-color: #fbfcfd; + border: 1px solid rgba(16, 38, 118, 0.1); + border-radius: 4px; + width: 16px; + height: 16px; + font: var(--font-text) !important; line-height: 1.5 !important; margin: 0 !important; font-family: var(--font-text) !important; font-size: 10px !important; transition: background-color 0.1s linear; +} +.kuiCheckBox::before { + position: relative; + left: 0.25em; + font-family: FontAwesome, sans-serif; + content: "\f00c"; + font-size: 1em; + opacity: 0; + color: #FFF; + transition: opacity 0.1s linear; +} +.kuiCheckBox:checked { + border-color: #006BB4; + background-color: #006BB4; +} +.kuiCheckBox:checked::before { + opacity: 1; +} +.kuiCheckBox:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #FFF, 0 0 0 2px #006BB4; } +.kuiCheckBox:disabled { + background-color: #eef2f7 !important; + border-color: #eef2f7 !important; + cursor: not-allowed !important; +} + +.kuiCheckBoxLabel { + display: flex; + align-items: center; + font-weight: normal !important; + line-height: 1.5; +} + +.kuiCheckBoxLabel__text { + font-size: 16px; + margin-left: 8px; +} + +.kuiLabel { + font-size: 16px; + line-height: 1.5; + font-weight: bold; + margin-bottom: 0; } + +.kuiSearchInput { + width: 180px; + display: inline-block; + position: relative; + font-size: 16px; + line-height: 1.5; +} +.kuiSearchInput.kuiSearchInput-isInvalid .kuiSearchInput__input { + border-color: #BD271E; +} + +.kuiSearchInput__icon { + position: absolute; + top: 0.5em; + left: 0.7em; + font-size: 1em; + color: #98A2B3; +} + +.kuiSearchInput__input { + appearance: none; + font-family: var(--font-text); + padding: 4px 12px 4px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #343741; + background-color: #fbfcfd; + border: 1px solid rgba(16, 38, 118, 0.1); + border-radius: 4px; + transition: border-color 0.1s linear; + min-height: 32px; padding-left: 28px; width: 100%; } +.kuiSearchInput__input:invalid { + border-color: #BD271E; +} +.kuiSearchInput__input:focus { + outline: none; + border-color: #006BB4; +} +.kuiSearchInput__input:disabled { + opacity: 0.4; + cursor: not-allowed; +} + +.kuiSearchInput--small { + width: 60px; +} + +.kuiSearchInput--large { + width: 400px; +} + +.kuiSelect { + appearance: none; + font-family: var(--font-text); + padding: 4px 12px 4px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #343741; + background-color: #fbfcfd; + border: 1px solid rgba(16, 38, 118, 0.1); + border-radius: 4px; + transition: border-color 0.1s linear; + min-height: 32px; padding-right: 30px; background-image: url('data:image/svg+xml;utf8,'); background-size: 14px; + background-repeat: no-repeat; + background-position: calc(100% - 8px); } +.kuiSelect:invalid { + border-color: #BD271E; +} +.kuiSelect:focus { + outline: none; + border-color: #006BB4; +} +.kuiSelect:disabled { + opacity: 0.4; + cursor: not-allowed; +} +.kuiSelect:-moz-focusring { + text-shadow: 0 0 0; } +.kuiSelect.kuiSelect-isInvalid { + border-color: #BD271E; +} +.kuiSelect:focus { + box-shadow: none; + outline: none; + border-color: #006BB4; +} + +.kuiSelect--small { + width: 60px; +} + +.kuiSelect--medium { + width: 180px; +} + +.kuiSelect--large { + width: 400px; +} + +.kuiStaticInput { + width: 180px; + appearance: none; + font-family: var(--font-text); + padding: 4px 12px 4px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #343741; + border: 1px solid transparent; background-color: transparent; +} + +.kuiTextArea { + width: 180px; + appearance: none; + font-family: var(--font-text); + padding: 4px 12px 4px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #343741; + background-color: #fbfcfd; + border: 1px solid rgba(16, 38, 118, 0.1); + border-radius: 4px; + transition: border-color 0.1s linear; + min-height: 32px; } +.kuiTextArea:invalid { + border-color: #BD271E; +} +.kuiTextArea:focus { + outline: none; + border-color: #006BB4; +} +.kuiTextArea:disabled { + opacity: 0.4; + cursor: not-allowed; +} +.kuiTextArea:focus { + box-shadow: none; + outline: none; + border-color: #006BB4; +} +.kuiTextArea.kuiTextArea-isInvalid { + border-color: #BD271E; +} + +.kuiTextArea--nonResizable { + resize: none; +} + +.kuiTextArea--small { + width: 60px; +} + +.kuiTextArea--large { + width: 400px; +} + +.kuiTextInput { + width: 180px; + appearance: none; + font-family: var(--font-text); + padding: 4px 12px 4px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #343741; + background-color: #fbfcfd; + border: 1px solid rgba(16, 38, 118, 0.1); + border-radius: 4px; + transition: border-color 0.1s linear; + min-height: 32px; } +.kuiTextInput:invalid { + border-color: #BD271E; +} +.kuiTextInput:focus { + outline: none; + border-color: #006BB4; +} +.kuiTextInput:disabled { + opacity: 0.4; + cursor: not-allowed; +} +.kuiTextInput.kuiTextInput-isInvalid { + border-color: #BD271E; +} + +.kuiTextInput--small { + width: 60px; +} + +.kuiTextInput--large { + width: 400px; +} + +.kuiFieldGroup { + display: flex; + align-items: center; } + +.kuiFieldGroup--alignTop { + align-items: flex-start; +} + +.kuiFieldGroupSection { + line-height: 1.5; +} +.kuiFieldGroupSection + .kuiFieldGroupSection { + margin-left: 10px; +} + +.kuiFieldGroupSection--wide { + flex: 1 1 auto; +} +.kuiFieldGroupSection--wide > * { + width: 100%; +} + +.kuiIcon { + display: inline-block; font: normal normal normal 14px/1 FontAwesome, sans-serif; font-size: inherit; text-rendering: auto; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } + +.kuiIcon--info { + color: #006BB4; +} + +.kuiIcon--success { + color: #017D73; +} + +.kuiIcon--warning { + color: #F5A700; +} + +.kuiIcon--error { + color: #BD271E; +} + +.kuiIcon--inactive { + color: #D3DAE6; +} + +.kuiIcon--basic { + color: #69707D; +} + +.kuiInfoPanel { + padding: 14px 20px 18px; + line-height: 1.5; + border: 2px solid; +} + +.kuiInfoPanel--info { + border-color: rgba(0, 107, 180, 0.25); } + +.kuiInfoPanel--success { + border-color: rgba(1, 125, 115, 0.25); } + +.kuiInfoPanel--warning { + border-color: rgba(245, 167, 0, 0.25); } + +.kuiInfoPanel--error { + border-color: rgba(189, 39, 30, 0.25); } + +.kuiInfoPanelHeader { + display: flex; + align-items: baseline; } + +.kuiInfoPanelHeader__icon { + margin-right: 10px; + font-size: 16px; + line-height: 1.5; +} + +.kuiInfoPanelHeader__title { + font-size: 16px; + line-height: 1.5; + font-weight: 700; +} + +.kuiInfoPanelBody { + margin-top: 8px; +} +.kuiInfoPanelBody > * + * { + margin-top: 8px; +} + +.kuiInfoPanelBody__message { + font-size: 16px; + line-height: 1.5; +} + +.kuiLink { + color: #006BB4; + text-decoration: none; + cursor: pointer; appearance: none; background-color: transparent; border: none; font-size: inherit; line-height: inherit; } +.kuiLink:visited, .kuiLink:active { + color: #006BB4; +} +.kuiLink:hover { + color: #004d81; + text-decoration: underline; +} + +.kuiLocalBreadcrumbs { + display: flex; + align-items: center; + padding: 12px 8px; border-bottom: 1px solid #D3DAE6; + flex-grow: 1; + flex-basis: 100%; + background-color: #FFF; +} + +.kuiLocalBreadcrumb { + font-size: 14px; + margin: 0; + font-weight: normal; +} +.kuiLocalBreadcrumb + .kuiLocalBreadcrumb { + margin-left: 6px; +} +.kuiLocalBreadcrumb + .kuiLocalBreadcrumb::before { + content: "/"; + user-select: none; + margin-right: 4px; + color: #D3DAE6; +} + +.kuiLocalBreadcrumb__link { + color: #006BB4; + text-decoration: none; + cursor: pointer; appearance: none; background-color: transparent; border: none; font-size: inherit; line-height: inherit; color: #006BB4; font-size: 16px; +} +.kuiLocalBreadcrumb__link:visited, .kuiLocalBreadcrumb__link:active { + color: #006BB4; +} +.kuiLocalBreadcrumb__link:hover { + color: #004d81; + text-decoration: underline; +} + +.kuiLocalBreadcrumb__emphasis { + font-weight: 700; +} + +.kuiDatePicker { + background-color: transparent; + border-collapse: collapse; + border-spacing: 0; + line-height: 1.5; +} + +.kuiDatePickerNavigationCell { + padding: 0; +} + +.kuiDatePickerNavigation { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 4px; +} + +.kuiDatePickerNavigationButton { + appearance: none; + background-color: transparent; + border: none; + font-size: 14px; + color: #343741; + padding: 3px 6px; + border-radius: 4px; +} +.kuiDatePickerNavigationButton:hover, .kuiDatePickerNavigationButton:active { + cursor: pointer; + color: #F5F7FA; + background-color: #006BB4; +} +.kuiDatePickerNavigationButton:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px transparent, 0 0 0 2px #006BB4; color: #343741; } + +.kuiDatePickerHeaderCell { + padding: 9px 0; + color: #343741; + font-size: 14px; + font-weight: bold; + text-align: center; + line-height: 1.2; +} + +.kuiDatePickerRowCell { + padding: 0; + text-align: center; +} + +.kuiDatePickerRowCellContent { + appearance: none; + background-color: transparent; + width: 100%; + border: 1px solid transparent; + color: #343741; + font-size: 14px; + padding: 8px; + border-radius: 4px; + line-height: 1.2; +} +.kuiDatePickerRowCellContent:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px transparent, 0 0 0 2px #006BB4; color: #343741; } +.kuiDatePickerRowCellContent:disabled { + pointer-events: none; + opacity: 0.5; +} +.kuiDatePickerRowCellContent.kuiDatePickerRowCellContent-isOtherMonth { + visibility: hidden; + pointer-events: none; +} +.kuiDatePickerRowCellContent.kuiDatePickerRowCellContent-isCurrent { + color: #006BB4; +} +.kuiDatePickerRowCellContent.kuiDatePickerRowCellContent-isSelected { + background-color: #69707D; + color: #343741; +} +.kuiDatePickerRowCellContent:hover, .kuiDatePickerRowCellContent:active { + cursor: pointer; + color: #F5F7FA; + background-color: #006BB4; +} + +.kuiLocalDropdown { + position: relative; + padding: 10px 8px 14px; + background-color: transparent; + margin-bottom: 10px; + line-height: 20px; +} + +.kuiLocalDropdownCloseButton { + appearance: none; + background-color: transparent; + padding: 4px; + border: none; + line-height: 1; + font-size: 16px; + color: #343741 !important; cursor: pointer; + opacity: 0.35; + position: absolute; + top: 1px; + right: 5px; +} +.kuiLocalDropdownCloseButton:hover { + opacity: 1; +} + +.kuiLocalDropdownPanels { + display: flex; +} + +.kuiLocalDropdownPanel { + flex: 1 1 0%; +} + +.kuiLocalDropdownPanel--left { + margin-right: 30px; +} + +.kuiLocalDropdownPanel--right { + margin-left: 30px; +} + +.kuiLocalDropdownTitle { + margin-top: 0; margin-bottom: 12px; + font-size: 18px; + color: #343741; +} + +.kuiLocalDropdownSection { + margin-bottom: 16px; +} +.kuiLocalDropdownSection:last-child { + margin-bottom: 0; +} + +.kuiLocalDropdownHeader { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 6px; +} + +.kuiLocalDropdownHeader__label { + font-size: 14px; + font-weight: 700; + margin-bottom: 0; color: #343741; +} + +.kuiLocalDropdownHeader__actions { + display: flex; +} + +.kuiLocalDropdownHeader__action { + color: #006BB4; + font-size: 12px; + text-decoration: none; + cursor: pointer; +} +.kuiLocalDropdownHeader__action + .kuiLocalDropdownHeader__action { + margin-left: 10px; +} +.kuiLocalDropdownHeader__action:hover, .kuiLocalDropdownHeader__action:active { + color: #004d81; +} + +.kuiLocalDropdownInput { + display: block; + width: 100%; + margin-bottom: 12px; + padding: 5px 15px; + font-size: 14px; + color: #343741; + background-color: #fbfcfd; + border: 1px solid rgba(16, 38, 118, 0.1); + border-radius: 4px; +} +.kuiLocalDropdownInput:focus { + border-color: #006BB4; +} + +.kuiLocalDropdownFormNote { + font-size: 14px; + color: #69707D; +} + +.kuiLocalDropdownWarning { + margin-bottom: 16px; + padding: 6px 10px; + font-size: 14px; + color: #343741; + background-color: #FFF; + border-left: solid 2px #BD271E; +} + +.kuiLocalDropdownHelpText { + margin-bottom: 16px; + font-size: 14px; + color: #69707D; +} + +.kuiLocalMenu { + display: flex; + align-items: stretch; + padding-top: 8px; + padding-bottom: 8px; +} + +.kuiLocalMenuItem { + display: flex; + align-items: center; + padding: 2px 8px; + font-size: 16px; + background-color: transparent; + color: #343741; + border: 0; + cursor: pointer; + border-bottom: solid 2px transparent; +} +.kuiLocalMenuItem:hover, .kuiLocalMenuItem:focus { + background-color: rgba(0, 107, 180, 0.15); + text-decoration: underline; +} +.kuiLocalMenuItem.kuiLocalMenuItem-isSelected { + color: #006BB4; + background-color: transparent; + border-color: #006BB4; + z-index: 2; +} +.kuiLocalMenuItem.kuiLocalMenuItem-isSelected:hover, .kuiLocalMenuItem.kuiLocalMenuItem-isSelected:focus { + text-decoration: none; +} +.kuiLocalMenuItem.kuiLocalMenuItem-isDisabled { + opacity: 0.5; + cursor: not-allowed; +} +.kuiLocalMenuItem.kuiLocalMenuItem-isDisabled:hover { + background-color: transparent; + text-decoration: none; +} + +.kuiLocalMenuItem__icon { + margin-right: 5px; + margin-bottom: -1px; +} + +.kuiLocalNav { + display: flex; + flex-direction: column; + justify-content: space-between; + min-height: 69px; color: #343741; + background-color: #FFF; + line-height: 1.5; + border-bottom: solid 1px #D3DAE6; +} + +.kuiLocalNavRow { + display: flex; + align-items: stretch; + justify-content: space-between; +} + +.kuiLocalNavRow__section { + display: flex; + align-items: stretch; +} + +.kuiLocalNavRow--secondary { + padding: 0 8px; align-items: flex-start; } + +.kuiLocalSearch { + display: flex; + width: 100%; + margin-bottom: 8px; +} + +.kuiLocalSearchInput { + appearance: none; + font-family: var(--font-text); + padding: 4px 12px 4px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #343741; + background-color: #fbfcfd; + border: 1px solid rgba(16, 38, 118, 0.1); + border-radius: 4px; + transition: border-color 0.1s linear; + min-height: 32px; flex: 1 1 100%; + border-color: #FFF; + border-color: #D3DAE6; + border-radius: 4px 0 0 4px; +} +.kuiLocalSearchInput:invalid { + border-color: #BD271E; +} +.kuiLocalSearchInput:focus { + outline: none; + border-color: #006BB4; +} +.kuiLocalSearchInput:disabled { + opacity: 0.4; + cursor: not-allowed; +} +.kuiLocalSearchInput:focus { + box-shadow: none; +} +.kuiLocalSearchInput.kuiLocalSearchInput-isInvalid { + border-color: #BD271E; +} + +.kuiLocalSearchInput--secondary { + height: 32px; + flex: 0 0 auto; + border-radius: 0; + border-left-width: 0; +} + +.kuiLocalSearchInput, +.kuiLocalSearchAssistedInput__input { + padding-right: 6em; } + +.kuiLocalSearchAssistedInput__assistance { + position: absolute; + right: 6px; + top: 50%; z-index: 2; + transform: translateY(-50%); } + +.kuiLocalSearchSelect { + appearance: none; + font-family: var(--font-text); + padding: 4px 12px 4px; + font-size: 16px; + font-weight: 400; + line-height: 1.5; + color: #343741; + background-color: #fbfcfd; + border: 1px solid rgba(16, 38, 118, 0.1); + border-radius: 4px; + transition: border-color 0.1s linear; + min-height: 32px; padding-right: 30px; background-image: url('data:image/svg+xml;utf8,'); background-size: 14px; + background-repeat: no-repeat; + background-position: calc(100% - 8px); border-left-width: 0; + border-radius: 0; +} +.kuiLocalSearchSelect:invalid { + border-color: #BD271E; +} +.kuiLocalSearchSelect:focus { + outline: none; + border-color: #006BB4; +} +.kuiLocalSearchSelect:disabled { + opacity: 0.4; + cursor: not-allowed; +} +.kuiLocalSearchSelect:-moz-focusring { + text-shadow: 0 0 0; } + +.kuiLocalSearchButton { + width: 43px; + height: 32px; + font-size: 16px; + line-height: 0; color: #FFF; + background-color: #006BB4; + border: 0; + border-radius: 0 4px 4px 0; +} +.kuiLocalSearchButton:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #D3DAE6, 0 0 0 2px #006BB4; } + +.kuiLocalTabs { + display: flex; + align-items: flex-end; + height: 100%; +} + +.kuiLocalTab { + padding: 5px 0 6px; + font-size: 18px; + color: #343741; + border-bottom: 2px solid transparent; + text-decoration: none; + cursor: pointer; + margin-top: 0 !important; margin-bottom: 0 !important; } +.kuiLocalTab:hover:not(.kuiLocalTab-isDisabled), .kuiLocalTab:active:not(.kuiLocalTab-isDisabled) { + color: #006BB4; +} +.kuiLocalTab.kuiLocalTab-isSelected { + color: #006BB4; + border-bottom-color: #006BB4; + cursor: default; +} +.kuiLocalTab.kuiLocalTab-isDisabled { + opacity: 0.5; + cursor: default; } +.kuiLocalTab + .kuiLocalTab { + margin-left: 15px; +} + +.kuiLocalTitle { + display: flex; + align-items: center; + padding: 12px 8px; + font-size: 16px; + font-weight: bold; + border-bottom: 1px solid #D3DAE6; + flex-grow: 1; + flex-basis: 100%; + background-color: #FFF; +} +.kuiLocalTitle:empty { + padding: 0; + display: none; +} + +.kuiPager { + display: flex; + align-items: center; +} +.kuiPager > * + * { + margin-left: 10px; } + +.kuiPagerText { + font-size: 16px; + line-height: 1.5; + color: #69707D; + white-space: nowrap; } + +.kuiPanel { + box-shadow: 0 2px 2px -1px rgba(152, 162, 179, 0.3), 0 1px 5px -2px rgba(152, 162, 179, 0.3); + background-color: #FFF; + border: 1px solid #D3DAE6; + border-radius: 4px; +} + +.kuiPanel--prompt { + display: flex; + flex-direction: column; + align-items: center; + text-align: center; + justify-content: center; + min-height: 300px; +} +.kuiPanel--prompt .kuiPanelBody { + padding: 30px; + max-width: 500px; +} + +.kuiPanel--noBorder { + border: none; +} + +.kuiPanel--withToolBar { + border-top: none; + border-radius: 0; +} + +.kuiPanel--centered { + display: flex; + justify-content: center; + align-items: center; +} + +.kuiPanelHeader { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 30px; padding: 10px; + height: 50px; + border-bottom: 1px solid #D3DAE6; +} +.kuiPanelHeader .kuiButton:not(a):enabled:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #D3DAE6, 0 0 0 2px #006BB4; } +a.kuiPanelHeader .kuiButton:not(.kuiButton-isDisabled):focus { z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #D3DAE6, 0 0 0 2px #006BB4; } + +.kuiPanelHeader .kuiButton--danger:not(a):enabled:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #D3DAE6, 0 0 0 2px #BD271E; } +a.kuiPanelHeader .kuiButton--danger:not(.kuiButton-isDisabled):focus { z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #D3DAE6, 0 0 0 2px #BD271E; } + +.kuiPanelHeader .kuiSelect { + border-color: #fbfcfd; +} +.kuiPanelHeader .kuiSelect:not(a):enabled:focus { + outline: none; + border-color: #006BB4; +} +a.kuiPanelHeader .kuiSelect:not(.kuiButton-isDisabled):focus { outline: none; + border-color: #006BB4; +} + +.kuiPanelHeader__title { + font-size: 20px; + line-height: 1.5; + margin: 0; } + +.kuiPanelHeaderSection { + display: flex; + align-items: center; + flex: 1 1 auto; + margin-left: 25px; + margin-right: 25px; +} +.kuiPanelHeaderSection:not(:first-child):not(:last-child):not(:only-child) { + justify-content: center; } +.kuiPanelHeaderSection:first-child { + margin-left: 0; +} +.kuiPanelHeaderSection:last-child { + margin-right: 0; + flex: 0 1 auto; justify-content: flex-end; } +.kuiPanelHeaderSection:only-child { + margin-left: auto; } +.kuiPanelHeaderSection > * + * { + margin-left: 10px; } +.kuiPanelHeaderSection:only-child { + margin-left: 0; margin-right: auto; } + +.kuiPanelBody { + padding: 10px; +} + +.kuiEmptyTablePrompt { + display: flex; + flex-direction: column; + align-items: center; + padding: 30px; +} + +.kuiEmptyTablePrompt__message { + font-size: 20px; + color: #69707D; + line-height: 1.5; +} + +.kuiEmptyTablePrompt__actions { + margin-top: 10px; +} + +.kuiStatusText { + display: inline-flex; + align-items: baseline; +} + +.kuiStatusText--info { + color: #006BB4; +} + +.kuiStatusText--success { + color: #017D73; +} + +.kuiStatusText--warning { + color: #F5A700; +} + +.kuiStatusText--error { + color: #BD271E; +} + +.kuiStatusText__icon { + margin-right: 6px; + width: 1.15em; max-height: 1.15em; } + +.kuiControlledTable { + background: #FFF; +} +.kuiControlledTable .kuiTable { + border-top: none; } +.kuiControlledTable .kuiToolBarFooter { + border-top: none; } +.kuiControlledTable .kuiMenu--contained { + border-top: none; } + +.kuiTable { + width: 100%; + border: 1px solid #D3DAE6; + border-collapse: collapse; + background-color: #FFF; +} + +.kuiTable--fluid { + width: auto; } +.kuiTable--fluid .kuiTableHeaderCell, +.kuiTable--fluid .kuiTableRowCell { + max-width: none; } + +.kuiTableHeaderCell { + font-size: 16px; + font-weight: 400; + text-align: left; + max-width: 20px; line-height: 1.5; + color: #69707D; +} + +.kuiTableHeaderCell__liner { + display: inline-block; + padding: 7px 8px 8px; +} + +.kuiTableHeaderCellButton { + user-select: none; cursor: pointer; + width: 100%; + appearance: none; background-color: transparent; border: 0; padding: 0; color: inherit; line-height: inherit; font-size: inherit; text-align: inherit; } +.kuiTableHeaderCellButton:hover .kuiTableSortIcon { + display: block; + opacity: 1; +} +.kuiTableHeaderCellButton .kuiTableHeaderCell__liner { + display: flex; + align-items: center; +} + +.kuiTableHeaderCell--alignRight { + text-align: right; +} + +.kuiTableSortIcon { + display: none; + pointer-events: none; + margin-left: 4px; +} +.kuiTableHeaderCellButton-isSorted .kuiTableSortIcon { + display: block; + opacity: 0.4; +} + +.kuiTableRow:hover .kuiTableRowHoverReveal { + display: inline-block; +} + +.kuiTableRowHoverReveal { + display: none; +} + +.kuiTableRowCell { + font-size: 16px; + font-weight: 400; + text-align: left; + max-width: 20px; color: #343741; + border-top: 1px solid #D3DAE6; + vertical-align: middle; +} + +.kuiTableRowCell__liner { + padding: 7px 8px 8px; line-height: 1.5; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.kuiTableRowCell__liner > * { + vertical-align: middle; } + +.kuiTableRowCell--wrap .kuiTableRowCell__liner { + white-space: normal; +} + +.kuiTableRowCell--overflowingContent .kuiTableRowCell__liner { + overflow: visible; + white-space: normal; +} + +.kuiTableRowCell--expanded { + border-top-color: #FFF; } + +.kuiTableRowCell--alignRight { + text-align: right; +} +.kuiTableRowCell--alignRight .kuiTableRowCell__liner { + text-align: right; +} + +.kuiTableHeaderCell--checkBox, +.kuiTableRowCell--checkBox { + width: 28px; line-height: 1; } +.kuiTableHeaderCell--checkBox .kuiTableRowCell__liner, +.kuiTableRowCell--checkBox .kuiTableRowCell__liner { + overflow: visible; } +.kuiTableHeaderCell--checkBox .kuiTableHeaderCell__liner, +.kuiTableHeaderCell--checkBox .kuiTableRowCell__liner, +.kuiTableRowCell--checkBox .kuiTableHeaderCell__liner, +.kuiTableRowCell--checkBox .kuiTableRowCell__liner { + padding-right: 0; +} + +.kuiTableInfo { + padding: 30px; + font-size: 20px; + color: #69707D; + line-height: 1.5; +} + +.kuiTabs { + display: flex; + border-bottom: 1px solid #D3DAE6; +} + +.kuiTab { + appearance: none; cursor: pointer; + padding: 10px 30px; + font-size: 16px; + color: #69707D; + background-color: transparent; border: 1px solid #D3DAE6; + border-radius: 0; margin-bottom: -1px; } +.kuiTab + .kuiTab { + border-left: none; +} +.kuiTab + .kuiTab:focus:not(.kuiTab-isSelected):not(:active) { + margin-left: -1px; } +.kuiTab:active { + outline: none !important; box-shadow: none; } +.kuiTab:focus { + outline: none; } +.kuiTab:focus:not(.kuiTab-isSelected):not(:active) { + z-index: 1; + color: #006BB4; + border: 1px solid #006BB4 !important; +} +.kuiTab:hover:not(.kuiTab-isSelected) { + color: #004d81; + background-color: #F5F7FA; +} +.kuiTab.kuiTab-isSelected { + cursor: default; + color: #343741; + background-color: transparent; + border-bottom-color: transparent; +} + +.kuiToolBar { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 30px; padding: 10px; + height: 50px; + background-color: transparent; + border: solid 1px #D3DAE6; +} +.kuiToolBar .kuiButton:not(a):enabled:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #D3DAE6, 0 0 0 2px #006BB4; } +a.kuiToolBar .kuiButton:not(.kuiButton-isDisabled):focus { z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #D3DAE6, 0 0 0 2px #006BB4; } + +.kuiToolBar .kuiButton--danger:not(a):enabled:focus { + z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #D3DAE6, 0 0 0 2px #BD271E; } +a.kuiToolBar .kuiButton--danger:not(.kuiButton-isDisabled):focus { z-index: 1; outline: none !important; box-shadow: 0 0 0 1px #D3DAE6, 0 0 0 2px #BD271E; } + +.kuiToolBar .kuiSelect { + border-color: #fbfcfd; +} +.kuiToolBar .kuiSelect:not(a):enabled:focus { + outline: none; + border-color: #006BB4; +} +a.kuiToolBar .kuiSelect:not(.kuiButton-isDisabled):focus { outline: none; + border-color: #006BB4; +} + +.kuiToolBarSection { + display: flex; + align-items: center; + flex: 1 1 auto; + margin-left: 25px; + margin-right: 25px; +} +.kuiToolBarSection:not(:first-child):not(:last-child):not(:only-child) { + justify-content: center; } +.kuiToolBarSection:first-child { + margin-left: 0; +} +.kuiToolBarSection:last-child { + margin-right: 0; + flex: 0 1 auto; justify-content: flex-end; } +.kuiToolBarSection:only-child { + margin-left: auto; } +.kuiToolBarSection > * + * { + margin-left: 10px; } + +.kuiToolBar--searchOnly .kuiToolBarSearch { + margin-left: 0 !important; } + +.kuiToolBarFooter { + display: flex; + align-items: center; + justify-content: space-between; + min-height: 30px; padding: 10px; + height: 40px; + background-color: #FFF; +} + +.kuiToolBarFooterSection { + display: flex; + align-items: center; + flex: 1 1 auto; + margin-left: 25px; + margin-right: 25px; +} +.kuiToolBarFooterSection:not(:first-child):not(:last-child):not(:only-child) { + justify-content: center; } +.kuiToolBarFooterSection:first-child { + margin-left: 0; +} +.kuiToolBarFooterSection:last-child { + margin-right: 0; + flex: 0 1 auto; justify-content: flex-end; } +.kuiToolBarFooterSection:only-child { + margin-left: auto; } +.kuiToolBarFooterSection > * + * { + margin-left: 10px; } + +.kuiToolBarSearch { + display: flex; + align-items: center; + margin-left: 25px; + margin-right: 25px; + flex: 1 1 auto; + max-width: 100%; line-height: 1.5; +} +.kuiToolBarSearch:first-child { + margin-left: 0; +} +.kuiToolBarSearch:last-child { + margin-right: 0; +} +.kuiToolBarSearch > * + * { + margin-left: 10px; } + +.kuiToolBarSearchBox { + flex: 1 1 auto; + position: relative; + font-size: 16px; + max-width: 800px; +} + +.kuiToolBarSearchBox__icon { + position: absolute; + top: 0.5em; + left: 0.7em; + font-size: 1em; + color: #acacac; +} + +.kuiToolBarSearchBox__input { + width: 100%; + min-width: 200px; + padding: 4px 12px 5px 28px; + font-family: var(--font-text); background-color: #FFF; + color: #343741; + border-radius: 4px; + font-size: 1em; + border: 1px solid #D3DAE6; + line-height: normal; transition: border-color 0.1s linear; +} +.kuiToolBarSearchBox__input:focus { + outline: none; + border-color: #006BB4; +} + +.kuiToolBarText { + font-size: 16px; + line-height: 1.5; + color: #69707D; + white-space: nowrap; } + +.kuiTitle { + margin: 0; font-weight: 400; font-size: 24px; +} + +.kuiSubTitle { + margin: 0; font-weight: 400; font-size: 20px; +} + +.kuiTextTitle { + margin: 0; font-weight: 700; line-height: 1.5; + font-size: 16px; +} + +.kuiText { + margin: 0; font-weight: 400; line-height: 1.5; + font-size: 16px; +} + +.kuiSubText { + margin: 0; font-weight: 400; line-height: 1.5; + font-size: 14px; +} + +.kuiSubduedText { + color: #69707D !important; +} + +.kuiVerticalRhythm + .kuiVerticalRhythm { + margin-top: 16px; +} + +.kuiVerticalRhythmSmall + .kuiVerticalRhythmSmall { + margin-top: 8px; +} + +.kuiVerticalRhythmLarge + .kuiVerticalRhythmLarge { + margin-top: 24px; +} + +.kuiVerticalRhythmXLarge + .kuiVerticalRhythmXLarge { + margin-top: 32px; +} + +.kuiView { + background-color: #fff; + flex: 1 1 auto; +} + +.kuiViewContent { + padding-top: 20px; + padding-bottom: 20px; + width: 100%; +} + +.kuiViewContent--constrainedWidth { + width: 100%; + max-width: 1100px; + margin-left: auto; + margin-right: auto; +} + +.kuiViewContentItem { + padding-left: 20px; + padding-right: 20px; +} diff --git a/packages/osd-ui-framework/package.json b/packages/osd-ui-framework/package.json index 68b2950369dd..e3176f43656e 100644 --- a/packages/osd-ui-framework/package.json +++ b/packages/osd-ui-framework/package.json @@ -23,7 +23,7 @@ "enzyme-adapter-react-16": "^1.9.1" }, "devDependencies": { - "@elastic/eui": "npm:@opensearch-project/oui@1.5.1", + "@elastic/eui": "npm:@opensearch-project/oui@1.12.0", "@osd/babel-preset": "1.0.0", "@osd/optimizer": "1.0.0", "comment-stripper": "^0.0.4", diff --git a/packages/osd-ui-framework/src/global_styling/mixins/_global_mixins.scss b/packages/osd-ui-framework/src/global_styling/mixins/_global_mixins.scss index 0c5f0fd02815..13e94a9fa09b 100644 --- a/packages/osd-ui-framework/src/global_styling/mixins/_global_mixins.scss +++ b/packages/osd-ui-framework/src/global_styling/mixins/_global_mixins.scss @@ -150,10 +150,6 @@ } } -/** - * We specifically don't include Angular's ng-${state} classes here because we don't want to be tightly - * coupled with Angular. - */ @mixin formControlInvalid { border-color: $kuiDangerBorderColor; } diff --git a/packages/osd-ui-framework/src/kui_v9_dark.scss b/packages/osd-ui-framework/src/kui_v9_dark.scss new file mode 100644 index 000000000000..fe1338cf9b69 --- /dev/null +++ b/packages/osd-ui-framework/src/kui_v9_dark.scss @@ -0,0 +1,15 @@ +// EUI global scope is used for KUI variables till fully deprecated +@import "../../../node_modules/@elastic/eui/src/themes/v9/v9_colors_dark"; +@import "../../../node_modules/@elastic/eui/src/global_styling/functions/index"; +@import "../../../node_modules/@elastic/eui/src/global_styling/variables/index"; +@import "../../../node_modules/@elastic/eui/src/global_styling/mixins/index"; + +// Configuration +@import "global_styling/variables/index"; + +// Coreß +@import "global_styling/mixins/index"; +@import "global_styling/reset/index"; + +// Components +@import "components/index"; diff --git a/packages/osd-ui-framework/src/kui_v9_light.scss b/packages/osd-ui-framework/src/kui_v9_light.scss new file mode 100644 index 000000000000..535be1bf1bfd --- /dev/null +++ b/packages/osd-ui-framework/src/kui_v9_light.scss @@ -0,0 +1,15 @@ +// EUI global scope is used for KUI variables till fully deprecated +@import "../../../node_modules/@elastic/eui/src/themes/v9/v9_colors_light"; +@import "../../../node_modules/@elastic/eui/src/global_styling/functions/index"; +@import "../../../node_modules/@elastic/eui/src/global_styling/variables/index"; +@import "../../../node_modules/@elastic/eui/src/global_styling/mixins/index"; + +// Configuration +@import "global_styling/variables/index"; + +// Core +@import "global_styling/mixins/index"; +@import "global_styling/reset/index"; + +// Components +@import "components/index"; diff --git a/packages/osd-ui-shared-deps/index.d.ts b/packages/osd-ui-shared-deps/index.d.ts index 49192a18d291..4e03db427bd4 100644 --- a/packages/osd-ui-shared-deps/index.d.ts +++ b/packages/osd-ui-shared-deps/index.d.ts @@ -43,6 +43,11 @@ export const jsFilename: string; */ export const jsDepFilenames: string[]; +/** + * Re-export all types from theme_config + */ +export * from './theme_config'; + /** * Filename of the unthemed css file in the distributable directory */ @@ -50,24 +55,27 @@ export const baseCssDistFilename: string; /** * Filename of the dark-theme css file in the distributable directory + * @deprecated */ export const darkCssDistFilename: string; /** * Filename of the dark-theme css file in the distributable directory + * @deprecated */ export const darkV8CssDistFilename: string; /** * Filename of the light-theme css file in the distributable directory + * @deprecated */ export const lightCssDistFilename: string; /** * Filename of the light-theme css file in the distributable directory + * @deprecated */ export const lightV8CssDistFilename: string; - /** * Externals mapping inteded to be used in a webpack config */ diff --git a/packages/osd-ui-shared-deps/index.js b/packages/osd-ui-shared-deps/index.js index 36218a28d4eb..fe3de2b1f003 100644 --- a/packages/osd-ui-shared-deps/index.js +++ b/packages/osd-ui-shared-deps/index.js @@ -30,13 +30,18 @@ const Path = require('path'); +Object.assign(exports, require('./theme_config')); exports.distDir = Path.resolve(__dirname, 'target'); exports.jsDepFilenames = ['osd-ui-shared-deps.@elastic.js']; exports.jsFilename = 'osd-ui-shared-deps.js'; exports.baseCssDistFilename = 'osd-ui-shared-deps.css'; +/** @deprecated */ exports.lightCssDistFilename = 'osd-ui-shared-deps.v7.light.css'; +/** @deprecated */ exports.lightV8CssDistFilename = 'osd-ui-shared-deps.v8.light.css'; +/** @deprecated */ exports.darkCssDistFilename = 'osd-ui-shared-deps.v7.dark.css'; +/** @deprecated */ exports.darkV8CssDistFilename = 'osd-ui-shared-deps.v8.dark.css'; exports.externals = { // stateful deps diff --git a/packages/osd-ui-shared-deps/package.json b/packages/osd-ui-shared-deps/package.json index 8f7777459b01..9162d4e4af31 100644 --- a/packages/osd-ui-shared-deps/package.json +++ b/packages/osd-ui-shared-deps/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@elastic/charts": "31.1.0", - "@elastic/eui": "npm:@opensearch-project/oui@1.5.1", + "@elastic/eui": "npm:@opensearch-project/oui@1.12.0", "@elastic/numeral": "npm:@amoo-miki/numeral@2.6.0", "@opensearch/datemath": "5.0.3", "@osd/i18n": "1.0.0", @@ -49,6 +49,6 @@ "sass-embedded": "1.66.1", "sass-loader": "npm:@amoo-miki/sass-loader@10.4.1-node-sass-9.0.0-libsass-3.6.5-with-sass-embedded.rc1", "val-loader": "^2.1.2", - "webpack": "npm:@amoo-miki/webpack@4.46.0-rc.2" + "webpack": "npm:@amoo-miki/webpack@4.46.0-xxhash.1" } } diff --git a/packages/osd-ui-shared-deps/theme.test.ts b/packages/osd-ui-shared-deps/theme.test.ts new file mode 100644 index 000000000000..1786f53af58c --- /dev/null +++ b/packages/osd-ui-shared-deps/theme.test.ts @@ -0,0 +1,17 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +describe('@osd/ui-shared-deps/theme', () => { + it('should return variables for each theme', async () => { + const oldTag = global.__osdThemeTag__; + for (const v of ['v7', 'v8', 'v9']) { + global.__osdThemeTag__ = `${v}light`; + const { euiThemeVars } = await import('./theme'); + expect(euiThemeVars).toBeTruthy(); + jest.resetModules(); + } + global.__osdThemeTag__ = oldTag; + }); +}); diff --git a/packages/osd-ui-shared-deps/theme.ts b/packages/osd-ui-shared-deps/theme.ts index c803a5e37ef7..cf769ab3f94a 100644 --- a/packages/osd-ui-shared-deps/theme.ts +++ b/packages/osd-ui-shared-deps/theme.ts @@ -37,15 +37,19 @@ export type Theme = typeof LightTheme; // in the OpenSearch Dashboards app we can rely on this global being defined, but in // some cases (like jest) the global is undefined -export const tag: string = globals.__osdThemeTag__ || 'v8light'; -export const version = tag.startsWith('v7') ? 7 : 8; -export const darkMode = tag.endsWith('dark'); +export const tag: string = globals.__osdThemeTag__; +const themeVersion = tag?.replace(/(light|dark)$/, '') || 'v8'; +export const version = parseInt(themeVersion.replace(/[^\d]+/g, ''), 10) || 8; +export const darkMode = tag?.endsWith?.('dark'); export let euiLightVars: Theme; export let euiDarkVars: Theme; -if (version === 7) { +if (themeVersion === 'v7') { euiLightVars = require('@elastic/eui/dist/eui_theme_light.json'); euiDarkVars = require('@elastic/eui/dist/eui_theme_dark.json'); +} else if (themeVersion === 'v9') { + euiLightVars = require('@elastic/eui/dist/eui_theme_v9_light.json'); + euiDarkVars = require('@elastic/eui/dist/eui_theme_v9_dark.json'); } else { euiLightVars = require('@elastic/eui/dist/eui_theme_next_light.json'); euiDarkVars = require('@elastic/eui/dist/eui_theme_next_dark.json'); diff --git a/packages/osd-ui-shared-deps/theme_config.d.ts b/packages/osd-ui-shared-deps/theme_config.d.ts new file mode 100644 index 000000000000..ff88a4965aca --- /dev/null +++ b/packages/osd-ui-shared-deps/theme_config.d.ts @@ -0,0 +1,41 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Types for valid theme tags (themeVersion + themeMode) + * Note: used by @osd/optimizer + */ +export type ThemeTag = 'v7light' | 'v7dark' | 'v8light' | 'v8dark' | 'v9light' | 'v9dark'; +export type ThemeTags = readonly ThemeTag[]; + +/** + * List of valid ThemeTags + * Note: used by @osd/optimizer + */ +export const themeTags: ThemeTags; + +/** + * Map of themeVersion values to labels + * Note: this is used for ui display + */ +export const themeVersionLabelMap: Record; + +/** + * Map of labels and versions to themeVersion values + * Note: this is used to correct incorrectly persisted ui settings + */ +export const themeVersionValueMap: Record; + +/** + * Theme CSS distributable filenames by themeVersion and themeMode + * Note: used by bootstrap template + */ +export const themeCssDistFilenames: Record>; + +/** + * KUI CSS distributable filenames by themeVersion and themeMode + * Note: used by bootstrap template + */ +export const kuiCssDistFilenames: Record>; diff --git a/packages/osd-ui-shared-deps/theme_config.js b/packages/osd-ui-shared-deps/theme_config.js new file mode 100644 index 000000000000..0ccd422e321d --- /dev/null +++ b/packages/osd-ui-shared-deps/theme_config.js @@ -0,0 +1,46 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * The purpose of this file is to centalize theme configuration so it can be used across server, + * client, and dev tooling. DO NOT add dependencies that wouldn't operate in all of these contexts. + * + * Default theme is specified in the uiSettings schema. + */ + +const THEME_MODES = ['light', 'dark']; +const THEME_VERSION_LABEL_MAP = { + v7: 'v7', + v8: 'Next (preview)', + v9: 'v9 (preview)', +}; +const THEME_VERSION_VALUE_MAP = { + // allow version lookup by label ... + ...Object.fromEntries(Object.entries(THEME_VERSION_LABEL_MAP).map((a) => a.reverse())), + // ... or by the version itself + ...Object.fromEntries(Object.keys(THEME_VERSION_LABEL_MAP).map((v) => [v, v])), +}; +const THEME_VERSIONS = Object.keys(THEME_VERSION_LABEL_MAP); +const THEME_TAGS = THEME_VERSIONS.flatMap((v) => THEME_MODES.map((m) => `${v}${m}`)); + +exports.themeVersionLabelMap = THEME_VERSION_LABEL_MAP; + +exports.themeVersionValueMap = THEME_VERSION_VALUE_MAP; + +exports.themeTags = THEME_TAGS; + +exports.themeCssDistFilenames = THEME_VERSIONS.reduce((map, v) => { + map[v] = THEME_MODES.reduce((acc, m) => { + acc[m] = `osd-ui-shared-deps.${v}.${m}.css`; + return acc; + }, {}); + return map; +}, {}); + +exports.kuiCssDistFilenames = { + v7: { dark: 'kui_dark.css', light: 'kui_light.css' }, + v8: { dark: 'kui_next_dark.css', light: 'kui_next_light.css' }, + v9: { dark: 'kui_v9_dark.css', light: 'kui_v9_light.css' }, +}; diff --git a/packages/osd-ui-shared-deps/webpack.config.js b/packages/osd-ui-shared-deps/webpack.config.js index d9bfd81af523..8a9e50dd05ef 100644 --- a/packages/osd-ui-shared-deps/webpack.config.js +++ b/packages/osd-ui-shared-deps/webpack.config.js @@ -47,6 +47,8 @@ exports.getWebpackConfig = ({ dev = false } = {}) => ({ 'osd-ui-shared-deps.v7.light': ['@elastic/eui/dist/eui_theme_light.css'], 'osd-ui-shared-deps.v8.dark': ['@elastic/eui/dist/eui_theme_next_dark.css'], 'osd-ui-shared-deps.v8.light': ['@elastic/eui/dist/eui_theme_next_light.css'], + 'osd-ui-shared-deps.v9.dark': ['@elastic/eui/dist/eui_theme_v9_dark.css'], + 'osd-ui-shared-deps.v9.light': ['@elastic/eui/dist/eui_theme_v9_light.css'], }, context: __dirname, devtool: dev ? '#cheap-source-map' : false, diff --git a/release-notes/opensearch-dashboards.release-notes-1.3.17.md b/release-notes/opensearch-dashboards.release-notes-1.3.17.md new file mode 100644 index 000000000000..e3f160692e0b --- /dev/null +++ b/release-notes/opensearch-dashboards.release-notes-1.3.17.md @@ -0,0 +1,17 @@ +# Version 1.3.17 Release Notes + +### 🛡 Security + +### 📈 Features/Enhancements + +### 🐛 Bug Fixes + +- Replace control characters before logging ([#6590](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6590)) + +### 🚞 Infrastructure + +### 📝 Documentation + +### 🛠 Maintenance + +- [Version] Increment version to 1.3.17 ([#6845](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6845)) \ No newline at end of file diff --git a/release-notes/opensearch-dashboards.release-notes-2.15.0.md b/release-notes/opensearch-dashboards.release-notes-2.15.0.md new file mode 100644 index 000000000000..6e6f81212bbb --- /dev/null +++ b/release-notes/opensearch-dashboards.release-notes-2.15.0.md @@ -0,0 +1,78 @@ +## What's Changed +### 📈 Features/Enhancements +* [MQL] support enhancing language selector (#6613) in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6760 +* [MDS] Modify toast + popover warning to include incompatible datasources in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6746 +* [MDS] Support for Timeline in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6493 +* Make Field Name Search Filter Case Insensitive in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6761 +* Add Server Side batching for UI Metric Collectors in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6819 +* [Workspace] Dashboard admin(groups/users) implementation in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6829 +* [Multiple Datasource] Add data source selection service to support storing and getting selected data source updates in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6875 +* [Multiple datasource] Adjust the padding size for aggregated view in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6896 +* [OSCI][FEAT] Changelog Project - PoC Changelog and release notes automation tool in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6918 +* [MD]Remove endpoint validation for create data source saved object API in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6927 +* [MD]Use placeholder for data source credentials fields when export saved object in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6931 +* Modify the adding sample data part for timeline in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6934 +* [Multiple DataSource] Do not support import data source object to Local cluster when not enable data source in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6913 +* [MDS] Fix sample data to use datasources for TSVB visualizations in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6943 +* [MDS] Use DataSourceError to replace Error in getDataSourceById call in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6952 +* Fix web log sample visualization & vis-builder not rendering with data source issue in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6958 +* [Data Explorer] Allow render from View directly, not from Data Explorer in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6959 +* [Workspace]Change workspace description field to textarea in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6960 +* [Workspace]Feat add use cases to workspace form in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6967 +* Use JSON11 for handling long numerals (#6915) in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6970 +* [VisBuilder] Change VisBuilder from experimental to production in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6971 +* Modify the import for timeline visualization to includes data source name in MDS scenario in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6975 +* [Manual Backport 2.x] Add missing aria-label for discover page in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6987 + +### 🐛 Bug Fixes +* add http://www.site.com to lycheeignore in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6781 +* [Workspace] Fix: Show a error toast when workspace read only user delete saved objects in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6783 +* [Workspace] Fix workspace name duplication check in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6813 +* fix vega visualization error message not been formatted in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6840 +* [MD] Fix server sider endpoint validation by passing in request when creating datasource client in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6824 +* Fix index pattern data source reference not updated in sample data in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6853 +* [Bug] Remove unused import and property (#6879) in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6884 +* fix for quickrange to use datemath to parse datetime strings in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6834 +* [BUG] fix default data source bug in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6912 +* Bug Fixes for Vis Builder in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6891 +* [Discover][Bug] Migrate global state from legacy URL in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6909 +* Revert 815d2bd7c612450e2a15a6543c7e74558adf4a81 "[Manual Backport 2.x] Feat (core): Make theme settings user-specific and user-configurable (#5652) (#6681)" in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6978 + +### 🛡 Security +* Bump tar from 6.1.13 to 6.2.1 in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6914 +* [CVE-2024-33883] Bump ejs from `3.1.7` to `3.1.10` in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6924 +* [GHSA-x565-32qp-m3vf] Bump `jimp` to remove phin dependency (#6977) in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6985 + +### 🚞 Infrastructure +* [Release] Add release.yml for release notes automation in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7005 + +### 📝 Documentation +* Add OpenAPI specification for get and create saved object APIs in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6801 +* Updating security reachout email in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6787 +* Add openAPI doc for saved_object find api in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6858 +* Add OpenAPI specification for bulk create and bulk update saved object APIs in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6863 +* Add OpenAPI specification for update, delete and migrate saved object API in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6866 +* Add OpenAPI specification for bulk_get saved object APIs in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6865 +* Add example in create API for create index pattern, vega visualizatinn, dashboards for docs in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6867 +* Add OpenAPI specification for import and export saved object api in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6883 +* Add OpenAPI specifications for resolve import errors api in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6886 +* docs: remove `attributes.` in SavedObjectsFindOptions.fields example in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6917 +* Update sidecar z-index style in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6968 +* Add changelog for #6898 in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6993 +* Add missing change log for PR 6872, 6903 in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6995 + +### 🛠 Maintenance +* add @zhyuanqi as a maintainer in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6789 +* Move @BSFishy to emeritus maintainer in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6793 +* add @mengweieric as maintainer in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6800 +* Update json5 dependency in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6946 +* Add Suchit to be a maintainer in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6997 + +### 🪛 Refactoring +* [Multi Datasource] Unify getDefaultDataSourceId and export in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6882 + +### 🔩 Tests +* [Multiple Datasource Test]add more test for icon and aggregated view in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6740 +* [Multiple Datasource Test] Add test for edit data source form in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6758 +* [Multiple Datasource Test] Add test for error_menu, item, data_source_multi_selectable in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6768 +* [Multiple Datasource Test]Add test for toast button and validation form in https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6804 diff --git a/release-notes/opensearch-dashboards.release-notes-2.16.0.md b/release-notes/opensearch-dashboards.release-notes-2.16.0.md new file mode 100644 index 000000000000..181bc0193d8f --- /dev/null +++ b/release-notes/opensearch-dashboards.release-notes-2.16.0.md @@ -0,0 +1,203 @@ +# VERSION 2.16.0 Release Note + +### 💥 Breaking Changes + +### Deprecations + + - Remove data enhancements config and readonly flag. Removes dead url link, ([#7291](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7291)) + +### 🛡 Security + + - [CVE-2024-28863] Bump tar from 6.1.11 to 6.2.1 ([#6492](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6492)) + - [CVE-2024-33883] Bump ejs from `3.1.7` to `3.1.10` ([#6770](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6770)) + - [CVE-2024-4067][CVE-2024-4068] Bump packages dependent on `braces` versions lower than 3.0.3 ([#6911](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6911)) + - [GHSA-x565-32qp-m3vf] Bump `jimp` to remove phin dependency ([#6977](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6977)) + - [SNYK-JS-AXIOS-6144788] Bump axios to `1.7.2` ([#7149](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7149)) + - [CVE-2024-37890] Bump ws from `8.5.0` to `8.17.1` and from `7.5.7` to `7.5.10` ([#7153](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7153)) + +### 📈 Features/Enhancements + + - Make theme and dark mode settings user/device specific (in local storage), with opt-out ([#5652](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/5652)) + - [Workspace]Import sample data to current workspace ([#6105](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6105)) + - [Data Explorer] Allow render from View directly, not from Data Explorer ([#6167](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6167)) + - [MDS] Allow querying from data sources in Timeline visualizations ([#6385](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6385)) + - [MDS] Prevent importing of data source object when MDS is not enabled ([#6395](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6395)) + - [VisBuilder] Change VisBuilder from experimental to production ([#6436](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6436)) + - Adds `migrations.delete` to delete saved objects by type during a migration ([#6443](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6443)) + - [Workspace] Duplicate selected/all saved objects ([#6478](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6478)) + - [Workspace] Dashboard admin(groups/users) implementation. ([#6554](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6554)) + - Support language selector from the data plugin ([#6613](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6613)) + - Add Server Side Batching for UI Metric Colector ([#6721](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6721)) + - Make Field Name Search Filter Case Insensitive ([#6759](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6759)) + - Add data source selection service to support storing and getting selected data source updates ([#6827](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6827)) + - [Workspace] Only OSD admin can create workspace ([#6831](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6831)) + - [Workspace]Add use cases to workspace form ([#6887](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6887)) + - Add missing aria-label for discover page ([#6898](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6898)) + - Remove endpoint validation for create data source saved object API ([#6899](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6899)) + - [Workspace] Change description field to textarea ([#6907](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6907)) + - Use JSON11 for handling long numerals ([#6915](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6915)) + - [MDS] Allow adding sample data for Timeline visualizations ([#6919](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6919)) + - [Multi DataSource] Add removedComponentIds for data source selection service ([#6920](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6920)) + - [MD]Use placeholder for data source credentials fields when export saved object ([#6928](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6928)) + - Query editor and UI settings toggle ([#7001](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7001)) + - Add search bar extensions ([#7034](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7034)) + - [Workspace] Refactor the UI of workspace picker ([#7045](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7045)) + - Render the datasource selector component conditionally ([#7059](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7059)) + - Introduce new interface for group ([#7060](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7060)) + - Support data source assignment in workspace. ([#7101](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7101)) + - [Workspace] Capabilities service add dashboard admin flag ([#7103](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7103)) + - Onboard dataframes support to MDS and create dataframe before request ([#7106](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7106)) + - Enhance Drag & Drop functionality in Vis Builder ([#7107](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7107)) + - Comply `recent items` with workspace ([#7115](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7115)) + - [Navigation-next] Add register nav group updater in chrome service ([#7117](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7117)) + - [Workspace] Refactor workspace form UI ([#7133](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7133)) + - [MDS] Observability Datasource Plugin migration with MDS support ([#7143](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7143)) + - Add description field in App. ([#7152](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7152)) + - Query editor and dataframes datasources container ([#7157](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7157)) + - [Workspace] Delete the virtual global workspace ([#7165](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7165)) + - 1. Add current nav group into chrome service 2. Prepend current nav group into breadcrumb ([#7166](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7166)) + - [QueryEditorExtensions] change `isEnabled` to an observable ([#7183](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7183)) + - Support workspace level default data source ([#7188](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7188)) + - Introduced an new plugin contentManagement for dynamic content rendering ([#7201](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7201)) + - Address styling of non-primary buttons by making secondary/empty ([#7211](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7211)) + - Add query enhancements plugin as a core plugin ([#7212](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7212)) + - Hide select data source panel for non dashboard admin in workspace create/edit page ([#7213](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7213)) + - [DataSource] Restrict to edit data source on the DSM UI. ([#7214](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7214)) + - Use registered nav group as workspace use case ([#7221](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7221)) + - [navigation-next] Add new left navigation ([#7230](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7230)) + - Add all use case ([#7235](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7235)) + - [navigation-next] add recent works in new homepage ([#7237](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7237)) + - [Workspace] Support workspace detail page ([#7241](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7241)) + - [Workspace] Register workspace settings under setup and settings ([#7242](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7242)) + - Register workspace list card into home page ([#7247](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7247)) + - Add recent items popup in top navigation ([#7257](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7257)) + - [navigation-next] Add new category ([#7275](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7275)) + - Enable landing page for settings and data administration ([#7282](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7282)) + - Support PPL in vega visualization ([#7285](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7285)) + - [VisBuilder] Add Capability to generate dynamic vega ([#7288](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7288)) + - Recover data source management in workspace ([#7296](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7296)) + - Disable certain routes when data_source.manageableBy is none ([#7298](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7298)) + - [navigation-next] fix: redirect to standard index pattern applications while nav group is enabled ([#7305](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7305)) + - Disable inputs in edit data source screen when data_source.manageableBy is none ([#7307](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7307)) + - Update query enhancement UI ([#7309](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7309)) + - [Workspace]Add "All use case" option to workspace form ([#7318](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7318)) + - [MDS] Data Connection details page with MDS support ([#7323](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7323)) + - Use compressed DataSourceSelector ([#7329](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7329)) + - [Workspace] Register four get started cards in home page ([#7333](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7333)) + - [Auto Suggest] OpenSearch SQL autosuggest with ANTLR ([#7336](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7336)) + - [navigation-next] update category ([#7339](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7339)) + - Add home page static list card ([#7351](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7351)) + - [Workspace]Hide create workspace button for non dashboard admin ([#7357](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7357)) + - Enrich breadcrumbs by workspace and use case ([#7360](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7360)) + - Bump OUI to 1.8.0 ([#7363](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7363)) + - [MDS] Observability Datasource Plugin migration with MDS support for Data Connection Table ([#7371](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7371)) + - Add MDS support along with a few cleanup and tests update ([#7463](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7463)) + - Add back data set navigator to control state issues ([#7492](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7492)) + - Fix discover options' location ([#7581](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7581)) + +### 🐛 Bug Fixes + + - [VisBuilder][BUG] Flat render structure in Metric and Table Vis ([#6674](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6674)) + - [MDS] Add a new message to data source components when there are no compatible datasources ([#6678](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6678)) + - Adjust the padding size for aggregated view ([#6715](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6715)) + - Add more test for icon and aggregated view ([#6729](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6729)) + - [OSD Availability] Prevent OSD process crashes when disk is full ([#6733](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6733)) + - Add test for edit data source form ([#6742](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6742)) + - Add test for data_source_error_menu, data_source_item, data_source_multi_selectable ([#6752](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6752)) + - Add test for toast button and validation form ([#6755](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6755)) + - Show error toast when fail to delete saved objects ([#6756](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6756)) + - Lint checker failure fix ([#6771](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6771)) + - Fix workspace name duplication check ([#6776](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6776)) + - Error message is not formatted in vis_type_vega url parser. ([#6777](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6777)) + - [Discover][Bug] Migrate global state from legacy URL ([#6780](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6780)) + - Quickrange selection fix ([#6782](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6782)) + - Bug Fixes for Vis Builder ([#6811](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6811)) + - Fix endpoint validation by passing in request when creating datasource client ([#6822](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6822)) + - Update index pattern references with data source when import sample data ([#6851](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6851)) + - Remove unused import and property which broke compilation ([#6879](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6879)) + - Fix not setting the default data source when creating data source bug ([#6908](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6908)) + - Close any open system flyout when changing view mode of the dashboard ([#6923](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6923)) + - Add TSVB Support for adding sample data ([#6940](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6940)) + - Fix web log sample visualization & vis-builder not rendering with data source issue ([#6948](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6948)) + - [MDS] Include data source name when importing a timeline visualization ([#6954](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6954)) + - Update z-index of sidecar container to make it more than mask, from 1000 to 1001. ([#6964](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6964)) + - [Discover] Check if the timestamp is already included to remove duplicate col ([#6983](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6983)) + - Highlight the anchor row in surrounding doc view ([#7025](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7025)) + - [MDS] Add data source engine type to data source saved object ([#7026](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7026)) + - Fix colors of the visualizations with more than 10 items ([#7051](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7051)) + - [BUG][NewHomePage] Temp Solution to avoid crash for anonymous user with no write permission ([#7054](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7054)) + - [Discover] Allow the last column of a table wider than the window to show up properly ([#7058](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7058)) + - Update error message in timeline visualization when MDS disabled ([#7069](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7069)) + - Fix object empty check and minor perf issue in query editor extensions ([#7077](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7077)) + - Remove angular related comment and code ([#7087](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7087)) + - [MDS][Version Decoupling] Add support of Version Decoupling in Index Patterns Dashboards Plugin ([#7100](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7100)) + - [Workspace]Restrict saved objects finding when workspace enabled ([#7125](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7125)) + - [MDS][Version Decoupling] Add support of required backend plugins check on data sources ([#7146](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7146)) + - [MDS] Fix the dsm plugin setup when mds feature flag is disabled ([#7163](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7163)) + - [MDS][Version Decoupling] Add dataSourceVersion' and 'installedPlugins in viewer returns ([#7172](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7172)) + - Break new lines in table cell in legacy discover ([#7207](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7207)) + - [Sample Data] Updates sample dashboard title in sample web logs data ([#7233](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7233)) + - Discover page status stuck in loading State ([#7252](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7252)) + - Unassign data source before deleteByWorkspace ([#7279](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7279)) + - Unused config setting and remove data sources as a required plugin. ([#7314](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7314)) + - Fix wrapping of labels in filter by type popover ([#7327](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7327)) + - [Navigation] Update dev tools tab css for new left navigation ([#7328](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7328)) + - Data source selector in dev tools tab moved to left ([#7347](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7347)) + - [navigation-next] Fix issues. ([#7356](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7356)) + - [DataSource] No restriction on setting default data source ([#7396](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7396)) + - Make breadcrumb of 4 new added applications comply with BrowserRouter. ([#7401](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7401)) + - [Bug][Workspace] Navigate to detail page when clicking all use case workspace ([#7405](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7405)) + - [Version Decoupling] Add data source version and installed plugins in data source viewer returns ([#7420](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7420)) + - [Bug][Workspace] Add permission validation at workspace detail page ([#7435](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7435)) + - [Bug][Data Source] Move data source manageable feature flag to DSM plugin ([#7440](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7440)) + - Update recent items icon from SVG to react component ([#7478](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7478)) + - [MDS] Fix the hide local cluster config ([#7497](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7497)) + - Update icon of recent items from OUI library to enable dark mode ([#7508](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7508)) + - Fix data source picker trigger local cluster call by default ([#7528](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7528)) + - Fix babel error ([#7541](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7541)) + - Fix tables not displaying in navigator and add local cluster to datasources ([#7542](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7542)) + - Fixes Discover next styling ([#7546](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7546)) + - [navigation]feat: redirect user to home in global when workspace is enabled ([#7551](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7551)) + - [Workspace]Add workspaces and permissions fields into saved objects _bulk_get response ([#7565](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7565)) + - Fixes databases not being displayed upon success ([#7567](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7567)) + +### 🚞 Infrastructure + +### 📝 Documentation + + - Add zhyuanqi as maintainer ([#6788](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6788)) + - Move @BSFishy to emeritus maintainer ([#6790](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6790)) + - Add mengweieric as maintainer ([#6798](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6798)) + - Add OpenAPI specification for GET and CREATE saved object API ([#6799](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6799)) + - Add example for saved object creation part for openapi doc. ([#6855](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6855)) + - Add openAPI doc for saved_object find api ([#6856](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6856)) + - Add OpenAPI specification for bulk create and bulk update saved object APIs ([#6859](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6859)) + - Add OpenAPI specification for bulk_get saved object APIs ([#6860](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6860)) + - Add OpenAPI specification for update, delete and migrate saved object API ([#6864](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6864)) + - Add OpenAPI specification for import and export saved object api ([#6872](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6872)) + - Add OpenAPI specifications for resolve import errors api ([#6885](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6885)) + - Add Suchit as maintainer ([#6980](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6980)) + - Add Viraj as maintainer ([#7196](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7196)) + - Add OpenAPI specification for API for retrieving fields of index patterns ([#7270](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7270)) + - Add Sean as maintainer ([#7458](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7458)) + - Add Joshua as maintainer ([#7553](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7553)) + +### 🛠 Maintenance + + - Skip running tests for updates in CODEOWNERS ([#7197](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7197)) + +### 🪛 Refactoring + + - Unify getDefaultDataSourceId and export ([#6843](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6843)) + - [MDS] Refactor error handling in data source management plugin to use DataSourceError ([#6903](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/6903)) + - [Look&Feel] Refactor to use semantic headers for page, modal & flyout ([#7192](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7192)) + - [Look&Feel] Consistency of Plus Icons ([#7195](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7195)) + - [Look&Feel] Update Popover Padding Size ([#7200](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7200)) + - [Look&Feel] Replace browser tooltip usage with OUI tooltip ([#7231](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7231)) + - [Look&Feel] Use small EuiTabs and EuiTabbedContent across the board ([#7232](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7232)) + - Density and consistency changes for discover and query bar ([#7299](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7299)) + - [Look&Feel] Apply guidance for visBuilder ([#7341](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7341)) + - [Look&Feel] Apply small popover padding and add Oui tooltips ([#7523](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7523)) + - [Look&Feel] Discover and Query Management fix ([#7530](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/7530)) + +### 🔩 Tests \ No newline at end of file diff --git a/src/core/public/application/__snapshots__/application_service.test.ts.snap b/src/core/public/application/__snapshots__/application_service.test.ts.snap index a6c9eb27e338..687977044cde 100644 --- a/src/core/public/application/__snapshots__/application_service.test.ts.snap +++ b/src/core/public/application/__snapshots__/application_service.test.ts.snap @@ -81,7 +81,13 @@ exports[`#start() getComponent returns renderable JSX tree 1`] = ` } mounters={Map {}} setAppActionMenu={[Function]} + setAppBadgeControls={[Function]} + setAppBottomControls={[Function]} + setAppCenterControls={[Function]} + setAppDescriptionControls={[Function]} setAppLeaveHandler={[Function]} + setAppLeftControls={[Function]} + setAppRightControls={[Function]} setIsMounting={[Function]} /> `; diff --git a/src/core/public/application/application_service.mock.ts b/src/core/public/application/application_service.mock.ts index b70a34095f0c..e3897f746cfb 100644 --- a/src/core/public/application/application_service.mock.ts +++ b/src/core/public/application/application_service.mock.ts @@ -65,6 +65,12 @@ const createStartContractMock = (): jest.Mocked => { navigateToUrl: jest.fn(), getUrlForApp: jest.fn(), registerMountContext: jest.fn(), + setAppLeftControls: jest.fn(), + setAppCenterControls: jest.fn(), + setAppRightControls: jest.fn(), + setAppBadgeControls: jest.fn(), + setAppDescriptionControls: jest.fn(), + setAppBottomControls: jest.fn(), }; }; @@ -98,6 +104,18 @@ const createInternalStartContractMock = (): jest.Mocked(undefined), + currentLeftControls$: new BehaviorSubject(undefined), + currentCenterControls$: new BehaviorSubject(undefined), + currentRightControls$: new BehaviorSubject(undefined), + currentBadgeControls$: new BehaviorSubject(undefined), + currentDescriptionControls$: new BehaviorSubject(undefined), + currentBottomControls$: new BehaviorSubject(undefined), + setAppLeftControls: jest.fn(), + setAppCenterControls: jest.fn(), + setAppRightControls: jest.fn(), + setAppBadgeControls: jest.fn(), + setAppDescriptionControls: jest.fn(), + setAppBottomControls: jest.fn(), getComponent: jest.fn(), getUrlForApp: jest.fn(), navigateToApp: jest.fn().mockImplementation((appId) => currentAppId$.next(appId)), diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts index 683884229764..aeacd44989e8 100644 --- a/src/core/public/application/application_service.test.ts +++ b/src/core/public/application/application_service.test.ts @@ -35,7 +35,7 @@ import { } from './application_service.test.mocks'; import { createElement } from 'react'; -import { BehaviorSubject, Subject } from 'rxjs'; +import { BehaviorSubject, Subject, Observable } from 'rxjs'; import { bufferCount, take, takeUntil } from 'rxjs/operators'; import { shallow, mount } from 'enzyme'; @@ -51,7 +51,9 @@ import { AppStatus, AppUpdater, WorkspaceAvailability, + InternalApplicationStart, } from './types'; +import { MountPoint } from '../types'; import { act } from 'react-dom/test-utils'; import { workspacesServiceMock } from '../mocks'; @@ -59,6 +61,7 @@ const createApp = (props: Partial): App => { return { id: 'some-id', title: 'some-title', + description: 'some-description', mount: () => () => undefined, ...props, }; @@ -153,6 +156,7 @@ describe('#setup()', () => { id: 'app2', navLinkStatus: AppNavLinkStatus.visible, status: AppStatus.accessible, + description: 'some-description', }) ); }); @@ -935,6 +939,34 @@ describe('#start()', () => { expect(setupDeps.redirectTo).not.toHaveBeenCalled(); }); }); + + describe('AppControls', () => { + test.each(['Left', 'Center', 'Right', 'Badge', 'Description', 'Bottom'])( + 'records the App%sControls', + async (container) => { + const { register } = service.setup(setupDeps); + + register(Symbol(), createApp({ id: `app${container}` })); + const appStart = await service.start(startDeps); + const setControls = appStart[ + `setApp${container}Controls` as keyof InternalApplicationStart + ] as (mount: MountPoint | undefined) => void; + const currentControls$ = appStart[ + `current${container}Controls$` as keyof InternalApplicationStart + ] as Observable; + + const oldMountPoint = jest.fn(); + const expectedMountPoint = jest.fn(); + + await appStart.navigateToApp(`app${container}`); + setControls(oldMountPoint); + setControls(expectedMountPoint); + + const mountPoint = await currentControls$.pipe(take(1)).toPromise(); + expect(mountPoint).toBe(expectedMountPoint); + } + ); + }); }); describe('#stop()', () => { diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index 76747490a305..630d97476b05 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -37,6 +37,7 @@ import { RecursiveReadonly } from '@osd/utility-types'; import { MountPoint } from '../types'; import { HttpSetup, HttpStart } from '../http'; import { OverlayStart } from '../overlays'; +import { HeaderControlsContainer } from '../chrome/constants'; import { ContextSetup, IContextContainer } from '../context'; import { PluginOpaqueId } from '../plugins'; import { AppRouter } from './ui'; @@ -104,6 +105,12 @@ interface AppUpdaterWrapper { interface AppInternalState { leaveHandler?: AppLeaveHandler; actionMenu?: MountPoint; + leftControls?: MountPoint; + centerControls?: MountPoint; + rightControls?: MountPoint; + badgeControls?: MountPoint; + descriptionControls?: MountPoint; + bottomControls?: MountPoint; } /** @@ -117,6 +124,15 @@ export class ApplicationService { private readonly appInternalStates = new Map(); private currentAppId$ = new BehaviorSubject(undefined); private currentActionMenu$ = new BehaviorSubject(undefined); + + // HeaderControls + private currentLeftControls$ = new BehaviorSubject(undefined); + private currentCenterControls$ = new BehaviorSubject(undefined); + private currentRightControls$ = new BehaviorSubject(undefined); + private currentBadgeControls$ = new BehaviorSubject(undefined); + private currentDescriptionControls$ = new BehaviorSubject(undefined); + private currentBottomControls$ = new BehaviorSubject(undefined); + private readonly statusUpdaters$ = new BehaviorSubject>(new Map()); private readonly subscriptions: Subscription[] = []; private stop$ = new Subject(); @@ -291,6 +307,15 @@ export class ApplicationService { this.currentAppId$.subscribe(() => this.refreshCurrentActionMenu()); + this.currentAppId$.subscribe(() => this.refreshCurrentControls(HeaderControlsContainer.LEFT)); + this.currentAppId$.subscribe(() => this.refreshCurrentControls(HeaderControlsContainer.CENTER)); + this.currentAppId$.subscribe(() => this.refreshCurrentControls(HeaderControlsContainer.RIGHT)); + this.currentAppId$.subscribe(() => this.refreshCurrentControls(HeaderControlsContainer.BADGE)); + this.currentAppId$.subscribe(() => + this.refreshCurrentControls(HeaderControlsContainer.DESCRIPTION) + ); + this.currentAppId$.subscribe(() => this.refreshCurrentControls(HeaderControlsContainer.BOTTOM)); + return { applications$: applications$.pipe( map((apps) => new Map([...apps.entries()].map(([id, app]) => [id, getAppInfo(app)]))), @@ -306,6 +331,46 @@ export class ApplicationService { distinctUntilChanged(), takeUntil(this.stop$) ), + + // HeaderControls + currentLeftControls$: this.currentLeftControls$.pipe( + distinctUntilChanged(), + takeUntil(this.stop$) + ), + currentCenterControls$: this.currentCenterControls$.pipe( + distinctUntilChanged(), + takeUntil(this.stop$) + ), + currentRightControls$: this.currentRightControls$.pipe( + distinctUntilChanged(), + takeUntil(this.stop$) + ), + currentBadgeControls$: this.currentBadgeControls$.pipe( + distinctUntilChanged(), + takeUntil(this.stop$) + ), + currentDescriptionControls$: this.currentDescriptionControls$.pipe( + distinctUntilChanged(), + takeUntil(this.stop$) + ), + currentBottomControls$: this.currentBottomControls$.pipe( + distinctUntilChanged(), + takeUntil(this.stop$) + ), + + setAppLeftControls: (mount: MountPoint | undefined) => + this.setAppLeftControls(this.currentAppId$.value, mount), + setAppCenterControls: (mount: MountPoint | undefined) => + this.setAppCenterControls(this.currentAppId$.value, mount), + setAppRightControls: (mount: MountPoint | undefined) => + this.setAppRightControls(this.currentAppId$.value, mount), + setAppBadgeControls: (mount: MountPoint | undefined) => + this.setAppBadgeControls(this.currentAppId$.value, mount), + setAppDescriptionControls: (mount: MountPoint | undefined) => + this.setAppDescriptionControls(this.currentAppId$.value, mount), + setAppBottomControls: (mount: MountPoint | undefined) => + this.setAppBottomControls(this.currentAppId$.value, mount), + history: this.history!, registerMountContext: this.mountContext.registerContext, getUrlForApp: ( @@ -339,6 +404,12 @@ export class ApplicationService { appStatuses$={applicationStatuses$} setAppLeaveHandler={this.setAppLeaveHandler} setAppActionMenu={this.setAppActionMenu} + setAppLeftControls={this.setAppLeftControls} + setAppCenterControls={this.setAppCenterControls} + setAppRightControls={this.setAppRightControls} + setAppBadgeControls={this.setAppBadgeControls} + setAppDescriptionControls={this.setAppDescriptionControls} + setAppBottomControls={this.setAppBottomControls} setIsMounting={(isMounting) => httpLoadingCount$.next(isMounting ? 1 : 0)} /> ); @@ -367,6 +438,71 @@ export class ApplicationService { this.currentActionMenu$.next(currentActionMenu); }; + private setAppLeftControls = (appPath: string | undefined, mount: MountPoint | undefined) => + this.setAppControls(appPath, mount, HeaderControlsContainer.LEFT); + + private setAppCenterControls = (appPath: string | undefined, mount: MountPoint | undefined) => + this.setAppControls(appPath, mount, HeaderControlsContainer.CENTER); + + private setAppRightControls = (appPath: string | undefined, mount: MountPoint | undefined) => + this.setAppControls(appPath, mount, HeaderControlsContainer.RIGHT); + + private setAppBadgeControls = (appPath: string | undefined, mount: MountPoint | undefined) => + this.setAppControls(appPath, mount, HeaderControlsContainer.BADGE); + + private setAppDescriptionControls = ( + appPath: string | undefined, + mount: MountPoint | undefined + ) => this.setAppControls(appPath, mount, HeaderControlsContainer.DESCRIPTION); + + private setAppBottomControls = (appPath: string | undefined, mount: MountPoint | undefined) => + this.setAppControls(appPath, mount, HeaderControlsContainer.BOTTOM); + + private setAppControls = ( + appPath: string | undefined, + mount: MountPoint | undefined, + container: HeaderControlsContainer + ) => { + if (!appPath) return; + + this.appInternalStates.set(appPath, { + ...(this.appInternalStates.get(appPath) ?? {}), + [`${container}Controls`]: mount, + }); + + this.refreshCurrentControls(container); + }; + + private refreshCurrentControls = (container: HeaderControlsContainer) => { + const appId = this.currentAppId$.getValue(); + switch (container) { + case HeaderControlsContainer.LEFT: + return this.currentLeftControls$.next( + appId ? this.appInternalStates.get(appId)?.leftControls : undefined + ); + case HeaderControlsContainer.CENTER: + return this.currentCenterControls$.next( + appId ? this.appInternalStates.get(appId)?.centerControls : undefined + ); + case HeaderControlsContainer.RIGHT: + return this.currentRightControls$.next( + appId ? this.appInternalStates.get(appId)?.rightControls : undefined + ); + case HeaderControlsContainer.BADGE: + return this.currentBadgeControls$.next( + appId ? this.appInternalStates.get(appId)?.badgeControls : undefined + ); + case HeaderControlsContainer.DESCRIPTION: + return this.currentDescriptionControls$.next( + appId ? this.appInternalStates.get(appId)?.descriptionControls : undefined + ); + case HeaderControlsContainer.BOTTOM: + return this.currentBottomControls$.next( + appId ? this.appInternalStates.get(appId)?.bottomControls : undefined + ); + } + }; + private async shouldNavigate(overlays: OverlayStart): Promise { const currentAppId = this.currentAppId$.value; if (currentAppId === undefined) { @@ -402,6 +538,12 @@ export class ApplicationService { this.stop$.next(); this.currentAppId$.complete(); this.currentActionMenu$.complete(); + this.currentLeftControls$.complete(); + this.currentCenterControls$.complete(); + this.currentRightControls$.complete(); + this.currentBadgeControls$.complete(); + this.currentDescriptionControls$.complete(); + this.currentBottomControls$.complete(); this.statusUpdaters$.complete(); this.subscriptions.forEach((sub) => sub.unsubscribe()); window.removeEventListener('beforeunload', this.onBeforeUnload); diff --git a/src/core/public/application/integration_tests/router.test.tsx b/src/core/public/application/integration_tests/router.test.tsx index 7e1bc437ca9d..88876f65f054 100644 --- a/src/core/public/application/integration_tests/router.test.tsx +++ b/src/core/public/application/integration_tests/router.test.tsx @@ -70,6 +70,12 @@ describe('AppRouter', () => { appStatuses$={mountersToAppStatus$()} setAppLeaveHandler={noop} setAppActionMenu={noop} + setAppLeftControls={noop} + setAppCenterControls={noop} + setAppRightControls={noop} + setAppBadgeControls={noop} + setAppDescriptionControls={noop} + setAppBottomControls={noop} setIsMounting={noop} /> ); diff --git a/src/core/public/application/scoped_history.test.ts b/src/core/public/application/scoped_history.test.ts index 067c33256bd1..6575a6aa1ab1 100644 --- a/src/core/public/application/scoped_history.test.ts +++ b/src/core/public/application/scoped_history.test.ts @@ -30,8 +30,23 @@ import { ScopedHistory } from './scoped_history'; import { createMemoryHistory } from 'history'; +import { getLocaleInUrl } from '../locale_helper'; +import { i18n } from '@osd/i18n'; + +jest.mock('../locale_helper', () => ({ + getLocaleInUrl: jest.fn(), +})); + +jest.mock('@osd/i18n', () => ({ + i18n: { + getLocale: jest.fn(), + }, +})); describe('ScopedHistory', () => { + beforeEach(() => { + (getLocaleInUrl as jest.Mock).mockReturnValue('en'); + }); describe('construction', () => { it('succeeds if current location matches basePath', () => { const gh = createMemoryHistory(); @@ -358,4 +373,49 @@ describe('ScopedHistory', () => { expect(gh.length).toBe(4); }); }); + + describe('locale handling', () => { + let originalLocation: Location; + + beforeEach(() => { + originalLocation = window.location; + delete (window as any).location; + window.location = { href: 'http://localhost/app/wow', reload: jest.fn() } as any; + (i18n.getLocale as jest.Mock).mockReturnValue('en'); + }); + + afterEach(() => { + window.location = originalLocation; + jest.resetAllMocks(); + }); + + it('reloads the page when locale changes', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + const h = new ScopedHistory(gh, '/app/wow'); + // Use the 'h' variable to trigger the listener + h.push('/new-page'); + + // Mock getLocaleInUrl to return a different locale + (getLocaleInUrl as jest.Mock).mockReturnValue('fr'); + + // Simulate navigation + gh.push('/app/wow/new-page'); + + expect(window.location.reload).toHaveBeenCalled(); + }); + + it('does not reload the page when locale changes', () => { + const gh = createMemoryHistory(); + gh.push('/app/wow'); + + // Mock getLocaleInUrl to return a different locale + (getLocaleInUrl as jest.Mock).mockReturnValue('en'); + + // Simulate navigation + gh.push('/app/wow/new-page'); + + expect(window.location.reload).not.toHaveBeenCalled(); + }); + }); }); diff --git a/src/core/public/application/scoped_history.ts b/src/core/public/application/scoped_history.ts index 487093be191f..74e4bb068d3e 100644 --- a/src/core/public/application/scoped_history.ts +++ b/src/core/public/application/scoped_history.ts @@ -39,6 +39,8 @@ import { Href, Action, } from 'history'; +import { i18n } from '@osd/i18n'; +import { getLocaleInUrl } from '../locale_helper'; /** * A wrapper around a `History` instance that is scoped to a particular base path of the history stack. Behaves @@ -307,6 +309,7 @@ export class ScopedHistory * state. Also forwards events to child listeners with the base path stripped from the location. */ private setupHistoryListener() { + const currentLocale = i18n.getLocale() || 'en'; const unlisten = this.parentHistory.listen((location, action) => { // If the user navigates outside the scope of this basePath, tear it down. if (!location.pathname.startsWith(this.basePath)) { @@ -315,6 +318,14 @@ export class ScopedHistory return; } + const localeValue = getLocaleInUrl(window.location.href); + + if (localeValue !== currentLocale) { + // Force a full page reload + window.location.reload(); + return; + } + /** * Track location keys using the same algorithm the browser uses internally. * - On PUSH, remove all items that came after the current location and append the new location. diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index 71c67f2b967d..63cbc5605561 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -35,7 +35,7 @@ import { RecursiveReadonly } from '@osd/utility-types'; import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; import { MountPoint } from '../types'; import { Capabilities } from './capabilities'; -import { ChromeStart } from '../chrome'; +import { ChromeStart, HeaderVariant } from '../chrome'; import { IContextProvider } from '../context'; import { DocLinksStart } from '../doc_links'; import { HttpStart } from '../http'; @@ -227,6 +227,11 @@ export interface App { */ chromeless?: boolean; + /** + * The application-wide header variant to use. Defaults to `page`. + */ + headerVariant?: HeaderVariant; + /** * A mount function called when the user navigates to this app's route. May have signature of {@link AppMount} or * {@link AppMountDeprecated}. @@ -268,6 +273,12 @@ export interface App { * indicating the application is available within or out of workspace. */ workspaceAvailability?: WorkspaceAvailability; + + /** + * The description of the application. + * Will be displayed in landing page or getting started cards to give more information about the feature. + */ + description?: string; } /** @@ -529,10 +540,19 @@ export interface AppMountParameters { * ``` */ setHeaderActionMenu: (menuMount: MountPoint | undefined) => void; + + setHeaderLeftControls: (menuMount: MountPoint | undefined) => void; + setHeaderCenterControls: (menuMount: MountPoint | undefined) => void; + setHeaderRightControls: (menuMount: MountPoint | undefined) => void; + setHeaderBadgeControls: (menuMount: MountPoint | undefined) => void; + setHeaderDescriptionControls: (menuMount: MountPoint | undefined) => void; + setHeaderBottomControls: (menuMount: MountPoint | undefined) => void; /** * Optional datasource id to pass while mounting app */ dataSourceId?: string; + + optionalRef?: Record>; } /** @@ -820,6 +840,13 @@ export interface ApplicationStart { * An observable that emits the current application id and each subsequent id update. */ currentAppId$: Observable; + + setAppLeftControls: (mount: MountPoint | undefined) => void; + setAppCenterControls: (mount: MountPoint | undefined) => void; + setAppRightControls: (mount: MountPoint | undefined) => void; + setAppBadgeControls: (mount: MountPoint | undefined) => void; + setAppDescriptionControls: (mount: MountPoint | undefined) => void; + setAppBottomControls: (mount: MountPoint | undefined) => void; } /** @internal */ @@ -850,6 +877,19 @@ export interface InternalApplicationStart extends Omit; + /** + * The potential header controls set by the currently mounted app. + * Consumed by the chrome header. + * + * @internal + */ + currentLeftControls$: Observable; + currentCenterControls$: Observable; + currentRightControls$: Observable; + currentBadgeControls$: Observable; + currentDescriptionControls$: Observable; + currentBottomControls$: Observable; + /** * The global history instance, exposed only to Core. * @internal diff --git a/src/core/public/application/ui/app_container.test.tsx b/src/core/public/application/ui/app_container.test.tsx index e9e2caed02e0..071dc4ed9ace 100644 --- a/src/core/public/application/ui/app_container.test.tsx +++ b/src/core/public/application/ui/app_container.test.tsx @@ -43,6 +43,12 @@ describe('AppContainer', () => { const setAppLeaveHandler = jest.fn(); const setAppActionMenu = jest.fn(); const setIsMounting = jest.fn(); + const setAppLeftControls = jest.fn(); + const setAppRightControls = jest.fn(); + const setAppCenterControls = jest.fn(); + const setAppBadgeControls = jest.fn(); + const setAppDescriptionControls = jest.fn(); + const setAppBottomControls = jest.fn(); beforeEach(() => { setAppLeaveHandler.mockClear(); @@ -89,6 +95,12 @@ describe('AppContainer', () => { mounter={mounter} setAppLeaveHandler={setAppLeaveHandler} setAppActionMenu={setAppActionMenu} + setAppLeftControls={setAppLeftControls} + setAppCenterControls={setAppCenterControls} + setAppRightControls={setAppRightControls} + setAppBadgeControls={setAppBadgeControls} + setAppDescriptionControls={setAppDescriptionControls} + setAppBottomControls={setAppBottomControls} setIsMounting={setIsMounting} createScopedHistory={(appPath: string) => // Create a history using the appPath as the current location @@ -130,6 +142,12 @@ describe('AppContainer', () => { mounter={mounter} setAppLeaveHandler={setAppLeaveHandler} setAppActionMenu={setAppActionMenu} + setAppLeftControls={setAppLeftControls} + setAppCenterControls={setAppCenterControls} + setAppRightControls={setAppRightControls} + setAppBadgeControls={setAppBadgeControls} + setAppDescriptionControls={setAppDescriptionControls} + setAppBottomControls={setAppBottomControls} setIsMounting={setIsMounting} createScopedHistory={(appPath: string) => // Create a history using the appPath as the current location @@ -172,6 +190,12 @@ describe('AppContainer', () => { mounter={mounter} setAppLeaveHandler={setAppLeaveHandler} setAppActionMenu={setAppActionMenu} + setAppLeftControls={setAppLeftControls} + setAppCenterControls={setAppCenterControls} + setAppRightControls={setAppRightControls} + setAppBadgeControls={setAppBadgeControls} + setAppDescriptionControls={setAppDescriptionControls} + setAppBottomControls={setAppBottomControls} setIsMounting={setIsMounting} createScopedHistory={(appPath: string) => // Create a history using the appPath as the current location diff --git a/src/core/public/application/ui/app_container.tsx b/src/core/public/application/ui/app_container.tsx index b7d0619a0f9f..8e81db2af34a 100644 --- a/src/core/public/application/ui/app_container.tsx +++ b/src/core/public/application/ui/app_container.tsx @@ -52,6 +52,12 @@ interface Props { appStatus: AppStatus; setAppLeaveHandler: (appId: string, handler: AppLeaveHandler) => void; setAppActionMenu: (appId: string, mount: MountPoint | undefined) => void; + setAppLeftControls: (appId: string, mount: MountPoint | undefined) => void; + setAppCenterControls: (appId: string, mount: MountPoint | undefined) => void; + setAppRightControls: (appId: string, mount: MountPoint | undefined) => void; + setAppBadgeControls: (appId: string, mount: MountPoint | undefined) => void; + setAppDescriptionControls: (appId: string, mount: MountPoint | undefined) => void; + setAppBottomControls: (appId: string, mount: MountPoint | undefined) => void; createScopedHistory: (appUrl: string) => ScopedHistory; setIsMounting: (isMounting: boolean) => void; } @@ -62,6 +68,12 @@ export const AppContainer: FunctionComponent = ({ appPath, setAppLeaveHandler, setAppActionMenu, + setAppLeftControls, + setAppCenterControls, + setAppRightControls, + setAppBadgeControls, + setAppDescriptionControls, + setAppBottomControls, createScopedHistory, appStatus, setIsMounting, @@ -99,6 +111,13 @@ export const AppContainer: FunctionComponent = ({ element: elementRef.current!, onAppLeave: (handler) => setAppLeaveHandler(appId, handler), setHeaderActionMenu: (menuMount) => setAppActionMenu(appId, menuMount), + setHeaderLeftControls: (menuMount) => setAppLeftControls(appId, menuMount), + setHeaderCenterControls: (menuMount) => setAppCenterControls(appId, menuMount), + setHeaderRightControls: (menuMount) => setAppRightControls(appId, menuMount), + setHeaderBadgeControls: (menuMount) => setAppBadgeControls(appId, menuMount), + setHeaderDescriptionControls: (menuMount) => + setAppDescriptionControls(appId, menuMount), + setHeaderBottomControls: (menuMount) => setAppBottomControls(appId, menuMount), })) || null; } catch (e) { // TODO: add error UI @@ -122,6 +141,12 @@ export const AppContainer: FunctionComponent = ({ createScopedHistory, setAppLeaveHandler, setAppActionMenu, + setAppLeftControls, + setAppRightControls, + setAppCenterControls, + setAppBadgeControls, + setAppDescriptionControls, + setAppBottomControls, appPath, setIsMounting, ]); diff --git a/src/core/public/application/ui/app_not_found_screen.tsx b/src/core/public/application/ui/app_not_found_screen.tsx index ac2b2f80b9c2..9575846095c8 100644 --- a/src/core/public/application/ui/app_not_found_screen.tsx +++ b/src/core/public/application/ui/app_not_found_screen.tsx @@ -28,7 +28,7 @@ * under the License. */ -import { EuiEmptyPrompt, EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiPage, EuiPageBody, EuiPageContent, EuiText } from '@elastic/eui'; import React from 'react'; import { FormattedMessage } from '@osd/i18n/react'; @@ -48,12 +48,14 @@ export const AppNotFound = () => ( } body={ -

- -

+ +

+ +

+
} /> diff --git a/src/core/public/application/ui/app_router.tsx b/src/core/public/application/ui/app_router.tsx index 9cfada1f3334..e5de0b406479 100644 --- a/src/core/public/application/ui/app_router.tsx +++ b/src/core/public/application/ui/app_router.tsx @@ -45,6 +45,12 @@ interface Props { appStatuses$: Observable>; setAppLeaveHandler: (appId: string, handler: AppLeaveHandler) => void; setAppActionMenu: (appId: string, mount: MountPoint | undefined) => void; + setAppLeftControls: (appId: string, mount: MountPoint | undefined) => void; + setAppCenterControls: (appId: string, mount: MountPoint | undefined) => void; + setAppRightControls: (appId: string, mount: MountPoint | undefined) => void; + setAppBadgeControls: (appId: string, mount: MountPoint | undefined) => void; + setAppDescriptionControls: (appId: string, mount: MountPoint | undefined) => void; + setAppBottomControls: (appId: string, mount: MountPoint | undefined) => void; setIsMounting: (isMounting: boolean) => void; } @@ -57,6 +63,12 @@ export const AppRouter: FunctionComponent = ({ mounters, setAppLeaveHandler, setAppActionMenu, + setAppLeftControls, + setAppCenterControls, + setAppRightControls, + setAppBadgeControls, + setAppDescriptionControls, + setAppBottomControls, appStatuses$, setIsMounting, }) => { @@ -79,7 +91,19 @@ export const AppRouter: FunctionComponent = ({ appPath={path} appStatus={appStatuses.get(appId) ?? AppStatus.inaccessible} createScopedHistory={createScopedHistory} - {...{ appId, mounter, setAppLeaveHandler, setAppActionMenu, setIsMounting }} + {...{ + appId, + mounter, + setAppLeaveHandler, + setAppLeftControls, + setAppCenterControls, + setAppRightControls, + setAppBadgeControls, + setAppDescriptionControls, + setAppBottomControls, + setAppActionMenu, + setIsMounting, + }} /> )} /> @@ -101,7 +125,18 @@ export const AppRouter: FunctionComponent = ({ appId={id ?? appId} appStatus={appStatuses.get(appId) ?? AppStatus.inaccessible} createScopedHistory={createScopedHistory} - {...{ mounter, setAppLeaveHandler, setAppActionMenu, setIsMounting }} + {...{ + mounter, + setAppLeaveHandler, + setAppLeftControls, + setAppCenterControls, + setAppRightControls, + setAppBadgeControls, + setAppDescriptionControls, + setAppBottomControls, + setAppActionMenu, + setIsMounting, + }} /> ); }} diff --git a/src/core/public/chrome/README.md b/src/core/public/chrome/README.md index 6ec765a3bb0b..7d86aa7f2c1c 100644 --- a/src/core/public/chrome/README.md +++ b/src/core/public/chrome/README.md @@ -6,6 +6,7 @@ - [Nav Links Service](#navlinksservice-) - [Recently Accessed Service](#recentlyaccessedservice-) - [Doc Title Service](#doctitleservice-) +- [Nav Group Service](#chromenavgroupservice-) - [UI](#ui-) ## About : @@ -112,6 +113,31 @@ Gets an Observable of the array of recently accessed history :- chrome.docTitle.change('My application title') chrome.docTitle.change(['My application', 'My section']) ``` +### ChromeNavGroupService: +- Interface : ChromeNavGroup +- Signature : ```navGroup: ChromeNavGroupService``` +- Methods : +Add nav links to group :- + +`addNavLinksToGroup: (group: ChromeNavGroup, navLinks: ChromeRegistrationNavLink[]) => void;` + +Gets an Observable of the array of registered groups :- + +`getNavGroupsMap$: Observable>` +##### Register a new group with a navLink + + ```ts + coreSetup.chrome.navGroup.addNavLinksToGroup( + { + id: 'my-group', + title: 'A demo group', + description: 'description for demo group' + }, + [{ + id: 'nav' + }] + ) + ``` ### UI : ###### consists of tsx/scss files && renders UI components from css Library e.g `````` diff --git a/src/core/public/chrome/chrome_service.mock.ts b/src/core/public/chrome/chrome_service.mock.ts index b6ce429528a7..c63232186672 100644 --- a/src/core/public/chrome/chrome_service.mock.ts +++ b/src/core/public/chrome/chrome_service.mock.ts @@ -36,6 +36,11 @@ import { getLogosMock } from '../../common/mocks'; const createSetupContractMock = () => { return { registerCollapsibleNavHeader: jest.fn(), + navGroup: { + addNavLinksToGroup: jest.fn(), + getNavGroupEnabled: jest.fn(), + registerNavGroupUpdater: jest.fn(), + }, }; }; @@ -66,13 +71,23 @@ const createStartContractMock = () => { registerLeft: jest.fn(), registerCenter: jest.fn(), registerRight: jest.fn(), + registerLeftBottom: jest.fn(), getLeft$: jest.fn(), getCenter$: jest.fn(), getRight$: jest.fn(), + getLeftBottom$: jest.fn(), + }, + navGroup: { + getNavGroupsMap$: jest.fn(() => new BehaviorSubject({})), + getNavGroupEnabled: jest.fn(), + getCurrentNavGroup$: jest.fn(() => new BehaviorSubject(undefined)), + setCurrentNavGroup: jest.fn(), }, setAppTitle: jest.fn(), setIsVisible: jest.fn(), getIsVisible$: jest.fn(), + setHeaderVariant: jest.fn(), + getHeaderVariant$: jest.fn(), addApplicationClass: jest.fn(), removeApplicationClass: jest.fn(), getApplicationClasses$: jest.fn(), @@ -80,6 +95,8 @@ const createStartContractMock = () => { setBadge: jest.fn(), getBreadcrumbs$: jest.fn(), setBreadcrumbs: jest.fn(), + getBreadcrumbsEnricher$: jest.fn(), + setBreadcrumbsEnricher: jest.fn(), getHelpExtension$: jest.fn(), setHelpExtension: jest.fn(), setHelpSupportUrl: jest.fn(), @@ -89,6 +106,7 @@ const createStartContractMock = () => { }; startContract.navLinks.getAll.mockReturnValue([]); startContract.getIsVisible$.mockReturnValue(new BehaviorSubject(false)); + startContract.getHeaderVariant$.mockReturnValue(new BehaviorSubject(undefined)); startContract.getApplicationClasses$.mockReturnValue(new BehaviorSubject(['class-name'])); startContract.getBadge$.mockReturnValue(new BehaviorSubject({} as ChromeBadge)); startContract.getBreadcrumbs$.mockReturnValue(new BehaviorSubject([{} as ChromeBreadcrumb])); diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts index b6599d6cbae8..082ffbfa16ed 100644 --- a/src/core/public/chrome/chrome_service.test.ts +++ b/src/core/public/chrome/chrome_service.test.ts @@ -41,12 +41,20 @@ import { notificationServiceMock } from '../notifications/notifications_service. import { uiSettingsServiceMock } from '../ui_settings/ui_settings_service.mock'; import { ChromeService } from './chrome_service'; import { getAppInfo } from '../application/utils'; -import { overlayServiceMock } from '../mocks'; +import { overlayServiceMock, workspacesServiceMock } from '../mocks'; +import { HeaderVariant } from './constants'; class FakeApp implements App { - public title = `${this.id} App`; + public title: string; public mount = () => () => {}; - constructor(public id: string, public chromeless?: boolean) {} + + constructor( + public id: string, + public chromeless?: boolean, + public headerVariant?: HeaderVariant + ) { + this.title = `${this.id} App`; + } } const store = new Map(); const originalLocalStorage = window.localStorage; @@ -69,6 +77,7 @@ function defaultStartDeps(availableApps?: App[]) { notifications: notificationServiceMock.createStartContract(), uiSettings: uiSettingsServiceMock.createStartContract(), overlays: overlayServiceMock.createStartContract(), + workspaces: workspacesServiceMock.createStartContract(), }; if (availableApps) { @@ -87,6 +96,8 @@ async function start({ }: { options?: any; cspConfigMock?: any; startDeps?: ReturnType } = {}) { const service = new ChromeService(options); + service.setup({ uiSettings: startDeps.uiSettings }); + if (cspConfigMock) { startDeps.injectedMetadata.getCspConfig.mockReturnValue(cspConfigMock); } @@ -116,8 +127,9 @@ describe('setup', () => { const customHeaderMock = React.createElement('TestCustomNavHeader'); const renderMock = jest.fn().mockReturnValue(customHeaderMock); const chrome = new ChromeService({ browserSupportsCsp: true }); + const uiSettings = uiSettingsServiceMock.createSetupContract(); - const chromeSetup = chrome.setup(); + const chromeSetup = chrome.setup({ uiSettings }); chromeSetup.registerCollapsibleNavHeader(renderMock); const chromeStart = await chrome.start(defaultStartDeps()); @@ -132,8 +144,9 @@ describe('setup', () => { const customHeaderMock = React.createElement('TestCustomNavHeader'); const renderMock = jest.fn().mockReturnValue(customHeaderMock); const chrome = new ChromeService({ browserSupportsCsp: true }); + const uiSettings = uiSettingsServiceMock.createSetupContract(); - const chromeSetup = chrome.setup(); + const chromeSetup = chrome.setup({ uiSettings }); // call 1st time chromeSetup.registerCollapsibleNavHeader(renderMock); // call 2nd time @@ -274,6 +287,68 @@ describe('start', () => { }); }); + describe('header variant', () => { + it('emits undefined when no application is mounted', async () => { + const { chrome, service } = await start(); + const promise = chrome.getHeaderVariant$().pipe(toArray()).toPromise(); + + chrome.setHeaderVariant(HeaderVariant.PAGE); + chrome.setHeaderVariant(HeaderVariant.APPLICATION); + chrome.setHeaderVariant(HeaderVariant.PAGE); + service.stop(); + + await expect(promise).resolves.toMatchInlineSnapshot(`Array []`); + }); + + it('emits application-wide value until manually overridden', async () => { + const startDeps = defaultStartDeps([ + new FakeApp('alpha', undefined, HeaderVariant.APPLICATION), + ]); + const { navigateToApp } = startDeps.application; + const { chrome, service } = await start({ startDeps }); + + const promise = chrome.getHeaderVariant$().pipe(toArray()).toPromise(); + + await navigateToApp('alpha'); + + chrome.setHeaderVariant(HeaderVariant.PAGE); + chrome.setHeaderVariant(HeaderVariant.APPLICATION); + + service.stop(); + + await expect(promise).resolves.toMatchInlineSnapshot(` + Array [ + "${HeaderVariant.APPLICATION}", + "${HeaderVariant.PAGE}", + "${HeaderVariant.APPLICATION}", + ] + `); + }); + + it('emits application-wide value after override is removed', async () => { + const startDeps = defaultStartDeps([new FakeApp('alpha', undefined, HeaderVariant.PAGE)]); + const { navigateToApp } = startDeps.application; + const { chrome, service } = await start({ startDeps }); + + const promise = chrome.getHeaderVariant$().pipe(toArray()).toPromise(); + + await navigateToApp('alpha'); + + chrome.setHeaderVariant(HeaderVariant.APPLICATION); + chrome.setHeaderVariant(); + + service.stop(); + + await expect(promise).resolves.toMatchInlineSnapshot(` + Array [ + "${HeaderVariant.PAGE}", + "${HeaderVariant.APPLICATION}", + "${HeaderVariant.PAGE}", + ] + `); + }); + }); + describe('application classes', () => { it('updates/emits the application classes', async () => { const { chrome, service } = await start(); diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 5c458a45e546..10ef3f3a4e19 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -30,8 +30,17 @@ import { EuiBreadcrumb, IconType } from '@elastic/eui'; import React from 'react'; -import { FormattedMessage } from '@osd/i18n/react'; -import { BehaviorSubject, combineLatest, merge, Observable, of, ReplaySubject } from 'rxjs'; +import ReactDOM from 'react-dom'; +import { FormattedMessage, I18nProvider } from '@osd/i18n/react'; +import { + BehaviorSubject, + combineLatest, + merge, + Observable, + of, + ReplaySubject, + Subscription, +} from 'rxjs'; import { flatMap, map, takeUntil } from 'rxjs/operators'; import { parse } from 'url'; import { EuiLink } from '@elastic/eui'; @@ -42,17 +51,22 @@ import { HttpStart } from '../http'; import { InjectedMetadataStart } from '../injected_metadata'; import { NotificationsStart } from '../notifications'; import { IUiSettingsClient } from '../ui_settings'; -import { OPENSEARCH_DASHBOARDS_ASK_OPENSEARCH_LINK } from './constants'; +import { HeaderVariant, OPENSEARCH_DASHBOARDS_ASK_OPENSEARCH_LINK } from './constants'; import { ChromeDocTitle, DocTitleService } from './doc_title'; import { ChromeNavControls, NavControlsService } from './nav_controls'; import { ChromeNavLinks, NavLinksService, ChromeNavLink } from './nav_links'; import { ChromeRecentlyAccessed, RecentlyAccessedService } from './recently_accessed'; import { Header } from './ui'; -import { ChromeHelpExtensionMenuLink } from './ui/header/header_help_menu'; -import { Branding } from '../'; +import { ChromeHelpExtensionMenuLink, HeaderHelpMenu } from './ui/header/header_help_menu'; +import { Branding, WorkspacesStart } from '../'; import { getLogos } from '../../common'; import type { Logos } from '../../common/types'; import { OverlayStart } from '../overlays'; +import { + ChromeNavGroupService, + ChromeNavGroupServiceSetupContract, + ChromeNavGroupServiceStartContract, +} from './nav_group'; export { ChromeNavControls, ChromeRecentlyAccessed, ChromeDocTitle }; @@ -68,6 +82,9 @@ export interface ChromeBadge { /** @public */ export type ChromeBreadcrumb = EuiBreadcrumb; +/** @public */ +export type ChromeBreadcrumbEnricher = (breadcrumbs: ChromeBreadcrumb[]) => ChromeBreadcrumb[]; + /** @public */ export type ChromeBranding = Branding; @@ -91,7 +108,11 @@ interface ConstructorParams { browserSupportsCsp: boolean; } -interface StartDeps { +export interface SetupDeps { + uiSettings: IUiSettingsClient; +} + +export interface StartDeps { application: InternalApplicationStart; docLinks: DocLinksStart; http: HttpStart; @@ -99,6 +120,7 @@ interface StartDeps { notifications: NotificationsStart; uiSettings: IUiSettingsClient; overlays: OverlayStart; + workspaces: WorkspacesStart; } type CollapsibleNavHeaderRender = () => JSX.Element | null; @@ -107,11 +129,16 @@ type CollapsibleNavHeaderRender = () => JSX.Element | null; export class ChromeService { private isVisible$!: Observable; private isForceHidden$!: BehaviorSubject; + private headerVariant$!: Observable; + private headerVariantOverride$!: BehaviorSubject; private readonly stop$ = new ReplaySubject(1); private readonly navControls = new NavControlsService(); private readonly navLinks = new NavLinksService(); private readonly recentlyAccessed = new RecentlyAccessedService(); private readonly docTitle = new DocTitleService(); + private readonly navGroup = new ChromeNavGroupService(); + private useUpdatedHeader = false; + private updatedHeaderSubscription: Subscription | undefined; private collapsibleNavHeaderRender?: CollapsibleNavHeaderRender; constructor(private readonly params: ConstructorParams) {} @@ -148,7 +175,30 @@ export class ChromeService { ); } - public setup() { + private initHeaderVariant(application: StartDeps['application']) { + this.headerVariantOverride$ = new BehaviorSubject(undefined); + + const appHeaderVariant$ = application.currentAppId$.pipe( + flatMap((appId) => + application.applications$.pipe( + map( + (applications) => + (appId && applications.has(appId) && applications.get(appId)!.headerVariant) as + | HeaderVariant + | undefined + ) + ) + ) + ); + + this.headerVariant$ = combineLatest([appHeaderVariant$, this.headerVariantOverride$]).pipe( + map(([appHeaderVariant, headerVariantOverride]) => headerVariantOverride || appHeaderVariant), + takeUntil(this.stop$) + ); + } + + public setup({ uiSettings }: SetupDeps): ChromeSetup { + const navGroup = this.navGroup.setup({ uiSettings }); return { registerCollapsibleNavHeader: (render: CollapsibleNavHeaderRender) => { if (this.collapsibleNavHeaderRender) { @@ -159,6 +209,7 @@ export class ChromeService { } this.collapsibleNavHeaderRender = render; }, + navGroup, }; } @@ -170,13 +221,24 @@ export class ChromeService { notifications, uiSettings, overlays, + workspaces, }: StartDeps): Promise { this.initVisibility(application); + this.initHeaderVariant(application); + + this.updatedHeaderSubscription = uiSettings + .get$('home:useNewHomePage', false) + .subscribe((value) => { + this.useUpdatedHeader = value; + }); const appTitle$ = new BehaviorSubject('Overview'); const applicationClasses$ = new BehaviorSubject>(new Set()); const helpExtension$ = new BehaviorSubject(undefined); const breadcrumbs$ = new BehaviorSubject([]); + const breadcrumbsEnricher$ = new BehaviorSubject( + undefined + ); const badge$ = new BehaviorSubject(undefined); const customNavLink$ = new BehaviorSubject(undefined); const helpSupportUrl$ = new BehaviorSubject(OPENSEARCH_DASHBOARDS_ASK_OPENSEARCH_LINK); @@ -185,8 +247,14 @@ export class ChromeService { const navControls = this.navControls.start(); const navLinks = this.navLinks.start({ application, http }); - const recentlyAccessed = await this.recentlyAccessed.start({ http }); + const recentlyAccessed = await this.recentlyAccessed.start({ http, workspaces }); const docTitle = this.docTitle.start({ document: window.document }); + const navGroup = await this.navGroup.start({ + navLinks, + application, + breadcrumbsEnricher$, + workspaces, + }); // erase chrome fields from a previous app while switching to a next app application.currentAppId$.subscribe(() => { @@ -205,6 +273,29 @@ export class ChromeService { const logos = getLogos(injectedMetadata.getBranding(), http.basePath.serverBasePath); + // Add Help menu + if (this.useUpdatedHeader) { + navControls.registerLeftBottom({ + order: 9000, + mount: (element: HTMLElement) => { + ReactDOM.render( + + + , + element + ); + return () => ReactDOM.unmountComponentAtNode(element); + }, + }); + } + const isIE = () => { const ua = window.navigator.userAgent; const msie = ua.indexOf('MSIE '); // IE 10 or older @@ -255,6 +346,7 @@ export class ChromeService { recentlyAccessed, docTitle, logos, + navGroup, getHeaderComponent: () => (
), @@ -295,6 +397,10 @@ export class ChromeService { setIsVisible: (isVisible: boolean) => this.isForceHidden$.next(!isVisible), + getHeaderVariant$: () => this.headerVariant$, + + setHeaderVariant: (variant?: HeaderVariant) => this.headerVariantOverride$.next(variant), + getApplicationClasses$: () => applicationClasses$.pipe( map((set) => [...set]), @@ -325,6 +431,12 @@ export class ChromeService { breadcrumbs$.next(newBreadcrumbs); }, + getBreadcrumbsEnricher$: () => breadcrumbsEnricher$.pipe(takeUntil(this.stop$)), + + setBreadcrumbsEnricher: (enricher: ChromeBreadcrumbEnricher) => { + breadcrumbsEnricher$.next(enricher); + }, + getHelpExtension$: () => helpExtension$.pipe(takeUntil(this.stop$)), setHelpExtension: (helpExtension?: ChromeHelpExtension) => { @@ -345,6 +457,8 @@ export class ChromeService { public stop() { this.navLinks.stop(); + this.navGroup.stop(); + this.updatedHeaderSubscription?.unsubscribe(); this.stop$.next(); } } @@ -361,6 +475,7 @@ export class ChromeService { */ export interface ChromeSetup { registerCollapsibleNavHeader: (render: CollapsibleNavHeaderRender) => void; + navGroup: ChromeNavGroupServiceSetupContract; } /** @@ -398,6 +513,8 @@ export interface ChromeStart { recentlyAccessed: ChromeRecentlyAccessed; /** {@inheritdoc ChromeDocTitle} */ docTitle: ChromeDocTitle; + /** {@inheritdoc NavGroupService} */ + navGroup: ChromeNavGroupServiceStartContract; /** {@inheritdoc Logos} */ readonly logos: Logos; @@ -422,6 +539,16 @@ export interface ChromeStart { */ setIsVisible(isVisible: boolean): void; + /** + * Get an observable of the current header variant. + */ + getHeaderVariant$(): Observable; + + /** + * Set or unset the temporary variant for the header. + */ + setHeaderVariant(variant?: HeaderVariant): void; + /** * Get the current set of classNames that will be set on the application container. */ @@ -457,6 +584,16 @@ export interface ChromeStart { */ setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[]): void; + /** + * Get an observable of the current breadcrumbs enricher + */ + getBreadcrumbsEnricher$(): Observable; + + /** + * Override the current ChromeBreadcrumbEnricher + */ + setBreadcrumbsEnricher(newBreadcrumbsEnricher: ChromeBreadcrumbEnricher | undefined): void; + /** * Get an observable of the current custom nav link */ diff --git a/src/core/public/chrome/constants.ts b/src/core/public/chrome/constants.ts index 4f98257ea5f8..ce65af852e07 100644 --- a/src/core/public/chrome/constants.ts +++ b/src/core/public/chrome/constants.ts @@ -37,3 +37,17 @@ export enum RightNavigationOrder { Settings = 10, DevTool = 20, } + +export enum HeaderControlsContainer { + LEFT = 'left', + CENTER = 'center', + RIGHT = 'right', + BADGE = 'badge', + DESCRIPTION = 'description', + BOTTOM = 'bottom', +} + +export enum HeaderVariant { + PAGE = 'page', + APPLICATION = 'application', +} diff --git a/src/core/public/chrome/index.ts b/src/core/public/chrome/index.ts index eb92ccdc6ba3..5403634705c4 100644 --- a/src/core/public/chrome/index.ts +++ b/src/core/public/chrome/index.ts @@ -44,9 +44,20 @@ export { ChromeHelpExtensionMenuDocumentationLink, ChromeHelpExtensionMenuGitHubLink, } from './ui/header/header_help_menu'; -export { NavType, RightNavigationButton, RightNavigationButtonProps } from './ui'; +export { + NavType, + RightNavigationButton, + RightNavigationButtonProps, + createRecentNavLink, +} from './ui'; export { ChromeNavLink, ChromeNavLinks, ChromeNavLinkUpdateableFields } from './nav_links'; -export { ChromeRecentlyAccessed, ChromeRecentlyAccessedHistoryItem } from './recently_accessed'; +export { + ChromeRecentlyAccessed, + ChromeRecentlyAccessedHistoryItem, + PersistedLog, +} from './recently_accessed'; export { ChromeNavControl, ChromeNavControls } from './nav_controls'; export { ChromeDocTitle } from './doc_title'; -export { RightNavigationOrder } from './constants'; +export { RightNavigationOrder, HeaderVariant } from './constants'; +export { ChromeRegistrationNavLink, ChromeNavGroupUpdater, NavGroupItemInMap } from './nav_group'; +export { fulfillRegistrationLinksToChromeNavLinks, LinkItemType, getSortedNavLinks } from './utils'; diff --git a/src/core/public/chrome/nav_controls/nav_controls_service.test.ts b/src/core/public/chrome/nav_controls/nav_controls_service.test.ts index 6e2a71537e17..a3d73168c789 100644 --- a/src/core/public/chrome/nav_controls/nav_controls_service.test.ts +++ b/src/core/public/chrome/nav_controls/nav_controls_service.test.ts @@ -143,4 +143,24 @@ describe('RecentlyAccessed#start()', () => { ]); }); }); + + describe('expanded left bottom controls', () => { + it('allows registration', async () => { + const navControls = getStart(); + const nc = { mount: jest.fn() }; + navControls.registerLeftBottom(nc); + expect(await navControls.getLeftBottom$().pipe(take(1)).toPromise()).toEqual([nc]); + }); + + it('sorts controls by order property', async () => { + const navControls = getStart(); + const nc1 = { mount: jest.fn(), order: 10 }; + const nc2 = { mount: jest.fn(), order: 0 }; + const nc3 = { mount: jest.fn(), order: 20 }; + navControls.registerLeftBottom(nc1); + navControls.registerLeftBottom(nc2); + navControls.registerLeftBottom(nc3); + expect(await navControls.getLeftBottom$().pipe(take(1)).toPromise()).toEqual([nc2, nc1, nc3]); + }); + }); }); diff --git a/src/core/public/chrome/nav_controls/nav_controls_service.ts b/src/core/public/chrome/nav_controls/nav_controls_service.ts index 57298dac39ff..19135cbf866c 100644 --- a/src/core/public/chrome/nav_controls/nav_controls_service.ts +++ b/src/core/public/chrome/nav_controls/nav_controls_service.ts @@ -62,12 +62,16 @@ export interface ChromeNavControls { registerRight(navControl: ChromeNavControl): void; /** Register a nav control to be presented on the top-center side of the chrome header. */ registerCenter(navControl: ChromeNavControl): void; + /** Register a nav control to be presented on the left-bottom side of the left navigation. */ + registerLeftBottom(navControl: ChromeNavControl): void; /** @internal */ getLeft$(): Observable; /** @internal */ getRight$(): Observable; /** @internal */ getCenter$(): Observable; + /** @internal */ + getLeftBottom$(): Observable; } /** @internal */ @@ -82,6 +86,7 @@ export class NavControlsService { const navControlsExpandedCenter$ = new BehaviorSubject>( new Set() ); + const navControlsLeftBottom$ = new BehaviorSubject>(new Set()); return { // In the future, registration should be moved to the setup phase. This @@ -105,6 +110,11 @@ export class NavControlsService { new Set([...navControlsExpandedCenter$.value.values(), navControl]) ), + registerLeftBottom: (navControl: ChromeNavControl) => + navControlsLeftBottom$.next( + new Set([...navControlsLeftBottom$.value.values(), navControl]) + ), + getLeft$: () => navControlsLeft$.pipe( map((controls) => sortBy([...controls.values()], 'order')), @@ -130,6 +140,11 @@ export class NavControlsService { map((controls) => sortBy([...controls.values()], 'order')), takeUntil(this.stop$) ), + getLeftBottom$: () => + navControlsLeftBottom$.pipe( + map((controls) => sortBy([...controls.values()], 'order')), + takeUntil(this.stop$) + ), }; } diff --git a/src/core/public/chrome/nav_group/index.ts b/src/core/public/chrome/nav_group/index.ts new file mode 100644 index 000000000000..3ff83b07e95c --- /dev/null +++ b/src/core/public/chrome/nav_group/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export { + ChromeNavGroupService, + ChromeNavGroupServiceSetupContract, + ChromeNavGroupServiceStartContract, + ChromeRegistrationNavLink, + NavGroupItemInMap, + ChromeNavGroupUpdater, +} from './nav_group_service'; diff --git a/src/core/public/chrome/nav_group/nav_group_service.test.ts b/src/core/public/chrome/nav_group/nav_group_service.test.ts new file mode 100644 index 000000000000..06712058fb23 --- /dev/null +++ b/src/core/public/chrome/nav_group/nav_group_service.test.ts @@ -0,0 +1,639 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as Rx from 'rxjs'; +import { first } from 'rxjs/operators'; +import { + ChromeNavGroupService, + ChromeRegistrationNavLink, + CURRENT_NAV_GROUP_ID, +} from './nav_group_service'; +import { uiSettingsServiceMock } from '../../ui_settings/ui_settings_service.mock'; +import { NavLinksService } from '../nav_links'; +import { applicationServiceMock, httpServiceMock, workspacesServiceMock } from '../../mocks'; +import { AppCategory } from 'opensearch-dashboards/public'; +import { DEFAULT_NAV_GROUPS, NavGroupStatus, ALL_USE_CASE_ID } from '../../'; +import { ChromeBreadcrumbEnricher } from '../chrome_service'; + +const mockedGroupFoo = { + id: 'foo', + title: 'foo', + description: 'foo', +}; + +const mockedGroupBar = { + id: 'bar', + title: 'bar', + description: 'bar', +}; + +const mockedNavLinkFoo: ChromeRegistrationNavLink = { + id: 'foo', + order: 10, +}; + +const mockedNavLinkBar: ChromeRegistrationNavLink = { + id: 'bar', + order: 20, +}; + +const mockedCategoryFoo: AppCategory = { + id: 'foo', + order: 15, + label: 'foo', +}; + +const mockedCategoryBar: AppCategory = { + id: 'bar', + order: 25, + label: 'bar', +}; + +const mockedHttpService = httpServiceMock.createStartContract(); +const mockedApplicationService = applicationServiceMock.createInternalStartContract(); +const mockWorkspaceService = workspacesServiceMock.createStartContract(); +const mockedNavLink = new NavLinksService(); +const mockedNavLinkService = mockedNavLink.start({ + http: mockedHttpService, + application: mockedApplicationService, +}); + +const mockedGetNavLinks = jest.fn(); +jest.spyOn(mockedNavLinkService, 'getNavLinks$').mockImplementation(mockedGetNavLinks); +mockedGetNavLinks.mockReturnValue( + new Rx.BehaviorSubject([ + { + id: 'foo', + }, + { + id: 'bar', + }, + { + id: 'foo-in-category-foo', + }, + { + id: 'foo-in-category-bar', + }, + { + id: 'bar-in-category-foo', + }, + { + id: 'bar-in-category-bar', + }, + { + id: 'link-with-parent-nav-link-id', + }, + ]) +); + +interface LooseObject { + [key: string]: any; +} + +// Mock sessionStorage +const sessionStorageMock = (() => { + let store = {} as LooseObject; + return { + getItem(key: string) { + return store[key] || null; + }, + setItem(key: string, value: string) { + store[key] = value.toString(); + }, + removeItem(key: string) { + delete store[key]; + }, + clear() { + store = {}; + }, + }; +})(); + +Object.defineProperty(window, 'sessionStorage', { value: sessionStorageMock }); + +describe('ChromeNavGroupService#setup()', () => { + it('should be able to `addNavLinksToGroup`', async () => { + const warnMock = jest.fn(); + jest.spyOn(console, 'warn').mockImplementation(warnMock); + const uiSettings = uiSettingsServiceMock.createSetupContract(); + const chromeNavGroupService = new ChromeNavGroupService(); + const chromeNavGroupServiceSetup = chromeNavGroupService.setup({ uiSettings }); + + chromeNavGroupServiceSetup.addNavLinksToGroup(mockedGroupFoo, [mockedGroupFoo, mockedGroupBar]); + chromeNavGroupServiceSetup.addNavLinksToGroup(mockedGroupBar, [mockedGroupBar]); + const chromeNavGroupServiceStart = await chromeNavGroupService.start({ + navLinks: mockedNavLinkService, + application: mockedApplicationService, + breadcrumbsEnricher$: new Rx.BehaviorSubject(undefined), + workspaces: workspacesServiceMock.createStartContract(), + }); + const groupsMap = await chromeNavGroupServiceStart.getNavGroupsMap$().pipe(first()).toPromise(); + expect(groupsMap[mockedGroupFoo.id].navLinks.length).toEqual(2); + expect(groupsMap[mockedGroupBar.id].navLinks.length).toEqual(1); + expect(groupsMap[mockedGroupFoo.id].id).toEqual(mockedGroupFoo.id); + expect(warnMock).toBeCalledTimes(0); + }); + + it('should output warning message if `addNavLinksToGroup` with same group id and navLink id', async () => { + const warnMock = jest.fn(); + jest.spyOn(console, 'warn').mockImplementation(warnMock); + const uiSettings = uiSettingsServiceMock.createSetupContract(); + const chromeNavGroupService = new ChromeNavGroupService(); + const chromeNavGroupServiceSetup = chromeNavGroupService.setup({ uiSettings }); + + chromeNavGroupServiceSetup.addNavLinksToGroup(mockedGroupFoo, [ + mockedNavLinkFoo, + mockedGroupFoo, + ]); + chromeNavGroupServiceSetup.addNavLinksToGroup(mockedGroupBar, [mockedGroupBar]); + const chromeNavGroupServiceStart = await chromeNavGroupService.start({ + navLinks: mockedNavLinkService, + application: mockedApplicationService, + breadcrumbsEnricher$: new Rx.BehaviorSubject(undefined), + workspaces: workspacesServiceMock.createStartContract(), + }); + const groupsMap = await chromeNavGroupServiceStart.getNavGroupsMap$().pipe(first()).toPromise(); + expect(groupsMap[mockedGroupFoo.id].navLinks.length).toEqual(1); + expect(groupsMap[mockedGroupBar.id].navLinks.length).toEqual(1); + expect(warnMock).toBeCalledTimes(1); + expect(warnMock).toBeCalledWith( + `[ChromeService] Navlink of ${mockedGroupFoo.id} has already been registered in group ${mockedGroupFoo.id}` + ); + }); + + it('should return navGroupEnabled from ui settings', () => { + const chrome = new ChromeNavGroupService(); + const uiSettings = uiSettingsServiceMock.createSetupContract(); + uiSettings.get$.mockImplementation(() => new Rx.BehaviorSubject(true)); + + const chromeSetup = chrome.setup({ uiSettings }); + expect(chromeSetup.getNavGroupEnabled()).toBe(true); + }); +}); + +describe('ChromeNavGroupService#start()', () => { + it('should be able to get the groups registered through addNavLinksToGroups with sorted order', async () => { + const chromeNavGroupService = new ChromeNavGroupService(); + const uiSettings = uiSettingsServiceMock.createSetupContract(); + const chromeNavGroupServiceSetup = chromeNavGroupService.setup({ uiSettings }); + + chromeNavGroupServiceSetup.addNavLinksToGroup(mockedGroupFoo, [ + mockedNavLinkFoo, + { + ...mockedNavLinkFoo, + id: 'foo-in-category-foo', + category: mockedCategoryFoo, + }, + { + ...mockedNavLinkBar, + id: 'bar-in-category-foo', + category: mockedCategoryFoo, + }, + { + ...mockedNavLinkFoo, + id: 'foo-in-category-bar', + category: mockedCategoryBar, + }, + { + ...mockedNavLinkBar, + id: 'bar-in-category-bar', + category: mockedCategoryBar, + }, + mockedNavLinkBar, + { + id: 'link-with-parent-nav-link-id', + parentNavLinkId: 'not-exist-id', + }, + ]); + chromeNavGroupServiceSetup.addNavLinksToGroup(mockedGroupBar, [mockedNavLinkBar]); + + const chromeStart = await chromeNavGroupService.start({ + navLinks: mockedNavLinkService, + application: mockedApplicationService, + breadcrumbsEnricher$: new Rx.BehaviorSubject(undefined), + workspaces: workspacesServiceMock.createStartContract(), + }); + + const groupsMap = await chromeStart.getNavGroupsMap$().pipe(first()).toPromise(); + + expect(Object.keys(groupsMap).length).toEqual(2); + expect(groupsMap[mockedGroupFoo.id].navLinks.map((item) => item.id)).toEqual([ + 'foo', + 'foo-in-category-foo', + 'bar-in-category-foo', + 'bar', + 'foo-in-category-bar', + 'bar-in-category-bar', + 'link-with-parent-nav-link-id', + ]); + expect(groupsMap[mockedGroupBar.id].navLinks.length).toEqual(1); + }); + + it('should return navGroupEnabled from ui settings', async () => { + const chromeNavGroupService = new ChromeNavGroupService(); + const uiSettings = uiSettingsServiceMock.createSetupContract(); + uiSettings.get$.mockImplementation(() => new Rx.BehaviorSubject(true)); + chromeNavGroupService.setup({ uiSettings }); + const chromeNavGroupServiceStart = await chromeNavGroupService.start({ + navLinks: mockedNavLinkService, + application: mockedApplicationService, + breadcrumbsEnricher$: new Rx.BehaviorSubject(undefined), + workspaces: workspacesServiceMock.createStartContract(), + }); + + expect(chromeNavGroupServiceStart.getNavGroupEnabled()).toBe(true); + }); + + it('should not update navGroupEnabled after stopped', async () => { + const uiSettings = uiSettingsServiceMock.createSetupContract(); + const navGroupEnabled$ = new Rx.BehaviorSubject(true); + uiSettings.get$.mockImplementation(() => navGroupEnabled$); + + const chromeNavGroupService = new ChromeNavGroupService(); + chromeNavGroupService.setup({ uiSettings }); + const chromeNavGroupServiceStart = await chromeNavGroupService.start({ + navLinks: mockedNavLinkService, + application: mockedApplicationService, + breadcrumbsEnricher$: new Rx.BehaviorSubject(undefined), + workspaces: workspacesServiceMock.createStartContract(), + }); + + navGroupEnabled$.next(false); + expect(chromeNavGroupServiceStart.getNavGroupEnabled()).toBe(false); + + chromeNavGroupService.stop(); + navGroupEnabled$.next(true); + expect(chromeNavGroupServiceStart.getNavGroupEnabled()).toBe(false); + }); + + it('should able to set current nav group', async () => { + const uiSettings = uiSettingsServiceMock.createSetupContract(); + const navGroupEnabled$ = new Rx.BehaviorSubject(true); + uiSettings.get$.mockImplementation(() => navGroupEnabled$); + + const chromeNavGroupService = new ChromeNavGroupService(); + const chromeNavGroupServiceSetup = chromeNavGroupService.setup({ uiSettings }); + + chromeNavGroupServiceSetup.addNavLinksToGroup( + { + id: 'foo', + title: 'foo title', + description: 'foo description', + }, + [mockedNavLinkFoo] + ); + + const chromeNavGroupServiceStart = await chromeNavGroupService.start({ + navLinks: mockedNavLinkService, + application: mockedApplicationService, + breadcrumbsEnricher$: new Rx.BehaviorSubject(undefined), + workspaces: workspacesServiceMock.createStartContract(), + }); + + // set an existing nav group id + chromeNavGroupServiceStart.setCurrentNavGroup('foo'); + + expect(sessionStorageMock.getItem(CURRENT_NAV_GROUP_ID)).toEqual('foo'); + + let currentNavGroup = await chromeNavGroupServiceStart + .getCurrentNavGroup$() + .pipe(first()) + .toPromise(); + + expect(currentNavGroup?.id).toEqual('foo'); + expect(currentNavGroup?.title).toEqual('foo title'); + + // set a invalid nav group id + chromeNavGroupServiceStart.setCurrentNavGroup('bar'); + currentNavGroup = await chromeNavGroupServiceStart + .getCurrentNavGroup$() + .pipe(first()) + .toPromise(); + + expect(sessionStorageMock.getItem(CURRENT_NAV_GROUP_ID)).toBeFalsy(); + expect(currentNavGroup).toBeUndefined(); + + // reset current nav group + chromeNavGroupServiceStart.setCurrentNavGroup(undefined); + currentNavGroup = await chromeNavGroupServiceStart + .getCurrentNavGroup$() + .pipe(first()) + .toPromise(); + + expect(sessionStorageMock.getItem(CURRENT_NAV_GROUP_ID)).toBeFalsy(); + expect(currentNavGroup).toBeUndefined(); + }); + + it('should set current nav group automatically if application only belongs 1 nav group', async () => { + const uiSettings = uiSettingsServiceMock.createSetupContract(); + const navGroupEnabled$ = new Rx.BehaviorSubject(true); + uiSettings.get$.mockImplementation(() => navGroupEnabled$); + + const chromeNavGroupService = new ChromeNavGroupService(); + const chromeNavGroupServiceSetup = chromeNavGroupService.setup({ uiSettings }); + + chromeNavGroupServiceSetup.addNavLinksToGroup( + { + id: 'foo-group', + title: 'fooGroupTitle', + description: 'foo description', + }, + [mockedNavLinkFoo] + ); + + chromeNavGroupServiceSetup.addNavLinksToGroup( + { + id: 'bar-group', + title: 'barGroupTitle', + description: 'bar description', + }, + [mockedNavLinkFoo, mockedNavLinkBar] + ); + + const chromeNavGroupServiceStart = await chromeNavGroupService.start({ + navLinks: mockedNavLinkService, + application: mockedApplicationService, + breadcrumbsEnricher$: new Rx.BehaviorSubject(undefined), + workspaces: workspacesServiceMock.createStartContract(), + }); + + mockedApplicationService.navigateToApp(mockedNavLinkFoo.id); + let currentNavGroup = await chromeNavGroupServiceStart + .getCurrentNavGroup$() + .pipe(first()) + .toPromise(); + + expect(currentNavGroup).toBeFalsy(); + + // reset + chromeNavGroupServiceStart.setCurrentNavGroup(undefined); + + mockedApplicationService.navigateToApp(mockedNavLinkBar.id); + + currentNavGroup = await chromeNavGroupServiceStart + .getCurrentNavGroup$() + .pipe(first()) + .toPromise(); + + expect(currentNavGroup?.id).toEqual('bar-group'); + expect(currentNavGroup?.title).toEqual('barGroupTitle'); + }); + + it('should be able to find the right nav group when visible nav group is all', async () => { + const uiSettings = uiSettingsServiceMock.createSetupContract(); + const navGroupEnabled$ = new Rx.BehaviorSubject(true); + uiSettings.get$.mockImplementation(() => navGroupEnabled$); + + const chromeNavGroupService = new ChromeNavGroupService(); + const chromeNavGroupServiceSetup = chromeNavGroupService.setup({ uiSettings }); + + chromeNavGroupServiceSetup.addNavLinksToGroup( + { + id: ALL_USE_CASE_ID, + title: 'fooGroupTitle', + description: 'foo description', + }, + [mockedNavLinkFoo] + ); + + chromeNavGroupServiceSetup.addNavLinksToGroup( + { + id: 'bar-group', + title: 'barGroupTitle', + description: 'bar description', + status: NavGroupStatus.Hidden, + }, + [mockedNavLinkFoo, mockedNavLinkBar] + ); + + const chromeNavGroupServiceStart = await chromeNavGroupService.start({ + navLinks: mockedNavLinkService, + application: mockedApplicationService, + breadcrumbsEnricher$: new Rx.BehaviorSubject(undefined), + workspaces: workspacesServiceMock.createStartContract(), + }); + mockedApplicationService.navigateToApp(mockedNavLinkBar.id); + + const currentNavGroup = await chromeNavGroupServiceStart + .getCurrentNavGroup$() + .pipe(first()) + .toPromise(); + + expect(currentNavGroup?.id).toEqual('bar-group'); + }); + + it('should erase current nav group if application can not be found in any of the visible nav groups', async () => { + const uiSettings = uiSettingsServiceMock.createSetupContract(); + const navGroupEnabled$ = new Rx.BehaviorSubject(true); + uiSettings.get$.mockImplementation(() => navGroupEnabled$); + + const chromeNavGroupService = new ChromeNavGroupService(); + const chromeNavGroupServiceSetup = chromeNavGroupService.setup({ uiSettings }); + + chromeNavGroupServiceSetup.addNavLinksToGroup( + { + id: 'foo-group', + title: 'fooGroupTitle', + description: 'foo description', + }, + [mockedNavLinkFoo] + ); + + chromeNavGroupServiceSetup.addNavLinksToGroup( + { + id: 'bar-group', + title: 'barGroupTitle', + description: 'bar description', + status: NavGroupStatus.Hidden, + }, + [mockedNavLinkFoo, mockedNavLinkBar] + ); + + const chromeNavGroupServiceStart = await chromeNavGroupService.start({ + navLinks: mockedNavLinkService, + application: mockedApplicationService, + breadcrumbsEnricher$: new Rx.BehaviorSubject(undefined), + workspaces: workspacesServiceMock.createStartContract(), + }); + + chromeNavGroupServiceStart.setCurrentNavGroup('foo-group'); + + mockedApplicationService.navigateToApp(mockedNavLinkBar.id); + const currentNavGroup = await chromeNavGroupServiceStart + .getCurrentNavGroup$() + .pipe(first()) + .toPromise(); + + expect(currentNavGroup).toBeFalsy(); + }); + + it('should set breadcrumbs enricher when nav group is enabled', async () => { + const uiSettings = uiSettingsServiceMock.createSetupContract(); + const navGroupEnabled$ = new Rx.BehaviorSubject(true); + uiSettings.get$.mockImplementation(() => navGroupEnabled$); + + const chromeNavGroupService = new ChromeNavGroupService(); + const chromeNavGroupServiceSetup = chromeNavGroupService.setup({ uiSettings }); + + chromeNavGroupServiceSetup.addNavLinksToGroup( + { + id: 'foo-group', + title: 'fooGroupTitle', + description: 'foo description', + }, + [mockedNavLinkFoo] + ); + + chromeNavGroupServiceSetup.addNavLinksToGroup( + { + id: 'bar-group', + title: 'barGroupTitle', + description: 'bar description', + }, + [mockedNavLinkFoo, mockedNavLinkBar] + ); + + const breadcrumbsEnricher$ = new Rx.BehaviorSubject( + undefined + ); + + const chromeNavGroupServiceStart = await chromeNavGroupService.start({ + navLinks: mockedNavLinkService, + application: mockedApplicationService, + breadcrumbsEnricher$, + workspaces: mockWorkspaceService, + }); + + chromeNavGroupServiceStart.setCurrentNavGroup('bar-group'); + + expect(breadcrumbsEnricher$.getValue()).toBeTruthy(); + + const breadcrumbs = [{ text: 'test' }]; + const enrichedBreadcrumbs = breadcrumbsEnricher$.getValue()?.(breadcrumbs); + + // home -> bar-group -> test + expect(enrichedBreadcrumbs).toHaveLength(3); + + // reset current nav group + chromeNavGroupServiceStart.setCurrentNavGroup(undefined); + expect(breadcrumbsEnricher$.getValue()).toBeFalsy(); + }); + + it('should NOT set breadcrumbs enricher when in a workspace', async () => { + const uiSettings = uiSettingsServiceMock.createSetupContract(); + const navGroupEnabled$ = new Rx.BehaviorSubject(true); + uiSettings.get$.mockImplementation(() => navGroupEnabled$); + + const chromeNavGroupService = new ChromeNavGroupService(); + const chromeNavGroupServiceSetup = chromeNavGroupService.setup({ uiSettings }); + + chromeNavGroupServiceSetup.addNavLinksToGroup( + { + id: 'foo-group', + title: 'fooGroupTitle', + description: 'foo description', + }, + [mockedNavLinkFoo] + ); + + chromeNavGroupServiceSetup.addNavLinksToGroup( + { + id: 'bar-group', + title: 'barGroupTitle', + description: 'bar description', + }, + [mockedNavLinkFoo, mockedNavLinkBar] + ); + + const breadcrumbsEnricher$ = new Rx.BehaviorSubject( + undefined + ); + mockWorkspaceService.currentWorkspace$.next({ id: 'test', name: 'test workspace' }); + + const chromeNavGroupServiceStart = await chromeNavGroupService.start({ + navLinks: mockedNavLinkService, + application: mockedApplicationService, + breadcrumbsEnricher$, + workspaces: mockWorkspaceService, + }); + + chromeNavGroupServiceStart.setCurrentNavGroup('bar-group'); + + expect(breadcrumbsEnricher$.getValue()).toBeFalsy(); + }); +}); + +describe('nav group updater', () => { + it('should emit updated nav group after nav group updater called', async () => { + const navGroup = new ChromeNavGroupService(); + const uiSettings = uiSettingsServiceMock.createSetupContract(); + uiSettings.get$.mockImplementation(() => new Rx.BehaviorSubject(true)); + + const navGroupSetup = navGroup.setup({ uiSettings }); + navGroupSetup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.dataAdministration, [ + { + id: 'foo', + }, + ]); + const navGroupStart = await navGroup.start({ + navLinks: mockedNavLinkService, + application: mockedApplicationService, + breadcrumbsEnricher$: new Rx.BehaviorSubject(undefined), + workspaces: workspacesServiceMock.createStartContract(), + }); + + expect(await navGroupStart.getNavGroupsMap$().pipe(first()).toPromise()).toEqual({ + dataAdministration: expect.not.objectContaining({ + status: expect.anything, + }), + }); + navGroupSetup.registerNavGroupUpdater( + new Rx.BehaviorSubject(() => ({ + status: 2, + })) + ); + expect(await navGroupStart.getNavGroupsMap$().pipe(first()).toPromise()).toEqual({ + dataAdministration: expect.objectContaining({ + status: 2, + }), + }); + }); + + it('should reset to original status after nav group updater unregister', async () => { + const navGroup = new ChromeNavGroupService(); + const uiSettings = uiSettingsServiceMock.createSetupContract(); + uiSettings.get$.mockImplementation(() => new Rx.BehaviorSubject(true)); + + const navGroupSetup = navGroup.setup({ uiSettings }); + navGroupSetup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.dataAdministration, [ + { + id: 'foo', + }, + ]); + const appUpdater$ = new Rx.BehaviorSubject(() => ({ + status: 2, + })); + const unregister = navGroupSetup.registerNavGroupUpdater(appUpdater$); + const navGroupStart = await navGroup.start({ + navLinks: mockedNavLinkService, + application: mockedApplicationService, + breadcrumbsEnricher$: new Rx.BehaviorSubject(undefined), + workspaces: workspacesServiceMock.createStartContract(), + }); + expect(await navGroupStart.getNavGroupsMap$().pipe(first()).toPromise()).toEqual({ + dataAdministration: expect.objectContaining({ + status: 2, + }), + }); + + unregister(); + + expect(await navGroupStart.getNavGroupsMap$().pipe(first()).toPromise()).toEqual({ + dataAdministration: expect.not.objectContaining({ + status: expect.anything, + }), + }); + }); +}); diff --git a/src/core/public/chrome/nav_group/nav_group_service.ts b/src/core/public/chrome/nav_group/nav_group_service.ts new file mode 100644 index 000000000000..729239081b42 --- /dev/null +++ b/src/core/public/chrome/nav_group/nav_group_service.ts @@ -0,0 +1,342 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { BehaviorSubject, combineLatest, Observable, of, ReplaySubject, Subscription } from 'rxjs'; +import { + AppCategory, + ApplicationStart, + ChromeNavGroup, + ChromeNavLink, + WorkspacesStart, +} from 'opensearch-dashboards/public'; +import { map, switchMap, takeUntil } from 'rxjs/operators'; +import { i18n } from '@osd/i18n'; +import { IUiSettingsClient } from '../../ui_settings'; +import { + fulfillRegistrationLinksToChromeNavLinks, + getSortedNavLinks, + getVisibleUseCases, +} from '../utils'; +import { ChromeNavLinks } from '../nav_links'; +import { InternalApplicationStart } from '../../application'; +import { NavGroupStatus } from '../../../../core/types'; +import { ChromeBreadcrumb, ChromeBreadcrumbEnricher } from '../chrome_service'; +import { ALL_USE_CASE_ID } from '../../../utils'; + +export const CURRENT_NAV_GROUP_ID = 'core.chrome.currentNavGroupId'; + +/** @public */ +export interface ChromeRegistrationNavLink { + id: string; + title?: string; + category?: AppCategory; + order?: number; + + /** + * link with parentNavLinkId field will be displayed as nested items in navigation. + */ + parentNavLinkId?: string; + + /** + * If the nav link should be shown in 'all' nav group + */ + showInAllNavGroup?: boolean; +} + +export type NavGroupItemInMap = ChromeNavGroup & { + navLinks: ChromeRegistrationNavLink[]; +}; + +export type ChromeNavGroupUpdater = (navGroup: ChromeNavGroup) => Partial | void; + +export interface ChromeNavGroupServiceSetupContract { + addNavLinksToGroup: (navGroup: ChromeNavGroup, navLinks: ChromeRegistrationNavLink[]) => void; + /** + * Get a boolean value to indicates whether use case is enabled + */ + getNavGroupEnabled: () => boolean; + registerNavGroupUpdater: (navGroupUpdater: Observable) => () => void; +} + +export interface ChromeNavGroupServiceStartContract { + getNavGroupsMap$: () => Observable>; + getNavGroupEnabled: ChromeNavGroupServiceSetupContract['getNavGroupEnabled']; + /** + * Get an observable of the current selected nav group + */ + getCurrentNavGroup$: () => Observable; + + /** + * Set current selected nav group + * @param navGroupId The id of the nav group to be set as current + */ + setCurrentNavGroup: (navGroupId: string | undefined) => void; +} + +/** @internal */ +export class ChromeNavGroupService { + private readonly navGroupsMap$ = new BehaviorSubject>({}); + private readonly stop$ = new ReplaySubject(1); + private navLinks$: Observable>> = new BehaviorSubject([]); + private navGroupEnabled: boolean = false; + private navGroupEnabledUiSettingsSubscription: Subscription | undefined; + private navGroupUpdaters$$ = new BehaviorSubject>>([]); + private currentNavGroup$ = new BehaviorSubject(undefined); + private currentNavGroupSubscription: Subscription | undefined; + private currentAppIdSubscription: Subscription | undefined; + + private addNavLinkToGroup( + currentGroupsMap: Record, + navGroup: ChromeNavGroup, + navLink: ChromeRegistrationNavLink + ) { + const matchedGroup = currentGroupsMap[navGroup.id]; + if (matchedGroup) { + const links = matchedGroup.navLinks; + const isLinkExistInGroup = links.some((link) => link.id === navLink.id); + if (isLinkExistInGroup) { + // eslint-disable-next-line no-console + console.warn( + `[ChromeService] Navlink of ${navLink.id} has already been registered in group ${navGroup.id}` + ); + return currentGroupsMap; + } + matchedGroup.navLinks.push(navLink); + } else { + currentGroupsMap[navGroup.id] = { + ...navGroup, + navLinks: [navLink], + }; + } + + return currentGroupsMap; + } + + private sortNavGroupNavLinks( + navGroup: NavGroupItemInMap, + allValidNavLinks: Array> + ) { + return getSortedNavLinks( + fulfillRegistrationLinksToChromeNavLinks(navGroup.navLinks, allValidNavLinks) + ); + } + + private getSortedNavGroupsMap$() { + return combineLatest([this.getUpdatedNavGroupsMap$(), this.navLinks$]) + .pipe(takeUntil(this.stop$)) + .pipe( + map(([navGroupsMap, navLinks]) => { + return Object.keys(navGroupsMap).reduce((sortedNavGroupsMap, navGroupId) => { + const navGroup = navGroupsMap[navGroupId]; + sortedNavGroupsMap[navGroupId] = { + ...navGroup, + navLinks: this.sortNavGroupNavLinks(navGroup, navLinks), + }; + return sortedNavGroupsMap; + }, {} as Record); + }) + ); + } + + private getUpdatedNavGroupsMap$() { + return combineLatest([this.navGroupsMap$, this.navGroupUpdaters$$]).pipe( + switchMap(([navGroupsMap, updaters$]) => { + if (updaters$.length === 0) { + return of(navGroupsMap); + } + return combineLatest(updaters$).pipe( + map((updaters) => { + return Object.keys(navGroupsMap).reduce>( + (previousValue, currentKey) => ({ + ...previousValue, + [currentKey]: updaters.reduce( + (prevNavGroup, currentUpdater) => ({ + ...prevNavGroup, + ...currentUpdater(prevNavGroup), + }), + navGroupsMap[currentKey] + ), + }), + {} + ); + }) + ); + }) + ); + } + setup({ uiSettings }: { uiSettings: IUiSettingsClient }): ChromeNavGroupServiceSetupContract { + this.navGroupEnabledUiSettingsSubscription = uiSettings + .get$('home:useNewHomePage', false) + .subscribe((value) => { + this.navGroupEnabled = value; + }); + + return { + addNavLinksToGroup: (navGroup: ChromeNavGroup, navLinks: ChromeRegistrationNavLink[]) => { + // Construct a new groups map pointer. + const currentGroupsMap = { ...this.navGroupsMap$.getValue() }; + + const navGroupsMapAfterAdd = navLinks.reduce( + (groupsMap, navLink) => this.addNavLinkToGroup(groupsMap, navGroup, navLink), + currentGroupsMap + ); + + this.navGroupsMap$.next(navGroupsMapAfterAdd); + }, + getNavGroupEnabled: () => this.navGroupEnabled, + registerNavGroupUpdater: (updater$) => { + this.navGroupUpdaters$$.next([...this.navGroupUpdaters$$.getValue(), updater$]); + return () => { + this.navGroupUpdaters$$.next( + this.navGroupUpdaters$$.getValue().filter((item) => item !== updater$) + ); + }; + }, + }; + } + async start({ + navLinks, + application, + breadcrumbsEnricher$, + workspaces, + }: { + navLinks: ChromeNavLinks; + application: InternalApplicationStart; + breadcrumbsEnricher$: BehaviorSubject; + workspaces: WorkspacesStart; + }): Promise { + this.navLinks$ = navLinks.getNavLinks$(); + + const currentNavGroupId = sessionStorage.getItem(CURRENT_NAV_GROUP_ID); + this.currentNavGroup$ = new BehaviorSubject( + currentNavGroupId ? this.navGroupsMap$.getValue()[currentNavGroupId] : undefined + ); + + const setCurrentNavGroup = (navGroupId: string | undefined) => { + const navGroup = navGroupId ? this.navGroupsMap$.getValue()[navGroupId] : undefined; + if (navGroup) { + this.currentNavGroup$.next(navGroup); + sessionStorage.setItem(CURRENT_NAV_GROUP_ID, navGroup.id); + } else { + this.currentNavGroup$.next(undefined); + sessionStorage.removeItem(CURRENT_NAV_GROUP_ID); + } + }; + + const currentNavGroupSorted$ = combineLatest([ + this.getSortedNavGroupsMap$(), + this.currentNavGroup$, + ]) + .pipe(takeUntil(this.stop$)) + .pipe( + map(([navGroupsMapSorted, currentNavGroup]) => { + if (currentNavGroup) { + return navGroupsMapSorted[currentNavGroup.id]; + } + }) + ); + + // when we not in any workspace or workspace is disabled + if (this.navGroupEnabled && !workspaces.currentWorkspace$.getValue()) { + this.currentNavGroupSubscription = currentNavGroupSorted$.subscribe((currentNavGroup) => { + if (currentNavGroup) { + breadcrumbsEnricher$.next((breadcrumbs) => + this.prependCurrentNavGroupToBreadcrumbs( + breadcrumbs, + currentNavGroup, + application.navigateToApp + ) + ); + } else { + breadcrumbsEnricher$.next(undefined); + } + }); + } + + this.currentAppIdSubscription = combineLatest([ + application.currentAppId$, + this.getSortedNavGroupsMap$(), + ]).subscribe(([appId, navGroupMap]) => { + if (appId && navGroupMap) { + const appIdNavGroupMap = new Map>(); + const visibleUseCases = getVisibleUseCases(navGroupMap); + const mapAppIdToNavGroup = (navGroup: NavGroupItemInMap) => { + navGroup.navLinks.forEach((navLink) => { + const navLinkId = navLink.id; + const navGroupSet = appIdNavGroupMap.get(navLinkId) || new Set(); + navGroupSet.add(navGroup.id); + appIdNavGroupMap.set(navLinkId, navGroupSet); + }); + }; + if (visibleUseCases.length === 1 && visibleUseCases[0].id === ALL_USE_CASE_ID) { + // If the only visible use case is all use case + // All the other nav groups will be visible because all use case can visit all of the nav groups. + Object.values(navGroupMap).forEach((navGroup) => mapAppIdToNavGroup(navGroup)); + } else { + // Nav group of Hidden status should be filtered out when counting navGroups the currentApp belongs to + Object.values(navGroupMap).forEach((navGroup) => { + if (navGroup.status === NavGroupStatus.Hidden) { + return; + } + + mapAppIdToNavGroup(navGroup); + }); + } + + const navGroups = appIdNavGroupMap.get(appId); + if (navGroups && navGroups.size === 1) { + setCurrentNavGroup(navGroups.values().next().value); + } else if (!navGroups) { + setCurrentNavGroup(undefined); + } + } + }); + + return { + getNavGroupsMap$: () => this.getSortedNavGroupsMap$(), + getNavGroupEnabled: () => this.navGroupEnabled, + + getCurrentNavGroup$: () => currentNavGroupSorted$, + setCurrentNavGroup, + }; + } + + /** + * prepend current nav group into existing breadcrumbs and return new breadcrumbs, the new breadcrumbs will looks like + * Home > Search > Visualization + * @param breadcrumbs existing breadcrumbs + * @param currentNavGroup current nav group object + * @param navigateToApp + * @returns new breadcrumbs array + */ + private prependCurrentNavGroupToBreadcrumbs( + breadcrumbs: ChromeBreadcrumb[], + currentNavGroup: NavGroupItemInMap, + navigateToApp: ApplicationStart['navigateToApp'] + ) { + const navGroupBreadcrumb: ChromeBreadcrumb = { + text: currentNavGroup.title, + onClick: () => { + if (currentNavGroup.navLinks && currentNavGroup.navLinks.length) { + navigateToApp(currentNavGroup.navLinks[0].id); + } + }, + }; + const homeBreadcrumb: ChromeBreadcrumb = { + text: i18n.translate('core.breadcrumbs.homeTitle', { defaultMessage: 'Home' }), + onClick: () => { + navigateToApp('home'); + }, + }; + return [homeBreadcrumb, navGroupBreadcrumb, ...breadcrumbs]; + } + + async stop() { + this.stop$.next(); + this.navGroupEnabledUiSettingsSubscription?.unsubscribe(); + this.currentAppIdSubscription?.unsubscribe(); + this.currentNavGroupSubscription?.unsubscribe(); + } +} diff --git a/src/core/public/chrome/nav_links/nav_link.ts b/src/core/public/chrome/nav_links/nav_link.ts index cddd45234514..21772e2f26ab 100644 --- a/src/core/public/chrome/nav_links/nav_link.ts +++ b/src/core/public/chrome/nav_links/nav_link.ts @@ -102,6 +102,11 @@ export interface ChromeNavLink { * Hides a link from the navigation. */ readonly hidden?: boolean; + + /** + * Description of the nav link + */ + readonly description?: string; } /** @public */ diff --git a/src/core/public/chrome/nav_links/to_nav_link.test.ts b/src/core/public/chrome/nav_links/to_nav_link.test.ts index 8d88c15c85ae..4bbf0497b2dc 100644 --- a/src/core/public/chrome/nav_links/to_nav_link.test.ts +++ b/src/core/public/chrome/nav_links/to_nav_link.test.ts @@ -58,6 +58,7 @@ describe('toNavLink', () => { title: 'title', order: 12, tooltip: 'tooltip', + description: 'some-description', euiIconType: 'my-icon' as EuiIconType, }), basePath @@ -69,6 +70,7 @@ describe('toNavLink', () => { order: 12, tooltip: 'tooltip', euiIconType: 'my-icon', + description: 'some-description', }) ); }); diff --git a/src/core/public/chrome/recently_accessed/index.ts b/src/core/public/chrome/recently_accessed/index.ts index 8bb2e306b221..9fb34b520470 100644 --- a/src/core/public/chrome/recently_accessed/index.ts +++ b/src/core/public/chrome/recently_accessed/index.ts @@ -33,3 +33,5 @@ export { ChromeRecentlyAccessedHistoryItem, RecentlyAccessedService, } from './recently_accessed_service'; + +export { PersistedLog } from './persisted_log'; diff --git a/src/core/public/chrome/recently_accessed/recently_accessed_service.test.ts b/src/core/public/chrome/recently_accessed/recently_accessed_service.test.ts index 7046d5efc236..c2e75349f83e 100644 --- a/src/core/public/chrome/recently_accessed/recently_accessed_service.test.ts +++ b/src/core/public/chrome/recently_accessed/recently_accessed_service.test.ts @@ -29,6 +29,7 @@ */ import { httpServiceMock } from '../../http/http_service.mock'; +import { workspacesServiceMock } from '../../mocks'; import { RecentlyAccessedService } from './recently_accessed_service'; import { Subject } from 'rxjs'; import { takeUntil, bufferCount } from 'rxjs/operators'; @@ -78,8 +79,9 @@ describe('RecentlyAccessed#start()', () => { const getStart = async () => { const http = httpServiceMock.createStartContract(); - const recentlyAccessed = await new RecentlyAccessedService().start({ http }); - return { http, recentlyAccessed }; + const workspaces = workspacesServiceMock.createStartContract(); + const recentlyAccessed = await new RecentlyAccessedService().start({ http, workspaces }); + return { http, recentlyAccessed, workspaces }; }; it('allows adding and getting items', async () => { @@ -151,4 +153,13 @@ Array [ ] `); }); + + it('adding items with workspaceId if is inside a workspace', async () => { + const { recentlyAccessed, workspaces } = await getStart(); + workspaces.currentWorkspaceId$.next('foo'); + recentlyAccessed.add('/app/item1', 'Item 1', 'item1'); + expect(recentlyAccessed.get()).toEqual([ + { link: '/app/item1', label: 'Item 1', id: 'item1', workspaceId: 'foo' }, + ]); + }); }); diff --git a/src/core/public/chrome/recently_accessed/recently_accessed_service.ts b/src/core/public/chrome/recently_accessed/recently_accessed_service.ts index d6b676529a96..99ca61513855 100644 --- a/src/core/public/chrome/recently_accessed/recently_accessed_service.ts +++ b/src/core/public/chrome/recently_accessed/recently_accessed_service.ts @@ -33,22 +33,29 @@ import { Observable } from 'rxjs'; import { PersistedLog } from './persisted_log'; import { createLogKey } from './create_log_key'; import { HttpSetup } from '../../http'; +import { WorkspacesStart } from '../../workspace'; /** @public */ export interface ChromeRecentlyAccessedHistoryItem { link: string; label: string; id: string; + workspaceId?: string; + meta?: { + type?: string; + lastAccessedTime?: number; + }; } interface StartDeps { http: HttpSetup; + workspaces: WorkspacesStart; } /** @internal */ export class RecentlyAccessedService { - async start({ http }: StartDeps): Promise { - const logKey = await createLogKey('recentlyAccessed', http.basePath.get()); + async start({ http, workspaces }: StartDeps): Promise { + const logKey = await createLogKey('recentlyAccessed', http.basePath.getBasePath()); const history = new PersistedLog(logKey, { maxLength: 20, isEqual: (oldItem, newItem) => oldItem.id === newItem.id, @@ -56,11 +63,20 @@ export class RecentlyAccessedService { return { /** Adds a new item to the history. */ - add: (link: string, label: string, id: string) => { + add: ( + link: string, + label: string, + id: string, + meta?: ChromeRecentlyAccessedHistoryItem['meta'] + ) => { + const currentWorkspaceId = workspaces.currentWorkspaceId$.getValue(); + history.add({ link, label, id, + ...(currentWorkspaceId && { workspaceId: currentWorkspaceId }), + ...(meta && { meta: { lastAccessedTime: Date.now(), ...meta } }), }); }, @@ -90,7 +106,12 @@ export interface ChromeRecentlyAccessed { * @param label the label to display in the UI * @param id a unique string used to de-duplicate the recently accessed list. */ - add(link: string, label: string, id: string): void; + add( + link: string, + label: string, + id: string, + meta?: ChromeRecentlyAccessedHistoryItem['meta'] + ): void; /** * Gets an Array of the current recently accessed history. diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap index 62f00bee2c74..23433a8514e7 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav.test.tsx.snap @@ -697,7 +697,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` Object { "aria-label": "recent 1", "data-test-subj": "collapsibleNavAppLink--recent", - "href": "http://localhost/recent%201", + "href": "http://localhost/test/recent%201", "label": "recent 1", "onClick": [Function], "title": "recent 1", @@ -705,7 +705,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` Object { "aria-label": "recent 2", "data-test-subj": "collapsibleNavAppLink--recent", - "href": "http://localhost/recent%202", + "href": "http://localhost/test/recent%202", "label": "recent 2", "onClick": [Function], "title": "recent 2", @@ -728,7 +728,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` aria-label="recent 1" color="subdued" data-test-subj="collapsibleNavAppLink--recent" - href="http://localhost/recent%201" + href="http://localhost/test/recent%201" key="title-0" label="recent 1" onClick={[Function]} @@ -744,7 +744,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` aria-label="recent 1" className="euiListGroupItem__button" data-test-subj="collapsibleNavAppLink--recent" - href="http://localhost/recent%201" + href="http://localhost/test/recent%201" onClick={[Function]} rel="noreferrer" title="recent 1" @@ -762,7 +762,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` aria-label="recent 2" color="subdued" data-test-subj="collapsibleNavAppLink--recent" - href="http://localhost/recent%202" + href="http://localhost/test/recent%202" key="title-1" label="recent 2" onClick={[Function]} @@ -778,7 +778,7 @@ exports[`CollapsibleNav renders links grouped by category 1`] = ` aria-label="recent 2" className="euiListGroupItem__button" data-test-subj="collapsibleNavAppLink--recent" - href="http://localhost/recent%202" + href="http://localhost/test/recent%202" onClick={[Function]} rel="noreferrer" title="recent 2" @@ -3665,7 +3665,7 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` Object { "aria-label": "recent", "data-test-subj": "collapsibleNavAppLink--recent", - "href": "http://localhost/recent", + "href": "http://localhost/test/recent", "label": "recent", "onClick": [Function], "title": "recent", @@ -3688,7 +3688,7 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` aria-label="recent" color="subdued" data-test-subj="collapsibleNavAppLink--recent" - href="http://localhost/recent" + href="http://localhost/test/recent" key="title-0" label="recent" onClick={[Function]} @@ -3704,7 +3704,7 @@ exports[`CollapsibleNav with custom branding renders the nav bar in dark mode 1` aria-label="recent" className="euiListGroupItem__button" data-test-subj="collapsibleNavAppLink--recent" - href="http://localhost/recent" + href="http://localhost/test/recent" onClick={[Function]} rel="noreferrer" title="recent" @@ -4781,7 +4781,7 @@ exports[`CollapsibleNav with custom branding renders the nav bar in default mode Object { "aria-label": "recent", "data-test-subj": "collapsibleNavAppLink--recent", - "href": "http://localhost/recent", + "href": "http://localhost/test/recent", "label": "recent", "onClick": [Function], "title": "recent", @@ -4804,7 +4804,7 @@ exports[`CollapsibleNav with custom branding renders the nav bar in default mode aria-label="recent" color="subdued" data-test-subj="collapsibleNavAppLink--recent" - href="http://localhost/recent" + href="http://localhost/test/recent" key="title-0" label="recent" onClick={[Function]} @@ -4820,7 +4820,7 @@ exports[`CollapsibleNav with custom branding renders the nav bar in default mode aria-label="recent" className="euiListGroupItem__button" data-test-subj="collapsibleNavAppLink--recent" - href="http://localhost/recent" + href="http://localhost/test/recent" onClick={[Function]} rel="noreferrer" title="recent" @@ -5890,7 +5890,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode Object { "aria-label": "recent", "data-test-subj": "collapsibleNavAppLink--recent", - "href": "http://localhost/recent", + "href": "http://localhost/test/recent", "label": "recent", "onClick": [Function], "title": "recent", @@ -5913,7 +5913,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode aria-label="recent" color="subdued" data-test-subj="collapsibleNavAppLink--recent" - href="http://localhost/recent" + href="http://localhost/test/recent" key="title-0" label="recent" onClick={[Function]} @@ -5929,7 +5929,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in dark mode aria-label="recent" className="euiListGroupItem__button" data-test-subj="collapsibleNavAppLink--recent" - href="http://localhost/recent" + href="http://localhost/test/recent" onClick={[Function]} rel="noreferrer" title="recent" @@ -6995,7 +6995,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in default m Object { "aria-label": "recent", "data-test-subj": "collapsibleNavAppLink--recent", - "href": "http://localhost/recent", + "href": "http://localhost/test/recent", "label": "recent", "onClick": [Function], "title": "recent", @@ -7018,7 +7018,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in default m aria-label="recent" color="subdued" data-test-subj="collapsibleNavAppLink--recent" - href="http://localhost/recent" + href="http://localhost/test/recent" key="title-0" label="recent" onClick={[Function]} @@ -7034,7 +7034,7 @@ exports[`CollapsibleNav without custom branding renders the nav bar in default m aria-label="recent" className="euiListGroupItem__button" data-test-subj="collapsibleNavAppLink--recent" - href="http://localhost/recent" + href="http://localhost/test/recent" onClick={[Function]} rel="noreferrer" title="recent" diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav_group_enabled.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav_group_enabled.test.tsx.snap new file mode 100644 index 000000000000..11b66bd6d5ae --- /dev/null +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav_group_enabled.test.tsx.snap @@ -0,0 +1,414 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render correctly 1`] = ` +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[` should render correctly 2`] = ` +
+
+
+
+
+
+
+
+
+`; + +exports[` should show all use case by default and able to click see all 1`] = ` +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; + +exports[` should show all use case when current nav group is \`all\` 1`] = ` +
+
+
+
+ +
+
+
+ +
+
+
+
+
+
+
+`; diff --git a/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav_groups.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav_groups.test.tsx.snap new file mode 100644 index 000000000000..3d923a49dd80 --- /dev/null +++ b/src/core/public/chrome/ui/header/__snapshots__/collapsible_nav_groups.test.tsx.snap @@ -0,0 +1,204 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` should render correctly 1`] = ` +
+
+ +
+
+`; diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index 790f24bc20e9..88f06ee9edcb 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -164,6 +164,60 @@ exports[`Header handles visibility and lock changes 1`] = ` "thrownError": null, }, }, + "currentBadgeControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentBottomControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentCenterControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentDescriptionControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentLeftControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, + "currentRightControls$": BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + }, "getComponent": [MockFunction], "getUrlForApp": [MockFunction], "history": Object { @@ -188,6 +242,12 @@ exports[`Header handles visibility and lock changes 1`] = ` "navigateToApp": [MockFunction], "navigateToUrl": [MockFunction], "registerMountContext": [MockFunction], + "setAppBadgeControls": [MockFunction], + "setAppBottomControls": [MockFunction], + "setAppCenterControls": [MockFunction], + "setAppDescriptionControls": [MockFunction], + "setAppLeftControls": [MockFunction], + "setAppRightControls": [MockFunction], } } badge$={ @@ -262,6 +322,92 @@ exports[`Header handles visibility and lock changes 1`] = ` "closed": false, "hasError": false, "isStopped": false, + "observers": Array [ + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + ], + "thrownError": null, + } + } + breadcrumbsEnricher$={ + BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, "observers": Array [ Subscriber { "_parentOrParents": null, @@ -304,6 +450,28 @@ exports[`Header handles visibility and lock changes 1`] = ` "thrownError": null, } } + currentNavGroup$={ + BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + } + } + currentWorkspace$={ + BehaviorSubject { + "_isScalar": false, + "_value": null, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + } + } customNavLink$={ BehaviorSubject { "_isScalar": false, @@ -444,6 +612,55 @@ exports[`Header handles visibility and lock changes 1`] = ` "thrownError": null, } } + headerVariant$={ + BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [ + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + ], + "thrownError": null, + } + } helpExtension$={ BehaviorSubject { "_isScalar": false, @@ -1690,6 +1907,17 @@ exports[`Header handles visibility and lock changes 1`] = ` "thrownError": null, } } + navControlsLeftBottom$={ + BehaviorSubject { + "_isScalar": false, + "_value": Array [], + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + } + } navControlsRight$={ BehaviorSubject { "_isScalar": false, @@ -1739,6 +1967,18 @@ exports[`Header handles visibility and lock changes 1`] = ` "thrownError": null, } } + navGroupEnabled={false} + navGroupsMap$={ + BehaviorSubject { + "_isScalar": false, + "_value": Object {}, + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + } + } navLinks$={ BehaviorSubject { "_isScalar": false, @@ -1927,6 +2167,7 @@ exports[`Header handles visibility and lock changes 1`] = ` "thrownError": null, } } + setCurrentNavGroup={[MockFunction]} sidecarConfig$={ BehaviorSubject { "_isScalar": false, @@ -1980,6 +2221,18 @@ exports[`Header handles visibility and lock changes 1`] = ` } } survey="/" + useUpdatedHeader={false} + workspaceList$={ + BehaviorSubject { + "_isScalar": false, + "_value": Array [], + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [], + "thrownError": null, + } + } >
- - - - + + -
- -
-
-
-
-
- , - } - } - className="euiHeaderSectionItemButton header__homeLoaderNavButton" - color="text" - data-test-subj="homeLoader" - href="/" - onClick={[Function]} - title="Go to home page" - > - - +
+ +
+
+
+ + + +
, } } + className="euiHeaderSectionItemButton header__homeLoaderNavButton" + color="text" + data-test-subj="homeLoader" + href="/" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} > - - -
- - - - - -
-
- - + + + + +
+
- - - -
+ + + + + +
+ + - - - - - - + + + + + +
@@ -3899,6 +4169,92 @@ exports[`Header handles visibility and lock changes 1`] = ` "closed": false, "hasError": false, "isStopped": false, + "observers": Array [ + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + ], + "thrownError": null, + } + } + breadcrumbsEnricher$={ + BehaviorSubject { + "_isScalar": false, + "_value": undefined, + "closed": false, + "hasError": false, + "isStopped": false, "observers": Array [ Subscriber { "_parentOrParents": null, @@ -3941,6 +4297,7 @@ exports[`Header handles visibility and lock changes 1`] = ` "thrownError": null, } } + useUpdatedHeader={false} > @@ -4062,9 +4420,10 @@ exports[`Header handles visibility and lock changes 1`] = ` >
@@ -5761,18 +6121,23 @@ exports[`Header handles visibility and lock changes 1`] = ` - - + + + + } closePopover={[Function]} data-test-subj="helpMenuButton" @@ -5781,7 +6146,7 @@ exports[`Header handles visibility and lock changes 1`] = ` id="headerHelpMenu" isOpen={false} ownFocus={true} - panelPaddingSize="m" + panelPaddingSize="s" repositionOnScroll={true} >
- - - - - - - - - - , - } - } - className="euiHeaderSectionItemButton" - color="text" - onClick={[Function]} + - , } } + className="euiHeaderSectionItemButton" + color="text" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} > - - - - + className="euiHeaderSectionItemButton__content" + > + + + + + - - - - - - + + + + + +
@@ -6541,7 +6923,7 @@ exports[`Header handles visibility and lock changes 1`] = ` Object { "aria-label": "dashboard, type: opensearchDashboards", "data-test-subj": "collapsibleNavAppLink--recent", - "href": "http://localhost/", + "href": "http://localhost/test/", "label": "dashboard", "onClick": [Function], "title": "dashboard, type: opensearchDashboards", @@ -6564,7 +6946,7 @@ exports[`Header handles visibility and lock changes 1`] = ` aria-label="dashboard, type: opensearchDashboards" color="subdued" data-test-subj="collapsibleNavAppLink--recent" - href="http://localhost/" + href="http://localhost/test/" key="title-0" label="dashboard" onClick={[Function]} @@ -6580,7 +6962,7 @@ exports[`Header handles visibility and lock changes 1`] = ` aria-label="dashboard, type: opensearchDashboards" className="euiListGroupItem__button" data-test-subj="collapsibleNavAppLink--recent" - href="http://localhost/" + href="http://localhost/test/" onClick={[Function]} rel="noreferrer" title="dashboard, type: opensearchDashboards" @@ -6768,7 +7150,7 @@ exports[`Header handles visibility and lock changes 1`] = `
`; -exports[`Header renders condensed header 1`] = ` +exports[`Header renders application header without title and breadcrumbs 1`] = `
- -
+ - -
- -
- - - - - - - - - - , - } - } - className="euiHeaderSectionItemButton" - color="text" + aria-pressed="false" + class="euiButtonEmpty euiButtonEmpty--text euiHeaderSectionItemButton newAppTopNavExpander" data-test-subj="toggleNavButton" - onClick={[Function]} + type="button" > - - - -
-
- + + , + } + } + className="euiHeaderSectionItemButton newAppTopNavExpander" + color="text" + data-test-subj="toggleNavButton" + onClick={[Function]} > -
- -
-
- + + + + + + + + + + + + + + +
-
- + } - } - navLinks$={ - BehaviorSubject { - "_isScalar": false, - "_value": Array [], - "closed": false, - "hasError": false, - "isStopped": false, - "observers": Array [ - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], + workspaceList$={ + BehaviorSubject { + "_isScalar": false, + "_value": Array [], + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [ + Subscriber { "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], "_subscriptions": null, "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, }, "isStopped": false, - "syncErrorThrowable": false, + "syncErrorThrowable": true, "syncErrorThrown": false, "syncErrorValue": null, }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - ], - "thrownError": null, + ], + "thrownError": null, + } } - } - navigateToApp={[MockFunction]} - > - - + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + initialFocus={false} + isOpen={false} + ownFocus={true} + panelPaddingSize="s" + repositionOnScroll={true} + > +
+
+ - - -
- -
- -
-
-
- , - } - } - className="euiHeaderSectionItemButton header__homeLoaderNavButton" - color="text" - data-test-subj="homeLoader" - href="/" - onClick={[Function]} - title="Go to home page" - > - - - - - -
- - - - - -
-
- - - - - -
-
+ + +
-
-
-
- - - -
- -
- - - - -
+
+ + +
+ +
-
- - - test - - -
-
- - - - - -
- -
-
- -
- -
- -
- -
- - +
+ +
+
+
+ + +
-
- + -
- - -
+
+
+ - + -
-
- -
- +
+ +
+ + +
+ +
+
+
+ +
+
+
+
+ + + + + +`; + +exports[`Header renders condensed header 1`] = ` +
+
+
+ +
+ +
+ +
+ + + + + + + + + + , + } + } + className="euiHeaderSectionItemButton" + color="text" + data-test-subj="toggleNavButton" + onClick={[Function]} + > + + + +
+
+ +
+ +
+
+ +
+ + + + + + + + +
+ +
+
+
+
+
+
+ , + } + } + className="euiHeaderSectionItemButton header__homeLoaderNavButton" + color="text" + data-test-subj="homeLoader" + href="/" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + > + + + + + +
+ + + + + +
+
+ + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + +
+ +
+
+ +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+
+ +
+ + + + + + + + } + closePopover={[Function]} + data-test-subj="helpMenuButton" + display="inlineBlock" + hasArrow={true} + id="headerHelpMenu" + isOpen={false} + ownFocus={true} + panelPaddingSize="s" + repositionOnScroll={true} + > +
+
+ + + + + + + + + + + + , + } + } + className="euiHeaderSectionItemButton" + color="text" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + > + + + + + +
+
+
+
+
+
+
+
+ +
+ +
+ + + +
+
+`; + +exports[`Header renders page header with application title 1`] = ` +
+
+
+
+ +
+ + + + + + + + + + , + } + } + className="euiHeaderSectionItemButton newPageTopNavExpander" + color="text" + data-test-subj="toggleNavButton" + onClick={[Function]} + > + + + + +
+ +
+ + } + workspaceList$={ + BehaviorSubject { + "_isScalar": false, + "_value": Array [], + "closed": false, + "hasError": false, + "isStopped": false, + "observers": Array [ + Subscriber { + "_parentOrParents": null, + "_subscriptions": Array [ + SubjectSubscription { + "_parentOrParents": [Circular], + "_subscriptions": null, + "closed": false, + "subject": [Circular], + "subscriber": [Circular], + }, + ], + "closed": false, + "destination": SafeSubscriber { + "_complete": undefined, + "_context": [Circular], + "_error": undefined, + "_next": [Function], + "_parentOrParents": null, + "_parentSubscriber": [Circular], + "_subscriptions": null, + "closed": false, + "destination": Object { + "closed": true, + "complete": [Function], + "error": [Function], + "next": [Function], + }, + "isStopped": false, + "syncErrorThrowable": false, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + "isStopped": false, + "syncErrorThrowable": true, + "syncErrorThrown": false, + "syncErrorValue": null, + }, + ], + "thrownError": null, + } + } + > + + + + } + closePopover={[Function]} + display="inlineBlock" + hasArrow={true} + initialFocus={false} + isOpen={false} + ownFocus={true} + panelPaddingSize="s" + repositionOnScroll={true} + > +
+
+ + + + + + + +
+
+
+
+
+
+
+
+ + + + + + + +
+
+ +
+ +
+ +
+ +

+ testTitle +

+
+
+
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+
+
+ + +
+ +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+ +
+ + +
+ +
+
+
+ +
+ + +
+ +
+ +
+ + +
+ +
+ +
+ +
+
+ + + +
+
+`; + +exports[`Header toggles primary navigation menu when clicked 1`] = ` +
+
+
+ +
+ +
+ +
+ + + + + + + + + + , + } + } + className="euiHeaderSectionItemButton" + color="text" + data-test-subj="toggleNavButton" + onClick={[Function]} + > + + + +
+
+ +
+ +
+
+ +
+ + + + + + + + +
+ +
+
+
+
+
+
+ , + } + } + className="euiHeaderSectionItemButton header__homeLoaderNavButton" + color="text" + data-test-subj="homeLoader" + href="/" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + > + + + + + +
+ + + + + +
+
+ + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + + + + +
+ +
+
+ +
+ +
+ +
+ +
+ + +
+ +
+
+ +
+ +
+
+ +
+ - - + + + + } closePopover={[Function]} data-test-subj="helpMenuButton" @@ -11320,7 +23652,7 @@ exports[`Header renders condensed header 1`] = ` id="headerHelpMenu" isOpen={false} ownFocus={true} - panelPaddingSize="m" + panelPaddingSize="s" repositionOnScroll={true} >
- - - - - - - - - - , - } - } - className="euiHeaderSectionItemButton" - color="text" - onClick={[Function]} + - , } } + className="euiHeaderSectionItemButton" + color="text" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} > - - - - + className="euiHeaderSectionItemButton__content" + > + + + + + - - - - - - + + + + + +
@@ -11547,7 +23896,7 @@ exports[`Header renders condensed header 1`] = ` homeHref="/" id="mockId" isLocked={false} - isNavOpen={false} + isNavOpen={true} logos={ Object { "AnimatedMark": Object { @@ -11757,10 +24106,306 @@ exports[`Header renders condensed header 1`] = ` data-test-subj="collapsibleNav" id="mockId" isDocked={false} - isOpen={false} + isOpen={true} onClose={[Function]} outsideClickCloses={false} - /> + > + +
+
+ +
+
+
+ +
+ +
+

+ No recently viewed items +

+
+
+
+
+
+
+
+
+
+
+ + + +
+
+ +
+ + +
+
+ +
    + + + + Dock navigation + + , + } + } + color="subdued" + data-test-subj="collapsible-nav-lock" + iconType="lockOpen" + label="Dock navigation" + onClick={[Function]} + size="xs" + > +
  • + +
  • +
    +
+
+
+
+
+
+
+
+ + +
diff --git a/src/core/public/chrome/ui/header/__snapshots__/header_breadcrumbs.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header_breadcrumbs.test.tsx.snap index 5080b23e99c2..441f43729e98 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header_breadcrumbs.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header_breadcrumbs.test.tsx.snap @@ -33,3 +33,18 @@ Array [ `; exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable 3`] = `null`; + +exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable with updated header 1`] = `null`; + +exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable with updated header 2`] = ` + + First + +`; + +exports[`HeaderBreadcrumbs renders updates to the breadcrumbs$ observable with updated header 3`] = `null`; diff --git a/src/core/public/chrome/ui/header/__snapshots__/header_help_menu.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header_help_menu.test.tsx.snap index 036e2b4ee0ce..efb1da552116 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header_help_menu.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header_help_menu.test.tsx.snap @@ -1616,18 +1616,23 @@ exports[`Header help menu hides survey link 1`] = ` - - + + + + } closePopover={[Function]} data-test-subj="helpMenuButton" @@ -1636,7 +1641,7 @@ exports[`Header help menu hides survey link 1`] = ` id="headerHelpMenu" isOpen={true} ownFocus={true} - panelPaddingSize="m" + panelPaddingSize="s" repositionOnScroll={true} >
- - - - - - - - - - , - } - } - className="euiHeaderSectionItemButton" - color="text" - onClick={[Function]} + - , } } + className="euiHeaderSectionItemButton" + color="text" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} > - - - - + className="euiHeaderSectionItemButton__content" + > + + + + + - - - - - - + + + + + +
- - + + + + } closePopover={[Function]} data-test-subj="helpMenuButton" @@ -3796,7 +3826,7 @@ exports[`Header help menu renders survey link 1`] = ` id="headerHelpMenu" isOpen={true} ownFocus={true} - panelPaddingSize="m" + panelPaddingSize="s" repositionOnScroll={true} >
- - - - - - - - - - , - } - } - className="euiHeaderSectionItemButton" - color="text" - onClick={[Function]} + - , } } + className="euiHeaderSectionItemButton" + color="text" + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} > - - - - + className="euiHeaderSectionItemButton__content" + > + + + + + - - - - - - + + + + + +
+ +
+ +
+
+ +
+
+ +`; + +exports[`Home loader shows the loading indicator if loading count > 0 1`] = ` + + +
+ +
+
+ +
+
+
+`; diff --git a/src/core/public/chrome/ui/header/__snapshots__/recent_items.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/recent_items.test.tsx.snap new file mode 100644 index 000000000000..17634ee1c3fd --- /dev/null +++ b/src/core/public/chrome/ui/header/__snapshots__/recent_items.test.tsx.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Recent items should render base element normally 1`] = ` + +
+
+
+ + + +
+
+
+ +`; diff --git a/src/core/public/chrome/ui/header/__snapshots__/right_navigation_button.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/right_navigation_button.test.tsx.snap index 27b5748febcf..d042e2eb3235 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/right_navigation_button.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/right_navigation_button.test.tsx.snap @@ -3,30 +3,33 @@ exports[`Right navigation button should render base element normally 1`] = `
- + +
`; diff --git a/src/core/public/chrome/ui/header/_index.scss b/src/core/public/chrome/ui/header/_index.scss index b5e93e61c297..d68ce9996e07 100644 --- a/src/core/public/chrome/ui/header/_index.scss +++ b/src/core/public/chrome/ui/header/_index.scss @@ -1,4 +1,4 @@ -:not(.headerIsExpanded) { +:not(.headerIsExpanded):not(.headerIsDense) { @include euiHeaderAffordForFixed($osdHeaderOffset); } diff --git a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.scss b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.scss new file mode 100644 index 000000000000..978ed743a24b --- /dev/null +++ b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.scss @@ -0,0 +1,85 @@ +.context-nav-wrapper { + border: none !important; + + .nav-link-item { + padding: calc($euiSize / 4) $euiSize; + border-radius: $euiSize; + box-shadow: none; + margin-bottom: 0; + margin-top: 0; + + .nav-link-item-btn { + margin-bottom: 0; + + &::after { + display: none; + } + } + } + + .nav-link-parent-item-button { + > span { + flex-direction: row-reverse; + + > * { + margin-right: $euiSizeS; + margin-left: 2px; + } + } + } + + .nav-link-fake-item { + margin-top: 0; + } + + .nav-link-fake-item-button { + display: none; + } + + .nav-nested-item { + margin-bottom: 4px; + + &::after { + height: unset; + } + + .nav-link-item-btn { + padding-left: 0; + padding-right: 0; + } + } + + .left-navigation-wrapper { + display: flex; + flex-direction: column; + border-right: $euiBorderThin; + } + + .flex-1-container { + flex: 1; + } + + .bottom-container { + padding: 0 $euiSize; + display: flex; + -ms-overflow-style: -ms-autohiding-scrollbar; + + &.bottom-container-collapsed { + flex-direction: column; + align-items: center; + gap: $euiSize; + padding-top: $euiSizeS; + padding-bottom: $euiSizeS; + + > * { + justify-content: center; + } + } + + &.bottom-container-expanded { + gap: 16px; + padding-top: $euiSize; + padding-bottom: $euiSize; + } + } +} diff --git a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.test.tsx b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.test.tsx new file mode 100644 index 000000000000..418dca694e21 --- /dev/null +++ b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.test.tsx @@ -0,0 +1,309 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { fireEvent, render } from '@testing-library/react'; +import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; +import { + CollapsibleNavGroupEnabled, + CollapsibleNavGroupEnabledProps, +} from './collapsible_nav_group_enabled'; +import { ChromeNavLink } from '../../nav_links'; +import { NavGroupItemInMap } from '../../nav_group'; +import { httpServiceMock } from '../../../mocks'; +import { getLogos } from '../../../../common'; +import { + ALL_USE_CASE_ID, + DEFAULT_APP_CATEGORIES, + DEFAULT_NAV_GROUPS, + WorkspaceObject, +} from '../../../../public'; +import { capabilitiesServiceMock } from '../../../application/capabilities/capabilities_service.mock'; + +jest.mock('./collapsible_nav_group_enabled_top', () => ({ + CollapsibleNavTop: () => , +})); + +const mockBasePath = httpServiceMock.createSetupContract({ basePath: '/test' }).basePath; + +const defaultNavGroupMap = { + [ALL_USE_CASE_ID]: { + ...DEFAULT_NAV_GROUPS[ALL_USE_CASE_ID], + navLinks: [ + { + id: 'link-in-all', + title: 'link-in-all', + }, + ], + }, + [DEFAULT_NAV_GROUPS.observability.id]: { + ...DEFAULT_NAV_GROUPS.observability, + navLinks: [ + { + id: 'link-in-observability', + title: 'link-in-observability', + showInAllNavGroup: true, + }, + ], + }, +}; + +describe('', () => { + function mockProps( + props?: Partial & { + navGroupsMap?: Record; + currentNavGroupId?: string; + navLinks?: ChromeNavLink[]; + } + ): CollapsibleNavGroupEnabledProps { + const navGroupsMap$ = new BehaviorSubject>( + props?.navGroupsMap || defaultNavGroupMap + ); + const currentNavGroup$ = new BehaviorSubject( + props?.currentNavGroupId ? navGroupsMap$.getValue()[props.currentNavGroupId] : undefined + ); + return { + appId$: new BehaviorSubject('test'), + basePath: mockBasePath, + id: 'collapsibe-nav', + isLocked: false, + isNavOpen: false, + currentWorkspace$: new BehaviorSubject({ id: 'test', name: 'test' }), + navLinks$: new BehaviorSubject([ + { + id: 'link-in-all', + title: 'link-in-all', + baseUrl: '', + href: '', + }, + { + id: 'link-in-observability', + title: 'link-in-observability', + baseUrl: '', + href: '', + }, + { + id: 'link-in-essentials', + title: 'link-in-essentials', + baseUrl: '', + href: '', + }, + ...(props?.navLinks || []), + ]), + storage: new StubBrowserStorage(), + onIsLockedUpdate: () => {}, + closeNav: () => {}, + navigateToApp: () => Promise.resolve(), + navigateToUrl: () => Promise.resolve(), + customNavLink$: new BehaviorSubject(undefined), + logos: getLogos({}, mockBasePath.serverBasePath), + navGroupsMap$, + navControlsLeftBottom$: new BehaviorSubject([]), + currentNavGroup$, + setCurrentNavGroup: (val: string | undefined) => { + if (val) { + const currentNavGroup = navGroupsMap$.getValue()[val]; + if (currentNavGroup) { + currentNavGroup$.next(currentNavGroup); + } + } else { + currentNavGroup$.next(undefined); + } + }, + capabilities: { ...capabilitiesServiceMock.createStartContract().capabilities }, + ...props, + }; + } + it('should render correctly', () => { + const props = mockProps({ + isNavOpen: true, + navGroupsMap: { + ...defaultNavGroupMap, + [DEFAULT_NAV_GROUPS.essentials.id]: { + ...DEFAULT_NAV_GROUPS.essentials, + navLinks: [ + { + id: 'link-in-essentials', + title: 'link-in-essentials', + showInAllNavGroup: true, + }, + ], + }, + }, + }); + const { container } = render(); + expect(container).toMatchSnapshot(); + const { container: isNavOpenCloseContainer } = render( + + ); + expect(isNavOpenCloseContainer).toMatchSnapshot(); + }); + + it('should render correctly when only one visible use case is provided', () => { + const props = mockProps({ + navGroupsMap: { + [DEFAULT_NAV_GROUPS.observability.id]: + defaultNavGroupMap[DEFAULT_NAV_GROUPS.observability.id], + }, + }); + const { getAllByTestId } = render(); + expect(getAllByTestId('collapsibleNavAppLink-link-in-observability').length).toEqual(1); + }); + + it('should show all use case by default and able to click see all', async () => { + const props = mockProps({ + navGroupsMap: { + ...defaultNavGroupMap, + [DEFAULT_NAV_GROUPS.essentials.id]: { + ...DEFAULT_NAV_GROUPS.essentials, + navLinks: [ + { + id: 'link-in-essentials', + title: 'link-in-essentials', + showInAllNavGroup: true, + }, + ], + }, + }, + }); + const { container, getAllByTestId } = render( + + ); + fireEvent.click(getAllByTestId('collapsibleNavAppLink-link-in-essentials')[1]); + expect(getAllByTestId('collapsibleNavAppLink-link-in-essentials').length).toEqual(1); + expect(container).toMatchSnapshot(); + }); + + it('should show all use case when current nav group is `all`', async () => { + const props = mockProps({ + currentNavGroupId: ALL_USE_CASE_ID, + navGroupsMap: { + ...defaultNavGroupMap, + [DEFAULT_NAV_GROUPS.essentials.id]: { + ...DEFAULT_NAV_GROUPS.essentials, + navLinks: [ + { + id: 'link-in-essentials', + title: 'link-in-essentials', + showInAllNavGroup: true, + }, + ], + }, + }, + }); + const { container, getAllByTestId } = render( + + ); + fireEvent.click(getAllByTestId('collapsibleNavAppLink-link-in-essentials')[1]); + expect(getAllByTestId('collapsibleNavAppLink-link-in-essentials').length).toEqual(1); + expect(container).toMatchSnapshot(); + }); + + it('should not show group if the nav link is hidden', async () => { + const props = mockProps({ + currentNavGroupId: ALL_USE_CASE_ID, + navGroupsMap: { + ...defaultNavGroupMap, + [DEFAULT_NAV_GROUPS.essentials.id]: { + ...DEFAULT_NAV_GROUPS.essentials, + navLinks: [ + { + id: 'link-in-essentials-but-hidden', + title: 'link-in-essentials-but-hidden', + showInAllNavGroup: true, + }, + ], + }, + }, + navLinks: [ + { + id: 'link-in-essentials-but-hidden', + hidden: true, + title: 'link-in-essentials-but-hidden', + baseUrl: '', + href: '', + }, + ], + }); + const { queryAllByTestId } = render(); + expect(queryAllByTestId('collapsibleNavAppLink-link-in-essentials-but-hidden').length).toEqual( + 0 + ); + expect(queryAllByTestId('collapsibleNavAppLink-link-in-all').length).toEqual(1); + }); + + it('should show links with custom category if the nav link is inside second level but no entry in all use case', async () => { + const props = mockProps({ + currentNavGroupId: ALL_USE_CASE_ID, + navGroupsMap: { + ...defaultNavGroupMap, + [DEFAULT_NAV_GROUPS.essentials.id]: { + ...DEFAULT_NAV_GROUPS.essentials, + navLinks: [ + { + id: 'link-in-essentials', + title: 'link-in-essentials', + }, + { + id: 'link-in-all', + title: 'link-in-all', + }, + ], + }, + }, + navLinks: [ + { + id: 'link-in-essentials', + title: 'link-in-essentials', + baseUrl: '', + href: '', + }, + { + id: 'link-in-all', + title: 'link-in-all', + baseUrl: '', + href: '', + }, + ], + }); + const { queryAllByTestId, getByText, getByTestId } = render( + + ); + // Should render custom category + expect(getByText('Custom')).toBeInTheDocument(); + expect(getByTestId('collapsibleNavAppLink-link-in-essentials')).toBeInTheDocument(); + expect(queryAllByTestId('collapsibleNavAppLink-link-in-all').length).toEqual(1); + }); + + it('should render manage category when in all use case if workspace disabled', () => { + const props = mockProps({ + currentNavGroupId: ALL_USE_CASE_ID, + navGroupsMap: { + ...defaultNavGroupMap, + [DEFAULT_NAV_GROUPS.dataAdministration.id]: { + ...DEFAULT_NAV_GROUPS.dataAdministration, + navLinks: [ + { + id: 'link-in-dataAdministration', + title: 'link-in-dataAdministration', + }, + ], + }, + }, + navLinks: [ + { + id: 'link-in-dataAdministration', + title: 'link-in-dataAdministration', + baseUrl: '', + href: '', + }, + ], + }); + const { getByText } = render(); + // Should render manage category + expect(getByText(DEFAULT_APP_CATEGORIES.manage.label)).toBeInTheDocument(); + }); +}); diff --git a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx new file mode 100644 index 000000000000..21ff68f879e2 --- /dev/null +++ b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled.tsx @@ -0,0 +1,366 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import './collapsible_nav_group_enabled.scss'; +import { EuiFlyout, EuiPanel, EuiHorizontalRule, EuiSpacer } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import React, { useMemo } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import * as Rx from 'rxjs'; +import classNames from 'classnames'; +import { WorkspacesStart } from 'src/core/public/workspace'; +import { ChromeNavControl, ChromeNavLink } from '../..'; +import { AppCategory, NavGroupType } from '../../../../types'; +import { InternalApplicationStart } from '../../../application/types'; +import { HttpStart } from '../../../http'; +import { OnIsLockedUpdate } from './'; +import { createEuiListItem } from './nav_link'; +import type { Logos } from '../../../../common/types'; +import { + ChromeNavGroupServiceStartContract, + ChromeRegistrationNavLink, + NavGroupItemInMap, +} from '../../nav_group'; +import { fulfillRegistrationLinksToChromeNavLinks, getVisibleUseCases, sortBy } from '../../utils'; +import { ALL_USE_CASE_ID, DEFAULT_APP_CATEGORIES } from '../../../../../core/utils'; +import { CollapsibleNavTop } from './collapsible_nav_group_enabled_top'; +import { HeaderNavControls } from './header_nav_controls'; +import { NavGroups } from './collapsible_nav_groups'; + +export interface CollapsibleNavGroupEnabledProps { + appId$: InternalApplicationStart['currentAppId$']; + collapsibleNavHeaderRender?: () => JSX.Element | null; + basePath: HttpStart['basePath']; + id: string; + isLocked: boolean; + isNavOpen: boolean; + navLinks$: Rx.Observable; + storage?: Storage; + onIsLockedUpdate: OnIsLockedUpdate; + closeNav: () => void; + navigateToApp: InternalApplicationStart['navigateToApp']; + navigateToUrl: InternalApplicationStart['navigateToUrl']; + customNavLink$: Rx.Observable; + logos: Logos; + navGroupsMap$: Rx.Observable>; + navControlsLeftBottom$: Rx.Observable; + currentNavGroup$: Rx.Observable; + setCurrentNavGroup: ChromeNavGroupServiceStartContract['setCurrentNavGroup']; + capabilities: InternalApplicationStart['capabilities']; + currentWorkspace$: WorkspacesStart['currentWorkspace$']; +} + +const titleForSeeAll = i18n.translate('core.ui.primaryNav.seeAllLabel', { + defaultMessage: 'See all...', +}); + +// Custom category is used for those features not belong to any of use cases in all use case. +// and the custom category should always sit after manage category +const customCategory: AppCategory = { + id: 'custom', + label: i18n.translate('core.ui.customNavList.label', { defaultMessage: 'Custom' }), + order: (DEFAULT_APP_CATEGORIES.manage.order || 0) + 500, +}; + +enum NavWidth { + Expanded = 270, + Collapsed = 48, // The Collasped width is supposed to be aligned with the hamburger icon on the top left navigation. +} + +export function CollapsibleNavGroupEnabled({ + basePath, + id, + isLocked, + isNavOpen, + storage = window.localStorage, + onIsLockedUpdate, + closeNav, + navigateToApp, + navigateToUrl, + logos, + setCurrentNavGroup, + capabilities, + collapsibleNavHeaderRender, + ...observables +}: CollapsibleNavGroupEnabledProps) { + const allNavLinks = useObservable(observables.navLinks$, []); + const navLinks = allNavLinks.filter((link) => !link.hidden); + const homeLink = useMemo(() => allNavLinks.find((item) => item.id === 'home'), [allNavLinks]); + const appId = useObservable(observables.appId$, ''); + const navGroupsMap = useObservable(observables.navGroupsMap$, {}); + const currentNavGroup = useObservable(observables.currentNavGroup$, undefined); + const firstVisibleNavLinkOfAllUseCase = useMemo( + () => + fulfillRegistrationLinksToChromeNavLinks( + navGroupsMap[ALL_USE_CASE_ID]?.navLinks || [], + navLinks + )[0], + [navGroupsMap, navLinks] + ); + + const visibleUseCases = useMemo(() => getVisibleUseCases(navGroupsMap), [navGroupsMap]); + + const currentNavGroupId = useMemo(() => { + if (!currentNavGroup) { + if (visibleUseCases.length === 1) { + return visibleUseCases[0].id; + } + + if (!capabilities.workspaces.enabled) { + return ALL_USE_CASE_ID; + } + } + + return currentNavGroup?.id; + }, [capabilities, currentNavGroup, visibleUseCases]); + + const shouldAppendManageCategory = capabilities.workspaces.enabled + ? !currentNavGroupId + : currentNavGroupId === ALL_USE_CASE_ID; + + const shouldShowCollapsedNavHeaderContent = + isNavOpen && !!collapsibleNavHeaderRender && !currentNavGroupId; + + const navLinksForRender: ChromeNavLink[] = useMemo(() => { + const getSystemNavGroups = () => { + const result: ChromeNavLink[] = []; + Object.values(navGroupsMap) + .sort(sortBy('order')) + .forEach((navGroup) => { + if (navGroup.type !== NavGroupType.SYSTEM) { + return; + } + const visibleNavLinksWithinNavGroup = fulfillRegistrationLinksToChromeNavLinks( + navGroup.navLinks, + navLinks + ); + /** + * We will take the first visible app inside the system nav groups + * when customers click the menu. If there is not a visible nav links, + * we should not show the nav group. + */ + if (visibleNavLinksWithinNavGroup[0]) { + result.push({ + ...visibleNavLinksWithinNavGroup[0], + title: navGroup.title, + category: DEFAULT_APP_CATEGORIES.manage, + }); + } + }); + + return result; + }; + + const navLinksResult: ChromeRegistrationNavLink[] = []; + + if (currentNavGroupId && currentNavGroupId !== ALL_USE_CASE_ID) { + navLinksResult.push(...(navGroupsMap[currentNavGroupId].navLinks || [])); + } + + if (currentNavGroupId === ALL_USE_CASE_ID) { + // Append all the links that do not have use case info to keep backward compatible + const linkIdsWithNavGroupInfo = Object.values(navGroupsMap).reduce((total, navGroup) => { + return [...total, ...navGroup.navLinks.map((navLink) => navLink.id)]; + }, [] as string[]); + navLinks.forEach((navLink) => { + if (linkIdsWithNavGroupInfo.includes(navLink.id)) { + return; + } + navLinksResult.push({ + ...navLink, + category: customCategory, + }); + }); + + // Append all the links registered to all use case + navGroupsMap[ALL_USE_CASE_ID]?.navLinks.forEach((navLink) => { + navLinksResult.push(navLink); + }); + + // Append use case section into left navigation + Object.values(navGroupsMap).forEach((group) => { + if (group.type) { + return; + } + const categoryInfo = { + id: group.id, + label: group.title, + order: group.order, + }; + + const fulfilledLinksOfNavGroup = fulfillRegistrationLinksToChromeNavLinks( + group.navLinks, + navLinks + ); + + const linksForAllUseCaseWithinNavGroup: ChromeRegistrationNavLink[] = []; + + fulfilledLinksOfNavGroup.forEach((navLink) => { + if (!navLink.showInAllNavGroup) { + return; + } + + linksForAllUseCaseWithinNavGroup.push({ + ...navLink, + category: categoryInfo, + }); + }); + + navLinksResult.push(...linksForAllUseCaseWithinNavGroup); + + if (linksForAllUseCaseWithinNavGroup.length) { + navLinksResult.push({ + id: fulfilledLinksOfNavGroup[0].id, + title: titleForSeeAll, + order: Number.MAX_SAFE_INTEGER, + category: categoryInfo, + }); + } else { + /** + * Find if there are any links inside a use case but without a `see all` entry. + * If so, append these features into custom category as a fallback + */ + fulfillRegistrationLinksToChromeNavLinks(group.navLinks, navLinks).forEach((navLink) => { + // Links that already exists in all use case do not need to reappend + if (navLinksResult.find((navLinkInAll) => navLinkInAll.id === navLink.id)) { + return; + } + navLinksResult.push({ + ...navLink, + category: customCategory, + }); + }); + } + }); + } + + if (shouldAppendManageCategory) { + navLinksResult.push(...getSystemNavGroups()); + } + + return fulfillRegistrationLinksToChromeNavLinks(navLinksResult, navLinks); + }, [navLinks, navGroupsMap, currentNavGroupId, shouldAppendManageCategory]); + + const width = useMemo(() => { + if (!isNavOpen) { + return NavWidth.Collapsed; + } + + return NavWidth.Expanded; + }, [isNavOpen]); + + const onGroupClick = ( + e: React.MouseEvent, + group: NavGroupItemInMap + ) => { + const fulfilledLinks = fulfillRegistrationLinksToChromeNavLinks( + navGroupsMap[group.id]?.navLinks, + navLinks + ); + setCurrentNavGroup(group.id); + + // the `navGroupsMap[group.id]?.navLinks` has already been sorted + const firstLink = fulfilledLinks[0]; + if (firstLink) { + const propsForEui = createEuiListItem({ + link: firstLink, + appId, + dataTestSubj: 'collapsibleNavAppLink', + navigateToApp, + }); + propsForEui.onClick(e); + } + }; + + return ( + +
+ {!isNavOpen ? null : ( + + + + )} + {!isNavOpen ? null : ( + + {shouldShowCollapsedNavHeaderContent && collapsibleNavHeaderRender ? ( + <> + {collapsibleNavHeaderRender()} + + + ) : null} + { + if (navItem.title === titleForSeeAll && navItem.category?.id) { + const navGroup = navGroupsMap[navItem.category.id]; + onGroupClick(event, navGroup); + } + }} + appId={appId} + /> + + )} + { + // This element is used to push icons to the bottom of left navigation when collapsed + !isNavOpen ?
: null + } + +
+ +
+
+ + ); +} diff --git a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.test.tsx b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.test.tsx new file mode 100644 index 000000000000..d5dac8cc471b --- /dev/null +++ b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.test.tsx @@ -0,0 +1,82 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; +import { ChromeNavLink } from '../../nav_links'; +import { ChromeRegistrationNavLink } from '../../nav_group'; +import { httpServiceMock } from '../../../mocks'; +import { getLogos } from '../../../../common'; +import { CollapsibleNavTop } from './collapsible_nav_group_enabled_top'; +import { BehaviorSubject } from 'rxjs'; +import { WorkspaceObject } from 'src/core/public/workspace'; +import { ALL_USE_CASE_ID } from '../../../'; + +const mockBasePath = httpServiceMock.createSetupContract({ basePath: '/test' }).basePath; + +describe('', () => { + const getMockedNavLink = ( + navLink: Partial + ): ChromeNavLink & ChromeRegistrationNavLink => ({ + baseUrl: '', + href: '', + id: '', + title: '', + ...navLink, + }); + const getMockedProps = () => { + return { + homeLink: getMockedNavLink({ id: 'home', title: 'Home', href: '/' }), + navigateToApp: jest.fn(), + logos: getLogos({}, mockBasePath.serverBasePath), + shouldShrinkNavigation: false, + visibleUseCases: [], + currentWorkspace$: new BehaviorSubject(null), + setCurrentNavGroup: jest.fn(), + }; + }; + + it('should render back icon when inside a workspace of all use case', async () => { + const props = { + ...getMockedProps(), + currentWorkspace$: new BehaviorSubject({ id: 'foo', name: 'foo' }), + visibleUseCases: [ + { + id: ALL_USE_CASE_ID, + title: 'navGroupFoo', + description: 'navGroupFoo', + navLinks: [], + }, + ], + currentNavGroup: { + id: 'navGroupFoo', + title: 'navGroupFoo', + description: 'navGroupFoo', + navLinks: [], + }, + firstVisibleNavLinkOfAllUseCase: getMockedNavLink({ + id: 'firstVisibleNavLinkOfAllUseCase', + }), + }; + const { findByTestId, findByText, getByTestId } = render(); + await findByTestId('collapsibleNavBackButton'); + await findByText('Back'); + fireEvent.click(getByTestId('collapsibleNavBackButton')); + expect(props.navigateToApp).toBeCalledWith('firstVisibleNavLinkOfAllUseCase'); + expect(props.setCurrentNavGroup).toBeCalledWith(ALL_USE_CASE_ID); + }); + + it('should render home icon when not in a workspace', async () => { + const { findByTestId } = render(); + await findByTestId('collapsibleNavHome'); + }); + + it('should render expand icon when collapsed', async () => { + const { findByTestId } = render( + + ); + await findByTestId('collapsibleNavShrinkButton'); + }); +}); diff --git a/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx new file mode 100644 index 000000000000..067fb2ffd2e1 --- /dev/null +++ b/src/core/public/chrome/ui/header/collapsible_nav_group_enabled_top.tsx @@ -0,0 +1,136 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useMemo } from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { Logos, WorkspacesStart } from 'opensearch-dashboards/public'; +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSpacer, + EuiText, +} from '@elastic/eui'; +import { InternalApplicationStart } from 'src/core/public/application'; +import { i18n } from '@osd/i18n'; +import { createEuiListItem } from './nav_link'; +import { ChromeNavGroupServiceStartContract, NavGroupItemInMap } from '../../nav_group'; +import { ChromeNavLink } from '../../nav_links'; +import { ALL_USE_CASE_ID } from '../../../../../core/utils'; + +export interface CollapsibleNavTopProps { + homeLink?: ChromeNavLink; + firstVisibleNavLinkOfAllUseCase?: ChromeNavLink; + currentNavGroup?: NavGroupItemInMap; + navigateToApp: InternalApplicationStart['navigateToApp']; + logos: Logos; + onClickShrink?: () => void; + shouldShrinkNavigation: boolean; + visibleUseCases: NavGroupItemInMap[]; + currentWorkspace$: WorkspacesStart['currentWorkspace$']; + setCurrentNavGroup: ChromeNavGroupServiceStartContract['setCurrentNavGroup']; +} + +export const CollapsibleNavTop = ({ + currentNavGroup, + navigateToApp, + logos, + onClickShrink, + shouldShrinkNavigation, + visibleUseCases, + currentWorkspace$, + setCurrentNavGroup, + homeLink, + firstVisibleNavLinkOfAllUseCase, +}: CollapsibleNavTopProps) => { + const currentWorkspace = useObservable(currentWorkspace$); + + /** + * We can ensure that left nav is inside second level once all the following conditions are met: + * 1. Inside a workspace + * 2. The use case type of current workspace is all use case + * 3. current nav group is not all use case + */ + const isInsideSecondLevelOfAllWorkspace = + !!currentWorkspace && + visibleUseCases[0].id === ALL_USE_CASE_ID && + currentNavGroup?.id !== ALL_USE_CASE_ID; + + const shouldShowBackButton = !shouldShrinkNavigation && isInsideSecondLevelOfAllWorkspace; + const shouldShowHomeLink = !shouldShrinkNavigation && !shouldShowBackButton; + + const homeLinkProps = useMemo(() => { + if (homeLink) { + const propsForHomeIcon = createEuiListItem({ + link: homeLink, + appId: 'home', + dataTestSubj: 'collapsibleNavHome', + navigateToApp, + }); + return { + 'data-test-subj': propsForHomeIcon['data-test-subj'], + onClick: propsForHomeIcon.onClick, + href: propsForHomeIcon.href, + }; + } + + return {}; + }, [homeLink, navigateToApp]); + + return ( +
+ + {shouldShowHomeLink ? ( + + + + + + ) : null} + {shouldShowBackButton ? ( + + { + if (firstVisibleNavLinkOfAllUseCase) { + navigateToApp(firstVisibleNavLinkOfAllUseCase.id); + } + setCurrentNavGroup(ALL_USE_CASE_ID); + }} + data-test-subj="collapsibleNavBackButton" + > + + {i18n.translate('core.ui.primaryNav.backButtonLabel', { + defaultMessage: 'Back', + })} + + + ) : null} + + + + + {currentNavGroup?.title && ( + <> + + +
+ {currentNavGroup?.title} +
+
+ + )} +
+ ); +}; diff --git a/src/core/public/chrome/ui/header/collapsible_nav_groups.test.tsx b/src/core/public/chrome/ui/header/collapsible_nav_groups.test.tsx new file mode 100644 index 000000000000..75865190cad8 --- /dev/null +++ b/src/core/public/chrome/ui/header/collapsible_nav_groups.test.tsx @@ -0,0 +1,79 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { fireEvent, render } from '@testing-library/react'; +import { NavGroups } from './collapsible_nav_groups'; +import { ChromeRegistrationNavLink } from '../../nav_group'; +import { ChromeNavLink } from '../../nav_links'; + +describe('', () => { + const getMockedNavLink = ( + navLink: Partial + ): ChromeNavLink & ChromeRegistrationNavLink => ({ + baseUrl: '', + href: '', + id: '', + title: '', + ...navLink, + }); + it('should render correctly', () => { + const navigateToApp = jest.fn(); + const onNavItemClick = jest.fn(); + const { container, getByTestId, queryByTestId } = render( + + ); + expect(container).toMatchSnapshot(); + expect(container.querySelectorAll('.nav-link-item-btn').length).toEqual(5); + fireEvent.click(getByTestId('collapsibleNavAppLink-pure')); + expect(navigateToApp).toBeCalledTimes(0); + // The accordion is collapsed + expect(queryByTestId('collapsibleNavAppLink-subLink')).toBeNull(); + + // Expand the accordion + fireEvent.click(getByTestId('collapsibleNavAppLink-pure')); + fireEvent.click(getByTestId('collapsibleNavAppLink-subLink')); + expect(navigateToApp).toBeCalledWith('subLink'); + }); +}); diff --git a/src/core/public/chrome/ui/header/collapsible_nav_groups.tsx b/src/core/public/chrome/ui/header/collapsible_nav_groups.tsx new file mode 100644 index 000000000000..53a75aeaaddd --- /dev/null +++ b/src/core/public/chrome/ui/header/collapsible_nav_groups.tsx @@ -0,0 +1,150 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import './collapsible_nav_group_enabled.scss'; +import { EuiFlexItem, EuiSideNavItemType, EuiSideNav, EuiText } from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import React from 'react'; +import classNames from 'classnames'; +import { ChromeNavLink } from '../..'; +import { InternalApplicationStart } from '../../../application/types'; +import { createEuiListItem } from './nav_link'; +import { getOrderedLinksOrCategories, LinkItem, LinkItemType } from '../../utils'; + +export interface NavGroupsProps { + navLinks: ChromeNavLink[]; + suffix?: React.ReactElement; + style?: React.CSSProperties; + appId?: string; + navigateToApp: InternalApplicationStart['navigateToApp']; + onNavItemClick: ( + event: React.MouseEvent, + navItem: ChromeNavLink + ) => void; +} + +const titleForSeeAll = i18n.translate('core.ui.primaryNav.seeAllLabel', { + defaultMessage: 'See all...', +}); + +const LEVEL_FOR_ROOT_ITEMS = 1; + +export function NavGroups({ + navLinks, + suffix, + style, + appId, + navigateToApp, + onNavItemClick, +}: NavGroupsProps) { + const createNavItem = ({ + link, + className, + }: { + link: ChromeNavLink; + className?: string; + }): EuiSideNavItemType<{}> => { + const euiListItem = createEuiListItem({ + link, + appId, + dataTestSubj: `collapsibleNavAppLink-${link.id}`, + navigateToApp, + onClick: (event) => { + onNavItemClick(event, link); + }, + }); + + return { + id: `${link.id}-${link.title}`, + name: {link.title}, + onClick: euiListItem.onClick, + href: euiListItem.href, + emphasize: euiListItem.isActive, + className: `nav-link-item ${className || ''}`, + buttonClassName: 'nav-link-item-btn', + 'data-test-subj': euiListItem['data-test-subj'], + 'aria-label': link.title, + }; + }; + const createSideNavItem = ( + navLink: LinkItem, + level: number, + className?: string + ): EuiSideNavItemType<{}> => { + if (navLink.itemType === LinkItemType.LINK) { + if (navLink.link.title === titleForSeeAll) { + const navItem = createNavItem({ + link: navLink.link, + }); + + return { + ...navItem, + name: {navItem.name}, + emphasize: false, + }; + } + + return createNavItem({ + link: navLink.link, + className, + }); + } + + if (navLink.itemType === LinkItemType.PARENT_LINK && navLink.link) { + const props = createNavItem({ link: navLink.link }); + const parentItem = { + ...props, + forceOpen: true, + /** + * The href and onClick should both be undefined to make parent item rendered as accordion. + */ + href: undefined, + onClick: undefined, + className: classNames(props.className, 'nav-link-parent-item'), + buttonClassName: classNames(props.buttonClassName, 'nav-link-parent-item-button'), + items: navLink.links.map((subNavLink) => + createSideNavItem(subNavLink, level + 1, 'nav-nested-item') + ), + }; + /** + * OuiSideBar will never render items of first level as accordion, + * in order to display accordion, we need to render a fake parent item. + */ + if (level === LEVEL_FOR_ROOT_ITEMS) { + return { + className: 'nav-link-fake-item', + buttonClassName: 'nav-link-fake-item-button', + name: '', + items: [parentItem], + id: `fake_${props.id}`, + }; + } + + return parentItem; + } + + if (navLink.itemType === LinkItemType.CATEGORY) { + return { + id: navLink.category?.id ?? '', + name:
{navLink.category?.label ?? ''}
, + items: navLink.links?.map((link) => createSideNavItem(link, level + 1)), + 'aria-label': navLink.category?.label, + }; + } + + return {} as EuiSideNavItemType<{}>; + }; + const orderedLinksOrCategories = getOrderedLinksOrCategories(navLinks); + const sideNavItems = orderedLinksOrCategories + .map((navLink) => createSideNavItem(navLink, LEVEL_FOR_ROOT_ITEMS)) + .filter((navItem) => !!navItem.id); + + return ( + + + {suffix} + + ); +} diff --git a/src/core/public/chrome/ui/header/header.scss b/src/core/public/chrome/ui/header/header.scss new file mode 100644 index 000000000000..9b0bffe633fe --- /dev/null +++ b/src/core/public/chrome/ui/header/header.scss @@ -0,0 +1,107 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +*/ + +.newTopNavHeader { + z-index: 1000; + padding: 0 $euiSize $euiSizeS; + box-shadow: none; + border-bottom: none; + background: none; + height: auto; + gap: $euiSizeS; + + &.primaryHeader { + margin-top: $euiSizeM; + } + + &.primaryApplicationHeader { + padding-top: $euiSizeM - $euiSizeXS; + border-bottom: 1px solid $euiColorLightShade; + + &:last-child { + margin-bottom: 0; + } + } + + .headerAppActionMenu { + // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors + & > .euiFlexGroup { + gap: $euiSizeS; + } + + // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors + & > .euiHeaderLinks > .euiHeaderLinks__list { + gap: $euiSizeS; + + & > * { + margin: 0; + } + } + } + + &:has(.headerDescriptionControl, .headerBottomControl) { + height: auto; + } + + // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors + & > .euiHeaderSection { + gap: $euiSizeS; + + &:only-child { + width: 100%; + } + + // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors + & > .euiHeaderSectionItem:empty { + display: none; + } + } + + // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors + &:not(:has(>:not(:empty))), + .euiHeaderSectionItem:not(:has(>:not(:empty))) { + display: none; + } +} + +.headerGlobalNav:has(.newTopNavHeader:not(.primaryApplicationHeader)) { + margin-bottom: -1 * $euiSizeS; +} + +.newTopNavHeaderTitle { + line-height: 1; + font-size: 2rem; +} + +.primaryApplicationHeader { + // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors + .euiHeaderSection > .euiHeaderSectionItem { + align-items: stretch; + } + + .headerAppActionMenuSection { + flex-grow: 1; + } + + .headerAppActionMenu { + width: 100%; + } +} + +.newAppTopNavExpander { + position: fixed; + left: 0; + top: $euiSize; +} + +.newPageTopNavExpander { + position: fixed; + left: 0; + top: $euiSize; +} + +.stretchedActionMenu { + width: 100%; +} diff --git a/src/core/public/chrome/ui/header/header.test.tsx b/src/core/public/chrome/ui/header/header.test.tsx index 0d3bc7e70d45..7edf893826ac 100644 --- a/src/core/public/chrome/ui/header/header.test.tsx +++ b/src/core/public/chrome/ui/header/header.test.tsx @@ -28,15 +28,18 @@ * under the License. */ +import { EuiHeaderSectionItemButton } from '@elastic/eui'; import React from 'react'; import { act } from 'react-dom/test-utils'; import { BehaviorSubject } from 'rxjs'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; import { httpServiceMock } from '../../../http/http_service.mock'; import { applicationServiceMock, chromeServiceMock } from '../../../mocks'; -import { Header } from './header'; -import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; import { ISidecarConfig, SIDECAR_DOCKED_MODE } from '../../../overlays'; +import { WorkspaceObject } from 'src/core/public/workspace'; +import { HeaderVariant } from '../../constants'; +import { Header } from './header'; jest.mock('@elastic/eui/lib/services/accessibility/html_id_generator', () => ({ htmlIdGenerator: () => () => 'mockId', @@ -52,8 +55,10 @@ function mockProps() { appTitle$: new BehaviorSubject('test'), badge$: new BehaviorSubject(undefined), breadcrumbs$: new BehaviorSubject([]), + breadcrumbsEnricher$: new BehaviorSubject(undefined), homeHref: '/', isVisible$: new BehaviorSubject(true), + headerVariant$: new BehaviorSubject(undefined), opensearchDashboardsDocLink: '/docs', navLinks$: new BehaviorSubject([]), customNavLink$: new BehaviorSubject(undefined), @@ -77,6 +82,14 @@ function mockProps() { dockedMode: SIDECAR_DOCKED_MODE.RIGHT, paddingSize: 640, }), + navGroupEnabled: false, + currentNavGroup$: new BehaviorSubject(undefined), + navGroupsMap$: new BehaviorSubject({}), + navControlsLeftBottom$: new BehaviorSubject([]), + setCurrentNavGroup: jest.fn(() => {}), + workspaceList$: new BehaviorSubject([]), + currentWorkspace$: new BehaviorSubject(null), + useUpdatedHeader: false, }; } @@ -166,4 +179,81 @@ describe('Header', () => { expect(component).toMatchSnapshot(); }); + + it('renders new header when feature flag is turned on', () => { + const branding = { + useExpandedHeader: false, + }; + const props = { + ...mockProps(), + branding, + }; + + const component = mountWithIntl(
); + + expect(component.find('CollapsibleNavGroupEnabled').exists()).toBeTruthy(); + }); + + it('toggles primary navigation menu when clicked', () => { + const branding = { + useExpandedHeader: false, + }; + const props = { + ...mockProps(), + branding, + }; + const component = mountWithIntl(
); + component.find(EuiHeaderSectionItemButton).first().simulate('click'); + expect(component).toMatchSnapshot(); + }); + + it('renders page header with application title', () => { + const branding = { + useExpandedHeader: false, + }; + const useUpdatedHeader = true; + const breadcrumbs$ = new BehaviorSubject([{ text: 'test' }, { text: 'testTitle' }]); + const props = { + ...mockProps(), + breadcrumbs$, + branding, + useUpdatedHeader, + }; + const component = mountWithIntl(
); + expect(component.find('[data-test-subj="headerApplicationTitle"]').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="breadcrumb first"]').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="headerBadgeControl"]').exists()).toBeTruthy(); + expect(component.find('HeaderBadge').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="headerLeftControl"]').exists()).toBeTruthy(); + expect(component.find('HeaderNavControls').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="headerCenterControl"]').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="headerRightControl"]').exists()).toBeTruthy(); + expect(component.find('HeaderActionMenu').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="headerDescriptionControl"]').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="headerBottomControl"]').exists()).toBeTruthy(); + expect(component).toMatchSnapshot(); + }); + + it('renders application header without title and breadcrumbs', () => { + const branding = { + useExpandedHeader: false, + }; + const useUpdatedHeader = true; + const headerVariant$ = new BehaviorSubject(HeaderVariant.APPLICATION); + const breadcrumbs$ = new BehaviorSubject([{ text: 'test' }, { text: 'testTitle' }]); + const props = { + ...mockProps(), + breadcrumbs$, + branding, + useUpdatedHeader, + headerVariant$, + }; + const component = mountWithIntl(
); + expect(component.find('[data-test-subj="headerApplicationTitle"]').exists()).toBeFalsy(); + expect(component.find('[data-test-subj="breadcrumb first"]').exists()).toBeFalsy(); + expect(component.find('HeaderActionMenu').exists()).toBeTruthy(); + expect(component.find('RecentItems').exists()).toBeTruthy(); + expect(component.find('[data-test-subj="headerRightControl"]').exists()).toBeTruthy(); + expect(component).toMatchSnapshot(); + }); }); diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index b8b40fa6c39f..6fa8d9a43a75 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -27,7 +27,6 @@ * specific language governing permissions and limitations * under the License. */ - import { EuiHeader, EuiHeaderProps, @@ -37,6 +36,7 @@ import { EuiHideFor, EuiIcon, EuiShowFor, + EuiTitle, htmlIdGenerator, } from '@elastic/eui'; import { i18n } from '@osd/i18n'; @@ -51,31 +51,45 @@ import { ChromeNavControl, ChromeNavLink, ChromeRecentlyAccessedHistoryItem, + HeaderVariant, } from '../..'; +import type { Logos } from '../../../../common/types'; +import { WorkspaceObject, WorkspacesStart } from '../../../../public/workspace'; import { InternalApplicationStart } from '../../../application/types'; import { HttpStart } from '../../../http'; -import { ChromeHelpExtension, ChromeBranding } from '../../chrome_service'; +import { getOsdSidecarPaddingStyle, ISidecarConfig } from '../../../overlays'; +import { + ChromeBranding, + ChromeBreadcrumbEnricher, + ChromeHelpExtension, +} from '../../chrome_service'; +import { ChromeNavGroupServiceStartContract, NavGroupItemInMap } from '../../nav_group'; import { OnIsLockedUpdate } from './'; import { CollapsibleNav } from './collapsible_nav'; +import { CollapsibleNavGroupEnabled } from './collapsible_nav_group_enabled'; +import './header.scss'; +import { HeaderActionMenu } from './header_action_menu'; import { HeaderBadge } from './header_badge'; import { HeaderBreadcrumbs } from './header_breadcrumbs'; +import { HeaderControlsContainer } from './header_controls_container'; import { HeaderHelpMenu } from './header_help_menu'; -import { HomeLoader } from './home_loader'; -import { HeaderNavControls } from './header_nav_controls'; -import { HeaderActionMenu } from './header_action_menu'; import { HeaderLogo } from './header_logo'; -import type { Logos } from '../../../../common/types'; -import { ISidecarConfig, getOsdSidecarPaddingStyle } from '../../../overlays'; +import { HeaderNavControls } from './header_nav_controls'; +import { HomeLoader } from './home_loader'; +import { RecentItems } from './recent_items'; + export interface HeaderProps { opensearchDashboardsVersion: string; application: InternalApplicationStart; appTitle$: Observable; badge$: Observable; breadcrumbs$: Observable; + breadcrumbsEnricher$: Observable; collapsibleNavHeaderRender?: () => JSX.Element | null; customNavLink$: Observable; homeHref: string; isVisible$: Observable; + headerVariant$: Observable; opensearchDashboardsDocLink: string; navLinks$: Observable; recentlyAccessed$: Observable; @@ -87,6 +101,7 @@ export interface HeaderProps { navControlsRight$: Observable; navControlsExpandedCenter$: Observable; navControlsExpandedRight$: Observable; + navControlsLeftBottom$: Observable; basePath: HttpStart['basePath']; isLocked$: Observable; loadingCount$: ReturnType; @@ -95,6 +110,13 @@ export interface HeaderProps { logos: Logos; survey: string | undefined; sidecarConfig$: Observable; + navGroupEnabled: boolean; + currentNavGroup$: Observable; + navGroupsMap$: Observable>; + setCurrentNavGroup: ChromeNavGroupServiceStartContract['setCurrentNavGroup']; + workspaceList$: Observable; + currentWorkspace$: WorkspacesStart['currentWorkspace$']; + useUpdatedHeader?: boolean; } export function Header({ @@ -108,12 +130,17 @@ export function Header({ survey, logos, collapsibleNavHeaderRender, + navGroupEnabled, + setCurrentNavGroup, + useUpdatedHeader, ...observables }: HeaderProps) { const isVisible = useObservable(observables.isVisible$, false); + const headerVariant = useObservable(observables.headerVariant$, HeaderVariant.PAGE); const isLocked = useObservable(observables.isLocked$, false); const [isNavOpen, setIsNavOpen] = useState(false); const sidecarConfig = useObservable(observables.sidecarConfig$, undefined); + const breadcrumbs = useObservable(observables.breadcrumbs$, []); const sidecarPaddingStyle = useMemo(() => { return getOsdSidecarPaddingStyle(sidecarConfig); @@ -127,154 +154,365 @@ export function Header({ const navId = htmlIdGenerator()(); const className = classnames('hide-for-sharing', 'headerGlobalNav'); const { useExpandedHeader = true } = branding; + const useApplicationHeader = headerVariant === HeaderVariant.APPLICATION; const expandedHeaderColorScheme: EuiHeaderProps['theme'] = 'dark'; + const renderLegacyExpandedHeader = () => ( + , + ], + borders: 'none', + }, + { + items: [ + + + , + ], + borders: 'none', + }, + { + items: [ + + + , + , + ], + borders: 'none', + }, + ]} + /> + ); + + const renderBreadcrumbs = (renderFullLength?: boolean) => ( + + ); + + const renderNavToggle = () => ( + setIsNavOpen(!isNavOpen)} + aria-expanded={isNavOpen} + aria-pressed={isNavOpen} + aria-controls={navId} + ref={toggleCollapsibleNavRef} + className={ + useUpdatedHeader + ? useApplicationHeader + ? 'newAppTopNavExpander' + : 'newPageTopNavExpander' + : undefined + } + > + + + ); + + const renderLeftControls = () => ( + <> + {useUpdatedHeader && ( + + + + )} + + {/* Nav controls left */} + + + + + ); + + const renderCenterControls = () => ( + <> + {useUpdatedHeader && ( + + + + )} + + {useUpdatedHeader && ( + + + + )} + + + + + + ); + + const renderRightControls = () => ( + <> + {useUpdatedHeader && ( + + + + )} + + {useUpdatedHeader && ( + + + + )} + + + + + + ); + + const renderActionMenu = () => ( + + + + ); + + const renderBadge = () => ( + <> + {useUpdatedHeader && ( + + + + )} + + {/* Nav controls badge */} + + + + + ); + + const renderHelp = () => ( + + + + ); + + const renderRecentItems = () => ( + + + + ); + + const renderLegacyHeader = () => ( + + + + {renderNavToggle()} + + + {renderLeftControls()} + + {/* Home loader left */} + + + + + + {renderBreadcrumbs()} + {renderBadge()} + + + {renderActionMenu()} + {renderCenterControls()} + {renderRightControls()} + {renderHelp()} + + + ); + + const renderPageHeader = () => ( +
+ + {isNavOpen ? null : renderNavToggle()} + + {renderRecentItems()} + + {renderBreadcrumbs()} + + + {/* Secondary header */} + + + + + {breadcrumbs &&

{breadcrumbs[breadcrumbs.length - 1]?.text}

} +
+
+ + {renderBadge()} + {renderLeftControls()} +
+ + + {renderCenterControls()} + {renderActionMenu()} + {renderRightControls()} + +
+ + + + + + + + +
+ ); + + const renderApplicationHeader = () => ( +
+ + {isNavOpen ? null : renderNavToggle()} + + {renderRecentItems()} + {renderActionMenu()} + + {renderRightControls()} + +
+ ); + + const renderHeader = () => { + return useApplicationHeader ? renderApplicationHeader() : renderPageHeader(); + }; + return ( <>
- {useExpandedHeader && ( - , - ], - borders: 'none', - }, - { - items: [ - - - , - ], - borders: 'none', - }, - { - items: [ - - - , - , - ], - borders: 'none', - }, - ]} - /> - )} - - - - - setIsNavOpen(!isNavOpen)} - aria-expanded={isNavOpen} - aria-pressed={isNavOpen} - aria-controls={navId} - ref={toggleCollapsibleNavRef} - > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {!useUpdatedHeader && useExpandedHeader && renderLegacyExpandedHeader()} + {useUpdatedHeader ? renderHeader() : renderLegacyHeader()}
- { - setIsNavOpen(false); - if (toggleCollapsibleNavRef.current) { - toggleCollapsibleNavRef.current.focus(); - } - }} - customNavLink$={observables.customNavLink$} - logos={logos} - /> + {navGroupEnabled ? ( + { + setIsNavOpen(false); + if (toggleCollapsibleNavRef.current) { + toggleCollapsibleNavRef.current.focus(); + } + }} + customNavLink$={observables.customNavLink$} + logos={logos} + navGroupsMap$={observables.navGroupsMap$} + navControlsLeftBottom$={observables.navControlsLeftBottom$} + currentNavGroup$={observables.currentNavGroup$} + setCurrentNavGroup={setCurrentNavGroup} + capabilities={application.capabilities} + currentWorkspace$={observables.currentWorkspace$} + /> + ) : ( + { + setIsNavOpen(false); + if (toggleCollapsibleNavRef.current) { + toggleCollapsibleNavRef.current.focus(); + } + }} + customNavLink$={observables.customNavLink$} + logos={logos} + /> + )}
); diff --git a/src/core/public/chrome/ui/header/header_action_menu.test.tsx b/src/core/public/chrome/ui/header/header_action_menu.test.tsx index cf7c1f0f89d1..1adfbc02d6c5 100644 --- a/src/core/public/chrome/ui/header/header_action_menu.test.tsx +++ b/src/core/public/chrome/ui/header/header_action_menu.test.tsx @@ -79,7 +79,7 @@ describe('HeaderActionMenu', () => { await refresh(); expect(component.html()).toMatchInlineSnapshot( - `"
FOO
"` + `"
FOO
"` ); }); @@ -92,7 +92,7 @@ describe('HeaderActionMenu', () => { await refresh(); expect(component.html()).toMatchInlineSnapshot( - `"
FOO
"` + `"
FOO
"` ); act(() => { @@ -101,7 +101,7 @@ describe('HeaderActionMenu', () => { await refresh(); expect(component.html()).toMatchInlineSnapshot( - `"
"` + `"
"` ); }); @@ -114,7 +114,7 @@ describe('HeaderActionMenu', () => { await refresh(); expect(component.html()).toMatchInlineSnapshot( - `"
FOO
"` + `"
FOO
"` ); act(() => { @@ -123,7 +123,7 @@ describe('HeaderActionMenu', () => { await refresh(); expect(component.html()).toMatchInlineSnapshot( - `"
BAR
"` + `"
BAR
"` ); }); diff --git a/src/core/public/chrome/ui/header/header_action_menu.tsx b/src/core/public/chrome/ui/header/header_action_menu.tsx index f45a2ac39e1e..92ae9ffcd030 100644 --- a/src/core/public/chrome/ui/header/header_action_menu.tsx +++ b/src/core/public/chrome/ui/header/header_action_menu.tsx @@ -71,5 +71,7 @@ export const HeaderActionMenu: FC = ({ actionMenu$ }) => } }, [mounter]); - return
; + return ( +
+ ); }; diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.scss b/src/core/public/chrome/ui/header/header_breadcrumbs.scss new file mode 100644 index 000000000000..b3f09e9e8a93 --- /dev/null +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.scss @@ -0,0 +1,35 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +*/ + +.headerBreadcrumbs { + margin-left: 0; + color: $euiColorPrimary; + + .euiBreadcrumbWall { + background-image: none; // Removes any background image + } + + .euiBreadcrumbWrapper { + padding-left: 0; + padding-right: 0; + + &.euiBreadcrumbWrapper--last::after, + &:not(.euiBreadcrumbWrapper--last)::after { + content: "/"; + background-color: transparent; + padding: 0 $euiSizeS; + color: $euiColorMediumShade; + } + + &.euiBreadcrumbWrapper--last::before, + &:not(.euiBreadcrumbWrapper--last)::before { + display: none; + } + } + + .euiBreadcrumb { + color: inherit !important; + } +} diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx index 2008a3f6c493..6cc25b392ce3 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx @@ -32,13 +32,18 @@ import { mount } from 'enzyme'; import React from 'react'; import { act } from 'react-dom/test-utils'; import { BehaviorSubject } from 'rxjs'; +import { ChromeBreadcrumb } from '../../chrome_service'; import { HeaderBreadcrumbs } from './header_breadcrumbs'; describe('HeaderBreadcrumbs', () => { it('renders updates to the breadcrumbs$ observable', () => { const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]); const wrapper = mount( - + ); expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); @@ -50,4 +55,57 @@ describe('HeaderBreadcrumbs', () => { wrapper.update(); expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); }); + + it('renders updates to the breadcrumbs$ observable with updated header', () => { + const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]); + const wrapper = mount( + + ); + expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); + expect(wrapper.find('.headerBreadcrumbs').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="breadcrumb first"]').exists()).toBeFalsy(); + + act(() => breadcrumbs$.next([{ text: 'First' }, { text: 'Second' }])); + wrapper.update(); + expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); + expect(wrapper.find('[data-test-subj="breadcrumb first"]').exists()).toBeTruthy(); + + act(() => breadcrumbs$.next([])); + wrapper.update(); + expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); + }); + + it('prepend current nav group into existing breadcrumbs when nav group is enabled', () => { + const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]); + const breadcrumbsEnricher$ = new BehaviorSubject((crumbs: ChromeBreadcrumb[]) => [ + { text: 'Home' }, + { text: 'Analytics' }, + ...crumbs, + ]); + const wrapper = mount( + + ); + const breadcrumbs = wrapper.find('.euiBreadcrumbWrapper'); + expect(breadcrumbs).toHaveLength(3); + expect(breadcrumbs.at(0).text()).toBe('Home'); + expect(breadcrumbs.at(1).text()).toBe('Analytics'); + expect(breadcrumbs.at(2).text()).toBe('First'); + + act(() => breadcrumbs$.next([{ text: 'First' }, { text: 'Second' }])); + wrapper.update(); + expect(wrapper.find('.euiBreadcrumbWrapper')).toHaveLength(4); + + act(() => breadcrumbs$.next([])); + wrapper.update(); + expect(wrapper.find('.euiBreadcrumbWrapper')).toHaveLength(2); + }); }); diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx index ca50b15d5af6..5361db221d10 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx @@ -30,34 +30,69 @@ import { EuiHeaderBreadcrumbs } from '@elastic/eui'; import classNames from 'classnames'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; import useObservable from 'react-use/lib/useObservable'; import { Observable } from 'rxjs'; -import { ChromeBreadcrumb } from '../../chrome_service'; +import { ChromeBreadcrumb, ChromeBreadcrumbEnricher } from '../../chrome_service'; +import './header_breadcrumbs.scss'; interface Props { appTitle$: Observable; breadcrumbs$: Observable; + breadcrumbsEnricher$: Observable; + useUpdatedHeader?: boolean; + renderFullLength?: boolean; } -export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$ }: Props) { +export function HeaderBreadcrumbs({ + appTitle$, + breadcrumbs$, + breadcrumbsEnricher$, + useUpdatedHeader, + renderFullLength, +}: Props) { const appTitle = useObservable(appTitle$, 'OpenSearch Dashboards'); const breadcrumbs = useObservable(breadcrumbs$, []); + const [breadcrumbEnricher, setBreadcrumbEnricher] = useState< + ChromeBreadcrumbEnricher | undefined + >(undefined); + + useEffect(() => { + const sub = breadcrumbsEnricher$.subscribe((enricher) => { + setBreadcrumbEnricher(() => enricher); + }); + return () => sub.unsubscribe(); + }); + let crumbs = breadcrumbs; if (breadcrumbs.length === 0 && appTitle) { crumbs = [{ text: appTitle }]; } + if (breadcrumbEnricher) { + crumbs = breadcrumbEnricher(crumbs); + } + crumbs = crumbs.map((breadcrumb, i) => ({ ...breadcrumb, 'data-test-subj': classNames( 'breadcrumb', breadcrumb['data-test-subj'], i === 0 && 'first', - i === breadcrumbs.length - 1 && 'last' + i === crumbs.length - 1 && 'last' ), })); - return ; + const remainingCrumbs = useUpdatedHeader ? crumbs.slice(0, -1) : crumbs; + const className = useUpdatedHeader ? 'headerBreadcrumbs' : ''; + + return ( + + ); } diff --git a/src/core/public/chrome/ui/header/header_controls_container.scss b/src/core/public/chrome/ui/header/header_controls_container.scss new file mode 100644 index 000000000000..472dab2005de --- /dev/null +++ b/src/core/public/chrome/ui/header/header_controls_container.scss @@ -0,0 +1,32 @@ +/* +* Copyright OpenSearch Contributors +* SPDX-License-Identifier: Apache-2.0 +*/ + +.headerControl { + gap: $euiSizeS; + + &.headerDescriptionControl { + .descriptionHeaderControl { + max-width: $euiLegibilityMaxWidth; + line-height: 1.5; + color: $euiTextSubduedColor; + + // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors + .euiHeaderLink { + vertical-align: unset; + border: 0; + height: auto; + } + } + } + + &:empty { + display: none; + } + + // stylelint-disable-next-line @osd/stylelint/no_modifying_global_selectors + .euiButton { + min-width: auto; + } +} diff --git a/src/core/public/chrome/ui/header/header_controls_container.test.tsx b/src/core/public/chrome/ui/header/header_controls_container.test.tsx new file mode 100644 index 000000000000..189d84f54ae9 --- /dev/null +++ b/src/core/public/chrome/ui/header/header_controls_container.test.tsx @@ -0,0 +1,131 @@ +/* + * 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. + */ + +import { mount, ReactWrapper } from 'enzyme'; +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { BehaviorSubject } from 'rxjs'; +import { MountPoint, UnmountCallback } from '../../../types'; +import { HeaderControlsContainer } from './header_controls_container'; + +type MockedUnmount = jest.MockedFunction; + +describe('HeaderControlsContainer', () => { + let component: ReactWrapper; + let controlMount$: BehaviorSubject; + let unmounts: Record; + + beforeEach(() => { + controlMount$ = new BehaviorSubject(undefined); + unmounts = {}; + }); + + const refresh = () => { + new Promise(async (resolve) => { + if (component) { + act(() => { + component.update(); + }); + } + setImmediate(() => resolve(component)); // flushes any pending promises + }); + }; + + const createMountPoint = (id: string, content: string = id): MountPoint => ( + root + ): MockedUnmount => { + const container = document.createElement('DIV'); + // eslint-disable-next-line no-unsanitized/property + container.innerHTML = content; + root.appendChild(container); + const unmount = jest.fn(() => container.remove()); + unmounts[id] = unmount; + return unmount; + }; + + it('mounts the current value of the provided observable', async () => { + component = mount(); + + act(() => { + controlMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
FOO
"` + ); + }); + + it('clears the content of the component when emitting undefined', async () => { + component = mount(); + + act(() => { + controlMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
FOO
"` + ); + + act(() => { + controlMount$.next(undefined); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
"` + ); + }); + + it('updates the dom when a new mount point is emitted', async () => { + component = mount(); + + act(() => { + controlMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
FOO
"` + ); + + act(() => { + controlMount$.next(createMountPoint('BAR')); + }); + await refresh(); + + expect(component.html()).toMatchInlineSnapshot( + `"
BAR
"` + ); + }); + + it('calls the previous mount point `unmount` when mounting a new mount point', async () => { + component = mount(); + + act(() => { + controlMount$.next(createMountPoint('FOO')); + }); + await refresh(); + + expect(Object.keys(unmounts)).toEqual(['FOO']); + expect(unmounts.FOO).not.toHaveBeenCalled(); + + act(() => { + controlMount$.next(createMountPoint('BAR')); + }); + await refresh(); + + expect(Object.keys(unmounts)).toEqual(['FOO', 'BAR']); + expect(unmounts.FOO).toHaveBeenCalledTimes(1); + expect(unmounts.BAR).not.toHaveBeenCalled(); + }); +}); diff --git a/src/core/public/chrome/ui/header/header_controls_container.tsx b/src/core/public/chrome/ui/header/header_controls_container.tsx new file mode 100644 index 000000000000..a2f22db83e78 --- /dev/null +++ b/src/core/public/chrome/ui/header/header_controls_container.tsx @@ -0,0 +1,95 @@ +/* + * 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, { FC, useRef, useLayoutEffect, useState } from 'react'; +import { Observable } from 'rxjs'; +import classNames from 'classnames'; +import { MountPoint, UnmountCallback } from '../../../types'; +import './header_controls_container.scss'; + +interface HeaderControlsContainerProps { + controls$: Observable; + className?: string; + 'data-test-subj'?: string; +} + +export const HeaderControlsContainer: FC = ({ + controls$, + className, + 'data-test-subj': testSubject, +}) => { + // useObservable relies on useState under the hood. The signature is type SetStateAction = S | ((prevState: S) => S); + // As we got a Observable here, React's setState setter assume he's getting a `(prevState: S) => S` signature, + // therefore executing the mount method, causing everything to crash. + // piping the observable before calling `useObservable` causes the effect to always having a new reference, as + // the piped observable is a new instance on every render, causing infinite loops. + // this is why we use `useLayoutEffect` manually here. + const [mounter, setMounter] = useState<{ mount: MountPoint | undefined }>({ mount: undefined }); + useLayoutEffect(() => { + const s = controls$.subscribe((value) => { + setMounter({ mount: value }); + }); + return () => s.unsubscribe(); + }, [controls$]); + + const elementRef = useRef(null); + const unmountRef = useRef(null); + + useLayoutEffect(() => { + if (unmountRef.current) { + unmountRef.current(); + unmountRef.current = null; + } + + if (mounter.mount && elementRef.current) { + try { + unmountRef.current = mounter.mount(elementRef.current); + } catch (e) { + // eslint-disable-next-line no-console + console.error(e); + } + } + }, [mounter]); + + const containerClassName = classNames( + 'euiHeaderSection', + 'euiHeaderSection--dontGrow', + 'headerControl', + className + ); + + return ( +
+ ); +}; diff --git a/src/core/public/chrome/ui/header/header_help_menu.tsx b/src/core/public/chrome/ui/header/header_help_menu.tsx index 0eba4c0c2673..b03306207058 100644 --- a/src/core/public/chrome/ui/header/header_help_menu.tsx +++ b/src/core/public/chrome/ui/header/header_help_menu.tsx @@ -43,7 +43,9 @@ import { EuiPopoverTitle, EuiSpacer, EuiTitle, + EuiToolTip, EuiHorizontalRule, + EuiButtonIcon, } from '@elastic/eui'; import { ExclusiveUnion } from '@elastic/eui'; @@ -123,6 +125,7 @@ interface Props { useDefaultContent?: boolean; opensearchDashboardsDocLink: string; surveyLink?: string; + useUpdatedAppearance?: boolean; } interface State { @@ -200,6 +203,7 @@ class HeaderHelpMenuUI extends Component { useDefaultContent, opensearchDashboardsDocLink, surveyLink, + useUpdatedAppearance, } = this.props; const { helpExtension, helpSupportUrl } = this.state; @@ -320,8 +324,11 @@ class HeaderHelpMenuUI extends Component { ); } - const button = ( - { defaultMessage: 'Help menu', })} onClick={this.onMenuButtonClick} + /> + ) : ( + - - + onClick={this.onMenuButtonClick} + > + + + ); return ( { isOpen={this.state.isOpen} ownFocus repositionOnScroll + panelPaddingSize="s" > diff --git a/src/core/public/chrome/ui/header/header_nav_controls.tsx b/src/core/public/chrome/ui/header/header_nav_controls.tsx index 82ac5792a1cd..6f6fc50eadb9 100644 --- a/src/core/public/chrome/ui/header/header_nav_controls.tsx +++ b/src/core/public/chrome/ui/header/header_nav_controls.tsx @@ -38,9 +38,10 @@ import { HeaderExtension } from './header_extension'; interface Props { navControls$: Observable; side?: 'left' | 'right'; + className?: HTMLElement['className']; } -export function HeaderNavControls({ navControls$, side }: Props) { +export function HeaderNavControls({ navControls$, side, className }: Props) { const navControls = useObservable(navControls$, []); if (!navControls) { @@ -55,6 +56,7 @@ export function HeaderNavControls({ navControls$, side }: Props) { diff --git a/src/core/public/chrome/ui/header/home_loader.test.tsx b/src/core/public/chrome/ui/header/home_loader.test.tsx new file mode 100644 index 000000000000..e26527cf510d --- /dev/null +++ b/src/core/public/chrome/ui/header/home_loader.test.tsx @@ -0,0 +1,60 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { getLogosMock } from '../../../../common/mocks'; +import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { HomeLoader } from './home_loader'; +import { LoadingIndicator } from '../loading_indicator'; +import { EuiToolTip } from '@elastic/eui'; +import { HomeIcon } from './home_icon'; + +const mockProps = () => ({ + href: '/', + navLinks$: new BehaviorSubject([]), + forceNavigation$: new BehaviorSubject(false), + navigateToApp: jest.fn(), + logos: getLogosMock.default, + branding: {}, +}); + +describe('Home loader', () => { + it('shows the loading indicator if loading count > 0', () => { + const props = { + ...mockProps(), + loadingCount$: new BehaviorSubject(1), + }; + const component = shallowWithIntl(); + const loadingIndicator = component.find(LoadingIndicator); + expect(loadingIndicator.exists()).toBeTruthy(); + expect(loadingIndicator.prop('loadingCount$')).toEqual(props.loadingCount$); + expect(component).toMatchSnapshot(); + }); + it('displays a EuiToolTip', () => { + const props = { + ...mockProps(), + loadingCount$: new BehaviorSubject(0), + }; + const component = shallowWithIntl(); + const toolTip = component.find(EuiToolTip); + expect(toolTip.exists()).toBeTruthy(); + expect(component).toMatchSnapshot(); + }); + describe('onClick', () => { + it('directs to home page', () => { + const props = { + ...mockProps(), + loadingCount$: new BehaviorSubject(0), + }; + const component = mountWithIntl(); + const homeIcon = component.find(HomeIcon); + expect(homeIcon.exists()).toBeTruthy(); + homeIcon.simulate('click'); + expect(props.navigateToApp).toHaveBeenCalledTimes(1); + expect(props.navigateToApp).toHaveBeenCalledWith('home'); + }); + }); +}); diff --git a/src/core/public/chrome/ui/header/home_loader.tsx b/src/core/public/chrome/ui/header/home_loader.tsx index 485edd2fd647..fbdd32da4b9b 100644 --- a/src/core/public/chrome/ui/header/home_loader.tsx +++ b/src/core/public/chrome/ui/header/home_loader.tsx @@ -35,7 +35,7 @@ import React from 'react'; import useObservable from 'react-use/lib/useObservable'; import { Observable } from 'rxjs'; import Url from 'url'; -import { EuiHeaderSectionItemButton } from '@elastic/eui'; +import { EuiHeaderSectionItemButton, EuiToolTip } from '@elastic/eui'; import { ChromeNavLink } from '../..'; import { ChromeBranding } from '../../chrome_service'; import { LoadingIndicator } from '../loading_indicator'; @@ -121,24 +121,25 @@ export function HomeLoader({ href, navigateToApp, branding, logos, ...observable }); return ( - ) => - onClick(e, forceNavigation, navLinks, navigateToApp) - } - href={href} - aria-label={label} - title={label} - > - {!(loadingCount > 0) && ( -
- + + ) => + onClick(e, forceNavigation, navLinks, navigateToApp) + } + href={href} + aria-label={label} + > + {!(loadingCount > 0) && ( +
+ +
+ )} +
+
- )} -
- -
-
+ +
); } diff --git a/src/core/public/chrome/ui/header/index.ts b/src/core/public/chrome/ui/header/index.ts index 811eca0cad84..a43a3e7470d2 100644 --- a/src/core/public/chrome/ui/header/index.ts +++ b/src/core/public/chrome/ui/header/index.ts @@ -38,3 +38,4 @@ export { ChromeHelpExtensionMenuGitHubLink, } from './header_help_menu'; export { RightNavigationButton, RightNavigationButtonProps } from './right_navigation_button'; +export { createRecentNavLink } from './nav_link'; diff --git a/src/core/public/chrome/ui/header/nav_link.test.tsx b/src/core/public/chrome/ui/header/nav_link.test.tsx index a89bdc01d5bb..fd68ff0cad2c 100644 --- a/src/core/public/chrome/ui/header/nav_link.test.tsx +++ b/src/core/public/chrome/ui/header/nav_link.test.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { isActiveNavLink, createEuiListItem } from './nav_link'; +import { isActiveNavLink, createEuiListItem, createRecentNavLink } from './nav_link'; import { ChromeNavLink } from '../../..'; import { httpServiceMock } from '../../../http/http_service.mock'; @@ -61,3 +61,48 @@ describe('createEuiListItem', () => { expect(listItem).toHaveProperty('isDisabled', mockProps.link.disabled); }); }); + +describe('createRecentNavLink', () => { + const mockNavLinks: ChromeNavLink[] = [ + { + id: 'foo', + title: 'foo', + baseUrl: '/app/foo', + href: '/app/foo', + }, + ]; + const mockedNavigateToUrl = jest.fn(); + beforeEach(() => { + mockedNavigateToUrl.mockClear(); + }); + it('create a recent link with correct properties', () => { + const recentLink = createRecentNavLink( + { + id: 'foo', + label: 'foo', + link: '/app/foo', + }, + mockNavLinks, + mockBasePath, + mockedNavigateToUrl + ); + + expect(recentLink.href).toEqual('http://localhost/test/app/foo'); + }); + + it('create a recent link with workspace id', () => { + const recentLink = createRecentNavLink( + { + id: 'foo', + label: 'foo', + link: '/app/foo', + workspaceId: 'foo', + }, + mockNavLinks, + mockBasePath, + mockedNavigateToUrl + ); + + expect(recentLink.href).toEqual('http://localhost/test/w/foo/app/foo'); + }); +}); diff --git a/src/core/public/chrome/ui/header/nav_link.tsx b/src/core/public/chrome/ui/header/nav_link.tsx index 38d31dbc09c9..a80ce86507aa 100644 --- a/src/core/public/chrome/ui/header/nav_link.tsx +++ b/src/core/public/chrome/ui/header/nav_link.tsx @@ -35,6 +35,7 @@ import { ChromeNavLink, ChromeRecentlyAccessedHistoryItem, CoreStart } from '../ import { HttpStart } from '../../../http'; import { InternalApplicationStart } from '../../../application/types'; import { relativeToAbsolute } from '../../nav_links/to_nav_link'; +import { formatUrlWithWorkspaceId } from '../../../utils'; export const isModifiedOrPrevented = (event: React.MouseEvent) => event.metaKey || event.altKey || event.ctrlKey || event.shiftKey || event.defaultPrevented; @@ -52,7 +53,7 @@ interface Props { appId?: string; basePath?: HttpStart['basePath']; dataTestSubj: string; - onClick?: Function; + onClick?: (event: React.MouseEvent) => void; navigateToApp: CoreStart['application']['navigateToApp']; externalLink?: boolean; } @@ -78,7 +79,7 @@ export function createEuiListItem({ /* Use href and onClick to support "open in new tab" and SPA navigation in the same link */ onClick(event: React.MouseEvent) { if (!isModifiedOrPrevented(event)) { - onClick(); + onClick(event); } if ( @@ -126,8 +127,12 @@ export function createRecentNavLink( basePath: HttpStart['basePath'], navigateToUrl: InternalApplicationStart['navigateToUrl'] ): RecentNavLink { - const { link, label } = recentLink; - const href = relativeToAbsolute(basePath.prepend(link)); + const { link, label, workspaceId } = recentLink; + const href = relativeToAbsolute( + basePath.prepend(formatUrlWithWorkspaceId(link, workspaceId || '', basePath), { + withoutClientBasePath: true, + }) + ); const navLink = navLinks.find((nl) => href.startsWith(nl.baseUrl)); let titleAndAriaLabel = label; diff --git a/src/core/public/chrome/ui/header/recent_items.scss b/src/core/public/chrome/ui/header/recent_items.scss new file mode 100644 index 000000000000..b82a533ca1c4 --- /dev/null +++ b/src/core/public/chrome/ui/header/recent_items.scss @@ -0,0 +1,10 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +.headerRecentItemsButton { + .primaryApplicationHeader & { + margin-top: $euiSizeXS; + } +} diff --git a/src/core/public/chrome/ui/header/recent_items.test.tsx b/src/core/public/chrome/ui/header/recent_items.test.tsx new file mode 100644 index 000000000000..a6b9a65f9e1f --- /dev/null +++ b/src/core/public/chrome/ui/header/recent_items.test.tsx @@ -0,0 +1,89 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { BehaviorSubject } from 'rxjs'; +import { applicationServiceMock, httpServiceMock } from '../../../mocks'; +import { Props, RecentItems } from './recent_items'; + +jest.mock('./nav_link', () => ({ + createRecentNavLink: jest.fn().mockImplementation(() => { + return { + href: '/recent_nav_link', + }; + }), +})); + +const defaultMockProps = { + navigateToUrl: applicationServiceMock.createStartContract().navigateToUrl, + workspaceList$: new BehaviorSubject([]), + recentlyAccessed$: new BehaviorSubject([]), + navLinks$: new BehaviorSubject([]), + basePath: httpServiceMock.createStartContract().basePath, + renderBreadcrumbs: <>, +}; +const setup = (props: Props) => { + return render(); +}; +describe('Recent items', () => { + it('should render base element normally', () => { + const { baseElement } = setup(defaultMockProps); + expect(baseElement).toMatchSnapshot(); + }); + + it('should get workspace name through workspace id and render it with brackets wrapper', () => { + const workspaceList$ = new BehaviorSubject([ + { + id: 'workspace_id', + name: 'workspace_name', + }, + ]); + const recentlyAccessed$ = new BehaviorSubject([ + { + label: 'item_label', + link: 'item_link', + id: 'item_id', + workspaceId: 'workspace_id', + }, + ]); + + setup({ + ...defaultMockProps, + workspaceList$, + recentlyAccessed$, + navigateToUrl: defaultMockProps.navigateToUrl, + }); + const button = screen.getByTestId('recentItemsSectionButton'); + fireEvent.click(button); + expect(screen.getByText('(workspace_name)')).toBeInTheDocument(); + }); + + it('should call navigateToUrl with link generated from createRecentNavLink when clicking item', () => { + const workspaceList$ = new BehaviorSubject([]); + const recentlyAccessed$ = new BehaviorSubject([ + { + label: 'item_label', + link: 'item_link', + id: 'item_id', + }, + ]); + const navigateToUrl = jest.fn(); + const renderBreadcrumbs = <>; + setup({ + ...defaultMockProps, + workspaceList$, + recentlyAccessed$, + navigateToUrl, + renderBreadcrumbs, + }); + const button = screen.getByTestId('recentItemsSectionButton'); + fireEvent.click(button); + const item = screen.getByText('item_label'); + expect(navigateToUrl).not.toHaveBeenCalled(); + fireEvent.click(item); + expect(navigateToUrl).toHaveBeenCalledWith('/recent_nav_link'); + }); +}); diff --git a/src/core/public/chrome/ui/header/recent_items.tsx b/src/core/public/chrome/ui/header/recent_items.tsx new file mode 100644 index 000000000000..c7894104f163 --- /dev/null +++ b/src/core/public/chrome/ui/header/recent_items.tsx @@ -0,0 +1,139 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ +import React, { useMemo, useState } from 'react'; +import * as Rx from 'rxjs'; +import { + EuiPopover, + EuiTextColor, + EuiListGroup, + EuiListGroupItem, + EuiTitle, + EuiText, + EuiSpacer, + EuiHeaderSectionItemButtonProps, + EuiButtonIcon, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import useObservable from 'react-use/lib/useObservable'; +import { ChromeRecentlyAccessedHistoryItem } from '../..'; +import { WorkspaceObject } from '../../../workspace'; +import { createRecentNavLink } from './nav_link'; +import { HttpStart } from '../../../http'; +import { ChromeNavLink } from '../../../'; +import './recent_items.scss'; + +export interface Props { + recentlyAccessed$: Rx.Observable; + workspaceList$: Rx.Observable; + navigateToUrl: (url: string) => Promise; + basePath: HttpStart['basePath']; + navLinks$: Rx.Observable; + renderBreadcrumbs: React.JSX.Element; + buttonSize?: EuiHeaderSectionItemButtonProps['size']; +} + +export const RecentItems = ({ + recentlyAccessed$, + workspaceList$, + navigateToUrl, + navLinks$, + basePath, + renderBreadcrumbs, + buttonSize = 's', +}: Props) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const recentlyAccessedItems = useObservable(recentlyAccessed$, []); + const workspaceList = useObservable(workspaceList$, []); + const navLinks = useObservable(navLinks$, []).filter((link) => !link.hidden); + + const items = useMemo(() => { + // Only display five most recent items + return recentlyAccessedItems.slice(0, 5).map((item) => { + return { + link: createRecentNavLink(item, navLinks, basePath, navigateToUrl).href, + label: item.label, + workspaceId: item.workspaceId, + workspaceName: + workspaceList.find((workspace) => workspace.id === item.workspaceId)?.name ?? '', + }; + }); + }, [recentlyAccessedItems, workspaceList, basePath, navLinks, navigateToUrl]); + + const handleItemClick = (link: string) => { + navigateToUrl(link); + setIsPopoverOpen(false); + }; + + const button = ( + + { + setIsPopoverOpen((prev) => !prev); + }} + data-test-subj="recentItemsSectionButton" + className="headerRecentItemsButton" + /> + + ); + + return ( + { + setIsPopoverOpen(false); + }} + anchorPosition="downCenter" + repositionOnScroll + initialFocus={false} + panelPaddingSize="s" + > + {renderBreadcrumbs} + + + +

Recents

+
+ {items.length > 0 ? ( + + {items.map((item) => ( + handleItemClick(item.link)} + key={item.link} + label={ + <> + {item.label} + {item.workspaceName ? ( + ({item.workspaceName}) + ) : null} + + } + color="text" + size="s" + /> + ))} + + ) : ( + No recently viewed items + )} +
+ ); +}; diff --git a/src/core/public/chrome/ui/header/right_navigation_button.tsx b/src/core/public/chrome/ui/header/right_navigation_button.tsx index 9fa98109e313..8cd5efa243cb 100644 --- a/src/core/public/chrome/ui/header/right_navigation_button.tsx +++ b/src/core/public/chrome/ui/header/right_navigation_button.tsx @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { EuiHeaderSectionItemButton, EuiIcon } from '@elastic/eui'; +import { EuiHeaderSectionItemButton, EuiIcon, EuiToolTip } from '@elastic/eui'; import React, { useMemo } from 'react'; import { CoreStart } from '../../..'; import { isModifiedOrPrevented } from './nav_link'; @@ -58,13 +58,15 @@ export const RightNavigationButton = ({ }; return ( - - - + + + + + ); }; diff --git a/src/core/public/chrome/ui/index.ts b/src/core/public/chrome/ui/index.ts index 11f0c0302df5..963f54571323 100644 --- a/src/core/public/chrome/ui/index.ts +++ b/src/core/public/chrome/ui/index.ts @@ -39,4 +39,5 @@ export { NavType, RightNavigationButton, RightNavigationButtonProps, + createRecentNavLink, } from './header'; diff --git a/src/core/public/chrome/utils.test.ts b/src/core/public/chrome/utils.test.ts new file mode 100644 index 000000000000..18a027e1bed3 --- /dev/null +++ b/src/core/public/chrome/utils.test.ts @@ -0,0 +1,147 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { ChromeRegistrationNavLink } from './nav_group'; +import { ChromeNavLink } from './nav_links'; +import { + getAllCategories, + fulfillRegistrationLinksToChromeNavLinks, + getOrderedLinks, + getOrderedLinksOrCategories, + getSortedNavLinks, +} from './utils'; + +const mockedNonCategoryLink = { + id: 'no-category', + title: 'no-category', + baseUrl: '', + href: '', + order: 6, +}; + +const mockedNavLinkA = { + id: 'a', + title: 'a', + baseUrl: '', + href: '', + category: { + id: 'a', + label: 'a', + order: 10, + }, + order: 10, +}; + +const mockedNavLinkB = { + id: 'b', + title: 'b', + baseUrl: '', + href: '', + category: { + id: 'b', + label: 'b', + order: 5, + }, + order: 5, +}; + +const mockedSubNavLinkA = { + id: 'sub_a', + parentNavLinkId: 'a', + title: 'sub_a', + baseUrl: '', + href: '', + order: 10, +}; + +describe('getAllCategories', () => { + it('should return all categories', () => { + const links = { + a: [mockedNavLinkA], + b: [mockedNavLinkB], + }; + const categories = getAllCategories(links); + expect(categories).toEqual({ + a: { + id: 'a', + label: 'a', + order: 10, + }, + b: { + id: 'b', + label: 'b', + order: 5, + }, + }); + }); +}); + +describe('fulfillRegistrationLinksToChromeNavLinks', () => { + it('should return fullfilled ChromeNavLink', () => { + const registrationNavLinks: ChromeRegistrationNavLink[] = [ + { + id: 'a', + title: 'a', + category: { + id: 'a', + label: 'a', + order: 10, + }, + }, + { + id: 'b', + }, + ]; + const navLinks: ChromeNavLink[] = [mockedNavLinkA, mockedNavLinkB]; + const fulfilledResult = fulfillRegistrationLinksToChromeNavLinks( + registrationNavLinks, + navLinks + ); + expect(fulfilledResult).toEqual([mockedNavLinkA, mockedNavLinkB]); + }); +}); + +describe('getOrderedLinks', () => { + it('should return ordered links', () => { + const navLinks = [mockedNavLinkA, mockedNavLinkB]; + const orderedLinks = getOrderedLinks(navLinks); + expect(orderedLinks).toEqual([mockedNavLinkB, mockedNavLinkA]); + }); +}); + +describe('getOrderedLinksOrCategories', () => { + it('should return ordered links', () => { + const navLinks = [mockedNonCategoryLink, mockedNavLinkA, mockedNavLinkB]; + const orderedLinks = getOrderedLinksOrCategories(navLinks); + expect(orderedLinks[0]).toEqual( + expect.objectContaining({ + category: mockedNavLinkB.category, + }) + ); + expect(orderedLinks[1]).toEqual( + expect.objectContaining({ + link: mockedNonCategoryLink, + }) + ); + expect(orderedLinks[2]).toEqual( + expect.objectContaining({ + category: mockedNavLinkA.category, + }) + ); + }); +}); + +describe('getSortedNavLinks', () => { + it('should return flattened links', () => { + const navLinks = [mockedNonCategoryLink, mockedNavLinkA, mockedNavLinkB, mockedSubNavLinkA]; + const sortedNavLinks = getSortedNavLinks(navLinks); + expect(sortedNavLinks.map((item) => item.id)).toEqual([ + mockedNavLinkB.id, + mockedNonCategoryLink.id, + mockedNavLinkA.id, + mockedSubNavLinkA.id, + ]); + }); +}); diff --git a/src/core/public/chrome/utils.ts b/src/core/public/chrome/utils.ts new file mode 100644 index 000000000000..2d165e103e9c --- /dev/null +++ b/src/core/public/chrome/utils.ts @@ -0,0 +1,223 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { AppCategory } from 'opensearch-dashboards/public'; +import { ChromeNavLink } from './nav_links'; +import { ChromeRegistrationNavLink, NavGroupItemInMap } from './nav_group'; +import { NavGroupStatus } from '../../../core/types'; + +type KeyOf = keyof T; + +export const sortBy = (key: KeyOf) => { + return (a: T, b: T): number => (a[key] > b[key] ? 1 : b[key] > a[key] ? -1 : 0); +}; + +const groupBy = (array: T[], getKey: (item: T) => string | undefined): Record => { + return array.reduce((result, currentValue) => { + const groupKey = String(getKey(currentValue)); + if (!result[groupKey]) { + result[groupKey] = []; + } + result[groupKey].push(currentValue); + return result; + }, {} as Record); +}; + +export const LinkItemType = { + LINK: 'link', + CATEGORY: 'category', + PARENT_LINK: 'parentLink', +} as const; + +export type LinkItem = { order?: number } & ( + | { itemType: 'link'; link: ChromeNavLink & ChromeRegistrationNavLink } + | { itemType: 'parentLink'; link?: ChromeNavLink & ChromeRegistrationNavLink; links: LinkItem[] } + | { itemType: 'category'; category?: AppCategory; links?: LinkItem[] } +); + +export function getAllCategories( + allCategorizedLinks: Record> +) { + const allCategories = {} as Record; + + for (const [key, value] of Object.entries(allCategorizedLinks)) { + allCategories[key] = value[0].category; + } + + return allCategories; +} + +/** + * This function accept an array of ChromeRegistrationNavLink and an array of ChromeNavLink + * return an fulfilled array of items which are the merged result of the registerNavLinks and navLinks. + * @param registerNavLinks ChromeRegistrationNavLink[] + * @param navLinks ChromeNavLink[] + * @returns Array + */ +export function fulfillRegistrationLinksToChromeNavLinks( + registerNavLinks: ChromeRegistrationNavLink[], + navLinks: ChromeNavLink[] +): Array { + const allExistingNavLinkId = navLinks.map((link) => link.id); + return ( + registerNavLinks + .filter((navLink) => allExistingNavLinkId.includes(navLink.id)) + .map((navLink) => ({ + ...navLinks[allExistingNavLinkId.indexOf(navLink.id)], + ...navLink, + })) || [] + ); +} + +export const getOrderedLinks = (navLinks: ChromeNavLink[]): ChromeNavLink[] => + navLinks.sort(sortBy('order')); + +function walkLinkItemsTree( + props: { + linkItems: LinkItem[]; + parentItem?: LinkItem; + }, + callback: (props: { currentItem: LinkItem; parentItem?: LinkItem }) => void +) { + props.linkItems.forEach((item) => { + callback({ + parentItem: props.parentItem, + currentItem: item, + }); + if (item.itemType === LinkItemType.PARENT_LINK) { + walkLinkItemsTree( + { + linkItems: item.links, + parentItem: item, + }, + callback + ); + } else if (item.itemType === LinkItemType.CATEGORY) { + walkLinkItemsTree( + { + linkItems: item.links || [], + parentItem: item, + }, + callback + ); + } + }); +} + +export const generateItemTypeByLink = ( + navLink: ChromeNavLink & ChromeRegistrationNavLink, + navLinksGroupedByParentNavLink: Record +): LinkItem => { + const navLinksUnderParentId = navLinksGroupedByParentNavLink[navLink.id]; + + if (navLinksUnderParentId) { + return { + itemType: LinkItemType.PARENT_LINK, + link: navLink, + links: getOrderedLinks(navLinksUnderParentId || []).map((navLinkUnderParentId) => + generateItemTypeByLink(navLinkUnderParentId, navLinksGroupedByParentNavLink) + ), + order: navLink.order, + }; + } else { + return { + itemType: LinkItemType.LINK, + link: navLink, + order: navLink.order, + }; + } +}; + +/** + * This function accept navLinks and gives a grouped result for category / parent nav link + * @param navLinks + * @returns LinkItem[] + */ +export function getOrderedLinksOrCategories( + navLinks: Array +): LinkItem[] { + // Get the nav links group by parent nav link + const allNavLinksWithParentNavLink = navLinks.filter((navLink) => navLink.parentNavLinkId); + const navLinksGroupedByParentNavLink = groupBy( + allNavLinksWithParentNavLink, + (navLink) => navLink.parentNavLinkId + ); + + // Group all the nav links without parentNavLinkId + const groupedNavLinks = groupBy( + navLinks.filter((item) => !item.parentNavLinkId), + (link) => link?.category?.id + ); + const { undefined: unknowns = [], ...allCategorizedLinks } = groupedNavLinks; + const categoryDictionary = getAllCategories(allCategorizedLinks); + + // Get all the parent nav ids that defined by nested items but can not find matched parent nav in navLinks + const unusedParentNavLinks = Object.keys(navLinksGroupedByParentNavLink).filter( + (navLinkId) => !navLinks.find((navLink) => navLink.id === navLinkId) + ); + + const result = [ + // Nav links without category, the order is determined by link itself + ...unknowns.map((linkWithoutCategory) => + generateItemTypeByLink(linkWithoutCategory, navLinksGroupedByParentNavLink) + ), + // Nav links with category, the order is determined by category order + ...Object.keys(allCategorizedLinks).map((categoryKey) => { + return { + itemType: LinkItemType.CATEGORY, + category: categoryDictionary[categoryKey], + order: categoryDictionary[categoryKey]?.order, + links: getOrderedLinks(allCategorizedLinks[categoryKey]).map((navLink) => + generateItemTypeByLink(navLink, navLinksGroupedByParentNavLink) + ), + }; + }), + // Nav links that should have belong to a parent nav id + // but not find matched parent nav in navLinks + // should be treated as normal link + ...unusedParentNavLinks.reduce((total, groupId) => { + return [ + ...total, + ...navLinksGroupedByParentNavLink[groupId].map((navLink) => + generateItemTypeByLink(navLink, navLinksGroupedByParentNavLink) + ), + ]; + }, [] as LinkItem[]), + ]; + + return result.sort(sortBy('order')); +} + +export const getSortedNavLinks = ( + navLinks: ChromeNavLink[], + enricher?: (currentItem: LinkItem, parentItem?: LinkItem) => LinkItem +) => { + const sortedNavLinksTree = getOrderedLinksOrCategories(navLinks); + const acc: ChromeNavLink[] = []; + walkLinkItemsTree( + { + linkItems: sortedNavLinksTree, + }, + (props) => { + const { currentItem, parentItem } = props; + const enricheredResult = enricher ? enricher(currentItem, parentItem) : currentItem; + if ( + enricheredResult.itemType === LinkItemType.LINK || + enricheredResult.itemType === LinkItemType.PARENT_LINK + ) { + if (enricheredResult.link) { + acc.push(enricheredResult.link); + } + } + } + ); + return acc; +}; + +export const getVisibleUseCases = (navGroupMap: Record) => { + return Object.values(navGroupMap).filter( + (navGroup) => navGroup.status !== NavGroupStatus.Hidden && navGroup.type === undefined + ); +}; diff --git a/src/core/public/core_app/errors/url_overflow_ui.tsx b/src/core/public/core_app/errors/url_overflow_ui.tsx index ab5b8cf972b8..01a4393f3b40 100644 --- a/src/core/public/core_app/errors/url_overflow_ui.tsx +++ b/src/core/public/core_app/errors/url_overflow_ui.tsx @@ -38,7 +38,7 @@ import { IS_IE } from './url_overflow'; export const UrlOverflowUi: React.FC<{ basePath: IBasePath }> = ({ basePath }) => { return ( - +

= ({ name, server - +

{name}

diff --git a/src/core/public/core_app/styles/_globals_v9dark.scss b/src/core/public/core_app/styles/_globals_v9dark.scss new file mode 100644 index 000000000000..ba3a02ca3922 --- /dev/null +++ b/src/core/public/core_app/styles/_globals_v9dark.scss @@ -0,0 +1,7 @@ +// v8dark global scope +// --- +// prepended to all .scss imports (from JS, when v8dark theme selected) + +@import "@elastic/eui/src/themes/v9/v9_colors_dark"; +@import "@elastic/eui/src/themes/v9/v9_globals"; +@import "./mixins"; diff --git a/src/core/public/core_app/styles/_globals_v9light.scss b/src/core/public/core_app/styles/_globals_v9light.scss new file mode 100644 index 000000000000..74c238b5378d --- /dev/null +++ b/src/core/public/core_app/styles/_globals_v9light.scss @@ -0,0 +1,7 @@ +// v8light global scope +// --- +// prepended to all .scss imports (from JS, when v8light theme selected) + +@import "@elastic/eui/src/themes/v9/v9_colors_light"; +@import "@elastic/eui/src/themes/v9/v9_globals"; +@import "./mixins"; diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 58e92a1fa355..7f450eedae1a 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -29,10 +29,15 @@ */ import { pick } from '@osd/std'; -import { CoreId } from '../server'; -import { PackageInfo, EnvironmentMode } from '../server/types'; import { CoreSetup, CoreStart } from '.'; +import { CoreId } from '../server'; +import { EnvironmentMode, PackageInfo } from '../server/types'; +import { ApplicationService } from './application'; +import type { InternalApplicationSetup, InternalApplicationStart } from './application/types'; import { ChromeService } from './chrome'; +import { ContextService } from './context'; +import { CoreApp } from './core_app'; +import { DocLinksService } from './doc_links'; import { FatalErrorsService, FatalErrorsSetup } from './fatal_errors'; import { HttpService } from './http'; import { I18nService } from './i18n'; @@ -42,18 +47,13 @@ import { InjectedMetadataSetup, InjectedMetadataStart, } from './injected_metadata'; +import { IntegrationsService } from './integrations'; import { NotificationsService } from './notifications'; import { OverlayService } from './overlays'; import { PluginsService } from './plugins'; -import { UiSettingsService } from './ui_settings'; -import { ApplicationService } from './application'; -import { DocLinksService } from './doc_links'; import { RenderingService } from './rendering'; import { SavedObjectsService } from './saved_objects'; -import { ContextService } from './context'; -import { IntegrationsService } from './integrations'; -import { CoreApp } from './core_app'; -import type { InternalApplicationSetup, InternalApplicationStart } from './application/types'; +import { UiSettingsService } from './ui_settings'; import { WorkspacesService } from './workspace'; interface Params { @@ -171,7 +171,7 @@ export class CoreSystem { }); const application = this.application.setup({ context, http }); this.coreApp.setup({ application, http, injectedMetadata, notifications }); - const chrome = this.chrome.setup(); + const chrome = this.chrome.setup({ uiSettings }); const core: InternalCoreSetup = { application, @@ -236,6 +236,7 @@ export class CoreSystem { notifications, uiSettings, overlays, + workspaces, }); this.coreApp.start({ application, http, notifications, uiSettings }); @@ -271,7 +272,11 @@ export class CoreSystem { await this.plugins.start(core); - const { useExpandedHeader = true } = injectedMetadata.getBranding() ?? {}; + let { useExpandedHeader = true } = injectedMetadata.getBranding() ?? {}; + if (uiSettings.get('home:useNewHomePage')) { + useExpandedHeader = false; + this.rootDomElement.classList.add('headerIsDense'); + } // ensure the rootDomElement is empty this.rootDomElement.textContent = ''; diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 41f97033c5e3..edfb02f6c35c 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -419,6 +419,8 @@ export class DocLinksService { dataSource: { // https://opensearch.org/docs/latest/dashboards/discover/multi-data-sources/ guide: `${OPENSEARCH_DASHBOARDS_VERSIONED_DOCS}discover/multi-data-sources/`, + // https://opensearch.org/docs/latest/dashboards/management/S3-data-source/ + s3DataSource: `${OPENSEARCH_DASHBOARDS_VERSIONED_DOCS}management/S3-data-source/`, }, visualize: { // https://opensearch.org/docs/latest/dashboards/visualize/viz-index/ @@ -821,6 +823,7 @@ export interface DocLinksStart { readonly browser: string; readonly dataSource: { readonly guide: string; + readonly s3DataSource: string; }; readonly visualize: Record; readonly management: Record; diff --git a/src/core/public/fatal_errors/__snapshots__/fatal_errors_screen.test.tsx.snap b/src/core/public/fatal_errors/__snapshots__/fatal_errors_screen.test.tsx.snap index 1f86c71d1a9e..5fd679baf7e3 100644 --- a/src/core/public/fatal_errors/__snapshots__/fatal_errors_screen.test.tsx.snap +++ b/src/core/public/fatal_errors/__snapshots__/fatal_errors_screen.test.tsx.snap @@ -18,7 +18,7 @@ exports[`FatalErrorsScreen rendering render matches snapshot 1`] = ` - , - , + @@ -39,17 +39,21 @@ exports[`FatalErrorsScreen rendering render matches snapshot 1`] = ` id="core.fatalErrors.goBackButtonLabel" values={Object {}} /> - , + , ] } body={ -

- -

+ +

+ +

+
} iconColor="danger" iconType="alert" diff --git a/src/core/public/fatal_errors/fatal_errors_screen.tsx b/src/core/public/fatal_errors/fatal_errors_screen.tsx index 5cc359ecc893..2333bf006813 100644 --- a/src/core/public/fatal_errors/fatal_errors_screen.tsx +++ b/src/core/public/fatal_errors/fatal_errors_screen.tsx @@ -29,14 +29,15 @@ */ import { - EuiButton, - EuiButtonEmpty, + EuiSmallButton, + EuiSmallButtonEmpty, EuiCallOut, EuiCodeBlock, EuiEmptyPrompt, EuiPage, EuiPageBody, EuiPageContent, + EuiText, } from '@elastic/eui'; import React from 'react'; import * as Rx from 'rxjs'; @@ -113,16 +114,18 @@ export class FatalErrorsScreen extends React.Component { } body={ -

- +

+ -

+ /> +

+
} actions={[ - { id="core.fatalErrors.clearYourSessionButtonLabel" defaultMessage="Clear your session" /> - , - + , + - , + , ]} /> {this.state.errors.map((error, i) => ( diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 10d1928d6cf2..2398b16e7c03 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -71,6 +71,15 @@ import { RightNavigationOrder, RightNavigationButton, RightNavigationButtonProps, + ChromeRegistrationNavLink, + ChromeNavGroupUpdater, + PersistedLog, + NavGroupItemInMap, + fulfillRegistrationLinksToChromeNavLinks, + createRecentNavLink, + HeaderVariant, + LinkItemType, + getSortedNavLinks, } from './chrome'; import { FatalErrorsSetup, FatalErrorsStart, FatalErrorInfo } from './fatal_errors'; import { HttpSetup, HttpStart } from './http'; @@ -101,8 +110,12 @@ export { DEFAULT_APP_CATEGORIES, WORKSPACE_TYPE, cleanWorkspaceId, - PUBLIC_WORKSPACE_ID, - PUBLIC_WORKSPACE_NAME, + DEFAULT_NAV_GROUPS, + ALL_USE_CASE_ID, + SEARCH_USE_CASE_ID, + ESSENTIAL_USE_CASE_ID, + OBSERVABILITY_USE_CASE_ID, + SECURITY_ANALYTICS_USE_CASE_ID, } from '../utils'; export { AppCategory, @@ -114,6 +127,10 @@ export { StringValidationRegex, StringValidationRegexString, WorkspaceAttribute, + ChromeNavGroup, + NavGroupType, + NavGroupStatus, + WorkspaceAttributeWithPermission, } from '../types'; export { @@ -366,10 +383,25 @@ export { RightNavigationOrder, RightNavigationButton, RightNavigationButtonProps, + ChromeRegistrationNavLink, + ChromeNavGroupUpdater, + PersistedLog, + NavGroupItemInMap, + fulfillRegistrationLinksToChromeNavLinks, + createRecentNavLink, + HeaderVariant, + LinkItemType, + getSortedNavLinks, }; export { __osdBootstrap__ } from './osd_bootstrap'; -export { WorkspacesStart, WorkspacesSetup, WorkspacesService, WorkspaceObject } from './workspace'; +export { + WorkspacesStart, + WorkspacesSetup, + WorkspacesService, + WorkspaceObject, + IWorkspaceClient, +} from './workspace'; export { debounce } from './utils'; diff --git a/src/core/public/locale_helper.test.ts b/src/core/public/locale_helper.test.ts new file mode 100644 index 000000000000..238dbced3892 --- /dev/null +++ b/src/core/public/locale_helper.test.ts @@ -0,0 +1,50 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { getLocaleInUrl } from './locale_helper'; + +describe('getLocaleInUrl', () => { + beforeEach(() => { + // Clear any warnings before each test + delete (window as any).__localeWarning; + }); + + it('should return the locale from a valid query string', () => { + const url = 'http://localhost:5603/app/home?locale=en-US'; + expect(getLocaleInUrl(url)).toBe('en-US'); + }); + + it('should return the locale from a valid hash query string', () => { + const url = 'http://localhost:5603/app/home#/?locale=fr-FR'; + expect(getLocaleInUrl(url)).toBe('fr-FR'); + }); + + it('should return en for a URL without locale', () => { + const url = 'http://localhost:5603/app/home'; + expect(getLocaleInUrl(url)).toBe('en'); + }); + + it('should return en and set a warning for an invalid locale format in hash', () => { + const url = 'http://localhost:5603/app/home#/&locale=de-DE'; + expect(getLocaleInUrl(url)).toBe('en'); + expect((window as any).__localeWarning).toBeDefined(); + expect((window as any).__localeWarning.title).toBe('Invalid URL Format'); + }); + + it('should return en for an empty locale value', () => { + const url = 'http://localhost:5603/app/home?locale='; + expect(getLocaleInUrl(url)).toBe('en'); + }); + + it('should handle URLs with other query parameters', () => { + const url = 'http://localhost:5603/app/home?param1=value1&locale=ja-JP¶m2=value2'; + expect(getLocaleInUrl(url)).toBe('ja-JP'); + }); + + it('should handle URLs with other hash parameters', () => { + const url = 'http://localhost:5603/app/home#/route?param1=value1&locale=zh-CN¶m2=value2'; + expect(getLocaleInUrl(url)).toBe('zh-CN'); + }); +}); diff --git a/src/core/public/locale_helper.ts b/src/core/public/locale_helper.ts new file mode 100644 index 000000000000..38a734a523b1 --- /dev/null +++ b/src/core/public/locale_helper.ts @@ -0,0 +1,68 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * Extracts the locale value from a given URL. + * + * This function looks for the 'locale' parameter in either the main query string + * or in the hash part of the URL. It supports two valid formats: + * 1. As a regular query parameter: "?locale=xx-XX" + * 2. In the hash with a proper query string: "#/?locale=xx-XX" + * + * If an invalid format is detected, it sets a warning message on the window object. + * + * @param url - The URL to extract the locale from + * @returns The locale value if found and valid, or null otherwise + */ +export function getLocaleInUrl(url: string): string | null { + let urlObject: URL; + // Attempt to parse the URL, return null if invalid + try { + urlObject = new URL(url, window.location.origin); + } catch (error) { + setInvalidUrlWarning(); + return null; + } + + let localeValue: string | null = null; + + // Check for locale in the main query string + if (urlObject.searchParams.has('locale')) { + localeValue = urlObject.searchParams.get('locale'); + } + // Check for locale in the hash, but only if it's in proper query string format + else if (urlObject.hash.includes('?')) { + const hashParams = new URLSearchParams(urlObject.hash.split('?')[1]); + if (hashParams.has('locale')) { + localeValue = hashParams.get('locale'); + } + } + + // Check for non standard query format: + if (localeValue === null && url.includes('&locale=')) { + setInvalidUrlWithLocaleWarning(); + return 'en'; + } + + // Return the locale value if found, or 'en' if not found + return localeValue && localeValue.trim() !== '' ? localeValue : 'en'; +} + +function setInvalidUrlWarning(): void { + (window as any).__localeWarning = { + title: 'Invalid URL Format', + text: 'The provided URL is not in a valid format.', + }; +} + +function setInvalidUrlWithLocaleWarning(): void { + (window as any).__localeWarning = { + title: 'Invalid URL Format', + text: + 'The locale parameter is not in a valid URL format. ' + + 'Use either "?locale=xx-XX" in the main URL or "#/?locale=xx-XX" in the hash. ' + + 'For example: "yourapp.com/page?locale=en-US" or "yourapp.com/page#/?locale=en-US".', + }; +} diff --git a/src/core/public/mocks.ts b/src/core/public/mocks.ts index 05c3b7d18d1b..d2c7f86f8216 100644 --- a/src/core/public/mocks.ts +++ b/src/core/public/mocks.ts @@ -31,24 +31,26 @@ import { createMemoryHistory } from 'history'; // Only import types from '.' to avoid triggering default Jest mocks. -import { CoreContext, PluginInitializerContext, AppMountParameters } from '.'; +import { AppMountParameters, CoreContext, PluginInitializerContext } from '.'; // Import values from their individual modules instead. import { ScopedHistory } from './application'; import { applicationServiceMock } from './application/application_service.mock'; import { chromeServiceMock } from './chrome/chrome_service.mock'; +import { contextServiceMock } from './context/context_service.mock'; import { docLinksServiceMock } from './doc_links/doc_links_service.mock'; import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock'; import { httpServiceMock } from './http/http_service.mock'; import { i18nServiceMock } from './i18n/i18n_service.mock'; +import { injectedMetadataServiceMock } from './injected_metadata/injected_metadata_service.mock'; import { notificationServiceMock } from './notifications/notifications_service.mock'; import { overlayServiceMock } from './overlays/overlay_service.mock'; -import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; import { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; -import { contextServiceMock } from './context/context_service.mock'; -import { injectedMetadataServiceMock } from './injected_metadata/injected_metadata_service.mock'; +import { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; import { workspacesServiceMock } from './workspace/workspaces_service.mock'; +export { applicationServiceMock } from './application/application_service.mock'; +export { scopedHistoryMock } from './application/scoped_history.mock'; export { chromeServiceMock } from './chrome/chrome_service.mock'; export { docLinksServiceMock } from './doc_links/doc_links_service.mock'; export { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock'; @@ -57,10 +59,8 @@ export { i18nServiceMock } from './i18n/i18n_service.mock'; export { injectedMetadataServiceMock } from './injected_metadata/injected_metadata_service.mock'; export { notificationServiceMock } from './notifications/notifications_service.mock'; export { overlayServiceMock } from './overlays/overlay_service.mock'; -export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; export { savedObjectsServiceMock } from './saved_objects/saved_objects_service.mock'; -export { scopedHistoryMock } from './application/scoped_history.mock'; -export { applicationServiceMock } from './application/application_service.mock'; +export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; export { workspacesServiceMock } from './workspace/workspaces_service.mock'; function createCoreSetupMock({ @@ -185,6 +185,12 @@ function createAppMountParametersMock(appBasePath = '') { history, onAppLeave: jest.fn(), setHeaderActionMenu: jest.fn(), + setHeaderLeftControls: jest.fn(), + setHeaderCenterControls: jest.fn(), + setHeaderRightControls: jest.fn(), + setHeaderBadgeControls: jest.fn(), + setHeaderDescriptionControls: jest.fn(), + setHeaderBottomControls: jest.fn(), }; return params; diff --git a/src/core/public/notifications/toasts/error_toast.tsx b/src/core/public/notifications/toasts/error_toast.tsx index 4f4debbb28c6..5924543b8eb5 100644 --- a/src/core/public/notifications/toasts/error_toast.tsx +++ b/src/core/public/notifications/toasts/error_toast.tsx @@ -32,6 +32,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { + EuiSmallButton, EuiButton, EuiCallOut, EuiCodeBlock, @@ -107,12 +108,12 @@ function showErrorDialog({ )} - modal.close()} fill> + modal.close()} fill> - + diff --git a/src/core/public/notifications/toasts/toasts_api.tsx b/src/core/public/notifications/toasts/toasts_api.tsx index 76d0bf9cf9b2..3166e814ed77 100644 --- a/src/core/public/notifications/toasts/toasts_api.tsx +++ b/src/core/public/notifications/toasts/toasts_api.tsx @@ -87,6 +87,10 @@ export interface ErrorToastOptions extends ToastOptions { * message will still be shown in the detailed error modal. */ toastMessage?: string; + /** + * The id of the error. + */ + id?: string; } const normalizeToast = (toastOrTitle: ToastInput): ToastInputFields => { diff --git a/src/core/public/osd_bootstrap.test.mocks.ts b/src/core/public/osd_bootstrap.test.mocks.ts index 77b47e8b895b..87a6ab499731 100644 --- a/src/core/public/osd_bootstrap.test.mocks.ts +++ b/src/core/public/osd_bootstrap.test.mocks.ts @@ -31,18 +31,6 @@ import { applicationServiceMock } from './application/application_service.mock'; import { fatalErrorsServiceMock } from './fatal_errors/fatal_errors_service.mock'; export const fatalErrorMock = fatalErrorsServiceMock.createSetupContract(); -export const coreSystemMock = { - setup: jest.fn().mockResolvedValue({ - fatalErrors: fatalErrorMock, - }), - start: jest.fn().mockResolvedValue({ - application: applicationServiceMock.createInternalStartContract(), - }), -}; -jest.doMock('./core_system', () => ({ - CoreSystem: jest.fn().mockImplementation(() => coreSystemMock), -})); - export const apmSystem = { setup: jest.fn().mockResolvedValue(undefined), start: jest.fn().mockResolvedValue(undefined), @@ -53,9 +41,25 @@ jest.doMock('./apm_system', () => ({ })); export const i18nLoad = jest.fn().mockResolvedValue(undefined); +export const i18nSetLocale = jest.fn(); jest.doMock('@osd/i18n', () => ({ i18n: { ...jest.requireActual('@osd/i18n').i18n, load: i18nLoad, + setLocale: i18nSetLocale, }, })); + +export const coreSystemMock = { + setup: jest.fn().mockResolvedValue({ + fatalErrors: fatalErrorMock, + }), + start: jest.fn().mockResolvedValue({ + application: applicationServiceMock.createInternalStartContract(), + }), +}; +jest.doMock('./core_system', () => ({ + CoreSystem: jest.fn().mockImplementation(() => coreSystemMock), +})); + +export const consoleWarnMock = jest.spyOn(console, 'warn').mockImplementation(() => {}); diff --git a/src/core/public/osd_bootstrap.test.ts b/src/core/public/osd_bootstrap.test.ts index e4209b460f86..7630b4441ab6 100644 --- a/src/core/public/osd_bootstrap.test.ts +++ b/src/core/public/osd_bootstrap.test.ts @@ -28,23 +28,46 @@ * under the License. */ -import { apmSystem, fatalErrorMock, i18nLoad } from './osd_bootstrap.test.mocks'; +import { + apmSystem, + fatalErrorMock, + i18nLoad, + i18nSetLocale, + consoleWarnMock, +} from './osd_bootstrap.test.mocks'; import { __osdBootstrap__ } from './'; +import { getLocaleInUrl } from './locale_helper'; + +jest.mock('./locale_helper', () => ({ + getLocaleInUrl: jest.fn(), +})); describe('osd_bootstrap', () => { + let originalWindowLocation: Location; + beforeAll(() => { const metadata = { branding: { darkMode: 'true' }, - i18n: { translationsUrl: 'http://localhost' }, + i18n: { translationsUrl: 'http://localhost/translations/en.json' }, vars: { apmConfig: null }, }; // eslint-disable-next-line no-unsanitized/property - document.body.innerHTML = ` -`; + document.body.innerHTML = ` `; + + originalWindowLocation = window.location; + delete (window as any).location; + window.location = { ...originalWindowLocation, href: 'http://localhost' }; }); beforeEach(() => { jest.clearAllMocks(); + (getLocaleInUrl as jest.Mock).mockReturnValue(null); + }); + + afterAll(() => { + window.location = originalWindowLocation; }); it('does not report a fatal error if apm load fails', async () => { @@ -64,4 +87,39 @@ describe('osd_bootstrap', () => { expect(fatalErrorMock.add).toHaveBeenCalledTimes(1); }); + + it('sets locale from URL if present', async () => { + (getLocaleInUrl as jest.Mock).mockReturnValue('fr'); + window.location.href = 'http://localhost/?locale=fr'; + + await __osdBootstrap__(); + + expect(i18nSetLocale).toHaveBeenCalledWith('fr'); + expect(i18nLoad).toHaveBeenCalledWith('http://localhost/translations/fr.json'); + }); + + it('sets default locale if not present in URL', async () => { + await __osdBootstrap__(); + + expect(i18nSetLocale).toHaveBeenCalledWith('en'); + expect(i18nLoad).toHaveBeenCalledWith('http://localhost/translations/en.json'); + }); + + it('displays locale warning if set', async () => { + (window as any).__localeWarning = { title: 'Locale Warning', text: 'Invalid locale' }; + + await __osdBootstrap__(); + + expect(consoleWarnMock).toHaveBeenCalledWith('Locale Warning: Invalid locale'); + expect((window as any).__localeWarning).toBeUndefined(); + }); + + it('displays i18n warning if set', async () => { + (window as any).__i18nWarning = { title: 'i18n Warning', text: 'Translation issue' }; + + await __osdBootstrap__(); + + expect(consoleWarnMock).toHaveBeenCalledWith('i18n Warning: Translation issue'); + expect((window as any).__i18nWarning).toBeUndefined(); + }); }); diff --git a/src/core/public/osd_bootstrap.ts b/src/core/public/osd_bootstrap.ts index ed64ed0bc2b5..5190fdc37e21 100644 --- a/src/core/public/osd_bootstrap.ts +++ b/src/core/public/osd_bootstrap.ts @@ -31,6 +31,7 @@ import { i18n } from '@osd/i18n'; import { CoreSystem } from './core_system'; import { ApmSystem } from './apm_system'; +import { getLocaleInUrl } from './locale_helper'; /** @internal */ export async function __osdBootstrap__() { @@ -38,6 +39,33 @@ export async function __osdBootstrap__() { document.querySelector('osd-injected-metadata')!.getAttribute('data')! ); + // Extract the locale from the URL if present + const currentLocale = i18n.getLocale(); + const urlLocale = getLocaleInUrl(window.location.href); + + if (urlLocale && urlLocale !== currentLocale) { + // If a locale is specified in the URL, update the i18n settings + // This enables dynamic language switching + // Note: This works in conjunction with server-side changes: + // 1. The server registers all available translation files at startup + // 2. A server route handles requests for specific locale translations + + // Set the locale in the i18n core + // This will affect all subsequent i18n.translate() calls + i18n.setLocale(urlLocale); + + // Modify the translationsUrl to include the new locale + // This ensures that the correct translation file is requested from the server + // The replace function changes the locale in the URL, e.g., + // from '/translations/en.json' to '/translations/zh-CN.json' + injectedMetadata.i18n.translationsUrl = injectedMetadata.i18n.translationsUrl.replace( + /\/([^/]+)\.json$/, + `/${urlLocale}.json` + ); + } else if (!urlLocale) { + i18n.setLocale('en'); + } + const globals: any = typeof window === 'undefined' ? {} : window; const themeTag: string = globals.__osdThemeTag__ || ''; @@ -67,4 +95,20 @@ export async function __osdBootstrap__() { const start = await coreSystem.start(); await apmSystem.start(start); + + // Display the i18n warning if it exists + if ((window as any).__i18nWarning) { + const warning = (window as any).__i18nWarning; + // eslint-disable-next-line no-console + console.warn(`${warning.title}: ${warning.text}`); + delete (window as any).__i18nWarning; + } + + // Display the locale warning if it exists + if ((window as any).__localeWarning) { + const warning = (window as any).__localeWarning; + // eslint-disable-next-line no-console + console.warn(`${warning.title}: ${warning.text}`); + delete (window as any).__localeWarning; + } } diff --git a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap index 09be5ffdeb46..68a37d308066 100644 --- a/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap +++ b/src/core/public/overlays/flyout/__snapshots__/flyout_service.test.tsx.snap @@ -8,6 +8,14 @@ Array [ ] `; +exports[`FlyoutService closeFlyout() can be called multiple times 1`] = ` +Array [ + Array [ +
, + ], +] +`; + exports[`FlyoutService openFlyout() renders a flyout to the DOM 1`] = ` Array [ Array [ diff --git a/src/core/public/overlays/flyout/flyout_service.mock.ts b/src/core/public/overlays/flyout/flyout_service.mock.ts index 82fab5e4a8ad..8e087888f8e0 100644 --- a/src/core/public/overlays/flyout/flyout_service.mock.ts +++ b/src/core/public/overlays/flyout/flyout_service.mock.ts @@ -37,6 +37,7 @@ const createStartContractMock = () => { close: jest.fn(), onClose: Promise.resolve(), }), + close: jest.fn(), }; return startContract; }; diff --git a/src/core/public/overlays/flyout/flyout_service.test.tsx b/src/core/public/overlays/flyout/flyout_service.test.tsx index d8acfaa0e331..0e168ea78d55 100644 --- a/src/core/public/overlays/flyout/flyout_service.test.tsx +++ b/src/core/public/overlays/flyout/flyout_service.test.tsx @@ -91,6 +91,43 @@ describe('FlyoutService', () => { }); }); }); + + describe('closeFlyout()', () => { + it('resolves onClose on the previous ref', async () => { + const ref = flyouts.open(mountText('Flyout content')); + const onCloseComplete = jest.fn(); + ref.onClose.then(onCloseComplete); + flyouts.close(); + await ref.onClose; + expect(onCloseComplete).toBeCalledTimes(1); + }); + + it('can be called multiple times', async () => { + flyouts.open(mountText('Flyout content')); + expect(mockReactDomUnmount).not.toHaveBeenCalled(); + flyouts.close(); + expect(mockReactDomUnmount.mock.calls).toMatchSnapshot(); + flyouts.close(); + expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); + }); + + it("doesn't affect an inactive flyout", async () => { + const ref = flyouts.open(mountText('Flyout content')); + flyouts.close(); + const onCloseComplete = jest.fn(); + ref.onClose.then(onCloseComplete); + await ref.onClose; + + mockReactDomUnmount.mockClear(); + onCloseComplete.mockClear(); + + flyouts.close(); + flyouts.close(); + expect(mockReactDomUnmount).toBeCalledTimes(0); + expect(onCloseComplete).toBeCalledTimes(0); + }); + }); + describe('FlyoutRef#close()', () => { it('resolves the onClose Promise', async () => { const ref = flyouts.open(mountText('Flyout content')); diff --git a/src/core/public/overlays/flyout/flyout_service.tsx b/src/core/public/overlays/flyout/flyout_service.tsx index 4d89b57d791a..954e9727966c 100644 --- a/src/core/public/overlays/flyout/flyout_service.tsx +++ b/src/core/public/overlays/flyout/flyout_service.tsx @@ -94,6 +94,11 @@ export interface OverlayFlyoutStart { * @return {@link OverlayRef} A reference to the opened flyout panel. */ open(mount: MountPoint, options?: OverlayFlyoutOpenOptions): OverlayRef; + + /** + * Closes any open flyout panel. + */ + close(): void; } /** @@ -149,6 +154,12 @@ export class FlyoutService { return flyout; }, + close: () => { + if (this.activeFlyout) { + this.activeFlyout.close(); + this.cleanupDom(); + } + }, }; } diff --git a/src/core/public/overlays/modal/modal_service.tsx b/src/core/public/overlays/modal/modal_service.tsx index a1f7a7e59cdb..e14f09eac2db 100644 --- a/src/core/public/overlays/modal/modal_service.tsx +++ b/src/core/public/overlays/modal/modal_service.tsx @@ -31,7 +31,7 @@ /* eslint-disable max-classes-per-file */ import { i18n as t } from '@osd/i18n'; -import { EuiModal, EuiConfirmModal, EuiConfirmModalProps } from '@elastic/eui'; +import { EuiModal, EuiConfirmModal, EuiConfirmModalProps, EuiModalProps } from '@elastic/eui'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Subject } from 'rxjs'; @@ -111,7 +111,7 @@ export interface OverlayModalStart { /** * @public */ -export interface OverlayModalOpenOptions { +export interface OverlayModalOpenOptions extends Pick { className?: string; closeButtonAriaLabel?: string; 'data-test-subj'?: string; diff --git a/src/core/public/overlays/overlay_service.mock.ts b/src/core/public/overlays/overlay_service.mock.ts index 5f2d70eec5d3..993f40b62c16 100644 --- a/src/core/public/overlays/overlay_service.mock.ts +++ b/src/core/public/overlays/overlay_service.mock.ts @@ -36,11 +36,13 @@ import { overlayModalServiceMock } from './modal/modal_service.mock'; import { overlaySidecarServiceMock } from './sidecar/sidecar_service.mock'; const createStartContractMock = () => { - const overlayStart = overlayModalServiceMock.createStartContract(); + const overlayModalStart = overlayModalServiceMock.createStartContract(); + const overlayFlyoutStart = overlayFlyoutServiceMock.createStartContract(); const startContract: DeeplyMockedKeys = { - openFlyout: overlayFlyoutServiceMock.createStartContract().open, - openModal: overlayStart.open, - openConfirm: overlayStart.openConfirm, + openFlyout: overlayFlyoutStart.open, + closeFlyout: overlayFlyoutStart.close, + openModal: overlayModalStart.open, + openConfirm: overlayModalStart.openConfirm, banners: overlayBannersServiceMock.createStartContract(), sidecar: overlaySidecarServiceMock.createStartContract(), }; diff --git a/src/core/public/overlays/overlay_service.ts b/src/core/public/overlays/overlay_service.ts index 92144d34c45b..557b018ca189 100644 --- a/src/core/public/overlays/overlay_service.ts +++ b/src/core/public/overlays/overlay_service.ts @@ -68,6 +68,7 @@ export class OverlayService { return { banners, openFlyout: flyouts.open.bind(flyouts), + closeFlyout: flyouts.close.bind(flyouts), openModal: modals.open.bind(modals), openConfirm: modals.openConfirm.bind(modals), sidecar: sidecars, @@ -81,6 +82,8 @@ export interface OverlayStart { banners: OverlayBannersStart; /** {@link OverlayFlyoutStart#open} */ openFlyout: OverlayFlyoutStart['open']; + /** {@link OverlayFlyoutStart#close} */ + closeFlyout: OverlayFlyoutStart['close']; /** {@link OverlayModalStart#open} */ openModal: OverlayModalStart['open']; /** {@link OverlayModalStart#openConfirm} */ diff --git a/src/core/public/overlays/sidecar/components/sidecar.scss b/src/core/public/overlays/sidecar/components/sidecar.scss index 1878b92b5074..7041abe74fd3 100644 --- a/src/core/public/overlays/sidecar/components/sidecar.scss +++ b/src/core/public/overlays/sidecar/components/sidecar.scss @@ -6,7 +6,9 @@ .osdSidecarFlyout { box-shadow: initial; position: fixed; - z-index: 1000; + + // Sidecar z-index should more than mask. Actually will be 1001. + z-index: $euiZMaskBelowHeader + 1; background: lightOrDarkTheme($euiColorGhost, $euiColorInk); display: flex; diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts index 6ef454abab46..7088be99c017 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/src/core/public/plugins/plugin_context.ts @@ -154,6 +154,12 @@ export function createPluginStartContext< navigateToApp: deps.application.navigateToApp, navigateToUrl: deps.application.navigateToUrl, getUrlForApp: deps.application.getUrlForApp, + setAppLeftControls: deps.application.setAppLeftControls, + setAppCenterControls: deps.application.setAppCenterControls, + setAppRightControls: deps.application.setAppRightControls, + setAppBadgeControls: deps.application.setAppBadgeControls, + setAppDescriptionControls: deps.application.setAppDescriptionControls, + setAppBottomControls: deps.application.setAppBottomControls, registerMountContext: (contextName, provider) => deps.application.registerMountContext(plugin.opaqueId, contextName, provider), }, diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index ad934e0a73ae..9dc6a84b484a 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -457,14 +457,14 @@ export class SavedObjectsClient { * { id: 'foo', type: 'index-pattern' } * ]) */ - public bulkGet = (objects: Array<{ id: string; type: string }> = []) => { + public bulkGet = (objects: Array<{ id: string; type: string }> = []) => { const filteredObjects = objects.map((obj) => pick(obj, ['id', 'type'])); return this.performBulkGet(filteredObjects).then((resp) => { resp.saved_objects = resp.saved_objects.map((d) => this.createSavedObject(d)); return renameKeys< PromiseType>, - SavedObjectsBatchResponse - >({ saved_objects: 'savedObjects' }, resp) as SavedObjectsBatchResponse; + SavedObjectsBatchResponse + >({ saved_objects: 'savedObjects' }, resp) as SavedObjectsBatchResponse; }); }; diff --git a/src/core/public/saved_objects/simple_saved_object.ts b/src/core/public/saved_objects/simple_saved_object.ts index 71b1445e95aa..cd53f2b4123a 100644 --- a/src/core/public/saved_objects/simple_saved_object.ts +++ b/src/core/public/saved_objects/simple_saved_object.ts @@ -52,6 +52,7 @@ export class SimpleSavedObject { public error: SavedObjectType['error']; public references: SavedObjectType['references']; public updated_at: SavedObjectType['updated_at']; + public workspaces: SavedObjectType['workspaces']; constructor( private client: SavedObjectsClientContract, @@ -64,6 +65,7 @@ export class SimpleSavedObject { references, migrationVersion, updated_at: updateAt, + workspaces, }: SavedObjectType ) { this.id = id; @@ -73,6 +75,7 @@ export class SimpleSavedObject { this._version = version; this.migrationVersion = migrationVersion; this.updated_at = updateAt; + this.workspaces = workspaces; if (error) { this.error = error; } diff --git a/src/core/public/ui_settings/__snapshots__/ui_settings_client.test.ts.snap b/src/core/public/ui_settings/__snapshots__/ui_settings_client.test.ts.snap index 68948ef0d4af..5d0b95e2938b 100644 --- a/src/core/public/ui_settings/__snapshots__/ui_settings_client.test.ts.snap +++ b/src/core/public/ui_settings/__snapshots__/ui_settings_client.test.ts.snap @@ -20,6 +20,19 @@ You can use \`IUiSettingsClient.get(\\"throwableProperty\\", defaultValue)\`, wh \`defaultValue\` when the key is unrecognized." `; +exports[`#getDefault converts json default values 1`] = ` +Object { + "a": 1, +} +`; + +exports[`#getDefault fetches correct uiSettings defaults 1`] = `"Browser"`; + +exports[`#getDefault throws on unknown properties that don't have a value yet. 1`] = ` +"Unexpected \`IUiSettingsClient.getDefaultValue(\\"unknownProperty\\")\` call on unrecognized configuration setting \\"unknownProperty\\". +Please check that the setting for \\"unknownProperty\\" exists." +`; + exports[`#getUpdate$ sends { key, newValue, oldValue } notifications when client changes 1`] = ` Array [ Array [ diff --git a/src/core/public/ui_settings/types.ts b/src/core/public/ui_settings/types.ts index 7eca4cc68d81..86f78443eee6 100644 --- a/src/core/public/ui_settings/types.ts +++ b/src/core/public/ui_settings/types.ts @@ -66,6 +66,13 @@ export interface IUiSettingsClient { */ getAll: () => Readonly>; + /** + * Gets the default value for a specific uiSetting. If the parameter is not defined and the key is + * not registered by any plugin then an error is thrown, otherwise reads the default value defined by + * a plugin. + */ + getDefault: (key: string) => T; + /** * Sets the value for a uiSetting. If the setting is not registered by any plugin * it will be stored as a custom setting. The new value will be synchronously available via diff --git a/src/core/public/ui_settings/ui_settings_client.test.ts b/src/core/public/ui_settings/ui_settings_client.test.ts index 9060b0d6db4e..9cf4985f440c 100644 --- a/src/core/public/ui_settings/ui_settings_client.test.ts +++ b/src/core/public/ui_settings/ui_settings_client.test.ts @@ -64,6 +64,24 @@ afterEach(() => { done$.complete(); }); +describe('#getDefault', () => { + it('fetches correct uiSettings defaults', () => { + const { client } = setup(); + expect(client.getDefault('dateFormat')).toMatchSnapshot(); + expect(client.getDefault('aLongNumeral')).toBe(BigInt(Number.MAX_SAFE_INTEGER) + 11n); + }); + + it('converts json default values', () => { + const { client } = setup({ defaults: { test: { value: '{"a": 1}', type: 'json' } } }); + expect(client.getDefault('test')).toMatchSnapshot(); + }); + + it("throws on unknown properties that don't have a value yet.", () => { + const { client } = setup(); + expect(() => client.getDefault('unknownProperty')).toThrowErrorMatchingSnapshot(); + }); +}); + describe('#get', () => { it('gives access to uiSettings values', () => { const { client } = setup(); diff --git a/src/core/public/ui_settings/ui_settings_client.ts b/src/core/public/ui_settings/ui_settings_client.ts index 19637debf948..ac548278bf47 100644 --- a/src/core/public/ui_settings/ui_settings_client.ts +++ b/src/core/public/ui_settings/ui_settings_client.ts @@ -32,7 +32,7 @@ import { cloneDeep, defaultsDeep } from 'lodash'; import { Observable, Subject, concat, defer, of } from 'rxjs'; import { filter, map } from 'rxjs/operators'; -import { UserProvidedValues, PublicUiSettingsParams } from 'src/core/server/types'; +import { UserProvidedValues, PublicUiSettingsParams, UiSettingsType } from 'src/core/server/types'; import { IUiSettingsClient, UiSettingsState } from './types'; import { UiSettingsApi } from './ui_settings_api'; @@ -58,13 +58,6 @@ export class UiSettingsClient implements IUiSettingsClient { this.defaults = cloneDeep(params.defaults); this.cache = defaultsDeep({}, this.defaults, cloneDeep(params.initialSettings)); - if ( - this.cache['theme:enableUserControl']?.userValue ?? - this.cache['theme:enableUserControl']?.value - ) { - this.cache = defaultsDeep(this.cache, this.getBrowserStoredSettings()); - } - params.done$.subscribe({ complete: () => { this.update$.complete(); @@ -78,6 +71,18 @@ export class UiSettingsClient implements IUiSettingsClient { return cloneDeep(this.cache); } + getDefault(key: string): T { + const declared = this.isDeclared(key); + + if (!declared) { + throw new Error( + `Unexpected \`IUiSettingsClient.getDefaultValue("${key}")\` call on unrecognized configuration setting "${key}". +Please check that the setting for "${key}" exists.` + ); + } + return this.resolveValue(this.cache[key].value, this.cache[key].type); + } + get(key: string, defaultOverride?: T) { const declared = this.isDeclared(key); @@ -99,16 +104,7 @@ You can use \`IUiSettingsClient.get("${key}", defaultValue)\`, which will just r const userValue = this.cache[key].userValue; const defaultValue = defaultOverride !== undefined ? defaultOverride : this.cache[key].value; const value = userValue == null ? defaultValue : userValue; - - if (type === 'json') { - return JSON.parse(value); - } - - return type === 'number' && typeof value !== 'bigint' - ? isFinite(value) && (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) - ? BigInt(value) - : parseFloat(value) - : value; + return this.resolveValue(value, type); } get$(key: string, defaultOverride?: T) { @@ -180,26 +176,16 @@ You can use \`IUiSettingsClient.get("${key}", defaultValue)\`, which will just r return this.updateErrors$.asObservable(); } - private getBrowserStoredSettings() { - const uiSettingsJSON = window.localStorage.getItem('uiSettings') || '{}'; - try { - return JSON.parse(uiSettingsJSON); - } catch (error) { - this.updateErrors$.next(error); + private resolveValue(value: any, type: UiSettingsType | undefined) { + if (type === 'json') { + return JSON.parse(value); } - return {}; - } - private setBrowserStoredSettings(key: string, newVal: any) { - const oldSettings = this.getBrowserStoredSettings(); - const newSettings = cloneDeep(oldSettings); - if (newVal === null) { - delete newSettings[key]; - } else { - newSettings[key] = { userValue: newVal }; - } - window.localStorage.setItem(`uiSettings`, JSON.stringify(newSettings)); - return { settings: newSettings }; + return type === 'number' && typeof value !== 'bigint' + ? isFinite(value) && (value > Number.MAX_SAFE_INTEGER || value < Number.MIN_SAFE_INTEGER) + ? BigInt(value) + : parseFloat(value) + : value; } private assertUpdateAllowed(key: string) { @@ -227,18 +213,8 @@ You can use \`IUiSettingsClient.get("${key}", defaultValue)\`, which will just r this.setLocally(key, newVal); try { - if ( - this.cache['theme:enableUserControl']?.userValue ?? - this.cache['theme:enableUserControl']?.value - ) { - const { settings } = this.cache[key]?.preferBrowserSetting - ? this.setBrowserStoredSettings(key, newVal) - : (await this.api.batchSet(key, newVal)) || {}; - this.cache = defaultsDeep({}, defaults, this.getBrowserStoredSettings(), settings); - } else { - const { settings } = (await this.api.batchSet(key, newVal)) || {}; - this.cache = defaultsDeep({}, defaults, settings); - } + const { settings } = await this.api.batchSet(key, newVal); + this.cache = defaultsDeep({}, defaults, settings); this.saved$.next({ key, newValue: newVal, oldValue: initialVal }); return true; } catch (error) { diff --git a/src/core/public/ui_settings/ui_settings_service.mock.ts b/src/core/public/ui_settings/ui_settings_service.mock.ts index 8458c86d6774..231627fa53bd 100644 --- a/src/core/public/ui_settings/ui_settings_service.mock.ts +++ b/src/core/public/ui_settings/ui_settings_service.mock.ts @@ -36,6 +36,7 @@ import { IUiSettingsClient } from './types'; const createSetupContractMock = () => { const setupContract: jest.Mocked = { getAll: jest.fn(), + getDefault: jest.fn(), get: jest.fn(), get$: jest.fn(), set: jest.fn(), @@ -67,6 +68,8 @@ const createMock = () => { }; mocked.setup.mockReturnValue(createSetupContractMock()); + // UiSettings.start returns the client that is returned by setup + mocked.start.mockReturnValue(createSetupContractMock()); return mocked; }; diff --git a/src/core/public/utils/share_weak_replay.ts b/src/core/public/utils/share_weak_replay.ts index 5cff5c7d817c..07357826c874 100644 --- a/src/core/public/utils/share_weak_replay.ts +++ b/src/core/public/utils/share_weak_replay.ts @@ -40,7 +40,7 @@ import { takeUntil } from 'rxjs/operators'; * - Replay-ability is only maintained while the source is active, if it completes or errors * then complete/error is sent to the current subscribers and the replay buffer is cleared. * - * - Any subscription after the the source completes or errors will create a new subscription + * - Any subscription after the source completes or errors will create a new subscription * to the source observable. * * @param bufferSize Optional, default is `Number.POSITIVE_INFINITY` diff --git a/src/core/public/workspace/index.ts b/src/core/public/workspace/index.ts index 712ad657fa65..a605aff02fb1 100644 --- a/src/core/public/workspace/index.ts +++ b/src/core/public/workspace/index.ts @@ -8,4 +8,5 @@ export { WorkspacesService, WorkspacesSetup, WorkspaceObject, + IWorkspaceClient, } from './workspaces_service'; diff --git a/src/core/public/workspace/workspaces_service.mock.ts b/src/core/public/workspace/workspaces_service.mock.ts index 9e8cdfce7393..ae0f4deb1d68 100644 --- a/src/core/public/workspace/workspaces_service.mock.ts +++ b/src/core/public/workspace/workspaces_service.mock.ts @@ -6,7 +6,7 @@ import { BehaviorSubject } from 'rxjs'; import type { PublicMethodsOf } from '@osd/utility-types'; -import { WorkspacesService, WorkspaceObject } from './workspaces_service'; +import { WorkspacesService, WorkspaceObject, IWorkspaceClient } from './workspaces_service'; const createWorkspacesSetupContractMock = () => { const currentWorkspaceId$ = new BehaviorSubject(''); @@ -18,6 +18,7 @@ const createWorkspacesSetupContractMock = () => { workspaceList$, currentWorkspace$, initialized$, + setClient: jest.fn(), }; }; @@ -26,11 +27,14 @@ const createWorkspacesStartContractMock = () => { const workspaceList$ = new BehaviorSubject([]); const currentWorkspace$ = new BehaviorSubject(null); const initialized$ = new BehaviorSubject(false); + const client$ = new BehaviorSubject(null); + return { currentWorkspaceId$, workspaceList$, currentWorkspace$, initialized$, + client$, }; }; diff --git a/src/core/public/workspace/workspaces_service.test.ts b/src/core/public/workspace/workspaces_service.test.ts index 4eca97ef2ed2..076100c8f558 100644 --- a/src/core/public/workspace/workspaces_service.test.ts +++ b/src/core/public/workspace/workspaces_service.test.ts @@ -3,14 +3,21 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { WorkspacesService, WorkspacesStart } from './workspaces_service'; +import { + WorkspacesService, + WorkspacesSetup, + WorkspacesStart, + IWorkspaceClient, +} from './workspaces_service'; describe('WorkspacesService', () => { let workspaces: WorkspacesService; let workspacesStart: WorkspacesStart; + let workspacesSetUp: WorkspacesSetup; beforeEach(() => { workspaces = new WorkspacesService(); + workspacesSetUp = workspaces.setup(); workspacesStart = workspaces.start(); }); @@ -31,6 +38,16 @@ describe('WorkspacesService', () => { expect(workspacesStart.workspaceList$.value.length).toBe(0); }); + it('client$ is not set by default', () => { + expect(workspacesStart.client$.value).toBe(null); + }); + + it('client is updated when set client', () => { + const client: IWorkspaceClient = { copy: jest.fn() }; + workspacesSetUp.setClient(client); + expect(workspacesStart.client$.value).toEqual(client); + }); + it('currentWorkspace is updated when currentWorkspaceId changes', () => { expect(workspacesStart.currentWorkspace$.value).toBe(null); diff --git a/src/core/public/workspace/workspaces_service.ts b/src/core/public/workspace/workspaces_service.ts index e4cf3bc7a826..6f934c294eba 100644 --- a/src/core/public/workspace/workspaces_service.ts +++ b/src/core/public/workspace/workspaces_service.ts @@ -10,6 +10,10 @@ import { CoreService, WorkspaceAttribute } from '../../types'; export type WorkspaceObject = WorkspaceAttribute & { readonly?: boolean }; +export interface IWorkspaceClient { + copy(objects: any[], targetWorkspace: string, includeReferencesDeep?: boolean): Promise; +} + interface WorkspaceObservables { /** * Indicates the current activated workspace id, the value should be changed every time @@ -43,14 +47,20 @@ enum WORKSPACE_ERROR { WORKSPACE_IS_STALE = 'WORKSPACE_IS_STALE', } -export type WorkspacesSetup = WorkspaceObservables; -export type WorkspacesStart = WorkspaceObservables; +export type WorkspacesSetup = WorkspaceObservables & { + setClient: (client: IWorkspaceClient) => void; +}; + +export type WorkspacesStart = WorkspaceObservables & { + client$: BehaviorSubject; +}; export class WorkspacesService implements CoreService { private currentWorkspaceId$ = new BehaviorSubject(''); private workspaceList$ = new BehaviorSubject([]); private currentWorkspace$ = new BehaviorSubject(null); private initialized$ = new BehaviorSubject(false); + private client$ = new BehaviorSubject(null); constructor() { combineLatest([this.initialized$, this.workspaceList$, this.currentWorkspaceId$]).subscribe( @@ -87,6 +97,9 @@ export class WorkspacesService implements CoreService { + this.client$.next(client); + }, }; } @@ -96,6 +109,7 @@ export class WorkspacesService implements CoreService { }); }); }); + +describe('getRedirectUrl', () => { + it('should prepend with basePath when no requestWorkspaceId is present in request', () => { + const request = httpServerMock.createOpenSearchDashboardsRequest(); + expect( + getRedirectUrl({ + request, + nextUrl: '/app/next_url', + basePath: '/base', + }) + ).toEqual('/base/app/next_url'); + }); + + it('should prepend with basePath and workspace prefix when requestWorkspaceId is present in request', () => { + const request = httpServerMock.createOpenSearchDashboardsRequest(); + updateWorkspaceState(request, { + requestWorkspaceId: 'workspace_id', + }); + expect( + getRedirectUrl({ + request, + nextUrl: '/app/next_url', + basePath: '/base', + }) + ).toEqual('/base/w/workspace_id/app/next_url'); + }); +}); diff --git a/src/core/server/http/http_tools.ts b/src/core/server/http/http_tools.ts index d0136500c7b0..4524f44e5e09 100644 --- a/src/core/server/http/http_tools.ts +++ b/src/core/server/http/http_tools.ts @@ -35,6 +35,9 @@ import { ValidationError } from 'joi'; import uuid from 'uuid'; import { HttpConfig } from './http_config'; import { validateObject } from './prototype_pollution'; +import { getWorkspaceState } from '../utils'; +import { OpenSearchDashboardsRequest } from './router'; +import { WORKSPACE_PATH_PREFIX } from '../../../core/utils'; /** * Converts OpenSearch Dashboards `HttpConfig` into `ServerOptions` that are accepted by the Hapi server. @@ -194,3 +197,24 @@ export function getRequestId(request: Request, options: HttpConfig['requestId']) ? request.headers['x-opaque-id'] ?? uuid.v4() : uuid.v4(); } + +/** + * Return back the expected redirect url with basePath and conditional workspace id based on the request. + * This method should be used when the request is not authenticated and need a redirect url after login. + * And it should take care all the stuff on prepending required params into the path. + * @param request Hapi request object + * @returns the expected next url + */ +export function getRedirectUrl(props: { + request: OpenSearchDashboardsRequest; + nextUrl: string; // The url without basePath and workspace id prefix + basePath: string; +}) { + const { request, nextUrl, basePath } = props; + const workspaceState = getWorkspaceState(request); + if (workspaceState.requestWorkspaceId) { + return `${basePath}${WORKSPACE_PATH_PREFIX}/${workspaceState.requestWorkspaceId}${nextUrl}`; + } + + return `${basePath}${nextUrl}`; +} diff --git a/src/core/server/http/index.ts b/src/core/server/http/index.ts index 14397456afd6..1ac011747fc4 100644 --- a/src/core/server/http/index.ts +++ b/src/core/server/http/index.ts @@ -105,3 +105,4 @@ export { } from './cookie_session_storage'; export * from './types'; export { BasePath, IBasePath } from './base_path_service'; +export { getRedirectUrl } from './http_tools'; diff --git a/src/core/server/index.ts b/src/core/server/index.ts index efabe251165c..921228416f42 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -341,6 +341,8 @@ export { Permissions, updateDataSourceNameInVegaSpec, extractVegaSpecFromSavedObject, + extractTimelineExpression, + updateDataSourceNameInTimeline, } from './saved_objects'; export { @@ -368,12 +370,7 @@ export { } from './metrics'; export { AppCategory, WorkspaceAttribute } from '../types'; -export { - DEFAULT_APP_CATEGORIES, - PUBLIC_WORKSPACE_ID, - PUBLIC_WORKSPACE_NAME, - WORKSPACE_TYPE, -} from '../utils'; +export { DEFAULT_APP_CATEGORIES, WORKSPACE_TYPE } from '../utils'; export { SavedObject, diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts index 5ef01f88f3f7..17600d421480 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.test.ts @@ -354,6 +354,8 @@ describe('requiredEnginePlugins', () => { requiredBundles: [], server: true, ui: false, + supportedOSDataSourceVersions: '', + requiredOSDataSourcePlugins: [], }); }); }); @@ -416,6 +418,8 @@ test('set defaults for all missing optional fields', async () => { requiredBundles: [], server: true, ui: false, + supportedOSDataSourceVersions: '', + requiredOSDataSourcePlugins: [], }); }); @@ -434,6 +438,11 @@ test('return all set optional fields as they are in manifest', async () => { 'test-opensearch-plugin-2': '>=1.0.0', }, ui: true, + supportedOSDataSourceVersions: '>=1.0.0', + requiredOSDataSourcePlugins: [ + 'some-required-data-source-plugin-1', + 'some-required-data-source-plugin-2', + ], }) ) ); @@ -452,6 +461,11 @@ test('return all set optional fields as they are in manifest', async () => { }, server: false, ui: true, + supportedOSDataSourceVersions: '>=1.0.0', + requiredOSDataSourcePlugins: [ + 'some-required-data-source-plugin-1', + 'some-required-data-source-plugin-2', + ], }); }); @@ -481,6 +495,8 @@ test('return manifest when plugin expected OpenSearch Dashboards version matches requiredBundles: [], server: true, ui: false, + supportedOSDataSourceVersions: '', + requiredOSDataSourcePlugins: [], }); }); @@ -509,5 +525,7 @@ test('return manifest when plugin expected OpenSearch Dashboards version is `ope requiredBundles: [], server: true, ui: true, + supportedOSDataSourceVersions: '', + requiredOSDataSourcePlugins: [], }); }); diff --git a/src/core/server/plugins/discovery/plugin_manifest_parser.ts b/src/core/server/plugins/discovery/plugin_manifest_parser.ts index cfc23f0dc197..d4db5f3df197 100644 --- a/src/core/server/plugins/discovery/plugin_manifest_parser.ts +++ b/src/core/server/plugins/discovery/plugin_manifest_parser.ts @@ -68,6 +68,8 @@ const KNOWN_MANIFEST_FIELDS = (() => { server: true, extraPublicDirs: true, requiredBundles: true, + supportedOSDataSourceVersions: true, + requiredOSDataSourcePlugins: true, }; return new Set(Object.keys(manifestFields)); @@ -245,6 +247,13 @@ export async function parseManifest( ui: includesUiPlugin, server: includesServerPlugin, extraPublicDirs: manifest.extraPublicDirs, + supportedOSDataSourceVersions: + manifest.supportedOSDataSourceVersions !== undefined + ? manifest.supportedOSDataSourceVersions + : '', + requiredOSDataSourcePlugins: Array.isArray(manifest.requiredOSDataSourcePlugins) + ? manifest.requiredOSDataSourcePlugins + : [], }; } diff --git a/src/core/server/plugins/plugin.test.ts b/src/core/server/plugins/plugin.test.ts index e48e81daa5c0..f1e91c9eb6f7 100644 --- a/src/core/server/plugins/plugin.test.ts +++ b/src/core/server/plugins/plugin.test.ts @@ -78,6 +78,8 @@ function createPluginManifest(manifestProps: Partial = {}): Plug requiredBundles: [], server: true, ui: true, + supportedOSDataSourceVersions: '>=1.0.0', + requiredOSDataSourcePlugins: ['some-required-data-source-plugin'], ...manifestProps, }; } @@ -127,6 +129,8 @@ test('`constructor` correctly initializes plugin instance', () => { expect(plugin.path).toBe('some-plugin-path'); expect(plugin.requiredPlugins).toEqual(['some-required-dep']); expect(plugin.optionalPlugins).toEqual(['some-optional-dep']); + expect(plugin.supportedOSDataSourceVersions).toEqual('>=1.0.0'); + expect(plugin.requiredOSDataSourcePlugins).toEqual(['some-required-data-source-plugin']); }); test('`setup` fails if `plugin` initializer is not exported', async () => { diff --git a/src/core/server/plugins/plugin.ts b/src/core/server/plugins/plugin.ts index 0aa13e53d650..348946488025 100644 --- a/src/core/server/plugins/plugin.ts +++ b/src/core/server/plugins/plugin.ts @@ -70,6 +70,8 @@ export class PluginWrapper< public readonly requiredBundles: PluginManifest['requiredBundles']; public readonly includesServerPlugin: PluginManifest['server']; public readonly includesUiPlugin: PluginManifest['ui']; + public readonly supportedOSDataSourceVersions: PluginManifest['supportedOSDataSourceVersions']; + public readonly requiredOSDataSourcePlugins: PluginManifest['requiredOSDataSourcePlugins']; private readonly log: Logger; private readonly initializerContext: PluginInitializerContext; @@ -100,6 +102,8 @@ export class PluginWrapper< this.requiredBundles = params.manifest.requiredBundles; this.includesServerPlugin = params.manifest.server; this.includesUiPlugin = params.manifest.ui; + this.supportedOSDataSourceVersions = params.manifest.supportedOSDataSourceVersions; + this.requiredOSDataSourcePlugins = params.manifest.requiredOSDataSourcePlugins; } /** diff --git a/src/core/server/plugins/types.ts b/src/core/server/plugins/types.ts index 9ce1682a371b..60f9b1eba6f8 100644 --- a/src/core/server/plugins/types.ts +++ b/src/core/server/plugins/types.ts @@ -200,6 +200,18 @@ export interface PluginManifest { * @deprecated */ readonly extraPublicDirs?: string[]; + + /** + * Specifies the supported version range when adding OpenSearch cluster as data sources. + * Value will be following semver as a range for example ">= 1.3.0" + */ + readonly supportedOSDataSourceVersions?: string; + + /** + * Specifies the required backend plugins that **must be** installed and enabled on the data source for this plugin to function properly + * when adding OpenSearch cluster as data sources. + */ + readonly requiredOSDataSourcePlugins?: readonly PluginName[]; } /** diff --git a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap index 01c238783ce5..ad92d759a832 100644 --- a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap +++ b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap @@ -8,6 +8,7 @@ Object { "branding": Object { "applicationTitle": "OpenSearch Dashboards", "assetFolderUrl": "/mock-server-basepath/ui/default_branding", + "darkMode": false, "loadingLogo": Object {}, "logo": Object {}, "mark": Object {}, @@ -60,6 +61,7 @@ Object { "branding": Object { "applicationTitle": "OpenSearch Dashboards", "assetFolderUrl": "/mock-server-basepath/ui/default_branding", + "darkMode": false, "loadingLogo": Object {}, "logo": Object {}, "mark": Object {}, @@ -112,6 +114,7 @@ Object { "branding": Object { "applicationTitle": "OpenSearch Dashboards", "assetFolderUrl": "/mock-server-basepath/ui/default_branding", + "darkMode": true, "loadingLogo": Object {}, "logo": Object {}, "mark": Object {}, @@ -164,6 +167,7 @@ Object { "branding": Object { "applicationTitle": "OpenSearch Dashboards", "assetFolderUrl": "/mock-server-basepath/ui/default_branding", + "darkMode": true, "loadingLogo": Object {}, "logo": Object {}, "mark": Object {}, @@ -220,6 +224,7 @@ Object { "branding": Object { "applicationTitle": "OpenSearch Dashboards", "assetFolderUrl": "/ui/default_branding", + "darkMode": false, "loadingLogo": Object {}, "logo": Object {}, "mark": Object {}, @@ -272,6 +277,7 @@ Object { "branding": Object { "applicationTitle": "OpenSearch Dashboards", "assetFolderUrl": "/mock-server-basepath/ui/default_branding", + "darkMode": false, "loadingLogo": Object {}, "logo": Object {}, "mark": Object {}, @@ -324,6 +330,7 @@ Object { "branding": Object { "applicationTitle": "OpenSearch Dashboards", "assetFolderUrl": "/mock-server-basepath/ui/default_branding", + "darkMode": false, "loadingLogo": Object {}, "logo": Object {}, "mark": Object {}, diff --git a/src/core/server/rendering/rendering_service.tsx b/src/core/server/rendering/rendering_service.tsx index 022eb2ab2aaf..36f55bb22097 100644 --- a/src/core/server/rendering/rendering_service.tsx +++ b/src/core/server/rendering/rendering_service.tsx @@ -33,6 +33,7 @@ import { renderToStaticMarkup } from 'react-dom/server'; import { first, take } from 'rxjs/operators'; import { i18n } from '@osd/i18n'; import { Agent as HttpsAgent } from 'https'; +import { themeVersionValueMap } from '@osd/ui-shared-deps'; import Axios from 'axios'; // @ts-expect-error untyped internal module used to prevent axios from using xhr adapter in tests @@ -96,8 +97,24 @@ export class RenderingService { defaults: uiSettings.getRegistered(), user: includeUserSettings ? await uiSettings.getUserProvided() : {}, }; + // Cannot use `uiSettings.get()` since a user might not be authenticated + const darkMode = + (settings.user?.['theme:darkMode']?.userValue ?? + uiSettings.getOverrideOrDefault('theme:darkMode')) || + false; + + // At the very least, the schema should define a default theme; the '' will be unreachable + const configuredThemeVersion = + (settings.user?.['theme:version']?.userValue ?? + uiSettings.getOverrideOrDefault('theme:version')) || + ''; + // Validate themeVersion is in valid format + const themeVersion = + themeVersionValueMap[configuredThemeVersion] || + (uiSettings.getDefault('theme:version') as string); const brandingAssignment = await this.assignBrandingConfig( + darkMode, opensearchDashboardsConfig as OpenSearchDashboardsConfigType ); @@ -107,9 +124,10 @@ export class RenderingService { strictCsp: http.csp.strict, uiPublicUrl, bootstrapScriptUrl: `${basePath}/bootstrap.js`, - startupScriptUrl: `${basePath}/startup.js`, i18n: i18n.translate, locale: i18n.getLocale(), + darkMode, + themeVersion, injectedMetadata: { version: env.packageInfo.version, buildNumber: env.packageInfo.buildNum, @@ -142,6 +160,7 @@ export class RenderingService { uiSettings: settings, }, branding: { + darkMode, assetFolderUrl: `${uiPublicUrl}/default_branding`, logo: { defaultUrl: brandingAssignment.logoDefault, @@ -195,16 +214,20 @@ export class RenderingService { /** * Assign values for branding related configurations based on branding validation - * by calling checkBrandingValid(). If URL is valid, pass in + * by calling checkBrandingValid(). For dark mode URLs, add additional validation + * to see if there is a valid default mode URL exist first. If URL is valid, pass in * the actual URL; if not, pass in undefined. * + * @param {boolean} darkMode * @param {Readonly} opensearchDashboardsConfig * @returns {BrandingAssignment} valid URLs or undefined assigned for each branding configs */ private assignBrandingConfig = async ( + darkMode: boolean, opensearchDashboardsConfig: Readonly ): Promise => { const brandingValidation: BrandingValidation = await this.checkBrandingValid( + darkMode, opensearchDashboardsConfig ); const branding = opensearchDashboardsConfig.branding; @@ -223,18 +246,47 @@ export class RenderingService { : undefined; // assign dark mode URLs based on brandingValidation function result - const logoDarkmode = brandingValidation.isLogoDarkmodeValid + let logoDarkmode = brandingValidation.isLogoDarkmodeValid ? branding.logo.darkModeUrl : undefined; - const markDarkmode = brandingValidation.isMarkDarkmodeValid + let markDarkmode = brandingValidation.isMarkDarkmodeValid ? branding.mark.darkModeUrl : undefined; - const loadingLogoDarkmode = brandingValidation.isLoadingLogoDarkmodeValid + let loadingLogoDarkmode = brandingValidation.isLoadingLogoDarkmodeValid ? branding.loadingLogo.darkModeUrl : undefined; + /** + * For dark mode URLs, we added another validation: + * user can only provide a dark mode URL after providing a valid default mode URL, + * If user provides a valid dark mode URL but fails to provide a valid default mode URL, + * return undefined for the dark mode URL + */ + if (logoDarkmode && !logoDefault) { + this.logger + .get('branding') + .error('Must provide a valid logo default mode URL before providing a logo dark mode URL'); + logoDarkmode = undefined; + } + + if (markDarkmode && !markDefault) { + this.logger + .get('branding') + .error('Must provide a valid mark default mode URL before providing a mark dark mode URL'); + markDarkmode = undefined; + } + + if (loadingLogoDarkmode && !loadingLogoDefault) { + this.logger + .get('branding') + .error( + 'Must provide a valid loading logo default mode URL before providing a loading logo dark mode URL' + ); + loadingLogoDarkmode = undefined; + } + // assign favicon based on brandingValidation function result const favicon = brandingValidation.isFaviconValid ? branding.faviconUrl : undefined; @@ -266,30 +318,35 @@ export class RenderingService { * user inputs valid or invalid URLs by calling isUrlValid() function. Also * check if title is valid by calling isTitleValid() function. * + * @param {boolean} darkMode * @param {Readonly} opensearchDashboardsConfig * @returns {BrandingValidation} indicate valid/invalid URL for each branding config */ private checkBrandingValid = async ( + darkMode: boolean, opensearchDashboardsConfig: Readonly ): Promise => { const branding = opensearchDashboardsConfig.branding; const isLogoDefaultValid = await this.isUrlValid(branding.logo.defaultUrl, 'logo default'); - const isLogoDarkmodeValid = await this.isUrlValid(branding.logo.darkModeUrl, 'logo darkMode'); + const isLogoDarkmodeValid = darkMode + ? await this.isUrlValid(branding.logo.darkModeUrl, 'logo darkMode') + : false; const isMarkDefaultValid = await this.isUrlValid(branding.mark.defaultUrl, 'mark default'); - const isMarkDarkmodeValid = await this.isUrlValid(branding.mark.darkModeUrl, 'mark darkMode'); + const isMarkDarkmodeValid = darkMode + ? await this.isUrlValid(branding.mark.darkModeUrl, 'mark darkMode') + : false; const isLoadingLogoDefaultValid = await this.isUrlValid( branding.loadingLogo.defaultUrl, 'loadingLogo default' ); - const isLoadingLogoDarkmodeValid = await this.isUrlValid( - branding.loadingLogo.darkModeUrl, - 'loadingLogo darkMode' - ); + const isLoadingLogoDarkmodeValid = darkMode + ? await this.isUrlValid(branding.loadingLogo.darkModeUrl, 'loadingLogo darkMode') + : false; const isFaviconValid = await this.isUrlValid(branding.faviconUrl, 'favicon'); diff --git a/src/core/server/rendering/types.ts b/src/core/server/rendering/types.ts index 0e0d63b67c79..15e6af4c83f4 100644 --- a/src/core/server/rendering/types.ts +++ b/src/core/server/rendering/types.ts @@ -43,9 +43,10 @@ export interface RenderingMetadata { strictCsp: ICspConfig['strict']; uiPublicUrl: string; bootstrapScriptUrl: string; - startupScriptUrl: string; i18n: typeof i18n.translate; locale: string; + darkMode: boolean; + themeVersion: string; injectedMetadata: { version: string; buildNumber: number; diff --git a/src/core/server/rendering/views/__snapshots__/template.test.tsx.snap b/src/core/server/rendering/views/__snapshots__/template.test.tsx.snap index 1204752a6469..36d073992ec8 100644 --- a/src/core/server/rendering/views/__snapshots__/template.test.tsx.snap +++ b/src/core/server/rendering/views/__snapshots__/template.test.tsx.snap @@ -50,6 +50,10 @@ Array [ content="[object Object]/ui/favicons/browserconfig.xml" name="msapplication-config" />, + , null, , null, - , - ' }, + }) + ); + expect(getByTestId('workspaceForm-workspaceDetails-descriptionInputText').value).toEqual( + '' + ); + expect(alertSpy).toBeCalledTimes(0); + alertSpy.mockRestore(); + }); +}); diff --git a/src/plugins/workspace/public/components/workspace_detail/workspace_detail.tsx b/src/plugins/workspace/public/components/workspace_detail/workspace_detail.tsx new file mode 100644 index 000000000000..8f02be4e6909 --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_detail/workspace_detail.tsx @@ -0,0 +1,262 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useEffect, useState } from 'react'; +import { + EuiPage, + EuiSpacer, + EuiPageBody, + EuiPageContent, + EuiConfirmModal, + EuiTabbedContent, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import { useObservable } from 'react-use'; +import { BehaviorSubject, of } from 'rxjs'; +import { useHistory, useLocation } from 'react-router-dom'; +import { WorkspaceUseCase } from '../../types'; +import { WorkspaceDetailForm, useWorkspaceFormContext } from '../workspace_form'; +import { WorkspaceDetailPanel } from './workspace_detail_panel'; +import { DeleteWorkspaceModal } from '../delete_workspace_modal'; +import { WORKSPACE_LIST_APP_ID } from '../../../common/constants'; +import { cleanWorkspaceId } from '../../../../../core/public/utils'; +import { DetailTab, DetailTabTitles, WorkspaceOperationType } from '../workspace_form/constants'; +import { CoreStart, WorkspaceAttribute } from '../../../../../core/public'; +import { getFirstUseCaseOfFeatureConfigs, getUseCaseUrl } from '../../utils'; +import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; +import { DataSourceManagementPluginSetup } from '../../../../../plugins/data_source_management/public'; +import { SelectDataSourceDetailPanel } from './select_data_source_panel'; +import { WorkspaceBottomBar } from './workspace_bottom_bar'; +import { + NavigationPublicPluginStart, + TopNavControlDescriptionData, + TopNavControlIconData, +} from '../../../../navigation/public'; + +export interface WorkspaceDetailProps { + registeredUseCases$: BehaviorSubject; +} + +export const WorkspaceDetail = (props: WorkspaceDetailProps) => { + const { + services: { + workspaces, + application, + http, + savedObjects, + dataSourceManagement, + uiSettings, + navigationUI: { HeaderControl }, + }, + } = useOpenSearchDashboards<{ + CoreStart: CoreStart; + dataSourceManagement?: DataSourceManagementPluginSetup; + navigationUI: NavigationPublicPluginStart['ui']; + }>(); + + const { + formData, + isEditing, + formId, + numberOfErrors, + handleResetForm, + numberOfChanges, + setIsEditing, + } = useWorkspaceFormContext(); + const [deletedWorkspace, setDeletedWorkspace] = useState(null); + const [selectedTabId, setSelectedTabId] = useState(DetailTab.Details); + const [modalVisible, setModalVisible] = useState(false); + const [tabId, setTabId] = useState(DetailTab.Details); + + const availableUseCases = useObservable(props.registeredUseCases$, []); + const isDashboardAdmin = !!application?.capabilities?.dashboards?.isDashboardAdmin; + const currentWorkspace = useObservable(workspaces ? workspaces.currentWorkspace$ : of(null)); + const isPermissionEnabled = application?.capabilities.workspaces.permissionEnabled; + const currentUseCase = availableUseCases.find( + (useCase) => useCase.id === getFirstUseCaseOfFeatureConfigs(currentWorkspace?.features ?? []) + ); + const history = useHistory(); + const location = useLocation(); + + useEffect(() => { + const params = new URLSearchParams(location.search); + const tab = params.get('tab'); + if (tab) { + setSelectedTabId(tab); + } + }, [location.search]); + + if (!currentWorkspace || !application || !http || !savedObjects || !uiSettings) { + return null; + } + + const useCaseUrl = getUseCaseUrl(currentUseCase, currentWorkspace, application, http); + + const handleTabClick = (tab: any) => { + if (numberOfChanges > 0) { + setTabId(tab.id); + setModalVisible(true); + return; + } + history.push(`?tab=${tab.id}`); + setIsEditing(false); + setSelectedTabId(tab.id); + }; + + const handleBadgeClick = () => { + if (selectedTabId !== DetailTab.Collaborators && numberOfChanges > 0) { + setTabId(DetailTab.Collaborators); + setModalVisible(true); + return; + } + history.push(`?tab=${DetailTab.Collaborators}`); + setSelectedTabId(DetailTab.Collaborators); + }; + + const createDetailTab = (id: DetailTab, detailTitle: string) => ({ + id, + name: detailTitle, + content: ( + + ), + }); + + const detailTabs = [ + createDetailTab(DetailTab.Details, DetailTabTitles.details), + ...(dataSourceManagement + ? [ + { + id: DetailTab.DataSources, + name: DetailTabTitles.dataSources, + content: ( + + ), + }, + ] + : []), + ...(isPermissionEnabled + ? [createDetailTab(DetailTab.Collaborators, DetailTabTitles.collaborators)] + : []), + ]; + + return ( + <> + + {currentWorkspace.description && ( + + )} + setDeletedWorkspace(currentWorkspace), + color: 'danger', + iconType: 'trash', + ariaLabel: i18n.translate('workspace.detail.delete.button', { + defaultMessage: 'Delete', + }), + testId: 'workspace-detail-delete-button', + controlType: 'icon', + display: 'base', + } as TopNavControlIconData, + ]} + setMountPoint={application.setAppRightControls} + /> + + + + + + + tab.id === selectedTabId)]} + onTabClick={handleTabClick} + size="s" + /> + + {deletedWorkspace && ( + setDeletedWorkspace(null)} + onDeleteSuccess={() => { + window.setTimeout(() => { + window.location.assign( + cleanWorkspaceId( + application.getUrlForApp(WORKSPACE_LIST_APP_ID, { + absolute: false, + }) + ) + ); + }, 1000); + }} + /> + )} + {modalVisible && ( + setModalVisible(false)} + onConfirm={() => { + handleResetForm(); + setModalVisible(false); + history.push(`?tab=${tabId}`); + setSelectedTabId(tabId); + }} + cancelButtonText={i18n.translate('workspace.form.cancelButtonText', { + defaultMessage: 'Cancel', + })} + confirmButtonText={i18n.translate('workspace.form.confirmButtonText', { + defaultMessage: 'Navigate away', + })} + buttonColor="danger" + defaultFocusedButton="confirm" + > + {i18n.translate('workspace.form.cancelModal.body', { + defaultMessage: 'Any unsaved changes will be lost.', + })} + + )} + + {isEditing && ( + + )} + + ); +}; diff --git a/src/plugins/workspace/public/components/workspace_detail/workspace_detail_panel.tsx b/src/plugins/workspace/public/components/workspace_detail/workspace_detail_panel.tsx new file mode 100644 index 000000000000..833581f0f54c --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_detail/workspace_detail_panel.tsx @@ -0,0 +1,140 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { + EuiLink, + EuiText, + EuiCopy, + EuiBadge, + EuiFlexItem, + EuiFlexGroup, + EuiButtonIcon, + EuiColorPickerSwatch, +} from '@elastic/eui'; +import moment from 'moment'; +import { i18n } from '@osd/i18n'; +import { WorkspaceUseCase } from '../../types'; +import { WorkspaceObject } from '../../../../../core/public'; +import { WorkspaceAttributeWithPermission } from '../../../../../core/types'; + +const detailUseCase = i18n.translate('workspace.detail.useCase', { + defaultMessage: 'Use case', +}); + +const detailOwner = i18n.translate('workspace.detail.owner', { + defaultMessage: 'Owner', +}); + +const detailLastUpdated = i18n.translate('workspace.detail.lastUpdated', { + defaultMessage: 'Last updated', +}); + +const detailID = i18n.translate('workspace.detail.id', { + defaultMessage: 'ID', +}); + +const workspaceOverview = i18n.translate('workspace.detail.workspaceOverview', { + defaultMessage: 'Workspace overview', +}); + +const overview = i18n.translate('workspace.detail.overview', { + defaultMessage: 'Overview', +}); + +function getOwners(currentWorkspace: WorkspaceAttributeWithPermission) { + const { groups = [], users = [] } = currentWorkspace?.permissions?.write || {}; + return [...groups, ...users]; +} + +interface WorkspaceDetailPanelProps { + useCaseUrl: string; + handleBadgeClick: () => void; + currentUseCase: WorkspaceUseCase | undefined; + currentWorkspace: WorkspaceObject; + dateFormat: string; +} +export const WorkspaceDetailPanel = ({ + useCaseUrl, + currentUseCase, + handleBadgeClick, + currentWorkspace, + dateFormat, +}: WorkspaceDetailPanelProps) => { + const owners = getOwners(currentWorkspace); + const formatDate = (lastUpdatedTime: string) => { + return moment(lastUpdatedTime).format(dateFormat); + }; + + return ( + + + +

{detailUseCase}

+

+ + {currentUseCase?.title} +

+
+
+ + +

{detailOwner}

+

+ {owners?.at(0)}   + {owners && owners.length > 1 && ( + + +{owners?.length - 1} more + + )} +

+
+
+ + +

{detailLastUpdated}

+

{formatDate(currentWorkspace.lastUpdatedTime || '')}

+
+
+ + +

{detailID}

+

+ {currentWorkspace.id} + + {(copy) => ( + + )} + +

+
+
+ + +

{workspaceOverview}

+

+ + {overview} + +

+
+
+
+ ); +}; diff --git a/src/plugins/workspace/public/components/workspace_detail_app.tsx b/src/plugins/workspace/public/components/workspace_detail_app.tsx new file mode 100644 index 000000000000..1347b130575b --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_detail_app.tsx @@ -0,0 +1,181 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useCallback, useEffect, useState } from 'react'; +import { I18nProvider } from '@osd/i18n/react'; +import { i18n } from '@osd/i18n'; +import { CoreStart } from 'opensearch-dashboards/public'; +import { useObservable } from 'react-use'; +import { EuiBreadcrumb } from '@elastic/eui'; +import { of } from 'rxjs'; +import { useOpenSearchDashboards } from '../../../opensearch_dashboards_react/public'; +import { WorkspaceDetail, WorkspaceDetailProps } from './workspace_detail/workspace_detail'; +import { WorkspaceFormProvider } from './workspace_form'; +import { + WorkspaceFormSubmitData, + WorkspaceOperationType, + convertPermissionSettingsToPermissions, + convertPermissionsToPermissionSettings, +} from './workspace_form'; +import { DataSource } from '../../common/types'; +import { WorkspaceClient } from '../workspace_client'; +import { formatUrlWithWorkspaceId } from '../../../../core/public/utils'; +import { WORKSPACE_DETAIL_APP_ID } from '../../common/constants'; +import { getDataSourcesList } from '../utils'; +import { WorkspaceAttributeWithPermission } from '../../../../core/types'; + +function getFormDataFromWorkspace( + currentWorkspace: WorkspaceAttributeWithPermission | null | undefined +) { + if (!currentWorkspace) { + return null; + } + return { + ...currentWorkspace, + permissionSettings: currentWorkspace.permissions + ? convertPermissionsToPermissionSettings(currentWorkspace.permissions) + : currentWorkspace.permissions, + }; +} + +type FormDataFromWorkspace = ReturnType & { + selectedDataSources: DataSource[]; +}; + +export const WorkspaceDetailApp = (props: WorkspaceDetailProps) => { + const { + services: { + workspaces, + chrome, + application, + savedObjects, + notifications, + workspaceClient, + http, + }, + } = useOpenSearchDashboards<{ CoreStart: CoreStart; workspaceClient: WorkspaceClient }>(); + const [currentWorkspaceFormData, setCurrentWorkspaceFormData] = useState(); + const currentWorkspace = useObservable(workspaces ? workspaces.currentWorkspace$ : of(null)); + const availableUseCases = useObservable(props.registeredUseCases$, []); + const isPermissionEnabled = application?.capabilities.workspaces.permissionEnabled; + + /** + * set breadcrumbs to chrome + */ + useEffect(() => { + const breadcrumbs: EuiBreadcrumb[] = [ + { + text: 'Home', + onClick: () => { + application?.navigateToApp('home'); + }, + }, + ]; + if (currentWorkspace) { + breadcrumbs.push({ + text: currentWorkspace.name, + }); + breadcrumbs.push({ + text: i18n.translate('workspace.detail.title', { + defaultMessage: '{name} settings', + values: { + name: currentWorkspace.name, + }, + }), + }); + } + chrome?.setBreadcrumbs(breadcrumbs); + }, [chrome, currentWorkspace, application]); + + useEffect(() => { + const rawFormData = getFormDataFromWorkspace(currentWorkspace); + + if (rawFormData && savedObjects && currentWorkspace) { + getDataSourcesList(savedObjects.client, [currentWorkspace.id]).then((selectedDataSources) => { + setCurrentWorkspaceFormData({ + ...rawFormData, + selectedDataSources, + }); + }); + } + }, [currentWorkspace, savedObjects]); + + const handleWorkspaceFormSubmit = useCallback( + async (data: WorkspaceFormSubmitData) => { + let result; + if (!currentWorkspace) { + notifications?.toasts.addDanger({ + title: i18n.translate('Cannot find current workspace', { + defaultMessage: 'Cannot update workspace', + }), + }); + return; + } + + try { + const { permissionSettings, selectedDataSources, ...attributes } = data; + const selectedDataSourceIds = (selectedDataSources ?? []).map((ds: DataSource) => { + return ds.id; + }); + + result = await workspaceClient.update(currentWorkspace.id, attributes, { + dataSources: selectedDataSourceIds, + permissions: convertPermissionSettingsToPermissions(permissionSettings), + }); + if (result?.success) { + notifications?.toasts.addSuccess({ + title: i18n.translate('workspace.update.success', { + defaultMessage: 'Update workspace successfully', + }), + }); + if (application && http) { + // Redirect page after one second, leave one second time to show update successful toast. + window.setTimeout(() => { + window.location.href = formatUrlWithWorkspaceId( + application.getUrlForApp(WORKSPACE_DETAIL_APP_ID, { + absolute: true, + }), + currentWorkspace.id, + http.basePath + ); + }, 1000); + } + return; + } else { + throw new Error(result?.error ? result?.error : 'update workspace failed'); + } + } catch (error) { + notifications?.toasts.addDanger({ + title: i18n.translate('workspace.update.failed', { + defaultMessage: 'Failed to update workspace', + }), + text: error instanceof Error ? error.message : JSON.stringify(error), + }); + return; + } + }, + [notifications?.toasts, currentWorkspace, http, application, workspaceClient] + ); + + if (!workspaces || !application || !http || !savedObjects || !currentWorkspaceFormData) { + return null; + } + + return ( + + + + + + ); +}; diff --git a/src/plugins/workspace/public/components/workspace_fatal_error/__snapshots__/workspace_fatal_error.test.tsx.snap b/src/plugins/workspace/public/components/workspace_fatal_error/__snapshots__/workspace_fatal_error.test.tsx.snap index 01403b9bc33c..e33c75aa2080 100644 --- a/src/plugins/workspace/public/components/workspace_fatal_error/__snapshots__/workspace_fatal_error.test.tsx.snap +++ b/src/plugins/workspace/public/components/workspace_fatal_error/__snapshots__/workspace_fatal_error.test.tsx.snap @@ -56,7 +56,7 @@ exports[` render error with callout 1`] = ` class="euiFlexItem euiFlexItem--flexGrowZero" > +
+
+
+
+ +   Explore live demo environment at + + + playground.opensearch.org + +
+
+
+
+
+
+
+ +
+ + + + +`; + +exports[`WorkspaceInitial render workspace initial page normally when user is dashboard admin 1`] = ` +
+
+
+
+
+
+ OpenSearch +
+
+
+
+
+
+
+

+ Create a workspace to get started +

+
+
+
+ Welcome to OpenSearch! This interface supports you to easily explore, enrich and visualize your data with developer-friendly tools and powerful integrations for machine learning, data process, and more. To begin, create a workspace for your use case. +
+
+ +
+

+ Create a workspace +

+
+ Organize projects by use case in a collaborative workspace +
+
+
+
+
+
+
+ +
+
+ + Observability + +
+

+ Gain visibility into your applications and infrastructure +

+
+
+
+
+
+
+
+ +
+
+ + Security Analytics + +
+

+ Enhance your security posture with advanced analytics +

+
+
+
+
+
+
+
+ +
+
+ + Search + +
+

+ Discover and query your data with ease +

+
+
+
+
+
+
+
+ +
+
+ + Essentials + +
+

+ Just the basics for exploring and analyzing data +

+
+
+
+
+
+
+
+
+
+ +
+
+
+
+ +   Explore live demo environment at + + + playground.opensearch.org + +
+
+
+
+
+
+
+ +
+
+
+
+
+`; + +exports[`WorkspaceInitial render workspace initial page normally when user is non dashboard admin 1`] = ` +
+
+
+
+
+
+ OpenSearch +
+
+
+
+
+
+
+

+ Create a workspace to get started +

+
+
+
+ Welcome to OpenSearch! This interface supports you to easily explore, enrich and visualize your data with developer-friendly tools and powerful integrations for machine learning, data process, and more. To begin, create a workspace for your use case. +
+
+ +
+

+ Create a workspace +

+
+ Organize projects by use case in a collaborative workspace +
+
+
+
+
+
+
+ +
+
+ + Observability + +
+

+ Gain visibility into your applications and infrastructure +

+
+
+
+
+
+
+
+ +
+
+ + Security Analytics + +
+

+ Enhance your security posture with advanced analytics +

+
+
+
+
+
+
+
+ +
+
+ + Search + +
+

+ Discover and query your data with ease +

+
+
+
+
+
+
+
+ +
+
+ + Essentials + +
+

+ Just the basics for exploring and analyzing data +

+
+
+
+
+
+
+
+
+
+
+ Contact your administrator to create a workspace or to be added to an existing one. +
+
+
+
+
+ +   Explore live demo environment at + + + playground.opensearch.org + +
+
+
+
+
+
+
+ +
+
+
+
+
+`; diff --git a/src/plugins/workspace/public/components/workspace_initial/workspace_initial.test.tsx b/src/plugins/workspace/public/components/workspace_initial/workspace_initial.test.tsx new file mode 100644 index 000000000000..698a4c109cfd --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_initial/workspace_initial.test.tsx @@ -0,0 +1,53 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { render } from '@testing-library/react'; +import React from 'react'; +import { WorkspaceInitial } from './workspace_initial'; +import { coreMock } from '../../../../../core/public/mocks'; +import { createOpenSearchDashboardsReactContext } from '../../../../opensearch_dashboards_react/public'; + +const mockCoreStart = coreMock.createStart(); +const WorkspaceInitialPage = (props: { isDashboardAdmin: boolean }) => { + const { isDashboardAdmin } = props; + const { Provider } = createOpenSearchDashboardsReactContext({ + ...mockCoreStart, + ...{ + application: { + ...mockCoreStart.application, + capabilities: { + ...mockCoreStart.application.capabilities, + dashboards: { + isDashboardAdmin, + }, + }, + }, + }, + }); + + return ( + + + + ); +}; + +describe('WorkspaceInitial', () => { + it('render workspace initial page normally when user is dashboard admin', async () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it('render workspace initial page normally when user is non dashboard admin', async () => { + const { container } = render(); + expect(container).toMatchSnapshot(); + }); + + it('render workspace initial page normally when theme is dark mode', async () => { + mockCoreStart.uiSettings.get.mockReturnValue(true); + const { container } = render(); + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/workspace/public/components/workspace_initial/workspace_initial.tsx b/src/plugins/workspace/public/components/workspace_initial/workspace_initial.tsx new file mode 100644 index 000000000000..7e77a2fa1130 --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_initial/workspace_initial.tsx @@ -0,0 +1,227 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { CoreStart } from 'opensearch-dashboards/public'; +import { + EuiLink, + EuiPage, + EuiIcon, + EuiText, + EuiCard, + EuiImage, + EuiPanel, + EuiTitle, + EuiSpacer, + EuiPageBody, + EuiFlexItem, + EuiFlexGroup, + EuiPageContent, + EuiSmallButton, + EuiSmallButtonEmpty, +} from '@elastic/eui'; +import { i18n } from '@osd/i18n'; +import BackgroundLightSVG from '../../assets/background_light.svg'; +import BackgroundDarkSVG from '../../assets/background_light.svg'; +import { WORKSPACE_CREATE_APP_ID } from '../../../common/constants'; +import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; + +export const WorkspaceInitial = () => { + const { + services: { application, chrome, uiSettings }, + } = useOpenSearchDashboards(); + const isDashboardAdmin = application.capabilities.dashboards?.isDashboardAdmin; + const logos = chrome.logos; + const createWorkspaceUrl = application.getUrlForApp(WORKSPACE_CREATE_APP_ID, { absolute: true }); + const settingsAndSetupUrl = application.getUrlForApp('settings_landing', { absolute: true }); + const isDarkTheme = uiSettings.get('theme:darkMode'); + const backGroundUrl = isDarkTheme ? BackgroundDarkSVG : BackgroundLightSVG; + + const createButton = ( + + {i18n.translate('workspace.initial.card.createWorkspace.button', { + defaultMessage: 'Create Workspace', + })} + + ); + + const noAdminText = ( + + {i18n.translate('workspace.initial.card.createWorkspace.text', { + defaultMessage: + 'Contact your administrator to create a workspace or to be added to an existing one.', + })} + + ); + + const cards = ( + + + } + title={i18n.translate('workspace.initial.card.observability.title', { + defaultMessage: 'Observability', + })} + description={i18n.translate('workspace.initial.card.observability.description', { + defaultMessage: 'Gain visibility into your applications and infrastructure', + })} + /> + + + } + title={i18n.translate('workspace.initial.card.securityAnalytics.title', { + defaultMessage: 'Security Analytics', + })} + description={i18n.translate('workspace.initial.card.securityAnalytics.description', { + defaultMessage: 'Enhance your security posture with advanced analytics', + })} + /> + + + } + title={i18n.translate('workspace.initial.card.search.title', { + defaultMessage: 'Search', + })} + description={i18n.translate('workspace.initial.card.search.description', { + defaultMessage: 'Discover and query your data with ease', + })} + /> + + + } + title={i18n.translate('workspace.initial.card.essentials.title', { + defaultMessage: 'Essentials', + })} + description={i18n.translate('workspace.initial.card.essentials.description', { + defaultMessage: 'Just the basics for exploring and analyzing data', + })} + /> + + + ); + + const content = ( + + + +

+ {i18n.translate('workspace.initial.title', { + defaultMessage: 'Create a workspace to get started', + })} +

+
+
+ + + {i18n.translate('workspace.initial.description', { + defaultMessage: + 'Welcome to OpenSearch! This interface supports you to easily explore, enrich and visualize your data with developer-friendly tools and powerful integrations for machine learning, data process, and more. To begin, create a workspace for your use case.', + })} + + + + + + {i18n.translate('workspace.initial.button.openSearch', { + defaultMessage: 'Learn more from documentation', + })} + + + + + +

+ {i18n.translate('workspace.initial.createWorkspace.title', { + defaultMessage: 'Create a workspace', + })} +

+
+ + {i18n.translate('workspace.initial.createWorkspace.describe', { + defaultMessage: 'Organize projects by use case in a collaborative workspace', + })} + +
+ {cards} + + + {isDashboardAdmin ? createButton : noAdminText} + + + + +   Explore live demo environment at{' '} + + playground.opensearch.org + + + + + + +
+ ); + + return ( + + + + + + + + + + {content} + + + + {i18n.translate('workspace.initial.button.settingsAndSetup', { + defaultMessage: 'Settings and setup', + })} + + + + + + + ); +}; diff --git a/src/plugins/workspace/public/components/workspace_initial_app.tsx b/src/plugins/workspace/public/components/workspace_initial_app.tsx new file mode 100644 index 000000000000..553a41cb5110 --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_initial_app.tsx @@ -0,0 +1,16 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React from 'react'; +import { I18nProvider } from '@osd/i18n/react'; +import { WorkspaceInitial } from './workspace_initial/workspace_initial'; + +export const WorkspaceInitialApp = () => { + return ( + + + + ); +}; diff --git a/src/plugins/workspace/public/components/workspace_list/__snapshots__/index.test.tsx.snap b/src/plugins/workspace/public/components/workspace_list/__snapshots__/index.test.tsx.snap index f90101772950..3c77bc87adc0 100644 --- a/src/plugins/workspace/public/components/workspace_list/__snapshots__/index.test.tsx.snap +++ b/src/plugins/workspace/public/components/workspace_list/__snapshots__/index.test.tsx.snap @@ -3,294 +3,1029 @@ exports[`WorkspaceList should render title and table normally 1`] = `
-
-
+ + + Create workspace + + + +
+
-

- Workspaces -

-
-

- Workspace allow you to save and organize library items, such as index patterns, visualizations, dashboards, saved searches, and share them with other OpenSearch Dashboards users. You can control which features are visible in each workspace, and which users and groups have read and write access to the library items in the workspace. -

+ +
+ + +
-
-
-
-
- -
-
+
+
+
+
+
+
- -
-
-
-
-
+ class="euiCheckbox" + > + +
+ +
+
+
-
- -
+ +
- - - - - - - - + + + +
+
- - -
-
+ + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
-
+
+ - + + + + - + + + Description + + + + + + Owners + + + + + + Last updated + + + + + + Data sources + + + + + + Actions + + +
+
+
+ +
+
+
+
+
+ Name +
+
+ + - -
+ + + + + +
+ Use case +
+
- +
+
+
+ Description +
+
+ +
- Description - + should be able to see the description tooltip when hovering over the description +
- -
+ + +
+ Owners +
+
+ admin +   - Features + + - -
+ + +
+ Last updated +
+
+ Aug 5, 1999 @ 22:00:00.000 +
+
+
+ Data sources +
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+ Name +
+
+ + + + +
+
+
+ Use case +
+
+ + Observability + +
+
+
+ Description +
+
+ +
+ should be able to see the description tooltip when hovering over the description +
+
+
+
+
+ Owners +
+
+
+
+ Last updated +
+
+ Aug 5, 1999 @ 20:00:00.000 +
+
+
+ Data sources +
+
+
+
+
+
+
+ + + +
+
+
+
+
+
+
+ +
+
+
+
+
+ Name +
+
+ + + + +
+
+
+ Use case +
+
+ Search + +
+
+
+ Description +
+
+ +
+ +
+
+
+ Owners +
+
+
+
+ Last updated +
+
+ Aug 5, 1999 @ 21:00:00.000 +
+
+
+ Data sources +
+
+
+
+
+
+
+ + + +
+
+
+
+
+ +
+
+
+
+
+
+
-
- No items found + + 1 + -
-
+ + + + + +
diff --git a/src/plugins/workspace/public/components/workspace_list/index.test.tsx b/src/plugins/workspace/public/components/workspace_list/index.test.tsx index d75ddf0d513f..bec37c5bb160 100644 --- a/src/plugins/workspace/public/components/workspace_list/index.test.tsx +++ b/src/plugins/workspace/public/components/workspace_list/index.test.tsx @@ -4,18 +4,30 @@ */ import React from 'react'; -import { WorkspaceList } from './index'; -import { coreMock } from '../../../../../core/public/mocks'; -import { render, fireEvent, screen } from '@testing-library/react'; -import { I18nProvider } from '@osd/i18n/react'; -import { switchWorkspace, navigateToWorkspaceUpdatePage } from '../utils/workspace'; - +import moment from 'moment'; import { of } from 'rxjs'; - +import { render, fireEvent, screen, waitFor } from '@testing-library/react'; +import { I18nProvider } from '@osd/i18n/react'; +import { coreMock } from '../../../../../core/public/mocks'; +import { navigateToWorkspaceDetail } from '../utils/workspace'; +import { createMockedRegisteredUseCases$ } from '../../mocks'; import { OpenSearchDashboardsContextProvider } from '../../../../../plugins/opensearch_dashboards_react/public'; +import { WorkspaceList } from './index'; jest.mock('../utils/workspace'); +const mockNavigatorWrite = jest.fn(); + +jest.mock('@elastic/eui', () => { + const original = jest.requireActual('@elastic/eui'); + return { + ...original, + copyToClipboard: jest.fn().mockImplementation((id) => { + mockNavigatorWrite(id); + }), + }; +}); + jest.mock('../delete_workspace_modal', () => ({ DeleteWorkspaceModal: ({ onClose }: { onClose: () => void }) => (
@@ -24,81 +36,201 @@ jest.mock('../delete_workspace_modal', () => ({ ), })); +jest.mock('../../utils', () => { + const original = jest.requireActual('../../utils'); + return { + ...original, + getDataSourcesList: jest.fn().mockResolvedValue(() => [ + { + id: 'ds_id1', + title: 'ds_title1', + workspaces: 'id1', + }, + { + id: 'ds_id2', + title: 'ds_title2', + workspaces: 'id1', + }, + { + id: 'ds_id3', + title: 'ds_title3', + workspaces: 'id1', + }, + ]), + }; +}); + function getWrapWorkspaceListInContext( workspaceList = [ - { id: 'id1', name: 'name1' }, - { id: 'id2', name: 'name2' }, - ] + { + id: 'id1', + name: 'name1', + features: ['use-case-all'], + description: + 'should be able to see the description tooltip when hovering over the description', + lastUpdatedTime: '1999-08-06T02:00:00.00Z', + permissions: { + write: { + users: ['admin', 'nonadmin'], + }, + }, + }, + { + id: 'id2', + name: 'name2', + features: ['use-case-observability'], + description: + 'should be able to see the description tooltip when hovering over the description', + lastUpdatedTime: '1999-08-06T00:00:00.00Z', + }, + { + id: 'id3', + name: 'name3', + features: ['use-case-search'], + description: '', + lastUpdatedTime: '1999-08-06T01:00:00.00Z', + }, + ], + isDashboardAdmin = true ) { const coreStartMock = coreMock.createStart(); + coreStartMock.application.capabilities = { + ...coreStartMock.application.capabilities, + dashboards: { + isDashboardAdmin, + }, + }; + + const mockHeaderControl = ({ controls }) => { + return controls?.[0].description ?? controls?.[0].renderComponent ?? null; + }; const services = { ...coreStartMock, workspaces: { workspaceList$: of(workspaceList), }, + uiSettings: { + get: jest.fn().mockImplementation((key) => { + if (key === 'dateFormat') { + return 'MMM D, YYYY @ HH:mm:ss.SSS'; + } + return null; + }), + }, + navigationUI: { + HeaderControl: mockHeaderControl, + }, }; return ( - + ); } describe('WorkspaceList', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('should render title and table normally', () => { - const { getByText, getByRole, container } = render(); - expect(getByText('Workspaces')).toBeInTheDocument(); + const { getByText, getByRole, container } = render(getWrapWorkspaceListInContext()); + expect( + getByText('Organize collaborative projects with use-case-specific workspaces.') + ).toBeInTheDocument(); expect(getByRole('table')).toBeInTheDocument(); expect(container).toMatchSnapshot(); }); it('should render data in table based on workspace list data', async () => { const { getByText } = render(getWrapWorkspaceListInContext()); + + // should display workspace names expect(getByText('name1')).toBeInTheDocument(); expect(getByText('name2')).toBeInTheDocument(); + + // should display use case + expect(getByText('Analytics (All)')).toBeInTheDocument(); + expect(getByText('Observability')).toBeInTheDocument(); }); - it('should be able to apply debounce search after input', async () => { - const list = [ - { id: 'id1', name: 'name1' }, - { id: 'id2', name: 'name2' }, - { id: 'id3', name: 'name3' }, - { id: 'id4', name: 'name4' }, - { id: 'id5', name: 'name5' }, - { id: 'id6', name: 'name6' }, - ]; - const { getByText, getByRole, queryByText } = render(getWrapWorkspaceListInContext(list)); - expect(getByText('name1')).toBeInTheDocument(); - expect(queryByText('name6')).not.toBeInTheDocument(); + + it('should be able to search and re-render the list', async () => { + const { getByText, getByRole, queryByText } = render(getWrapWorkspaceListInContext()); const input = getByRole('searchbox'); fireEvent.change(input, { - target: { value: 'nam' }, + target: { value: 'name2' }, }); + expect(getByText('name2')).toBeInTheDocument(); + expect(queryByText('name1')).not.toBeInTheDocument(); + expect(queryByText('name3')).not.toBeInTheDocument(); + }); + + it('should be able to apply debounce search after input', async () => { + const { getByText, getByRole, queryByText } = render(getWrapWorkspaceListInContext()); + const input = getByRole('searchbox'); fireEvent.change(input, { - target: { value: 'name6' }, + target: { value: 'name2' }, }); - expect(queryByText('name6')).not.toBeInTheDocument(); + expect(getByText('name2')).toBeInTheDocument(); + expect(queryByText('name1')).not.toBeInTheDocument(); + expect(queryByText('name3')).not.toBeInTheDocument(); }); it('should be able to switch workspace after clicking name', async () => { const { getByText } = render(getWrapWorkspaceListInContext()); const nameLink = getByText('name1'); fireEvent.click(nameLink); - expect(switchWorkspace).toBeCalled(); + expect(navigateToWorkspaceDetail).toBeCalled(); + }); + + it('should be able to perform the time format transformation', async () => { + const { getByText } = render(getWrapWorkspaceListInContext()); + expect( + getByText(moment('1999-08-06T00:00:00.00Z').format('MMM D, YYYY @ HH:mm:ss.SSS')) + ).toBeInTheDocument(); + expect( + getByText(moment('1999-08-06T01:00:00.00Z').format('MMM D, YYYY @ HH:mm:ss.SSS')) + ).toBeInTheDocument(); + expect( + getByText(moment('1999-08-06T02:00:00.00Z').format('MMM D, YYYY @ HH:mm:ss.SSS')) + ).toBeInTheDocument(); + }); + + it('should be able to see the 3 operations: copy, update, delete after click in the meatballs button', async () => { + const { getAllByTestId, getByText } = render(getWrapWorkspaceListInContext()); + const operationIcons = getAllByTestId('euiCollapsedItemActionsButton')[0]; + fireEvent.click(operationIcons); + expect(getByText('Copy ID')).toBeInTheDocument(); + expect(getByText('Edit')).toBeInTheDocument(); + expect(getByText('Delete')).toBeInTheDocument(); + }); + + it('should be able to copy workspace ID after clicking copy button', async () => { + const { getByText, getAllByTestId } = render(getWrapWorkspaceListInContext()); + const operationIcons = getAllByTestId('euiCollapsedItemActionsButton')[0]; + fireEvent.click(operationIcons); + const copyIcon = getByText('Copy ID'); + fireEvent.click(copyIcon); + expect(mockNavigatorWrite).toHaveBeenCalledWith('id1'); }); it('should be able to update workspace after clicking name', async () => { - const { getAllByTestId } = render(getWrapWorkspaceListInContext()); - const editIcon = getAllByTestId('workspace-list-edit-icon')[0]; + const { getByText, getAllByTestId } = render(getWrapWorkspaceListInContext()); + const operationIcons = getAllByTestId('euiCollapsedItemActionsButton')[0]; + fireEvent.click(operationIcons); + const editIcon = getByText('Edit'); fireEvent.click(editIcon); - expect(navigateToWorkspaceUpdatePage).toBeCalled(); + expect(navigateToWorkspaceDetail).toBeCalled(); }); it('should be able to call delete modal after clicking delete button', async () => { - const { getAllByTestId } = render(getWrapWorkspaceListInContext()); - const deleteIcon = getAllByTestId('workspace-list-delete-icon')[0]; + const { getByText, getAllByTestId } = render(getWrapWorkspaceListInContext()); + const operationIcons = getAllByTestId('euiCollapsedItemActionsButton')[0]; + fireEvent.click(operationIcons); + const deleteIcon = getByText('Delete'); fireEvent.click(deleteIcon); expect(screen.queryByLabelText('mock delete workspace modal')).toBeInTheDocument(); const modalCancelButton = screen.getByLabelText('mock delete workspace modal button'); @@ -108,12 +240,48 @@ describe('WorkspaceList', () => { it('should be able to pagination when clicking pagination button', async () => { const list = [ - { id: 'id1', name: 'name1' }, - { id: 'id2', name: 'name2' }, - { id: 'id3', name: 'name3' }, - { id: 'id4', name: 'name4' }, - { id: 'id5', name: 'name5' }, - { id: 'id6', name: 'name6' }, + { + id: 'id1', + name: 'name1', + features: ['use-case-all'], + description: '', + lastUpdatedTime: '2024-08-06T00:00:00.00Z', + }, + { + id: 'id2', + name: 'name2', + features: ['use-case-observability'], + description: '', + lastUpdatedTime: '2024-08-06T00:00:00.00Z', + }, + { + id: 'id3', + name: 'name3', + features: ['use-case-search'], + description: '', + lastUpdatedTime: '2024-08-06T00:00:00.00Z', + }, + { + id: 'id4', + name: 'name4', + features: ['use-case-all'], + description: '', + lastUpdatedTime: '2024-08-05T00:00:00.00Z', + }, + { + id: 'id5', + name: 'name5', + features: ['use-case-observability'], + description: '', + lastUpdatedTime: '2024-08-06T00:00:00.00Z', + }, + { + id: 'id6', + name: 'name6', + features: ['use-case-search'], + description: '', + lastUpdatedTime: '2024-08-06T00:00:00.00Z', + }, ]; const { getByTestId, getByText, queryByText } = render(getWrapWorkspaceListInContext(list)); expect(getByText('name1')).toBeInTheDocument(); @@ -123,4 +291,36 @@ describe('WorkspaceList', () => { expect(queryByText('name1')).not.toBeInTheDocument(); expect(getByText('name6')).toBeInTheDocument(); }); + + it('should display create workspace button for dashboard admin', async () => { + const { getAllByText } = render(getWrapWorkspaceListInContext([], true)); + expect(getAllByText('Create workspace')[0]).toBeInTheDocument(); + }); + + it('should hide create workspace button for non dashboard admin', async () => { + const { queryByText } = render(getWrapWorkspaceListInContext([], false)); + expect(queryByText('Create workspace')).toBeNull(); + }); + + it('should render data source badge when more than two data sources', async () => { + const { getByTestId } = render(getWrapWorkspaceListInContext()); + expect(navigateToWorkspaceDetail).not.toHaveBeenCalled(); + await waitFor(() => { + const badge = getByTestId('workspaceList-more-dataSources-badge'); + expect(badge).toBeInTheDocument(); + fireEvent.click(badge); + }); + expect(navigateToWorkspaceDetail).toHaveBeenCalledTimes(1); + }); + + it('should render owners badge when more than one owners', async () => { + const { getByTestId } = render(getWrapWorkspaceListInContext()); + expect(navigateToWorkspaceDetail).not.toHaveBeenCalled(); + await waitFor(() => { + const badge = getByTestId('workspaceList-more-collaborators-badge'); + expect(badge).toBeInTheDocument(); + fireEvent.click(badge); + }); + expect(navigateToWorkspaceDetail).toHaveBeenCalledTimes(1); + }); }); diff --git a/src/plugins/workspace/public/components/workspace_list/index.tsx b/src/plugins/workspace/public/components/workspace_list/index.tsx index b22a0fdb99fd..88e80609a410 100644 --- a/src/plugins/workspace/public/components/workspace_list/index.tsx +++ b/src/plugins/workspace/public/components/workspace_list/index.tsx @@ -3,216 +3,481 @@ * SPDX-License-Identifier: Apache-2.0 */ -import React, { useState, useMemo, useCallback } from 'react'; +import React, { useState, useMemo, useCallback, useEffect } from 'react'; +import moment from 'moment'; import { EuiPage, - EuiPageBody, - EuiPageHeader, EuiPageContent, EuiLink, - EuiButton, + EuiSmallButton, EuiInMemoryTable, + EuiToolTip, + EuiText, EuiSearchBarProps, + copyToClipboard, + EuiTableSelectionType, + EuiButtonEmpty, + EuiButton, + EuiEmptyPrompt, + EuiBadge, } from '@elastic/eui'; import useObservable from 'react-use/lib/useObservable'; -import { of } from 'rxjs'; +import { BehaviorSubject, of } from 'rxjs'; import { i18n } from '@osd/i18n'; -import { debounce } from '../../../../../core/public'; -import { WorkspaceAttribute } from '../../../../../core/public'; +import { + DEFAULT_NAV_GROUPS, + WorkspaceAttribute, + WorkspaceAttributeWithPermission, +} from '../../../../../core/public'; import { useOpenSearchDashboards } from '../../../../../plugins/opensearch_dashboards_react/public'; -import { switchWorkspace, navigateToWorkspaceUpdatePage } from '../utils/workspace'; +import { navigateToWorkspaceDetail } from '../utils/workspace'; +import { DetailTab } from '../workspace_form/constants'; import { WORKSPACE_CREATE_APP_ID } from '../../../common/constants'; -import { cleanWorkspaceId } from '../../../../../core/public'; import { DeleteWorkspaceModal } from '../delete_workspace_modal'; +import { getFirstUseCaseOfFeatureConfigs, getDataSourcesList } from '../../utils'; +import { WorkspaceUseCase } from '../../types'; +import { NavigationPublicPluginStart } from '../../../../../plugins/navigation/public'; +import { WorkspacePermissionMode } from '../../../common/constants'; +import { DataSourceAttributesWithWorkspaces } from '../../types'; -const WORKSPACE_LIST_PAGE_DESCRIPTIOIN = i18n.translate('workspace.list.description', { - defaultMessage: - 'Workspace allow you to save and organize library items, such as index patterns, visualizations, dashboards, saved searches, and share them with other OpenSearch Dashboards users. You can control which features are visible in each workspace, and which users and groups have read and write access to the library items in the workspace.', -}); +export interface WorkspaceListProps { + registeredUseCases$: BehaviorSubject; +} -export const WorkspaceList = () => { - const { - services: { workspaces, application, http }, - } = useOpenSearchDashboards(); +interface WorkspaceAttributeWithUseCaseIDAndDataSources extends WorkspaceAttribute { + useCase?: string; + dataSources?: string[]; +} +export const WorkspaceList = ({ registeredUseCases$ }: WorkspaceListProps) => { + const { + services: { + workspaces, + application, + http, + navigationUI: { HeaderControl }, + uiSettings, + savedObjects, + }, + } = useOpenSearchDashboards<{ + navigationUI: NavigationPublicPluginStart['ui']; + }>(); + const registeredUseCases = useObservable(registeredUseCases$); + const isDashboardAdmin = application?.capabilities?.dashboards?.isDashboardAdmin; const initialSortField = 'name'; const initialSortDirection = 'asc'; const workspaceList = useObservable(workspaces?.workspaceList$ ?? of([]), []); - const [queryInput, setQueryInput] = useState(''); + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 5, pageSizeOptions: [5, 10, 20], }); - const [deletedWorkspace, setDeletedWorkspace] = useState(null); + const [deletedWorkspaces, setDeletedWorkspaces] = useState([]); + const [selection, setSelection] = useState([]); + const [allDataSources, setAllDataSources] = useState([]); - const handleSwitchWorkspace = useCallback( - (id: string) => { - if (application && http) { - switchWorkspace({ application, http }, id); + const dateFormat = uiSettings?.get('dateFormat'); + + const extractUseCaseFromFeatures = useCallback( + (features: string[]) => { + if (!features || features.length === 0) { + return ''; + } + const useCaseId = getFirstUseCaseOfFeatureConfigs(features); + const usecase = + useCaseId === DEFAULT_NAV_GROUPS.all.id + ? DEFAULT_NAV_GROUPS.all + : registeredUseCases?.find(({ id }) => id === useCaseId); + if (usecase) { + return usecase.title; } }, - [application, http] + [registeredUseCases] ); - const handleUpdateWorkspace = useCallback( - (id: string) => { + useEffect(() => { + if (savedObjects) { + getDataSourcesList(savedObjects.client, ['*']).then((data) => { + setAllDataSources(data); + }); + } + }, [savedObjects]); + + const newWorkspaceList: WorkspaceAttributeWithUseCaseIDAndDataSources[] = useMemo(() => { + return workspaceList.map( + (workspace): WorkspaceAttributeWithUseCaseIDAndDataSources => { + const associatedDataSourcesTitles = allDataSources + .filter((ds) => ds.workspaces && ds.workspaces.includes(workspace.id)) + .map((ds) => ds.title as string); + return { + ...workspace, + useCase: extractUseCaseFromFeatures(workspace.features ?? []), + dataSources: associatedDataSourcesTitles, + }; + } + ); + }, [workspaceList, extractUseCaseFromFeatures, allDataSources]); + const workspaceCreateUrl = useMemo(() => { + if (!application) { + return ''; + } + + const appUrl = application.getUrlForApp(WORKSPACE_CREATE_APP_ID, { + absolute: false, + }); + if (!appUrl) return ''; + + return appUrl; + }, [application]); + + const emptyStateMessage = useMemo(() => { + return ( + + {i18n.translate('workspace.workspaceList.emptyState.title', { + defaultMessage: 'No workspace available', + })} + + } + titleSize="s" + body={i18n.translate('workspace.workspaceList.emptyState.body', { + defaultMessage: 'There are no workspace to display. Create workspace to get started.', + })} + actions={ + isDashboardAdmin && ( + + {i18n.translate('workspace.workspaceList.buttons.createWorkspace', { + defaultMessage: 'Create workspace', + })} + + ) + } + /> + ); + }, [isDashboardAdmin, workspaceCreateUrl]); + + const renderCreateWorkspaceButton = () => { + const button = ( + + {i18n.translate('workspace.list.buttons.createWorkspace', { + defaultMessage: 'Create workspace', + })} + + ); + return ( + + ); + }; + + const handleCopyId = (id: string) => { + copyToClipboard(id); + }; + + const handleSwitchWorkspace = useCallback( + (id: string, tab?: DetailTab) => { if (application && http) { - navigateToWorkspaceUpdatePage({ application, http }, id); + navigateToWorkspaceDetail({ application, http }, id, tab); } }, [application, http] ); - const searchResult = useMemo(() => { - if (queryInput) { - const normalizedQuery = queryInput.toLowerCase(); - const result = workspaceList.filter((item) => { - return ( - item.id.toLowerCase().indexOf(normalizedQuery) > -1 || - item.name.toLowerCase().indexOf(normalizedQuery) > -1 - ); - }); - return result; + const renderDataWithMoreBadge = ( + data: string[], + maxDisplayedAmount: number, + workspaceId: string, + tab: DetailTab + ) => { + const amount = data.length; + const mostDisplayedTitles = data.slice(0, maxDisplayedAmount).join(','); + return amount <= maxDisplayedAmount ? ( + mostDisplayedTitles + ) : ( + <> + {mostDisplayedTitles}  + handleSwitchWorkspace(workspaceId, tab)} + iconOnClick={() => handleSwitchWorkspace(workspaceId, tab)} + iconOnClickAriaLabel="Open workspace detail" + onClickAriaLabel="Open workspace detail" + data-test-subj={`workspaceList-more-${tab}-badge`} + > + + {amount - maxDisplayedAmount} more + + + ); + }; + + const renderToolsLeft = () => { + if (selection.length === 0) { + return; } - return workspaceList; - }, [workspaceList, queryInput]); + + const onClick = () => { + const deleteWorkspacesByIds = (workSpaces: WorkspaceAttribute[], ids: string[]) => { + const needToBeDeletedWorkspaceList: WorkspaceAttribute[] = []; + ids.forEach((id) => { + const index = workSpaces.findIndex((workSpace) => workSpace.id === id); + if (index >= 0) { + needToBeDeletedWorkspaceList.push(workSpaces[index]); + } + }); + return needToBeDeletedWorkspaceList; + }; + + setDeletedWorkspaces( + deleteWorkspacesByIds( + newWorkspaceList, + selection.map((item) => item.id) + ) + ); + + setSelection([]); + }; + + return ( + <> + + Delete {selection.length} Workspace + + {deletedWorkspaces && deletedWorkspaces.length > 0 && ( + setDeletedWorkspaces([])} + /> + )} + + ); + }; + + const selectionValue: EuiTableSelectionType = { + onSelectionChange: (deletedSelection) => setSelection(deletedSelection), + }; + + const search: EuiSearchBarProps = { + box: { + incremental: true, + }, + filters: [ + { + type: 'field_value_selection', + field: 'useCase', + name: 'Use Case', + multiSelect: false, + options: Array.from( + new Set(newWorkspaceList.map(({ useCase }) => useCase).filter(Boolean)) + ).map((useCase) => ({ + value: useCase!, + name: useCase!, + })), + }, + ], + toolsLeft: renderToolsLeft(), + }; const columns = [ { field: 'name', name: 'Name', + width: '15%', sortable: true, - render: (name: string, item: WorkspaceAttribute) => ( + render: (name: string, item: WorkspaceAttributeWithPermission) => ( - handleSwitchWorkspace(item.id)}>{name} + handleSwitchWorkspace(item.id)}> + {name} + ), }, + { - field: 'id', - name: 'ID', - sortable: true, + field: 'useCase', + name: 'Use case', + width: '15%', }, + { field: 'description', name: 'Description', - truncateText: true, + width: '15%', + render: (description: string) => ( + + {/* Here I need to set width mannuly as the tooltip will ineffect the property : truncateText ', */} + + {description} + + + ), }, { - field: 'features', - name: 'Features', - isExpander: true, - hasActions: true, + field: 'permissions', + name: 'Owners', + width: '15%', + render: ( + permissions: WorkspaceAttributeWithPermission['permissions'], + item: WorkspaceAttributeWithPermission + ) => { + const owners = permissions?.[WorkspacePermissionMode.Write]?.users ?? []; + return renderDataWithMoreBadge(owners, 1, item.id, DetailTab.Collaborators); + }, + }, + { + field: 'lastUpdatedTime', + name: 'Last updated', + width: '15%', + truncateText: false, + render: (lastUpdatedTime: string) => { + return moment(lastUpdatedTime).format(dateFormat); + }, + }, + { + field: 'dataSources', + width: '15%', + name: 'Data sources', + render: (dataSources: string[], item: WorkspaceAttributeWithPermission) => { + return renderDataWithMoreBadge(dataSources, 2, item.id, DetailTab.DataSources); + }, }, { name: 'Actions', field: '', actions: [ + { + name: 'Copy ID', + type: 'button', + description: 'Copy id', + 'data-test-subj': 'workspace-list-copy-id-icon', + render: ({ id }: WorkspaceAttribute) => { + return ( + handleCopyId(id)} + size="xs" + iconType="copy" + color="text" + > + Copy ID + + ); + }, + }, { name: 'Edit', - icon: 'pencil', type: 'icon', + icon: 'edit', + color: 'danger', description: 'Edit workspace', - onClick: ({ id }: WorkspaceAttribute) => handleUpdateWorkspace(id), 'data-test-subj': 'workspace-list-edit-icon', + onClick: ({ id }: WorkspaceAttribute) => handleSwitchWorkspace(id), + render: ({ id }: WorkspaceAttribute) => { + return ( + handleSwitchWorkspace(id)} + iconType="pencil" + size="xs" + color="text" + > + Edit + + ); + }, }, { name: 'Delete', - icon: 'trash', - type: 'icon', + type: 'button', description: 'Delete workspace', - onClick: (item: WorkspaceAttribute) => setDeletedWorkspace(item), 'data-test-subj': 'workspace-list-delete-icon', + render: (item: WorkspaceAttribute) => { + return ( + { + setDeletedWorkspaces([item]); + }} + size="s" + iconType="trash" + color="danger" + style={{ padding: 0 }} + > + Delete + + ); + }, }, ], }, ]; - const workspaceCreateUrl = useMemo(() => { - if (!application || !http) { - return ''; - } - - const appUrl = application.getUrlForApp(WORKSPACE_CREATE_APP_ID, { - absolute: false, - }); - if (!appUrl) return ''; - - return cleanWorkspaceId(appUrl); - }, [application, http]); - - const debouncedSetQueryInput = useMemo(() => { - return debounce(setQueryInput, 300); - }, [setQueryInput]); - - const handleSearchInput: EuiSearchBarProps['onChange'] = useCallback( - ({ query }) => { - debouncedSetQueryInput(query?.text ?? ''); - }, - [debouncedSetQueryInput] - ); - - const search: EuiSearchBarProps = { - onChange: handleSearchInput, - box: { - incremental: true, - }, - toolsRight: [ - - Create workspace - , - ], - }; - return ( - - - + + {isDashboardAdmin && renderCreateWorkspaceButton()} + + + setPagination((prev) => { + return { ...prev, pageIndex: index, pageSize: size }; + }) + } + pagination={pagination} + sorting={{ + sort: { + field: initialSortField, + direction: initialSortDirection, + }, + }} + isSelectable={true} + search={search} + selection={selectionValue} /> - - - setPagination((prev) => { - return { ...prev, pageIndex: index, pageSize: size }; - }) - } - pagination={pagination} - sorting={{ - sort: { - field: initialSortField, - direction: initialSortDirection, - }, - }} - isSelectable={true} - search={search} - /> - - - {deletedWorkspace && ( + + + {deletedWorkspaces.length > 0 && ( setDeletedWorkspace(null)} + selectedWorkspaces={deletedWorkspaces} + onClose={() => setDeletedWorkspaces([])} /> )} diff --git a/src/plugins/workspace/public/components/workspace_list_app.tsx b/src/plugins/workspace/public/components/workspace_list_app.tsx index 8970cfc46fc7..7d532b3ef38e 100644 --- a/src/plugins/workspace/public/components/workspace_list_app.tsx +++ b/src/plugins/workspace/public/components/workspace_list_app.tsx @@ -7,9 +7,11 @@ import React, { useEffect } from 'react'; import { I18nProvider } from '@osd/i18n/react'; import { i18n } from '@osd/i18n'; import { useOpenSearchDashboards } from '../../../opensearch_dashboards_react/public'; -import { WorkspaceList } from './workspace_list'; +import { WorkspaceList, WorkspaceListProps } from './workspace_list'; -export const WorkspaceListApp = () => { +export type WorkspaceListAppProps = WorkspaceListProps; + +export const WorkspaceListApp = (props: WorkspaceListAppProps) => { const { services: { chrome }, } = useOpenSearchDashboards(); @@ -29,7 +31,7 @@ export const WorkspaceListApp = () => { return ( - + ); }; diff --git a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx index c63b232bb232..606052f5c92c 100644 --- a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx +++ b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.test.tsx @@ -4,23 +4,52 @@ */ import React from 'react'; -import { fireEvent, render, screen, waitFor } from '@testing-library/react'; +import { fireEvent, render, screen } from '@testing-library/react'; import { WorkspaceMenu } from './workspace_menu'; import { coreMock } from '../../../../../core/public/mocks'; -import { CoreStart } from '../../../../../core/public'; +import { CoreStart, DEFAULT_NAV_GROUPS } from '../../../../../core/public'; +import { BehaviorSubject } from 'rxjs'; +import { IntlProvider } from 'react-intl'; +import { recentWorkspaceManager } from '../../recent_workspace_manager'; +import * as workspaceUtils from '../utils/workspace'; describe('', () => { let coreStartMock: CoreStart; + const navigateToApp = jest.fn(); + const registeredUseCases$ = new BehaviorSubject([ + { ...DEFAULT_NAV_GROUPS.observability, features: [{ id: 'discover', title: 'Discover' }] }, + ]); beforeEach(() => { coreStartMock = coreMock.createStart(); + coreStartMock.application.capabilities = { + navLinks: {}, + management: {}, + catalogue: {}, + savedObjectsManagement: {}, + workspaces: { permissionEnabled: true }, + dashboards: { isDashboardAdmin: true }, + }; + coreStartMock.application = { + ...coreStartMock.application, + navigateToApp, + }; + coreStartMock.workspaces.initialized$.next(true); jest.spyOn(coreStartMock.application, 'getUrlForApp').mockImplementation((appId: string) => { return `https://test.com/app/${appId}`; }); }); + const WorkspaceMenuCreatorComponent = () => { + return ( + + + + ); + }; + afterEach(() => { jest.clearAllMocks(); jest.restoreAllMocks(); @@ -28,38 +57,57 @@ describe('', () => { it('should display a list of workspaces in the dropdown', () => { coreStartMock.workspaces.workspaceList$.next([ - { id: 'workspace-1', name: 'workspace 1' }, + { id: 'workspace-1', name: 'workspace 1', features: [] }, { id: 'workspace-2', name: 'workspace 2' }, ]); - render(); - fireEvent.click(screen.getByText(/select a workspace/i)); + render(); + const selectButton = screen.getByTestId('workspace-select-button'); + fireEvent.click(selectButton); - expect(screen.getByText(/workspace 1/i)).toBeInTheDocument(); - expect(screen.getByText(/workspace 2/i)).toBeInTheDocument(); + expect(screen.getByText(/all workspaces/i)).toBeInTheDocument(); + expect(screen.getByTestId('workspace-menu-item-all-workspace-1')).toBeInTheDocument(); + expect(screen.getByTestId('workspace-menu-item-all-workspace-2')).toBeInTheDocument(); }); - it('should display current workspace name', () => { - coreStartMock.workspaces.currentWorkspace$.next({ id: 'workspace-1', name: 'workspace 1' }); - render(); - expect(screen.getByText(/workspace 1/i)).toBeInTheDocument(); - }); + it('should display a list of recent workspaces in the dropdown', () => { + jest.spyOn(recentWorkspaceManager, 'getRecentWorkspaces').mockReturnValue([ + { id: 'workspace-1', timestamp: 1234567890 }, + { id: 'workspace-2', timestamp: 1234567899 }, + ]); - it('should close the workspace dropdown list', async () => { - render(); - fireEvent.click(screen.getByText(/select a workspace/i)); + coreStartMock.workspaces.workspaceList$.next([ + { id: 'workspace-1', name: 'workspace 1', features: [] }, + { id: 'workspace-2', name: 'workspace 2', features: [] }, + ]); + + render(); + + const selectButton = screen.getByTestId('workspace-select-button'); + fireEvent.click(selectButton); + + expect(screen.getByText(/recent workspaces/i)).toBeInTheDocument(); + expect(screen.getByTestId('workspace-menu-item-recent-workspace-1')).toBeInTheDocument(); + expect(screen.getByTestId('workspace-menu-item-recent-workspace-2')).toBeInTheDocument(); + }); - expect(screen.getByLabelText(/close workspace dropdown/i)).toBeInTheDocument(); - fireEvent.click(screen.getByLabelText(/close workspace dropdown/i)); - await waitFor(() => { - expect(screen.queryByLabelText(/close workspace dropdown/i)).not.toBeInTheDocument(); + it('should display current workspace name and use case name', () => { + coreStartMock.workspaces.currentWorkspace$.next({ + id: 'workspace-1', + name: 'workspace 1', + features: ['use-case-observability'], }); + render(); + + fireEvent.click(screen.getByTestId('current-workspace-button')); + expect(screen.getByTestId('workspace-menu-current-workspace-name')).toBeInTheDocument(); + expect(screen.getByTestId('workspace-menu-current-use-case')).toBeInTheDocument(); + expect(screen.getByText('Observability')).toBeInTheDocument(); }); - it('should navigate to the workspace', () => { + it('should navigate to the first feature of workspace use case', () => { coreStartMock.workspaces.workspaceList$.next([ - { id: 'workspace-1', name: 'workspace 1' }, - { id: 'workspace-2', name: 'workspace 2' }, + { id: 'workspace-1', name: 'workspace 1', features: ['use-case-observability'] }, ]); const originalLocation = window.location; @@ -69,12 +117,12 @@ describe('', () => { }, }); - render(); - fireEvent.click(screen.getByText(/select a workspace/i)); + render(); + fireEvent.click(screen.getByTestId('workspace-select-button')); fireEvent.click(screen.getByText(/workspace 1/i)); expect(window.location.assign).toHaveBeenCalledWith( - 'https://test.com/w/workspace-1/app/workspace_overview' + 'https://test.com/w/workspace-1/app/discover' ); Object.defineProperty(window, 'location', { @@ -82,7 +130,11 @@ describe('', () => { }); }); - it('should navigate to create workspace page', () => { + it('should navigate to the workspace detail page when use case is all', () => { + coreStartMock.workspaces.workspaceList$.next([ + { id: 'workspace-1', name: 'workspace 1', features: ['use-case-all'] }, + ]); + const originalLocation = window.location; Object.defineProperty(window, 'location', { value: { @@ -90,31 +142,68 @@ describe('', () => { }, }); - render(); - fireEvent.click(screen.getByText(/select a workspace/i)); - fireEvent.click(screen.getByText(/create workspace/i)); - expect(window.location.assign).toHaveBeenCalledWith('https://test.com/app/workspace_create'); + render(); + fireEvent.click(screen.getByTestId('workspace-select-button')); + fireEvent.click(screen.getByText(/workspace 1/i)); + + expect(window.location.assign).toHaveBeenCalledWith( + 'https://test.com/w/workspace-1/app/workspace_detail' + ); Object.defineProperty(window, 'location', { value: originalLocation, }); }); - it('should navigate to workspace list page', () => { - const originalLocation = window.location; - Object.defineProperty(window, 'location', { - value: { - assign: jest.fn(), - }, + it('should navigate to workspace management page', () => { + coreStartMock.workspaces.currentWorkspace$.next({ + id: 'workspace-1', + name: 'workspace 1', + features: ['use-case-observability'], }); + const navigateToWorkspaceDetail = jest.spyOn(workspaceUtils, 'navigateToWorkspaceDetail'); + render(); - render(); - fireEvent.click(screen.getByText(/select a workspace/i)); - fireEvent.click(screen.getByText(/all workspace/i)); - expect(window.location.assign).toHaveBeenCalledWith('https://test.com/app/workspace_list'); + fireEvent.click(screen.getByTestId('current-workspace-button')); + const button = screen.getByText(/Manage workspace/i); + fireEvent.click(button); + expect(navigateToWorkspaceDetail).toBeCalled(); + }); - Object.defineProperty(window, 'location', { - value: originalLocation, - }); + it('should navigate to workspaces management page', () => { + render(); + fireEvent.click(screen.getByTestId('workspace-select-button')); + fireEvent.click(screen.getByText(/manage workspaces/i)); + expect(coreStartMock.application.navigateToApp).toHaveBeenCalledWith('workspace_list'); + }); + + it('should navigate to create workspace page', () => { + render(); + fireEvent.click(screen.getByTestId('workspace-select-button')); + fireEvent.click(screen.getByText(/create workspace/i)); + expect(coreStartMock.application.navigateToApp).toHaveBeenCalledWith('workspace_create'); + }); + + it('should navigate to workspace list page', () => { + render(); + + fireEvent.click(screen.getByTestId('workspace-select-button')); + fireEvent.click(screen.getByText(/View all/i)); + expect(coreStartMock.application.navigateToApp).toHaveBeenCalledWith('workspace_list'); + }); + + it('should hide create workspace button for non dashboard admin', () => { + coreStartMock.application.capabilities = { + ...coreStartMock.application.capabilities, + dashboards: { + ...coreStartMock.application.capabilities.dashboards, + isDashboardAdmin: false, + }, + }; + render(); + + fireEvent.click(screen.getByTestId('workspace-select-button')); + expect(screen.getByText(/View all/i)).toBeInTheDocument(); + expect(screen.queryByText(/create workspaces/i)).toBeNull(); }); }); diff --git a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx index 5b16b9766b22..d2ce42ed6097 100644 --- a/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx +++ b/src/plugins/workspace/public/components/workspace_menu/workspace_menu.tsx @@ -7,58 +7,70 @@ import { i18n } from '@osd/i18n'; import React, { useState } from 'react'; import { useObservable } from 'react-use'; import { - EuiButtonIcon, - EuiContextMenu, - EuiFlexGroup, - EuiFlexItem, - EuiIcon, - EuiListGroup, - EuiListGroupItem, - EuiPopover, EuiText, + EuiPanel, + EuiAvatar, + EuiPopover, + EuiToolTip, + EuiFlexItem, + EuiFlexGroup, + EuiSmallButtonIcon, + EuiSmallButtonEmpty, + EuiSmallButton, } from '@elastic/eui'; -import type { EuiContextMenuPanelItemDescriptor } from '@elastic/eui'; - -import { - WORKSPACE_CREATE_APP_ID, - WORKSPACE_LIST_APP_ID, - WORKSPACE_OVERVIEW_APP_ID, -} from '../../../common/constants'; -import { cleanWorkspaceId, formatUrlWithWorkspaceId } from '../../../../../core/public/utils'; +import { BehaviorSubject } from 'rxjs'; +import { WORKSPACE_CREATE_APP_ID, WORKSPACE_LIST_APP_ID } from '../../../common/constants'; import { CoreStart, WorkspaceObject } from '../../../../../core/public'; +import { getFirstUseCaseOfFeatureConfigs } from '../../utils'; +import { WorkspaceUseCase } from '../../types'; +import { navigateToWorkspaceDetail } from '../utils/workspace'; +import { validateWorkspaceColor } from '../../../common/utils'; +import { WorkspacePickerContent } from '../workspace_picker_content/workspace_picker_content'; + +const defaultHeaderName = i18n.translate('workspace.menu.defaultHeaderName', { + defaultMessage: 'Workspaces', +}); + +const createWorkspaceButton = i18n.translate('workspace.menu.button.createWorkspace', { + defaultMessage: 'Create workspace', +}); + +const viewAllButton = i18n.translate('workspace.menu.button.viewAll', { + defaultMessage: 'View all', +}); + +const manageWorkspaceButton = i18n.translate('workspace.menu.button.manageWorkspace', { + defaultMessage: 'Manage workspace', +}); + +const manageWorkspacesButton = i18n.translate('workspace.menu.button.manageWorkspaces', { + defaultMessage: 'Manage workspaces', +}); + +const getValidWorkspaceColor = (color?: string) => + validateWorkspaceColor(color) ? color : undefined; interface Props { coreStart: CoreStart; + registeredUseCases$: BehaviorSubject; } -/** - * Return maximum five workspaces, the current selected workspace - * will be on the top of the list. - */ -function getFilteredWorkspaceList( - workspaceList: WorkspaceObject[], - currentWorkspace: WorkspaceObject | null -): WorkspaceObject[] { - return [ - ...(currentWorkspace ? [currentWorkspace] : []), - ...workspaceList.filter((workspace) => workspace.id !== currentWorkspace?.id), - ].slice(0, 5); -} - -export const WorkspaceMenu = ({ coreStart }: Props) => { +export const WorkspaceMenu = ({ coreStart, registeredUseCases$ }: Props) => { const [isPopoverOpen, setPopover] = useState(false); const currentWorkspace = useObservable(coreStart.workspaces.currentWorkspace$, null); - const workspaceList = useObservable(coreStart.workspaces.workspaceList$, []); + const isDashboardAdmin = coreStart.application.capabilities?.dashboards?.isDashboardAdmin; + const availableUseCases = useObservable(registeredUseCases$, []); - const defaultHeaderName = i18n.translate( - 'core.ui.primaryNav.workspacePickerMenu.defaultHeaderName', - { - defaultMessage: 'Select a workspace', - } - ); - const filteredWorkspaceList = getFilteredWorkspaceList(workspaceList, currentWorkspace); const currentWorkspaceName = currentWorkspace?.name ?? defaultHeaderName; + const getUseCase = (workspace: WorkspaceObject) => { + if (!workspace.features) { + return; + } + const useCaseId = getFirstUseCaseOfFeatureConfigs(workspace.features); + return availableUseCases.find((useCase) => useCase.id === useCaseId); + }; + const openPopover = () => { setPopover(!isPopoverOpen); }; @@ -67,114 +79,29 @@ export const WorkspaceMenu = ({ coreStart }: Props) => { setPopover(false); }; - const workspaceToItem = (workspace: WorkspaceObject) => { - const workspaceURL = formatUrlWithWorkspaceId( - coreStart.application.getUrlForApp(WORKSPACE_OVERVIEW_APP_ID, { - absolute: false, - }), - workspace.id, - coreStart.http.basePath - ); - const name = - currentWorkspace?.name === workspace.name ? ( - - {workspace.name} - - ) : ( - workspace.name - ); - return { - name, - key: workspace.id, - icon: , - onClick: () => { - window.location.assign(workspaceURL); - }, - }; - }; - - const getWorkspaceListItems = () => { - const workspaceListItems: EuiContextMenuPanelItemDescriptor[] = filteredWorkspaceList.map( - workspaceToItem - ); - workspaceListItems.push({ - icon: , - name: i18n.translate('core.ui.primaryNav.workspaceContextMenu.createWorkspace', { - defaultMessage: 'Create workspace', - }), - key: WORKSPACE_CREATE_APP_ID, - onClick: () => { - window.location.assign( - cleanWorkspaceId( - coreStart.application.getUrlForApp(WORKSPACE_CREATE_APP_ID, { - absolute: false, - }) - ) - ); - }, - }); - workspaceListItems.push({ - icon: , - name: i18n.translate('core.ui.primaryNav.workspaceContextMenu.allWorkspace', { - defaultMessage: 'All workspaces', - }), - key: WORKSPACE_LIST_APP_ID, - onClick: () => { - window.location.assign( - cleanWorkspaceId( - coreStart.application.getUrlForApp(WORKSPACE_LIST_APP_ID, { - absolute: false, - }) - ) - ); - }, - }); - return workspaceListItems; - }; - - const currentWorkspaceButton = ( - <> - - - - - ); - - const currentWorkspaceTitle = ( - - - {currentWorkspaceName} - - - - - + const currentWorkspaceButton = currentWorkspace ? ( + + + + ) : ( + ); - const panels = [ - { - id: 0, - title: currentWorkspaceTitle, - items: getWorkspaceListItems(), - }, - ]; - return ( { button={currentWorkspaceButton} isOpen={isPopoverOpen} closePopover={closePopover} - panelPaddingSize="none" + panelPaddingSize="s" anchorPosition="downCenter" > - + + + {currentWorkspace ? ( + <> + + + + + + + {currentWorkspaceName} + + + + + {getUseCase(currentWorkspace)?.title ?? ''} + + + { + closePopover(); + navigateToWorkspaceDetail(coreStart, currentWorkspace.id); + }} + > + {manageWorkspaceButton} + + + + ) : ( + <> + + + + + {currentWorkspaceName} + + + { + closePopover(); + coreStart.application.navigateToApp(WORKSPACE_LIST_APP_ID); + }} + > + {manageWorkspacesButton} + + + + )} + + + + setPopover(false)} + /> + + + + + { + closePopover(); + coreStart.application.navigateToApp(WORKSPACE_LIST_APP_ID); + }} + > + {viewAllButton} + + + {isDashboardAdmin && ( + + { + closePopover(); + coreStart.application.navigateToApp(WORKSPACE_CREATE_APP_ID); + }} + > + {createWorkspaceButton} + + + )} + + ); }; diff --git a/src/plugins/workspace/public/components/workspace_overview/__snapshots__/workspace_overview.test.tsx.snap b/src/plugins/workspace/public/components/workspace_overview/__snapshots__/workspace_overview.test.tsx.snap deleted file mode 100644 index 650f0775b8e4..000000000000 --- a/src/plugins/workspace/public/components/workspace_overview/__snapshots__/workspace_overview.test.tsx.snap +++ /dev/null @@ -1,314 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`WorkspaceOverview render workspace overview page normally 1`] = ` -
-
-
-
-
-
-

-
-
- foo -
-
-

-
-
-
-
-

- Start working -

-
-
-
-
-
- - - - -
- -
-
-
-
-
- - - - -
- -
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
-
- - - -
-
-
-
-
-
-
-
-
-
- - About - -
-

- this is my foo workspace description -

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-`; diff --git a/src/plugins/workspace/public/components/workspace_overview/all_get_started_cards.ts b/src/plugins/workspace/public/components/workspace_overview/all_get_started_cards.ts deleted file mode 100644 index ad7dcad86bb8..000000000000 --- a/src/plugins/workspace/public/components/workspace_overview/all_get_started_cards.ts +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { GetStartCard } from './types'; -import { WORKSPACE_APP_CATEGORIES } from '../../../common/constants'; - -/** - * All getting start cards - */ -export const getStartCards: GetStartCard[] = [ - // getStarted - { - id: '', // set id as empty so that it will always show up - featureDescription: 'Discover pre-loaded datasets before adding your own.', - featureName: 'Sample Datasets', - link: '/app/import_sample_data', - category: WORKSPACE_APP_CATEGORIES.getStarted, - }, - { - id: 'workspace_create', - featureDescription: 'Build a collaborative hub for your team.', - featureName: 'Workspaces', - link: '/app/workspace_create', - category: WORKSPACE_APP_CATEGORIES.getStarted, - }, - { - id: 'datasources', - featureDescription: 'Seamlessly integrate your data sources.', - featureName: 'Data Sources', - link: '/app/datasources', - category: WORKSPACE_APP_CATEGORIES.getStarted, - }, - { - id: 'management', - featureDescription: 'Unlock seamless data access.', - featureName: 'Index Patterns', - link: '/app/management/opensearch-dashboards/indexPatterns', - category: WORKSPACE_APP_CATEGORIES.getStarted, - }, - { - id: 'integrations', - featureDescription: 'Gain instant insights with pre-configured log dashboards.', - featureName: 'Integrations', - link: '/app/integrations', - category: WORKSPACE_APP_CATEGORIES.getStarted, - }, - // dashboardAndReport - { - id: 'dashboards', - featureDescription: 'Gain clarity and visibility with dynamic data visualization tools.', - featureName: 'Dashboards', - link: '/app/dashboards', - category: WORKSPACE_APP_CATEGORIES.dashboardAndReport, - }, - { - id: 'visualize', - featureDescription: - 'Unlock insightful data exploration with powerful visualization and aggregation tools.', - featureName: 'Visualizations', - link: '/app/visualize', - category: WORKSPACE_APP_CATEGORIES.dashboardAndReport, - }, - { - id: 'maps-dashboards', - featureDescription: 'Unlock spatial insights with multi-layer map visualizations.', - featureName: 'Maps', - link: '/app/maps-dashboards', - category: WORKSPACE_APP_CATEGORIES.dashboardAndReport, - }, - { - id: 'observability-notebooks', - featureDescription: 'Gain real-time visibility with dynamic, data-powered report generation.', - featureName: 'Notebooks', - link: '/app/observability-notebooks', - category: WORKSPACE_APP_CATEGORIES.dashboardAndReport, - }, - { - id: 'reports-dashboards', - featureDescription: 'Collaborate effectively with multi-format report sharing.', - featureName: 'Reports', - link: '/app/reports-dashboards', - category: WORKSPACE_APP_CATEGORIES.dashboardAndReport, - }, - // investigate - { - id: 'discover', - featureDescription: 'Uncover insights with raw data exploration.', - featureName: 'Discover', - link: '/app/data-explorer/discover', - category: WORKSPACE_APP_CATEGORIES.investigate, - }, - { - id: 'observability-traces', - featureDescription: 'Unveil performance bottlenecks with event flow visualization.', - featureName: 'Traces', - link: '/app/observability-traces', - category: WORKSPACE_APP_CATEGORIES.investigate, - }, - { - id: 'observability-metrics', - featureDescription: 'Transform logs into actionable visualizations with metric extraction.', - featureName: 'Metrics', - link: '/app/observability-metrics', - category: WORKSPACE_APP_CATEGORIES.investigate, - }, - { - id: 'observability-applications', - featureDescription: - 'Gain comprehensive system visibility with unified log, trace, and metric analysis.', - featureName: 'Applications', - link: '/app/observability-applications', - category: WORKSPACE_APP_CATEGORIES.investigate, - }, - // detect - { - id: 'alerting', - featureDescription: 'Proactively identify risks with customizable alter triggers.', - featureName: 'Alerts', - link: '/app/alerting', - category: WORKSPACE_APP_CATEGORIES.detect, - }, - { - id: 'anomaly-detection-dashboards', - featureDescription: 'Unveil anomalies with real-time data monitoring.', - featureName: 'Anomaly Detectors', - link: '/app/anomaly-detection-dashboards#/detectors', - category: WORKSPACE_APP_CATEGORIES.detect, - }, - { - id: 'opensearch_security_analytics_dashboards', - featureDescription: 'Receive timely notifications with detector-driven alert configuration.', - featureName: 'Threat Alerts', - link: '/app/opensearch_security_analytics_dashboards#/alerts', - category: WORKSPACE_APP_CATEGORIES.detect, - }, - { - id: 'opensearch_security_analytics_dashboards', - featureDescription: 'Proactively safeguard your systems with customizable detection rules.', - featureName: 'Threat Detectors', - link: '/app/opensearch_security_analytics_dashboards#/detectors', - category: WORKSPACE_APP_CATEGORIES.detect, - }, - { - id: 'opensearch_security_analytics_dashboards', - featureDescription: 'Tailor detection capabilities with flexible rule management.', - featureName: 'Detection Rules', - link: '/app/opensearch_security_analytics_dashboards#/rules', - category: WORKSPACE_APP_CATEGORIES.detect, - }, - { - id: 'opensearch_security_analytics_dashboards', - featureDescription: 'Detect multi-system threats with correlation rule builder.', - featureName: 'Correlations', - link: '/app/opensearch_security_analytics_dashboards#/correlations', - category: WORKSPACE_APP_CATEGORIES.detect, - }, - { - id: 'opensearch_security_analytics_dashboards', - featureDescription: 'Uncover hidden patterns and trends with detector finding analysis.', - featureName: 'Findings', - link: '/app/opensearch_security_analytics_dashboards#/findings', - category: WORKSPACE_APP_CATEGORIES.investigate, - }, - // build search solutions - { - id: 'searchRelevance', - featureDescription: 'Optimize query performance with side-by-side comparison.', - featureName: 'Compare Search Results', - link: '/app/searchRelevance', - category: WORKSPACE_APP_CATEGORIES.searchSolution, - }, -]; diff --git a/src/plugins/workspace/public/components/workspace_overview/getting_start_card.test.tsx b/src/plugins/workspace/public/components/workspace_overview/getting_start_card.test.tsx deleted file mode 100644 index 107568751cbc..000000000000 --- a/src/plugins/workspace/public/components/workspace_overview/getting_start_card.test.tsx +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { fireEvent, render } from '@testing-library/react'; -import React from 'react'; -import { WorkspaceOverviewCard } from './getting_start_card'; -import { coreMock } from '../../../../../core/public/mocks'; -import { DEFAULT_APP_CATEGORIES } from '../../../../../core/public'; -import { GetStartCard } from './types'; - -describe('WorkspaceOverviewCard', () => { - const featureName = 'Visualizations'; - const featureDescription = 'this is a description'; - const card = { - id: 'visualize', - featureDescription, - featureName, - link: '/app/visualize', - category: DEFAULT_APP_CATEGORIES.dashboardAndReport, - }; - const mockCoreStart = coreMock.createStart(); - const renderWorkspaceCard = (_card: GetStartCard) => { - return ( - - ); - }; - - it('render getting start card normally', async () => { - const { container } = render(renderWorkspaceCard(card)); - expect(container).toHaveTextContent(`with Visualizations`); - expect(container).toHaveTextContent(featureDescription); - }); - - it('click on card will navigate to related URL', async () => { - const { getByTestId } = render(renderWorkspaceCard(card)); - fireEvent.click(getByTestId(featureName)); - expect(mockCoreStart.application.getUrlForApp).not.toHaveBeenCalled(); - expect(mockCoreStart.application.navigateToUrl).toHaveBeenCalledWith( - 'http://localhost/w/test/app/visualize' - ); - }); - - it('click on card will navigate to specified app if no link provided', async () => { - const { getByTestId } = render(renderWorkspaceCard({ ...card, link: undefined })); - fireEvent.click(getByTestId(featureName)); - expect(mockCoreStart.application.getUrlForApp).toHaveBeenCalledWith('visualize'); - expect(mockCoreStart.application.navigateToUrl).toHaveBeenCalledWith( - 'http://localhost/w/test/app/visualize' - ); - }); -}); diff --git a/src/plugins/workspace/public/components/workspace_overview/getting_start_card.tsx b/src/plugins/workspace/public/components/workspace_overview/getting_start_card.tsx deleted file mode 100644 index a25451129fb0..000000000000 --- a/src/plugins/workspace/public/components/workspace_overview/getting_start_card.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { EuiCard, EuiFlexGroup, EuiFlexItem, EuiText, EuiTextColor } from '@elastic/eui'; -import React from 'react'; -import { ApplicationStart, IBasePath } from 'opensearch-dashboards/public'; -import { FormattedMessage } from '@osd/i18n/react'; -import { formatUrlWithWorkspaceId } from '../../../../../core/public/utils'; -import { GetStartCard } from './types'; - -export interface WorkspaceOverviewCardProps { - card: GetStartCard; - workspaceId: string; - basePath: IBasePath; - application: ApplicationStart; -} - -export const WorkspaceOverviewCard = ({ - card, - application, - workspaceId, - basePath, -}: WorkspaceOverviewCardProps) => { - return ( - -

{card.featureDescription}

- - } - description={''} - footer={ - - - - - - - - - - } - onClick={() => { - let url = card.link; - if (!url && card.id) { - url = application.getUrlForApp(card.id); - } - - if (workspaceId && url) { - application.navigateToUrl(formatUrlWithWorkspaceId(url, workspaceId, basePath)); - } - }} - /> - ); -}; diff --git a/src/plugins/workspace/public/components/workspace_overview/getting_start_modal.test.tsx b/src/plugins/workspace/public/components/workspace_overview/getting_start_modal.test.tsx deleted file mode 100644 index d63459a1dfe0..000000000000 --- a/src/plugins/workspace/public/components/workspace_overview/getting_start_modal.test.tsx +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { fireEvent, render } from '@testing-library/react'; -import React from 'react'; -import { coreMock } from '../../../../../core/public/mocks'; -import { - WorkspaceOverviewGettingStartModal, - WorkspaceOverviewGettingStartModalProps, -} from './getting_start_modal'; -import { GetStartCard } from './types'; -import { waitFor } from '@testing-library/dom'; -import { WORKSPACE_APP_CATEGORIES } from '../../../common/constants'; - -// see https://github.com/elastic/eui/issues/5271 as workaround to render EuiSelectable correctly -jest.mock('react-virtualized-auto-sizer', () => ({ children }: any) => - children({ height: 600, width: 300 }) -); - -describe('WorkspaceOverviewGettingStartModal', () => { - const mockCoreStart = coreMock.createStart(); - const closeModal = jest.fn(); - const renderWorkspaceCardModal = (cards: GetStartCard[]) => { - const props: WorkspaceOverviewGettingStartModalProps = { - availableCards: cards, - onCloseModal: closeModal, - application: mockCoreStart.application, - basePath: mockCoreStart.http.basePath, - workspaceId: 'foo', - }; - return ; - }; - - it('render getting start card modal normally with empty cards', async () => { - const { getByTestId } = render(renderWorkspaceCardModal([])); - await waitFor(() => expect(getByTestId('category_single_selection')).toHaveTextContent('All')); - }); - - it('render getting start card modal normally', async () => { - const cards = [ - { - id: 'home', - featureDescription: 'Discover pre-loaded datasets before adding your own.', - featureName: 'Sample Datasets', - link: '/app/home#/tutorial_directory', - category: WORKSPACE_APP_CATEGORIES.getStarted, - }, - { - id: 'dashboards', - featureDescription: 'Gain clarity and visibility with dynamic data visualization tools.', - featureName: 'Dashboards', - link: '/app/dashboards', - category: WORKSPACE_APP_CATEGORIES.dashboardAndReport, - }, - ]; - const { queryByText, getByTestId } = render(renderWorkspaceCardModal(cards)); - expect(getByTestId('category_single_selection')).toHaveTextContent('All'); - expect(getByTestId('category_single_selection')).toHaveTextContent('Get started'); - expect(getByTestId('category_single_selection')).toHaveTextContent('Dashboard and report'); - expect( - queryByText('Gain clarity and visibility with dynamic data visualization tools.') - ).not.toBeNull(); - expect(queryByText('Discover pre-loaded datasets before adding your own.')).not.toBeNull(); - }); - - it('click on category to filter cards', async () => { - const cards = [ - { - id: 'home', - featureDescription: 'Discover pre-loaded datasets before adding your own.', - featureName: 'Sample Datasets', - link: '/app/home#/tutorial_directory', - category: WORKSPACE_APP_CATEGORIES.getStarted, - }, - { - id: 'dashboards', - featureDescription: 'Gain clarity and visibility with dynamic data visualization tools.', - featureName: 'Dashboards', - link: '/app/dashboards', - category: WORKSPACE_APP_CATEGORIES.dashboardAndReport, - }, - ]; - const { queryByText, getByTitle } = render(renderWorkspaceCardModal(cards)); - // click `Get started` category - fireEvent.click(getByTitle('Get started')); - expect( - queryByText('Gain clarity and visibility with dynamic data visualization tools.') - ).toBeNull(); - expect(queryByText('Discover pre-loaded datasets before adding your own.')).not.toBeNull(); - - // click `Dashboard and report` category - fireEvent.click(getByTitle('Dashboard and report')); - expect( - queryByText('Gain clarity and visibility with dynamic data visualization tools.') - ).not.toBeNull(); - expect(queryByText('Discover pre-loaded datasets before adding your own.')).toBeNull(); - }); - - it('click on close will close the modal', async () => { - const { getByTestId } = render(renderWorkspaceCardModal([])); - fireEvent.click(getByTestId('close')); - expect(closeModal).toHaveBeenCalled(); - }); -}); diff --git a/src/plugins/workspace/public/components/workspace_overview/getting_start_modal.tsx b/src/plugins/workspace/public/components/workspace_overview/getting_start_modal.tsx deleted file mode 100644 index c2bfc104cc05..000000000000 --- a/src/plugins/workspace/public/components/workspace_overview/getting_start_modal.tsx +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { ReactNode, useState } from 'react'; -import { - EuiFlexItem, - EuiText, - slugify, - EuiTitle, - EuiSpacer, - EuiFlexGrid, - EuiModal, - EuiModalHeader, - EuiModalBody, - EuiModalHeaderTitle, - EuiModalFooter, - EuiButton, - EuiFlexGroup, - EuiPanel, - EuiSelectable, - EuiSelectableOption, -} from '@elastic/eui'; -import { WorkspaceOverviewCard, WorkspaceOverviewCardProps } from './getting_start_card'; -import { GetStartCard } from './types'; -import './index.scss'; - -export interface WorkspaceOverviewGettingStartModalProps - extends Omit { - onCloseModal: () => void; - availableCards: GetStartCard[]; -} - -export const WorkspaceOverviewGettingStartModal = ( - props: WorkspaceOverviewGettingStartModalProps -) => { - const ALL = 'All'; - const [selectedItemName, setSelectedItem] = useState(ALL); - const { onCloseModal, availableCards } = props; - - const categories: string[] = [ - ...new Set( - availableCards.map((card) => { - return card?.category?.label || ALL; - }) - ), - ]; - - const options: EuiSelectableOption[] = [ALL, ...categories].map((category) => { - return { - label: category, - checked: selectedItemName === category ? 'on' : undefined, - className: 'gettingStartCategoryItem', - }; - }); - - const categorySelection = ( - { - const selectedOption = newOptions.find((option) => option.checked === 'on'); - setSelectedItem(selectedOption?.label || ALL); - }} - singleSelection={true} - listProps={{ - bordered: false, - rowHeight: 48, - onFocusBadge: false, - windowProps: { - className: 'gettingStartCategoryItemList', - }, - }} - > - {(list) => { - return list; - }} - - ); - - const cardList: ReactNode[] = categories - .filter((category) => category === selectedItemName || selectedItemName === ALL) - .map((category) => { - const cards = availableCards.filter((card) => { - return card.category?.label === category; - }); - - return ( -
- -

{category}

-
- - - {cards.map((card) => { - return ( - - - - ); - })} - - -
- ); - }); - - return ( - - - -

Define your path forward

- Discover tailored solutions for your unique objectives -
-
- - - - - {categorySelection} - - - - {cardList} - - - - - - - Close - - -
- ); -}; diff --git a/src/plugins/workspace/public/components/workspace_overview/index.scss b/src/plugins/workspace/public/components/workspace_overview/index.scss deleted file mode 100644 index 5c878370220c..000000000000 --- a/src/plugins/workspace/public/components/workspace_overview/index.scss +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -.gettingStartModel { - width: calc(90vw - #{$euiSize}); -} - -.gettingStartModel_body { - overflow-y: auto; - height: calc(75vh - 200px); -} - -.gettingStartCategoryItem { - border: none !important; -} - -.gettingStartCategory { - div { - &:focus-within { - outline: none; - animation: none !important; - } - } -} - -.gettingStartCategoryItemList { - mask: none !important; -} diff --git a/src/plugins/workspace/public/components/workspace_overview/types.ts b/src/plugins/workspace/public/components/workspace_overview/types.ts deleted file mode 100644 index e9ba69402cc9..000000000000 --- a/src/plugins/workspace/public/components/workspace_overview/types.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { App } from 'opensearch-dashboards/public'; - -export interface GetStartCard extends Partial { - /** - * feature Name - */ - featureName: string; - /** - * card description - */ - featureDescription: string; - /** - * destination when the card been clicked - */ - link?: string; -} diff --git a/src/plugins/workspace/public/components/workspace_overview/workspace_overview.test.tsx b/src/plugins/workspace/public/components/workspace_overview/workspace_overview.test.tsx deleted file mode 100644 index 64ff49fcbdd6..000000000000 --- a/src/plugins/workspace/public/components/workspace_overview/workspace_overview.test.tsx +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { fireEvent, render, screen } from '@testing-library/react'; -import React from 'react'; -import { coreMock } from '../../../../../core/public/mocks'; -import { createOpenSearchDashboardsReactContext } from '../../../../opensearch_dashboards_react/public'; -import { BehaviorSubject } from 'rxjs'; -import { PublicAppInfo, WorkspaceObject } from 'opensearch-dashboards/public'; -import { IS_WORKSPACE_OVERVIEW_COLLAPSED_KEY, WorkspaceOverview } from './workspace_overview'; - -// all applications -const PublicAPPInfoMap = new Map([ - ['alerting', { id: 'alerting', title: 'alerting' }], - ['home', { id: 'home', title: 'home' }], -]); - -const mockCoreStart = coreMock.createStart(); - -const createWorkspacesSetupContractMockWithValue = (workspace?: WorkspaceObject) => { - const currentWorkspace = workspace - ? workspace - : { - id: 'foo_id', - name: 'foo', - description: 'this is my foo workspace description', - features: ['alerting'], - color: '', - icon: '', - reserved: false, - }; - const currentWorkspaceId$ = new BehaviorSubject(currentWorkspace.id); - const workspaceList$ = new BehaviorSubject([currentWorkspace]); - const currentWorkspace$ = new BehaviorSubject(currentWorkspace); - const initialized$ = new BehaviorSubject(true); - return { - currentWorkspaceId$, - workspaceList$, - currentWorkspace$, - initialized$, - }; -}; - -const WorkspaceOverviewPage = (props: any) => { - const workspacesService = props.workspacesService || createWorkspacesSetupContractMockWithValue(); - const { Provider } = createOpenSearchDashboardsReactContext({ - ...mockCoreStart, - ...{ - application: { - ...mockCoreStart.application, - applications$: new BehaviorSubject>(PublicAPPInfoMap as any), - }, - workspaces: workspacesService, - }, - }); - - return ( - - - - ); -}; - -const setLocalStorage = jest.fn(); -const localStorageMock = { - getItem: jest.fn(), - setItem: setLocalStorage, - removeItem: jest.fn(), - key: jest.fn(), - clear: jest.fn(), -}; - -describe('WorkspaceOverview', () => { - const localStorage = window.localStorage; - - beforeAll(() => { - // Mock localStorage globally - Object.defineProperty(window, 'localStorage', { - value: localStorageMock, - }); - }); - - afterAll(() => { - Object.defineProperty(window, 'localStorage', { - value: localStorage, - }); - }); - - it('render workspace overview page normally', async () => { - const { container } = render(WorkspaceOverviewPage({})); - expect(container).toMatchSnapshot(); - }); - - it('filter getting start cards when workspace features is `*`', async () => { - const workspaceObject = { - id: 'foo_id', - name: 'foo', - description: 'this is my foo workspace description', - features: ['*'], - color: '', - icon: '', - reserved: false, - }; - const workspaceService = createWorkspacesSetupContractMockWithValue(workspaceObject); - const { getByTestId } = render(WorkspaceOverviewPage({ workspacesService: workspaceService })); - expect(getByTestId('workspaceGetStartCards')).toHaveTextContent('Sample Datasets'); - // see more - expect(getByTestId('workspaceGetStartCards')).toHaveTextContent( - 'Explore more paths to kick-start your OpenSearch journey.' - ); - }); - - it('filter getting start cards when workspace features is subset of all features', async () => { - const workspaceObject = { - id: 'foo_id', - name: 'foo', - description: 'this is my foo workspace description', - features: ['alerting', 'home'], - color: '', - icon: '', - reserved: false, - }; - const workspaceService = createWorkspacesSetupContractMockWithValue(workspaceObject); - const { getByTestId } = render(WorkspaceOverviewPage({ workspacesService: workspaceService })); - expect(getByTestId('workspaceGetStartCards')).toHaveTextContent('with Sample Datasets'); - expect(getByTestId('workspaceGetStartCards')).toHaveTextContent('with Alerts'); - // no see more - expect(getByTestId('workspaceGetStartCards')).not.toHaveTextContent( - 'Explore more paths to kick-start your OpenSearch journey.' - ); - }); - - it('getting start section is expanded by default', async () => { - const workspaceObject = { - id: 'foo_id', - name: 'foo', - description: 'this is my foo workspace description', - features: ['alerting', 'home'], - color: '', - icon: '', - reserved: false, - }; - const workspaceService = createWorkspacesSetupContractMockWithValue(workspaceObject); - const { getByTestId } = render(WorkspaceOverviewPage({ workspacesService: workspaceService })); - expect(getByTestId('Collapse')).toBeVisible(); - }); - - it('getting start section visible setting will saved to localStorage', async () => { - const workspaceObject = { - id: 'foo_id', - name: 'foo', - description: 'this is my foo workspace description', - features: ['alerting', 'home'], - color: '', - icon: '', - reserved: false, - }; - const workspaceService = createWorkspacesSetupContractMockWithValue(workspaceObject); - const { getByTestId } = render(WorkspaceOverviewPage({ workspacesService: workspaceService })); - expect(getByTestId('Collapse')).toBeVisible(); - fireEvent.click(getByTestId('Collapse')); - expect(getByTestId('Expand')).toBeVisible(); - expect(localStorageMock.setItem).toHaveBeenCalledWith( - IS_WORKSPACE_OVERVIEW_COLLAPSED_KEY + '_foo_id', - 'true' - ); - // click on Collapse - fireEvent.click(getByTestId('Expand')); - expect(getByTestId('Collapse')).toBeVisible(); - expect(localStorageMock.setItem).toHaveBeenCalledWith( - IS_WORKSPACE_OVERVIEW_COLLAPSED_KEY + '_foo_id', - 'false' - ); - }); - - it('click on library tab will redirect to saved objects page', async () => { - const workspaceObject = { - id: 'foo_id', - name: 'foo', - description: 'this is my foo workspace description', - features: ['alerting', 'home'], - color: '', - icon: '', - reserved: false, - }; - const workspaceService = createWorkspacesSetupContractMockWithValue(workspaceObject); - const { getByText } = render(WorkspaceOverviewPage({ workspacesService: workspaceService })); - fireEvent.click(getByText('Library')); - - expect(mockCoreStart.application.navigateToApp).toHaveBeenCalledWith('management', { - path: 'opensearch-dashboards/objects', - }); - }); - - it('click on settings tab will show workspace update page', async () => { - const workspaceObject = { - id: 'foo_id', - name: 'foo', - description: 'this is my foo workspace description', - features: ['alerting', 'home'], - color: '', - icon: '', - reserved: false, - }; - const workspaceService = createWorkspacesSetupContractMockWithValue(workspaceObject); - const { getByText } = render(WorkspaceOverviewPage({ workspacesService: workspaceService })); - fireEvent.click(getByText('Settings')); - expect(screen.queryByText('Workspace Details')).not.toBeNull(); - // title is hidden - expect(screen.queryByText('Update Workspace')).toBeNull(); - }); - - it('default selected tab is overview', async () => { - const workspaceObject = { - id: 'foo_id', - name: 'foo', - description: 'this is my foo workspace description', - features: ['alerting', 'home'], - color: '', - icon: '', - reserved: false, - }; - const workspaceService = createWorkspacesSetupContractMockWithValue(workspaceObject); - render(WorkspaceOverviewPage({ workspacesService: workspaceService })); - expect(document.querySelector('#overview')).toHaveClass('euiTab-isSelected'); - }); -}); diff --git a/src/plugins/workspace/public/components/workspace_overview/workspace_overview.tsx b/src/plugins/workspace/public/components/workspace_overview/workspace_overview.tsx deleted file mode 100644 index 63073bbce8c6..000000000000 --- a/src/plugins/workspace/public/components/workspace_overview/workspace_overview.tsx +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { useState, useMemo, ReactNode, useEffect } from 'react'; -import { - EuiPage, - EuiPageBody, - EuiPageHeader, - EuiSpacer, - EuiFlexItem, - EuiText, - EuiTabbedContent, - EuiTitle, - EuiFlexGroup, - EuiPanel, - EuiButtonEmpty, -} from '@elastic/eui'; - -import { useObservable } from 'react-use'; -import { i18n } from '@osd/i18n'; -import { App, CoreStart, PublicAppInfo } from 'opensearch-dashboards/public'; -import { BehaviorSubject } from 'rxjs'; -import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; -import { WorkspaceOverviewSettings } from './workspace_overview_settings'; -import { WorkspaceOverviewContent } from './workspace_overview_content'; -import { getStartCards } from './all_get_started_cards'; -import { isAppAccessibleInWorkspace } from '../../utils'; -import { WorkspaceOverviewCard } from './getting_start_card'; -import { WorkspaceOverviewGettingStartModal } from './getting_start_modal'; - -export const IS_WORKSPACE_OVERVIEW_COLLAPSED_KEY = 'workspace:overview_collapsed'; - -export interface WorkspaceOverviewProps { - workspaceConfigurableApps$?: BehaviorSubject; -} - -export const WorkspaceOverview = (props: WorkspaceOverviewProps) => { - const { - services: { workspaces, application, http }, - } = useOpenSearchDashboards(); - - const currentWorkspace = useObservable(workspaces.currentWorkspace$); - const currentWorkspaceId = useObservable(workspaces.currentWorkspaceId$); - - // workspace level setting - const workspaceOverviewCollapsedKey = `${IS_WORKSPACE_OVERVIEW_COLLAPSED_KEY}_${ - currentWorkspaceId || '' - }`; - - const [isModalVisible, setIsModalVisible] = useState(false); - const [isGettingStartCardsCollapsed, setIsGettingStartCardsCollapsed] = useState(false); - - useEffect(() => { - setIsGettingStartCardsCollapsed(localStorage.getItem(workspaceOverviewCollapsedKey) === 'true'); - }, [workspaceOverviewCollapsedKey]); - - /** - * all available cards based on workspace selected features - */ - const availableCards = useMemo(() => { - if (!currentWorkspace) return []; - return getStartCards.filter( - (card) => !card.id || isAppAccessibleInWorkspace(card as App, currentWorkspace) - ); - }, [currentWorkspace]); - - if (!currentWorkspace) { - return null; - } - - const pageTitle = ( - - {currentWorkspace?.name} - - ); - - const tabs = [ - { - id: 'overview', - name: i18n.translate('workspace.overview.tabTitle', { - defaultMessage: 'Overview', - }), - content: , - }, - { - id: 'library', - name: i18n.translate('workspace.overview.library.tabTitle', { - defaultMessage: 'Library', - }), - content: <>, - }, - { - id: 'settings', - name: i18n.translate('workspace.overview.setting.tabTitle', { - defaultMessage: 'Settings', - }), - content: , - }, - ]; - - const collapseButton = ( - { - const newValue = !isGettingStartCardsCollapsed; - setIsGettingStartCardsCollapsed(newValue); - localStorage.setItem(workspaceOverviewCollapsedKey, newValue ? 'true' : 'false'); - }} - > - {isGettingStartCardsCollapsed ? 'Expand' : 'Collapse'} - - ); - - const rightSideItems: ReactNode[] = isGettingStartCardsCollapsed ? [collapseButton] : []; - - return ( - <> - - - {!isGettingStartCardsCollapsed ? ( - <> - -

- {i18n.translate('workspace.overview.startWorking.title', { - defaultMessage: 'Start working', - })} -

-
- - - {availableCards.slice(0, 5).map((card, i) => { - return ( - - - - ); - })} - {availableCards.length > 5 ? ( - - { - setIsModalVisible(true); - }} - > - - {i18n.translate('workspace.overview.seeMore.description', { - defaultMessage: - 'Explore more paths to kick-start your OpenSearch journey.', - })} - - - - ) : null} - - - - {collapseButton} - - - ) : null} -
-
- - - { - if (tab.id === 'library') { - application.navigateToApp('management', { - path: 'opensearch-dashboards/objects', - }); - } - }} - /> - {isModalVisible ? ( - { - setIsModalVisible(false); - }} - availableCards={availableCards} - workspaceId={currentWorkspace.id} - basePath={http.basePath} - application={application} - /> - ) : null} - - - - ); -}; diff --git a/src/plugins/workspace/public/components/workspace_overview/workspace_overview_content.tsx b/src/plugins/workspace/public/components/workspace_overview/workspace_overview_content.tsx deleted file mode 100644 index 6b2ad0fbe689..000000000000 --- a/src/plugins/workspace/public/components/workspace_overview/workspace_overview_content.tsx +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import { - EuiFlexItem, - EuiCard, - EuiFlexGroup, - EuiPage, - EuiPageContent, - EuiPageBody, - EuiSpacer, -} from '@elastic/eui'; -import React from 'react'; -import { useObservable } from 'react-use'; -import { of } from 'rxjs'; -import { useOpenSearchDashboards } from '../../../../../plugins/opensearch_dashboards_react/public'; - -export const WorkspaceOverviewContent = () => { - const { - services: { workspaces }, - } = useOpenSearchDashboards(); - - const currentWorkspace = useObservable(workspaces ? workspaces.currentWorkspace$ : of(null)); - - return ( - - - - - - - - - - - - - - ); -}; diff --git a/src/plugins/workspace/public/components/workspace_overview/workspace_overview_settings.tsx b/src/plugins/workspace/public/components/workspace_overview/workspace_overview_settings.tsx deleted file mode 100644 index 9515fab260c6..000000000000 --- a/src/plugins/workspace/public/components/workspace_overview/workspace_overview_settings.tsx +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { WorkspaceUpdater } from '../workspace_updater'; -import { WorkspaceOverviewProps } from './workspace_overview'; - -export const WorkspaceOverviewSettings = ({ - workspaceConfigurableApps$, -}: WorkspaceOverviewProps) => { - return ( - - ); -}; diff --git a/src/plugins/workspace/public/components/workspace_overview_app.tsx b/src/plugins/workspace/public/components/workspace_overview_app.tsx deleted file mode 100644 index c581aad9d322..000000000000 --- a/src/plugins/workspace/public/components/workspace_overview_app.tsx +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { useEffect } from 'react'; -import { I18nProvider } from '@osd/i18n/react'; -import { CoreStart } from 'opensearch-dashboards/public'; -import { useObservable } from 'react-use'; -import { EuiBreadcrumb } from '@elastic/eui'; -import { useOpenSearchDashboards } from '../../../opensearch_dashboards_react/public'; -import { WorkspaceOverview, WorkspaceOverviewProps } from './workspace_overview/workspace_overview'; - -export const WorkspaceOverviewApp = (props: WorkspaceOverviewProps) => { - const { - services: { workspaces, chrome, application }, - } = useOpenSearchDashboards(); - - const currentWorkspace = useObservable(workspaces.currentWorkspace$); - - /** - * set breadcrumbs to chrome - */ - useEffect(() => { - const breadcrumbs: EuiBreadcrumb[] = [ - { - text: 'Home', - onClick: () => { - application.navigateToApp('home'); - }, - }, - ]; - if (currentWorkspace) { - breadcrumbs.push({ - text: currentWorkspace.name, - }); - } - chrome?.setBreadcrumbs(breadcrumbs); - }, [chrome, currentWorkspace, application]); - - return ( - - - - ); -}; diff --git a/src/plugins/workspace/public/components/workspace_picker_content/workspace_picker_content.tsx b/src/plugins/workspace/public/components/workspace_picker_content/workspace_picker_content.tsx new file mode 100644 index 000000000000..4dace89ea119 --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_picker_content/workspace_picker_content.tsx @@ -0,0 +1,111 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { i18n } from '@osd/i18n'; +import React, { useMemo } from 'react'; +import { useObservable } from 'react-use'; +import { EuiTitle, EuiAvatar, EuiListGroup, EuiListGroupItem } from '@elastic/eui'; +import { BehaviorSubject } from 'rxjs'; +import { MAX_WORKSPACE_PICKER_NUM } from '../../../common/constants'; +import { CoreStart, WorkspaceObject } from '../../../../../core/public'; +import { recentWorkspaceManager } from '../../recent_workspace_manager'; +import { WorkspaceUseCase } from '../../types'; +import { validateWorkspaceColor } from '../../../common/utils'; +import { getFirstUseCaseOfFeatureConfigs, getUseCaseUrl } from '../../utils'; + +const allWorkspacesTitle = i18n.translate('workspace.menu.title.allWorkspaces', { + defaultMessage: 'All workspaces', +}); + +const recentWorkspacesTitle = i18n.translate('workspace.menu.title.recentWorkspaces', { + defaultMessage: 'Recent workspaces', +}); + +const getValidWorkspaceColor = (color?: string) => + validateWorkspaceColor(color) ? color : undefined; + +interface Props { + coreStart: CoreStart; + registeredUseCases$: BehaviorSubject; + onClickWorkspace?: () => void; +} + +export const WorkspacePickerContent = ({ + coreStart, + registeredUseCases$, + onClickWorkspace, +}: Props) => { + const workspaceList = useObservable(coreStart.workspaces.workspaceList$, []); + const availableUseCases = useObservable(registeredUseCases$, []); + + const filteredWorkspaceList = useMemo(() => { + return workspaceList.slice(0, MAX_WORKSPACE_PICKER_NUM); + }, [workspaceList]); + + const filteredRecentWorkspaces = useMemo(() => { + return recentWorkspaceManager + .getRecentWorkspaces() + .map((workspace) => workspaceList.find((ws) => ws.id === workspace.id)) + .filter((workspace): workspace is WorkspaceObject => workspace !== undefined) + .slice(0, MAX_WORKSPACE_PICKER_NUM); + }, [workspaceList]); + + const getUseCase = (workspace: WorkspaceObject) => { + if (!workspace.features) { + return; + } + const useCaseId = getFirstUseCaseOfFeatureConfigs(workspace.features); + return availableUseCases.find((useCase) => useCase.id === useCaseId); + }; + + const getWorkspaceListGroup = (filterWorkspaceList: WorkspaceObject[], itemType: string) => { + const listItems = filterWorkspaceList.map((workspace: WorkspaceObject) => { + const useCase = getUseCase(workspace); + const useCaseURL = getUseCaseUrl(useCase, workspace, coreStart.application, coreStart.http); + return ( + + } + label={workspace.name} + onClick={() => { + onClickWorkspace?.(); + window.location.assign(useCaseURL); + }} + /> + ); + }); + return ( + + +

{itemType === 'all' ? allWorkspacesTitle : recentWorkspacesTitle}

+ + } + /> + {listItems} +
+ ); + }; + + return ( + <> + {filteredRecentWorkspaces.length > 0 && + getWorkspaceListGroup(filteredRecentWorkspaces, 'recent')} + {filteredWorkspaceList.length > 0 && getWorkspaceListGroup(filteredWorkspaceList, 'all')} + + ); +}; diff --git a/src/plugins/workspace/public/components/workspace_updater/workspace_updater.test.tsx b/src/plugins/workspace/public/components/workspace_updater/workspace_updater.test.tsx deleted file mode 100644 index 450dfc357db3..000000000000 --- a/src/plugins/workspace/public/components/workspace_updater/workspace_updater.test.tsx +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React from 'react'; -import { PublicAppInfo, WorkspaceObject } from 'opensearch-dashboards/public'; -import { fireEvent, render, waitFor } from '@testing-library/react'; -import { BehaviorSubject } from 'rxjs'; -import { WorkspaceUpdater as WorkspaceCreatorComponent } from './workspace_updater'; -import { coreMock, workspacesServiceMock } from '../../../../../core/public/mocks'; -import { createOpenSearchDashboardsReactContext } from '../../../../opensearch_dashboards_react/public'; - -const workspaceClientUpdate = jest.fn().mockReturnValue({ result: true, success: true }); - -const navigateToApp = jest.fn(); -const notificationToastsAddSuccess = jest.fn(); -const notificationToastsAddDanger = jest.fn(); -const PublicAPPInfoMap = new Map([ - ['app1', { id: 'app1', title: 'app1' }], - ['app2', { id: 'app2', title: 'app2', category: { id: 'category1', label: 'category1' } }], - ['app3', { id: 'app3', category: { id: 'category1', label: 'category1' } }], - ['app4', { id: 'app4', category: { id: 'category2', label: 'category2' } }], - ['app5', { id: 'app5', category: { id: 'category2', label: 'category2' } }], -]); -const createWorkspacesSetupContractMockWithValue = () => { - const currentWorkspaceId$ = new BehaviorSubject('abljlsds'); - const currentWorkspace: WorkspaceObject = { - id: 'abljlsds', - name: 'test1', - description: 'test1', - features: [], - color: '', - icon: '', - reserved: false, - }; - const workspaceList$ = new BehaviorSubject([currentWorkspace]); - const currentWorkspace$ = new BehaviorSubject(currentWorkspace); - const initialized$ = new BehaviorSubject(false); - return { - currentWorkspaceId$, - workspaceList$, - currentWorkspace$, - initialized$, - }; -}; - -const mockCoreStart = coreMock.createStart(); - -const WorkspaceUpdater = (props: any) => { - const workspacesService = props.workspacesService || createWorkspacesSetupContractMockWithValue(); - const { Provider } = createOpenSearchDashboardsReactContext({ - ...mockCoreStart, - ...{ - application: { - ...mockCoreStart.application, - capabilities: { - ...mockCoreStart.application.capabilities, - workspaces: { - permissionEnabled: true, - }, - }, - navigateToApp, - getUrlForApp: jest.fn(() => '/app/workspace_overview'), - applications$: new BehaviorSubject>(PublicAPPInfoMap as any), - }, - workspaces: workspacesService, - notifications: { - ...mockCoreStart.notifications, - toasts: { - ...mockCoreStart.notifications.toasts, - addDanger: notificationToastsAddDanger, - addSuccess: notificationToastsAddSuccess, - }, - }, - workspaceClient: { - ...mockCoreStart.workspaces, - update: workspaceClientUpdate, - }, - }, - }); - - return ( - - - - ); -}; - -function clearMockedFunctions() { - workspaceClientUpdate.mockClear(); - notificationToastsAddDanger.mockClear(); - notificationToastsAddSuccess.mockClear(); -} - -describe('WorkspaceUpdater', () => { - beforeEach(() => clearMockedFunctions()); - const { location } = window; - const setHrefSpy = jest.fn((href) => href); - - beforeAll(() => { - if (window.location) { - // @ts-ignore - delete window.location; - } - window.location = {} as Location; - Object.defineProperty(window.location, 'href', { - get: () => 'http://localhost/', - set: setHrefSpy, - }); - }); - - afterAll(() => { - window.location = location; - }); - - it('cannot render when the name of the current workspace is empty', async () => { - const mockedWorkspacesService = workspacesServiceMock.createSetupContract(); - const { container } = render( - - ); - expect(container).toMatchInlineSnapshot(`
`); - }); - - it('cannot update workspace with invalid name', async () => { - const { getByTestId } = render( - - ); - const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText'); - fireEvent.input(nameInput, { - target: { value: '~' }, - }); - expect(workspaceClientUpdate).not.toHaveBeenCalled(); - }); - - it('cannot update workspace with invalid description', async () => { - const { getByTestId } = render( - - ); - const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText'); - fireEvent.input(nameInput, { - target: { value: 'test workspace name' }, - }); - const descriptionInput = getByTestId('workspaceForm-workspaceDetails-descriptionInputText'); - fireEvent.input(descriptionInput, { - target: { value: '~' }, - }); - expect(workspaceClientUpdate).not.toHaveBeenCalled(); - }); - - it('cancel update workspace', async () => { - const { findByText, getByTestId } = render( - - ); - fireEvent.click(getByTestId('workspaceForm-bottomBar-cancelButton')); - await findByText('Discard changes?'); - fireEvent.click(getByTestId('confirmModalConfirmButton')); - expect(navigateToApp).toHaveBeenCalled(); - }); - - it('update workspace successfully', async () => { - const { getByTestId, getByText, getAllByText } = render( - - ); - const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText'); - fireEvent.input(nameInput, { - target: { value: 'test workspace name' }, - }); - - const descriptionInput = getByTestId('workspaceForm-workspaceDetails-descriptionInputText'); - fireEvent.input(descriptionInput, { - target: { value: 'test workspace description' }, - }); - - const colorSelector = getByTestId( - 'euiColorPickerAnchor workspaceForm-workspaceDetails-colorPicker' - ); - fireEvent.input(colorSelector, { - target: { value: '#000000' }, - }); - - fireEvent.click(getByTestId('workspaceForm-workspaceFeatureVisibility-app1')); - fireEvent.click(getByTestId('workspaceForm-workspaceFeatureVisibility-category1')); - - fireEvent.click(getByText('Users & Permissions')); - fireEvent.click(getByTestId('workspaceForm-permissionSettingPanel-user-addNew')); - const userIdInput = getAllByText('Select')[0]; - fireEvent.click(userIdInput); - fireEvent.input(getByTestId('comboBoxSearchInput'), { - target: { value: 'test user id' }, - }); - fireEvent.blur(getByTestId('comboBoxSearchInput')); - - fireEvent.click(getByTestId('workspaceForm-bottomBar-updateButton')); - expect(workspaceClientUpdate).toHaveBeenCalledWith( - expect.any(String), - expect.objectContaining({ - name: 'test workspace name', - color: '#000000', - description: 'test workspace description', - features: expect.arrayContaining(['app1', 'app2', 'app3']), - }), - { - read: { - users: ['test user id'], - }, - library_read: { - users: ['test user id'], - }, - } - ); - await waitFor(() => { - expect(notificationToastsAddSuccess).toHaveBeenCalled(); - }); - expect(notificationToastsAddDanger).not.toHaveBeenCalled(); - await waitFor(() => { - expect(setHrefSpy).toHaveBeenCalledWith(expect.stringMatching(/workspace_overview$/)); - }); - }); - - it('should show danger toasts after update workspace failed', async () => { - workspaceClientUpdate.mockReturnValue({ result: false, success: false }); - const { getByTestId } = render( - - ); - const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText'); - fireEvent.input(nameInput, { - target: { value: 'test workspace name' }, - }); - fireEvent.click(getByTestId('workspaceForm-bottomBar-updateButton')); - expect(workspaceClientUpdate).toHaveBeenCalled(); - await waitFor(() => { - expect(notificationToastsAddDanger).toHaveBeenCalled(); - }); - expect(notificationToastsAddSuccess).not.toHaveBeenCalled(); - }); - - it('should show danger toasts after update workspace threw error', async () => { - workspaceClientUpdate.mockImplementation(() => { - throw new Error('update workspace failed'); - }); - const { getByTestId } = render( - - ); - const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText'); - fireEvent.input(nameInput, { - target: { value: 'test workspace name' }, - }); - fireEvent.click(getByTestId('workspaceForm-bottomBar-updateButton')); - expect(workspaceClientUpdate).toHaveBeenCalled(); - await waitFor(() => { - expect(notificationToastsAddDanger).toHaveBeenCalled(); - }); - expect(notificationToastsAddSuccess).not.toHaveBeenCalled(); - }); - - it('should show danger toasts when currentWorkspace is missing after click update button', async () => { - const mockedWorkspacesService = workspacesServiceMock.createSetupContract(); - const { getByTestId } = render( - - ); - - const nameInput = getByTestId('workspaceForm-workspaceDetails-nameInputText'); - fireEvent.input(nameInput, { - target: { value: 'test workspace name' }, - }); - fireEvent.click(getByTestId('workspaceForm-bottomBar-updateButton')); - mockedWorkspacesService.currentWorkspace$ = new BehaviorSubject(null); - expect(workspaceClientUpdate).toHaveBeenCalled(); - await waitFor(() => { - expect(notificationToastsAddDanger).toHaveBeenCalled(); - }); - expect(notificationToastsAddSuccess).not.toHaveBeenCalled(); - }); -}); diff --git a/src/plugins/workspace/public/components/workspace_updater/workspace_updater.tsx b/src/plugins/workspace/public/components/workspace_updater/workspace_updater.tsx deleted file mode 100644 index d786abc19179..000000000000 --- a/src/plugins/workspace/public/components/workspace_updater/workspace_updater.tsx +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { useCallback, useEffect, useState } from 'react'; -import { EuiPage, EuiPageBody, EuiPageHeader, EuiPageContent, EuiSpacer } from '@elastic/eui'; -import { i18n } from '@osd/i18n'; -import { PublicAppInfo } from 'opensearch-dashboards/public'; -import { useObservable } from 'react-use'; -import { BehaviorSubject, of } from 'rxjs'; -import { useOpenSearchDashboards } from '../../../../opensearch_dashboards_react/public'; -import { WORKSPACE_OVERVIEW_APP_ID } from '../../../common/constants'; -import { formatUrlWithWorkspaceId } from '../../../../../core/public/utils'; -import { WorkspaceAttributeWithPermission } from '../../../../../core/types'; -import { WorkspaceClient } from '../../workspace_client'; -import { - WorkspaceForm, - WorkspaceFormSubmitData, - WorkspaceOperationType, - convertPermissionsToPermissionSettings, - convertPermissionSettingsToPermissions, -} from '../workspace_form'; - -export interface WorkspaceUpdaterProps { - workspaceConfigurableApps$?: BehaviorSubject; - hideTitle?: boolean; - maxWidth?: number | string; -} - -function getFormDataFromWorkspace( - currentWorkspace: WorkspaceAttributeWithPermission | null | undefined -) { - if (!currentWorkspace) { - return null; - } - return { - ...currentWorkspace, - permissionSettings: currentWorkspace.permissions - ? convertPermissionsToPermissionSettings(currentWorkspace.permissions) - : currentWorkspace.permissions, - }; -} - -export const WorkspaceUpdater = (props: WorkspaceUpdaterProps) => { - const { - services: { application, workspaces, notifications, http, workspaceClient }, - } = useOpenSearchDashboards<{ workspaceClient: WorkspaceClient }>(); - const isPermissionEnabled = application?.capabilities.workspaces.permissionEnabled; - - const currentWorkspace = useObservable(workspaces ? workspaces.currentWorkspace$ : of(null)); - const workspaceConfigurableApps = useObservable( - props.workspaceConfigurableApps$ ?? of(undefined) - ); - const [currentWorkspaceFormData, setCurrentWorkspaceFormData] = useState( - getFormDataFromWorkspace(currentWorkspace) - ); - - const handleWorkspaceFormSubmit = useCallback( - async (data: WorkspaceFormSubmitData) => { - let result; - if (!currentWorkspace) { - notifications?.toasts.addDanger({ - title: i18n.translate('Cannot find current workspace', { - defaultMessage: 'Cannot update workspace', - }), - }); - return; - } - - try { - const { permissionSettings, ...attributes } = data; - result = await workspaceClient.update( - currentWorkspace.id, - attributes, - convertPermissionSettingsToPermissions(permissionSettings) - ); - if (result?.success) { - notifications?.toasts.addSuccess({ - title: i18n.translate('workspace.update.success', { - defaultMessage: 'Update workspace successfully', - }), - }); - if (application && http) { - // Redirect page after one second, leave one second time to show update successful toast. - window.setTimeout(() => { - window.location.href = formatUrlWithWorkspaceId( - application.getUrlForApp(WORKSPACE_OVERVIEW_APP_ID, { - absolute: true, - }), - currentWorkspace.id, - http.basePath - ); - }, 1000); - } - return; - } else { - throw new Error(result?.error ? result?.error : 'update workspace failed'); - } - } catch (error) { - notifications?.toasts.addDanger({ - title: i18n.translate('workspace.update.failed', { - defaultMessage: 'Failed to update workspace', - }), - text: error instanceof Error ? error.message : JSON.stringify(error), - }); - return; - } - }, - [notifications?.toasts, currentWorkspace, http, application, workspaceClient] - ); - - useEffect(() => { - setCurrentWorkspaceFormData(getFormDataFromWorkspace(currentWorkspace)); - }, [currentWorkspace]); - - if (!currentWorkspaceFormData) { - return null; - } - - return ( - - - {!props.hideTitle ? : null} - - - {application && ( - - )} - - - - ); -}; diff --git a/src/plugins/workspace/public/components/workspace_updater_app.tsx b/src/plugins/workspace/public/components/workspace_updater_app.tsx deleted file mode 100644 index ab106b5c4b7a..000000000000 --- a/src/plugins/workspace/public/components/workspace_updater_app.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import React, { useEffect } from 'react'; -import { I18nProvider } from '@osd/i18n/react'; -import { i18n } from '@osd/i18n'; -import { useOpenSearchDashboards } from '../../../opensearch_dashboards_react/public'; -import { WorkspaceUpdater, WorkspaceUpdaterProps } from './workspace_updater'; - -export const WorkspaceUpdaterApp = (props: WorkspaceUpdaterProps) => { - const { - services: { chrome }, - } = useOpenSearchDashboards(); - - /** - * set breadcrumbs to chrome - */ - useEffect(() => { - chrome?.setBreadcrumbs([ - { - text: i18n.translate('workspace.workspaceUpdateTitle', { - defaultMessage: 'Update workspace', - }), - }, - ]); - }, [chrome]); - - return ( - - - - ); -}; diff --git a/src/plugins/workspace/public/components/workspace_use_case_overview_app.tsx b/src/plugins/workspace/public/components/workspace_use_case_overview_app.tsx new file mode 100644 index 000000000000..dcdcee22b729 --- /dev/null +++ b/src/plugins/workspace/public/components/workspace_use_case_overview_app.tsx @@ -0,0 +1,38 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useEffect } from 'react'; +import { I18nProvider } from '@osd/i18n/react'; +import { useObservable } from 'react-use'; +import { EuiBreadcrumb } from '@elastic/eui'; +import { useOpenSearchDashboards } from '../../../opensearch_dashboards_react/public'; +import { Services } from '../types'; + +interface WorkspaceUseCaseOverviewProps { + pageId: string; +} + +export const WorkspaceUseCaseOverviewApp = (props: WorkspaceUseCaseOverviewProps) => { + const { + services: { contentManagement, workspaces, chrome }, + } = useOpenSearchDashboards(); + + const currentWorkspace = useObservable(workspaces.currentWorkspace$); + + useEffect(() => { + const breadcrumbs: EuiBreadcrumb[] = [ + { + text: currentWorkspace?.name, + }, + ]; + chrome.setBreadcrumbs(breadcrumbs); + }, [chrome, currentWorkspace]); + + const pageId = props.pageId; + + return ( + {contentManagement ? contentManagement.renderPage(pageId) : null} + ); +}; diff --git a/src/plugins/workspace/public/mocks.ts b/src/plugins/workspace/public/mocks.ts new file mode 100644 index 000000000000..2f74bcf64835 --- /dev/null +++ b/src/plugins/workspace/public/mocks.ts @@ -0,0 +1,21 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { BehaviorSubject } from 'rxjs'; +import { WORKSPACE_USE_CASES } from '../common/constants'; + +export const createMockedRegisteredUseCases = () => + [ + WORKSPACE_USE_CASES.observability, + WORKSPACE_USE_CASES['security-analytics'], + WORKSPACE_USE_CASES.essentials, + WORKSPACE_USE_CASES.search, + ].map((item) => ({ + ...item, + features: item.features.map((id) => ({ id })), + })); + +export const createMockedRegisteredUseCases$ = () => + new BehaviorSubject(createMockedRegisteredUseCases()); diff --git a/src/plugins/workspace/public/plugin.test.ts b/src/plugins/workspace/public/plugin.test.ts index bda4ac1a1dc4..cf0f67f89829 100644 --- a/src/plugins/workspace/public/plugin.test.ts +++ b/src/plugins/workspace/public/plugin.test.ts @@ -5,22 +5,37 @@ import { BehaviorSubject, Observable, Subscriber } from 'rxjs'; import { waitFor } from '@testing-library/dom'; -import { ChromeBreadcrumb } from 'opensearch-dashboards/public'; -import { workspaceClientMock, WorkspaceClientMock } from './workspace_client.mock'; +import { first } from 'rxjs/operators'; + import { applicationServiceMock, chromeServiceMock, coreMock } from '../../../core/public/mocks'; -import { WorkspacePlugin } from './plugin'; -import { WORKSPACE_FATAL_ERROR_APP_ID, WORKSPACE_OVERVIEW_APP_ID } from '../common/constants'; +import { + ChromeBreadcrumb, + NavGroupStatus, + DEFAULT_NAV_GROUPS, + AppNavLinkStatus, +} from '../../../core/public'; +import { WORKSPACE_FATAL_ERROR_APP_ID, WORKSPACE_DETAIL_APP_ID } from '../common/constants'; import { savedObjectsManagementPluginMock } from '../../saved_objects_management/public/mocks'; import { managementPluginMock } from '../../management/public/mocks'; +import { UseCaseService } from './services/use_case_service'; +import { workspaceClientMock, WorkspaceClientMock } from './workspace_client.mock'; +import { WorkspacePlugin, WorkspacePluginStartDeps } from './plugin'; +import { contentManagementPluginMocks } from '../../content_management/public'; + +// Expect 6 app registrations: create, fatal error, detail, initial, navigation, and list apps. +const registrationAppNumber = 6; describe('Workspace plugin', () => { - const getSetupMock = () => ({ - ...coreMock.createSetup(), - }); + const mockDependencies: WorkspacePluginStartDeps = { + contentManagement: contentManagementPluginMocks.createStartContract(), + }; + const getSetupMock = () => coreMock.createSetup(); + beforeEach(() => { WorkspaceClientMock.mockClear(); Object.values(workspaceClientMock).forEach((item) => item.mockClear()); }); + it('#setup', async () => { const setupMock = getSetupMock(); const savedObjectManagementSetupMock = savedObjectsManagementPluginMock.createSetupContract(); @@ -29,7 +44,7 @@ describe('Workspace plugin', () => { savedObjectsManagement: savedObjectManagementSetupMock, management: managementPluginMock.createSetupContract(), }); - expect(setupMock.application.register).toBeCalledTimes(5); + expect(setupMock.application.register).toBeCalledTimes(registrationAppNumber); expect(WorkspaceClientMock).toBeCalledTimes(1); expect(savedObjectManagementSetupMock.columns.register).toBeCalledTimes(1); }); @@ -39,10 +54,10 @@ describe('Workspace plugin', () => { const setupMock = getSetupMock(); const coreStart = coreMock.createStart(); await workspacePlugin.setup(setupMock, {}); - workspacePlugin.start(coreStart); + workspacePlugin.start(coreStart, mockDependencies); coreStart.workspaces.currentWorkspaceId$.next('foo'); expect(coreStart.savedObjects.client.setCurrentWorkspace).toHaveBeenCalledWith('foo'); - expect(setupMock.application.register).toBeCalledTimes(5); + expect(setupMock.application.register).toBeCalledTimes(registrationAppNumber); expect(WorkspaceClientMock).toBeCalledTimes(1); expect(workspaceClientMock.enterWorkspace).toBeCalledTimes(0); }); @@ -79,10 +94,10 @@ describe('Workspace plugin', () => { await workspacePlugin.setup(setupMock, { management: managementPluginMock.createSetupContract(), }); - expect(setupMock.application.register).toBeCalledTimes(5); + expect(setupMock.application.register).toBeCalledTimes(registrationAppNumber); expect(WorkspaceClientMock).toBeCalledTimes(1); expect(workspaceClientMock.enterWorkspace).toBeCalledWith('workspaceId'); - expect(setupMock.getStartServices).toBeCalledTimes(1); + expect(setupMock.getStartServices).toBeCalledTimes(2); await waitFor( () => { expect(applicationStartMock.navigateToApp).toBeCalledWith(WORKSPACE_FATAL_ERROR_APP_ID, { @@ -115,6 +130,7 @@ describe('Workspace plugin', () => { }); const setupMock = getSetupMock(); const applicationStartMock = applicationServiceMock.createStartContract(); + const chromeStartMock = chromeServiceMock.createStartContract(); let currentAppIdSubscriber: Subscriber | undefined; setupMock.getStartServices.mockImplementation(() => { return Promise.resolve([ @@ -125,6 +141,7 @@ describe('Workspace plugin', () => { currentAppIdSubscriber = subscriber; }), }, + chrome: chromeStartMock, }, {}, {}, @@ -136,20 +153,174 @@ describe('Workspace plugin', () => { management: managementPluginMock.createSetupContract(), }); currentAppIdSubscriber?.next(WORKSPACE_FATAL_ERROR_APP_ID); - expect(applicationStartMock.navigateToApp).toBeCalledWith(WORKSPACE_OVERVIEW_APP_ID); + expect(applicationStartMock.navigateToApp).toBeCalledWith(WORKSPACE_DETAIL_APP_ID); windowSpy.mockRestore(); }); - it('#setup register workspace dropdown menu when setup', async () => { + it('#setup should register workspace list with a visible application and register to settingsAndSetup nav group', async () => { + const setupMock = coreMock.createSetup(); + setupMock.chrome.navGroup.getNavGroupEnabled.mockReturnValue(true); + const workspacePlugin = new WorkspacePlugin(); + await workspacePlugin.setup(setupMock, {}); + + expect(setupMock.application.register).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'workspace_list', + navLinkStatus: AppNavLinkStatus.visible, + }) + ); + expect(setupMock.chrome.navGroup.addNavLinksToGroup).toHaveBeenCalledWith( + DEFAULT_NAV_GROUPS.settingsAndSetup, + expect.arrayContaining([ + { + id: 'workspace_list', + order: 350, + title: 'Workspaces', + }, + ]) + ); + }); + + it('#setup should register workspace detail with a hidden application and not register to all nav group', async () => { const setupMock = coreMock.createSetup(); + setupMock.chrome.navGroup.getNavGroupEnabled.mockReturnValue(true); + const workspacePlugin = new WorkspacePlugin(); + await workspacePlugin.setup(setupMock, {}); + + expect(setupMock.application.register).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'workspace_detail', + }) + ); + + // not register to all nav group + expect(setupMock.chrome.navGroup.addNavLinksToGroup).not.toHaveBeenCalledWith( + DEFAULT_NAV_GROUPS.all, + expect.arrayContaining([ + { + id: 'workspace_detail', + title: 'Overview', + order: 100, + }, + ]) + ); + }); + + it('#setup should register workspace initial with a visible application', async () => { + const setupMock = coreMock.createSetup(); + const workspacePlugin = new WorkspacePlugin(); + await workspacePlugin.setup(setupMock, {}); + + expect(setupMock.application.register).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'workspace_initial', + navLinkStatus: AppNavLinkStatus.hidden, + }) + ); + }); + + it('#setup should register registerCollapsibleNavHeader when new left nav is turned on', async () => { + const setupMock = coreMock.createSetup(); + let collapsibleNavHeaderImplementation = () => null; + setupMock.chrome.navGroup.getNavGroupEnabled.mockReturnValue(true); + setupMock.chrome.registerCollapsibleNavHeader.mockImplementation( + (func) => (collapsibleNavHeaderImplementation = func) + ); + const workspacePlugin = new WorkspacePlugin(); + await workspacePlugin.setup(setupMock, {}); + expect(collapsibleNavHeaderImplementation()).toEqual(null); + const startMock = coreMock.createStart(); + await workspacePlugin.start(startMock, mockDependencies); + expect(collapsibleNavHeaderImplementation()).not.toEqual(null); + }); + + it('#setup should register workspace essential use case when new home is disabled', async () => { + const setupMock = { + ...coreMock.createSetup(), + chrome: { + ...coreMock.createSetup().chrome, + navGroup: { + ...coreMock.createSetup().chrome.navGroup, + getNavGroupEnabled: jest.fn().mockReturnValue(false), + }, + }, + }; const workspacePlugin = new WorkspacePlugin(); await workspacePlugin.setup(setupMock, { - management: managementPluginMock.createSetupContract(), + contentManagement: { + registerPage: jest.fn(), + }, + }); + + expect(setupMock.application.register).not.toHaveBeenCalledWith( + expect.objectContaining({ + id: 'essential_overview', + }) + ); + expect(setupMock.application.register).not.toHaveBeenCalledWith( + expect.objectContaining({ + id: 'analytics_all_overview', + }) + ); + }); + + it('#setup should register workspace essential use case when new nav is enabled', async () => { + const setupMock = { + ...coreMock.createSetup(), + chrome: { + ...coreMock.createSetup().chrome, + navGroup: { + ...coreMock.createSetup().chrome.navGroup, + getNavGroupEnabled: jest.fn().mockReturnValue(true), + }, + }, + }; + const workspacePlugin = new WorkspacePlugin(); + await workspacePlugin.setup(setupMock, { + contentManagement: { + registerPage: jest.fn(), + }, + }); + + expect(setupMock.application.register).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'essentials_overview', + }) + ); + }); + + it('#setup should register workspace analytics(All) use case when new nav is enabled', async () => { + const setupMock = { + ...coreMock.createSetup(), + chrome: { + ...coreMock.createSetup().chrome, + navGroup: { + ...coreMock.createSetup().chrome.navGroup, + getNavGroupEnabled: jest.fn().mockReturnValue(true), + }, + }, + }; + const workspacePlugin = new WorkspacePlugin(); + await workspacePlugin.setup(setupMock, { + contentManagement: { + registerPage: jest.fn(), + }, }); - expect(setupMock.chrome.registerCollapsibleNavHeader).toBeCalledTimes(1); }); - it('#start add workspace overview page to breadcrumbs when start', async () => { + it('#setup should register workspace navigation with a visible application', async () => { + const setupMock = coreMock.createSetup(); + const workspacePlugin = new WorkspacePlugin(); + await workspacePlugin.setup(setupMock, {}); + expect(setupMock.application.register).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'workspace_navigation', + navLinkStatus: AppNavLinkStatus.hidden, + }) + ); + }); + + it('#start add workspace detail page to breadcrumbs when start', async () => { const startMock = coreMock.createStart(); const workspaceObject = { id: 'foo', @@ -159,7 +330,7 @@ describe('Workspace plugin', () => { const breadcrumbs = new BehaviorSubject([{ text: 'dashboards' }]); startMock.chrome.getBreadcrumbs$.mockReturnValue(breadcrumbs); const workspacePlugin = new WorkspacePlugin(); - workspacePlugin.start(startMock); + workspacePlugin.start(startMock, mockDependencies); expect(startMock.chrome.setBreadcrumbs).toBeCalledWith( expect.arrayContaining([ expect.objectContaining({ @@ -172,7 +343,7 @@ describe('Workspace plugin', () => { ); }); - it('#start do not add workspace overview page to breadcrumbs when already exists', async () => { + it('#start do not add workspace detail page to breadcrumbs when already exists', async () => { const startMock = coreMock.createStart(); const workspaceObject = { id: 'foo', @@ -185,7 +356,160 @@ describe('Workspace plugin', () => { ]); startMock.chrome.getBreadcrumbs$.mockReturnValue(breadcrumbs); const workspacePlugin = new WorkspacePlugin(); - workspacePlugin.start(startMock); + workspacePlugin.start(startMock, mockDependencies); expect(startMock.chrome.setBreadcrumbs).not.toHaveBeenCalled(); }); + + it('#start should register workspace list card into new home page', async () => { + const startMock = coreMock.createStart(); + startMock.chrome.navGroup.getNavGroupEnabled.mockReturnValue(true); + const workspacePlugin = new WorkspacePlugin(); + workspacePlugin.start(startMock, mockDependencies); + expect(mockDependencies.contentManagement.registerContentProvider).toHaveBeenCalledWith( + expect.objectContaining({ + id: 'workspace_list_card_home', + }) + ); + }); + + it('#start should call navGroupUpdater$.next after currentWorkspace set', async () => { + const workspacePlugin = new WorkspacePlugin(); + const setupMock = getSetupMock(); + const coreStart = coreMock.createStart(); + await workspacePlugin.setup(setupMock, {}); + + expect(setupMock.chrome.navGroup.registerNavGroupUpdater).toHaveBeenCalled(); + const navGroupUpdater$ = setupMock.chrome.navGroup.registerNavGroupUpdater.mock.calls[0][0]; + + expect(navGroupUpdater$).toBeTruthy(); + jest.spyOn(navGroupUpdater$, 'next'); + + expect(navGroupUpdater$.next).not.toHaveBeenCalled(); + workspacePlugin.start(coreStart, mockDependencies); + + waitFor(() => { + expect(navGroupUpdater$.next).toHaveBeenCalled(); + }); + }); + + it('#start register workspace dropdown menu at left navigation bottom when start', async () => { + const coreStart = coreMock.createStart(); + coreStart.chrome.navGroup.getNavGroupEnabled.mockReturnValue(true); + const workspacePlugin = new WorkspacePlugin(); + workspacePlugin.start(coreStart, mockDependencies); + + expect(coreStart.chrome.navControls.registerLeftBottom).toBeCalledTimes(1); + }); + + it('#start should not update systematic use case features after currentWorkspace set', async () => { + const registeredUseCases$ = new BehaviorSubject([ + { + id: 'foo', + title: 'Foo', + features: [{ id: 'system-feature', title: 'System feature' }], + systematic: true, + description: '', + }, + ]); + jest.spyOn(UseCaseService.prototype, 'start').mockImplementationOnce(() => ({ + getRegisteredUseCases$: jest.fn(() => registeredUseCases$), + })); + const workspacePlugin = new WorkspacePlugin(); + const setupMock = getSetupMock(); + const coreStart = coreMock.createStart(); + await workspacePlugin.setup(setupMock, {}); + const workspaceObject = { + id: 'foo', + name: 'bar', + features: ['baz'], + }; + coreStart.workspaces.currentWorkspace$.next(workspaceObject); + + const appUpdater$ = setupMock.application.registerAppUpdater.mock.calls[0][0]; + + workspacePlugin.start(coreStart, mockDependencies); + + const appUpdater = await appUpdater$.pipe(first()).toPromise(); + + expect(appUpdater({ id: 'system-feature', title: '', mount: () => () => {} })).toBeUndefined(); + }); + + it('#start should update nav group status after currentWorkspace set', async () => { + const workspacePlugin = new WorkspacePlugin(); + const setupMock = getSetupMock(); + const coreStart = coreMock.createStart(); + await workspacePlugin.setup(setupMock, {}); + const workspaceObject = { + id: 'foo', + name: 'bar', + features: ['use-case-foo'], + }; + coreStart.workspaces.currentWorkspace$.next(workspaceObject); + + const navGroupUpdater$ = setupMock.chrome.navGroup.registerNavGroupUpdater.mock.calls[0][0]; + + workspacePlugin.start(coreStart, mockDependencies); + + const navGroupUpdater = await navGroupUpdater$.pipe(first()).toPromise(); + + expect(navGroupUpdater({ id: 'foo' })).toBeUndefined(); + expect(navGroupUpdater({ id: 'bar' })).toEqual({ + status: NavGroupStatus.Hidden, + }); + }); + + it('#stop should call unregisterNavGroupUpdater', async () => { + const workspacePlugin = new WorkspacePlugin(); + const setupMock = getSetupMock(); + const unregisterNavGroupUpdater = jest.fn(); + setupMock.chrome.navGroup.registerNavGroupUpdater.mockReturnValueOnce( + unregisterNavGroupUpdater + ); + await workspacePlugin.setup(setupMock, {}); + + workspacePlugin.stop(); + + expect(unregisterNavGroupUpdater).toHaveBeenCalled(); + }); + + it('#stop should not call appUpdater$.next anymore', async () => { + const registeredUseCases$ = new BehaviorSubject([ + { + id: 'foo', + title: 'Foo', + features: ['system-feature'], + systematic: true, + description: '', + }, + ]); + jest.spyOn(UseCaseService.prototype, 'start').mockImplementationOnce(() => ({ + getRegisteredUseCases$: jest.fn(() => registeredUseCases$), + })); + const workspacePlugin = new WorkspacePlugin(); + const setupMock = getSetupMock(); + const coreStart = coreMock.createStart(); + await workspacePlugin.setup(setupMock, {}); + const workspaceObject = { + id: 'foo', + name: 'bar', + features: ['baz'], + }; + coreStart.workspaces.currentWorkspace$.next(workspaceObject); + + const appUpdater$ = setupMock.application.registerAppUpdater.mock.calls[0][0]; + const appUpdaterChangeMock = jest.fn(); + appUpdater$.subscribe(appUpdaterChangeMock); + + workspacePlugin.start(coreStart, mockDependencies); + + // Wait for filterNav been executed + await new Promise(setImmediate); + + expect(appUpdaterChangeMock).toHaveBeenCalledTimes(2); + + workspacePlugin.stop(); + + registeredUseCases$.next([]); + expect(appUpdaterChangeMock).toHaveBeenCalledTimes(2); + }); }); diff --git a/src/plugins/workspace/public/plugin.ts b/src/plugins/workspace/public/plugin.ts index 8c6bd2ddc0e6..3e04e61a8404 100644 --- a/src/plugins/workspace/public/plugin.ts +++ b/src/plugins/workspace/public/plugin.ts @@ -6,7 +6,8 @@ import { BehaviorSubject, combineLatest, Subscription } from 'rxjs'; import React from 'react'; import { i18n } from '@osd/i18n'; -import { first } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; +import { EuiIcon, EuiPanel } from '@elastic/eui'; import { Plugin, CoreStart, @@ -15,45 +16,95 @@ import { AppNavLinkStatus, AppUpdater, AppStatus, - PublicAppInfo, ChromeBreadcrumb, WorkspaceAvailability, + ChromeNavGroupUpdater, + NavGroupStatus, + DEFAULT_NAV_GROUPS, + NavGroupType, + ALL_USE_CASE_ID, } from '../../../core/public'; import { WORKSPACE_FATAL_ERROR_APP_ID, - WORKSPACE_OVERVIEW_APP_ID, + WORKSPACE_DETAIL_APP_ID, WORKSPACE_CREATE_APP_ID, - WORKSPACE_UPDATE_APP_ID, WORKSPACE_LIST_APP_ID, + WORKSPACE_USE_CASES, + WORKSPACE_INITIAL_APP_ID, + WORKSPACE_NAVIGATION_APP_ID, } from '../common/constants'; import { getWorkspaceIdFromUrl } from '../../../core/public/utils'; -import { Services } from './types'; +import { Services, WorkspaceUseCase } from './types'; import { WorkspaceClient } from './workspace_client'; import { SavedObjectsManagementPluginSetup } from '../../../plugins/saved_objects_management/public'; import { ManagementSetup } from '../../../plugins/management/public'; +import { + ANALYTICS_ALL_OVERVIEW_PAGE_ID, + ContentManagementPluginSetup, + ContentManagementPluginStart, + ESSENTIAL_OVERVIEW_PAGE_ID, +} from '../../../plugins/content_management/public'; import { WorkspaceMenu } from './components/workspace_menu/workspace_menu'; import { getWorkspaceColumn } from './components/workspace_column'; -import { filterWorkspaceConfigurableApps, isAppAccessibleInWorkspace } from './utils'; +import { DataSourceManagementPluginSetup } from '../../../plugins/data_source_management/public'; +import { + enrichBreadcrumbsWithWorkspace, + filterWorkspaceConfigurableApps, + getFirstUseCaseOfFeatureConfigs, + getUseCaseUrl, + isAppAccessibleInWorkspace, + isNavGroupInFeatureConfigs, +} from './utils'; +import { recentWorkspaceManager } from './recent_workspace_manager'; +import { toMountPoint } from '../../opensearch_dashboards_react/public'; +import { UseCaseService } from './services/use_case_service'; +import { WorkspaceListCard } from './components/service_card'; +import { UseCaseFooter } from './components/home_get_start_card'; +import { NavigationPublicPluginStart } from '../../../plugins/navigation/public'; +import { WorkspacePickerContent } from './components/workspace_picker_content/workspace_picker_content'; +import { HOME_CONTENT_AREAS } from '../../../plugins/content_management/public'; +import { + registerEssentialOverviewContent, + setEssentialOverviewSection, +} from './components/use_case_overview'; +import { + registerAnalyticsAllOverviewContent, + setAnalyticsAllOverviewSection, +} from './components/use_case_overview/setup_overview'; type WorkspaceAppType = ( params: AppMountParameters, services: Services, - props: Record + props: Record & { registeredUseCases$: BehaviorSubject } ) => () => void; interface WorkspacePluginSetupDeps { savedObjectsManagement?: SavedObjectsManagementPluginSetup; management?: ManagementSetup; + dataSourceManagement?: DataSourceManagementPluginSetup; + contentManagement?: ContentManagementPluginSetup; +} + +export interface WorkspacePluginStartDeps { + contentManagement: ContentManagementPluginStart; + navigation: NavigationPublicPluginStart; } -export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> { +export class WorkspacePlugin + implements Plugin<{}, {}, WorkspacePluginSetupDeps, WorkspacePluginStartDeps> { private coreStart?: CoreStart; private currentWorkspaceSubscription?: Subscription; private breadcrumbsSubscription?: Subscription; private currentWorkspaceIdSubscription?: Subscription; private managementCurrentWorkspaceIdSubscription?: Subscription; private appUpdater$ = new BehaviorSubject(() => undefined); - private workspaceConfigurableApps$ = new BehaviorSubject([]); + private navGroupUpdater$ = new BehaviorSubject(() => undefined); + private unregisterNavGroupUpdater?: () => void; + private registeredUseCases$ = new BehaviorSubject([]); + private registeredUseCasesUpdaterSubscription?: Subscription; + private workspaceAndUseCasesCombineSubscription?: Subscription; + private useCase = new UseCaseService(); + private _changeSavedObjectCurrentWorkspace() { if (this.coreStart) { return this.coreStart.workspaces.currentWorkspaceId$.subscribe((currentWorkspaceId) => { @@ -70,19 +121,37 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> */ private filterNavLinks = (core: CoreStart) => { const currentWorkspace$ = core.workspaces.currentWorkspace$; - this.currentWorkspaceSubscription?.unsubscribe(); - this.currentWorkspaceSubscription = currentWorkspace$.subscribe((currentWorkspace) => { + this.workspaceAndUseCasesCombineSubscription?.unsubscribe(); + this.workspaceAndUseCasesCombineSubscription = combineLatest([ + currentWorkspace$, + this.registeredUseCases$, + ]).subscribe(([currentWorkspace, registeredUseCases]) => { if (currentWorkspace) { + const workspaceUseCase = currentWorkspace.features + ? getFirstUseCaseOfFeatureConfigs(currentWorkspace.features) + : undefined; + this.appUpdater$.next((app) => { - if (isAppAccessibleInWorkspace(app, currentWorkspace)) { - return; + // essential use overview is only available in essential workspace + if (app.id === ESSENTIAL_OVERVIEW_PAGE_ID && workspaceUseCase === ALL_USE_CASE_ID) { + return { status: AppStatus.inaccessible }; } + if (isAppAccessibleInWorkspace(app, currentWorkspace, registeredUseCases)) { + return; + } if (app.status === AppStatus.inaccessible) { return; } - + if ( + registeredUseCases.some( + (useCase) => + useCase.systematic && useCase.features.some((feature) => feature.id === app.id) + ) + ) { + return; + } /** * Change the app to `inaccessible` if it is not configured in the workspace * If trying to access such app, an "Application Not Found" page will be displayed @@ -91,23 +160,45 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> }); } }); + + this.currentWorkspaceSubscription?.unsubscribe(); + this.currentWorkspaceSubscription = currentWorkspace$.subscribe((currentWorkspace) => { + if (currentWorkspace) { + this.navGroupUpdater$.next((navGroup) => { + /** + * The following logic determines whether a navigation group should be hidden or not based on the workspace's feature configurations. + * It checks the following conditions: + * 1. The navigation group is not a system-level group. + * 2. The current workspace has feature configurations set up. + * 3. The current navigation group is not included in the feature configurations of the workspace. + * + * If all these conditions are true, it means that the navigation group should be hidden. + */ + if ( + navGroup.type !== NavGroupType.SYSTEM && + currentWorkspace.features && + !isNavGroupInFeatureConfigs(navGroup.id, currentWorkspace.features) + ) { + return { + status: NavGroupStatus.Hidden, + }; + } + }); + } + }); }; /** - * Initiate an observable with the value of all applications which can be configured by workspace + * Return an observable with the value of all applications which can be configured by workspace */ - private setWorkspaceConfigurableApps = async (core: CoreStart) => { - const allApps = await new Promise((resolve) => { - core.application.applications$.pipe(first()).subscribe((apps) => { - resolve([...apps.values()]); - }); - }); - const availableApps = filterWorkspaceConfigurableApps(allApps); - this.workspaceConfigurableApps$.next(availableApps); + private getWorkspaceConfigurableApps$ = (core: CoreStart) => { + return core.application.applications$.pipe( + map((apps) => filterWorkspaceConfigurableApps([...apps.values()])) + ); }; /** - * If workspace is enabled and user has entered workspace, hide advance settings and dataSource menu by disabling the corresponding apps. + * If workspace is enabled and user has entered workspace, hide advance settings by disabling the corresponding apps. */ private disableManagementApps(core: CoreSetup, management: ManagementSetup) { const currentWorkspaceId$ = core.workspaces.currentWorkspaceId$; @@ -116,7 +207,7 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> this.managementCurrentWorkspaceIdSubscription = currentWorkspaceId$.subscribe( (currentWorkspaceId) => { if (currentWorkspaceId) { - ['settings', 'dataSources'].forEach((appId) => + ['settings'].forEach((appId) => management.sections.section.opensearchDashboards.getApp(appId)?.disable() ); } @@ -125,7 +216,7 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> } /** - * Add workspace overview page to breadcrumbs + * Add workspace detail page to breadcrumbs * @param core CoreStart * @private */ @@ -142,7 +233,7 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> const workspaceBreadcrumb: ChromeBreadcrumb = { text: currentWorkspace.name, onClick: () => { - core.application.navigateToApp(WORKSPACE_OVERVIEW_APP_ID); + core.application.navigateToApp(WORKSPACE_DETAIL_APP_ID); }, }; const homeBreadcrumb: ChromeBreadcrumb = { @@ -160,12 +251,28 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> } public async setup( - core: CoreSetup, - { savedObjectsManagement, management }: WorkspacePluginSetupDeps + core: CoreSetup, + { + savedObjectsManagement, + management, + dataSourceManagement, + contentManagement, + }: WorkspacePluginSetupDeps ) { const workspaceClient = new WorkspaceClient(core.http, core.workspaces); await workspaceClient.init(); + core.workspaces.setClient(workspaceClient); + + this.useCase.setup({ + chrome: core.chrome, + getStartServices: core.getStartServices, + workspaces: core.workspaces, + }); + core.application.registerAppUpdater(this.appUpdater$); + this.unregisterNavGroupUpdater = core.chrome.navGroup.registerNavGroupUpdater( + this.navGroupUpdater$ + ); // Hide advance settings and dataSource menus and disable in setup if (management) { @@ -206,23 +313,28 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> const [{ application }] = await core.getStartServices(); const currentAppIdSubscription = application.currentAppId$.subscribe((currentAppId) => { if (currentAppId === WORKSPACE_FATAL_ERROR_APP_ID) { - application.navigateToApp(WORKSPACE_OVERVIEW_APP_ID); + application.navigateToApp(WORKSPACE_DETAIL_APP_ID); } currentAppIdSubscription.unsubscribe(); }); + // Add workspace id to recent workspaces. + recentWorkspaceManager.addRecentWorkspace(workspaceId); })(); } } const mountWorkspaceApp = async (params: AppMountParameters, renderApp: WorkspaceAppType) => { - const [coreStart] = await core.getStartServices(); + const [coreStart, { navigation }] = await core.getStartServices(); + const services = { ...coreStart, workspaceClient, + dataSourceManagement, + navigationUI: navigation.ui, }; return renderApp(params, services, { - workspaceConfigurableApps$: this.workspaceConfigurableApps$, + registeredUseCases$: this.registeredUseCases$, }); }; @@ -230,7 +342,7 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> core.application.register({ id: WORKSPACE_CREATE_APP_ID, title: i18n.translate('workspace.settings.workspaceCreate', { - defaultMessage: 'Create Workspace', + defaultMessage: 'Create a workspace', }), navLinkStatus: AppNavLinkStatus.hidden, async mount(params: AppMountParameters) { @@ -252,50 +364,74 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> }); /** - * register workspace overview page + * register workspace detail page */ core.application.register({ - id: WORKSPACE_OVERVIEW_APP_ID, - title: i18n.translate('workspace.settings.workspaceOverview', { - defaultMessage: 'Workspace Overview', + id: WORKSPACE_DETAIL_APP_ID, + title: i18n.translate('workspace.settings.workspaceDetail', { + defaultMessage: 'Workspace Detail', }), - navLinkStatus: AppNavLinkStatus.hidden, async mount(params: AppMountParameters) { - const { renderOverviewApp } = await import('./application'); - return mountWorkspaceApp(params, renderOverviewApp); + const { renderDetailApp } = await import('./application'); + return mountWorkspaceApp(params, renderDetailApp); }, }); - /** - * register workspace update page - */ + // workspace initial page core.application.register({ - id: WORKSPACE_UPDATE_APP_ID, - title: i18n.translate('workspace.settings.workspaceUpdate', { - defaultMessage: 'Update Workspace', + id: WORKSPACE_INITIAL_APP_ID, + title: i18n.translate('workspace.settings.workspaceInitial', { + defaultMessage: 'Workspace Initial', }), navLinkStatus: AppNavLinkStatus.hidden, async mount(params: AppMountParameters) { - const { renderUpdaterApp } = await import('./application'); - return mountWorkspaceApp(params, renderUpdaterApp); + const { renderInitialApp } = await import('./application'); + return mountWorkspaceApp(params, renderInitialApp); }, + workspaceAvailability: WorkspaceAvailability.outsideWorkspace, }); - /** - * Register workspace dropdown selector on the top of left navigation menu - */ - core.chrome.registerCollapsibleNavHeader(() => { - if (!this.coreStart) { - return null; - } - return React.createElement(WorkspaceMenu, { coreStart: this.coreStart }); + const registeredUseCases$ = this.registeredUseCases$; + // register workspace navigation + core.application.register({ + id: WORKSPACE_NAVIGATION_APP_ID, + title: '', + chromeless: true, + navLinkStatus: AppNavLinkStatus.hidden, + async mount() { + const [coreStart] = await core.getStartServices(); + const { application, http, workspaces } = coreStart; + const workspace = workspaces.currentWorkspace$.getValue(); + if (workspace) { + const availableUseCases = registeredUseCases$.getValue(); + const currentUseCase = availableUseCases.find( + (useCase) => useCase.id === getFirstUseCaseOfFeatureConfigs(workspace?.features ?? []) + ); + const useCaseUrl = getUseCaseUrl(currentUseCase, workspace, application, http); + application.navigateToUrl(useCaseUrl); + } else { + application.navigateToApp('home'); + } + return () => {}; + }, + workspaceAvailability: WorkspaceAvailability.insideWorkspace, }); // workspace list core.application.register({ id: WORKSPACE_LIST_APP_ID, title: '', - navLinkStatus: AppNavLinkStatus.hidden, + /** + * Nav link status should be visible when nav group enabled. + * The page should be refreshed and all applications need to register again + * after nav group enabled changed. + */ + navLinkStatus: core.chrome.navGroup.getNavGroupEnabled() + ? AppNavLinkStatus.visible + : AppNavLinkStatus.hidden, + description: i18n.translate('workspace.workspaceList.description', { + defaultMessage: 'Organize collaborative projects in use-case-specific workspaces.', + }), async mount(params: AppMountParameters) { const { renderListApp } = await import('./application'); return mountWorkspaceApp(params, renderListApp); @@ -303,33 +439,234 @@ export class WorkspacePlugin implements Plugin<{}, {}, WorkspacePluginSetupDeps> workspaceAvailability: WorkspaceAvailability.outsideWorkspace, }); + if (core.chrome.navGroup.getNavGroupEnabled() && contentManagement) { + // workspace essential use case overview + core.application.register({ + id: ESSENTIAL_OVERVIEW_PAGE_ID, + title: '', + async mount(params: AppMountParameters) { + const { renderUseCaseOverviewApp } = await import('./application'); + const [ + coreStart, + { contentManagement: contentManagementStart }, + ] = await core.getStartServices(); + const services = { + ...coreStart, + workspaceClient, + dataSourceManagement, + contentManagement: contentManagementStart, + }; + + return renderUseCaseOverviewApp(params, services, ESSENTIAL_OVERVIEW_PAGE_ID); + }, + workspaceAvailability: WorkspaceAvailability.insideWorkspace, + }); + + core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.essentials, [ + { + id: ESSENTIAL_OVERVIEW_PAGE_ID, + order: -1, + title: i18n.translate('workspace.nav.essential_overview.title', { + defaultMessage: 'Overview', + }), + }, + ]); + + // initial the page structure + setEssentialOverviewSection(contentManagement); + + // register workspace Analytics(all) use case overview app + core.application.register({ + id: ANALYTICS_ALL_OVERVIEW_PAGE_ID, + title: '', + async mount(params: AppMountParameters) { + const { renderUseCaseOverviewApp } = await import('./application'); + const [ + coreStart, + { contentManagement: contentManagementStart }, + ] = await core.getStartServices(); + const services = { + ...coreStart, + workspaceClient, + dataSourceManagement, + contentManagement: contentManagementStart, + }; + + return renderUseCaseOverviewApp(params, services, ANALYTICS_ALL_OVERVIEW_PAGE_ID); + }, + workspaceAvailability: WorkspaceAvailability.insideWorkspace, + }); + + core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.all, [ + { + id: ANALYTICS_ALL_OVERVIEW_PAGE_ID, + order: -1, + title: i18n.translate('workspace.nav.analyticsAll_overview.title', { + defaultMessage: 'Overview', + }), + }, + ]); + + // initial the page structure + setAnalyticsAllOverviewSection(contentManagement); + } + /** * register workspace column into saved objects table */ savedObjectsManagement?.columns.register(getWorkspaceColumn(core)); + /** + * Add workspace list to settings and setup group + */ + core.chrome.navGroup.addNavLinksToGroup(DEFAULT_NAV_GROUPS.settingsAndSetup, [ + { + id: WORKSPACE_LIST_APP_ID, + order: 350, + title: i18n.translate('workspace.settings.workspaces', { + defaultMessage: 'Workspaces', + }), + }, + ]); + + if (core.chrome.navGroup.getNavGroupEnabled()) { + /** + * Show workspace picker content when outside of workspace and not in any nav group + */ + core.chrome.registerCollapsibleNavHeader(() => { + if (!this.coreStart) { + return null; + } + return React.createElement(EuiPanel, { + hasShadow: false, + hasBorder: false, + children: [ + React.createElement(WorkspacePickerContent, { + key: 'workspacePickerContent', + coreStart: this.coreStart, + registeredUseCases$: this.registeredUseCases$, + }), + ], + }); + }); + } + return {}; } - public start(core: CoreStart) { + private registerGetStartedCardToNewHome( + core: CoreStart, + contentManagement: ContentManagementPluginStart + ) { + const useCases = [ + WORKSPACE_USE_CASES.observability, + WORKSPACE_USE_CASES['security-analytics'], + WORKSPACE_USE_CASES.search, + WORKSPACE_USE_CASES.essentials, + ]; + + useCases.forEach((useCase, index) => { + contentManagement.registerContentProvider({ + id: `home_get_start_${useCase.id}`, + getTargetArea: () => [HOME_CONTENT_AREAS.GET_STARTED], + getContent: () => ({ + id: useCase.id, + kind: 'card', + order: (index + 1) * 1000, + description: useCase.description, + title: useCase.title, + getIcon: () => React.createElement(EuiIcon, { size: 'xl', type: 'logoOpenSearch' }), + getFooter: () => + React.createElement(UseCaseFooter, { + useCaseId: useCase.id, + useCaseTitle: useCase.title, + core, + registeredUseCases$: this.registeredUseCases$, + }), + }), + }); + }); + } + + public start(core: CoreStart, { contentManagement, navigation }: WorkspacePluginStartDeps) { this.coreStart = core; this.currentWorkspaceIdSubscription = this._changeSavedObjectCurrentWorkspace(); - this.setWorkspaceConfigurableApps(core).then(() => { - // filter the nav links based on the current workspace - this.filterNavLinks(core); + const useCaseStart = this.useCase.start({ + chrome: core.chrome, + workspaceConfigurableApps$: this.getWorkspaceConfigurableApps$(core), }); - this.addWorkspaceToBreadcrumbs(core); + this.registeredUseCasesUpdaterSubscription = useCaseStart + .getRegisteredUseCases$() + .subscribe((registeredUseCases) => { + this.registeredUseCases$.next(registeredUseCases); + }); + + this.filterNavLinks(core); + + if (!core.chrome.navGroup.getNavGroupEnabled()) { + this.addWorkspaceToBreadcrumbs(core); + } else { + /** + * Register workspace dropdown selector on the left navigation bottom + */ + core.chrome.navControls.registerLeftBottom({ + order: 2, + mount: toMountPoint( + React.createElement(WorkspaceMenu, { + coreStart: core, + registeredUseCases$: this.registeredUseCases$, + }) + ), + }); + // register workspace list in home page + this.registerWorkspaceListToHome(core, contentManagement); + + // register get started card in new home page + this.registerGetStartedCardToNewHome(core, contentManagement); + + // set breadcrumbs enricher for workspace + this.breadcrumbsSubscription = enrichBreadcrumbsWithWorkspace(core); + + // register content to essential overview page + registerEssentialOverviewContent(contentManagement, core); + + // register content to analytics(All) overview page + registerAnalyticsAllOverviewContent(contentManagement, core); + } return {}; } + private registerWorkspaceListToHome( + core: CoreStart, + contentManagement: ContentManagementPluginStart + ) { + if (contentManagement) { + contentManagement.registerContentProvider({ + id: 'workspace_list_card_home', + getContent: () => ({ + id: 'workspace_list', + kind: 'custom', + order: 0, + width: 16, + render: () => React.createElement(WorkspaceListCard, { core }), + }), + getTargetArea: () => HOME_CONTENT_AREAS.SERVICE_CARDS, + }); + } + } + public stop() { this.currentWorkspaceSubscription?.unsubscribe(); this.currentWorkspaceIdSubscription?.unsubscribe(); this.managementCurrentWorkspaceIdSubscription?.unsubscribe(); this.breadcrumbsSubscription?.unsubscribe(); + this.unregisterNavGroupUpdater?.(); + this.registeredUseCasesUpdaterSubscription?.unsubscribe(); + this.workspaceAndUseCasesCombineSubscription?.unsubscribe(); + this.useCase.stop(); } } diff --git a/src/plugins/workspace/public/recent_workspace_manager.test.ts b/src/plugins/workspace/public/recent_workspace_manager.test.ts new file mode 100644 index 000000000000..16ea6291faef --- /dev/null +++ b/src/plugins/workspace/public/recent_workspace_manager.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { RecentWorkspaceManager } from './recent_workspace_manager'; + +describe('RecentWorkspaceManager', () => { + let recentWorkspaceManager: RecentWorkspaceManager; + + beforeEach(() => { + recentWorkspaceManager = RecentWorkspaceManager.getInstance(); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should be a singleton', () => { + const anotherInstance = RecentWorkspaceManager.getInstance(); + expect(recentWorkspaceManager).toBe(anotherInstance); + }); + + it('should add and get recent workspaces', () => { + recentWorkspaceManager.addRecentWorkspace('workspace1'); + recentWorkspaceManager.addRecentWorkspace('workspace2'); + + const recentWorkspaces = recentWorkspaceManager.getRecentWorkspaces(); + expect(recentWorkspaces.length).toEqual(2); + expect(recentWorkspaces[0].id).toEqual('workspace2'); + expect(recentWorkspaces[1].id).toEqual('workspace1'); + }); +}); diff --git a/src/plugins/workspace/public/recent_workspace_manager.ts b/src/plugins/workspace/public/recent_workspace_manager.ts new file mode 100644 index 000000000000..2bcadc72e1bd --- /dev/null +++ b/src/plugins/workspace/public/recent_workspace_manager.ts @@ -0,0 +1,51 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { PersistedLog } from '../../../core/public'; +import { RECENT_WORKSPACES_KEY } from '../common/constants'; + +export interface WorkspaceEntry { + id: string; + timestamp: number; +} + +export class RecentWorkspaceManager { + private static instance: RecentWorkspaceManager; + private recentWorkspaceLog: PersistedLog; + + private constructor() { + const customIsEqual = (oldItem: WorkspaceEntry, newItem: WorkspaceEntry) => { + return oldItem.id === newItem.id; + }; + this.recentWorkspaceLog = new PersistedLog(RECENT_WORKSPACES_KEY, { + maxLength: 10, + isEqual: customIsEqual, + }); + } + + // Singleton pattern to ensure only one instance is used + public static getInstance(): RecentWorkspaceManager { + if (!RecentWorkspaceManager.instance) { + RecentWorkspaceManager.instance = new RecentWorkspaceManager(); + } + return RecentWorkspaceManager.instance; + } + + public getRecentWorkspaces(): WorkspaceEntry[] { + return this.recentWorkspaceLog.get(); + } + + public addRecentWorkspace(newWorkspace: string): WorkspaceEntry[] { + const newEntry: WorkspaceEntry = { + id: newWorkspace, + timestamp: Date.now(), + }; + this.recentWorkspaceLog.add(newEntry); + return this.getRecentWorkspaces(); + } +} + +// Export the singleton instance +export const recentWorkspaceManager = RecentWorkspaceManager.getInstance(); diff --git a/src/plugins/workspace/public/services/use_case_service.test.ts b/src/plugins/workspace/public/services/use_case_service.test.ts new file mode 100644 index 000000000000..203cb48fe842 --- /dev/null +++ b/src/plugins/workspace/public/services/use_case_service.test.ts @@ -0,0 +1,214 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { BehaviorSubject } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { chromeServiceMock, coreMock } from '../../../../core/public/mocks'; +import { + ALL_USE_CASE_ID, + DEFAULT_APP_CATEGORIES, + DEFAULT_NAV_GROUPS, + NavGroupItemInMap, + NavGroupType, +} from '../../../../core/public'; +import { UseCaseService } from './use_case_service'; +import { waitFor } from '@testing-library/dom'; +import { WORKSPACE_DETAIL_APP_ID } from '../../common/constants'; + +const mockNavGroupsMap = { + system: { + id: 'system', + title: 'System', + description: 'System use case', + navLinks: [], + type: NavGroupType.SYSTEM, + }, + search: { + id: 'search', + title: 'Search', + description: 'Search use case', + navLinks: [{ id: 'searchRelevance', title: 'Search Relevance' }], + order: 2000, + }, + observability: { + id: 'observability', + title: 'Observability', + description: 'Observability description', + navLinks: [{ id: 'dashboards', title: 'Dashboards' }], + order: 1000, + }, +}; +const setupUseCaseStart = (options?: { navGroupEnabled?: boolean }) => { + const chrome = chromeServiceMock.createStartContract(); + const workspaceConfigurableApps$ = new BehaviorSubject([ + { id: 'searchRelevance', title: 'Search Relevance' }, + ]); + const navGroupsMap$ = new BehaviorSubject>(mockNavGroupsMap); + const useCase = new UseCaseService(); + + chrome.navGroup.getNavGroupEnabled.mockImplementation(() => options?.navGroupEnabled ?? true); + chrome.navGroup.getNavGroupsMap$.mockImplementation(() => navGroupsMap$); + + return { + chrome, + navGroupsMap$, + workspaceConfigurableApps$, + useCaseStart: useCase.start({ + chrome, + workspaceConfigurableApps$, + ...options, + }), + }; +}; + +describe('UseCaseService', () => { + describe('#setup', () => { + it('should add manage workspace category to current use case', async () => { + const useCaseService = new UseCaseService(); + const coreSetup = coreMock.createSetup(); + const navGroupMap$ = new BehaviorSubject>({}); + const coreStartMock = coreMock.createStart(); + coreSetup.getStartServices.mockResolvedValue([coreStartMock, {}, {}]); + coreStartMock.chrome.navGroup.getNavGroupsMap$.mockReturnValue(navGroupMap$); + useCaseService.setup(coreSetup); + const navGroupInfo = { + ...DEFAULT_NAV_GROUPS.all, + navLinks: [], + }; + navGroupMap$.next({ + [ALL_USE_CASE_ID]: navGroupInfo, + }); + coreSetup.workspaces.currentWorkspace$.next({ + id: ALL_USE_CASE_ID, + name: ALL_USE_CASE_ID, + features: [`use-case-${ALL_USE_CASE_ID}`], + }); + await waitFor(() => { + expect(coreSetup.chrome.navGroup.addNavLinksToGroup).toBeCalledWith(navGroupInfo, [ + { + id: 'dataSources', + category: DEFAULT_APP_CATEGORIES.manageWorkspace, + order: 100, + }, + { + id: 'indexPatterns', + category: DEFAULT_APP_CATEGORIES.manageWorkspace, + order: 200, + }, + { + id: 'objects', + category: DEFAULT_APP_CATEGORIES.manageWorkspace, + order: 300, + }, + { + id: WORKSPACE_DETAIL_APP_ID, + category: DEFAULT_APP_CATEGORIES.manageWorkspace, + order: 400, + title: 'Workspace settings', + }, + ]); + }); + }); + }); + describe('#start', () => { + it('should return built in use cases when nav group disabled', async () => { + const { useCaseStart } = setupUseCaseStart({ + navGroupEnabled: false, + }); + const useCases = await useCaseStart.getRegisteredUseCases$().pipe(first()).toPromise(); + + expect(useCases).toHaveLength(2); + expect(useCases).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: 'search', + title: 'Search', + features: expect.arrayContaining([ + { id: 'searchRelevance', title: 'Search Relevance' }, + ]), + }), + expect.objectContaining({ + ...DEFAULT_NAV_GROUPS.all, + }), + ]) + ); + }); + + it('should return registered use cases when nav group enabled', async () => { + const { useCaseStart } = setupUseCaseStart(); + const useCases = await useCaseStart.getRegisteredUseCases$().pipe(first()).toPromise(); + + expect(useCases).toEqual([ + expect.objectContaining({ + id: 'observability', + title: 'Observability', + features: expect.arrayContaining([{ id: 'dashboards', title: 'Dashboards' }]), + }), + expect.objectContaining({ + id: 'search', + title: 'Search', + features: expect.arrayContaining([{ id: 'searchRelevance', title: 'Search Relevance' }]), + }), + expect.objectContaining({ + id: 'system', + title: 'System', + features: [], + systematic: true, + }), + ]); + }); + + it('should not emit after navGroupsMap$ emit same value', async () => { + const { useCaseStart, navGroupsMap$ } = setupUseCaseStart(); + const registeredUseCases$ = useCaseStart.getRegisteredUseCases$(); + const fn = jest.fn(); + + registeredUseCases$.subscribe(fn); + + expect(fn).toHaveBeenCalledTimes(1); + + navGroupsMap$.next({ ...mockNavGroupsMap }); + expect(fn).toHaveBeenCalledTimes(1); + + navGroupsMap$.next({ + ...mockNavGroupsMap, + observability: { + ...mockNavGroupsMap.observability, + navLinks: [{ id: 'bar' }], + }, + }); + expect(fn).toHaveBeenCalledTimes(2); + }); + it('should move all use case to the last one', async () => { + const { useCaseStart, navGroupsMap$ } = setupUseCaseStart(); + + navGroupsMap$.next({ + ...mockNavGroupsMap, + [ALL_USE_CASE_ID]: { ...DEFAULT_NAV_GROUPS.all, navLinks: [], order: -1 }, + }); + let useCases = await useCaseStart.getRegisteredUseCases$().pipe(first()).toPromise(); + + expect(useCases[useCases.length - 1]).toEqual( + expect.objectContaining({ + id: ALL_USE_CASE_ID, + systematic: true, + }) + ); + + navGroupsMap$.next({ + [ALL_USE_CASE_ID]: { ...DEFAULT_NAV_GROUPS.all, navLinks: [], order: 1500 }, + ...mockNavGroupsMap, + }); + useCases = await useCaseStart.getRegisteredUseCases$().pipe(first()).toPromise(); + + expect(useCases[useCases.length - 1]).toEqual( + expect.objectContaining({ + id: ALL_USE_CASE_ID, + systematic: true, + }) + ); + }); + }); +}); diff --git a/src/plugins/workspace/public/services/use_case_service.ts b/src/plugins/workspace/public/services/use_case_service.ts new file mode 100644 index 000000000000..630ba9f15e29 --- /dev/null +++ b/src/plugins/workspace/public/services/use_case_service.ts @@ -0,0 +1,184 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { combineLatest, Observable, Subscription } from 'rxjs'; +import { distinctUntilChanged, map } from 'rxjs/operators'; +import { i18n } from '@osd/i18n'; + +import { + ChromeStart, + CoreSetup, + DEFAULT_APP_CATEGORIES, + PublicAppInfo, + WorkspacesSetup, + DEFAULT_NAV_GROUPS, + ALL_USE_CASE_ID, +} from '../../../../core/public'; +import { WORKSPACE_DETAIL_APP_ID, WORKSPACE_USE_CASES } from '../../common/constants'; +import { + convertNavGroupToWorkspaceUseCase, + getFirstUseCaseOfFeatureConfigs, + isEqualWorkspaceUseCase, +} from '../utils'; +import { WorkspaceUseCase } from '../types'; + +export interface UseCaseServiceSetupDeps { + chrome: CoreSetup['chrome']; + workspaces: WorkspacesSetup; + getStartServices: CoreSetup['getStartServices']; +} + +export class UseCaseService { + private workspaceAndManageWorkspaceCategorySubscription?: Subscription; + constructor() {} + + /** + * Add nav links belong to `manage workspace` to all of the use cases. + * @param coreSetup + * @param currentWorkspace + */ + private async registerManageWorkspaceCategory(setupDeps: UseCaseServiceSetupDeps) { + const [coreStart] = await setupDeps.getStartServices(); + this.workspaceAndManageWorkspaceCategorySubscription?.unsubscribe(); + this.workspaceAndManageWorkspaceCategorySubscription = combineLatest([ + setupDeps.workspaces.currentWorkspace$, + coreStart.chrome.navGroup.getNavGroupsMap$(), + ]) + .pipe( + map(([currentWorkspace, navGroupMap]) => { + const currentUseCase = getFirstUseCaseOfFeatureConfigs(currentWorkspace?.features || []); + if (!currentUseCase) { + return undefined; + } + + return navGroupMap[currentUseCase]; + }) + ) + .pipe( + distinctUntilChanged((navGroupInfo, anotherNavGroup) => { + return navGroupInfo?.id === anotherNavGroup?.id; + }) + ) + .subscribe((navGroupInfo) => { + if (navGroupInfo) { + setupDeps.chrome.navGroup.addNavLinksToGroup(navGroupInfo, [ + { + id: 'dataSources', + category: DEFAULT_APP_CATEGORIES.manageWorkspace, + order: 100, + }, + { + id: 'indexPatterns', + category: DEFAULT_APP_CATEGORIES.manageWorkspace, + order: 200, + }, + { + id: 'objects', + category: DEFAULT_APP_CATEGORIES.manageWorkspace, + order: 300, + }, + { + id: WORKSPACE_DETAIL_APP_ID, + category: DEFAULT_APP_CATEGORIES.manageWorkspace, + order: 400, + title: i18n.translate('workspace.settings.workspaceSettings', { + defaultMessage: 'Workspace settings', + }), + }, + ]); + } + }); + } + + setup({ chrome, workspaces, getStartServices }: UseCaseServiceSetupDeps) { + this.registerManageWorkspaceCategory({ + chrome, + workspaces, + getStartServices, + }); + } + + start({ + chrome, + workspaceConfigurableApps$, + }: { + chrome: ChromeStart; + workspaceConfigurableApps$: Observable; + }) { + return { + getRegisteredUseCases$: () => { + if (chrome.navGroup.getNavGroupEnabled()) { + return chrome.navGroup + .getNavGroupsMap$() + .pipe( + map((navGroupsMap) => + Object.values(navGroupsMap).map(convertNavGroupToWorkspaceUseCase) + ) + ) + .pipe( + distinctUntilChanged((useCases, anotherUseCases) => { + return ( + useCases.length === anotherUseCases.length && + useCases.every( + (useCase) => + !!anotherUseCases.find((anotherUseCase) => + isEqualWorkspaceUseCase(useCase, anotherUseCase) + ) + ) + ); + }) + ) + .pipe( + map((useCases) => + useCases.sort((a, b) => { + // Make sure all use case should be the latest + if (a.id === ALL_USE_CASE_ID) { + return 1; + } + if (b.id === ALL_USE_CASE_ID) { + return -1; + } + return ( + (a.order ?? Number.MAX_SAFE_INTEGER) - (b.order ?? Number.MAX_SAFE_INTEGER) + ); + }) + ) + ); + } + + return workspaceConfigurableApps$.pipe( + map((configurableApps) => { + const configurableAppsId = configurableApps.map((app) => app.id); + + return [ + WORKSPACE_USE_CASES.observability, + WORKSPACE_USE_CASES['security-analytics'], + WORKSPACE_USE_CASES.essentials, + WORKSPACE_USE_CASES.search, + ] + .filter((useCase) => { + return useCase.features.some((featureId) => configurableAppsId.includes(featureId)); + }) + .map((item) => ({ + ...item, + features: item.features.map((featureId) => ({ + title: configurableApps.find((app) => app.id === featureId)?.title, + id: featureId, + })), + })) + .concat({ + ...DEFAULT_NAV_GROUPS.all, + features: configurableApps.map((app) => ({ id: app.id, title: app.title })), + }) as WorkspaceUseCase[]; + }) + ); + }, + }; + } + + stop() { + this.workspaceAndManageWorkspaceCategorySubscription?.unsubscribe(); + } +} diff --git a/src/plugins/workspace/public/types.ts b/src/plugins/workspace/public/types.ts index 1b3f38e50857..bec84221e2f1 100644 --- a/src/plugins/workspace/public/types.ts +++ b/src/plugins/workspace/public/types.ts @@ -5,5 +5,32 @@ import { CoreStart } from '../../../core/public'; import { WorkspaceClient } from './workspace_client'; +import { DataSourceManagementPluginSetup } from '../../../plugins/data_source_management/public'; +import { NavigationPublicPluginStart } from '../../../plugins/navigation/public'; +import { ContentManagementPluginStart } from '../../../plugins/content_management/public'; +import { DataSourceAttributes } from '../../../plugins/data_source/common/data_sources'; -export type Services = CoreStart & { workspaceClient: WorkspaceClient }; +export type Services = CoreStart & { + workspaceClient: WorkspaceClient; + dataSourceManagement?: DataSourceManagementPluginSetup; + navigationUI?: NavigationPublicPluginStart['ui']; + contentManagement?: ContentManagementPluginStart; +}; + +export interface WorkspaceUseCaseFeature { + id: string; + title?: string; +} + +export interface WorkspaceUseCase { + id: string; + title: string; + description: string; + features: WorkspaceUseCaseFeature[]; + systematic?: boolean; + order?: number; +} + +export interface DataSourceAttributesWithWorkspaces extends Omit { + workspaces?: string[]; +} diff --git a/src/plugins/workspace/public/utils.test.ts b/src/plugins/workspace/public/utils.test.ts index c68dc844da2e..dfca65fcaf98 100644 --- a/src/plugins/workspace/public/utils.test.ts +++ b/src/plugins/workspace/public/utils.test.ts @@ -3,17 +3,40 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { AppNavLinkStatus, PublicAppInfo } from '../../../core/public'; +import { AppNavLinkStatus, NavGroupType, PublicAppInfo } from '../../../core/public'; import { featureMatchesConfig, filterWorkspaceConfigurableApps, isAppAccessibleInWorkspace, + isFeatureIdInsideUseCase, + isNavGroupInFeatureConfigs, + getDataSourcesList, + convertNavGroupToWorkspaceUseCase, + isEqualWorkspaceUseCase, + USE_CASE_PREFIX, + prependWorkspaceToBreadcrumbs, + getIsOnlyAllowEssentialUseCase, } from './utils'; import { WorkspaceAvailability } from '../../../core/public'; +import { coreMock } from '../../../core/public/mocks'; +import { WORKSPACE_DETAIL_APP_ID } from '../common/constants'; +import { SigV4ServiceName } from '../../../plugins/data_source/common/data_sources'; +import { createMockedRegisteredUseCases } from './mocks'; + +const startMock = coreMock.createStart(); +const STATIC_USE_CASES = createMockedRegisteredUseCases(); +const useCaseMock = { + id: 'foo', + title: 'Foo', + description: 'Foo description', + features: [{ id: 'bar' }], + systematic: false, + order: 1, +}; describe('workspace utils: featureMatchesConfig', () => { it('feature configured with `*` should match any features', () => { - const match = featureMatchesConfig(['*']); + const match = featureMatchesConfig(['*'], STATIC_USE_CASES); expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe( true ); @@ -23,28 +46,31 @@ describe('workspace utils: featureMatchesConfig', () => { }); it('should NOT match the config if feature id not matches', () => { - const match = featureMatchesConfig(['discover', 'dashboards', 'visualize']); + const match = featureMatchesConfig(['discover', 'dashboards', 'visualize'], STATIC_USE_CASES); expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe( false ); }); it('should match the config if feature id matches', () => { - const match = featureMatchesConfig(['discover', 'dashboards', 'visualize']); + const match = featureMatchesConfig(['discover', 'dashboards', 'visualize'], STATIC_USE_CASES); expect( match({ id: 'discover', category: { id: 'opensearchDashboards', label: 'Library' } }) ).toBe(true); }); it('should match the config if feature category matches', () => { - const match = featureMatchesConfig(['discover', 'dashboards', '@management', 'visualize']); + const match = featureMatchesConfig( + ['discover', 'dashboards', '@management', 'visualize'], + STATIC_USE_CASES + ); expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe( true ); }); it('should match any features but not the excluded feature id', () => { - const match = featureMatchesConfig(['*', '!discover']); + const match = featureMatchesConfig(['*', '!discover'], STATIC_USE_CASES); expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe( true ); @@ -54,7 +80,7 @@ describe('workspace utils: featureMatchesConfig', () => { }); it('should match any features but not the excluded feature category', () => { - const match = featureMatchesConfig(['*', '!@management']); + const match = featureMatchesConfig(['*', '!@management'], STATIC_USE_CASES); expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe( false ); @@ -67,7 +93,7 @@ describe('workspace utils: featureMatchesConfig', () => { }); it('should NOT match the excluded feature category', () => { - const match = featureMatchesConfig(['!@management']); + const match = featureMatchesConfig(['!@management'], STATIC_USE_CASES); expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe( false ); @@ -77,7 +103,7 @@ describe('workspace utils: featureMatchesConfig', () => { }); it('should match features of a category but NOT the excluded feature', () => { - const match = featureMatchesConfig(['@management', '!dev_tools']); + const match = featureMatchesConfig(['@management', '!dev_tools'], STATIC_USE_CASES); expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe( false ); @@ -88,7 +114,7 @@ describe('workspace utils: featureMatchesConfig', () => { it('a config presents later in the config array should override the previous config', () => { // though `dev_tools` is excluded, but this config will override by '@management' as dev_tools has category 'management' - const match = featureMatchesConfig(['!dev_tools', '@management']); + const match = featureMatchesConfig(['!dev_tools', '@management'], STATIC_USE_CASES); expect(match({ id: 'dev_tools', category: { id: 'management', label: 'Management' } })).toBe( true ); @@ -96,6 +122,23 @@ describe('workspace utils: featureMatchesConfig', () => { true ); }); + + it('should match features include by any use cases', () => { + const match = featureMatchesConfig( + ['use-case-observability', 'use-case-search'], + STATIC_USE_CASES + ); + expect(match({ id: 'dashboards' })).toBe(true); + expect(match({ id: 'observability-traces' })).toBe(true); + + /** + * The searchRelevance is a feature under search use case. Since each workspace only can be a specific use case, + * the feature matches will use first use case to check if features exists. The observability doesn't have + * searchRelevance feature, it will return false. + */ + expect(match({ id: 'searchRelevance' })).toBe(false); + expect(match({ id: 'not-in-any-use-case' })).toBe(false); + }); }); describe('workspace utils: isAppAccessibleInWorkspace', () => { @@ -103,7 +146,8 @@ describe('workspace utils: isAppAccessibleInWorkspace', () => { expect( isAppAccessibleInWorkspace( { id: 'any_app', title: 'Any app', mount: jest.fn() }, - { id: 'workspace_id', name: 'workspace name' } + { id: 'workspace_id', name: 'workspace name' }, + STATIC_USE_CASES ) ).toBe(true); }); @@ -112,7 +156,8 @@ describe('workspace utils: isAppAccessibleInWorkspace', () => { expect( isAppAccessibleInWorkspace( { id: 'dev_tools', title: 'Any app', mount: jest.fn() }, - { id: 'workspace_id', name: 'workspace name', features: ['dev_tools'] } + { id: 'workspace_id', name: 'workspace name', features: ['dev_tools'] }, + STATIC_USE_CASES ) ).toBe(true); }); @@ -121,7 +166,8 @@ describe('workspace utils: isAppAccessibleInWorkspace', () => { expect( isAppAccessibleInWorkspace( { id: 'dev_tools', title: 'Any app', mount: jest.fn() }, - { id: 'workspace_id', name: 'workspace name', features: [] } + { id: 'workspace_id', name: 'workspace name', features: [] }, + STATIC_USE_CASES ) ).toBe(false); }); @@ -135,7 +181,8 @@ describe('workspace utils: isAppAccessibleInWorkspace', () => { mount: jest.fn(), navLinkStatus: AppNavLinkStatus.hidden, }, - { id: 'workspace_id', name: 'workspace name', features: [] } + { id: 'workspace_id', name: 'workspace name', features: [] }, + STATIC_USE_CASES ) ).toBe(true); }); @@ -149,7 +196,8 @@ describe('workspace utils: isAppAccessibleInWorkspace', () => { mount: jest.fn(), chromeless: true, }, - { id: 'workspace_id', name: 'workspace name', features: [] } + { id: 'workspace_id', name: 'workspace name', features: [] }, + STATIC_USE_CASES ) ).toBe(true); }); @@ -163,7 +211,8 @@ describe('workspace utils: isAppAccessibleInWorkspace', () => { mount: jest.fn(), workspaceAvailability: WorkspaceAvailability.outsideWorkspace, }, - { id: 'workspace_id', name: 'workspace name', features: [] } + { id: 'workspace_id', name: 'workspace name', features: [] }, + STATIC_USE_CASES ) ).toBe(false); }); @@ -176,7 +225,8 @@ describe('workspace utils: isAppAccessibleInWorkspace', () => { mount: jest.fn(), workspaceAvailability: WorkspaceAvailability.insideWorkspace, }, - { id: 'workspace_id', name: 'workspace name', features: ['home'] } + { id: 'workspace_id', name: 'workspace name', features: ['home'] }, + STATIC_USE_CASES ) ).toBe(true); }); @@ -191,7 +241,17 @@ describe('workspace utils: isAppAccessibleInWorkspace', () => { // eslint-disable-next-line no-bitwise WorkspaceAvailability.insideWorkspace | WorkspaceAvailability.outsideWorkspace, }, - { id: 'workspace_id', name: 'workspace name', features: ['home'] } + { id: 'workspace_id', name: 'workspace name', features: ['home'] }, + STATIC_USE_CASES + ) + ).toBe(true); + }); + it('any app is accessible when workspace is all use case', () => { + expect( + isAppAccessibleInWorkspace( + { id: 'any_app', title: 'Any app', mount: jest.fn() }, + { id: 'workspace_id', name: 'workspace name', features: ['use-case-all'] }, + STATIC_USE_CASES ) ).toBe(true); }); @@ -261,3 +321,423 @@ describe('workspace utils: filterWorkspaceConfigurableApps', () => { expect(filteredApps[1].id).toEqual('management'); }); }); + +describe('workspace utils: isFeatureIdInsideUseCase', () => { + it('should return false for invalid use case', () => { + expect(isFeatureIdInsideUseCase('discover', 'invalid', [])).toBe(false); + }); + it('should return false if feature not in use case', () => { + expect( + isFeatureIdInsideUseCase('discover', 'foo', [ + { + id: 'foo', + title: 'Foo', + description: 'Foo description', + features: [], + }, + ]) + ).toBe(false); + }); + it('should return true if feature id exists in use case', () => { + expect( + isFeatureIdInsideUseCase('discover', 'foo', [ + { + id: 'foo', + title: 'Foo', + description: 'Foo description', + features: [{ id: 'discover' }], + }, + ]) + ).toBe(true); + }); +}); + +describe('workspace utils: isNavGroupInFeatureConfigs', () => { + it('should return false if nav group not in feature configs', () => { + expect( + isNavGroupInFeatureConfigs('dataAdministration', [ + 'use-case-observability', + 'use-case-search', + ]) + ).toBe(false); + }); + it('should return true if nav group in feature configs', () => { + expect( + isNavGroupInFeatureConfigs('observability', ['use-case-observability', 'use-case-search']) + ).toBe(true); + }); +}); + +describe('workspace utils: getDataSourcesList', () => { + const mockedSavedObjectClient = startMock.savedObjects.client; + + it('should return result when passed saved object client', async () => { + mockedSavedObjectClient.find = jest.fn().mockResolvedValue({ + savedObjects: [ + { + id: 'id1', + get: (param: string) => { + switch (param) { + case 'title': + return 'title1'; + case 'description': + return 'description1'; + case 'dataSourceEngineType': + return 'dataSourceEngineType1'; + case 'auth': + return 'mock_value'; + } + }, + }, + ], + }); + expect(await getDataSourcesList(mockedSavedObjectClient, [])).toStrictEqual([ + { + id: 'id1', + title: 'title1', + auth: 'mock_value', + description: 'description1', + dataSourceEngineType: 'dataSourceEngineType1', + workspaces: [], + }, + ]); + }); + + it('should return empty array if no saved objects responded', async () => { + mockedSavedObjectClient.find = jest.fn().mockResolvedValue({}); + expect(await getDataSourcesList(mockedSavedObjectClient, [])).toStrictEqual([]); + }); +}); + +describe('workspace utils: getIsOnlyAllowEssentialUseCase', () => { + const mockedSavedObjectClient = startMock.savedObjects.client; + + it('should return true when all data sources are serverless', async () => { + mockedSavedObjectClient.find = jest.fn().mockResolvedValue({ + savedObjects: [ + { + id: 'id1', + get: () => { + return { + credentials: { + service: SigV4ServiceName.OpenSearchServerless, + }, + }; + }, + }, + ], + }); + expect(await getIsOnlyAllowEssentialUseCase(mockedSavedObjectClient)).toBe(true); + }); + + it('should return false when not all data sources are serverless', async () => { + mockedSavedObjectClient.find = jest.fn().mockResolvedValue({ + savedObjects: [ + { + id: 'id1', + get: () => { + return { + credentials: { + service: SigV4ServiceName.OpenSearchServerless, + }, + }; + }, + }, + { + id: 'id2', + get: () => { + return { + credentials: { + service: SigV4ServiceName.OpenSearch, + }, + }; + }, + }, + ], + }); + expect(await getIsOnlyAllowEssentialUseCase(mockedSavedObjectClient)).toBe(false); + }); +}); + +describe('workspace utils: convertNavGroupToWorkspaceUseCase', () => { + it('should convert nav group to consistent workspace use case', () => { + expect( + convertNavGroupToWorkspaceUseCase({ + id: 'foo', + title: 'Foo', + description: 'Foo description', + navLinks: [{ id: 'bar', title: 'Bar' }], + }) + ).toEqual({ + id: 'foo', + title: 'Foo', + description: 'Foo description', + features: [{ id: 'bar', title: 'Bar' }], + systematic: false, + }); + + expect( + convertNavGroupToWorkspaceUseCase({ + id: 'foo', + title: 'Foo', + description: 'Foo description', + navLinks: [{ id: 'bar', title: 'Bar' }], + type: NavGroupType.SYSTEM, + }) + ).toEqual({ + id: 'foo', + title: 'Foo', + description: 'Foo description', + features: [{ id: 'bar', title: 'Bar' }], + systematic: true, + }); + }); +}); + +describe('workspace utils: isEqualWorkspaceUseCase', () => { + it('should return false when id not equal', () => { + expect( + isEqualWorkspaceUseCase(useCaseMock, { + ...useCaseMock, + id: 'foo1', + }) + ).toEqual(false); + }); + it('should return false when title not equal', () => { + expect( + isEqualWorkspaceUseCase(useCaseMock, { + ...useCaseMock, + title: 'Foo1', + }) + ).toEqual(false); + }); + it('should return false when description not equal', () => { + expect( + isEqualWorkspaceUseCase(useCaseMock, { + ...useCaseMock, + description: 'Foo description 1', + }) + ).toEqual(false); + }); + it('should return false when systematic not equal', () => { + expect( + isEqualWorkspaceUseCase(useCaseMock, { + ...useCaseMock, + systematic: true, + }) + ).toEqual(false); + }); + it('should return false when order not equal', () => { + expect( + isEqualWorkspaceUseCase(useCaseMock, { + ...useCaseMock, + order: 2, + }) + ).toEqual(false); + }); + it('should return false when features length not equal', () => { + expect( + isEqualWorkspaceUseCase(useCaseMock, { + ...useCaseMock, + features: [], + }) + ).toEqual(false); + }); + it('should return false when features id not equal', () => { + expect( + isEqualWorkspaceUseCase(useCaseMock, { + ...useCaseMock, + features: [{ id: 'baz' }], + }) + ).toEqual(false); + }); + it('should return false when features title not equal', () => { + expect( + isEqualWorkspaceUseCase(useCaseMock, { + ...useCaseMock, + features: [{ id: 'bar', title: 'Baz' }], + }) + ).toEqual(false); + }); + it('should return false for duplicate features', () => { + expect( + isEqualWorkspaceUseCase( + { ...useCaseMock, features: [useCaseMock.features[0], useCaseMock.features[0]] }, + { + ...useCaseMock, + features: [ + useCaseMock.features[0], + { + id: 'another', + title: 'Another', + }, + ], + } + ) + ).toEqual(false); + }); + it('should return true for multi same features', () => { + const anotherFeature = { + id: 'another', + title: 'Another', + }; + expect( + isEqualWorkspaceUseCase( + { ...useCaseMock, features: [useCaseMock.features[0], anotherFeature] }, + { + ...useCaseMock, + features: [useCaseMock.features[0], anotherFeature], + } + ) + ).toEqual(true); + }); + it('should return true when all properties equal', () => { + expect( + isEqualWorkspaceUseCase(useCaseMock, { + ...useCaseMock, + }) + ).toEqual(true); + }); +}); + +describe('workspace utils: prependWorkspaceToBreadcrumbs', () => { + const workspace = { + id: 'workspace-1', + name: 'test workspace 1', + features: [`${USE_CASE_PREFIX}search`], + }; + + it('should not enrich breadcrumbs for workspace detail page', () => { + const coreStart = coreMock.createStart(); + prependWorkspaceToBreadcrumbs(coreStart, workspace, WORKSPACE_DETAIL_APP_ID, undefined, {}); + expect(coreStart.chrome.setBreadcrumbsEnricher).toHaveBeenCalledWith(undefined); + }); + + it('should not enrich breadcrumbs when out a workspace', async () => { + const coreStart = coreMock.createStart(); + prependWorkspaceToBreadcrumbs(coreStart, null, 'app1', undefined, {}); + expect(coreStart.chrome.setBreadcrumbsEnricher).not.toHaveBeenCalled(); + }); + + it('should enrich breadcrumbs when in a workspace and use workspace use case as current nav group', async () => { + const navGroupSearch = { + id: 'search', + title: 'Search', + description: 'search desc', + navLinks: [], + }; + const navGroupDashboards = { + id: 'ds', + title: 'Dashboards', + description: 'Dashboards desc', + navLinks: [], + }; + + const coreStart = coreMock.createStart(); + prependWorkspaceToBreadcrumbs(coreStart, workspace, 'app1', undefined, { + search: navGroupSearch, + ds: navGroupDashboards, + }); + expect(coreStart.chrome.setBreadcrumbsEnricher).toHaveBeenCalledTimes(1); + let calls = coreStart.chrome.setBreadcrumbsEnricher.mock.calls; + // calls is an array of arrays, where each inner array represents the arguments for a single call + // get the actual enricher + let enricher = calls[0][0]; + + const breadcrumbs = [{ text: 'test app' }]; + let enrichedBreadcrumbs = enricher?.(breadcrumbs); + expect(enrichedBreadcrumbs).toHaveLength(3); + expect(enrichedBreadcrumbs?.[1].text).toEqual('Search'); + + // ignore current nav group + prependWorkspaceToBreadcrumbs(coreStart, workspace, 'app1', navGroupDashboards, { + search: navGroupSearch, + ds: navGroupDashboards, + }); + expect(coreStart.chrome.setBreadcrumbsEnricher).toHaveBeenCalledTimes(2); + calls = coreStart.chrome.setBreadcrumbsEnricher.mock.calls; + // calls is an array of arrays, where each inner array represents the arguments for a single call + // get the actual enricher + enricher = calls[0][0]; + + enrichedBreadcrumbs = enricher?.(breadcrumbs); + expect(enrichedBreadcrumbs).toHaveLength(3); + expect(enrichedBreadcrumbs?.[1].text).toEqual('Search'); + }); + + it('should enrich breadcrumbs when in a workspace with all use case and use selected nav group', async () => { + const workspaceWithAllUseCase = { + id: 'workspace-all', + name: 'test workspace 1', + features: [`${USE_CASE_PREFIX}all`], + }; + + const navGroupSearch = { + id: 'search', + title: 'Search', + description: 'search desc', + navLinks: [], + }; + const navGroupDashboards = { + id: 'ds', + title: 'Dashboards', + description: 'Dashboards desc', + navLinks: [], + }; + + const coreStart = coreMock.createStart(); + prependWorkspaceToBreadcrumbs(coreStart, workspaceWithAllUseCase, 'app1', navGroupDashboards, { + search: navGroupSearch, + ds: navGroupDashboards, + }); + expect(coreStart.chrome.setBreadcrumbsEnricher).toHaveBeenCalledTimes(1); + + const calls = coreStart.chrome.setBreadcrumbsEnricher.mock.calls; + // calls is an array of arrays, where each inner array represents the arguments for a single call + // get the actual enricher + const enricher = calls[0][0]; + + const breadcrumbs = [{ text: 'test app' }]; + const enrichedBreadcrumbs = enricher?.(breadcrumbs); + expect(enrichedBreadcrumbs).toHaveLength(4); + expect(enrichedBreadcrumbs?.[1].text).toEqual(workspaceWithAllUseCase.name); + expect(enrichedBreadcrumbs?.[2].text).toEqual(navGroupDashboards.title); + }); + + it('should enrich breadcrumbs when in a workspace with all use case and current nav group is null', async () => { + const workspaceWithAllUseCase = { + id: 'workspace-all', + name: 'test workspace 1', + features: [`${USE_CASE_PREFIX}all`], + }; + + const navGroupSearch = { + id: 'search', + title: 'Search', + description: 'search desc', + navLinks: [], + }; + const navGroupDashboards = { + id: 'ds', + title: 'Dashboards', + description: 'Dashboards desc', + navLinks: [], + }; + + const coreStart = coreMock.createStart(); + prependWorkspaceToBreadcrumbs(coreStart, workspaceWithAllUseCase, 'app1', undefined, { + search: navGroupSearch, + ds: navGroupDashboards, + }); + expect(coreStart.chrome.setBreadcrumbsEnricher).toHaveBeenCalledTimes(1); + + const calls = coreStart.chrome.setBreadcrumbsEnricher.mock.calls; + // calls is an array of arrays, where each inner array represents the arguments for a single call + // get the actual enricher + const enricher = calls[0][0]; + + const enrichedBreadcrumbs = enricher?.([{ text: 'overview' }]); + expect(enrichedBreadcrumbs).toHaveLength(3); + expect(enrichedBreadcrumbs?.[1].text).toEqual(workspaceWithAllUseCase.name); + }); +}); diff --git a/src/plugins/workspace/public/utils.ts b/src/plugins/workspace/public/utils.ts index ba9ab5399e21..01969d15159c 100644 --- a/src/plugins/workspace/public/utils.ts +++ b/src/plugins/workspace/public/utils.ts @@ -3,6 +3,18 @@ * SPDX-License-Identifier: Apache-2.0 */ +import { combineLatest } from 'rxjs'; +import { + NavGroupType, + SavedObjectsStart, + NavGroupItemInMap, + ALL_USE_CASE_ID, + CoreStart, + ChromeBreadcrumb, + ApplicationStart, + HttpSetup, + DEFAULT_NAV_GROUPS, +} from '../../../core/public'; import { App, AppCategory, @@ -12,7 +24,40 @@ import { WorkspaceObject, WorkspaceAvailability, } from '../../../core/public'; -import { DEFAULT_SELECTED_FEATURES_IDS } from '../common/constants'; +import { WORKSPACE_DETAIL_APP_ID } from '../common/constants'; +import { WorkspaceUseCase, WorkspaceUseCaseFeature } from './types'; +import { formatUrlWithWorkspaceId } from '../../../core/public/utils'; +import { SigV4ServiceName } from '../../../plugins/data_source/common/data_sources'; +import { + ANALYTICS_ALL_OVERVIEW_PAGE_ID, + ESSENTIAL_OVERVIEW_PAGE_ID, +} from '../../../plugins/content_management/public'; + +export const USE_CASE_PREFIX = 'use-case-'; + +export const getUseCaseFeatureConfig = (useCaseId: string) => `${USE_CASE_PREFIX}${useCaseId}`; + +export const isUseCaseFeatureConfig = (featureConfig: string) => + featureConfig.startsWith(USE_CASE_PREFIX); + +export const getUseCaseFromFeatureConfig = (featureConfig: string) => { + if (isUseCaseFeatureConfig(featureConfig)) { + return featureConfig.substring(USE_CASE_PREFIX.length); + } + return null; +}; + +export const isFeatureIdInsideUseCase = ( + featureId: string, + useCaseId: string, + useCases: WorkspaceUseCase[] +) => { + const availableFeatures = useCases.find(({ id }) => id === useCaseId)?.features ?? []; + return availableFeatures.some((feature) => feature.id === featureId); +}; + +export const isNavGroupInFeatureConfigs = (navGroupId: string, featureConfigs: string[]) => + featureConfigs.includes(getUseCaseFeatureConfig(navGroupId)); /** * Checks if a given feature matches the provided feature configuration. @@ -24,8 +69,10 @@ import { DEFAULT_SELECTED_FEATURES_IDS } from '../common/constants'; * 4. To exclude a feature or category, prepend with `!`, e.g., `!discover` or `!@management`. * 5. The order of featureConfig array matters. From left to right, later configs override the previous ones. * For example, ['!@management', '*'] matches any feature because '*' overrides the previous setting: '!@management'. + * 6. For feature id start with use case prefix, it will read use case's features and match every passed apps. + * For example, ['user-case-observability'] matches all features under observability use case. */ -export const featureMatchesConfig = (featureConfigs: string[]) => ({ +export const featureMatchesConfig = (featureConfigs: string[], useCases: WorkspaceUseCase[]) => ({ id, category, }: { @@ -33,6 +80,7 @@ export const featureMatchesConfig = (featureConfigs: string[]) => ({ category?: AppCategory; }) => { let matched = false; + let firstUseCaseId: string | undefined; /** * Iterate through each feature configuration to determine if the given feature matches any of them. @@ -45,6 +93,17 @@ export const featureMatchesConfig = (featureConfigs: string[]) => ({ matched = true; } + // matches any feature inside use cases + if (!firstUseCaseId) { + const useCaseId = getUseCaseFromFeatureConfig(featureConfig); + if (useCaseId) { + firstUseCaseId = useCaseId; + if (isFeatureIdInsideUseCase(id, firstUseCaseId, useCases)) { + matched = true; + } + } + } + // The config starts with `@` matches a category if (category && featureConfig === `@${category.id}`) { matched = true; @@ -73,7 +132,11 @@ export const featureMatchesConfig = (featureConfigs: string[]) => ({ /** * Check if an app is accessible in a workspace based on the workspace configured features */ -export function isAppAccessibleInWorkspace(app: App, workspace: WorkspaceObject) { +export function isAppAccessibleInWorkspace( + app: App, + workspace: WorkspaceObject, + availableUseCases: WorkspaceUseCase[] +) { /** * App is not accessible within workspace if it explicitly declare itself as WorkspaceAvailability.outsideWorkspace */ @@ -88,10 +151,17 @@ export function isAppAccessibleInWorkspace(app: App, workspace: WorkspaceObject) return true; } + /** + * When workspace is all use case, all apps are accessible + */ + if (getFirstUseCaseOfFeatureConfigs(workspace.features) === ALL_USE_CASE_ID) { + return true; + } + /** * The app is configured into a workspace, it is accessible after entering the workspace */ - const featureMatcher = featureMatchesConfig(workspace.features); + const featureMatcher = featureMatchesConfig(workspace.features, availableUseCases); if (featureMatcher({ id: app.id, category: app.category })) { return true; } @@ -123,7 +193,6 @@ export const filterWorkspaceConfigurableApps = (applications: PublicAppInfo[]) = const filterCondition = navLinkStatus !== AppNavLinkStatus.hidden && !chromeless && - !DEFAULT_SELECTED_FEATURES_IDS.includes(id) && workspaceAvailability !== WorkspaceAvailability.outsideWorkspace; // If the category is management, only retain Dashboards Management which contains saved objets and index patterns. // Saved objets can show all saved objects in the current workspace and index patterns is at workspace level. @@ -136,3 +205,206 @@ export const filterWorkspaceConfigurableApps = (applications: PublicAppInfo[]) = return visibleApplications; }; + +export const getDataSourcesList = ( + client: SavedObjectsStart['client'], + targetWorkspaces: string[] +) => { + return client + .find({ + type: 'data-source', + fields: ['id', 'title', 'auth', 'description', 'dataSourceEngineType'], + perPage: 10000, + workspaces: targetWorkspaces, + }) + .then((response) => { + const objects = response?.savedObjects; + if (objects) { + return objects.map((source) => { + const id = source.id; + const title = source.get('title'); + const workspaces = source.workspaces ?? []; + const auth = source.get('auth'); + const description = source.get('description'); + const dataSourceEngineType = source.get('dataSourceEngineType'); + return { + id, + title, + auth, + description, + dataSourceEngineType, + workspaces, + }; + }); + } else { + return []; + } + }); +}; + +// If all connected data sources are serverless, will only allow to select essential use case. +export const getIsOnlyAllowEssentialUseCase = async (client: SavedObjectsStart['client']) => { + const allDataSources = await getDataSourcesList(client, ['*']); + if (allDataSources.length > 0) { + return allDataSources.every( + (ds) => ds?.auth?.credentials?.service === SigV4ServiceName.OpenSearchServerless + ); + } + return false; +}; + +export const convertNavGroupToWorkspaceUseCase = ({ + id, + title, + description, + navLinks, + type, + order, +}: NavGroupItemInMap): WorkspaceUseCase => ({ + id, + title, + description, + features: navLinks.map((item) => ({ id: item.id, title: item.title })), + systematic: type === NavGroupType.SYSTEM || id === ALL_USE_CASE_ID, + order, +}); + +const compareFeatures = ( + features1: WorkspaceUseCaseFeature[], + features2: WorkspaceUseCaseFeature[] +) => { + const featuresSerializer = (features: WorkspaceUseCaseFeature[]) => + features + .map(({ id, title }) => `${id}-${title}`) + .sort() + .join(); + return featuresSerializer(features1) === featuresSerializer(features2); +}; + +export const isEqualWorkspaceUseCase = (a: WorkspaceUseCase, b: WorkspaceUseCase) => { + if (a.id !== b.id) { + return false; + } + if (a.title !== b.title) { + return false; + } + if (a.description !== b.description) { + return false; + } + if (a.systematic !== b.systematic) { + return false; + } + if (a.order !== b.order) { + return false; + } + if (a.features.length !== b.features.length || !compareFeatures(a.features, b.features)) { + return false; + } + return true; +}; + +const isNotNull = (value: T | null): value is T => !!value; + +export const getFirstUseCaseOfFeatureConfigs = (featureConfigs: string[]): string | undefined => + featureConfigs.map(getUseCaseFromFeatureConfig).filter(isNotNull)[0]; + +export function enrichBreadcrumbsWithWorkspace(core: CoreStart) { + return combineLatest([ + core.workspaces.currentWorkspace$, + core.application.currentAppId$, + core.chrome.navGroup.getCurrentNavGroup$(), + core.chrome.navGroup.getNavGroupsMap$(), + ]).subscribe(([currentWorkspace, appId, currentNavGroup, navGroupsMap]) => { + prependWorkspaceToBreadcrumbs(core, currentWorkspace, appId, currentNavGroup, navGroupsMap); + }); +} + +/** + * prepend workspace or its use case to breadcrumbs + * @param core CoreStart + */ +export function prependWorkspaceToBreadcrumbs( + core: CoreStart, + currentWorkspace: WorkspaceObject | null, + appId: string | undefined, + currentNavGroup: NavGroupItemInMap | undefined, + navGroupsMap: Record +) { + if ( + appId === WORKSPACE_DETAIL_APP_ID || + appId === ESSENTIAL_OVERVIEW_PAGE_ID || + appId === ANALYTICS_ALL_OVERVIEW_PAGE_ID + ) { + core.chrome.setBreadcrumbsEnricher(undefined); + return; + } + + /** + * There has 3 cases + * nav group is enable + workspace enable + in a workspace -> workspace enricher + * nav group is enable + workspace enable + out a workspace -> nav group enricher + * nav group is enable + workspace disabled -> nav group enricher + * + * switch workspace will cause page refresh, breadcrumbs enricher will reset automatically + * so we don't need to have reset logic for workspace + */ + if (currentWorkspace) { + const useCase = getFirstUseCaseOfFeatureConfigs(currentWorkspace?.features || []); + // get workspace the only use case + if (useCase && useCase !== ALL_USE_CASE_ID) { + currentNavGroup = navGroupsMap[useCase]; + } + const navGroupBreadcrumb: ChromeBreadcrumb = { + text: currentNavGroup?.title, + onClick: () => { + // current nav group links are sorted, we don't need to sort it again here + if (currentNavGroup?.navLinks[0].id) { + core.application.navigateToApp(currentNavGroup?.navLinks[0].id); + } + }, + }; + const homeBreadcrumb: ChromeBreadcrumb = { + text: 'Home', + onClick: () => { + core.application.navigateToApp('home'); + }, + }; + + core.chrome.setBreadcrumbsEnricher((breadcrumbs) => { + if (!breadcrumbs || !breadcrumbs.length) return breadcrumbs; + + const workspaceBreadcrumb: ChromeBreadcrumb = { + text: currentWorkspace.name, + onClick: () => { + core.application.navigateToApp(WORKSPACE_DETAIL_APP_ID); + }, + }; + if (useCase === ALL_USE_CASE_ID) { + if (currentNavGroup && currentNavGroup.id !== DEFAULT_NAV_GROUPS.all.id) { + return [homeBreadcrumb, workspaceBreadcrumb, navGroupBreadcrumb, ...breadcrumbs]; + } else { + return [homeBreadcrumb, workspaceBreadcrumb, ...breadcrumbs]; + } + } else { + return [homeBreadcrumb, navGroupBreadcrumb, ...breadcrumbs]; + } + }); + } +} + +export const getUseCaseUrl = ( + useCase: WorkspaceUseCase | undefined, + workspace: WorkspaceObject, + application: ApplicationStart, + http: HttpSetup +): string => { + const appId = useCase?.features?.[0].id || WORKSPACE_DETAIL_APP_ID; + const useCaseURL = formatUrlWithWorkspaceId( + application.getUrlForApp(appId, { + absolute: false, + }), + workspace.id, + http.basePath + ); + return useCaseURL; +}; diff --git a/src/plugins/workspace/public/workspace_client.mock.ts b/src/plugins/workspace/public/workspace_client.mock.ts index 2ceeae5627d1..4710f8bae13a 100644 --- a/src/plugins/workspace/public/workspace_client.mock.ts +++ b/src/plugins/workspace/public/workspace_client.mock.ts @@ -13,6 +13,7 @@ export const workspaceClientMock = { list: jest.fn(), get: jest.fn(), update: jest.fn(), + copy: jest.fn(), stop: jest.fn(), }; diff --git a/src/plugins/workspace/public/workspace_client.test.ts b/src/plugins/workspace/public/workspace_client.test.ts index c18ed3db64e7..798562c04bd3 100644 --- a/src/plugins/workspace/public/workspace_client.test.ts +++ b/src/plugins/workspace/public/workspace_client.test.ts @@ -178,6 +178,7 @@ describe('#WorkspaceClient', () => { expect(workspaceMock.workspaceList$.getValue()).toEqual([ { id: 'foo', + readonly: false, }, ]); expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces/_list', { @@ -209,4 +210,46 @@ describe('#WorkspaceClient', () => { }); expect(workspaceMock.workspaceList$.getValue()).toEqual([]); }); + + it('#copy', async () => { + const { workspaceClient, httpSetupMock } = getWorkspaceClient(); + httpSetupMock.fetch.mockResolvedValue({ + success: true, + result: {}, + }); + const body = JSON.stringify({ + objects: [{ id: 1, type: 'url' }], + targetWorkspace: 'workspace-1', + includeReferencesDeep: false, + }); + await workspaceClient.copy([{ id: 1, type: 'url' }], 'workspace-1', false); + expect(httpSetupMock.fetch).toBeCalledWith('/api/workspaces/_duplicate_saved_objects', { + body, + method: 'POST', + }); + }); + + it('#init with resultWithWritePermission is not success ', async () => { + const { workspaceClient, httpSetupMock, workspaceMock } = getWorkspaceClient(); + httpSetupMock.fetch + .mockResolvedValueOnce({ + success: true, + result: { + workspaces: [ + { + id: 'foo', + name: 'foo', + }, + ], + total: 1, + per_page: 999, + page: 1, + }, + }) + .mockResolvedValueOnce({ + success: false, + }); + await workspaceClient.init(); + expect(workspaceMock.workspaceList$.getValue()).toEqual([]); + }); }); diff --git a/src/plugins/workspace/public/workspace_client.ts b/src/plugins/workspace/public/workspace_client.ts index 892fcc52cef1..05b3578076cd 100644 --- a/src/plugins/workspace/public/workspace_client.ts +++ b/src/plugins/workspace/public/workspace_client.ts @@ -10,7 +10,9 @@ import { HttpSetup, WorkspaceAttribute, WorkspacesSetup, + IWorkspaceClient, } from '../../../core/public'; +import { WorkspacePermissionMode } from '../common/constants'; import { SavedObjectPermissions, WorkspaceAttributeWithPermission } from '../../../core/types'; const WORKSPACES_API_BASE_URL = '/api/workspaces'; @@ -38,6 +40,7 @@ interface WorkspaceFindOptions { searchFields?: string[]; sortField?: string; sortOrder?: string; + permissionModes?: WorkspacePermissionMode[]; } /** @@ -46,7 +49,7 @@ interface WorkspaceFindOptions { * * @public */ -export class WorkspaceClient { +export class WorkspaceClient implements IWorkspaceClient { private http: HttpSetup; private workspaces: WorkspacesSetup; @@ -118,7 +121,20 @@ export class WorkspaceClient { }); if (result?.success) { - this.workspaces.workspaceList$.next(result.result.workspaces); + const resultWithWritePermission = await this.list({ + perPage: 999, + permissionModes: [WorkspacePermissionMode.LibraryWrite], + }); + if (resultWithWritePermission?.success) { + const workspaceIdsWithWritePermission = resultWithWritePermission.result.workspaces.map( + (workspace: WorkspaceAttribute) => workspace.id + ); + const workspaces = result.result.workspaces.map((workspace: WorkspaceAttribute) => ({ + ...workspace, + readonly: !workspaceIdsWithWritePermission.includes(workspace.id), + })); + this.workspaces.workspaceList$.next(workspaces); + } } else { this.workspaces.workspaceList$.next([]); } @@ -185,7 +201,10 @@ export class WorkspaceClient { */ public async create( attributes: Omit, - permissions?: SavedObjectPermissions + settings: { + dataSources?: string[]; + permissions?: SavedObjectPermissions; + } ): Promise>> { const path = this.getPath(); @@ -193,7 +212,7 @@ export class WorkspaceClient { method: 'POST', body: JSON.stringify({ attributes, - permissions, + settings, }), }); @@ -214,6 +233,9 @@ export class WorkspaceClient { const result = await this.safeFetch(this.getPath(id), { method: 'DELETE' }); if (result.success) { + // After deleting workspace, need to reset current workspace ID. + this.workspaces.currentWorkspaceId$.next(''); + await this.updateWorkspaceList(); } @@ -230,6 +252,7 @@ export class WorkspaceClient { * @property {integer} [options.page=1] * @property {integer} [options.perPage=20] * @property {array} options.fields + * @property {string array} permissionModes * @returns A find result with workspaces matching the specified search. */ public list( @@ -272,12 +295,15 @@ export class WorkspaceClient { public async update( id: string, attributes: Partial, - permissions?: SavedObjectPermissions + settings: { + dataSources?: string[]; + permissions?: SavedObjectPermissions; + } ): Promise> { const path = this.getPath(id); const body = { attributes, - permissions, + settings, }; const result = await this.safeFetch(path, { @@ -292,6 +318,34 @@ export class WorkspaceClient { return result; } + /** + * copy saved objects to target workspace + * + * @param {Array<{ id: string; type: string }>} objects + * @param {string} targetWorkspace + * @param {boolean} includeReferencesDeep + * @returns {Promise>} result for this operation + */ + public async copy( + objects: Array<{ id: string; type: string }>, + targetWorkspace: string, + includeReferencesDeep: boolean = true + ): Promise> { + const path = this.getPath('_duplicate_saved_objects'); + const body = { + objects, + targetWorkspace, + includeReferencesDeep, + }; + + const result = await this.safeFetch(path, { + method: 'POST', + body: JSON.stringify(body), + }); + + return result; + } + public stop() { this.workspaces.workspaceList$.unsubscribe(); this.workspaces.currentWorkspaceId$.unsubscribe(); diff --git a/src/plugins/workspace/server/integration_tests/routes.test.ts b/src/plugins/workspace/server/integration_tests/routes.test.ts index 1e0530217f21..e3de40309e72 100644 --- a/src/plugins/workspace/server/integration_tests/routes.test.ts +++ b/src/plugins/workspace/server/integration_tests/routes.test.ts @@ -83,6 +83,29 @@ describe('workspace service api integration test', () => { expect(result.body.success).toEqual(true); expect(typeof result.body.result.id).toBe('string'); }); + it('create with empty/blank name', async () => { + let result = await osdTestServer.request + .post(root, `/api/workspaces`) + .send({ + attributes: { name: '' }, + }) + .expect(400); + + expect(result.body.message).toEqual( + "[request body.attributes.name]: can't be empty or blank." + ); + + result = await osdTestServer.request + .post(root, `/api/workspaces`) + .send({ + attributes: { name: ' ' }, + }) + .expect(400); + + expect(result.body.message).toEqual( + "[request body.attributes.name]: can't be empty or blank." + ); + }); it('create workspace failed when name duplicate', async () => { let result: any = await osdTestServer.request @@ -577,7 +600,10 @@ describe('workspace service api integration test when savedObjects.permission.en .post(root, `/api/workspaces`) .send({ attributes: omitId(testWorkspace), - permissions: { invalid_type: { users: ['foo'] } }, + settings: { + permissions: { invalid_type: { users: ['foo'] } }, + dataSources: [], + }, }) .expect(400); @@ -585,7 +611,10 @@ describe('workspace service api integration test when savedObjects.permission.en .post(root, `/api/workspaces`) .send({ attributes: omitId(testWorkspace), - permissions: { read: { users: ['foo'] } }, + settings: { + permissions: { read: { users: ['foo'] } }, + dataSources: [], + }, }) .expect(200); @@ -613,7 +642,10 @@ describe('workspace service api integration test when savedObjects.permission.en attributes: { ...omitId(testWorkspace), }, - permissions: { write: { users: ['foo'] } }, + settings: { + permissions: { write: { users: ['foo'] } }, + dataSources: [], + }, }) .expect(200); expect(updateResult.body.result).toBe(true); diff --git a/src/plugins/workspace/server/permission_control/client.test.ts b/src/plugins/workspace/server/permission_control/client.test.ts index 4d041cc7df56..a585710da54d 100644 --- a/src/plugins/workspace/server/permission_control/client.test.ts +++ b/src/plugins/workspace/server/permission_control/client.test.ts @@ -102,7 +102,16 @@ describe('PermissionControl', () => { }); const batchValidateResult = await permissionControlClient.batchValidate( httpServerMock.createOpenSearchDashboardsRequest(), - [], + [ + { + id: 'foo', + type: 'dashboard', + }, + { + id: 'bar', + type: 'dashboard', + }, + ], ['read'] ); expect(batchValidateResult.success).toEqual(true); @@ -142,7 +151,16 @@ describe('PermissionControl', () => { }); const batchValidateResult = await permissionControlClient.batchValidate( httpServerMock.createOpenSearchDashboardsRequest(), - [], + [ + { + id: 'foo', + type: 'dashboard', + }, + { + id: 'bar', + type: 'dashboard', + }, + ], ['read'] ); expect(batchValidateResult.success).toEqual(true); @@ -197,4 +215,123 @@ describe('PermissionControl', () => { ); }); }); + + describe('saved objects cache', () => { + it('should not call bulk get again if saved objects cached', async () => { + const permissionControlClient = new SavedObjectsPermissionControl(loggerMock.create()); + const getScopedClient = jest.fn(); + const clientMock = savedObjectsClientMock.create(); + const requestMock = httpServerMock.createOpenSearchDashboardsRequest(); + getScopedClient.mockImplementation((request) => { + return clientMock; + }); + permissionControlClient.setup(getScopedClient, mockAuth); + permissionControlClient.addToCacheAllowlist(requestMock, [ + { + type: 'workspace', + id: 'foo', + }, + ]); + clientMock.bulkGet.mockResolvedValue({ + saved_objects: [ + { + type: 'workspace', + id: 'foo', + attributes: {}, + references: [], + }, + ], + }); + + await permissionControlClient.validate(requestMock, { id: 'foo', type: 'workspace' }, [ + 'read', + ]); + expect(clientMock.bulkGet).toHaveBeenCalledTimes(1); + + await permissionControlClient.validate(requestMock, { id: 'foo', type: 'workspace' }, [ + 'read', + ]); + expect(clientMock.bulkGet).toHaveBeenCalledTimes(1); + }); + it('should call bulk get again for different requests', async () => { + const permissionControlClient = new SavedObjectsPermissionControl(loggerMock.create()); + const getScopedClient = jest.fn(); + const clientMock = savedObjectsClientMock.create(); + const requestMock = httpServerMock.createOpenSearchDashboardsRequest(); + getScopedClient.mockImplementation((request) => { + return clientMock; + }); + permissionControlClient.setup(getScopedClient, mockAuth); + permissionControlClient.addToCacheAllowlist(requestMock, [ + { + type: 'workspace', + id: 'foo', + }, + ]); + clientMock.bulkGet.mockResolvedValue({ + saved_objects: [ + { + type: 'workspace', + id: 'foo', + attributes: {}, + references: [], + }, + ], + }); + + await permissionControlClient.validate(requestMock, { id: 'foo', type: 'workspace' }, [ + 'read', + ]); + expect(clientMock.bulkGet).toHaveBeenCalledTimes(1); + + await permissionControlClient.validate( + httpServerMock.createOpenSearchDashboardsRequest({ + opensearchDashboardsRequestState: { + requestId: '123', + requestUuid: 'another-uuid', + }, + }), + { id: 'foo', type: 'workspace' }, + ['read'] + ); + expect(clientMock.bulkGet).toHaveBeenCalledTimes(2); + }); + it('should call bulk get again after cache been cleared', async () => { + const permissionControlClient = new SavedObjectsPermissionControl(loggerMock.create()); + const getScopedClient = jest.fn(); + const clientMock = savedObjectsClientMock.create(); + const requestMock = httpServerMock.createOpenSearchDashboardsRequest(); + getScopedClient.mockImplementation((request) => { + return clientMock; + }); + permissionControlClient.setup(getScopedClient, mockAuth); + permissionControlClient.addToCacheAllowlist(requestMock, [ + { + type: 'workspace', + id: 'foo', + }, + ]); + clientMock.bulkGet.mockResolvedValue({ + saved_objects: [ + { + type: 'workspace', + id: 'foo', + attributes: {}, + references: [], + }, + ], + }); + + await permissionControlClient.validate(requestMock, { id: 'foo', type: 'workspace' }, [ + 'read', + ]); + expect(clientMock.bulkGet).toHaveBeenCalledTimes(1); + permissionControlClient.clearSavedObjectsCache(requestMock); + + await permissionControlClient.validate(requestMock, { id: 'foo', type: 'workspace' }, [ + 'read', + ]); + expect(clientMock.bulkGet).toHaveBeenCalledTimes(2); + }); + }); }); diff --git a/src/plugins/workspace/server/permission_control/client.ts b/src/plugins/workspace/server/permission_control/client.ts index bdc67f830913..0850690325f1 100644 --- a/src/plugins/workspace/server/permission_control/client.ts +++ b/src/plugins/workspace/server/permission_control/client.ts @@ -13,7 +13,6 @@ import { Principals, SavedObject, WORKSPACE_TYPE, - Permissions, HttpAuth, } from '../../../../core/server'; import { WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID } from '../../common/constants'; @@ -30,6 +29,8 @@ export class SavedObjectsPermissionControl { private readonly logger: Logger; private _getScopedClient?: SavedObjectsServiceStart['getScopedClient']; private auth?: HttpAuth; + private _savedObjectCache: Map = new Map(); + private _shouldCachedSavedObjects: Map = new Map(); /** * Returns a saved objects client that is able to: * 1. Read objects whose type is `workspace` because workspace is a hidden type and the permission control client will need to get the metadata of a specific workspace to do the permission check. @@ -48,11 +49,46 @@ export class SavedObjectsPermissionControl { this.logger = logger; } + private generateSavedObjectKey = ({ type, id }: { type: string; id: string }) => { + return `${type}:${id}`; + }; + private async bulkGetSavedObjects( request: OpenSearchDashboardsRequest, savedObjects: SavedObjectsBulkGetObject[] ) { - return (await this.getScopedClient?.(request)?.bulkGet(savedObjects))?.saved_objects || []; + const requestKey = request.uuid; + const savedObjectsToGet = savedObjects.filter( + (savedObject) => + !this._savedObjectCache.get(requestKey)?.[this.generateSavedObjectKey(savedObject)] + ); + const retrievedSavedObjects = + savedObjectsToGet.length > 0 + ? (await this.getScopedClient?.(request)?.bulkGet(savedObjectsToGet))?.saved_objects || [] + : []; + + const retrievedSavedObjectsMap: { [key: string]: SavedObject } = {}; + retrievedSavedObjects.forEach((savedObject) => { + const savedObjectKey = this.generateSavedObjectKey(savedObject); + if (this._shouldCachedSavedObjects.get(requestKey)?.includes(savedObjectKey)) { + const cachedSavedObjectsMap = this._savedObjectCache.get(requestKey) || {}; + cachedSavedObjectsMap[savedObjectKey] = savedObject; + this._savedObjectCache.set(requestKey, cachedSavedObjectsMap); + } + retrievedSavedObjectsMap[savedObjectKey] = savedObject; + }); + + const results: SavedObject[] = []; + savedObjects.forEach((savedObject) => { + const savedObjectKey = this.generateSavedObjectKey(savedObject); + const foundedSavedObject = + this._savedObjectCache.get(requestKey)?.[savedObjectKey] || + retrievedSavedObjectsMap[savedObjectKey]; + if (foundedSavedObject) { + results.push(foundedSavedObject); + } + }); + return results; } public async setup(getScopedClient: SavedObjectsServiceStart['getScopedClient'], auth: HttpAuth) { this._getScopedClient = getScopedClient; @@ -175,4 +211,30 @@ export class SavedObjectsPermissionControl { result: hasPermissionToAllObjects, }; } + + public addToCacheAllowlist( + request: OpenSearchDashboardsRequest, + savedObjects: Array> + ) { + const requestKey = request.uuid; + this._shouldCachedSavedObjects.set( + requestKey, + Array.from( + new Set([ + ...(this._shouldCachedSavedObjects.get(requestKey) ?? []), + ...savedObjects.map(this.generateSavedObjectKey), + ]) + ) + ); + } + + public clearSavedObjectsCache(request: OpenSearchDashboardsRequest) { + const requestKey = request.uuid; + if (this._shouldCachedSavedObjects.has(requestKey)) { + this._shouldCachedSavedObjects.delete(requestKey); + } + if (this._savedObjectCache.has(requestKey)) { + this._savedObjectCache.delete(requestKey); + } + } } diff --git a/src/plugins/workspace/server/plugin.test.ts b/src/plugins/workspace/server/plugin.test.ts index 0b943cf47252..433fb2703f00 100644 --- a/src/plugins/workspace/server/plugin.test.ts +++ b/src/plugins/workspace/server/plugin.test.ts @@ -4,23 +4,38 @@ */ import { OnPostAuthHandler, OnPreRoutingHandler } from 'src/core/server'; -import { coreMock, httpServerMock } from '../../../core/server/mocks'; +import { coreMock, httpServerMock, uiSettingsServiceMock } from '../../../core/server/mocks'; import { WorkspacePlugin } from './plugin'; -import { getWorkspaceState } from '../../../core/server/utils'; +import { getWorkspaceState, updateWorkspaceState } from '../../../core/server/utils'; import * as utilsExports from './utils'; +import { SavedObjectsPermissionControl } from './permission_control/client'; describe('Workspace server plugin', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + it('#setup', async () => { let value; + const capabilities = {} as any; const setupMock = coreMock.createSetup(); const initializerContextConfigMock = coreMock.createPluginInitializerContext({ enabled: true, }); + const request = httpServerMock.createOpenSearchDashboardsRequest(); + setupMock.capabilities.registerProvider.mockImplementationOnce((fn) => (value = fn())); + setupMock.capabilities.registerSwitcher.mockImplementationOnce((fn) => { + return fn(request, capabilities); + }); + const workspacePlugin = new WorkspacePlugin(initializerContextConfigMock); await workspacePlugin.setup(setupMock); expect(value).toMatchInlineSnapshot(` Object { + "dashboards": Object { + "isDashboardAdmin": false, + }, "workspaces": Object { "enabled": true, "permissionEnabled": true, @@ -28,6 +43,23 @@ describe('Workspace server plugin', () => { } `); expect(setupMock.savedObjects.addClientWrapper).toBeCalledTimes(4); + + let registerSwitcher; + let result; + updateWorkspaceState(request, { isDashboardAdmin: false }); + registerSwitcher = setupMock.capabilities.registerSwitcher.mock.calls[0][0]; + result = registerSwitcher(request, capabilities); + expect(result).toEqual({ dashboards: { isDashboardAdmin: false } }); + + updateWorkspaceState(request, { isDashboardAdmin: true }); + registerSwitcher = setupMock.capabilities.registerSwitcher.mock.calls[0][0]; + result = registerSwitcher(request, capabilities); + expect(result).toEqual({ dashboards: { isDashboardAdmin: true } }); + + updateWorkspaceState(request, { isDashboardAdmin: undefined }); + registerSwitcher = setupMock.capabilities.registerSwitcher.mock.calls[0][0]; + result = registerSwitcher(request, capabilities); + expect(result).toEqual({ dashboards: { isDashboardAdmin: true } }); }); it('#proxyWorkspaceTrafficToRealHandler', async () => { @@ -130,6 +162,162 @@ describe('Workspace server plugin', () => { ); expect(toolKitMock.next).toBeCalledTimes(1); }); + + it('should clear saved objects cache', async () => { + jest.spyOn(utilsExports, 'getPrincipalsFromRequest').mockImplementation(() => ({})); + const clearSavedObjectsCacheMock = jest + .spyOn(SavedObjectsPermissionControl.prototype, 'clearSavedObjectsCache') + .mockImplementationOnce(() => {}); + + await workspacePlugin.setup(setupMock); + const toolKitMock = httpServerMock.createToolkit(); + + expect(setupMock.http.registerOnPreResponse).toHaveBeenCalled(); + const preResponseFn = setupMock.http.registerOnPreResponse.mock.calls[0][0]; + + preResponseFn(requestWithWorkspaceInUrl, { statusCode: 200 }, toolKitMock); + expect(clearSavedObjectsCacheMock).toHaveBeenCalled(); + }); + }); + + describe('#setUpRedirectPage', () => { + const setupMock = coreMock.createSetup(); + const uiSettingsMock = uiSettingsServiceMock.createClient(); + const initializerContextConfigMock = coreMock.createPluginInitializerContext({ + enabled: true, + permission: { + enabled: true, + }, + }); + let registerOnPostAuthFn: OnPostAuthHandler = () => httpServerMock.createResponseFactory().ok(); + setupMock.http.registerOnPostAuth.mockImplementation((fn) => { + registerOnPostAuthFn = fn; + return fn; + }); + const workspacePlugin = new WorkspacePlugin(initializerContextConfigMock); + const response = httpServerMock.createResponseFactory(); + + it('without / request path', async () => { + const request = httpServerMock.createOpenSearchDashboardsRequest({ + path: '/foo', + }); + await workspacePlugin.setup(setupMock); + const toolKitMock = httpServerMock.createToolkit(); + + await registerOnPostAuthFn(request, response, toolKitMock); + expect(toolKitMock.next).toBeCalledTimes(1); + }); + + it('with / request path and no workspaces', async () => { + const request = httpServerMock.createOpenSearchDashboardsRequest({ + path: '/', + }); + await workspacePlugin.setup(setupMock); + const toolKitMock = httpServerMock.createToolkit(); + + await registerOnPostAuthFn(request, response, toolKitMock); + expect(response.redirected).toBeCalledWith({ + headers: { location: '/mock-server-basepath/app/workspace_initial' }, + }); + }); + + it('with / request path and one workspace', async () => { + const request = httpServerMock.createOpenSearchDashboardsRequest({ + path: '/', + }); + const workspaceSetup = await workspacePlugin.setup(setupMock); + const client = workspaceSetup.client; + jest.spyOn(client, 'list').mockResolvedValue({ + success: true, + result: { + total: 1, + per_page: 100, + page: 1, + workspaces: [{ id: 'workspace-1', name: 'workspace-1' }], + }, + }); + const toolKitMock = httpServerMock.createToolkit(); + + await registerOnPostAuthFn(request, response, toolKitMock); + expect(response.redirected).toBeCalledWith({ + headers: { + location: '/mock-server-basepath/w/workspace-1/app/workspace_navigation', + }, + }); + }); + + it('with / request path and more than one workspaces', async () => { + const request = httpServerMock.createOpenSearchDashboardsRequest({ + path: '/', + }); + const workspaceSetup = await workspacePlugin.setup(setupMock); + const client = workspaceSetup.client; + jest.spyOn(client, 'list').mockResolvedValue({ + success: true, + result: { + total: 2, + per_page: 100, + page: 1, + workspaces: [ + { id: 'workspace-1', name: 'workspace-1' }, + { id: 'workspace-2', name: 'workspace-2' }, + ], + }, + }); + const toolKitMock = httpServerMock.createToolkit(); + + await registerOnPostAuthFn(request, response, toolKitMock); + expect(response.redirected).toBeCalledWith({ + headers: { + location: '/mock-server-basepath/app/home', + }, + }); + }); + + it('with / request path and default workspace', async () => { + const request = httpServerMock.createOpenSearchDashboardsRequest({ + path: '/', + }); + setupMock.getStartServices.mockResolvedValue([ + { + ...coreMock.createStart(), + uiSettings: { + asScopedToClient: () => ({ + ...uiSettingsMock, + get: jest.fn().mockImplementation((key) => { + if (key === 'defaultWorkspace') { + return Promise.resolve('defaultWorkspace'); + } + }), + }), + }, + }, + {}, + {}, + ]); + const workspaceSetup = await workspacePlugin.setup(setupMock); + const client = workspaceSetup.client; + jest.spyOn(client, 'list').mockResolvedValue({ + success: true, + result: { + total: 2, + per_page: 100, + page: 1, + workspaces: [ + { id: 'defaultWorkspace', name: 'default-workspace' }, + { id: 'workspace-2', name: 'workspace-2' }, + ], + }, + }); + const toolKitMock = httpServerMock.createToolkit(); + + await registerOnPostAuthFn(request, response, toolKitMock); + expect(response.redirected).toBeCalledWith({ + headers: { + location: '/mock-server-basepath/w/defaultWorkspace/app/workspace_navigation', + }, + }); + }); }); it('#start', async () => { diff --git a/src/plugins/workspace/server/plugin.ts b/src/plugins/workspace/server/plugin.ts index 2ec8447ad817..21f90fbe0d91 100644 --- a/src/plugins/workspace/server/plugin.ts +++ b/src/plugins/workspace/server/plugin.ts @@ -22,6 +22,8 @@ import { PRIORITY_FOR_PERMISSION_CONTROL_WRAPPER, WORKSPACE_UI_SETTINGS_CLIENT_WRAPPER_ID, PRIORITY_FOR_WORKSPACE_UI_SETTINGS_WRAPPER, + WORKSPACE_INITIAL_APP_ID, + WORKSPACE_NAVIGATION_APP_ID, } from '../common/constants'; import { IWorkspaceClientImpl, WorkspacePluginSetup, WorkspacePluginStart } from './types'; import { WorkspaceClient } from './workspace_client'; @@ -30,6 +32,7 @@ import { WorkspaceSavedObjectsClientWrapper } from './saved_objects'; import { cleanWorkspaceId, getWorkspaceIdFromUrl, + getWorkspaceState, updateWorkspaceState, } from '../../../core/server/utils'; import { WorkspaceConflictSavedObjectsClientWrapper } from './saved_objects/saved_objects_wrapper_for_check_workspace_conflict'; @@ -100,6 +103,63 @@ export class WorkspacePlugin implements Plugin { + this.permissionControl?.clearSavedObjectsCache(request); + return toolkit.next(); + }); + } + + private setUpRedirectPage(core: CoreSetup) { + core.http.registerOnPostAuth(async (request, response, toolkit) => { + const path = request.url.pathname; + if (path === '/') { + const workspaceListResponse = await this.client?.list( + { request, logger: this.logger }, + { page: 1, perPage: 100 } + ); + const basePath = core.http.basePath.serverBasePath; + + if (workspaceListResponse?.success && workspaceListResponse.result.total > 0) { + const workspaceList = workspaceListResponse.result.workspaces; + // If user only has one workspace, go to overview page of that workspace + if (workspaceList.length === 1) { + return response.redirected({ + headers: { + location: `${basePath}/w/${workspaceList[0].id}/app/${WORKSPACE_NAVIGATION_APP_ID}`, + }, + }); + } + const [coreStart] = await core.getStartServices(); + const uiSettings = coreStart.uiSettings.asScopedToClient( + coreStart.savedObjects.getScopedClient(request) + ); + // Temporarily use defaultWorkspace as a placeholder + const defaultWorkspaceId = await uiSettings.get('defaultWorkspace'); + const defaultWorkspace = workspaceList.find( + (workspace) => workspace.id === defaultWorkspaceId + ); + // If user has a default workspace configured, go to overview page of that workspace + // If user has more than one workspaces, go to homepage + if (defaultWorkspace) { + return response.redirected({ + headers: { + location: `${basePath}/w/${defaultWorkspace.id}/app/${WORKSPACE_NAVIGATION_APP_ID}`, + }, + }); + } else { + return response.redirected({ + headers: { location: `${basePath}/app/home` }, + }); + } + } + // If user has no workspaces, go to initial page + return response.redirected({ + headers: { location: `${basePath}/app/${WORKSPACE_INITIAL_APP_ID}` }, + }); + } + return toolkit.next(); + }); } constructor(initializerContext: PluginInitializerContext) { @@ -112,7 +172,7 @@ export class WorkspacePlugin implements Plugin { + // If the value is undefined/true, the user is dashboard admin. + const isDashboardAdmin = getWorkspaceState(request).isDashboardAdmin !== false; + return { dashboards: { isDashboardAdmin } }; + }); + + this.setUpRedirectPage(core); return { client: this.client, @@ -168,6 +237,7 @@ export class WorkspacePlugin implements Plugin { + if (!validateWorkspaceColor(color)) { + return 'invalid workspace color format'; + } + }, + }) + ), icon: schema.maybe(schema.string()), defaultVISTheme: schema.maybe(schema.string()), reserved: schema.maybe(schema.boolean()), }; +const workspaceNameSchema = schema.string({ + maxLength: MAX_WORKSPACE_NAME_LENGTH, + validate(value) { + if (!value || value.trim().length === 0) { + return "can't be empty or blank."; + } + }, +}); + const createWorkspaceAttributesSchema = schema.object({ - name: schema.string(), + name: workspaceNameSchema, ...workspaceOptionalAttributesSchema, }); const updateWorkspaceAttributesSchema = schema.object({ - name: schema.maybe(schema.string()), + name: schema.maybe(workspaceNameSchema), ...workspaceOptionalAttributesSchema, }); @@ -82,7 +112,6 @@ export function registerRoutes({ router.handleLegacyErrors(async (context, req, res) => { const result = await client.list( { - context, request: req, logger, }, @@ -109,7 +138,6 @@ export function registerRoutes({ const { id } = req.params; const result = await client.get( { - context, request: req, logger, }, @@ -127,35 +155,32 @@ export function registerRoutes({ validate: { body: schema.object({ attributes: createWorkspaceAttributesSchema, - permissions: schema.maybe(workspacePermissions), + settings: settingsSchema, }), }, }, router.handleLegacyErrors(async (context, req, res) => { - const { attributes, permissions } = req.body; + const { attributes, settings } = req.body; const principals = permissionControlClient?.getPrincipalsFromRequest(req); - const createPayload: Omit = attributes; + const createPayload: Omit & { + dataSources?: string[]; + } = attributes; if (isPermissionControlEnabled) { - createPayload.permissions = permissions; - // Assign workspace owner to current user + createPayload.permissions = settings.permissions; if (!!principals?.users?.length) { - const acl = new ACL(permissions); const currentUserId = principals.users[0]; - [WorkspacePermissionMode.Write, WorkspacePermissionMode.LibraryWrite].forEach( - (permissionMode) => { - if (!acl.hasPermission([permissionMode], { users: [currentUserId] })) { - acl.addPermission([permissionMode], { users: [currentUserId] }); - } - } + const acl = new ACL( + transferCurrentUserInPermissions(currentUserId, settings.permissions) ); createPayload.permissions = acl.getPermissions(); } } + createPayload.dataSources = settings.dataSources; + const result = await client.create( { - context, request: req, logger, }, @@ -173,24 +198,24 @@ export function registerRoutes({ }), body: schema.object({ attributes: updateWorkspaceAttributesSchema, - permissions: schema.maybe(workspacePermissions), + settings: settingsSchema, }), }, }, router.handleLegacyErrors(async (context, req, res) => { const { id } = req.params; - const { attributes, permissions } = req.body; + const { attributes, settings } = req.body; const result = await client.update( { - context, request: req, logger, }, id, { ...attributes, - ...(isPermissionControlEnabled ? { permissions } : {}), + ...(isPermissionControlEnabled ? { permissions: settings.permissions } : {}), + ...{ dataSources: settings.dataSources }, } ); return res.ok({ body: result }); @@ -210,7 +235,6 @@ export function registerRoutes({ const result = await client.delete( { - context, request: req, logger, }, diff --git a/src/plugins/workspace/server/saved_objects/integration_tests/saved_objects_wrapper_for_check_workspace_conflict.test.ts b/src/plugins/workspace/server/saved_objects/integration_tests/saved_objects_wrapper_for_check_workspace_conflict.test.ts index 1700c51ee11e..9405c59a4796 100644 --- a/src/plugins/workspace/server/saved_objects/integration_tests/saved_objects_wrapper_for_check_workspace_conflict.test.ts +++ b/src/plugins/workspace/server/saved_objects/integration_tests/saved_objects_wrapper_for_check_workspace_conflict.test.ts @@ -381,10 +381,10 @@ describe('saved_objects_wrapper_for_check_workspace_conflict integration test', ], }); - const findAdvancedSettings = await osdTestServer.request - .get(root, `/api/saved_objects/_find?type=${advancedSettings.type}`) + const getAdvancedSettingsResult = await osdTestServer.request + .get(root, `/api/saved_objects/${advancedSettings.type}/${packageInfo.version}`) .expect(200); - expect(findAdvancedSettings.body.total).toEqual(1); + expect(getAdvancedSettingsResult.body.id).toBe(packageInfo.version); }); it('checkConflicts when importing ndjson', async () => { @@ -493,7 +493,7 @@ describe('saved_objects_wrapper_for_check_workspace_conflict integration test', const importWithWorkspacesResult = await osdTestServer.request .post( root, - `/api/saved_objects/_import?workspaces=${createdFooWorkspace.id}&overwrite=true` + `/api/saved_objects/_import?workspaces=${createdFooWorkspace.id}&overwrite=true&dataSourceEnabled=true` ) .attach( 'file', diff --git a/src/plugins/workspace/server/saved_objects/integration_tests/workspace_saved_objects_client_wrapper.test.ts b/src/plugins/workspace/server/saved_objects/integration_tests/workspace_saved_objects_client_wrapper.test.ts index b5399de9fb5d..6aeb2ebc8610 100644 --- a/src/plugins/workspace/server/saved_objects/integration_tests/workspace_saved_objects_client_wrapper.test.ts +++ b/src/plugins/workspace/server/saved_objects/integration_tests/workspace_saved_objects_client_wrapper.test.ts @@ -241,20 +241,20 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { }); describe('find', () => { - it('should throw not authorized error when user not permitted', async () => { - let error; - try { - await notPermittedSavedObjectedClient.find({ - type: 'dashboard', - workspaces: ['workspace-1'], - perPage: 999, - page: 1, - }); - } catch (e) { - error = e; - } + it('should return empty result if user not permitted', async () => { + const result = await notPermittedSavedObjectedClient.find({ + type: 'dashboard', + workspaces: ['workspace-1'], + perPage: 999, + page: 1, + }); - expect(SavedObjectsErrorHelpers.isNotAuthorizedError(error)).toBe(true); + expect(result).toEqual({ + saved_objects: [], + total: 0, + page: 1, + per_page: 999, + }); }); it('should return consistent inner workspace data when user permitted', async () => { @@ -269,6 +269,36 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { true ); }); + + it('should return consistent result when workspaces and ACLSearchParams not provided', async () => { + const result = await permittedSavedObjectedClient.find({ + type: 'dashboard', + perPage: 999, + page: 1, + }); + + expect(result.saved_objects).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: 'inner-workspace-dashboard-1' }), + expect.objectContaining({ id: 'acl-controlled-dashboard-2' }), + ]) + ); + }); + + it('should return acl controled dashboards when only ACLSearchParams provided', async () => { + const result = await permittedSavedObjectedClient.find({ + type: 'dashboard', + perPage: 999, + page: 1, + ACLSearchParams: { + permissionModes: ['read', 'write'], + }, + }); + + expect(result.saved_objects).toEqual( + expect.arrayContaining([expect.objectContaining({ id: 'acl-controlled-dashboard-2' })]) + ); + }); }); describe('create', () => { diff --git a/src/plugins/workspace/server/saved_objects/saved_objects_wrapper_for_check_workspace_conflict.test.ts b/src/plugins/workspace/server/saved_objects/saved_objects_wrapper_for_check_workspace_conflict.test.ts index f5613550a541..118b7840ce3a 100644 --- a/src/plugins/workspace/server/saved_objects/saved_objects_wrapper_for_check_workspace_conflict.test.ts +++ b/src/plugins/workspace/server/saved_objects/saved_objects_wrapper_for_check_workspace_conflict.test.ts @@ -499,20 +499,4 @@ describe('WorkspaceConflictSavedObjectsClientWrapper', () => { ); }); }); - - describe('find', () => { - beforeEach(() => { - mockedClient.find.mockClear(); - }); - - it(`workspaces parameters should be removed when finding data sources`, async () => { - await wrapperClient.find({ - type: DATA_SOURCE_SAVED_OBJECT_TYPE, - workspaces: ['foo'], - }); - expect(mockedClient.find).toBeCalledWith({ - type: DATA_SOURCE_SAVED_OBJECT_TYPE, - }); - }); - }); }); diff --git a/src/plugins/workspace/server/saved_objects/saved_objects_wrapper_for_check_workspace_conflict.ts b/src/plugins/workspace/server/saved_objects/saved_objects_wrapper_for_check_workspace_conflict.ts index 0c4447e69e21..c71e59d3ab72 100644 --- a/src/plugins/workspace/server/saved_objects/saved_objects_wrapper_for_check_workspace_conflict.ts +++ b/src/plugins/workspace/server/saved_objects/saved_objects_wrapper_for_check_workspace_conflict.ts @@ -50,11 +50,6 @@ export class WorkspaceConflictSavedObjectsClientWrapper { private isConfigType(type: SavedObject['type']): boolean { return type === UI_SETTINGS_SAVED_OBJECTS_TYPE; } - private formatFindParams(options: SavedObjectsFindOptions): SavedObjectsFindOptions { - const isListingDataSource = this.isDataSourceType(options.type); - const { workspaces, ...otherOptions } = options; - return isListingDataSource ? otherOptions : options; - } /** * Workspace is a concept to manage saved objects and the `workspaces` field of each object indicates workspaces the object belongs to. @@ -412,10 +407,7 @@ export class WorkspaceConflictSavedObjectsClientWrapper { bulkCreate: bulkCreateWithWorkspaceConflictCheck, checkConflicts: checkConflictWithWorkspaceConflictCheck, delete: wrapperOptions.client.delete, - find: (options: SavedObjectsFindOptions) => - // TODO: The `formatFindParams` is a workaround for 2.14 to always list global data sources, - // should remove this workaround in the upcoming release once readonly share is available. - wrapperOptions.client.find(this.formatFindParams(options)), + find: wrapperOptions.client.find, bulkGet: wrapperOptions.client.bulkGet, get: wrapperOptions.client.get, update: wrapperOptions.client.update, diff --git a/src/plugins/workspace/server/saved_objects/workspace_id_consumer_wrapper.test.ts b/src/plugins/workspace/server/saved_objects/workspace_id_consumer_wrapper.test.ts index 4e3b26d421a8..2db8d146822f 100644 --- a/src/plugins/workspace/server/saved_objects/workspace_id_consumer_wrapper.test.ts +++ b/src/plugins/workspace/server/saved_objects/workspace_id_consumer_wrapper.test.ts @@ -5,7 +5,6 @@ import { updateWorkspaceState } from '../../../../core/server/utils'; import { SavedObject } from '../../../../core/public'; -import { PUBLIC_WORKSPACE_ID } from '../../../../core/server'; import { httpServerMock, savedObjectsClientMock, coreMock } from '../../../../core/server/mocks'; import { WorkspaceIdConsumerWrapper } from './workspace_id_consumer_wrapper'; @@ -116,69 +115,8 @@ describe('WorkspaceIdConsumerWrapper', () => { }); }); - it(`Should set workspacesSearchOperator to OR when search with public workspace`, async () => { - await wrapperClient.find({ - type: 'dashboard', - workspaces: [PUBLIC_WORKSPACE_ID], - }); - expect(mockedClient.find).toBeCalledWith({ - type: 'dashboard', - workspaces: [PUBLIC_WORKSPACE_ID], - workspacesSearchOperator: 'OR', - }); - }); - - it(`Should set workspace as pubic when workspace is not specified`, async () => { - const mockRequest = httpServerMock.createOpenSearchDashboardsRequest(); - updateWorkspaceState(mockRequest, {}); - const mockedWrapperClient = wrapperInstance.wrapperFactory({ - client: mockedClient, - typeRegistry: requestHandlerContext.savedObjects.typeRegistry, - request: mockRequest, - }); - await mockedWrapperClient.find({ - type: ['dashboard', 'visualization'], - }); - expect(mockedClient.find).toBeCalledWith({ - type: ['dashboard', 'visualization'], - workspaces: [PUBLIC_WORKSPACE_ID], - workspacesSearchOperator: 'OR', - }); - }); - - it(`Should remove public workspace when permission control is enabled`, async () => { - const consumer = new WorkspaceIdConsumerWrapper(true); - const client = consumer.wrapperFactory({ - client: mockedClient, - typeRegistry: requestHandlerContext.savedObjects.typeRegistry, - request: workspaceEnabledMockRequest, - }); - await client.find({ - type: 'dashboard', - workspaces: ['bar', PUBLIC_WORKSPACE_ID], - }); - expect(mockedClient.find).toBeCalledWith({ - type: 'dashboard', - workspaces: ['bar'], - workspacesSearchOperator: 'OR', - }); - }); - - it(`Should not override workspacesSearchOperator when workspacesSearchOperator is specified`, async () => { - await wrapperClient.find({ - type: 'dashboard', - workspaces: [PUBLIC_WORKSPACE_ID], - workspacesSearchOperator: 'AND', - }); - expect(mockedClient.find).toBeCalledWith({ - type: 'dashboard', - workspaces: [PUBLIC_WORKSPACE_ID], - workspacesSearchOperator: 'AND', - }); - }); - - it(`Should not pass a empty workspace array`, async () => { - const workspaceIdConsumerWrapper = new WorkspaceIdConsumerWrapper(true); + it(`Should pass a empty workspace array`, async () => { + const workspaceIdConsumerWrapper = new WorkspaceIdConsumerWrapper(); const mockRequest = httpServerMock.createOpenSearchDashboardsRequest(); updateWorkspaceState(mockRequest, {}); const mockedWrapperClient = workspaceIdConsumerWrapper.wrapperFactory({ @@ -189,10 +127,8 @@ describe('WorkspaceIdConsumerWrapper', () => { await mockedWrapperClient.find({ type: ['dashboard', 'visualization'], }); - // empty workspace array will get deleted expect(mockedClient.find).toBeCalledWith({ type: ['dashboard', 'visualization'], - workspacesSearchOperator: 'OR', }); }); }); diff --git a/src/plugins/workspace/server/saved_objects/workspace_id_consumer_wrapper.ts b/src/plugins/workspace/server/saved_objects/workspace_id_consumer_wrapper.ts index 63f19b5dd0f7..1f9e196d1f49 100644 --- a/src/plugins/workspace/server/saved_objects/workspace_id_consumer_wrapper.ts +++ b/src/plugins/workspace/server/saved_objects/workspace_id_consumer_wrapper.ts @@ -12,8 +12,6 @@ import { SavedObjectsCheckConflictsObject, OpenSearchDashboardsRequest, SavedObjectsFindOptions, - PUBLIC_WORKSPACE_ID, - WORKSPACE_TYPE, } from '../../../../core/server'; type WorkspaceOptions = Pick | undefined; @@ -29,7 +27,8 @@ export class WorkspaceIdConsumerWrapper { const workspaceIdsInUserOptions = options?.workspaces; let finalWorkspaces: string[] = []; if (options?.hasOwnProperty('workspaces')) { - finalWorkspaces = workspaceIdsInUserOptions || []; + // In order to get all data sources in workspace, use * to skip appending workspace id automatically + finalWorkspaces = (workspaceIdsInUserOptions || []).filter((id) => id !== '*'); } else if (workspaceIdParsedFromRequest) { finalWorkspaces = [workspaceIdParsedFromRequest]; } @@ -40,14 +39,6 @@ export class WorkspaceIdConsumerWrapper { }; } - private isWorkspaceType(type: SavedObjectsFindOptions['type']): boolean { - if (Array.isArray(type)) { - return type.every((item) => item === WORKSPACE_TYPE); - } - - return type === WORKSPACE_TYPE; - } - public wrapperFactory: SavedObjectsClientWrapperFactory = (wrapperOptions) => { return { ...wrapperOptions.client, @@ -75,32 +66,9 @@ export class WorkspaceIdConsumerWrapper { ), delete: wrapperOptions.client.delete, find: (options: SavedObjectsFindOptions) => { - const findOptions = this.formatWorkspaceIdParams(wrapperOptions.request, options); - if (this.isWorkspaceType(findOptions.type)) { - return wrapperOptions.client.find(findOptions); - } - - // if workspace is enabled, we always find by workspace - if (!findOptions.workspaces || findOptions.workspaces.length === 0) { - findOptions.workspaces = [PUBLIC_WORKSPACE_ID]; - } - - // `PUBLIC_WORKSPACE_ID` includes both saved objects without any workspace and with `PUBLIC_WORKSPACE_ID` workspace - const index = findOptions.workspaces - ? findOptions.workspaces.indexOf(PUBLIC_WORKSPACE_ID) - : -1; - if (!findOptions.workspacesSearchOperator && findOptions.workspaces && index !== -1) { - findOptions.workspacesSearchOperator = 'OR'; - // remove this deletion logic when public workspace becomes to real - if (this.isPermissionControlEnabled) { - // remove public workspace to make sure we can pass permission control validation, more details in `WorkspaceSavedObjectsClientWrapper` - findOptions.workspaces.splice(index, 1); - } - } - if (findOptions.workspaces && findOptions.workspaces.length === 0) { - delete findOptions.workspaces; - } - return wrapperOptions.client.find(findOptions); + return wrapperOptions.client.find( + this.formatWorkspaceIdParams(wrapperOptions.request, options) + ); }, bulkGet: wrapperOptions.client.bulkGet, get: wrapperOptions.client.get, @@ -112,5 +80,5 @@ export class WorkspaceIdConsumerWrapper { }; }; - constructor(private isPermissionControlEnabled?: boolean) {} + constructor() {} } diff --git a/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.test.ts b/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.test.ts index 79e1b25cd823..85d372b76177 100644 --- a/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.test.ts +++ b/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.test.ts @@ -7,6 +7,7 @@ import { getWorkspaceState, updateWorkspaceState } from '../../../../core/server import { SavedObjectsErrorHelpers } from '../../../../core/server'; import { WorkspaceSavedObjectsClientWrapper } from './workspace_saved_objects_client_wrapper'; import { httpServerMock } from '../../../../core/server/mocks'; +import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../../data_source/common'; const DASHBOARD_ADMIN = 'dashnoard_admin'; const NO_DASHBOARD_ADMIN = 'no_dashnoard_admin'; @@ -46,6 +47,23 @@ const generateWorkspaceSavedObjectsClientWrapper = (role = NO_DASHBOARD_ADMIN) = id: 'not-permitted-workspace', attributes: { name: 'Not permitted workspace' }, }, + { + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + id: 'global-data-source', + attributes: { title: 'Global data source' }, + }, + { + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + id: 'workspace-1-data-source', + attributes: { title: 'Workspace 1 data source' }, + workspaces: ['workspace-1'], + }, + { + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + id: 'workspace-2-data-source', + attributes: { title: 'Workspace 2 data source' }, + workspaces: ['mock-request-workspace-id'], + }, ]; const clientMock = { get: jest.fn().mockImplementation(async (type, id) => { @@ -77,10 +95,21 @@ const generateWorkspaceSavedObjectsClientWrapper = (role = NO_DASHBOARD_ADMIN) = ), }; }), - find: jest.fn(), + find: jest.fn().mockImplementation(({ type, workspaces }) => { + const savedObjects = savedObjectsStore.filter( + (item) => + item.type === type && + (!workspaces || item.workspaces?.some((workspaceId) => workspaces.includes(workspaceId))) + ); + return { + saved_objects: savedObjects, + total: savedObjects.length, + }; + }), deleteByWorkspace: jest.fn(), }; const requestMock = httpServerMock.createOpenSearchDashboardsRequest(); + updateWorkspaceState(requestMock, { requestWorkspaceId: 'mock-request-workspace-id' }); if (role === DASHBOARD_ADMIN) updateWorkspaceState(requestMock, { isDashboardAdmin: true }); const wrapperOptions = { client: clientMock, @@ -100,6 +129,7 @@ const generateWorkspaceSavedObjectsClientWrapper = (role = NO_DASHBOARD_ADMIN) = getPrincipalsFromRequest: jest.fn().mockImplementation(() => { return { users: ['user-1'] }; }), + addToCacheAllowlist: jest.fn(), }; const wrapper = new WorkspaceSavedObjectsClientWrapper(permissionControlMock); @@ -535,6 +565,41 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { expect(clientMock.get).toHaveBeenCalledWith(...getArgs); expect(result).toMatchInlineSnapshot(`[Error: Not Found]`); }); + + it('should validate data source workspace field', async () => { + const { wrapper } = generateWorkspaceSavedObjectsClientWrapper(); + let errorCatched; + try { + await wrapper.get('data-source', 'workspace-1-data-source'); + } catch (e) { + errorCatched = e; + } + expect(errorCatched?.message).toEqual( + 'Invalid data source permission, please associate it to current workspace' + ); + + const result = await wrapper.get('data-source', 'workspace-2-data-source'); + expect(result).toEqual({ + attributes: { + title: 'Workspace 2 data source', + }, + id: 'workspace-2-data-source', + type: 'data-source', + workspaces: ['mock-request-workspace-id'], + }); + }); + + it('should not validate data source when not in workspace', async () => { + const { wrapper, requestMock } = generateWorkspaceSavedObjectsClientWrapper(); + updateWorkspaceState(requestMock, { requestWorkspaceId: undefined }); + const result = await wrapper.get('data-source', 'workspace-1-data-source'); + expect(result).toEqual({ + type: DATA_SOURCE_SAVED_OBJECT_TYPE, + id: 'workspace-1-data-source', + attributes: { title: 'Workspace 1 data source' }, + workspaces: ['workspace-1'], + }); + }); }); describe('bulk get', () => { it("should call permission validate with object's workspace and throw permission error", async () => { @@ -600,73 +665,85 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { {} ); }); - }); - describe('find', () => { - it('should call client.find with ACLSearchParams for workspace type', async () => { - const { wrapper, clientMock } = generateWorkspaceSavedObjectsClientWrapper(); - await wrapper.find({ - type: 'workspace', - }); - expect(clientMock.find).toHaveBeenCalledWith({ - type: 'workspace', - ACLSearchParams: { - principals: { - users: ['user-1'], + it('should validate data source workspace field', async () => { + const { wrapper } = generateWorkspaceSavedObjectsClientWrapper(); + let errorCatched; + try { + await wrapper.bulkGet([ + { + type: 'data-source', + id: 'workspace-1-data-source', }, - permissionModes: ['read', 'write'], + ]); + } catch (e) { + errorCatched = e; + } + expect(errorCatched?.message).toEqual( + 'Invalid data source permission, please associate it to current workspace' + ); + + const result = await await wrapper.bulkGet([ + { + type: 'data-source', + id: 'workspace-2-data-source', }, - }); - }); - it('should call client.find with ACLSearchParams if no workspaces is provided', async () => { - const { wrapper, clientMock } = generateWorkspaceSavedObjectsClientWrapper(); - // no workspaces - await wrapper.find({ - type: 'dashboards', - }); - expect(clientMock.find).toHaveBeenCalledWith({ - type: 'dashboards', - ACLSearchParams: { - principals: { - users: ['user-1'], + ]); + expect(result).toEqual({ + saved_objects: [ + { + attributes: { + title: 'Workspace 2 data source', + }, + id: 'workspace-2-data-source', + type: 'data-source', + workspaces: ['mock-request-workspace-id'], }, - permissionModes: ['read', 'write'], - }, + ], }); + }); - // workspaces parameter is undefined - clientMock.find.mockReset(); - await wrapper.find({ - type: 'dashboards', - workspaces: undefined, - }); - expect(clientMock.find).toHaveBeenLastCalledWith({ - type: 'dashboards', - ACLSearchParams: { - principals: { - users: ['user-1'], - }, - permissionModes: ['read', 'write'], + it('should not validate data source when not in workspace', async () => { + const { wrapper, requestMock } = generateWorkspaceSavedObjectsClientWrapper(); + updateWorkspaceState(requestMock, { requestWorkspaceId: undefined }); + const result = await wrapper.bulkGet([ + { + type: 'data-source', + id: 'workspace-1-data-source', }, + ]); + expect(result).toEqual({ + saved_objects: [ + { + attributes: { + title: 'Workspace 1 data source', + }, + id: 'workspace-1-data-source', + type: 'data-source', + workspaces: ['workspace-1'], + }, + ], }); - - // empty workspaces array - clientMock.find.mockReset(); + }); + }); + describe('find', () => { + it('should call client.find with consistent params when ACLSearchParams and workspaceOperator not provided', async () => { + const { wrapper, clientMock } = generateWorkspaceSavedObjectsClientWrapper(); await wrapper.find({ - type: 'dashboards', - workspaces: [], + type: 'workspace', }); - expect(clientMock.find).toHaveBeenLastCalledWith({ - type: 'dashboards', - workspaces: [], + expect(clientMock.find).toHaveBeenCalledWith({ + type: 'workspace', ACLSearchParams: { principals: { users: ['user-1'], }, permissionModes: ['read', 'write'], }, + workspaces: ['workspace-1'], + workspacesSearchOperator: 'OR', }); }); - it('should call client.find with only read permission if find workspace and permissionModes provided', async () => { + it('should call client.find with ACLSearchParams when only ACLSearchParams provided', async () => { const { wrapper, clientMock } = generateWorkspaceSavedObjectsClientWrapper(); await wrapper.find({ type: 'workspace', @@ -684,60 +761,15 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { }, }); }); - it('should throw workspace permission error if provided workspaces not permitted', async () => { - const { wrapper } = generateWorkspaceSavedObjectsClientWrapper(); - let errorCatched; - try { - errorCatched = await wrapper.find({ - type: 'dashboard', - workspaces: ['not-permitted-workspace'], - }); - } catch (e) { - errorCatched = e; - } - expect(errorCatched?.message).toEqual('Invalid workspace permission'); - }); - it('should remove not permitted workspace and call client.find with arguments', async () => { + it('should call client.find with filtered workspaces when only workspaces provided', async () => { const { wrapper, clientMock } = generateWorkspaceSavedObjectsClientWrapper(); await wrapper.find({ type: 'dashboard', - workspaces: ['not-permitted-workspace', 'workspace-1'], + workspaces: ['workspace-1', 'workspace-2'], }); expect(clientMock.find).toHaveBeenCalledWith({ type: 'dashboard', workspaces: ['workspace-1'], - ACLSearchParams: {}, - }); - }); - it('should find permitted workspaces with filtered permission modes', async () => { - const { wrapper, scopedClientMock } = generateWorkspaceSavedObjectsClientWrapper(); - await wrapper.find({ - type: 'dashboard', - ACLSearchParams: { - permissionModes: ['read', 'library_read'], - }, - }); - expect(scopedClientMock.find).toHaveBeenCalledWith( - expect.objectContaining({ - type: 'workspace', - ACLSearchParams: { - permissionModes: ['library_read'], - principals: { users: ['user-1'] }, - }, - }) - ); - }); - it('should call client.find with arguments if not workspace type and no options.workspace', async () => { - const { wrapper, clientMock } = generateWorkspaceSavedObjectsClientWrapper(); - await wrapper.find({ - type: 'dashboard', - }); - expect(clientMock.find).toHaveBeenCalledWith({ - type: 'dashboard', - ACLSearchParams: { - permissionModes: ['read', 'write'], - principals: { users: ['user-1'] }, - }, }); }); }); @@ -780,6 +812,7 @@ describe('WorkspaceSavedObjectsClientWrapper', () => { } = generateWorkspaceSavedObjectsClientWrapper(DASHBOARD_ADMIN); expect(getWorkspaceState(requestMock)).toEqual({ isDashboardAdmin: true, + requestWorkspaceId: 'mock-request-workspace-id', }); it('should bypass permission check for call client.delete', async () => { const deleteArgs = ['dashboard', 'not-permitted-dashboard'] as const; diff --git a/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts b/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts index 06701fba338a..c5025da8c9b9 100644 --- a/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts +++ b/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts @@ -33,6 +33,7 @@ import { WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID, WorkspacePermissionMode, } from '../../common/constants'; +import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../../data_source/common'; // Can't throw unauthorized for now, the page will be refreshed if unauthorized const generateWorkspacePermissionError = () => @@ -53,6 +54,15 @@ const generateSavedObjectsPermissionError = () => ) ); +const generateDataSourcePermissionError = () => + SavedObjectsErrorHelpers.decorateForbiddenError( + new Error( + i18n.translate('saved_objects.data_source.invalidate', { + defaultMessage: 'Invalid data source permission, please associate it to current workspace', + }) + ) + ); + const generateOSDAdminPermissionError = () => SavedObjectsErrorHelpers.decorateForbiddenError( new Error( @@ -62,14 +72,16 @@ const generateOSDAdminPermissionError = () => ) ); -const intersection = (...args: T[][]) => { - const occursCountMap: { [key: string]: number } = {}; - for (let i = 0; i < args.length; i++) { - new Set(args[i]).forEach((key) => { - occursCountMap[key] = (occursCountMap[key] || 0) + 1; - }); - } - return Object.keys(occursCountMap).filter((key) => occursCountMap[key] === args.length); +const getWorkspacesFromSavedObjects = (savedObjects: SavedObject[]) => { + return savedObjects + .reduce( + (previous, { workspaces }) => Array.from(new Set([...previous, ...(workspaces ?? [])])), + [] + ) + .map((id) => ({ + type: WORKSPACE_TYPE, + id, + })); }; const getDefaultValuesForEmpty = (values: T[] | undefined, defaultValues: T[]) => { @@ -126,31 +138,18 @@ export class WorkspaceSavedObjectsClientWrapper { return false; } for (const workspaceId of workspaces) { - const validateResult = await this.permissionControl.validate( + const validateResult = await this.validateMultiWorkspacesPermissions( + [workspaceId], request, - { - type: WORKSPACE_TYPE, - id: workspaceId, - }, permissionModes ); - if (validateResult?.result) { + if (validateResult) { return true; } } return false; }; - /** - * check if the type include workspace - * Workspace permission check is totally different from object permission check. - * @param type - * @returns - */ - private isRelatedToWorkspace(type: string | string[]): boolean { - return type === WORKSPACE_TYPE || (Array.isArray(type) && type.includes(WORKSPACE_TYPE)); - } - private async validateWorkspacesAndSavedObjectsPermissions( savedObject: Pick, request: OpenSearchDashboardsRequest, @@ -199,6 +198,15 @@ export class WorkspaceSavedObjectsClientWrapper { return hasPermission; } + // Data source is a workspace level object, validate if the request has access to the data source within the requested workspace. + private validateDataSourcePermissions = ( + object: SavedObject, + request: OpenSearchDashboardsRequest + ) => { + const requestWorkspaceId = getWorkspaceState(request).requestWorkspaceId; + return !requestWorkspaceId || !!object.workspaces?.includes(requestWorkspaceId); + }; + private getWorkspaceTypeEnabledClient(request: OpenSearchDashboardsRequest) { return this.getScopedClient?.(request, { includedHiddenTypes: [WORKSPACE_TYPE], @@ -266,6 +274,10 @@ export class WorkspaceSavedObjectsClientWrapper { options?: SavedObjectsBulkUpdateOptions ): Promise> => { const objectsToUpdate = await wrapperOptions.client.bulkGet(objects, options); + this.permissionControl.addToCacheAllowlist( + wrapperOptions.request, + getWorkspacesFromSavedObjects(objectsToUpdate.saved_objects) + ); for (const object of objectsToUpdate.saved_objects) { const permitted = await validateUpdateWithWorkspacePermission(object); @@ -323,6 +335,10 @@ export class WorkspaceSavedObjectsClientWrapper { throw error; } } + this.permissionControl.addToCacheAllowlist( + wrapperOptions.request, + getWorkspacesFromSavedObjects([rawObject]) + ); if ( !(await this.validateWorkspacesAndSavedObjectsPermissions( rawObject, @@ -398,6 +414,16 @@ export class WorkspaceSavedObjectsClientWrapper { ): Promise> => { const objectToGet = await wrapperOptions.client.get(type, id, options); + if (objectToGet.type === DATA_SOURCE_SAVED_OBJECT_TYPE) { + const hasPermission = this.validateDataSourcePermissions( + objectToGet, + wrapperOptions.request + ); + if (!hasPermission) { + throw generateDataSourcePermissionError(); + } + } + if ( !(await this.validateWorkspacesAndSavedObjectsPermissions( objectToGet, @@ -417,8 +443,19 @@ export class WorkspaceSavedObjectsClientWrapper { options: SavedObjectsBaseOptions = {} ): Promise> => { const objectToBulkGet = await wrapperOptions.client.bulkGet(objects, options); + this.permissionControl.addToCacheAllowlist( + wrapperOptions.request, + getWorkspacesFromSavedObjects(objectToBulkGet.saved_objects) + ); for (const object of objectToBulkGet.saved_objects) { + if (object.type === DATA_SOURCE_SAVED_OBJECT_TYPE) { + const hasPermission = this.validateDataSourcePermissions(object, wrapperOptions.request); + if (!hasPermission) { + throw generateDataSourcePermissionError(); + } + } + if ( !(await this.validateWorkspacesAndSavedObjectsPermissions( object, @@ -439,92 +476,47 @@ export class WorkspaceSavedObjectsClientWrapper { options: SavedObjectsFindOptions ) => { const principals = this.permissionControl.getPrincipalsFromRequest(wrapperOptions.request); - if (!options.ACLSearchParams) { - options.ACLSearchParams = {}; - } - - if (this.isRelatedToWorkspace(options.type)) { - /** - * - * This case is for finding workspace saved objects, will use passed permissionModes - * and override passed principals from request to get all readable workspaces. - * - */ - options.ACLSearchParams.permissionModes = getDefaultValuesForEmpty( - options.ACLSearchParams.permissionModes, - [WorkspacePermissionMode.Read, WorkspacePermissionMode.Write] - ); - options.ACLSearchParams.principals = principals; + const permittedWorkspaceIds = ( + await this.getWorkspaceTypeEnabledClient(wrapperOptions.request).find({ + type: WORKSPACE_TYPE, + perPage: 999, + ACLSearchParams: { + principals, + permissionModes: [ + WorkspacePermissionMode.LibraryRead, + WorkspacePermissionMode.LibraryWrite, + ], + }, + // By declaring workspaces as null, + // workspaces won't be appended automatically into the options. + // or workspaces can not be found because workspace object do not have `workspaces` field. + workspaces: null, + }) + ).saved_objects.map((item) => item.id); + + if (!options.workspaces && !options.ACLSearchParams) { + options.workspaces = permittedWorkspaceIds; + options.ACLSearchParams = { + permissionModes: [WorkspacePermissionMode.Read, WorkspacePermissionMode.Write], + principals, + }; + options.workspacesSearchOperator = 'OR'; } else { - /** - * Workspace is a hidden type so that we need to - * initialize a new saved objects client with workspace enabled to retrieve all the workspaces with permission. - */ - const permittedWorkspaceIds = ( - await this.getWorkspaceTypeEnabledClient(wrapperOptions.request).find({ - type: WORKSPACE_TYPE, - perPage: 999, - ACLSearchParams: { - principals, - /** - * The permitted workspace ids will be passed to the options.workspaces - * or options.ACLSearchParams.workspaces. These two were indicated the saved - * objects data inner specific workspaces. We use Library related permission here. - * For outside passed permission modes, it may contains other permissions. Add a intersection - * here to make sure only Library related permission modes will be used. - */ - permissionModes: getDefaultValuesForEmpty( - options.ACLSearchParams.permissionModes - ? intersection(options.ACLSearchParams.permissionModes, [ - WorkspacePermissionMode.LibraryRead, - WorkspacePermissionMode.LibraryWrite, - ]) - : [], - [WorkspacePermissionMode.LibraryRead, WorkspacePermissionMode.LibraryWrite] - ), - }, - // By declaring workspaces as null, - // workspaces won't be appended automatically into the options. - // or workspaces can not be found because workspace object do not have `workspaces` field. - workspaces: null, - }) - ).saved_objects.map((item) => item.id); - - if (options.workspaces && options.workspaces.length > 0) { - const permittedWorkspaces = options.workspaces.filter((item) => - permittedWorkspaceIds.includes(item) - ); - if (!permittedWorkspaces.length) { - /** - * If user does not have any one workspace access - * deny the request - */ - throw SavedObjectsErrorHelpers.decorateNotAuthorizedError( - new Error( - i18n.translate('workspace.permission.invalidate', { - defaultMessage: 'Invalid workspace permission', - }) - ) - ); - } - - /** - * Overwrite the options.workspaces when user has access on partial workspaces. - */ - options.workspaces = permittedWorkspaces; - } else { - /** - * If no workspaces present, find all the docs that - * ACL matches read / write / user passed permission - */ - options.ACLSearchParams.permissionModes = getDefaultValuesForEmpty( - options.ACLSearchParams.permissionModes, - [WorkspacePermissionMode.Read, WorkspacePermissionMode.Write] + if (options.workspaces) { + options.workspaces = options.workspaces.filter((workspaceId) => + permittedWorkspaceIds.includes(workspaceId) ); - options.ACLSearchParams.principals = principals; + } + if (options.ACLSearchParams) { + options.ACLSearchParams = { + permissionModes: getDefaultValuesForEmpty(options.ACLSearchParams.permissionModes, [ + WorkspacePermissionMode.Read, + WorkspacePermissionMode.Write, + ]), + principals, + }; } } - return await wrapperOptions.client.find(options); }; diff --git a/src/plugins/workspace/server/saved_objects/workspace_ui_settings_client_wrapper.test.ts b/src/plugins/workspace/server/saved_objects/workspace_ui_settings_client_wrapper.test.ts index 68687d1ce510..2a53959e5103 100644 --- a/src/plugins/workspace/server/saved_objects/workspace_ui_settings_client_wrapper.test.ts +++ b/src/plugins/workspace/server/saved_objects/workspace_ui_settings_client_wrapper.test.ts @@ -7,6 +7,7 @@ import { loggerMock } from '@osd/logging/target/mocks'; import { httpServerMock, savedObjectsClientMock, coreMock } from '../../../../core/server/mocks'; import { WorkspaceUiSettingsClientWrapper } from './workspace_ui_settings_client_wrapper'; import { WORKSPACE_TYPE } from '../../../../core/server'; +import { DEFAULT_DATA_SOURCE_UI_SETTINGS_ID } from '../../../data_source_management/common'; import * as utils from '../../../../core/server/utils'; @@ -28,6 +29,7 @@ describe('WorkspaceUiSettingsClientWrapper', () => { type: 'config', attributes: { defaultIndex: 'default-index-global', + [DEFAULT_DATA_SOURCE_UI_SETTINGS_ID]: 'default-ds-global', }, }); } else if (type === WORKSPACE_TYPE) { @@ -59,7 +61,7 @@ describe('WorkspaceUiSettingsClientWrapper', () => { }; }; - it('should return workspace ui settings if in a workspace', async () => { + it('should return workspace ui settings and should return workspace default data source and not extend global if in a workspace', async () => { // Currently in a workspace jest.spyOn(utils, 'getWorkspaceState').mockReturnValue({ requestWorkspaceId: 'workspace-id' }); @@ -72,6 +74,7 @@ describe('WorkspaceUiSettingsClientWrapper', () => { type: 'config', attributes: { defaultIndex: 'default-index-workspace', + [DEFAULT_DATA_SOURCE_UI_SETTINGS_ID]: undefined, }, }); }); @@ -89,6 +92,7 @@ describe('WorkspaceUiSettingsClientWrapper', () => { type: 'config', attributes: { defaultIndex: 'default-index-global', + [DEFAULT_DATA_SOURCE_UI_SETTINGS_ID]: 'default-ds-global', }, }); }); diff --git a/src/plugins/workspace/server/saved_objects/workspace_ui_settings_client_wrapper.ts b/src/plugins/workspace/server/saved_objects/workspace_ui_settings_client_wrapper.ts index 9cc860ec903e..52cccc6a01c0 100644 --- a/src/plugins/workspace/server/saved_objects/workspace_ui_settings_client_wrapper.ts +++ b/src/plugins/workspace/server/saved_objects/workspace_ui_settings_client_wrapper.ts @@ -18,6 +18,7 @@ import { } from '../../../../core/server'; import { WORKSPACE_UI_SETTINGS_CLIENT_WRAPPER_ID } from '../../common/constants'; import { Logger } from '../../../../core/server'; +import { DEFAULT_DATA_SOURCE_UI_SETTINGS_ID } from '../../../data_source_management/common'; /** * This saved object client wrapper offers methods to get and update UI settings considering @@ -73,9 +74,14 @@ export class WorkspaceUiSettingsClientWrapper { this.logger.error(`Unable to get workspaceObject with id: ${requestWorkspaceId}`); } + const workspaceLevelDefaultDS = + workspaceObject?.attributes?.uiSettings?.[DEFAULT_DATA_SOURCE_UI_SETTINGS_ID]; + configObject.attributes = { ...configObject.attributes, ...(workspaceObject ? workspaceObject.attributes.uiSettings : {}), + // Workspace level default data source value should not extend global UIsettings value. + [DEFAULT_DATA_SOURCE_UI_SETTINGS_ID]: workspaceLevelDefaultDS, }; return configObject as SavedObject; diff --git a/src/plugins/workspace/server/types.ts b/src/plugins/workspace/server/types.ts index 10a7649c87da..b21f31e3accd 100644 --- a/src/plugins/workspace/server/types.ts +++ b/src/plugins/workspace/server/types.ts @@ -12,11 +12,13 @@ import { WorkspaceAttribute, SavedObjectsServiceStart, Permissions, + UiSettingsServiceStart, } from '../../../core/server'; export interface WorkspaceAttributeWithPermission extends WorkspaceAttribute { permissions?: Permissions; } +import { WorkspacePermissionMode } from '../common/constants'; export interface WorkspaceFindOptions { page?: number; @@ -25,11 +27,11 @@ export interface WorkspaceFindOptions { searchFields?: string[]; sortField?: string; sortOrder?: string; + permissionModes?: WorkspacePermissionMode[]; } export interface IRequestDetail { request: OpenSearchDashboardsRequest; - context: RequestHandlerContext; logger: Logger; } @@ -48,16 +50,24 @@ export interface IWorkspaceClientImpl { * @public */ setSavedObjects(savedObjects: SavedObjectsServiceStart): void; + /** + * Set ui settings client that will be used inside the workspace client. + * @param uiSettings {@link UiSettingsServiceStart} + * @returns void + * @public + */ + setUiSettings(uiSettings: UiSettingsServiceStart): void; /** * Create a workspace * @param requestDetail {@link IRequestDetail} - * @param payload {@link WorkspaceAttribute} - * @returns a Promise with a new-created id for the workspace + * @param payload - An object of type {@link WorkspaceAttributeWithPermission} excluding the 'id' property, and also containing an optional array of string. * @public */ create( requestDetail: IRequestDetail, - payload: Omit + payload: Omit & { + dataSources?: string[]; + } ): Promise>; /** * List workspaces @@ -88,14 +98,16 @@ export interface IWorkspaceClientImpl { * Update the detail of a given workspace * @param requestDetail {@link IRequestDetail} * @param id workspace id - * @param payload {@link WorkspaceAttribute} + * @param payload - An object of type {@link WorkspaceAttributeWithPermission} excluding the 'id' property, and also containing an optional array of string. * @returns a Promise with a boolean result indicating if the update operation successed. * @public */ update( requestDetail: IRequestDetail, id: string, - payload: Partial> + payload: Partial> & { + dataSources?: string[]; + } ): Promise>; /** * Delete a given workspace diff --git a/src/plugins/workspace/server/utils.test.ts b/src/plugins/workspace/server/utils.test.ts index 15e6783c2cab..ba7532c216eb 100644 --- a/src/plugins/workspace/server/utils.test.ts +++ b/src/plugins/workspace/server/utils.test.ts @@ -4,15 +4,24 @@ */ import { AuthStatus } from '../../../core/server'; -import { httpServerMock, httpServiceMock } from '../../../core/server/mocks'; +import { + httpServerMock, + httpServiceMock, + savedObjectsClientMock, + uiSettingsServiceMock, +} from '../../../core/server/mocks'; import { generateRandomId, getOSDAdminConfigFromYMLConfig, getPrincipalsFromRequest, updateDashboardAdminStateForRequest, + transferCurrentUserInPermissions, + getDataSourcesList, + checkAndSetDefaultDataSource, } from './utils'; import { getWorkspaceState } from '../../../core/server/utils'; import { Observable, of } from 'rxjs'; +import { DEFAULT_DATA_SOURCE_UI_SETTINGS_ID } from '../../data_source_management/common'; describe('workspace utils', () => { const mockAuth = httpServiceMock.createAuth(); @@ -128,7 +137,7 @@ describe('workspace utils', () => { const configGroups: string[] = []; const configUsers: string[] = []; updateDashboardAdminStateForRequest(mockRequest, groups, users, configGroups, configUsers); - expect(getWorkspaceState(mockRequest)?.isDashboardAdmin).toBe(false); + expect(getWorkspaceState(mockRequest)?.isDashboardAdmin).toBe(true); }); it('should get correct admin config when admin config is enabled ', async () => { @@ -151,4 +160,100 @@ describe('workspace utils', () => { expect(groups).toEqual([]); expect(users).toEqual([]); }); + + it('should transfer current user placeholder in permissions', () => { + expect(transferCurrentUserInPermissions('foo', undefined)).toBeUndefined(); + expect( + transferCurrentUserInPermissions('foo', { + library_write: { + users: ['%me%', 'bar'], + }, + write: { + users: ['%me%'], + }, + read: { + users: ['bar'], + }, + }) + ).toEqual({ + library_write: { + users: ['foo', 'bar'], + }, + write: { + users: ['foo'], + }, + read: { + users: ['bar'], + }, + }); + }); + + it('should return dataSources list when passed savedObject client', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + const dataSources = [ + { + id: 'ds-1', + }, + ]; + savedObjectsClient.find = jest.fn().mockResolvedValue({ + saved_objects: dataSources, + }); + const result = await getDataSourcesList(savedObjectsClient, []); + expect(result).toEqual(dataSources); + }); + + it('should return empty array when finding no saved objects', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + savedObjectsClient.find = jest.fn().mockResolvedValue({}); + const result = await getDataSourcesList(savedObjectsClient, []); + expect(result).toEqual([]); + }); + + it('should set first data sources as default when not need check', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + const uiSettings = uiSettingsServiceMock.createStartContract(); + const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); + const dataSources = ['id1', 'id2']; + await checkAndSetDefaultDataSource(uiSettingsClient, dataSources, false); + expect(uiSettingsClient.set).toHaveBeenCalledWith( + DEFAULT_DATA_SOURCE_UI_SETTINGS_ID, + dataSources[0] + ); + }); + + it('should not set default data source after checking if not needed', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + const uiSettings = uiSettingsServiceMock.createStartContract(); + const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); + const dataSources = ['id1', 'id2']; + uiSettingsClient.get = jest.fn().mockResolvedValue(dataSources[0]); + await checkAndSetDefaultDataSource(uiSettingsClient, dataSources, true); + expect(uiSettingsClient.set).not.toBeCalled(); + }); + + it('should check then set first data sources as default if needed when checking', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + const uiSettings = uiSettingsServiceMock.createStartContract(); + const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); + const dataSources = ['id1', 'id2']; + uiSettingsClient.get = jest.fn().mockResolvedValue(''); + await checkAndSetDefaultDataSource(uiSettingsClient, dataSources, true); + expect(uiSettingsClient.set).toHaveBeenCalledWith( + DEFAULT_DATA_SOURCE_UI_SETTINGS_ID, + dataSources[0] + ); + }); + + it('should clear default data source if there is no new data source', async () => { + const savedObjectsClient = savedObjectsClientMock.create(); + const uiSettings = uiSettingsServiceMock.createStartContract(); + const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); + const dataSources: string[] = []; + uiSettingsClient.get = jest.fn().mockResolvedValue(''); + await checkAndSetDefaultDataSource(uiSettingsClient, dataSources, true); + expect(uiSettingsClient.set).toHaveBeenCalledWith( + DEFAULT_DATA_SOURCE_UI_SETTINGS_ID, + undefined + ); + }); }); diff --git a/src/plugins/workspace/server/utils.ts b/src/plugins/workspace/server/utils.ts index 9037038f16af..9f144c7eb1c3 100644 --- a/src/plugins/workspace/server/utils.ts +++ b/src/plugins/workspace/server/utils.ts @@ -13,9 +13,14 @@ import { Principals, PrincipalType, SharedGlobalConfig, + Permissions, + SavedObjectsClientContract, + IUiSettingsClient, } from '../../../core/server'; import { AuthInfo } from './types'; import { updateWorkspaceState } from '../../../core/server/utils'; +import { DEFAULT_DATA_SOURCE_UI_SETTINGS_ID } from '../../data_source_management/common'; +import { CURRENT_USER_PLACEHOLDER } from '../common/constants'; /** * Generate URL friendly random ID @@ -67,9 +72,9 @@ export const updateDashboardAdminStateForRequest = ( updateWorkspaceState(request, { isDashboardAdmin: true }); return; } - + // If groups/users are not configured or [], login defaults to OSD Admin if (!configGroups.length && !configUsers.length) { - updateWorkspaceState(request, { isDashboardAdmin: false }); + updateWorkspaceState(request, { isDashboardAdmin: true }); return; } const groupMatchAny = groups.some((group) => configGroups.includes(group)); @@ -89,3 +94,69 @@ export const getOSDAdminConfigFromYMLConfig = async ( return [groupsResult, usersResult]; }; + +export const transferCurrentUserInPermissions = ( + realUserId: string, + permissions: Permissions | undefined +) => { + if (!permissions) { + return permissions; + } + return Object.keys(permissions).reduce( + (previousPermissions, currentKey) => ({ + ...previousPermissions, + [currentKey]: { + ...permissions[currentKey], + users: permissions[currentKey].users?.map((user) => + user === CURRENT_USER_PLACEHOLDER ? realUserId : user + ), + }, + }), + {} + ); +}; + +export const getDataSourcesList = (client: SavedObjectsClientContract, workspaces: string[]) => { + return client + .find({ + type: 'data-source', + fields: ['id', 'title'], + perPage: 10000, + workspaces, + }) + .then((response) => { + const objects = response?.saved_objects; + if (objects) { + return objects.map((source) => { + const id = source.id; + return { + id, + }; + }); + } else { + return []; + } + }); +}; + +export const checkAndSetDefaultDataSource = async ( + uiSettingsClient: IUiSettingsClient, + dataSources: string[], + needCheck: boolean +) => { + if (dataSources?.length > 0) { + if (!needCheck) { + // Create# Will set first data source as default data source. + await uiSettingsClient.set(DEFAULT_DATA_SOURCE_UI_SETTINGS_ID, dataSources[0]); + } else { + // Update will check if default DS still exists. + const defaultDSId = (await uiSettingsClient.get(DEFAULT_DATA_SOURCE_UI_SETTINGS_ID)) ?? ''; + if (!dataSources.includes(defaultDSId)) { + await uiSettingsClient.set(DEFAULT_DATA_SOURCE_UI_SETTINGS_ID, dataSources[0]); + } + } + } else { + // If there is no data source left, clear workspace level default data source. + await uiSettingsClient.set(DEFAULT_DATA_SOURCE_UI_SETTINGS_ID, undefined); + } +}; diff --git a/src/plugins/workspace/server/workspace_client.test.ts b/src/plugins/workspace/server/workspace_client.test.ts new file mode 100644 index 000000000000..f6e341603aa0 --- /dev/null +++ b/src/plugins/workspace/server/workspace_client.test.ts @@ -0,0 +1,217 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import { WorkspaceClient } from './workspace_client'; + +import { + coreMock, + httpServerMock, + uiSettingsServiceMock, + loggingSystemMock, +} from '../../../core/server/mocks'; +import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../data_source/common'; +import { SavedObjectsServiceStart, SavedObjectsClientContract } from '../../../core/server'; +import { IRequestDetail } from './types'; + +const coreSetup = coreMock.createSetup(); + +const mockWorkspaceId = 'workspace_id'; +const mockWorkspaceName = 'workspace_name'; +const mockCheckAndSetDefaultDataSource = jest.fn(); +const logger = loggingSystemMock.create().get(); + +jest.mock('./utils', () => ({ + generateRandomId: () => mockWorkspaceId, + getDataSourcesList: jest.fn().mockResolvedValue([ + { + id: 'id1', + }, + { + id: 'id2', + }, + ]), + checkAndSetDefaultDataSource: (...args) => mockCheckAndSetDefaultDataSource(...args), +})); + +describe('#WorkspaceClient', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + const find = jest.fn(); + const addToWorkspaces = jest.fn(); + const deleteFromWorkspaces = jest.fn(); + const savedObjectClient = ({ + find, + addToWorkspaces, + deleteFromWorkspaces, + create: jest.fn(), + get: jest.fn().mockResolvedValue({ + attributes: { + name: mockWorkspaceName, + }, + }), + } as unknown) as SavedObjectsClientContract; + const savedObjects = ({ + ...coreSetup.savedObjects, + getScopedClient: () => savedObjectClient, + } as unknown) as SavedObjectsServiceStart; + + const uiSettings = uiSettingsServiceMock.createStartContract(); + + const mockRequestDetail = ({ + request: httpServerMock.createOpenSearchDashboardsRequest(), + context: coreMock.createRequestHandlerContext(), + logger: {}, + } as unknown) as IRequestDetail; + + it('create# should not call addToWorkspaces if no data sources passed', async () => { + const client = new WorkspaceClient(coreSetup, logger); + await client.setup(coreSetup); + client?.setSavedObjects(savedObjects); + + await client.create(mockRequestDetail, { + permissions: {}, + dataSources: [], + name: mockWorkspaceName, + }); + expect(addToWorkspaces).not.toHaveBeenCalled(); + }); + + it('create# should call addToWorkspaces with passed data sources normally', async () => { + const client = new WorkspaceClient(coreSetup, logger); + await client.setup(coreSetup); + client?.setSavedObjects(savedObjects); + + await client.create(mockRequestDetail, { + name: mockWorkspaceName, + permissions: {}, + dataSources: ['id1'], + }); + + expect(addToWorkspaces).toHaveBeenCalledWith(DATA_SOURCE_SAVED_OBJECT_TYPE, 'id1', [ + mockWorkspaceId, + ]); + }); + + it('create# should call set default data source after creating', async () => { + const client = new WorkspaceClient(coreSetup, logger); + await client.setup(coreSetup); + client?.setSavedObjects(savedObjects); + client?.setUiSettings(uiSettings); + + await client.create(mockRequestDetail, { + name: mockWorkspaceName, + permissions: {}, + dataSources: ['id1'], + }); + + const uiSettingsClient = uiSettings.asScopedToClient(savedObjectClient); + + expect(mockCheckAndSetDefaultDataSource).toHaveBeenCalledWith(uiSettingsClient, ['id1'], false); + }); + + it('update# should not call addToWorkspaces if no new data sources added', async () => { + const client = new WorkspaceClient(coreSetup, logger); + await client.setup(coreSetup); + client?.setSavedObjects(savedObjects); + + await client.update(mockRequestDetail, mockWorkspaceId, { + permissions: {}, + name: mockWorkspaceName, + dataSources: ['id1', 'id2'], + }); + + expect(addToWorkspaces).not.toHaveBeenCalled(); + }); + + it('update# should call deleteFromWorkspaces if there is data source to be removed', async () => { + const client = new WorkspaceClient(coreSetup, logger); + await client.setup(coreSetup); + client?.setSavedObjects(savedObjects); + + await client.update(mockRequestDetail, mockWorkspaceId, { + permissions: {}, + name: 'workspace_name', + dataSources: ['id3', 'id4'], + }); + expect(deleteFromWorkspaces).toHaveBeenCalledWith(DATA_SOURCE_SAVED_OBJECT_TYPE, 'id1', [ + mockWorkspaceId, + ]); + + expect(deleteFromWorkspaces).toHaveBeenCalledWith(DATA_SOURCE_SAVED_OBJECT_TYPE, 'id2', [ + mockWorkspaceId, + ]); + }); + it('update# should calculate data sources to be added and to be removed', async () => { + const client = new WorkspaceClient(coreSetup, logger); + await client.setup(coreSetup); + client?.setSavedObjects(savedObjects); + + await client.update(mockRequestDetail, mockWorkspaceId, { + permissions: {}, + name: mockWorkspaceName, + dataSources: ['id1', 'id3'], + }); + expect(addToWorkspaces).toHaveBeenCalledWith(DATA_SOURCE_SAVED_OBJECT_TYPE, 'id3', [ + mockWorkspaceId, + ]); + + expect(deleteFromWorkspaces).toHaveBeenCalledWith(DATA_SOURCE_SAVED_OBJECT_TYPE, 'id2', [ + mockWorkspaceId, + ]); + }); + + it('update# should call set default data source with check after updating', async () => { + const client = new WorkspaceClient(coreSetup, logger); + await client.setup(coreSetup); + client?.setSavedObjects(savedObjects); + client?.setUiSettings(uiSettings); + + await client.update(mockRequestDetail, mockWorkspaceId, { + name: mockWorkspaceName, + permissions: {}, + dataSources: ['id1'], + }); + + const uiSettingsClient = uiSettings.asScopedToClient(savedObjectClient); + + expect(mockCheckAndSetDefaultDataSource).toHaveBeenCalledWith(uiSettingsClient, ['id1'], true); + }); + + it('update# should log error when failed set default data source', async () => { + const client = new WorkspaceClient(coreSetup, logger); + await client.setup(coreSetup); + client?.setSavedObjects(savedObjects); + client?.setUiSettings(uiSettings); + mockCheckAndSetDefaultDataSource.mockRejectedValueOnce(new Error()); + + await client.update(mockRequestDetail, mockWorkspaceId, { + name: mockWorkspaceName, + permissions: {}, + dataSources: ['id1'], + }); + + expect(logger.error).toHaveBeenLastCalledWith( + 'Set default data source error during workspace updating' + ); + }); + + it('delete# should unassign data source before deleting related saved objects', async () => { + const client = new WorkspaceClient(coreSetup, logger); + await client.setup(coreSetup); + client?.setSavedObjects(savedObjects); + client?.setUiSettings(uiSettings); + + await client.delete(mockRequestDetail, mockWorkspaceId); + + expect(deleteFromWorkspaces).toHaveBeenCalledWith(DATA_SOURCE_SAVED_OBJECT_TYPE, 'id1', [ + mockWorkspaceId, + ]); + expect(deleteFromWorkspaces).toHaveBeenCalledWith(DATA_SOURCE_SAVED_OBJECT_TYPE, 'id2', [ + mockWorkspaceId, + ]); + }); +}); diff --git a/src/plugins/workspace/server/workspace_client.ts b/src/plugins/workspace/server/workspace_client.ts index a906e3548cab..06476f5e23df 100644 --- a/src/plugins/workspace/server/workspace_client.ts +++ b/src/plugins/workspace/server/workspace_client.ts @@ -10,8 +10,11 @@ import { CoreSetup, WorkspaceAttribute, SavedObjectsServiceStart, + UiSettingsServiceStart, + WORKSPACE_TYPE, + Logger, } from '../../../core/server'; -import { WORKSPACE_TYPE } from '../../../core/server'; +import { updateWorkspaceState, getWorkspaceState } from '../../../core/server/utils'; import { IWorkspaceClientImpl, WorkspaceFindOptions, @@ -20,11 +23,12 @@ import { WorkspaceAttributeWithPermission, } from './types'; import { workspace } from './saved_objects'; -import { generateRandomId } from './utils'; +import { generateRandomId, getDataSourcesList, checkAndSetDefaultDataSource } from './utils'; import { WORKSPACE_ID_CONSUMER_WRAPPER_ID, WORKSPACE_SAVED_OBJECTS_CLIENT_WRAPPER_ID, } from '../common/constants'; +import { DATA_SOURCE_SAVED_OBJECT_TYPE } from '../../data_source/common'; const WORKSPACE_ID_SIZE = 6; @@ -34,10 +38,13 @@ const DUPLICATE_WORKSPACE_NAME_ERROR = i18n.translate('workspace.duplicate.name. export class WorkspaceClient implements IWorkspaceClientImpl { private setupDep: CoreSetup; + private logger: Logger; private savedObjects?: SavedObjectsServiceStart; + private uiSettings?: UiSettingsServiceStart; - constructor(core: CoreSetup) { + constructor(core: CoreSetup, logger: Logger) { this.setupDep = core; + this.logger = logger; } private getScopedClientWithoutPermission( @@ -70,6 +77,7 @@ export class WorkspaceClient implements IWorkspaceClientImpl { ): WorkspaceAttributeWithPermission { return { ...savedObject.attributes, + lastUpdatedTime: savedObject.updated_at, id: savedObject.id, permissions: savedObject.permissions, }; @@ -86,10 +94,12 @@ export class WorkspaceClient implements IWorkspaceClientImpl { } public async create( requestDetail: IRequestDetail, - payload: Omit + payload: Omit & { + dataSources?: string[]; + } ): ReturnType { try { - const { permissions, ...attributes } = payload; + const { permissions, dataSources, ...attributes } = payload; const id = generateRandomId(WORKSPACE_ID_SIZE); const client = this.getSavedObjectClientsFromRequestDetail(requestDetail); const existingWorkspaceRes = await this.getScopedClientWithoutPermission(requestDetail)?.find( @@ -102,6 +112,15 @@ export class WorkspaceClient implements IWorkspaceClientImpl { if (existingWorkspaceRes && existingWorkspaceRes.total > 0) { throw new Error(DUPLICATE_WORKSPACE_NAME_ERROR); } + + if (dataSources) { + const promises = []; + for (const dataSourceId of dataSources) { + promises.push(client.addToWorkspaces(DATA_SOURCE_SAVED_OBJECT_TYPE, dataSourceId, [id])); + } + await Promise.all(promises); + } + const result = await client.create>( WORKSPACE_TYPE, attributes, @@ -110,6 +129,26 @@ export class WorkspaceClient implements IWorkspaceClientImpl { permissions, } ); + if (dataSources && this.uiSettings && client) { + const rawState = getWorkspaceState(requestDetail.request); + // This is for setting in workspace environment, otherwise uiSettings can't set workspace level value. + updateWorkspaceState(requestDetail.request, { + requestWorkspaceId: id, + }); + // Set first data source as default after creating workspace + const uiSettingsClient = this.uiSettings.asScopedToClient(client); + try { + await checkAndSetDefaultDataSource(uiSettingsClient, dataSources, false); + } catch (e) { + this.logger.error('Set default data source error'); + } finally { + // Reset workspace state + updateWorkspaceState(requestDetail.request, { + requestWorkspaceId: rawState.requestWorkspaceId, + }); + } + } + return { success: true, result: { @@ -135,6 +174,7 @@ export class WorkspaceClient implements IWorkspaceClientImpl { { ...options, type: WORKSPACE_TYPE, + ACLSearchParams: { permissionModes: options.permissionModes }, } ); return { @@ -173,12 +213,14 @@ export class WorkspaceClient implements IWorkspaceClientImpl { public async update( requestDetail: IRequestDetail, id: string, - payload: Partial> + payload: Partial> & { + dataSources?: string[]; + } ): Promise> { - const { permissions, ...attributes } = payload; + const { permissions, dataSources: newDataSources, ...attributes } = payload; try { const client = this.getSavedObjectClientsFromRequestDetail(requestDetail); - const workspaceInDB: SavedObject = await client.get(WORKSPACE_TYPE, id); + let workspaceInDB: SavedObject = await client.get(WORKSPACE_TYPE, id); if (workspaceInDB.attributes.name !== attributes.name) { const existingWorkspaceRes = await this.getScopedClientWithoutPermission( requestDetail @@ -192,6 +234,53 @@ export class WorkspaceClient implements IWorkspaceClientImpl { throw new Error(DUPLICATE_WORKSPACE_NAME_ERROR); } } + + if (newDataSources) { + const originalSelectedDataSources = await getDataSourcesList(client, [id]); + const originalSelectedDataSourceIds = originalSelectedDataSources.map((ds) => ds.id); + const dataSourcesToBeRemoved = originalSelectedDataSourceIds.filter( + (ds) => !newDataSources.find((item) => item === ds) + ); + const dataSourcesToBeAdded = newDataSources.filter( + (ds) => !originalSelectedDataSourceIds.find((item) => item === ds) + ); + + const promises = []; + if (dataSourcesToBeRemoved.length > 0) { + for (const dataSourceId of dataSourcesToBeRemoved) { + promises.push( + client.deleteFromWorkspaces(DATA_SOURCE_SAVED_OBJECT_TYPE, dataSourceId, [id]) + ); + } + } + if (dataSourcesToBeAdded.length > 0) { + for (const dataSourceId of dataSourcesToBeAdded) { + promises.push( + client.addToWorkspaces(DATA_SOURCE_SAVED_OBJECT_TYPE, dataSourceId, [id]) + ); + } + } + if (promises.length > 0) { + await Promise.all(promises); + } + } + + /** + * When the workspace owner unassign themselves, ensure the default data source is set before + * updating the workspace permissions. This prevents a lack of write permission on saved objects + * after the user is removed from the workspace. + **/ + if (newDataSources && this.uiSettings && client) { + const uiSettingsClient = this.uiSettings.asScopedToClient(client); + try { + await checkAndSetDefaultDataSource(uiSettingsClient, newDataSources, true); + // Doc version may changed after default data source updated. + workspaceInDB = await client.get(WORKSPACE_TYPE, id); + } catch (error) { + this.logger.error('Set default data source error during workspace updating'); + } + } + await client.create>( WORKSPACE_TYPE, { ...workspaceInDB.attributes, ...attributes }, @@ -202,6 +291,7 @@ export class WorkspaceClient implements IWorkspaceClientImpl { version: workspaceInDB.version, } ); + return { success: true, result: true, @@ -229,6 +319,21 @@ export class WorkspaceClient implements IWorkspaceClientImpl { }), }; } + + // When workspace is to be deleted, unassign all assigned data source before deleting saved object by workspace. + const selectedDataSources = await getDataSourcesList(savedObjectClient, [id]); + if (selectedDataSources.length > 0) { + const promises = []; + for (const dataSource of selectedDataSources) { + promises.push( + savedObjectClient.deleteFromWorkspaces(DATA_SOURCE_SAVED_OBJECT_TYPE, dataSource.id, [ + id, + ]) + ); + } + await Promise.all(promises); + } + await savedObjectClient.deleteByWorkspace(id); // delete workspace itself at last, deleteByWorkspace depends on the workspace to do permission check await savedObjectClient.delete(WORKSPACE_TYPE, id); @@ -246,6 +351,9 @@ export class WorkspaceClient implements IWorkspaceClientImpl { public setSavedObjects(savedObjects: SavedObjectsServiceStart) { this.savedObjects = savedObjects; } + public setUiSettings(uiSettings: UiSettingsServiceStart) { + this.uiSettings = uiSettings; + } public async destroy(): Promise> { return { success: true, diff --git a/src/test_utils/public/testbed/types.ts b/src/test_utils/public/testbed/types.ts index 9df36d3f97e3..b4595e6140ad 100644 --- a/src/test_utils/public/testbed/types.ts +++ b/src/test_utils/public/testbed/types.ts @@ -104,8 +104,8 @@ export interface TestBed { isAsync?: boolean ) => Promise | void; /** - * Set the value of a or a mocked - * For the you need to mock it like this + * Set the value of a or a mocked + * For the you need to mock it like this * ```typescript jest.mock('@elastic/eui', () => { @@ -113,7 +113,7 @@ export interface TestBed { return { ...original, - EuiSuperSelect: (props: any) => ( + EuiCompressedSuperSelect: (props: any) => ( { */ selectCheckBox: (checkboxTestSubject: T, isChecked?: boolean) => void; /** - * Toggle the EuiSwitch + * Toggle the EuiCompressedSwitch * - * @param switchTestSubject The test subject of the EuiSwitch (can be a nested path. e.g. "myForm.mySwitch"). + * @param switchTestSubject The test subject of the EuiCompressedSwitch (can be a nested path. e.g. "myForm.mySwitch"). */ toggleEuiSwitch: (switchTestSubject: T, isChecked?: boolean) => void; /** diff --git a/test/common/config.js b/test/common/config.js index 5a9d121ad631..5793e01d823a 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -79,6 +79,7 @@ export default function () { `--opensearchDashboards.branding.mark.defaultUrl=https://opensearch.org/assets/brand/SVG/Mark/opensearch_mark_default.svg`, `--opensearchDashboards.branding.mark.darkModeUrl=https://opensearch.org/assets/brand/SVG/Mark/opensearch_mark_darkmode.svg`, `--opensearchDashboards.branding.applicationTitle=OpenSearch`, + `--uiSettings.overrides['query:enhancements:enabled']=false`, ], }, services, diff --git a/test/functional/apps/dashboard/dashboard_saved_query.js b/test/functional/apps/dashboard/dashboard_saved_query.js index a8d54afac776..c1995eaa2a45 100644 --- a/test/functional/apps/dashboard/dashboard_saved_query.js +++ b/test/functional/apps/dashboard/dashboard_saved_query.js @@ -58,7 +58,7 @@ export default function ({ getService, getPageObjects }) { await savedQueryManagementComponent.openSavedQueryManagementComponent(); const descriptionText = await testSubjects.getVisibleText('saved-query-management-popover'); expect(descriptionText).to.eql( - 'SAVED QUERIES\nThere are no saved queries. Save query text and filters that you want to use again.\nSave current query' + 'Saved Queries\nThere are no saved queries. Save query text and filters that you want to use again.\nSave current query' ); }); diff --git a/test/functional/apps/vis_builder/_experimental_vis.ts b/test/functional/apps/vis_builder/_experimental_vis.ts deleted file mode 100644 index 1743d4882281..000000000000 --- a/test/functional/apps/vis_builder/_experimental_vis.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright OpenSearch Contributors - * SPDX-License-Identifier: Apache-2.0 - */ - -import expect from '@osd/expect'; -import { VISUALIZE_ENABLE_LABS_SETTING } from '../../../../src/plugins/visualizations/common/constants'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['visualize', 'visBuilder']); - const log = getService('log'); - const opensearchDashboardsServer = getService('opensearchDashboardsServer'); - - describe('experimental settings for visBuilder app ', function () { - it('should show an notification when creating visBuilder visualization', async () => { - log.debug('navigateToApp visualize'); - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.waitForVisualizationSelectPage(); - - // Try to find the visBuilder Vis type. - const visBuilderVisTypeExists = await PageObjects.visualize.hasVisType('vis-builder'); - expect(visBuilderVisTypeExists).to.be(true); - - // Create a new visualization - await PageObjects.visualize.clickVisType('vis-builder'); - - // Check that the experimental banner is there and state that this is experimental - const info = await PageObjects.visBuilder.getExperimentalInfo(); - expect(await info.getVisibleText()).to.contain('experimental'); - }); - - it('should not be available in the picker when disabled', async () => { - log.debug('navigateToApp visualize'); - await opensearchDashboardsServer.uiSettings.replace({ - [VISUALIZE_ENABLE_LABS_SETTING]: false, - }); - await PageObjects.visualize.navigateToNewVisualization(); - await PageObjects.visualize.waitForVisualizationSelectPage(); - - // Try to find the visBuilder Vis type. - const visBuilderVisTypeExists = await PageObjects.visualize.hasVisType('vis-builder'); - expect(visBuilderVisTypeExists).to.be(false); - }); - - after(async () => { - // unset the experimental ui setting - await opensearchDashboardsServer.uiSettings.unset(VISUALIZE_ENABLE_LABS_SETTING); - }); - }); -} diff --git a/test/functional/apps/vis_builder/index.ts b/test/functional/apps/vis_builder/index.ts index c0e7b9c35c77..67b333deb5d6 100644 --- a/test/functional/apps/vis_builder/index.ts +++ b/test/functional/apps/vis_builder/index.ts @@ -34,6 +34,5 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { }); loadTestFile(require.resolve('./_base')); - loadTestFile(require.resolve('./_experimental_vis')); }); } diff --git a/test/functional/apps/visualize/_custom_branding.ts b/test/functional/apps/visualize/_custom_branding.ts index 1f18b82bf99e..37f07e932ee5 100644 --- a/test/functional/apps/visualize/_custom_branding.ts +++ b/test/functional/apps/visualize/_custom_branding.ts @@ -47,7 +47,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('with customized logo for opensearch overview header in dark mode', async () => { await PageObjects.common.navigateToApp('management/opensearch-dashboards/settings'); - await PageObjects.settings.toggleAdvancedSettingCheckbox('theme:enableUserControl'); await PageObjects.settings.toggleAdvancedSettingCheckbox('theme:darkMode'); await PageObjects.common.navigateToApp('opensearch_dashboards_overview'); await testSubjects.existOrFail('osdOverviewPageHeaderLogo'); @@ -102,7 +101,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('with customized logo in dark mode', async () => { await PageObjects.common.navigateToApp('management/opensearch-dashboards/settings'); - await PageObjects.settings.toggleAdvancedSettingCheckbox('theme:enableUserControl'); await PageObjects.settings.toggleAdvancedSettingCheckbox('theme:darkMode'); await PageObjects.common.navigateToApp('home'); await testSubjects.existOrFail('welcomeCustomLogo'); @@ -127,8 +125,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('in default mode', async () => { it('with customized logo in header bar', async () => { - // The expanded header shows up with a dark background irrespective of the color scheme and because of that, we will always have a dark logo there. - await globalNav.logoExistsOrFail(expectedFullLogoDarkMode); + await globalNav.logoExistsOrFail(expectedFullLogo); }); it('with customized mark logo button in header bar', async () => { @@ -183,7 +180,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('in dark mode', async () => { before(async function () { await PageObjects.common.navigateToApp('management/opensearch-dashboards/settings'); - await PageObjects.settings.toggleAdvancedSettingCheckbox('theme:enableUserControl'); await PageObjects.settings.toggleAdvancedSettingCheckbox('theme:darkMode'); await PageObjects.common.navigateToApp('home'); }); diff --git a/test/functional/screenshots/baseline/area_chart.png b/test/functional/screenshots/baseline/area_chart.png index 8975ae86c1ad..239c8fff1843 100644 Binary files a/test/functional/screenshots/baseline/area_chart.png and b/test/functional/screenshots/baseline/area_chart.png differ diff --git a/test/functional/screenshots/baseline/tsvb_dashboard.png b/test/functional/screenshots/baseline/tsvb_dashboard.png index 86cd81d45864..b050f5bd1bf4 100644 Binary files a/test/functional/screenshots/baseline/tsvb_dashboard.png and b/test/functional/screenshots/baseline/tsvb_dashboard.png differ diff --git a/test/interpreter_functional/plugins/osd_tp_run_pipeline/package.json b/test/interpreter_functional/plugins/osd_tp_run_pipeline/package.json index 602da7bd57cd..416c31d22b7c 100644 --- a/test/interpreter_functional/plugins/osd_tp_run_pipeline/package.json +++ b/test/interpreter_functional/plugins/osd_tp_run_pipeline/package.json @@ -12,7 +12,7 @@ "build": "../../../../scripts/use_node ../../../../scripts/remove.js './target' && tsc" }, "devDependencies": { - "@elastic/eui": "npm:@opensearch-project/oui@1.5.1", + "@elastic/eui": "npm:@opensearch-project/oui@1.12.0", "@osd/plugin-helpers": "1.0.0", "react": "^16.14.0", "react-dom": "^16.12.0", diff --git a/test/plugin_functional/plugins/osd_sample_panel_action/package.json b/test/plugin_functional/plugins/osd_sample_panel_action/package.json index 1a517e6ddcca..6a9567cb1967 100644 --- a/test/plugin_functional/plugins/osd_sample_panel_action/package.json +++ b/test/plugin_functional/plugins/osd_sample_panel_action/package.json @@ -12,7 +12,7 @@ "build": "../../../../scripts/use_node ../../../../scripts/remove.js './target' && tsc" }, "devDependencies": { - "@elastic/eui": "npm:@opensearch-project/oui@1.5.1", + "@elastic/eui": "npm:@opensearch-project/oui@1.12.0", "react": "^16.14.0", "typescript": "4.0.2" } diff --git a/test/plugin_functional/plugins/osd_tp_custom_visualizations/package.json b/test/plugin_functional/plugins/osd_tp_custom_visualizations/package.json index ee8b5f4a0597..8b0b24a07f4b 100644 --- a/test/plugin_functional/plugins/osd_tp_custom_visualizations/package.json +++ b/test/plugin_functional/plugins/osd_tp_custom_visualizations/package.json @@ -12,7 +12,7 @@ "build": "../../../../scripts/use_node ../../../../scripts/remove.js './target' && tsc" }, "devDependencies": { - "@elastic/eui": "npm:@opensearch-project/oui@1.5.1", + "@elastic/eui": "npm:@opensearch-project/oui@1.12.0", "@osd/plugin-helpers": "1.0.0", "react": "^16.14.0", "typescript": "4.0.2" diff --git a/test/plugin_functional/plugins/osd_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.tsx b/test/plugin_functional/plugins/osd_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.tsx index 4e373f96a0b4..b87585b87766 100644 --- a/test/plugin_functional/plugins/osd_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.tsx +++ b/test/plugin_functional/plugins/osd_tp_custom_visualizations/public/self_changing_vis/self_changing_editor.tsx @@ -30,7 +30,7 @@ import React from 'react'; -import { EuiFieldNumber, EuiFormRow } from '@elastic/eui'; +import { EuiCompressedFieldNumber, EuiCompressedFormRow } from '@elastic/eui'; import { VisOptionsProps } from 'src/plugins/vis_default_editor/public/vis_options_props'; interface CounterParams { @@ -44,14 +44,14 @@ export class SelfChangingEditor extends React.Component - + - + ); } } diff --git a/test/plugin_functional/test_suites/dashboard_listing_plugin/dashboard_listing_plugin.ts b/test/plugin_functional/test_suites/dashboard_listing_plugin/dashboard_listing_plugin.ts index 354cfac4fa87..3ffa225779d1 100644 --- a/test/plugin_functional/test_suites/dashboard_listing_plugin/dashboard_listing_plugin.ts +++ b/test/plugin_functional/test_suites/dashboard_listing_plugin/dashboard_listing_plugin.ts @@ -54,8 +54,13 @@ export default function ({ getService, getPageObjects }) { it('should be able to navigate to edit dashboard', async () => { await listingTable.searchForItemWithName(dashboardName); - const editBttn = await find.allByCssSelector('.euiToolTipAnchor'); - await editBttn[0].click(); + // Find and click the edit button + const editBtn = await testSubjects.find('dashboardEditBtn'); + await editBtn.click(); + + // Find and click the edit option in the dropdown + const editOption = await testSubjects.find('dashboardEditDashboard'); + await editOption.click(); await PageObjects.dashboard.clickCancelOutOfEditMode(); await PageObjects.dashboard.gotoDashboardLandingPage(); }); diff --git a/yarn.lock b/yarn.lock index ff9c7b670574..2fa99056db46 100644 --- a/yarn.lock +++ b/yarn.lock @@ -174,6 +174,14 @@ "@babel/highlight" "^7.22.13" chalk "^2.4.2" +"@babel/code-frame@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== + dependencies: + "@babel/highlight" "^7.24.7" + picocolors "^1.0.0" + "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9", "@babel/compat-data@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.3.tgz#3febd552541e62b5e883a25eb3effd7c7379db11" @@ -210,6 +218,16 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" +"@babel/generator@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.0.tgz#f858ddfa984350bc3d3b7f125073c9af6988f18e" + integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw== + dependencies: + "@babel/types" "^7.25.0" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.16.0", "@babel/helper-annotate-as-pure@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882" @@ -217,6 +235,13 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-annotate-as-pure@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab" + integrity sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg== + dependencies: + "@babel/types" "^7.24.7" + "@babel/helper-builder-binary-assignment-operator-visitor@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz#5426b109cf3ad47b91120f8328d8ab1be8b0b956" @@ -250,6 +275,19 @@ "@babel/helper-split-export-declaration" "^7.22.6" semver "^6.3.1" +"@babel/helper-create-class-features-plugin@^7.24.7": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz#a109bf9c3d58dfed83aaf42e85633c89f43a6253" + integrity sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.8" + "@babel/helper-optimise-call-expression" "^7.24.7" + "@babel/helper-replace-supers" "^7.25.0" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/traverse" "^7.25.0" + semver "^6.3.1" + "@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.15", "@babel/helper-create-regexp-features-plugin@^7.22.5": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz#5ee90093914ea09639b01c711db0d6775e558be1" @@ -297,6 +335,14 @@ dependencies: "@babel/types" "^7.23.0" +"@babel/helper-member-expression-to-functions@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz#6155e079c913357d24a4c20480db7c712a5c3fb6" + integrity sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA== + dependencies: + "@babel/traverse" "^7.24.8" + "@babel/types" "^7.24.8" + "@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.16.0", "@babel/helper-module-imports@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" @@ -322,11 +368,23 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-optimise-call-expression@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz#8b0a0456c92f6b323d27cfd00d1d664e76692a0f" + integrity sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A== + dependencies: + "@babel/types" "^7.24.7" + "@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== +"@babel/helper-plugin-utils@^7.24.7": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" + integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== + "@babel/helper-remap-async-to-generator@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz#7b68e1cb4fa964d2996fd063723fb48eca8498e0" @@ -345,6 +403,15 @@ "@babel/helper-member-expression-to-functions" "^7.22.15" "@babel/helper-optimise-call-expression" "^7.22.5" +"@babel/helper-replace-supers@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.25.0.tgz#ff44deac1c9f619523fe2ca1fd650773792000a9" + integrity sha512-q688zIvQVYtZu+i2PsdIu/uWGRpfxzr5WESsfpShfZECkO+d2o+WROWezCi/Q6kJ0tfPa5+pUGUlfx2HhrA3Bg== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.24.8" + "@babel/helper-optimise-call-expression" "^7.24.7" + "@babel/traverse" "^7.25.0" + "@babel/helper-simple-access@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" @@ -359,6 +426,14 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-skip-transparent-expression-wrappers@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz#5f8fa83b69ed5c27adc56044f8be2b3ea96669d9" + integrity sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + "@babel/helper-split-export-declaration@^7.22.6": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" @@ -371,11 +446,21 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" + integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== + "@babel/helper-validator-identifier@^7.22.20": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== +"@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== + "@babel/helper-validator-option@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.15.tgz#694c30dfa1d09a6534cdfcafbe56789d36aba040" @@ -408,11 +493,26 @@ chalk "^2.4.2" js-tokens "^4.0.0" +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== + dependencies: + "@babel/helper-validator-identifier" "^7.24.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + "@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.22.15", "@babel/parser@^7.22.9", "@babel/parser@^7.23.3", "@babel/parser@^7.7.0": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.3.tgz#0ce0be31a4ca4f1884b5786057cadcb6c3be58f9" integrity sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw== +"@babel/parser@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.0.tgz#9fdc9237504d797b6e7b8f66e78ea7f570d256ad" + integrity sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.23.3.tgz#5cd1c87ba9380d0afb78469292c954fee5d2411a" @@ -647,6 +747,15 @@ "@babel/helper-plugin-utils" "^7.22.5" "@babel/plugin-syntax-class-static-block" "^7.14.5" +"@babel/plugin-transform-class-static-block@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz#c82027ebb7010bc33c116d4b5044fbbf8c05484d" + integrity sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-transform-classes@^7.23.3": version "7.23.3" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.23.3.tgz#73380c632c095b03e8503c24fd38f95ad41ffacb" @@ -1175,20 +1284,13 @@ core-js-pure "^3.30.2" regenerator-runtime "^0.14.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.22.9", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.15.4", "@babel/runtime@^7.16.3", "@babel/runtime@^7.22.9", "@babel/runtime@^7.23.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": version "7.23.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" integrity sha512-mM8eg4yl5D6i3lu2QKPuPH4FArvJ8KhTofbE7jwMUv9KX5mBvwPAqnV3MlyBNqdp9RyRKP6Yck8TrfYrPvX3bg== dependencies: regenerator-runtime "^0.14.0" -"@babel/runtime@^7.23.2": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" - integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== - dependencies: - regenerator-runtime "^0.14.0" - "@babel/template@^7.22.15", "@babel/template@^7.3.3": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" @@ -1198,20 +1300,26 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" -"@babel/traverse@^7.23.2", "@babel/traverse@^7.23.3", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": - version "7.23.3" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.3.tgz#26ee5f252e725aa7aca3474aa5b324eaf7908b5b" - integrity sha512-+K0yF1/9yR0oHdE0StHuEj3uTPzwwbrLGfNOndVJVV2TqA5+j3oljJUb4nmB954FLGjNem976+B+eDuLIjesiQ== - dependencies: - "@babel/code-frame" "^7.22.13" - "@babel/generator" "^7.23.3" - "@babel/helper-environment-visitor" "^7.22.20" - "@babel/helper-function-name" "^7.23.0" - "@babel/helper-hoist-variables" "^7.22.5" - "@babel/helper-split-export-declaration" "^7.22.6" - "@babel/parser" "^7.23.3" - "@babel/types" "^7.23.3" - debug "^4.1.0" +"@babel/template@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" + integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.25.0" + "@babel/types" "^7.25.0" + +"@babel/traverse@^7.23.2", "@babel/traverse@^7.23.3", "@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.0.tgz#e8533c0a57ed97921d1e5b20dd3db63d3efaebf5" + integrity sha512-ubALThHQy4GCf6mbb+5ZRNmLLCI7bJ3f8Q6LHBSRlSKSWj5a7dSUzJBLv3VuIhFrFPgjF4IzPF567YG/HSCdZA== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/parser" "^7.25.0" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.0" + debug "^4.3.1" globals "^11.1.0" "@babel/types@^7.0.0", "@babel/types@^7.22.15", "@babel/types@^7.22.19", "@babel/types@^7.22.5", "@babel/types@^7.22.9", "@babel/types@^7.23.0", "@babel/types@^7.23.3", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": @@ -1223,6 +1331,15 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.0.tgz#e6e3656c581f28da8452ed4f69e38008ec0ba41b" + integrity sha512-LcnxQSsd9aXOIgmmSpvZ/1yo46ra2ESYyqLcryaBZOghxy5qqOBjvCWP5JfkI8yl9rlxRgdLTTMCQQRcN2hdCg== + dependencies: + "@babel/helper-string-parser" "^7.24.8" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -1314,20 +1431,15 @@ resolved "https://registry.yarnpkg.com/@elastic/eslint-plugin-eui/-/eslint-plugin-eui-0.0.2.tgz#56b9ef03984a05cc213772ae3713ea8ef47b0314" integrity sha512-IoxURM5zraoQ7C8f+mJb9HYSENiZGgRVcG4tLQxE61yHNNRDXtGDWTZh8N1KIHcsqN1CEPETjuzBXkJYF/fDiQ== -"@elastic/eui@npm:@opensearch-project/oui@1.5.1": - version "1.5.1" - resolved "https://registry.yarnpkg.com/@opensearch-project/oui/-/oui-1.5.1.tgz#15838314c4c8c63ba45d3abfa1bc08815a4e879b" - integrity sha512-dnoKlHXAdO4K+Ice/iI/13yFzBOgJKugDlUcB9xwIc+JPRjPyGUX7U53GmKxNzU/2H9bH9WsAibsotCPSzMGNw== +"@elastic/eui@npm:@opensearch-project/oui@1.12.0": + version "1.12.0" + resolved "https://registry.yarnpkg.com/@opensearch-project/oui/-/oui-1.12.0.tgz#dfb34669549fec2500e71f66e43868932178318c" + integrity sha512-0phXBR8OZneJr/TwIiqTMWL53e/2PqzEDzl/y7QYEc6xUofD+RQSNi/YZNw4OZgscogu/zTajWA5tdQdrxT2+g== dependencies: "@types/chroma-js" "^2.4.0" - "@types/lodash" "4.14.192" - "@types/numeral" "^2.0.2" "@types/react-beautiful-dnd" "^13.1.3" "@types/react-input-autosize" "^2.2.1" - "@types/react-virtualized-auto-sizer" "^1.0.1" "@types/react-window" "^1.8.5" - "@types/refractor" "^3.0.0" - "@types/resize-observer-browser" "^0.1.7" chroma-js "^2.4.2" classnames "^2.3.2" lodash "^4.17.21" @@ -1343,7 +1455,6 @@ react-virtualized-auto-sizer "1.0.7" react-window "^1.8.9" refractor "^3.6.0" - rehype-raw "^5.0.0" rehype-react "^6.0.0" rehype-slug "^4.0.1" rehype-stringify "^8.0.0" @@ -1757,6 +1868,18 @@ "@hapi/bourne" "2.x.x" "@hapi/hoek" "9.x.x" +"@isaacs/cliui@^8.0.2": + version "8.0.2" + resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" + integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== + dependencies: + string-width "^5.1.2" + string-width-cjs "npm:string-width@^4.2.0" + strip-ansi "^7.0.1" + strip-ansi-cjs "npm:strip-ansi@^6.0.1" + wrap-ansi "^8.1.0" + wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced" @@ -1942,294 +2065,261 @@ "@types/yargs" "^16.0.0" chalk "^4.0.0" -"@jimp/bmp@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.14.0.tgz#6df246026554f276f7b354047c6fff9f5b2b5182" - integrity sha512-5RkX6tSS7K3K3xNEb2ygPuvyL9whjanhoaB/WmmXlJS6ub4DjTqrapu8j4qnIWmO4YYtFeTbDTXV6v9P1yMA5A== +"@jimp/bmp@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.22.12.tgz#0316044dc7b1a90274aef266d50349347fb864d4" + integrity sha512-aeI64HD0npropd+AR76MCcvvRaa+Qck6loCOS03CkkxGHN5/r336qTM5HPUdHKMDOGzqknuVPA8+kK1t03z12g== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" bmp-js "^0.1.0" -"@jimp/core@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/core/-/core-0.14.0.tgz#870c9ca25b40be353ebda1d2abb48723d9010055" - integrity sha512-S62FcKdtLtj3yWsGfJRdFXSutjvHg7aQNiFogMbwq19RP4XJWqS2nOphu7ScB8KrSlyy5nPF2hkWNhLRLyD82w== +"@jimp/core@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/core/-/core-0.22.12.tgz#70785ea7d10b138fb65bcfe9f712826f00a10e1d" + integrity sha512-l0RR0dOPyzMKfjUW1uebzueFEDtCOj9fN6pyTYWWOM/VS4BciXQ1VVrJs8pO3kycGYZxncRKhCoygbNr8eEZQA== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" any-base "^1.1.0" buffer "^5.2.0" exif-parser "^0.1.12" - file-type "^9.0.0" - load-bmfont "^1.3.1" - mkdirp "^0.5.1" - phin "^2.9.1" + file-type "^16.5.4" + isomorphic-fetch "^3.0.0" pixelmatch "^4.0.2" - tinycolor2 "^1.4.1" + tinycolor2 "^1.6.0" -"@jimp/custom@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/custom/-/custom-0.14.0.tgz#1dbbf0094df7403f4e03bc984ed92e7458842f74" - integrity sha512-kQJMeH87+kWJdVw8F9GQhtsageqqxrvzg7yyOw3Tx/s7v5RToe8RnKyMM+kVtBJtNAG+Xyv/z01uYQ2jiZ3GwA== +"@jimp/custom@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/custom/-/custom-0.22.12.tgz#236f2a3f016b533c50869ff22ad1ac00dd0c36be" + integrity sha512-xcmww1O/JFP2MrlGUMd3Q78S3Qu6W3mYTXYuIqFq33EorgYHV/HqymHfXy9GjiCJ7OI+7lWx6nYFOzU7M4rd1Q== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/core" "^0.14.0" + "@jimp/core" "^0.22.12" -"@jimp/gif@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/gif/-/gif-0.14.0.tgz#db159f57c3cfd1566bbe8b124958791998614960" - integrity sha512-DHjoOSfCaCz72+oGGEh8qH0zE6pUBaBxPxxmpYJjkNyDZP7RkbBkZJScIYeQ7BmJxmGN4/dZn+MxamoQlr+UYg== +"@jimp/gif@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/gif/-/gif-0.22.12.tgz#6caccb45df497fb971b7a88690345596e22163c0" + integrity sha512-y6BFTJgch9mbor2H234VSjd9iwAhaNf/t3US5qpYIs0TSbAvM02Fbc28IaDETj9+4YB4676sz4RcN/zwhfu1pg== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" - gifwrap "^0.9.2" + "@jimp/utils" "^0.22.12" + gifwrap "^0.10.1" omggif "^1.0.9" -"@jimp/jpeg@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/jpeg/-/jpeg-0.14.0.tgz#8a687a6a653bbbae38c522edef8f84bb418d9461" - integrity sha512-561neGbr+87S/YVQYnZSTyjWTHBm9F6F1obYHiyU3wVmF+1CLbxY3FQzt4YolwyQHIBv36Bo0PY2KkkU8BEeeQ== +"@jimp/jpeg@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/jpeg/-/jpeg-0.22.12.tgz#b5c74a5aac9826245311370dda8c71a1fcca05ed" + integrity sha512-Rq26XC/uQWaQKyb/5lksCTCxXhtY01NJeBN+dQv5yNYedN0i7iYu+fXEoRsfaJ8xZzjoANH8sns7rVP4GE7d/Q== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" - jpeg-js "^0.4.0" + "@jimp/utils" "^0.22.12" + jpeg-js "^0.4.4" -"@jimp/plugin-blit@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-blit/-/plugin-blit-0.14.0.tgz#5eb374be1201313b2113899fb842232d8fcfd345" - integrity sha512-YoYOrnVHeX3InfgbJawAU601iTZMwEBZkyqcP1V/S33Qnz9uzH1Uj1NtC6fNgWzvX6I4XbCWwtr4RrGFb5CFrw== +"@jimp/plugin-blit@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-blit/-/plugin-blit-0.22.12.tgz#0fa8320767fda77434b4408798655ff7c7e415d4" + integrity sha512-xslz2ZoFZOPLY8EZ4dC29m168BtDx95D6K80TzgUi8gqT7LY6CsajWO0FAxDwHz6h0eomHMfyGX0stspBrTKnQ== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-blur@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-blur/-/plugin-blur-0.14.0.tgz#fe07e4932d5a2f5d8c9831e245561553224bfc60" - integrity sha512-9WhZcofLrT0hgI7t0chf7iBQZib//0gJh9WcQMUt5+Q1Bk04dWs8vTgLNj61GBqZXgHSPzE4OpCrrLDBG8zlhQ== +"@jimp/plugin-blur@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-blur/-/plugin-blur-0.22.12.tgz#0c37b2ff4e588b45f4307b4f13d3d0eef813920d" + integrity sha512-S0vJADTuh1Q9F+cXAwFPlrKWzDj2F9t/9JAbUvaaDuivpyWuImEKXVz5PUZw2NbpuSHjwssbTpOZ8F13iJX4uw== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-circle@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-circle/-/plugin-circle-0.14.0.tgz#82c0e904a34e90fa672fb9c286bc892e92088ddf" - integrity sha512-o5L+wf6QA44tvTum5HeLyLSc5eVfIUd5ZDVi5iRfO4o6GT/zux9AxuTSkKwnjhsG8bn1dDmywAOQGAx7BjrQVA== +"@jimp/plugin-circle@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-circle/-/plugin-circle-0.22.12.tgz#9fffda83d3fc5bad8c1e1492b15b1433cb42e16e" + integrity sha512-SWVXx1yiuj5jZtMijqUfvVOJBwOifFn0918ou4ftoHgegc5aHWW5dZbYPjvC9fLpvz7oSlptNl2Sxr1zwofjTg== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-color@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-color/-/plugin-color-0.14.0.tgz#772bd2d80a88bc66ea1331d010207870f169a74b" - integrity sha512-JJz512SAILYV0M5LzBb9sbOm/XEj2fGElMiHAxb7aLI6jx+n0agxtHpfpV/AePTLm1vzzDxx6AJxXbKv355hBQ== +"@jimp/plugin-color@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-color/-/plugin-color-0.22.12.tgz#1e49f2e7387186507e917b0686599767c15be336" + integrity sha512-xImhTE5BpS8xa+mAN6j4sMRWaUgUDLoaGHhJhpC+r7SKKErYDR0WQV4yCE4gP+N0gozD0F3Ka1LUSaMXrn7ZIA== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" - tinycolor2 "^1.4.1" + "@jimp/utils" "^0.22.12" + tinycolor2 "^1.6.0" -"@jimp/plugin-contain@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-contain/-/plugin-contain-0.14.0.tgz#c68115420d182e696f81bbe76fb5e704909b2b6a" - integrity sha512-RX2q233lGyaxiMY6kAgnm9ScmEkNSof0hdlaJAVDS1OgXphGAYAeSIAwzESZN4x3ORaWvkFefeVH9O9/698Evg== +"@jimp/plugin-contain@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-contain/-/plugin-contain-0.22.12.tgz#ed5ed9af3d4afd02a7568ff8d60603cff340e3f3" + integrity sha512-Eo3DmfixJw3N79lWk8q/0SDYbqmKt1xSTJ69yy8XLYQj9svoBbyRpSnHR+n9hOw5pKXytHwUW6nU4u1wegHNoQ== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-cover@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-cover/-/plugin-cover-0.14.0.tgz#4755322589c5885e44e14e31b86b542e907297ce" - integrity sha512-0P/5XhzWES4uMdvbi3beUgfvhn4YuQ/ny8ijs5kkYIw6K8mHcl820HahuGpwWMx56DJLHRl1hFhJwo9CeTRJtQ== +"@jimp/plugin-cover@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-cover/-/plugin-cover-0.22.12.tgz#4abbfabe4c78c71d8d46e707c35a65dc55f08afd" + integrity sha512-z0w/1xH/v/knZkpTNx+E8a7fnasQ2wHG5ze6y5oL2dhH1UufNua8gLQXlv8/W56+4nJ1brhSd233HBJCo01BXA== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-crop@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-crop/-/plugin-crop-0.14.0.tgz#4cbd856ca84ffc37230fad2534906f2f75aa3057" - integrity sha512-Ojtih+XIe6/XSGtpWtbAXBozhCdsDMmy+THUJAGu2x7ZgKrMS0JotN+vN2YC3nwDpYkM+yOJImQeptSfZb2Sug== +"@jimp/plugin-crop@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-crop/-/plugin-crop-0.22.12.tgz#e28329a9f285071442998560b040048d2ef5c32e" + integrity sha512-FNuUN0OVzRCozx8XSgP9MyLGMxNHHJMFt+LJuFjn1mu3k0VQxrzqbN06yIl46TVejhyAhcq5gLzqmSCHvlcBVw== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-displace@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-displace/-/plugin-displace-0.14.0.tgz#b0e6a57d00cb1f893f541413fe9d737d23c3b70c" - integrity sha512-c75uQUzMgrHa8vegkgUvgRL/PRvD7paFbFJvzW0Ugs8Wl+CDMGIPYQ3j7IVaQkIS+cAxv+NJ3TIRBQyBrfVEOg== +"@jimp/plugin-displace@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-displace/-/plugin-displace-0.22.12.tgz#2e4b2b989a23da6687c49f2f628e1e6d686ec9b6" + integrity sha512-qpRM8JRicxfK6aPPqKZA6+GzBwUIitiHaZw0QrJ64Ygd3+AsTc7BXr+37k2x7QcyCvmKXY4haUrSIsBug4S3CA== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-dither@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-dither/-/plugin-dither-0.14.0.tgz#9185ec4c38e02edc9e5831f5d709f6ba891e1b93" - integrity sha512-g8SJqFLyYexXQQsoh4dc1VP87TwyOgeTElBcxSXX2LaaMZezypmxQfLTzOFzZoK8m39NuaoH21Ou1Ftsq7LzVQ== +"@jimp/plugin-dither@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-dither/-/plugin-dither-0.22.12.tgz#3cc5f3a58dbf85653c4e532d31a756a4fc8cabf7" + integrity sha512-jYgGdSdSKl1UUEanX8A85v4+QUm+PE8vHFwlamaKk89s+PXQe7eVE3eNeSZX4inCq63EHL7cX580dMqkoC3ZLw== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-fisheye@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-fisheye/-/plugin-fisheye-0.14.0.tgz#9f26346cf2fbc660cc2008cd7fd30a83b5029e78" - integrity sha512-BFfUZ64EikCaABhCA6mR3bsltWhPpS321jpeIQfJyrILdpFsZ/OccNwCgpW1XlbldDHIoNtXTDGn3E+vCE7vDg== +"@jimp/plugin-fisheye@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.12.tgz#77aef2f3ec59c0bafbd2dbc94b89eab60ce05a3e" + integrity sha512-LGuUTsFg+fOp6KBKrmLkX4LfyCy8IIsROwoUvsUPKzutSqMJnsm3JGDW2eOmWIS/jJpPaeaishjlxvczjgII+Q== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-flip@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-flip/-/plugin-flip-0.14.0.tgz#7966d6aa3b5fe1aa4d2d561ff12b8ef5ccb9b071" - integrity sha512-WtL1hj6ryqHhApih+9qZQYA6Ye8a4HAmdTzLbYdTMrrrSUgIzFdiZsD0WeDHpgS/+QMsWwF+NFmTZmxNWqKfXw== +"@jimp/plugin-flip@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-flip/-/plugin-flip-0.22.12.tgz#7e2154592da01afcf165a3f9d1d25032aa8d8c57" + integrity sha512-m251Rop7GN8W0Yo/rF9LWk6kNclngyjIJs/VXHToGQ6EGveOSTSQaX2Isi9f9lCDLxt+inBIb7nlaLLxnvHX8Q== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-gaussian@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-gaussian/-/plugin-gaussian-0.14.0.tgz#452bc1971a4467ad9b984aa67f4c200bf941bb65" - integrity sha512-uaLwQ0XAQoydDlF9tlfc7iD9drYPriFe+jgYnWm8fbw5cN+eOIcnneEX9XCOOzwgLPkNCxGox6Kxjn8zY6GxtQ== +"@jimp/plugin-gaussian@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.12.tgz#49a40950cedbbea6c84b3a6bccc45365fe78d6b7" + integrity sha512-sBfbzoOmJ6FczfG2PquiK84NtVGeScw97JsCC3rpQv1PHVWyW+uqWFF53+n3c8Y0P2HWlUjflEla2h/vWShvhg== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-invert@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-invert/-/plugin-invert-0.14.0.tgz#cd31a555860e9f821394936d15af161c09c42921" - integrity sha512-UaQW9X9vx8orQXYSjT5VcITkJPwDaHwrBbxxPoDG+F/Zgv4oV9fP+udDD6qmkgI9taU+44Fy+zm/J/gGcMWrdg== +"@jimp/plugin-invert@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-invert/-/plugin-invert-0.22.12.tgz#c569e85c1f59911a9a33ef36a51c9cf26065078e" + integrity sha512-N+6rwxdB+7OCR6PYijaA/iizXXodpxOGvT/smd/lxeXsZ/empHmFFFJ/FaXcYh19Tm04dGDaXcNF/dN5nm6+xQ== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-mask@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-mask/-/plugin-mask-0.14.0.tgz#52619643ac6222f85e6b27dee33c771ca3a6a4c9" - integrity sha512-tdiGM69OBaKtSPfYSQeflzFhEpoRZ+BvKfDEoivyTjauynbjpRiwB1CaiS8En1INTDwzLXTT0Be9SpI3LkJoEA== +"@jimp/plugin-mask@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-mask/-/plugin-mask-0.22.12.tgz#0ac0d9c282f403255b126556521f90fb8e2997f0" + integrity sha512-4AWZg+DomtpUA099jRV8IEZUfn1wLv6+nem4NRJC7L/82vxzLCgXKTxvNvBcNmJjT9yS1LAAmiJGdWKXG63/NA== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-normalize@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-normalize/-/plugin-normalize-0.14.0.tgz#bf39e356b6d473f582ce95633ad49c9cdb82492b" - integrity sha512-AfY8sqlsbbdVwFGcyIPy5JH/7fnBzlmuweb+Qtx2vn29okq6+HelLjw2b+VT2btgGUmWWHGEHd86oRGSoWGyEQ== +"@jimp/plugin-normalize@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-normalize/-/plugin-normalize-0.22.12.tgz#6c44d216f2489cf9b0e0f1e03aa5dfb97f198c53" + integrity sha512-0So0rexQivnWgnhacX4cfkM2223YdExnJTTy6d06WbkfZk5alHUx8MM3yEzwoCN0ErO7oyqEWRnEkGC+As1FtA== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-print@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-print/-/plugin-print-0.14.0.tgz#1c43c2a92a7adc05b464863882cb89ce486d63e6" - integrity sha512-MwP3sH+VS5AhhSTXk7pui+tEJFsxnTKFY3TraFJb8WFbA2Vo2qsRCZseEGwpTLhENB7p/JSsLvWoSSbpmxhFAQ== +"@jimp/plugin-print@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-print/-/plugin-print-0.22.12.tgz#6a49020947a9bf21a5a28324425670a25587ca65" + integrity sha512-c7TnhHlxm87DJeSnwr/XOLjJU/whoiKYY7r21SbuJ5nuH+7a78EW1teOaj5gEr2wYEd7QtkFqGlmyGXY/YclyQ== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" - load-bmfont "^1.4.0" + "@jimp/utils" "^0.22.12" + load-bmfont "^1.4.1" -"@jimp/plugin-resize@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-resize/-/plugin-resize-0.14.0.tgz#ef7fc6c2e45f8bcab62456baf8fd3bc415b02b64" - integrity sha512-qFeMOyXE/Bk6QXN0GQo89+CB2dQcXqoxUcDb2Ah8wdYlKqpi53skABkgVy5pW3EpiprDnzNDboMltdvDslNgLQ== +"@jimp/plugin-resize@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-resize/-/plugin-resize-0.22.12.tgz#f92acbf73beb97dd1fe93b166ef367a323b81e81" + integrity sha512-3NyTPlPbTnGKDIbaBgQ3HbE6wXbAlFfxHVERmrbqAi8R3r6fQPxpCauA8UVDnieg5eo04D0T8nnnNIX//i/sXg== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" + "@jimp/utils" "^0.22.12" -"@jimp/plugin-rotate@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-rotate/-/plugin-rotate-0.14.0.tgz#3632bc159bf1c3b9ec9f459d9c05d02a11781ee7" - integrity sha512-aGaicts44bvpTcq5Dtf93/8TZFu5pMo/61lWWnYmwJJU1RqtQlxbCLEQpMyRhKDNSfPbuP8nyGmaqXlM/82J0Q== - dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" - -"@jimp/plugin-scale@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-scale/-/plugin-scale-0.14.0.tgz#d30f0cd1365b8e68f43fa423300ae7f124e9bf10" - integrity sha512-ZcJk0hxY5ZKZDDwflqQNHEGRblgaR+piePZm7dPwPUOSeYEH31P0AwZ1ziceR74zd8N80M0TMft+e3Td6KGBHw== - dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" - -"@jimp/plugin-shadow@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-shadow/-/plugin-shadow-0.14.0.tgz#471fdb9f109ff2d9e20d533d45e1e18e0b48c749" - integrity sha512-p2igcEr/iGrLiTu0YePNHyby0WYAXM14c5cECZIVnq/UTOOIQ7xIcWZJ1lRbAEPxVVXPN1UibhZAbr3HAb5BjQ== +"@jimp/plugin-rotate@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-rotate/-/plugin-rotate-0.22.12.tgz#2235d45aeb4914ff70d99e95750a6d9de45a0d9f" + integrity sha512-9YNEt7BPAFfTls2FGfKBVgwwLUuKqy+E8bDGGEsOqHtbuhbshVGxN2WMZaD4gh5IDWvR+emmmPPWGgaYNYt1gA== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" - -"@jimp/plugin-threshold@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugin-threshold/-/plugin-threshold-0.14.0.tgz#ebd72721c7d1d518c5bb6e494e55d97ac3351d3b" - integrity sha512-N4BlDgm/FoOMV/DQM2rSpzsgqAzkP0DXkWZoqaQrlRxQBo4zizQLzhEL00T/YCCMKnddzgEhnByaocgaaa0fKw== + "@jimp/utils" "^0.22.12" + +"@jimp/plugin-scale@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-scale/-/plugin-scale-0.22.12.tgz#91f1ec3d114ff44092b946a16e66b14d918e32ed" + integrity sha512-dghs92qM6MhHj0HrV2qAwKPMklQtjNpoYgAB94ysYpsXslhRTiPisueSIELRwZGEr0J0VUxpUY7HgJwlSIgGZw== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" - -"@jimp/plugins@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/plugins/-/plugins-0.14.0.tgz#41dba85f15ab8dadb4162100eb54e5f27b93ee2c" - integrity sha512-vDO3XT/YQlFlFLq5TqNjQkISqjBHT8VMhpWhAfJVwuXIpilxz5Glu4IDLK6jp4IjPR6Yg2WO8TmRY/HI8vLrOw== - dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/plugin-blit" "^0.14.0" - "@jimp/plugin-blur" "^0.14.0" - "@jimp/plugin-circle" "^0.14.0" - "@jimp/plugin-color" "^0.14.0" - "@jimp/plugin-contain" "^0.14.0" - "@jimp/plugin-cover" "^0.14.0" - "@jimp/plugin-crop" "^0.14.0" - "@jimp/plugin-displace" "^0.14.0" - "@jimp/plugin-dither" "^0.14.0" - "@jimp/plugin-fisheye" "^0.14.0" - "@jimp/plugin-flip" "^0.14.0" - "@jimp/plugin-gaussian" "^0.14.0" - "@jimp/plugin-invert" "^0.14.0" - "@jimp/plugin-mask" "^0.14.0" - "@jimp/plugin-normalize" "^0.14.0" - "@jimp/plugin-print" "^0.14.0" - "@jimp/plugin-resize" "^0.14.0" - "@jimp/plugin-rotate" "^0.14.0" - "@jimp/plugin-scale" "^0.14.0" - "@jimp/plugin-shadow" "^0.14.0" - "@jimp/plugin-threshold" "^0.14.0" + "@jimp/utils" "^0.22.12" + +"@jimp/plugin-shadow@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-shadow/-/plugin-shadow-0.22.12.tgz#52e3a1d55f61ddfcfb3265544f8d23b887a667b8" + integrity sha512-FX8mTJuCt7/3zXVoeD/qHlm4YH2bVqBuWQHXSuBK054e7wFRnRnbSLPUqAwSeYP3lWqpuQzJtgiiBxV3+WWwTg== + dependencies: + "@jimp/utils" "^0.22.12" + +"@jimp/plugin-threshold@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugin-threshold/-/plugin-threshold-0.22.12.tgz#1efe20e154bf3a1fc4a5cc016092dbacaa60c958" + integrity sha512-4x5GrQr1a/9L0paBC/MZZJjjgjxLYrqSmWd+e+QfAEPvmRxdRoQ5uKEuNgXnm9/weHQBTnQBQsOY2iFja+XGAw== + dependencies: + "@jimp/utils" "^0.22.12" + +"@jimp/plugins@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/plugins/-/plugins-0.22.12.tgz#45a3b96d2d24cec21d4f8b79d1cfcec6fcb2f1d4" + integrity sha512-yBJ8vQrDkBbTgQZLty9k4+KtUQdRjsIDJSPjuI21YdVeqZxYywifHl4/XWILoTZsjTUASQcGoH0TuC0N7xm3ww== + dependencies: + "@jimp/plugin-blit" "^0.22.12" + "@jimp/plugin-blur" "^0.22.12" + "@jimp/plugin-circle" "^0.22.12" + "@jimp/plugin-color" "^0.22.12" + "@jimp/plugin-contain" "^0.22.12" + "@jimp/plugin-cover" "^0.22.12" + "@jimp/plugin-crop" "^0.22.12" + "@jimp/plugin-displace" "^0.22.12" + "@jimp/plugin-dither" "^0.22.12" + "@jimp/plugin-fisheye" "^0.22.12" + "@jimp/plugin-flip" "^0.22.12" + "@jimp/plugin-gaussian" "^0.22.12" + "@jimp/plugin-invert" "^0.22.12" + "@jimp/plugin-mask" "^0.22.12" + "@jimp/plugin-normalize" "^0.22.12" + "@jimp/plugin-print" "^0.22.12" + "@jimp/plugin-resize" "^0.22.12" + "@jimp/plugin-rotate" "^0.22.12" + "@jimp/plugin-scale" "^0.22.12" + "@jimp/plugin-shadow" "^0.22.12" + "@jimp/plugin-threshold" "^0.22.12" timm "^1.6.1" -"@jimp/png@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/png/-/png-0.14.0.tgz#0f2dddb5125c0795ca7e67c771204c5437fcda4b" - integrity sha512-0RV/mEIDOrPCcNfXSPmPBqqSZYwGADNRVUTyMt47RuZh7sugbYdv/uvKmQSiqRdR0L1sfbCBMWUEa5G/8MSbdA== +"@jimp/png@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/png/-/png-0.22.12.tgz#e033586caf38d9c9d33808e92eb87c4d7f0aa1eb" + integrity sha512-Mrp6dr3UTn+aLK8ty/dSKELz+Otdz1v4aAXzV5q53UDD2rbB5joKVJ/ChY310B+eRzNxIovbUF1KVrUsYdE8Hg== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.14.0" - pngjs "^3.3.3" + "@jimp/utils" "^0.22.12" + pngjs "^6.0.0" -"@jimp/tiff@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/tiff/-/tiff-0.14.0.tgz#a5b25bbe7c43fc3b07bad4e2ab90e0e164c1967f" - integrity sha512-zBYDTlutc7j88G/7FBCn3kmQwWr0rmm1e0FKB4C3uJ5oYfT8645lftUsvosKVUEfkdmOaMAnhrf4ekaHcb5gQw== +"@jimp/tiff@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/tiff/-/tiff-0.22.12.tgz#67cac3f2ded6fde3ef631fbf74bea0fa53800123" + integrity sha512-E1LtMh4RyJsoCAfAkBRVSYyZDTtLq9p9LUiiYP0vPtXyxX4BiYBUYihTLSBlCQg5nF2e4OpQg7SPrLdJ66u7jg== dependencies: - "@babel/runtime" "^7.7.2" - utif "^2.0.1" + utif2 "^4.0.1" -"@jimp/types@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/types/-/types-0.14.0.tgz#ef681ff702883c5f105b5e4e30d49abf39ee9e34" - integrity sha512-hx3cXAW1KZm+b+XCrY3LXtdWy2U+hNtq0rPyJ7NuXCjU7lZR3vIkpz1DLJ3yDdS70hTi5QDXY3Cd9kd6DtloHQ== - dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/bmp" "^0.14.0" - "@jimp/gif" "^0.14.0" - "@jimp/jpeg" "^0.14.0" - "@jimp/png" "^0.14.0" - "@jimp/tiff" "^0.14.0" +"@jimp/types@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/types/-/types-0.22.12.tgz#6f83761ba171cb8cd5998fa66a5cbfb0b22d3d8c" + integrity sha512-wwKYzRdElE1MBXFREvCto5s699izFHNVvALUv79GXNbsOVqlwlOxlWJ8DuyOGIXoLP4JW/m30YyuTtfUJgMRMA== + dependencies: + "@jimp/bmp" "^0.22.12" + "@jimp/gif" "^0.22.12" + "@jimp/jpeg" "^0.22.12" + "@jimp/png" "^0.22.12" + "@jimp/tiff" "^0.22.12" timm "^1.6.1" -"@jimp/utils@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@jimp/utils/-/utils-0.14.0.tgz#296254e63118554c62c31c19ac6b8c4bfe6490e5" - integrity sha512-MY5KFYUru0y74IsgM/9asDwb3ERxWxXEu3CRCZEvE7DtT86y1bR1XgtlSliMrptjz4qbivNGMQSvUBpEFJDp1A== +"@jimp/utils@^0.22.12": + version "0.22.12" + resolved "https://registry.yarnpkg.com/@jimp/utils/-/utils-0.22.12.tgz#8ffaed8f2dc2962539ccaf14727ac60793c7a537" + integrity sha512-yJ5cWUknGnilBq97ZXOyOS0HhsHOyAyjHwYfHxGbSyMTohgQI6sVyE8KPgDwH8HHW/nMKXk8TrSwAE71zt716Q== dependencies: - "@babel/runtime" "^7.7.2" regenerator-runtime "^0.13.3" "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": @@ -2241,6 +2331,15 @@ "@jridgewell/sourcemap-codec" "^1.4.10" "@jridgewell/trace-mapping" "^0.3.9" +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + "@jridgewell/resolve-uri@^3.1.0": version "3.1.1" resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721" @@ -2251,12 +2350,12 @@ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw== -"@jridgewell/sourcemap-codec@^1.4.10": - version "1.4.11" - resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.11.tgz#771a1d8d744eeb71b6adb35808e1a6c7b9b8c8ec" - integrity sha512-Fg32GrJo61m+VqYSdRSjRXMjQ06j8YIYfcTqndLYVAaHmroZHLJZCydsWBOTDqXS2v+mjxohBWEMfg97GXmYQg== +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== -"@jridgewell/sourcemap-codec@^1.4.14": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -2269,6 +2368,14 @@ "@jridgewell/resolve-uri" "^3.1.0" "@jridgewell/sourcemap-codec" "^1.4.14" +"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + "@kwsites/file-exists@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" @@ -2373,14 +2480,6 @@ resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.13.2.tgz#3b0efb6d3903bd49edb073696f60e90df08efb26" integrity sha512-WrHvO8PDL8wd8T2+zBGKrMwVL5IyzR3ryWUsl0PXgEV0QHup4mTLi0QcATefGI6Gx9Anu7vthPyyyLpY0EpiQg== -"@mrmlnc/readdir-enhanced@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" - integrity sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g== - dependencies: - call-me-maybe "^1.0.1" - glob-to-regexp "^0.3.0" - "@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2": version "3.0.2" resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz#44d752c1a2dc113f15f781b7cc4f53a307e3fa38" @@ -2513,11 +2612,6 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.stat@^1.1.2": - version "1.1.3" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" - integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== - "@nodelib/fs.walk@^1.2.3": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" @@ -2542,14 +2636,19 @@ mkdirp "^1.0.4" rimraf "^3.0.2" -"@opensearch-project/opensearch-next@npm:@opensearch-project/opensearch@^2.6.0": - version "2.6.0" - resolved "https://registry.yarnpkg.com/@opensearch-project/opensearch/-/opensearch-2.6.0.tgz#cbacb34f92aed04e98cabcdc0dc65eb495023880" - integrity sha512-zgDSa/qUpoEwA+Nxjtv0qtln63M+hS4SVO94R9XjwzJAoqsUiNMjjzF6D6Djq/xJMgCzIYjvBZ5vUlB8/kXwjQ== +"@opensearch-dashboards-test/opensearch-dashboards-test-library@https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.6.tar.gz": + version "1.0.6" + resolved "https://github.com/opensearch-project/opensearch-dashboards-test-library/archive/refs/tags/1.0.6.tar.gz#f2f489832a75191e243c6d2b42d49047265d9ce3" + +"@opensearch-project/opensearch-next@npm:@opensearch-project/opensearch@^2.9.0": + version "2.9.0" + resolved "https://registry.yarnpkg.com/@opensearch-project/opensearch/-/opensearch-2.9.0.tgz#319b4d174540b6d000c31477a56618e5054c6fcb" + integrity sha512-BXPWSBME1rszZ8OvtBVQ9F6kLiZSENDSFPawbPa1fv0GouuQfWxkKSI9TcnfGLp869fgLTEIfeC5Qexd4RbAYw== dependencies: aws4 "^1.11.0" debug "^4.3.1" hpagent "^1.2.0" + json11 "^1.0.4" ms "^2.1.3" secure-json-parse "^2.4.0" @@ -2697,6 +2796,11 @@ resolved "https://registry.yarnpkg.com/@percy/sdk-utils/-/sdk-utils-1.10.4.tgz#5cab2f29f75588372743713b634e0780abdc681e" integrity sha512-5MTB30SSKLMMX3Mc19Ig62stZJeKbEyRZpVj8df47GQB4s5vbB3qtRwy0cmJBwcbDZxU5LWYQABsfr9UdAKvVg== +"@pkgjs/parseargs@^0.11.0": + version "0.11.0" + resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" + integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== + "@popperjs/core@^2.4.0": version "2.11.4" resolved "https://registry.yarnpkg.com/@popperjs/core/-/core-2.11.4.tgz#d8c7b8db9226d2d7664553a0741ad7d0397ee503" @@ -2871,6 +2975,11 @@ "@testing-library/dom" "^8.0.0" "@types/react-dom" "*" +"@tokenizer/token@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276" + integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A== + "@tootallnate/once@1": version "1.1.2" resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" @@ -3151,14 +3260,6 @@ "@types/minimatch" "*" "@types/node" "*" -"@types/globby@^8.0.0": - version "8.0.0" - resolved "https://registry.yarnpkg.com/@types/globby/-/globby-8.0.0.tgz#7bd10eaf802e1e11afdb1e5436cf472ddf4c0dd2" - integrity sha512-xDtsX5tlctxJzvg29r/LN12z30oJpoFP9cE8eJ8nY5cbSvN0c0RdRHrVlEq4LRh362Sd+JsqxJ3QWw0Wnyto8w== - dependencies: - "@types/glob" "*" - fast-glob "^2.0.2" - "@types/graceful-fs@*", "@types/graceful-fs@^4.1.2": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.5.tgz#21ffba0d98da4350db64891f92a9e5db3cdb4e15" @@ -3390,11 +3491,6 @@ "@types/node" "*" "@types/webpack" "^4" -"@types/lodash@4.14.192": - version "4.14.192" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.192.tgz#5790406361a2852d332d41635d927f1600811285" - integrity sha512-km+Vyn3BYm5ytMO13k9KTp27O75rbQ0NFw+U//g+PX7VZyjCioXaRFisqSIJRECljcTv73G3i6BpglNGHgUQ5A== - "@types/lodash@^4.14.170": version "4.14.181" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.181.tgz#d1d3740c379fda17ab175165ba04e2d03389385d" @@ -3516,11 +3612,6 @@ resolved "https://registry.yarnpkg.com/@types/normalize-path/-/normalize-path-3.0.0.tgz#bb5c46cab77b93350b4cf8d7ff1153f47189ae31" integrity sha512-Nd8y/5t/7CRakPYiyPzr/IAfYusy1FkcZYFEAcoMZkwpJv2n4Wm+olW+e7xBdHEXhOnWdG9ddbar0gqZWS4x5Q== -"@types/numeral@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@types/numeral/-/numeral-2.0.2.tgz#8ea2c4f4e64c0cc948ad7da375f6f827778a7912" - integrity sha512-A8F30k2gYJ/6e07spSCPpkuZu79LCnkPTvqmIWQzNGcrzwFKpVOydG41lNt5wZXjSI149qjyzC2L1+F2PD/NUA== - "@types/ora@^1.3.5": version "1.3.5" resolved "https://registry.yarnpkg.com/@types/ora/-/ora-1.3.5.tgz#1a08bf64902c1473d3d47de58549a49e07140f1c" @@ -3538,11 +3629,6 @@ resolved "https://registry.yarnpkg.com/@types/parse-link-header/-/parse-link-header-1.0.1.tgz#6eade790736a050b9242c7c6b2fceda9b3dc394a" integrity sha512-E2+Go9rQgPbmpkeA2iFXTWSTxX38KXlXwcdiIbt71Oorqr+G5QtH4AhpuDdxwRVyiTzdUrHnaaIumW/LhiZwVg== -"@types/parse5@^5.0.0": - version "5.0.3" - resolved "https://registry.yarnpkg.com/@types/parse5/-/parse5-5.0.3.tgz#e7b5aebbac150f8b5fdd4a46e7f0bd8e65e19109" - integrity sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw== - "@types/pegjs@^0.10.1": version "0.10.3" resolved "https://registry.yarnpkg.com/@types/pegjs/-/pegjs-0.10.3.tgz#9e254036c6bf2254cd98caec447a1d79b6607bff" @@ -3560,11 +3646,6 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.4.4.tgz#5d9b63132df54d8909fce1c3f8ca260fdd693e17" integrity sha512-ReVR2rLTV1kvtlWFyuot+d1pkpG2Fw/XKE3PDAdj57rbM97ttSp9JZ2UsP+2EHTylra9cUf6JA7tGwW1INzUrA== -"@types/prismjs@*": - version "1.26.0" - resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.0.tgz#a1c3809b0ad61c62cac6d4e0c56d610c910b7654" - integrity sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ== - "@types/prop-types@*", "@types/prop-types@^15.7.3": version "15.7.4" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.4.tgz#fcf7205c25dff795ee79af1e30da2c9790808f11" @@ -3658,13 +3739,6 @@ dependencies: "@types/react" "*" -"@types/react-virtualized-auto-sizer@^1.0.1": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/react-virtualized-auto-sizer/-/react-virtualized-auto-sizer-1.0.1.tgz#b3187dae1dfc4c15880c9cfc5b45f2719ea6ebd4" - integrity sha512-GH8sAnBEM5GV9LTeiz56r4ZhMOUSrP43tAQNSRVxNexDjcNKLCEtnxusAItg1owFUFE6k0NslV26gqVClVvong== - dependencies: - "@types/react" "*" - "@types/react-virtualized@^9.18.7": version "9.21.20" resolved "https://registry.yarnpkg.com/@types/react-virtualized/-/react-virtualized-9.21.20.tgz#756c78b5512a2a1804fdaf749a5f5cff3d805e5b" @@ -3710,18 +3784,6 @@ dependencies: redux "^4.0.5" -"@types/refractor@^3.0.0": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@types/refractor/-/refractor-3.0.2.tgz#2d42128d59f78f84d2c799ffc5ab5cadbcba2d82" - integrity sha512-2HMXuwGuOqzUG+KUTm9GDJCHl0LCBKsB5cg28ujEmVi/0qgTb6jOmkVSO5K48qXksyl2Fr3C0Q2VrgD4zbwyXg== - dependencies: - "@types/prismjs" "*" - -"@types/resize-observer-browser@^0.1.7": - version "0.1.7" - resolved "https://registry.yarnpkg.com/@types/resize-observer-browser/-/resize-observer-browser-0.1.7.tgz#294aaadf24ac6580b8fbd1fe3ab7b59fe85f9ef3" - integrity sha512-G9eN0Sn0ii9PWQ3Vl72jDPgeJwRWhv2Qk/nQkJuWmRmOB4HX3/BhD5SE1dZs/hzPZL/WKnvF0RHdTSG54QJFyg== - "@types/responselike@*", "@types/responselike@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@types/responselike/-/responselike-1.0.0.tgz#251f4fe7d154d2bad125abe1b429b23afd262e29" @@ -3904,14 +3966,13 @@ "@types/expect" "^1.20.4" "@types/node" "*" -"@types/watchpack@^1.1.6": - version "1.1.7" - resolved "https://registry.yarnpkg.com/@types/watchpack/-/watchpack-1.1.7.tgz#9e680b223294cdfc10cb5a04da0543a97023cdd7" - integrity sha512-2XsUDmKGy5ymzQ48ev61nMimudAiZEH/qEBo9zueKA45F8OXIAD2iwEFZPi+uCHnsxcV5MMnbu+Hkx0CLgfTxw== +"@types/watchpack@^2.4.4": + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/watchpack/-/watchpack-2.4.4.tgz#d492bca62ba73cf041eda26e494fe7a540852836" + integrity sha512-SbuSavsPxfOPZwVHBgQUVuzYBe6+8KL7dwiJLXaj5rmv3DxktOMwX5WP1J6UontwUbewjVoc7pCgZvqy6rPn+A== dependencies: "@types/graceful-fs" "*" "@types/node" "*" - chokidar "^2.1.2" "@types/webpack-env@^1.16.3": version "1.16.3" @@ -4489,11 +4550,33 @@ ansi-styles@^5.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b" integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA== +ansi-styles@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" + integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== + ansi-wrap@0.1.0, ansi-wrap@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" integrity sha1-qCJQ3bABXponyoLoLqYDu/pF768= +antlr4-c3@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/antlr4-c3/-/antlr4-c3-3.4.1.tgz#2c2973af98b7e1ed9b5ad1fea9c41aa78e34240f" + integrity sha512-YLO/ncwUp8w2GNK/lnsYXtMkd8izHCWjxtk7EaTGIZq07THfvI5aHDuhls/RctX3EYDlM9zeTKdqn54eLYNglQ== + dependencies: + antlr4ng "^3.0.1" + +antlr4ng-cli@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/antlr4ng-cli/-/antlr4ng-cli-2.0.0.tgz#4f86f3c3818a2170aa1041d4e1633b489af00131" + integrity sha512-oAt5OSSYhRQn1PgahtpAP4Vp3BApCoCqlzX7Q8ZUWWls4hX59ryYuu0t7Hwrnfk796OxP/vgIJaqxdltd/oEvQ== + +antlr4ng@^3.0.1, antlr4ng@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/antlr4ng/-/antlr4ng-3.0.4.tgz#71a47e6148ae75f72fa5f27fbed5ef3462815c7c" + integrity sha512-u1Ww6wVv9hq70E9AaYe5qW3ba8hvnjJdO3ZsKnb3iJWFV/medLEEhbyWwXCvvD2ef0ptdaiIUgmaazS/WE6uyQ== + any-base@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/any-base/-/any-base-1.1.0.tgz#ae101a62bc08a597b4c9ab5b7089d456630549fe" @@ -4504,14 +4587,6 @@ any-observable@^0.3.0: resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog== -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - 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" @@ -4610,11 +4685,6 @@ arr-diff@^4.0.0: resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - arr-union@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" @@ -4664,7 +4734,7 @@ array-slice@^1.0.0: resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-1.1.0.tgz#e368ea15f89bc7069f7ffb89aec3a6c7d4ac22d4" integrity sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w== -array-union@^1.0.1, array-union@^1.0.2: +array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" integrity sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk= @@ -4681,11 +4751,6 @@ array-uniq@^1.0.1: resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - array.prototype.every@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/array.prototype.every/-/array.prototype.every-1.1.3.tgz#31f01b48e1160bc4b49ecab246bf7f765c6686f9" @@ -4707,16 +4772,7 @@ array.prototype.filter@^1.0.0: es-array-method-boxes-properly "^1.0.0" is-string "^1.0.7" -array.prototype.find@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.1.2.tgz#6abbd0c2573925d8094f7d23112306af8c16d534" - integrity sha512-00S1O4ewO95OmmJW7EesWfQlrCrLEL8kZ40w3+GkLX2yTt0m2ggcePPa2uHPJ9KUmJvwRq+lCV9bD8Yim23x/Q== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - -array.prototype.find@^2.2.2: +array.prototype.find@^2.1.1, array.prototype.find@^2.2.2: version "2.2.2" resolved "https://registry.yarnpkg.com/array.prototype.find/-/array.prototype.find-2.2.2.tgz#e862cf891e725d8f2a10e5e42d750629faaabd32" integrity sha512-DRumkfW97iZGOfn+lIXbkVrXL04sfYKX+EfOodo8XboR5sxPDVvOjZTF/rysusa9lmhmSOeD6Vp6RKQP+eP4Tg== @@ -4822,11 +4878,6 @@ async-cache@^1.1.0: dependencies: lru-cache "^4.0.0" -async-each@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" - integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== - async-value-promise@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/async-value-promise/-/async-value-promise-1.1.1.tgz#68957819e3eace804f3b4b69477e2bd276c15378" @@ -4886,26 +4937,10 @@ available-typed-arrays@^1.0.5: resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7" integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw== -aws-sdk@^2.650.0: - version "2.1214.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1214.0.tgz#6a57945b5bc9db79f8ee5ed99128a06110a88f83" - integrity sha512-50WxqYgEDB5UxwPJ0IDFWXe3ipAHhHmqfRnMNaQaZhb2aJpprbT7c0zic8AH9E1xJ9s+6QkhYrwQf/vXEHnLwg== - dependencies: - buffer "4.9.2" - events "1.1.1" - ieee754 "1.1.13" - jmespath "0.16.0" - querystring "0.2.0" - sax "1.2.1" - url "0.10.3" - util "^0.12.4" - uuid "8.0.0" - xml2js "0.4.19" - -aws-sdk@^2.814.0: - version "2.1319.0" - resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1319.0.tgz#be1d91fcac13262fa106f0f0a15ee07c023972fd" - integrity sha512-/gPCVsCARitph9FmBTXZmzjX0Br8LwBfu2MTNPGjVCiZkEUSoUWAAigIWkvrjTWOXCI7TSElRwtCzlsVHdv5VA== +aws-sdk@^2.650.0, aws-sdk@^2.814.0: + version "2.1271.0" + resolved "https://registry.yarnpkg.com/aws-sdk/-/aws-sdk-2.1271.0.tgz#a060fe65ff33afddb7385913200df4a26717f691" + integrity sha512-hQF+mjwe2FXFKOMNQGlfqn9InIL1bRp650ftctRqDo+VpnrYnKqF9eZa5Hk2kugs3/WUa4J2aqQa+foGWeH+Fg== dependencies: buffer "4.9.2" events "1.1.1" @@ -4929,20 +4964,20 @@ axe-core@^4.0.2, axe-core@^4.3.5: integrity sha512-gd1kmb21kwNuWr6BQz8fv6GNECPBnUasepcoLbekws23NVBLODdsClRZ+bQ8+9Uomf3Sm3+Vwn0oYG9NvwnJCw== axios@^0.28.0: - version "0.28.0" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.28.0.tgz#801a4d991d0404961bccef46800e1170f8278c89" - integrity sha512-Tu7NYoGY4Yoc7I+Npf9HhUMtEEpV7ZiLH9yndTCoNhcpBH0kwcvFbzYN9/u5QKI5A6uefjsNNWaz5olJVYS62Q== + version "0.28.1" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.28.1.tgz#2a7bcd34a3837b71ee1a5ca3762214b86b703e70" + integrity sha512-iUcGA5a7p0mVb4Gm/sy+FSECNkPFT4y7wt6OM/CDpO/OnNCvSs3PoMG8ibrC9jRoGYU0gUK5pXVC4NPXq6lHRQ== dependencies: follow-redirects "^1.15.0" form-data "^4.0.0" proxy-from-env "^1.1.0" axios@^1.1.3: - version "1.6.7" - resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.7.tgz#7b48c2e27c96f9c68a2f8f31e2ab19f59b06b0a7" - integrity sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA== + version "1.7.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.7.2.tgz#b625db8a7051fbea61c35a3cbb3a1daa7b9c7621" + integrity sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw== dependencies: - follow-redirects "^1.15.4" + follow-redirects "^1.15.6" form-data "^4.0.0" proxy-from-env "^1.1.0" @@ -5126,19 +5161,6 @@ base64url@^3.0.1: resolved "https://registry.yarnpkg.com/base64url/-/base64url-3.0.1.tgz#6399d572e2bc3f90a9a8b22d5dbb0a32d33f788d" integrity sha512-ir1UPr3dkwexU7FdV8qBBbNDRUhMmIekYMFZfi+C/sLNnRESKPl23nB9b2pltqfOQNnGzsDdId90AEtG5tCx4A== -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - basic-auth@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a" @@ -5156,11 +5178,6 @@ big.js@^5.2.2: resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== -binary-extensions@^1.0.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65" - integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw== - binary-extensions@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" @@ -5171,13 +5188,6 @@ binary-search@^1.3.3: resolved "https://registry.yarnpkg.com/binary-search/-/binary-search-1.3.6.tgz#e32426016a0c5092f0f3598836a1c7da3560565c" integrity sha512-nbE1WxOTTrUWIfsfZ4aHGYu5DOuNkbxGokjV6Z2kxfJK3uaAb8zNK1muzOeipoLHZjInT4Br88BHpzevc681xA== -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - bl@^4.0.3: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" @@ -5207,12 +5217,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== -bn.js@^5.0.0, bn.js@^5.1.1: - version "5.2.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" - integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== - -bn.js@^5.2.1: +bn.js@^5.0.0, bn.js@^5.1.1, bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== @@ -5235,28 +5240,12 @@ brace@0.11.1, brace@^0.11.1: resolved "https://registry.yarnpkg.com/brace/-/brace-0.11.1.tgz#4896fcc9d544eef45f4bb7660db320d3b379fe58" integrity sha1-SJb8ydVE7vRfS7dmDbMg07N5/lg= -braces@^2.3.1, braces@^2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== +braces@^3.0.3, braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== dependencies: - fill-range "^7.0.1" + fill-range "^7.1.1" breadth-filter@^2.0.0: version "2.0.0" @@ -5341,35 +5330,15 @@ browserify-zlib@^0.2.0: dependencies: pako "~1.0.5" -browserslist@*, browserslist@^4.21.10: - version "4.21.10" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" - integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== - dependencies: - caniuse-lite "^1.0.30001517" - electron-to-chromium "^1.4.477" - node-releases "^2.0.13" - update-browserslist-db "^1.0.11" - -browserslist@^4.21.5: - version "4.21.9" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635" - integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg== - dependencies: - caniuse-lite "^1.0.30001503" - electron-to-chromium "^1.4.431" - node-releases "^2.0.12" - update-browserslist-db "^1.0.11" - -browserslist@^4.21.9, browserslist@^4.22.1: - version "4.22.1" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.1.tgz#ba91958d1a59b87dab6fed8dfbcb3da5e2e9c619" - integrity sha512-FEVc202+2iuClEhZhrWy6ZiAcRLvNMyYcxZ8raemul1DYVOVdFsbqckWLdsixQZCpJlwe77Z3UTalE7jsjnKfQ== +browserslist@*, browserslist@^4.21.10, browserslist@^4.21.5, browserslist@^4.21.9, browserslist@^4.22.1: + version "4.23.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" + integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== dependencies: - caniuse-lite "^1.0.30001541" - electron-to-chromium "^1.4.535" - node-releases "^2.0.13" - update-browserslist-db "^1.0.13" + caniuse-lite "^1.0.30001640" + electron-to-chromium "^1.4.820" + node-releases "^2.0.14" + update-browserslist-db "^1.1.0" bser@2.1.1: version "2.1.1" @@ -5507,21 +5476,6 @@ cacache@^15.0.5: tar "^6.0.2" unique-filename "^1.1.1" -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - cacheable-lookup@^5.0.3: version "5.0.4" resolved "https://registry.yarnpkg.com/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz#5a6b865b2c44357be3d5ebc2a467b032719a7005" @@ -5550,15 +5504,7 @@ caching-transform@^4.0.0: package-hash "^4.0.0" write-file-atomic "^3.0.0" -call-bind@^1.0.0, call-bind@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" - integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA== - dependencies: - function-bind "^1.1.1" - get-intrinsic "^1.0.2" - -call-bind@^1.0.4, call-bind@^1.0.5: +call-bind@^1.0.0, call-bind@^1.0.2, call-bind@^1.0.4, call-bind@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.5.tgz#6fa2b7845ce0ea49bf4d8b9ef64727a2c2e2e513" integrity sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ== @@ -5567,11 +5513,6 @@ call-bind@^1.0.4, call-bind@^1.0.5: get-intrinsic "^1.2.1" set-function-length "^1.1.1" -call-me-maybe@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" - integrity sha1-JtII6onje1y95gJQoV8DHBak1ms= - callsites@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" @@ -5601,16 +5542,28 @@ camelize@^1.0.0: resolved "https://registry.yarnpkg.com/camelize/-/camelize-1.0.0.tgz#164a5483e630fa4321e5af07020e531831b2609b" integrity sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs= -caniuse-lite@^1.0.30001464, caniuse-lite@^1.0.30001503, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001541: +caniuse-lite@^1.0.30001464: version "1.0.30001587" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz" integrity sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA== +caniuse-lite@^1.0.30001640: + version "1.0.30001643" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd" + integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== + ccount@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.1.0.tgz#246687debb6014735131be8abab2d93898f8d043" integrity sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg== +centra@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/centra/-/centra-2.7.0.tgz#4c8312a58436e8a718302011561db7e6a2b0ec18" + integrity sha512-PbFMgMSrmgx6uxCdm57RUos9Tc3fclMvhLSATYN39XsDV29B89zZ3KA89jmY0vwSGazyU+uerqwa6t+KaodPcg== + dependencies: + follow-redirects "^1.15.6" + chai@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" @@ -5738,7 +5691,7 @@ cheerio@^1.0.0-rc.3: parse5-htmlparser2-tree-adapter "^6.0.1" tslib "^2.2.0" -chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.2: +chokidar@3.5.3, chokidar@^2.1.8, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.2, chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -5753,25 +5706,6 @@ chokidar@3.5.3, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.2: optionalDependencies: fsevents "~2.3.2" -chokidar@^2.1.2, chokidar@^2.1.8: - version "2.1.8" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" - integrity sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg== - dependencies: - anymatch "^2.0.0" - async-each "^1.0.1" - braces "^2.3.2" - glob-parent "^3.1.0" - inherits "^2.0.3" - is-binary-path "^1.0.0" - is-glob "^4.0.0" - normalize-path "^3.0.0" - path-is-absolute "^1.0.0" - readdirp "^2.2.1" - upath "^1.1.1" - optionalDependencies: - fsevents "^1.2.7" - chownr@^1.1.1, chownr@^1.1.2: version "1.1.4" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.4.tgz#6fc9d7b42d32a583596337666e7d08084da2cc6b" @@ -5823,16 +5757,6 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" integrity sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA== -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - classnames@2.3.1, classnames@2.x, classnames@^2.2.5, classnames@^2.2.6: version "2.3.1" resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.1.tgz#dfcfa3891e306ec1dad105d0e88f4417b8535e8e" @@ -6005,14 +5929,6 @@ collect-v8-coverage@^1.0.0: resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg== -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - color-convert@^1.8.2, color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -6137,7 +6053,7 @@ complex.js@^2.1.1: resolved "https://registry.yarnpkg.com/complex.js/-/complex.js-2.1.1.tgz#0675dac8e464ec431fb2ab7d30f41d889fb25c31" integrity sha512-8njCHOTtFFLtegk6zQo0kkVX1rngygb/KQI6z1qZxlFI3scluC+LVTCFbrkWjBv4vvLlbQ9t88IPMC6k95VTTg== -component-emitter@^1.2.1, component-emitter@^1.3.0: +component-emitter@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== @@ -6242,11 +6158,6 @@ copy-concurrently@^1.0.0: rimraf "^2.5.4" run-queue "^1.0.0" -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - copy-to-clipboard@^3.2.0: version "3.3.1" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" @@ -6864,7 +6775,7 @@ debug@4.3.1: dependencies: ms "2.1.2" -debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: +debug@^2.2.0, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -6894,12 +6805,7 @@ decamelize@^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" - integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ== - -decimal.js@^10.4.3: +decimal.js@^10.2.1, decimal.js@^10.4.3: version "10.4.3" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23" integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA== @@ -6992,22 +6898,7 @@ define-data-property@^1.0.1, define-data-property@^1.1.1: gopd "^1.0.1" has-property-descriptors "^1.0.0" -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== - dependencies: - object-keys "^1.0.12" - -define-properties@^1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1" - integrity sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA== - dependencies: - has-property-descriptors "^1.0.0" - object-keys "^1.1.1" - -define-properties@^1.2.0: +define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.1.tgz#10781cc616eb951a80a034bafcaa7377f6af2b6c" integrity sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg== @@ -7016,28 +6907,6 @@ define-properties@^1.2.0: has-property-descriptors "^1.0.0" object-keys "^1.1.1" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - defined@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" @@ -7175,15 +7044,7 @@ detective@^5.0.2: defined "^1.0.0" minimist "^1.1.1" -dezalgo@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" - integrity sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY= - dependencies: - asap "^2.0.0" - wrappy "1" - -dezalgo@^1.0.4: +dezalgo@^1.0.0, dezalgo@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.4.tgz#751235260469084c132157dfa857f386d4c33d81" integrity sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig== @@ -7225,13 +7086,6 @@ diffie-hellman@^5.0.0: miller-rabin "^4.0.0" randombytes "^2.0.0" -dir-glob@^2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-2.2.2.tgz#fa09f0694153c8918b18ba0deafae94769fc50c4" - integrity sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw== - dependencies: - path-type "^3.0.0" - dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -7406,6 +7260,11 @@ eachr@^4.5.0: dependencies: typechecker "^6.2.0" +eastasianwidth@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" + integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== + ejs@^3.1.10: version "3.1.10" resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" @@ -7477,20 +7336,10 @@ elasticsearch@^16.4.0, elasticsearch@^16.7.0: chalk "^1.0.0" lodash "^4.17.10" -electron-to-chromium@^1.4.431: - version "1.4.477" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.477.tgz#05669aa6f161ee9076a6805457e9bd9fe6d0dfd1" - integrity sha512-shUVy6Eawp33dFBFIoYbIwLHrX0IZ857AlH9ug2o4rvbWmpaCUdBpQ5Zw39HRrfzAFm4APJE9V+E2A/WB0YqJw== - -electron-to-chromium@^1.4.477: - version "1.4.482" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.482.tgz#77c5ed37b93d4dda860e27538e0e2a01d6a19e02" - integrity sha512-h+UqpfmEr1Qkk0zp7ej/jid7CXoq4m4QzW6wNTb0ELJ/BZCpA4wgUylBIMGCe621tnr4l5VmoHjdoSx2lbnNJA== - -electron-to-chromium@^1.4.535: - version "1.4.573" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.573.tgz#aa6e5edf86448bb9398f529357abcc6a17b6341d" - integrity sha512-tzxxvKDTO3V5vzN2F+3v9jrK9gEbCdf1YYJUx/zVq1cyzyh+x1ddeYNNWh0ZS2ETNCVK3+Pns1LHIBq4w20X2Q== +electron-to-chromium@^1.4.820: + version "1.5.0" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.0.tgz#0d3123a9f09189b9c7ab4b5d6848d71b3c1fd0e8" + integrity sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA== elegant-spinner@^1.0.1: version "1.0.1" @@ -7703,62 +7552,7 @@ error-stack-parser@^2.0.6: dependencies: stackframe "^1.1.1" -es-abstract@^1.18.5, es-abstract@^1.19.0, es-abstract@^1.19.1: - version "1.19.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.19.2.tgz#8f7b696d8f15b167ae3640b4060670f3d054143f" - integrity sha512-gfSBJoZdlL2xRiOCy0g8gLMryhoe1TlimjzU99L/31Z8QEGIhVQI+EWwt5lT+AuU9SnorVupXFqqOGqGfsyO6w== - dependencies: - call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - get-intrinsic "^1.1.1" - get-symbol-description "^1.0.0" - has "^1.0.3" - has-symbols "^1.0.3" - internal-slot "^1.0.3" - is-callable "^1.2.4" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.1" - is-string "^1.0.7" - is-weakref "^1.0.2" - object-inspect "^1.12.0" - object-keys "^1.1.1" - object.assign "^4.1.2" - string.prototype.trimend "^1.0.4" - string.prototype.trimstart "^1.0.4" - unbox-primitive "^1.0.1" - -es-abstract@^1.19.5, es-abstract@^1.20.0: - version "1.20.2" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.20.2.tgz#8495a07bc56d342a3b8ea3ab01bd986700c2ccb3" - integrity sha512-XxXQuVNrySBNlEkTYJoDNFe5+s2yIOpzq80sUHEdPdQr0S5nTLz4ZPPPswNIpKseDDUS5yghX1gfLIHQZ1iNuQ== - dependencies: - call-bind "^1.0.2" - es-to-primitive "^1.2.1" - function-bind "^1.1.1" - function.prototype.name "^1.1.5" - get-intrinsic "^1.1.2" - get-symbol-description "^1.0.0" - has "^1.0.3" - has-property-descriptors "^1.0.0" - has-symbols "^1.0.3" - internal-slot "^1.0.3" - is-callable "^1.2.4" - is-negative-zero "^2.0.2" - is-regex "^1.1.4" - is-shared-array-buffer "^1.0.2" - is-string "^1.0.7" - is-weakref "^1.0.2" - object-inspect "^1.12.2" - object-keys "^1.1.1" - object.assign "^4.1.4" - regexp.prototype.flags "^1.4.3" - string.prototype.trimend "^1.0.5" - string.prototype.trimstart "^1.0.5" - unbox-primitive "^1.0.2" - -es-abstract@^1.22.1: +es-abstract@^1.19.0, es-abstract@^1.19.1, es-abstract@^1.22.1: version "1.22.3" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.22.3.tgz#48e79f5573198de6dee3589195727f4f74bc4f32" integrity sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA== @@ -7906,6 +7700,11 @@ escalade@^3.0.2, escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== +escalade@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + escape-latex@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/escape-latex/-/escape-latex-1.2.0.tgz#07c03818cf7dac250cce517f4fda1b001ef2bca1" @@ -8402,19 +8201,6 @@ exit@^0.1.2, exit@~0.1.2: resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw= -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - expand-tilde@^2.0.0, expand-tilde@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" @@ -8444,14 +8230,7 @@ ext@^1.1.2: dependencies: type "^2.5.0" -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: +extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= @@ -8473,20 +8252,6 @@ external-editor@^3.0.3: iconv-lite "^0.4.24" tmp "^0.0.33" -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - extract-opts@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/extract-opts/-/extract-opts-4.3.0.tgz#ee0a797249abb77e449bdaaba66ebeff4775505e" @@ -8516,18 +8281,6 @@ fast-diff@^1.1.2: resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w== -fast-glob@^2.0.2, fast-glob@^2.2.6: - version "2.2.7" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" - integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== - dependencies: - "@mrmlnc/readdir-enhanced" "^2.2.1" - "@nodelib/fs.stat" "^1.1.2" - glob-parent "^3.1.0" - is-glob "^4.0.0" - merge2 "^1.2.3" - micromatch "^3.1.10" - fast-glob@^3.0.3, fast-glob@^3.2.11, fast-glob@^3.2.4, fast-glob@^3.2.9: version "3.2.11" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" @@ -8685,15 +8438,14 @@ file-sync-cmp@^0.1.0: resolved "https://registry.yarnpkg.com/file-sync-cmp/-/file-sync-cmp-0.1.1.tgz#a5e7a8ffbfa493b43b923bbd4ca89a53b63b612b" integrity sha1-peeo/7+kk7Q7kju9TKiaU7Y7YSs= -file-type@^9.0.0: - version "9.0.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-9.0.0.tgz#a68d5ad07f486414dfb2c8866f73161946714a18" - integrity sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw== - -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== +file-type@^16.5.4: + version "16.5.4" + resolved "https://registry.yarnpkg.com/file-type/-/file-type-16.5.4.tgz#474fb4f704bee427681f98dd390058a172a6c2fd" + integrity sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw== + dependencies: + readable-web-to-node-stream "^3.0.0" + strtok3 "^6.2.4" + token-types "^4.1.1" filelist@^1.0.1: version "1.0.2" @@ -8702,20 +8454,10 @@ filelist@^1.0.1: dependencies: minimatch "^3.0.4" -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -8862,7 +8604,7 @@ focus-lock@^0.10.2: dependencies: tslib "^2.0.3" -follow-redirects@^1.15.0, follow-redirects@^1.15.4: +follow-redirects@^1.15.0, follow-redirects@^1.15.4, follow-redirects@^1.15.6: version "1.15.6" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== @@ -8879,7 +8621,7 @@ for-each@^0.3.3: dependencies: is-callable "^1.1.3" -for-in@^1.0.1, for-in@^1.0.2: +for-in@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= @@ -8891,11 +8633,6 @@ for-own@^1.0.0: dependencies: for-in "^1.0.1" -foreach@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" - integrity sha1-C+4AUBiusmDQo6865ljdATbsG5k= - foreground-child@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-2.0.0.tgz#71b32800c9f15aa8f2f83f4a6bd9bff35d861a53" @@ -8904,6 +8641,14 @@ foreground-child@^2.0.0: cross-spawn "^7.0.0" signal-exit "^3.0.2" +foreground-child@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" + integrity sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA== + dependencies: + cross-spawn "^7.0.0" + signal-exit "^4.0.1" + form-data@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" @@ -8947,23 +8692,11 @@ fp-ts@^2.3.1: resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.11.9.tgz#bbc204e0932954b59c98a282635754a4b624a05e" integrity sha512-GhYlNKkCOfdjp71ocdtyaQGoqCswEoWDJLRr+2jClnBBq2dnSOtd6QxmJdALq8UhfqCyZZ0f0lxadU4OhwY9nw== -fraction.js@4.3.4: +fraction.js@4.3.4, fraction.js@^4.2.0: version "4.3.4" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.4.tgz#b2bac8249a610c3396106da97c5a71da75b94b1c" integrity sha512-pwiTgt0Q7t+GHZA4yaLjObx4vXmmdcS0iSJ19o8d/goUGgItX9UZWKWNnLHehxviD8wU2IWRsnR8cD5+yOJP2Q== -fraction.js@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" - integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== - -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - from2@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" @@ -9026,40 +8759,17 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^1.2.7: - version "1.2.13" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.13.tgz#f325cb0455592428bcf11b383370ef70e3bfcc38" - integrity sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw== - dependencies: - bindings "^1.5.0" - nan "^2.12.1" - fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -function-bind@^1.1.2: +function-bind@^1.1.1, function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -function.prototype.name@^1.1.2, function.prototype.name@^1.1.3, function.prototype.name@^1.1.5: - version "1.1.5" - resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621" - integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.0" - functions-have-names "^1.2.2" - -function.prototype.name@^1.1.6: +function.prototype.name@^1.1.2, function.prototype.name@^1.1.3, function.prototype.name@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== @@ -9074,11 +8784,6 @@ functional-red-black-tree@^1.0.1: resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= -functions-have-names@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.2.tgz#98d93991c39da9361f8e50b337c4f6e41f120e21" - integrity sha512-bLgc3asbWdwPbx2mNk2S49kmJCuQeu0nfmaOgbs8WIyzzkw3r4htszdIi9Q9EMezDPTYuJx2wvjZ/EwgAthpnA== - functions-have-names@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" @@ -9105,25 +8810,7 @@ get-caller-file@^2.0.1, get-caller-file@^2.0.5: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-intrinsic@^1.0.1, get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" - integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.1" - -get-intrinsic@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.2.tgz#336975123e05ad0b7ba41f152ee4aadbea6cf598" - integrity sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA== - dependencies: - function-bind "^1.1.1" - has "^1.0.3" - has-symbols "^1.0.3" - -get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: +get-intrinsic@^1.0.1, get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0, get-intrinsic@^1.2.1, get-intrinsic@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.2.tgz#281b7622971123e1ef4b3c90fd7539306da93f3b" integrity sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA== @@ -9173,11 +8860,6 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" -get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - get-value@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/get-value/-/get-value-3.0.1.tgz#5efd2a157f1d6a516d7524e124ac52d0a39ef5a8" @@ -9202,10 +8884,10 @@ getos@^3.2.1: dependencies: async "^3.2.0" -gifwrap@^0.9.2: - version "0.9.4" - resolved "https://registry.yarnpkg.com/gifwrap/-/gifwrap-0.9.4.tgz#f4eb6169ba027d61df64aafbdcb1f8ae58ccc0c5" - integrity sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ== +gifwrap@^0.10.1: + version "0.10.1" + resolved "https://registry.yarnpkg.com/gifwrap/-/gifwrap-0.10.1.tgz#9ed46a5d51913b482d4221ce9c727080260b681e" + integrity sha512-2760b1vpJHNmLzZ/ubTtNnEx5WApN/PYWJvXvgS+tL1egTTthayFYIQQNi136FLEDcN/IyEY2EcGpIITD6eYUw== dependencies: image-q "^4.0.0" omggif "^1.0.10" @@ -9246,11 +8928,6 @@ glob-stream@^6.1.0: to-absolute-glob "^2.0.0" unique-stream "^2.0.2" -glob-to-regexp@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" - integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= - glob-to-regexp@^0.4.0, glob-to-regexp@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" @@ -9280,6 +8957,18 @@ glob@7.2.0, glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glo once "^1.3.0" path-is-absolute "^1.0.0" +glob@^10.3.7: + version "10.4.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" + integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== + dependencies: + foreground-child "^3.1.0" + jackspeak "^3.1.2" + minimatch "^9.0.4" + minipass "^7.1.2" + package-json-from-dist "^1.0.0" + path-scurry "^1.11.1" + glob@~5.0.0: version "5.0.15" resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" @@ -9354,7 +9043,7 @@ globalthis@^1.0.3: dependencies: define-properties "^1.1.3" -globby@^10.0.1: +globby@^10.0.1, globby@^9.2.0: version "10.0.2" resolved "https://registry.yarnpkg.com/globby/-/globby-10.0.2.tgz#277593e745acaa4646c3ab411289ec47a0392543" integrity sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg== @@ -9391,20 +9080,6 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -globby@^9.2.0: - version "9.2.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-9.2.0.tgz#fd029a706c703d29bdd170f4b6db3a3f7a7cb63d" - integrity sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg== - dependencies: - "@types/glob" "^7.1.1" - array-union "^1.0.2" - dir-glob "^2.2.2" - fast-glob "^2.2.6" - glob "^7.1.3" - ignore "^4.0.3" - pify "^4.0.1" - slash "^2.0.0" - globjoin@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/globjoin/-/globjoin-0.1.4.tgz#2f4494ac8919e3767c5cbb691e9f463324285d43" @@ -9626,12 +9301,7 @@ has-ansi@^3.0.0: dependencies: ansi-regex "^3.0.0" -has-bigints@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" - integrity sha512-LSBS2LjbNBTf6287JEbEzvJgftkF5qFkmCo9hDRpAzKhUOlJ+hx8dd4USs00SgsUNwc4617J9ki5YtEClM2ffA== - -has-bigints@^1.0.2: +has-bigints@^1.0.1, has-bigints@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa" integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ== @@ -9685,15 +9355,6 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - has-value@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/has-value/-/has-value-2.0.2.tgz#d0f12e8780ba8e90e66ad1a21c707fdb67c25658" @@ -9702,14 +9363,6 @@ has-value@^2.0.2: get-value "^3.0.0" has-values "^2.0.1" -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - has-values@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/has-values/-/has-values-2.0.1.tgz#3876200ff86d8a8546a9264a952c17d5fc17579d" @@ -9769,18 +9422,6 @@ hast-to-hyperscript@^9.0.0: unist-util-is "^4.0.0" web-namespaces "^1.0.0" -hast-util-from-parse5@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/hast-util-from-parse5/-/hast-util-from-parse5-6.0.1.tgz#554e34abdeea25ac76f5bd950a1f0180e0b3bc2a" - integrity sha512-jeJUWiN5pSxW12Rh01smtVkZgZr33wBokLzKLwinYOUfSzm1Nl/c3GUGebDyOKjdsRgMvoVbV0VpAcpjF4NrJA== - dependencies: - "@types/parse5" "^5.0.0" - hastscript "^6.0.0" - property-information "^5.0.0" - vfile "^4.0.0" - vfile-location "^3.2.0" - web-namespaces "^1.0.0" - hast-util-has-property@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/hast-util-has-property/-/hast-util-has-property-1.0.4.tgz#9f137565fad6082524b382c1e7d7d33ca5059f36" @@ -9801,23 +9442,6 @@ hast-util-parse-selector@^2.0.0: resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz#d57c23f4da16ae3c63b3b6ca4616683313499c3a" integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ== -hast-util-raw@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/hast-util-raw/-/hast-util-raw-6.1.0.tgz#e16a3c2642f65cc7c480c165400a40d604ab75d0" - integrity sha512-5FoZLDHBpka20OlZZ4I/+RBw5piVQ8iI1doEvffQhx5CbCyTtP8UCq8Tw6NmTAMtXgsQxmhW7Ly8OdFre5/YMQ== - dependencies: - "@types/hast" "^2.0.0" - hast-util-from-parse5 "^6.0.0" - hast-util-to-parse5 "^6.0.0" - html-void-elements "^1.0.0" - parse5 "^6.0.0" - unist-util-position "^3.0.0" - unist-util-visit "^2.0.0" - vfile "^4.0.0" - web-namespaces "^1.0.0" - xtend "^4.0.0" - zwitch "^1.0.0" - hast-util-to-html@^7.1.1: version "7.1.3" resolved "https://registry.yarnpkg.com/hast-util-to-html/-/hast-util-to-html-7.1.3.tgz#9f339ca9bea71246e565fc79ff7dbfe98bb50f5e" @@ -9834,17 +9458,6 @@ hast-util-to-html@^7.1.1: unist-util-is "^4.0.0" xtend "^4.0.0" -hast-util-to-parse5@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/hast-util-to-parse5/-/hast-util-to-parse5-6.0.0.tgz#1ec44650b631d72952066cea9b1445df699f8479" - integrity sha512-Lu5m6Lgm/fWuz8eWnrKezHtVY83JeRGaNQ2kn9aJgqaxvVkFCZQBEhgodZUDUvoodgyROHDb3r5IxAEdl6suJQ== - dependencies: - hast-to-hyperscript "^9.0.0" - property-information "^5.0.0" - web-namespaces "^1.0.0" - xtend "^4.0.0" - zwitch "^1.0.0" - hast-util-to-string@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/hast-util-to-string/-/hast-util-to-string-1.0.4.tgz#9b24c114866bdb9478927d7e9c36a485ac728378" @@ -10136,7 +9749,7 @@ iferr@^0.1.5: resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= -ignore@^4.0.3, ignore@^4.0.6: +ignore@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== @@ -10170,12 +9783,7 @@ immediate@~3.0.5: resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= -immer@^9.0.6: - version "9.0.12" - resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20" - integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA== - -immer@^9.0.7: +immer@^9.0.6, immer@^9.0.7: version "9.0.15" resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.15.tgz#0b9169e5b1d22137aba7d43f8a81a495dd1b62dc" integrity sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ== @@ -10292,16 +9900,7 @@ inquirer@^7.0.0, inquirer@^7.3.3: strip-ansi "^6.0.0" through "^2.3.6" -internal-slot@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" - integrity sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA== - dependencies: - get-intrinsic "^1.1.0" - has "^1.0.3" - side-channel "^1.0.4" - -internal-slot@^1.0.5: +internal-slot@^1.0.3, internal-slot@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.6.tgz#37e756098c4911c5e912b8edbf71ed3aa116f930" integrity sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg== @@ -10405,20 +10004,6 @@ is-absolute@^1.0.0: is-relative "^1.0.0" is-windows "^1.0.1" -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - is-alphabetical@^1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-1.0.4.tgz#9e7d6b94916be22153745d184c298cbf986a686d" @@ -10466,13 +10051,6 @@ is-bigint@^1.0.1: dependencies: has-bigints "^1.0.1" -is-binary-path@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" - integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= - dependencies: - binary-extensions "^1.0.0" - is-binary-path@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" @@ -10498,44 +10076,18 @@ is-buffer@^2.0.0: resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.5.tgz#ebc252e400d22ff8d77fa09888821a24a658c191" integrity sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ== -is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.4.tgz#47301d58dd0259407865547853df6d61fe471945" - integrity sha512-nsuwtxZfMX67Oryl9LCQ+upnC0Z0BgpwntpS89m1H/TLF0zNfzfLMV/9Wa/6MZsj0acpEjAO0KF1xT6ZdLl95w== - -is-callable@^1.2.7: +is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.1.5, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.1.0, is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.0, is-core-module@^2.9.0: - version "2.11.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" - integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== - dependencies: - has "^1.0.3" - -is-core-module@^2.13.0, is-core-module@^2.13.1: +is-core-module@^2.1.0, is-core-module@^2.13.0, is-core-module@^2.13.1, is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.0, is-core-module@^2.9.0: version "2.13.1" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== dependencies: hasown "^2.0.0" -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - is-date-object@^1.0.1, is-date-object@^1.0.2: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" @@ -10548,29 +10100,6 @@ is-decimal@^1.0.0: resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-1.0.4.tgz#65a3a5958a1c5b63a706e1b333d7cd9f630d3fa5" integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw== -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - is-extendable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" @@ -10688,13 +10217,6 @@ is-number-object@^1.0.4: dependencies: has-tostringtag "^1.0.0" -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -10793,11 +10315,6 @@ is-set@^2.0.1, is-set@^2.0.2: resolved "https://registry.yarnpkg.com/is-set/-/is-set-2.0.2.tgz#90755fa4c2562dc1c5d4024760d6119b94ca18ec" integrity sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g== -is-shared-array-buffer@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.1.tgz#97b0c85fbdacb59c9c446fe653b82cf2b5b7cfe6" - integrity sha512-IU0NmyknYZN0rChcKhRO1X8LYz5Isj/Fsqh8NJOSf+N/hCOTwy29F32Ik7a+QszE63IdvmwdTPDd6cZ5pg4cwA== - is-shared-array-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79" @@ -10834,35 +10351,13 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.2" -is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.9: +is-typed-array@^1.1.10, is-typed-array@^1.1.12, is-typed-array@^1.1.3, is-typed-array@^1.1.9: version "1.1.12" resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.12.tgz#d0bab5686ef4a76f7a73097b95470ab199c57d4a" integrity sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg== dependencies: which-typed-array "^1.1.11" -is-typed-array@^1.1.3: - version "1.1.9" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.9.tgz#246d77d2871e7d9f5aeb1d54b9f52c71329ece67" - integrity sha512-kfrlnTTn8pZkfpJMUgYD7YZ3qzeJgWUn8XfVYBARc4wnmNOmLbmuuaAs3q5fvB0UJOn6yHAKaGTPM7d6ezoD/A== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - es-abstract "^1.20.0" - for-each "^0.3.3" - has-tostringtag "^1.0.0" - -is-typed-array@^1.1.7: - version "1.1.8" - resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.8.tgz#cbaa6585dc7db43318bc5b89523ea384a6f65e79" - integrity sha512-HqH41TNZq2fgtGT8WHVFVJhBVGuY3AnP3Q36K8JKXUxSxRgk/d+7NjmwG2vo2mYmXK8UYZKu0qH8bVP5gEisjA== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - es-abstract "^1.18.5" - foreach "^2.0.5" - has-tostringtag "^1.0.0" - is-typedarray@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -10993,6 +10488,14 @@ isobject@^4.0.0: resolved "https://registry.yarnpkg.com/isobject/-/isobject-4.0.0.tgz#3f1c9155e73b192022a80819bacd0343711697b0" integrity sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA== +isomorphic-fetch@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz#0267b005049046d2421207215d45d6a262b8b8b4" + integrity sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA== + dependencies: + node-fetch "^2.6.1" + whatwg-fetch "^3.4.1" + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.0.0-alpha.1, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" @@ -11065,6 +10568,15 @@ istanbul-reports@^3.0.2, istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +jackspeak@^3.1.2: + version "3.4.3" + resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" + integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== + dependencies: + "@isaacs/cliui" "^8.0.2" + optionalDependencies: + "@pkgjs/parseargs" "^0.11.0" + jake@^10.8.5: version "10.8.5" resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" @@ -11506,15 +11018,14 @@ jest@^27.5.1: import-local "^3.0.2" jest-cli "^27.5.1" -jimp@^0.14.0: - version "0.14.0" - resolved "https://registry.yarnpkg.com/jimp/-/jimp-0.14.0.tgz#fde55f69bdb918c1b01ac633d89a25853af85625" - integrity sha512-8BXU+J8+SPmwwyq9ELihpSV4dWPTiOKBWCEgtkbnxxAVMjXdf3yGmyaLSshBfXc8sP/JQ9OZj5R8nZzz2wPXgA== +jimp@^0.22.12: + version "0.22.12" + resolved "https://registry.yarnpkg.com/jimp/-/jimp-0.22.12.tgz#f99d1f3ec0d9d930cb7bd8f5b479859ee3a15694" + integrity sha512-R5jZaYDnfkxKJy1dwLpj/7cvyjxiclxU3F4TrI/J4j2rS0niq6YDUMoPn5hs8GDpO+OZGo7Ky057CRtWesyhfg== dependencies: - "@babel/runtime" "^7.7.2" - "@jimp/custom" "^0.14.0" - "@jimp/plugins" "^0.14.0" - "@jimp/types" "^0.14.0" + "@jimp/custom" "^0.22.12" + "@jimp/plugins" "^0.22.12" + "@jimp/types" "^0.22.12" regenerator-runtime "^0.13.3" jit-grunt@0.10.0: @@ -11552,7 +11063,7 @@ joi@^17.3.0: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" -jpeg-js@^0.4.0: +jpeg-js@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.4.tgz#a9f1c6f1f9f0fa80cdb3484ed9635054d28936aa" integrity sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg== @@ -11712,6 +11223,11 @@ json-stringify-safe@5.0.1, json-stringify-safe@^5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== +json11@^1.0.4, json11@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/json11/-/json11-1.1.2.tgz#35ffd3ee5073b0cc09ef826b0a0dc005ebef2b5b" + integrity sha512-5r1RHT1/Gr/jsI/XZZj/P6F11BKM8xvTaftRuiLkQI9Z2PFDukM82Ysxw8yDszb3NJP/NKnRlSGmhUdG99rlBw== + json5@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" @@ -11776,7 +11292,7 @@ keyv@^4.0.0: dependencies: json-buffer "3.0.1" -kind-of@>=6.0.3, kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0, kind-of@^4.0.0, kind-of@^5.0.0, kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: +kind-of@>=6.0.3, kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== @@ -11990,7 +11506,7 @@ lmdb@^2.8.0: "@lmdb/lmdb-linux-x64" "2.8.5" "@lmdb/lmdb-win32-x64" "2.8.5" -load-bmfont@^1.3.1, load-bmfont@^1.4.0: +load-bmfont@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/load-bmfont/-/load-bmfont-1.4.1.tgz#c0f5f4711a1e2ccff725a7b6078087ccfcddd3e9" integrity sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA== @@ -12256,6 +11772,11 @@ lowercase-keys@^2.0.0: resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-2.0.0.tgz#2603e78b7b4b0006cbca2fbcc8a3202558ac9479" integrity sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA== +lru-cache@^10.2.0: + version "10.4.3" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" + integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== + lru-cache@^4.0.0, lru-cache@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" @@ -12324,7 +11845,7 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -map-cache@^0.2.0, map-cache@^0.2.2: +map-cache@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= @@ -12339,13 +11860,6 @@ map-obj@^4.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.3.0.tgz#9304f906e93faae70880da102a9f1df0ea8bb05a" integrity sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ== -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - mapcap@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/mapcap/-/mapcap-1.0.0.tgz#e8e29d04a160eaf8c92ec4bcbd2c5d07ed037e5a" @@ -12542,31 +12056,12 @@ methods@^1.1.1, methods@^1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micromatch@3.1.10, micromatch@^3.1.10, micromatch@^3.1.4: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - -micromatch@^4.0.2, micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.7: + version "4.0.7" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" + integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" miller-rabin@^4.0.0: @@ -12658,7 +12153,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@5.0.1, 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@^9.0.4, 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== @@ -12712,6 +12207,11 @@ minipass@^5.0.0: resolved "https://registry.yarnpkg.com/minipass/-/minipass-5.0.0.tgz#3e9788ffb90b694a5d0ec94479a45b5d8738133d" integrity sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ== +"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" + integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== + minizlib@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931" @@ -12736,14 +12236,6 @@ mississippi@^3.0.0: stream-each "^1.1.0" through2 "^2.0.0" -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - mkdirp-classic@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" @@ -12931,11 +12423,6 @@ mute-stream@0.0.8: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -nan@^2.12.1: - version "2.15.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.15.0.tgz#3f34a473ff18e15c1b5626b62903b5ad6e665fee" - integrity sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ== - nano-css@^5.2.1: version "5.3.4" resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.3.4.tgz#40af6a83a76f84204f346e8ccaa9169cdae9167b" @@ -12950,33 +12437,11 @@ nano-css@^5.2.1: stacktrace-js "^2.0.2" stylis "^4.0.6" -nanoid@3.3.3: +nanoid@3.3.3, nanoid@^3.3.1: 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" - integrity sha512-CuHBogktKwpm5g2sRgv83jEy2ijFzBwMoYA60orPDR7ynsLijJDqgsi4RDGj3OJpy3Ieb+LYwiRmIOGyytgITA== - -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -13072,10 +12537,10 @@ node-emoji@^1.10.0: dependencies: lodash "^4.17.21" -node-fetch@^2.6.7: - version "2.6.7" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" - integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== +node-fetch@^2.6.1, node-fetch@^2.6.7: + version "2.7.0" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" + integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== dependencies: whatwg-url "^5.0.0" @@ -13152,10 +12617,10 @@ node-preload@^0.2.1: dependencies: process-on-spawn "^1.0.0" -node-releases@^2.0.12, node-releases@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== +node-releases@^2.0.14: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== node-stream-zip@^1.15.0: version "1.15.0" @@ -13310,15 +12775,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-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - object-filter-sequence@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/object-filter-sequence/-/object-filter-sequence-1.0.0.tgz#10bb05402fff100082b80d7e83991b10db411692" @@ -13331,17 +12787,7 @@ object-identity-map@^1.0.2: dependencies: object.entries "^1.1.0" -object-inspect@^1.12.0, object-inspect@^1.7.0, object-inspect@^1.9.0: - version "1.12.0" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" - integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== - -object-inspect@^1.12.2: - version "1.12.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" - integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== - -object-inspect@^1.13.1: +object-inspect@^1.12.0, object-inspect@^1.13.1, object-inspect@^1.7.0, object-inspect@^1.9.0: version "1.13.1" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== @@ -13354,29 +12800,12 @@ 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.12, object-keys@^1.1.1: +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== -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - -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" - integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ== - dependencies: - call-bind "^1.0.0" - define-properties "^1.1.3" - has-symbols "^1.0.1" - object-keys "^1.1.1" - -object.assign@^4.1.4: +object.assign@^4.0.4, object.assign@^4.1.0, object.assign@^4.1.2, object.assign@^4.1.4: version "4.1.4" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f" integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ== @@ -13430,7 +12859,7 @@ object.map@^1.0.1: for-own "^1.0.0" make-iterator "^1.0.0" -object.pick@^1.2.0, object.pick@^1.3.0: +object.pick@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= @@ -13676,7 +13105,12 @@ package-hash@^4.0.0: lodash.flattendeep "^4.4.0" release-zalgo "^1.0.0" -pako@^1.0.5, pako@~1.0.2, pako@~1.0.5: +package-json-from-dist@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" + integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== + +pako@^1.0.11, pako@~1.0.2, pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== @@ -13803,7 +13237,7 @@ parse5-htmlparser2-tree-adapter@^6.0.1: dependencies: parse5 "^6.0.1" -parse5@6.0.1, parse5@^6.0.0, parse5@^6.0.1: +parse5@6.0.1, parse5@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b" integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw== @@ -13815,11 +13249,6 @@ parse5@^3.0.1: dependencies: "@types/node" "*" -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - path-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" @@ -13872,6 +13301,14 @@ path-root@^0.1.1: dependencies: path-root-regex "^0.1.0" +path-scurry@^1.11.1: + version "1.11.1" + resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" + integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== + dependencies: + lru-cache "^10.2.0" + minipass "^5.0.0 || ^6.0.2 || ^7.0.0" + path-to-regexp@^1.7.0: version "1.8.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" @@ -13889,13 +13326,6 @@ path-to-regexp@^6.2.0: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-6.2.0.tgz#f7b3803336104c346889adece614669230645f38" integrity sha512-f66KywYG6+43afgE/8j/GoiNyygk/bnoCbps++3ErRKsIYkGGupyv07R2Ok5m9i67Iqc+T2g1eAUGUPzWhYTyg== -path-type@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" - integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg== - dependencies: - pify "^3.0.0" - path-type@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" @@ -13912,6 +13342,11 @@ pbkdf2@^3.0.3: safe-buffer "^5.0.1" sha.js "^2.4.8" +peek-readable@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72" + integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg== + pegjs@0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/pegjs/-/pegjs-0.10.0.tgz#cf8bafae6eddff4b5a7efb185269eaaf4610ddbd" @@ -13932,16 +13367,23 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -phin@^2.9.1: - version "2.9.3" - resolved "https://registry.yarnpkg.com/phin/-/phin-2.9.3.tgz#f9b6ac10a035636fb65dfc576aaaa17b8743125c" - integrity sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA== +phin@^2.9.1, phin@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/phin/-/phin-3.7.1.tgz#bf841da75ee91286691b10e41522a662aa628fd6" + integrity sha512-GEazpTWwTZaEQ9RhL7Nyz0WwqilbqgLahDM3D0hxWwmVDI52nXEybHqiN6/elwpkJBhcuj+WbBu+QfT0uhPGfQ== + dependencies: + centra "^2.7.0" picocolors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== +picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3, picomatch@^2.3.0, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" @@ -13952,11 +13394,6 @@ pify@^2.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= -pify@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" - integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= - pify@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/pify/-/pify-4.0.1.tgz#4b2cd25c50d598735c50292224fd8c6df41e3231" @@ -14056,7 +13493,7 @@ plur@^4.0.0: dependencies: irregular-plurals "^3.2.0" -pngjs@^3.0.0, pngjs@^3.3.3, pngjs@^3.4.0: +pngjs@^3.0.0, pngjs@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.4.0.tgz#99ca7d725965fb655814eaf65f38f12bbdbf555f" integrity sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w== @@ -14066,10 +13503,10 @@ pngjs@^4.0.1: resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-4.0.1.tgz#f803869bb2fc1bfe1bf99aa4ec21c108117cfdbe" integrity sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg== -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= +pngjs@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-6.0.0.tgz#ca9e5d2aa48db0228a52c419c3308e87720da821" + integrity sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg== postcss-loader@^4.2.0: version "4.3.0" @@ -14846,10 +14283,10 @@ read-pkg@^5.2.0: string_decoder "~1.1.1" util-deprecate "~1.0.1" -"readable-stream@2 || 3", readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" - integrity sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA== +"readable-stream@2 || 3", readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0, readable-stream@^3.6.2: + version "3.6.2" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" + integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" @@ -14865,14 +14302,12 @@ read-pkg@^5.2.0: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@^3.6.2: - version "3.6.2" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" - integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA== +readable-web-to-node-stream@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz#5d52bb5df7b54861fd48d015e93a2cb87b3ee0bb" + integrity sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw== dependencies: - inherits "^2.0.3" - string_decoder "^1.1.1" - util-deprecate "^1.0.1" + readable-stream "^3.6.0" readdir-glob@^1.0.0: version "1.1.1" @@ -14891,15 +14326,6 @@ readdir-scoped-modules@^1.0.0: graceful-fs "^4.1.2" once "^1.3.0" -readdirp@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" - integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== - dependencies: - graceful-fs "^4.1.11" - micromatch "^3.1.10" - readable-stream "^2.0.2" - readdirp@~3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" @@ -14941,14 +14367,7 @@ redux-thunk@^2.3.0, redux-thunk@^2.4.1: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz#0dd8042cf47868f4b29699941de03c9301a75714" integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== -redux@^4.0.0, redux@^4.0.4, redux@^4.0.5: - version "4.1.2" - resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104" - integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw== - dependencies: - "@babel/runtime" "^7.9.2" - -redux@^4.1.2: +redux@^4.0.0, redux@^4.0.4, redux@^4.0.5, redux@^4.1.2: version "4.2.0" resolved "https://registry.yarnpkg.com/redux/-/redux-4.2.0.tgz#46f10d6e29b6666df758780437651eeb2b969f13" integrity sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA== @@ -15018,32 +14437,7 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -regexp.prototype.flags@^1.3.0, regexp.prototype.flags@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307" - integrity sha512-pMR7hBVUUGI7PMA37m2ofIdQCsomVnas+Jn5UPGAHQ+/LlwKm/aTLJHdasmHRzlfeZwHiAOaRSo2rbBDm3nNUQ== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - -regexp.prototype.flags@^1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac" - integrity sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - functions-have-names "^1.2.2" - -regexp.prototype.flags@^1.5.1: +regexp.prototype.flags@^1.3.0, regexp.prototype.flags@^1.4.1, regexp.prototype.flags@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.1.tgz#90ce989138db209f81492edd734183ce99f9677e" integrity sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg== @@ -15081,13 +14475,6 @@ regjsparser@^0.9.1: dependencies: jsesc "~0.5.0" -rehype-raw@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/rehype-raw/-/rehype-raw-5.1.0.tgz#66d5e8d7188ada2d31bc137bc19a1000cf2c6b7e" - integrity sha512-MDvHAb/5mUnif2R+0IPCYJU8WjHa9UzGtM/F4AVy5GixPlDZ1z3HacYy4xojDU+uBa+0X/3PIfyQI26/2ljJNA== - dependencies: - hast-util-raw "^6.1.0" - rehype-react@^6.0.0: version "6.2.1" resolved "https://registry.yarnpkg.com/rehype-react/-/rehype-react-6.2.1.tgz#9b9bf188451ad6f63796b784fe1f51165c67b73a" @@ -15214,12 +14601,7 @@ remove-trailing-separator@^1.0.1: resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= -repeat-element@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" - integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== - -repeat-string@^1.5.4, repeat-string@^1.6.1: +repeat-string@^1.5.4: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= @@ -15278,12 +14660,7 @@ requires-regex@^0.3.3: resolved "https://registry.yarnpkg.com/requires-regex/-/requires-regex-0.3.3.tgz#60309eaabbfd5ca8259e090b8b5a94b2144eb0dd" integrity sha1-YDCeqrv9XKglngkLi1qUshROsN0= -reselect@^4.0.0: - version "4.1.5" - resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.5.tgz#852c361247198da6756d07d9296c2b51eddb79f6" - integrity sha512-uVdlz8J7OO+ASpBYoz1Zypgx0KasCY20H+N8JD13oUMtPvSHQuscrHop4KbXrbsBcdB9Ds7lVK7eRkBIfO43vQ== - -reselect@^4.1.5: +reselect@^4.0.0, reselect@^4.1.5: version "4.1.6" resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.1.6.tgz#19ca2d3d0b35373a74dc1c98692cdaffb6602656" integrity sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ== @@ -15459,6 +14836,13 @@ rimraf@^3.0.0, rimraf@^3.0.2, rimraf@~3.0.2: dependencies: glob "^7.1.3" +rimraf@^5.0.7: + version "5.0.9" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.9.tgz#c3baa1b886eadc2ec7981a06a593c3d01134ffe9" + integrity sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA== + dependencies: + glob "^10.3.7" + ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" @@ -15559,13 +14943,6 @@ safe-regex-test@^1.0.0: get-intrinsic "^1.1.3" is-regex "^1.1.4" -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - safefs@^6.12.0: version "6.16.0" resolved "https://registry.yarnpkg.com/safefs/-/safefs-6.16.0.tgz#e1901c0ea1e8926289bd8d5383a65ce21bb44d2f" @@ -15793,7 +15170,7 @@ set-immediate-shim@~1.0.1: resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= -set-value@^2.0.0, set-value@^2.0.1, set-value@^4.1.0: +set-value@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/set-value/-/set-value-4.1.0.tgz#aa433662d87081b75ad88a4743bd450f044e7d09" integrity sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw== @@ -15878,6 +15255,11 @@ signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== +signal-exit@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" + integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== + simple-git@^3.16.0: version "3.16.0" resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-3.16.0.tgz#421773e24680f5716999cc4a1d60127b4b6a9dec" @@ -15950,36 +15332,6 @@ slide@~1.1.3: resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" integrity sha1-VusCfWW00tzmyy4tMsTUr8nh1wc= -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - sonic-boom@^1.0.2: version "1.4.1" resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-1.4.1.tgz#d35d6a74076624f12e6f917ade7b9d75e918f53e" @@ -16005,7 +15357,7 @@ source-map-js@^1.0.2: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== -source-map-resolve@^0.5.0, source-map-resolve@^0.5.2: +source-map-resolve@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== @@ -16042,7 +15394,7 @@ source-map@0.5.6: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" integrity sha1-dc449SvwczxafwwRjYEzSiu19BI= -source-map@^0.5.1, source-map@^0.5.6: +source-map@^0.5.1: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= @@ -16187,13 +15539,6 @@ split-on-first@^1.0.0: resolved "https://registry.yarnpkg.com/split-on-first/-/split-on-first-1.1.0.tgz#f610afeee3b12bce1d0c30425e76398b78249a5f" integrity sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw== -split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - sprintf-js@1.1.2, sprintf-js@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.2.tgz#da1765262bf8c0f571749f2ad6c26300207ae673" @@ -16277,14 +15622,6 @@ state-toggle@^1.0.0: resolved "https://registry.yarnpkg.com/state-toggle/-/state-toggle-1.0.3.tgz#e123b16a88e143139b09c6852221bc9815917dfe" integrity sha512-d/5Z4/2iiCnHw6Xzghyhb+GcmF89bxwgXG60wjIiZaxnymbyOmI8Hk4VqHXiVVp6u2ysaskFfXg3ekCj4WNftQ== -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -16352,6 +15689,15 @@ string-similarity@^4.0.1: resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-4.0.4.tgz#42d01ab0b34660ea8a018da8f56a3309bb8b2a5b" integrity sha512-/q/8Q4Bl4ZKAPjj8WerIBJWALKkaPRfrvhfF8k/B23i4nzrlRj2/go1m90In7nG/3XDSbOo0+pu6RvCTM9RGMQ== +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" @@ -16387,6 +15733,15 @@ string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" +string-width@^5.0.1, string-width@^5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" + integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + string.prototype.matchall@^4.0.6: version "4.0.7" resolved "https://registry.yarnpkg.com/string.prototype.matchall/-/string.prototype.matchall-4.0.7.tgz#8e6ecb0d8a1fb1fda470d81acecb2dba057a481d" @@ -16401,16 +15756,7 @@ string.prototype.matchall@^4.0.6: regexp.prototype.flags "^1.4.1" side-channel "^1.0.4" -string.prototype.trim@^1.2.1, string.prototype.trim@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.5.tgz#a587bcc8bfad8cb9829a577f5de30dd170c1682c" - integrity sha512-Lnh17webJVsD6ECeovpVN17RlAKjmz4rF9S+8Y45CkMc/ufVpTkU3vZIyIC7sllQ1FCvObZnnCdNs/HXTUOTlg== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - es-abstract "^1.19.1" - -string.prototype.trim@^1.2.8: +string.prototype.trim@^1.2.1, string.prototype.trim@^1.2.5, string.prototype.trim@^1.2.8: version "1.2.8" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.8.tgz#f9ac6f8af4bd55ddfa8895e6aea92a96395393bd" integrity sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ== @@ -16419,23 +15765,6 @@ string.prototype.trim@^1.2.8: define-properties "^1.2.0" es-abstract "^1.22.1" -string.prototype.trimend@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" - integrity sha512-y9xCjw1P23Awk8EvTpcyL2NIr1j7wJ39f+k6lvRnSMz+mz9CGz9NYPelDk42kOz6+ql8xjfK8oYzy3jAP5QU5A== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.3" - -string.prototype.trimend@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.5.tgz#914a65baaab25fbdd4ee291ca7dde57e869cb8d0" - integrity sha512-I7RGvmjV4pJ7O3kdf+LXFpVfdNOxtCW/2C8f6jNiW4+PQchwxkCDzlk1/7p+Wl4bqFIZeF47qAHXLuHHWKAxog== - dependencies: - call-bind "^1.0.2" - define-properties "^1.1.4" - es-abstract "^1.19.5" - string.prototype.trimend@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.7.tgz#1bb3afc5008661d73e2dc015cd4853732d6c471e" @@ -16445,7 +15774,7 @@ string.prototype.trimend@^1.0.7: define-properties "^1.2.0" es-abstract "^1.22.1" -string.prototype.trimstart@^1.0.4, string.prototype.trimstart@^1.0.5, string.prototype.trimstart@^1.0.7: +string.prototype.trimstart@^1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.7.tgz#d4cdb44b83a4737ffbac2d406e405d43d0184298" integrity sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg== @@ -16482,6 +15811,13 @@ stringify-entities@^3.0.1: character-entities-legacy "^1.0.0" xtend "^4.0.0" +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@*: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -16524,6 +15860,13 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-ansi@^7.0.1: + version "7.1.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" + integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== + dependencies: + ansi-regex "^6.0.1" + strip-bom-string@1.X: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-bom-string/-/strip-bom-string-1.0.0.tgz#e5211e9224369fbb81d633a2f00044dc8cedad92" @@ -16565,6 +15908,14 @@ strong-log-transformer@^2.1.0: minimist "^1.2.0" through "^2.3.4" +strtok3@^6.2.4: + version "6.3.0" + resolved "https://registry.yarnpkg.com/strtok3/-/strtok3-6.3.0.tgz#358b80ffe6d5d5620e19a073aa78ce947a90f9a0" + integrity sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw== + dependencies: + "@tokenizer/token" "^0.3.0" + peek-readable "^4.1.0" + style-loader@^1.1.3: version "1.3.0" resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.3.0.tgz#828b4a3b3b7e7aa5847ce7bae9e874512114249e" @@ -17061,10 +16412,10 @@ tiny-warning@^1.0.0, tiny-warning@^1.0.3: resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== -tinycolor2@^1.0.0, tinycolor2@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.4.2.tgz#3f6a4d1071ad07676d7fa472e1fac40a719d8803" - integrity sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA== +tinycolor2@^1.0.0, tinycolor2@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/tinycolor2/-/tinycolor2-1.6.0.tgz#f98007460169b0263b97072c5ae92484ce02d09e" + integrity sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw== tinygradient@^1.1.5: version "1.1.5" @@ -17116,21 +16467,6 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -17138,16 +16474,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - to-source-code@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/to-source-code/-/to-source-code-1.0.2.tgz#dd136bdb1e1dbd80bbeacf088992678e9070bfea" @@ -17167,6 +16493,14 @@ toggle-selection@^1.0.6: resolved "https://registry.yarnpkg.com/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32" integrity sha1-bkWxJj8gF/oKzH2J14sVuL932jI= +token-types@^4.1.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/token-types/-/token-types-4.2.1.tgz#0f897f03665846982806e138977dbe72d44df753" + integrity sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ== + dependencies: + "@tokenizer/token" "^0.3.0" + ieee754 "^1.2.1" + topo@3.x.x: version "3.0.3" resolved "https://registry.yarnpkg.com/topo/-/topo-3.0.3.tgz#d5a67fb2e69307ebeeb08402ec2a2a6f5f7ad95c" @@ -17383,12 +16717,7 @@ type@^1.0.1: resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== -type@^2.5.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/type/-/type-2.6.0.tgz#3ca6099af5981d36ca86b78442973694278a219f" - integrity sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ== - -type@^2.7.2: +type@^2.5.0, type@^2.7.2: version "2.7.2" resolved "https://registry.yarnpkg.com/type/-/type-2.7.2.tgz#2376a15a3a28b1efa0f5350dcf72d24df6ef98d0" integrity sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw== @@ -17469,16 +16798,6 @@ uglify-js@^3.1.4: resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.15.4.tgz#fa95c257e88f85614915b906204b9623d4fa340d" integrity sha512-vMOPGDuvXecPs34V74qDKk4iJ/SN4vL3Ow/23ixafENYvtrNvtbcgUeugTcUGRGsOF/5fU8/NYSL5Hyb3l1OJA== -unbox-primitive@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" - integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== - dependencies: - function-bind "^1.1.1" - has-bigints "^1.0.1" - has-symbols "^1.0.2" - which-boxed-primitive "^1.0.2" - unbox-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e" @@ -17570,16 +16889,6 @@ unified@^9.2.0: trough "^1.0.0" vfile "^4.0.0" -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - unique-filename@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" @@ -17713,7 +17022,7 @@ unlazy-loader@^0.1.3: dependencies: requires-regex "^0.3.3" -unset-value@^1.0.0, unset-value@^2.0.1: +unset-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-2.0.1.tgz#57bed0c22d26f28d69acde5df9a11b77c74d2df3" integrity sha512-2hvrBfjUE00PkqN+q0XP6yRAOGrR06uSiUoIQGZkc7GxvQ9H7v8quUPNtZjMg4uux69i8HWpIjLPUKwCuRGyNg== @@ -17721,26 +17030,13 @@ unset-value@^1.0.0, unset-value@^2.0.1: has-value "^2.0.2" isobject "^4.0.0" -upath@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" - integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg== - -update-browserslist-db@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" - integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" + escalade "^3.1.2" + picocolors "^1.0.1" uri-js@^4.2.2: version "4.4.1" @@ -17805,17 +17101,12 @@ use-sidecar@^1.0.1, use-sidecar@^1.0.5: detect-node-es "^1.1.0" tslib "^1.9.3" -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -utif@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/utif/-/utif-2.0.1.tgz#9e1582d9bbd20011a6588548ed3266298e711759" - integrity sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg== +utif2@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/utif2/-/utif2-4.1.0.tgz#e768d37bd619b995d56d9780b5d2b4611a3d932b" + integrity sha512-+oknB9FHrJ7oW7A2WZYajOcv4FcDR4CfoGB0dPNfxbi4GO05RRnFmt5oa23+9w32EanrYcSJWspUiJkLMs+37w== dependencies: - pako "^1.0.5" + pako "^1.0.11" util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" @@ -18329,7 +17620,7 @@ vfile-location@^2.0.0: resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-2.0.6.tgz#8a274f39411b8719ea5728802e10d9e0dff1519e" integrity sha512-sSFdyCP3G6Ka0CEmN83A2YCMKIieHx0EDaj5IDP4g1pa5ZJ4FJDvpO0WODLxo4LUX4oe52gmSCK7Jw4SBghqxA== -vfile-location@^3.0.0, vfile-location@^3.2.0: +vfile-location@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/vfile-location/-/vfile-location-3.2.0.tgz#d8e41fbcbd406063669ebf6c33d56ae8721d0f3c" integrity sha512-aLEIZKv/oxuCDZ8lkJGhuhztf/BW4M+iHdCwglA/eWc+vtuRFJj8EtgceYFX4LRjOhCAAiNHsKGssC6onJ+jbA== @@ -18554,10 +17845,10 @@ webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack- source-list-map "^2.0.0" source-map "~0.6.1" -"webpack@npm:@amoo-miki/webpack@4.46.0-rc.2": - version "4.46.0-rc.2" - resolved "https://registry.yarnpkg.com/@amoo-miki/webpack/-/webpack-4.46.0-rc.2.tgz#36824597c14557a7bb0a8e13203e30275e7b02bd" - integrity sha512-Y/ZqxTHOoDF1kz3SR63Y9SZGTDUpZNNFrisTRHofWhP8QvNX3LMN+TCmEP56UfLaiLVKMcaiFjx8kFb2TgyBaQ== +"webpack@npm:@amoo-miki/webpack@4.46.0-xxhash.1": + version "4.46.0-xxhash.1" + resolved "https://registry.yarnpkg.com/@amoo-miki/webpack/-/webpack-4.46.0-xxhash.1.tgz#e8fdb0399d36715e558ad35e3ff70fd1d4ea47a7" + integrity sha512-gPwQMqfrE9326g+rZoU4BPOfcYvEcAR0XxfpV9AGUcRwl2oHoqEwq7nxSWchilpGV1i9XI7mCa8u8k4ePz6ziA== dependencies: "@node-rs/xxhash" "^1.3.0" "@webassemblyjs/ast" "1.9.0" @@ -18570,11 +17861,11 @@ webpack-sources@^1.1.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack- chrome-trace-event "^1.0.2" enhanced-resolve "^4.5.0" eslint-scope "^4.0.3" + glob-to-regexp "^0.4.1" json-parse-better-errors "^1.0.2" loader-runner "^2.4.0" loader-utils "^2.0.4" memory-fs "^0.4.1" - micromatch "^3.1.10" mkdirp "^0.5.3" neo-async "^2.6.1" node-libs-browser "^2.2.1" @@ -18591,10 +17882,10 @@ whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-fetch@^3.0.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" - integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA== +whatwg-fetch@^3.0.0, whatwg-fetch@^3.4.1: + version "3.6.20" + resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" + integrity sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg== whatwg-mimetype@^2.3.0: version "2.3.0" @@ -18662,7 +17953,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which-typed-array@^1.1.11, which-typed-array@^1.1.13: +which-typed-array@^1.1.11, which-typed-array@^1.1.13, which-typed-array@^1.1.2: version "1.1.13" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.13.tgz#870cd5be06ddb616f504e7b039c4c24898184d36" integrity sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow== @@ -18673,18 +17964,6 @@ which-typed-array@^1.1.11, which-typed-array@^1.1.13: gopd "^1.0.1" has-tostringtag "^1.0.0" -which-typed-array@^1.1.2: - version "1.1.7" - resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.7.tgz#2761799b9a22d4b8660b3c1b40abaa7739691793" - integrity sha512-vjxaB4nfDqwKI0ws7wZpxIlde1XrLX5uB0ZjpfshgmapJMD7jJWhZI+yToJTqaFByF0eNBcYxbjmCzoRP7CfEw== - dependencies: - available-typed-arrays "^1.0.5" - call-bind "^1.0.2" - es-abstract "^1.18.5" - foreach "^2.0.5" - has-tostringtag "^1.0.0" - is-typed-array "^1.1.7" - 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" @@ -18726,6 +18005,15 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" @@ -18752,6 +18040,15 @@ wrap-ansi@^7.0.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" + integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== + dependencies: + ansi-styles "^6.1.0" + string-width "^5.0.1" + strip-ansi "^7.0.1" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -18813,14 +18110,14 @@ write@1.0.3: mkdirp "^0.5.1" ws@^7.4.6: - version "7.5.7" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67" - integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A== + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== ws@^8.0.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" - integrity sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg== + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== x-is-string@^0.1.0: version "0.1.0" @@ -19038,8 +18335,3 @@ zlib@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/zlib/-/zlib-1.0.5.tgz#6e7c972fc371c645a6afb03ab14769def114fcc0" integrity sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA= - -zwitch@^1.0.0: - version "1.0.5" - resolved "https://registry.yarnpkg.com/zwitch/-/zwitch-1.0.5.tgz#d11d7381ffed16b742f6af7b3f223d5cd9fe9920" - integrity sha512-V50KMwwzqJV0NpZIZFwfOD5/lyny3WlSzRiXgA0G7VUnRlqttta1L6UQIHzd6EuBY/cHGfwTIck7w1yH6Q5zUw==