diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 23b4fe42c58da..dc8ce1f3aef17 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -19,4 +19,4 @@ /helm/superset/ @craig-rueda @dpgaspar @villebro # Notify E2E test maintainers of changes -/superset-frontend/cypress-base/ @jinghua-qa +/superset-frontend/cypress-base/ @jinghua-qa @geido diff --git a/.github/workflows/superset-python-unittest.yml b/.github/workflows/superset-python-unittest.yml index 64db4d3e649af..1ff07375d4727 100644 --- a/.github/workflows/superset-python-unittest.yml +++ b/.github/workflows/superset-python-unittest.yml @@ -37,7 +37,7 @@ jobs: python-version: ${{ matrix.python-version }} cache: 'pip' cache-dependency-path: 'requirements/testing.txt' -# TODO: separated requiermentes.txt file just for unit tests +# TODO: separated requirements.txt file just for unit tests - name: Install dependencies if: steps.check.outcome == 'failure' uses: ./.github/actions/cached-dependencies diff --git a/RELEASING/README.md b/RELEASING/README.md index 04e38b85113cb..901d21aefb3d7 100644 --- a/RELEASING/README.md +++ b/RELEASING/README.md @@ -290,10 +290,6 @@ git tag ${SUPERSET_VERSION_RC} git push origin ${SUPERSET_VERSION_RC} ``` -### Create a release on Github - -After submitting the tag, follow the steps [here](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository) to create the release. Use the vote email text as the content for the release description. Make sure to check the "This is a pre-release" checkbox for release canditates. You can check previous releases if you need an example. - ## Preparing the release candidate The first step of preparing an Apache Release is packaging a release candidate @@ -347,7 +343,11 @@ To build and run the recently created tarball **from SVN**: # login using admin/admin ``` -### Voting +## Create a release on Github + +After submitting the tag and testing the release candidate, follow the steps [here](https://docs.github.com/en/repositories/releasing-projects-on-github/managing-releases-in-a-repository) to create the release on GitHub. Use the vote email text as the content for the release description. Make sure to check the "This is a pre-release" checkbox for release candidates. You can check previous releases if you need an example. + +## Voting Now you're ready to start the [VOTE] thread. Here's an example of a previous release vote thread: @@ -385,7 +385,7 @@ A List of people with -1 vote (ex: John): The script will generate the email text that should be sent to dev@superset.apache.org using an email client. The release version and release candidate number are fetched from the previously set environment variables. -### Validating a release +## Validating a release https://www.apache.org/info/verification.html diff --git a/UPDATING.md b/UPDATING.md index d53f7cae94792..a422e425773fb 100644 --- a/UPDATING.md +++ b/UPDATING.md @@ -27,6 +27,8 @@ assists people when migrating to a new version. - [20606](https://github.com/apache/superset/pull/20606): When user clicks on chart title or "Edit chart" button in Dashboard page, Explore opens in the same tab. Clicking while holding cmd/ctrl opens Explore in a new tab. To bring back the old behaviour (always opening Explore in a new tab), flip feature flag `DASHBOARD_EDIT_CHART_IN_NEW_TAB` to `True`. - [20799](https://github.com/apache/superset/pull/20799): Presto and Trino engine will now display tracking URL for running queries in SQL Lab. If for some reason you don't want to show the tracking URL (for example, when your data warehouse hasn't enable access for to Presto or Trino UI), update `TRACKING_URL_TRANSFORMER` in `config.py` to return `None`. - [21002](https://github.com/apache/superset/pull/21002): Support Python 3.10 and bump pandas 1.4 and pyarrow 6. +- [21163](https://github.com/apache/superset/pull/21163): When `GENERIC_CHART_AXES` feature flags set to `True`, the Time Grain control will move below the X-Axis control. +- [21284](https://github.com/apache/superset/pull/21284): The non-functional `MAX_TABLE_NAMES` config key has been removed. ### Breaking Changes diff --git a/docker/pythonpath_dev/superset_config.py b/docker/pythonpath_dev/superset_config.py index 794239d23f13b..84c1dc58ab502 100644 --- a/docker/pythonpath_dev/superset_config.py +++ b/docker/pythonpath_dev/superset_config.py @@ -69,6 +69,16 @@ def get_env_variable(var_name: str, default: Optional[str] = None) -> str: RESULTS_BACKEND = FileSystemCache("/app/superset_home/sqllab") +CACHE_CONFIG = { + "CACHE_TYPE": "redis", + "CACHE_DEFAULT_TIMEOUT": 300, + "CACHE_KEY_PREFIX": "superset_", + "CACHE_REDIS_HOST": REDIS_HOST, + "CACHE_REDIS_PORT": REDIS_PORT, + "CACHE_REDIS_DB": REDIS_RESULTS_DB, +} +DATA_CACHE_CONFIG = CACHE_CONFIG + class CeleryConfig(object): BROKER_URL = f"redis://{REDIS_HOST}:{REDIS_PORT}/{REDIS_CELERY_DB}" diff --git a/docs/docs/databases/kusto.mdx b/docs/docs/databases/kusto.mdx new file mode 100644 index 0000000000000..3efe4ac8f5bec --- /dev/null +++ b/docs/docs/databases/kusto.mdx @@ -0,0 +1,26 @@ +--- +name: Kusto +hide_title: true +sidebar_position: 41 +version: 2 +--- + +## Kusto + +The recommended connector library for Kusto is +[sqlalchemy-kusto](https://pypi.org/project/sqlalchemy-kusto/2.0.0/)>=2.0.0. + +The connection string for Kusto (sql dialect) looks like this: + +``` +kustosql+https://{cluster_url}/{database}?azure_ad_client_id={azure_ad_client_id}&azure_ad_client_secret={azure_ad_client_secret}&azure_ad_tenant_id={azure_ad_tenant_id}&msi=False +``` + +The connection string for Kusto (kql dialect) looks like this: + +``` +kustokql+https://{cluster_url}/{database}?azure_ad_client_id={azure_ad_client_id}&azure_ad_client_secret={azure_ad_client_secret}&azure_ad_tenant_id={azure_ad_tenant_id}&msi=False +``` + +Make sure the user has privileges to access and use all required +databases/tables/views. diff --git a/docs/docs/databases/snowflake.mdx b/docs/docs/databases/snowflake.mdx index f0fc1a4a58e59..c6a51cbcc0688 100644 --- a/docs/docs/databases/snowflake.mdx +++ b/docs/docs/databases/snowflake.mdx @@ -29,3 +29,31 @@ user/role rights during engine creation by default. However, when pressing the button in the Create or Edit Database dialog, user/role credentials are validated by passing “validate_default_parameters”: True to the connect() method during engine creation. If the user/role is not authorized to access the database, an error is recorded in the Superset logs. + +And if you want connect Snowflake with [Key Pair Authentication](https://docs.snowflake.com/en/user-guide/key-pair-auth.html#step-6-configure-the-snowflake-client-to-use-key-pair-authentication). +Plase make sure you have the key pair and the public key is registered in Snowflake. +To connect Snowflake with Key Pair Authentication, you need to add the following parameters to "SECURE EXTRA" field. + +***Please note that you need to merge multi-line private key content to one line and insert `\n` between each line*** + +``` +{ + "auth_method": "keypair", + "auth_params": { + "privatekey_body": "-----BEGIN ENCRYPTED PRIVATE KEY-----\n...\n...\n-----END ENCRYPTED PRIVATE KEY-----", + "privatekey_pass":"Your Private Key Password" + } + } +``` + +If your private key is stored on server, you can replace "privatekey_body" with “privatekey_path” in parameter. + +``` +{ + "auth_method": "keypair", + "auth_params": { + "privatekey_path":"Your Private Key Path", + "privatekey_pass":"Your Private Key Password" + } +} +``` diff --git a/docs/docs/installation/alerts-reports.mdx b/docs/docs/installation/alerts-reports.mdx index d382b95f8ef81..d8f04817e8724 100644 --- a/docs/docs/installation/alerts-reports.mdx +++ b/docs/docs/installation/alerts-reports.mdx @@ -126,6 +126,7 @@ SLACK_API_TOKEN = "xoxb-" # Email configuration SMTP_HOST = "smtp.sendgrid.net" #change to your host SMTP_STARTTLS = True +SMTP_SSL_SERVER_AUTH = True # If your using an SMTP server with a valid certificate SMTP_SSL = False SMTP_USER = "your_user" SMTP_PORT = 2525 # your port eg. 587 diff --git a/docs/static/resources/openapi.json b/docs/static/resources/openapi.json index 9082e94dc5aa2..29d9d7e8a9543 100644 --- a/docs/static/resources/openapi.json +++ b/docs/static/resources/openapi.json @@ -3532,9 +3532,6 @@ }, "Database": { "properties": { - "allow_multi_schema_metadata_fetch": { - "type": "boolean" - }, "allows_cost_estimate": { "type": "boolean" }, @@ -3679,10 +3676,6 @@ "nullable": true, "type": "boolean" }, - "allow_multi_schema_metadata_fetch": { - "nullable": true, - "type": "boolean" - }, "allow_run_async": { "nullable": true, "type": "boolean" @@ -3771,10 +3764,6 @@ "nullable": true, "type": "boolean" }, - "allow_multi_schema_metadata_fetch": { - "nullable": true, - "type": "boolean" - }, "allow_run_async": { "nullable": true, "type": "boolean" @@ -3870,10 +3859,6 @@ "description": "Allow to upload CSV file data into this databaseIf selected, please set the schemas allowed for csv upload in Extra.", "type": "boolean" }, - "allow_multi_schema_metadata_fetch": { - "description": "Allow SQL Lab to fetch a list of all tables and all views across all database schemas. For large data warehouse with thousands of tables, this can be expensive and put strain on the system.", - "type": "boolean" - }, "allow_run_async": { "description": "Operate the database in asynchronous mode, meaning that the queries are executed on remote workers as opposed to on the web server itself. This assumes that you have a Celery worker setup as well as a results backend. Refer to the installation docs for more information.", "type": "boolean" @@ -3971,10 +3956,6 @@ "description": "Allow to upload CSV file data into this databaseIf selected, please set the schemas allowed for csv upload in Extra.", "type": "boolean" }, - "allow_multi_schema_metadata_fetch": { - "description": "Allow SQL Lab to fetch a list of all tables and all views across all database schemas. For large data warehouse with thousands of tables, this can be expensive and put strain on the system.", - "type": "boolean" - }, "allow_run_async": { "description": "Operate the database in asynchronous mode, meaning that the queries are executed on remote workers as opposed to on the web server itself. This assumes that you have a Celery worker setup as well as a results backend. Refer to the installation docs for more information.", "type": "boolean" diff --git a/helm/superset/Chart.yaml b/helm/superset/Chart.yaml index 9b53c89378396..7e8c6c07d72a3 100644 --- a/helm/superset/Chart.yaml +++ b/helm/superset/Chart.yaml @@ -22,7 +22,7 @@ maintainers: - name: craig-rueda email: craig@craigrueda.com url: https://github.com/craig-rueda -version: 0.7.1 +version: 0.7.2 dependencies: - name: postgresql version: 11.1.22 diff --git a/helm/superset/values.yaml b/helm/superset/values.yaml index 86029c8951a92..992a950b64450 100644 --- a/helm/superset/values.yaml +++ b/helm/superset/values.yaml @@ -53,9 +53,21 @@ envFromSecrets: [] ## Extra environment variables that will be passed into pods ## extraEnv: {} + # Different gunicorn settings, refer to the gunicorn documentation + # https://docs.gunicorn.org/en/stable/settings.html# + # These variables are used as Flags at the gunicorn startup + # https://github.com/apache/superset/blob/master/docker/run-server.sh#L22 # Extend timeout to allow long running queries. # GUNICORN_TIMEOUT: 300 - + # Increase the gunicorn worker amount, can improve performance drastically + # See: https://docs.gunicorn.org/en/stable/design.html#how-many-workers + # SERVER_WORKER_AMOUNT: 4 + # WORKER_MAX_REQUESTS: 0 + # WORKER_MAX_REQUESTS_JITTER: 0 + # SERVER_THREADS_AMOUNT: 20 + # GUNICORN_KEEPALIVE: 2 + # SERVER_LIMIT_REQUEST_LINE: 0 + # SERVER_LIMIT_REQUEST_FIELD_SIZE: 0 # OAUTH_HOME_DOMAIN: .. # # If a whitelist is not set, any address that can use your OAuth2 endpoint will be able to login. diff --git a/requirements/base.txt b/requirements/base.txt index ead18fba5b561..cf257c4bdbdfa 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -86,7 +86,7 @@ flask==2.0.3 # flask-migrate # flask-sqlalchemy # flask-wtf -flask-appbuilder==4.1.3 +flask-appbuilder==4.1.4 # via apache-superset flask-babel==1.0.0 # via flask-appbuilder @@ -185,7 +185,7 @@ packaging==21.3 # via # bleach # deprecation -pandas==1.4.3 +pandas==1.4.4 # via apache-superset parsedatetime==2.6 # via apache-superset @@ -269,7 +269,7 @@ sqlalchemy==1.4.36 # flask-sqlalchemy # marshmallow-sqlalchemy # sqlalchemy-utils -sqlalchemy-utils==0.37.8 +sqlalchemy-utils==0.38.3 # via # apache-superset # flask-appbuilder diff --git a/requirements/development.txt b/requirements/development.txt index ea8435c9397da..5fc11b914cbfe 100644 --- a/requirements/development.txt +++ b/requirements/development.txt @@ -103,7 +103,7 @@ tableschema==1.20.2 # via apache-superset tabulator==1.53.5 # via tableschema -thrift==0.13.0 +thrift==0.14.1 # via # apache-superset # pyhive diff --git a/scripts/python_tests.sh b/scripts/python_tests.sh index 0554f8ca65ad9..6491a3f6f9d46 100755 --- a/scripts/python_tests.sh +++ b/scripts/python_tests.sh @@ -32,4 +32,4 @@ superset init echo "Running tests" -pytest --durations-min=2 --maxfail=1 --cov-report= --cov=superset "$@" +pytest --durations-min=2 --maxfail=1 --cov-report= --cov=superset ./tests/integration_tests "$@" diff --git a/setup.py b/setup.py index 411a184db0189..e62f8cdfa78af 100644 --- a/setup.py +++ b/setup.py @@ -83,7 +83,7 @@ def get_git_sha() -> str: "cryptography>=3.3.2", "deprecation>=2.1.0, <2.2.0", "flask>=2.0.0, <3.0.0", - "flask-appbuilder>=4.1.3, <5.0.0", + "flask-appbuilder>=4.1.4, <5.0.0", "flask-caching>=1.10.0", "flask-compress", "flask-talisman", @@ -100,7 +100,7 @@ def get_git_sha() -> str: "markdown>=3.0", "msgpack>=1.0.0, <1.1", "numpy==1.22.1", - "pandas>=1.4.3, <1.5", + "pandas>=1.4.4, <1.5", "parsedatetime", "pgsanity", "polyline", @@ -116,7 +116,7 @@ def get_git_sha() -> str: "simplejson>=3.15.0", "slackclient==2.5.0", # PINNED! slack changes file upload api in the future versions "sqlalchemy>=1.4, <2", - "sqlalchemy-utils>=0.37.8, <0.38", + "sqlalchemy-utils>=0.38.3, <0.39", "sqlparse==0.3.0", # PINNED! see https://github.com/andialbrecht/sqlparse/issues/562 "tabulate==0.8.9", # needed to support Literal (3.8) and TypeGuard (3.10) @@ -144,16 +144,16 @@ def get_git_sha() -> str: "drill": ["sqlalchemy-drill==0.1.dev"], "druid": ["pydruid>=0.6.1,<0.7"], "solr": ["sqlalchemy-solr >= 0.2.0"], - "elasticsearch": ["elasticsearch-dbapi>=0.2.0, <0.3.0"], + "elasticsearch": ["elasticsearch-dbapi>=0.2.9, <0.3.0"], "exasol": ["sqlalchemy-exasol >= 2.4.0, <3.0"], "excel": ["xlrd>=1.2.0, <1.3"], "firebird": ["sqlalchemy-firebird>=0.7.0, <0.8"], "firebolt": ["firebolt-sqlalchemy>=0.0.1"], "gsheets": ["shillelagh[gsheetsapi]>=1.0.14, <2"], "hana": ["hdbcli==2.4.162", "sqlalchemy_hana==0.4.0"], - "hive": ["pyhive[hive]>=0.6.5", "tableschema", "thrift>=0.11.0, <1.0.0"], + "hive": ["pyhive[hive]>=0.6.5", "tableschema", "thrift>=0.14.1, <1.0.0"], "impala": ["impyla>0.16.2, <0.17"], - "kusto": ["sqlalchemy-kusto>=1.0.1, <2"], + "kusto": ["sqlalchemy-kusto>=2.0.0, <3"], "kylin": ["kylinpy>=2.8.1, <2.9"], "mssql": ["pymssql>=2.1.4, <2.2"], "mysql": ["mysqlclient>=2.1.0, <3"], @@ -169,7 +169,7 @@ def get_git_sha() -> str: "shillelagh[datasetteapi,gsheetsapi,socrata,weatherapi]>=1.0.3, <2" ], "snowflake": ["snowflake-sqlalchemy>=1.2.4, <2"], - "spark": ["pyhive[hive]>=0.6.5", "tableschema", "thrift>=0.11.0, <1.0.0"], + "spark": ["pyhive[hive]>=0.6.5", "tableschema", "thrift>=0.14.1, <1.0.0"], "teradata": ["teradatasql>=16.20.0.23"], "thumbnails": ["Pillow>=9.1.1, <10.0.0"], "vertica": ["sqlalchemy-vertica-python>=0.5.9, < 0.6"], diff --git a/superset-embedded-sdk/README.md b/superset-embedded-sdk/README.md index 93b0aa4c09e63..7e05d94a6ce1d 100644 --- a/superset-embedded-sdk/README.md +++ b/superset-embedded-sdk/README.md @@ -40,7 +40,12 @@ embedDashboard({ supersetDomain: "https://superset.example.com", mountPoint: document.getElementById("my-superset-container"), // any html element that can contain an iframe fetchGuestToken: () => fetchGuestTokenFromBackend(), - dashboardUiConfig: { hideTitle: true }, // dashboard UI config: hideTitle, hideTab, hideChartControls (optional) + dashboardUiConfig: { // dashboard UI config: hideTitle, hideTab, hideChartControls, filters.visible, filters.expanded (optional) + hideTitle: true, + filters: { + expanded: true, + } + }, }); ``` diff --git a/superset-embedded-sdk/src/const.ts b/superset-embedded-sdk/src/const.ts index e887974520359..72eba8525d758 100644 --- a/superset-embedded-sdk/src/const.ts +++ b/superset-embedded-sdk/src/const.ts @@ -18,3 +18,7 @@ */ export const IFRAME_COMMS_MESSAGE_TYPE = "__embedded_comms__"; +export const DASHBOARD_UI_FILTER_CONFIG_URL_PARAM_KEY: { [index: string]: any } = { + visible: "show_filters", + expanded: "expand_filters", +} diff --git a/superset-embedded-sdk/src/index.ts b/superset-embedded-sdk/src/index.ts index 317195522c88d..200acbb587ccc 100644 --- a/superset-embedded-sdk/src/index.ts +++ b/superset-embedded-sdk/src/index.ts @@ -17,7 +17,10 @@ * under the License. */ -import { IFRAME_COMMS_MESSAGE_TYPE } from './const'; +import { + DASHBOARD_UI_FILTER_CONFIG_URL_PARAM_KEY, + IFRAME_COMMS_MESSAGE_TYPE +} from './const'; // We can swap this out for the actual switchboard package once it gets published import { Switchboard } from '@superset-ui/switchboard'; @@ -34,6 +37,11 @@ export type UiConfigType = { hideTitle?: boolean hideTab?: boolean hideChartControls?: boolean + filters?: { + [key: string]: boolean | undefined + visible?: boolean + expanded?: boolean + } } export type EmbedDashboardParams = { @@ -45,7 +53,7 @@ export type EmbedDashboardParams = { mountPoint: HTMLElement /** A function to fetch a guest token from the Host App's backend server */ fetchGuestToken: GuestTokenFetchFn - /** The dashboard UI config: hideTitle, hideTab, hideChartControls **/ + /** The dashboard UI config: hideTitle, hideTab, hideChartControls, filters.visible, filters.expanded **/ dashboardUiConfig?: UiConfigType /** Are we in debug mode? */ debug?: boolean @@ -99,6 +107,13 @@ export async function embedDashboard({ return new Promise(resolve => { const iframe = document.createElement('iframe'); const dashboardConfig = dashboardUiConfig ? `?uiConfig=${calculateConfig()}` : "" + const filterConfig = dashboardUiConfig?.filters || {} + const filterConfigKeys = Object.keys(filterConfig) + const filterConfigUrlParams = filterConfigKeys.length > 0 + ? "&" + + filterConfigKeys + .map(key => DASHBOARD_UI_FILTER_CONFIG_URL_PARAM_KEY[key] + '=' + filterConfig[key]).join('&') + : "" // setup the iframe's sandbox configuration iframe.sandbox.add("allow-same-origin"); // needed for postMessage to work @@ -132,7 +147,7 @@ export async function embedDashboard({ resolve(new Switchboard({ port: ourPort, name: 'superset-embedded-sdk', debug })); }); - iframe.src = `${supersetDomain}/embedded/${id}${dashboardConfig}`; + iframe.src = `${supersetDomain}/embedded/${id}${dashboardConfig}${filterConfigUrlParams}`; mountPoint.replaceChildren(iframe); log('placed the iframe') }); diff --git a/superset-frontend/cypress-base/applitools.config.js b/superset-frontend/cypress-base/applitools.config.js index fc963a1c81fa7..507bcea035782 100644 --- a/superset-frontend/cypress-base/applitools.config.js +++ b/superset-frontend/cypress-base/applitools.config.js @@ -20,9 +20,10 @@ module.exports = { apiKey: process.env.APPLITOOLS_API_KEY, batchId: process.env.APPLITOOLS_BATCH_ID, batchName: process.env.APPLITOOLS_BATCH_NAME, - browser: [{ width: 1000, height: 660, name: 'chrome' }], + browser: [{ width: 1920, height: 1080, name: 'chrome' }], failCypressOnDiff: false, isDisabled: false, showLogs: false, testConcurrency: 10, + ignoreCaret: true, }; diff --git a/superset-frontend/cypress-base/cypress/downloads/new-chart-2022-09-09T14-22-31.728Z.jpg b/superset-frontend/cypress-base/cypress/downloads/new-chart-2022-09-09T14-22-31.728Z.jpg new file mode 100644 index 0000000000000..81bcb35cff225 Binary files /dev/null and b/superset-frontend/cypress-base/cypress/downloads/new-chart-2022-09-09T14-22-31.728Z.jpg differ diff --git a/superset-frontend/cypress-base/cypress/fixtures/charts.json b/superset-frontend/cypress-base/cypress/fixtures/charts.json new file mode 100644 index 0000000000000..6b342ee9a55ea --- /dev/null +++ b/superset-frontend/cypress-base/cypress/fixtures/charts.json @@ -0,0 +1,38 @@ +[ + { + "slice_name": "1 - Sample chart", + "description": "chart description", + "owners": [1], + "viz_type": "line", + "cache_timeout": 1000, + "datasource_id": 1, + "datasource_type": "table" + }, + { + "slice_name": "2 - Sample chart", + "description": "chart description", + "owners": [1], + "viz_type": "line", + "cache_timeout": 1000, + "datasource_id": 1, + "datasource_type": "table" + }, + { + "slice_name": "3 - Sample chart", + "description": "chart description", + "owners": [1], + "viz_type": "line", + "cache_timeout": 1000, + "datasource_id": 1, + "datasource_type": "table" + }, + { + "slice_name": "4 - Sample chart", + "description": "chart description", + "owners": [1], + "viz_type": "line", + "cache_timeout": 1000, + "datasource_id": 1, + "datasource_type": "table" + } +] diff --git a/superset-frontend/cypress-base/cypress/fixtures/dashboards.json b/superset-frontend/cypress-base/cypress/fixtures/dashboards.json new file mode 100644 index 0000000000000..083d7647b9623 --- /dev/null +++ b/superset-frontend/cypress-base/cypress/fixtures/dashboards.json @@ -0,0 +1,18 @@ +[ + { + "dashboard_title": "1 - Sample dashboard", + "slug": "1-sample-dashboard" + }, + { + "dashboard_title": "2 - Sample dashboard", + "slug": "2-sample-dashboard" + }, + { + "dashboard_title": "3 - Sample dashboard", + "slug": "3-sample-dashboard" + }, + { + "dashboard_title": "4 - Sample dashboard", + "slug": "4-sample-dashboard" + } +] diff --git a/superset-frontend/cypress-base/cypress/fixtures/example.json b/superset-frontend/cypress-base/cypress/fixtures/example.json deleted file mode 100644 index 02e4254378e97..0000000000000 --- a/superset-frontend/cypress-base/cypress/fixtures/example.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "name": "Using fixtures to represent data", - "email": "hello@cypress.io", - "body": "Fixtures are a great way to mock data for responses to routes" -} diff --git a/superset-frontend/cypress-base/cypress/integration/alerts_and_reports/alerts.test.ts b/superset-frontend/cypress-base/cypress/integration/alerts_and_reports/alerts.test.ts index 97bf2cc9be854..03af055a27ace 100644 --- a/superset-frontend/cypress-base/cypress/integration/alerts_and_reports/alerts.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/alerts_and_reports/alerts.test.ts @@ -16,31 +16,28 @@ * specific language governing permissions and limitations * under the License. */ -import { ALERT_LIST } from './alert_report.helper'; +import { ALERT_LIST } from 'cypress/utils/urls'; -describe('alert list view', () => { - beforeEach(() => { - cy.login(); +describe('Alert list view', () => { + before(() => { + cy.visit(ALERT_LIST); }); - afterEach(() => { - cy.eyesClose(); + beforeEach(() => { + cy.preserveLogin(); }); it('should load alert lists', () => { - cy.visit(ALERT_LIST); - - cy.get('[data-test="listview-table"]').should('be.visible'); - // check alert list view header - cy.get('[data-test="sort-header"]').eq(1).contains('Last run'); - cy.get('[data-test="sort-header"]').eq(2).contains('Name'); - cy.get('[data-test="sort-header"]').eq(3).contains('Schedule'); - cy.get('[data-test="sort-header"]').eq(4).contains('Notification method'); - cy.get('[data-test="sort-header"]').eq(5).contains('Created by'); - cy.get('[data-test="sort-header"]').eq(6).contains('Owners'); - cy.get('[data-test="sort-header"]').eq(7).contains('Modified'); - // TODO: this assert is flaky, we need to find a way to make it work consistenly - // cy.get('[data-test="sort-header"]').eq(7).contains('Active'); - // cy.get('[data-test="sort-header"]').eq(8).contains('Actions'); + cy.getBySel('listview-table').should('be.visible'); + cy.getBySel('sort-header').eq(1).contains('Last run'); + cy.getBySel('sort-header').eq(2).contains('Name'); + cy.getBySel('sort-header').eq(3).contains('Schedule'); + cy.getBySel('sort-header').eq(4).contains('Notification method'); + cy.getBySel('sort-header').eq(5).contains('Created by'); + cy.getBySel('sort-header').eq(6).contains('Owners'); + cy.getBySel('sort-header').eq(7).contains('Modified'); + cy.getBySel('sort-header').eq(8).contains('Active'); + // TODO Cypress won't recognize the Actions column + // cy.getBySel('sort-header').eq(9).contains('Actions'); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/alerts_and_reports/reports.test.ts b/superset-frontend/cypress-base/cypress/integration/alerts_and_reports/reports.test.ts index ea117c507a6d6..12fe43a165a4d 100644 --- a/superset-frontend/cypress-base/cypress/integration/alerts_and_reports/reports.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/alerts_and_reports/reports.test.ts @@ -16,31 +16,28 @@ * specific language governing permissions and limitations * under the License. */ -import { REPORT_LIST } from './alert_report.helper'; +import { REPORT_LIST } from 'cypress/utils/urls'; -describe('report list view', () => { - beforeEach(() => { - cy.login(); +describe('Report list view', () => { + before(() => { + cy.visit(REPORT_LIST); }); - afterEach(() => { - cy.eyesClose(); + beforeEach(() => { + cy.preserveLogin(); }); it('should load report lists', () => { - cy.visit(REPORT_LIST); - - cy.get('[data-test="listview-table"]').should('be.visible'); - // check report list view header - cy.get('[data-test="sort-header"]').eq(1).contains('Last run'); - cy.get('[data-test="sort-header"]').eq(2).contains('Name'); - cy.get('[data-test="sort-header"]').eq(3).contains('Schedule'); - cy.get('[data-test="sort-header"]').eq(4).contains('Notification method'); - cy.get('[data-test="sort-header"]').eq(5).contains('Created by'); - cy.get('[data-test="sort-header"]').eq(6).contains('Owners'); - cy.get('[data-test="sort-header"]').eq(7).contains('Modified'); - // TODO: this assert is flaky, we need to find a way to make it work consistenly - // cy.get('[data-test="sort-header"]').eq(7).contains('Active'); - // cy.get('[data-test="sort-header"]').eq(8).contains('Actions'); + cy.getBySel('listview-table').should('be.visible'); + cy.getBySel('sort-header').eq(1).contains('Last run'); + cy.getBySel('sort-header').eq(2).contains('Name'); + cy.getBySel('sort-header').eq(3).contains('Schedule'); + cy.getBySel('sort-header').eq(4).contains('Notification method'); + cy.getBySel('sort-header').eq(5).contains('Created by'); + cy.getBySel('sort-header').eq(6).contains('Owners'); + cy.getBySel('sort-header').eq(7).contains('Modified'); + cy.getBySel('sort-header').eq(8).contains('Active'); + // TODO Cypress won't recognize the Actions column + // cy.getBySel('sort-header').eq(9).contains('Actions'); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/card_view.test.ts b/superset-frontend/cypress-base/cypress/integration/chart_list/card_view.test.ts deleted file mode 100644 index 1335fcb422204..0000000000000 --- a/superset-frontend/cypress-base/cypress/integration/chart_list/card_view.test.ts +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF 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 { CHART_LIST } from './chart_list.helper'; - -describe('chart card view', () => { - beforeEach(() => { - cy.login(); - cy.visit(CHART_LIST); - cy.get('[aria-label="card-view"]').click(); - }); - - it('should load cards', () => { - cy.get('[data-test="chart-list-view"]'); - cy.get('[data-test="styled-card"]').should('be.visible'); - cy.get('[data-test="styled-card"]').should('have.length', 25); - }); - - it('should allow to favorite/unfavorite chart card', () => { - cy.get("[data-test='card-actions']") - .first() - .find("[aria-label='favorite-selected']") - .should('not.exist'); - cy.get("[data-test='card-actions']") - .find("[aria-label='favorite-unselected']") - .first() - .click(); - cy.get("[data-test='card-actions']") - .first() - .find("[aria-label='favorite-selected']") - .should('be.visible'); - cy.get("[data-test='card-actions']") - .first() - .find("[aria-label='favorite-unselected']") - .should('not.exist'); - - cy.get("[data-test='card-actions']") - .first() - .find("[aria-label='favorite-unselected']") - .should('not.exist'); - cy.get("[data-test='card-actions']") - .first() - .find("[aria-label='favorite-selected']") - .click(); - cy.get("[data-test='card-actions']") - .first() - .find("[aria-label='favorite-unselected']") - .should('be.visible'); - cy.get("[data-test='card-actions']") - .first() - .find("[aria-label='favorite-selected']") - .should('not.exist'); - }); - - xit('should sort correctly', () => { - // sort Alphabetical - cy.get('.Select__control').last().should('be.visible'); - cy.get('.Select__control').last().click(); - cy.get('.Select__menu').contains('Alphabetical').click(); - cy.get('[data-test="chart-list-view"]').should('be.visible'); - cy.get('[data-test="styled-card"]').first().contains('% Rural'); - - // sort Recently Modified - cy.get('.Select__control').last().should('be.visible'); - cy.get('.Select__control').last().click(); - cy.get('.Select__menu').contains('Recently Modified').click(); - cy.get('[data-test="chart-list-view"]').should('be.visible'); - // TODO - next line is/was flaky - cy.get('[data-test="styled-card"]').first().contains('Unicode Cloud'); - cy.get('[data-test="styled-card"]') - .last() - .contains('Life Expectancy VS Rural %'); - }); - - // flaky - xit('should delete correctly', () => { - // show delete modal - cy.get('[data-test="more-horiz"]').last().trigger('mouseover'); - cy.get('[data-test="chart-list-delete-option"]') - .last() - .should('be.visible'); - cy.get('[data-test="chart-list-delete-option"]') - .last() - .contains('Delete') - .click(); - cy.get('[data-test="Please Confirm-modal"]').should('be.visible'); - cy.get('[data-test="modal-confirm-button"]').should( - 'have.attr', - 'disabled', - ); - cy.get('[data-test="Please Confirm-modal"]').should('be.visible'); - cy.get("[data-test='delete-modal-input']").type('DELETE'); - cy.get('[data-test="modal-confirm-button"]').should( - 'not.have.attr', - 'disabled', - ); - cy.get('[data-test="modal-cancel-button"]').click(); - }); - - // flaky - xit('should edit correctly', () => { - // show edit modal - cy.get('[data-test="more-horiz"]').last().trigger('mouseover'); - cy.get('[data-test="chart-list-edit-option"]').last().should('be.visible'); - cy.get('[data-test="chart-list-edit-option"]').last().click(); - cy.get('[data-test="properties-edit-modal"]').should('be.visible'); - cy.get('[data-test="properties-modal-name-input"]').should( - 'not.have.value', - ); - cy.get('[data-test="properties-modal-cancel-button"]') - .contains('Cancel') - .click(); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/chartlist.applitools.test.ts b/superset-frontend/cypress-base/cypress/integration/chart_list/chartlist.applitools.test.ts index f858f03f5f4b3..60bf87ed1d349 100644 --- a/superset-frontend/cypress-base/cypress/integration/chart_list/chartlist.applitools.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/chart_list/chartlist.applitools.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { CHART_LIST } from './chart_list.helper'; +import { CHART_LIST } from 'cypress/utils/urls'; describe('charts list view', () => { beforeEach(() => { @@ -33,7 +33,7 @@ describe('charts list view', () => { cy.eyesOpen({ testName: 'Charts list-view', }); - cy.eyesCheckWindow('Charts loaded'); + cy.eyesCheckWindow('Charts list-view loaded'); }); it('should load the Charts card list', () => { @@ -41,6 +41,6 @@ describe('charts list view', () => { cy.eyesOpen({ testName: 'Charts card-view', }); - cy.eyesCheckWindow('Charts loaded'); + cy.eyesCheckWindow('Charts card-view loaded'); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts index 4466cc2ad5899..d925ce93c4817 100644 --- a/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/chart_list/filter.test.ts @@ -16,119 +16,85 @@ * specific language governing permissions and limitations * under the License. */ -import { CHART_LIST } from './chart_list.helper'; +import { CHART_LIST } from 'cypress/utils/urls'; +import { setGridMode, clearAllInputs } from 'cypress/utils'; +import { setFilter } from '../explore/utils'; -describe('chart card view filters', () => { - beforeEach(() => { - cy.login(); +describe('Charts filters', () => { + before(() => { cy.visit(CHART_LIST); - cy.get('[aria-label="card-view"]').click(); }); - it('should filter by owners correctly', () => { - // filter by owners - cy.get('[data-test="filters-select"]').first().click(); - cy.get('.rc-virtual-list').contains('alpha user').click(); - cy.get('[data-test="styled-card"]').should('not.exist'); - cy.get('[data-test="filters-select"]').first().click(); - cy.get('.rc-virtual-list').contains('gamma user').click(); - cy.get('[data-test="styled-card"]').should('not.exist'); + beforeEach(() => { + cy.preserveLogin(); + clearAllInputs(); }); - it('should filter by created by correctly', () => { - // filter by created by - cy.get('[data-test="filters-select"]').eq(1).click(); - cy.get('.rc-virtual-list').contains('alpha user').click(); - cy.get('.ant-card').should('not.exist'); - cy.get('[data-test="filters-select"]').eq(1).click(); - cy.get('.rc-virtual-list').contains('gamma user').click(); - cy.get('[data-test="styled-card"]').should('not.exist'); - }); + describe('card-view', () => { + before(() => { + setGridMode('card'); + }); - xit('should filter by viz type correctly', () => { - // filter by viz type - cy.get('[data-test="filters-select"]').eq(2).click(); - cy.get('.rc-virtual-list').contains('area').click({ timeout: 5000 }); - cy.get('[data-test="styled-card"]').its('length').should('be.gt', 0); - cy.get('[data-test="styled-card"]') - .contains("World's Pop Growth") - .should('be.visible'); - cy.get('[data-test="filters-select"]').eq(2).click(); - cy.get('[data-test="filters-select"]').eq(2).type('world_map{enter}'); - cy.get('[data-test="styled-card"]').should('have.length', 1); - cy.get('[data-test="styled-card"]') - .contains('% Rural') - .should('be.visible'); - }); + it('should filter by owners correctly', () => { + setFilter('Owner', 'alpha user'); + cy.getBySel('styled-card').should('not.exist'); + setFilter('Owner', 'admin user'); + cy.getBySel('styled-card').should('exist'); + }); - it('should filter by datasource correctly', () => { - // filter by datasource - cy.get('[data-test="filters-select"]').eq(3).click(); - cy.get('.rc-virtual-list').contains('unicode_test').click(); - cy.get('[data-test="styled-card"]').should('have.length', 1); - cy.get('[data-test="styled-card"]') - .contains('Unicode Cloud') - .should('be.visible'); - cy.get('[data-test="filters-select"]').eq(2).click(); - cy.get('[data-test="filters-select"]') - .eq(2) - .type('energy_usage{enter}{enter}'); - cy.get('[data-test="styled-card"]').its('length').should('be.gt', 0); - }); -}); + it('should filter by created by correctly', () => { + setFilter('Created by', 'alpha user'); + cy.getBySel('styled-card').should('not.exist'); + setFilter('Created by', 'admin user'); + cy.getBySel('styled-card').should('exist'); + }); -describe('chart list view filters', () => { - beforeEach(() => { - cy.login(); - cy.visit(CHART_LIST); - cy.get('[aria-label="list-view"]').click(); - }); + it('should filter by viz type correctly', () => { + setFilter('Chart type', 'Area Chart'); + cy.getBySel('styled-card').should('have.length', 3); + setFilter('Chart type', 'Bubble Chart'); + cy.getBySel('styled-card').should('have.length', 1); + }); - it('should filter by owners correctly', () => { - // filter by owners - cy.get('[data-test="filters-select"]').first().click(); - cy.get('.rc-virtual-list').contains('alpha user').click(); - cy.get('[data-test="table-row"]').should('not.exist'); - cy.get('[data-test="filters-select"]').first().click(); - cy.get('.rc-virtual-list').contains('gamma user').click(); - cy.get('[data-test="table-row"]').should('not.exist'); + it('should filter by datasource correctly', () => { + setFilter('Dataset', 'energy_usage'); + cy.getBySel('styled-card').should('have.length', 3); + setFilter('Dataset', 'unicode_test'); + cy.getBySel('styled-card').should('have.length', 1); + }); }); - it('should filter by created by correctly', () => { - // filter by created by - cy.get('[data-test="filters-select"]').eq(1).click(); - cy.get('.rc-virtual-list').contains('alpha user').click(); - cy.get('[data-test="table-row"]').should('not.exist'); - cy.get('[data-test="filters-select"]').eq(1).click(); - cy.get('.rc-virtual-list').contains('gamma user').click(); - cy.get('[data-test="table-row"]').should('not.exist'); - }); + describe('list-view', () => { + before(() => { + setGridMode('list'); + }); - // this is flaky, but seems to fail along with the card view test of the same name - xit('should filter by viz type correctly', () => { - // filter by viz type - cy.get('[data-test="filters-select"]').eq(2).click(); - cy.get('.rc-virtual-list').contains('area').click({ timeout: 5000 }); - cy.get('[data-test="table-row"]').its('length').should('be.gt', 0); - cy.get('[data-test="table-row"]') - .contains("World's Pop Growth") - .should('exist'); - cy.get('[data-test="filters-select"]').eq(2).click(); - cy.get('[data-test="filters-select"]').eq(2).type('world_map{enter}'); - cy.get('[data-test="table-row"]').should('have.length', 1); - cy.get('[data-test="table-row"]').contains('% Rural').should('exist'); - }); + it('should filter by owners correctly', () => { + setFilter('Owner', 'alpha user'); + cy.getBySel('table-row').should('not.exist'); + setFilter('Owner', 'admin user'); + cy.getBySel('table-row').should('exist'); + }); + + it('should filter by created by correctly', () => { + setFilter('Created by', 'alpha user'); + cy.getBySel('table-row').should('not.exist'); + setFilter('Created by', 'admin user'); + cy.getBySel('table-row').should('exist'); + }); + + it('should filter by viz type correctly', () => { + setFilter('Chart type', 'Area Chart'); + cy.getBySel('table-row').should('have.length', 3); + setFilter('Chart type', 'Bubble Chart'); + cy.getBySel('table-row').should('have.length', 1); + }); - it('should filter by datasource correctly', () => { - // filter by datasource - cy.get('[data-test="filters-select"]').eq(3).click(); - cy.get('.rc-virtual-list').contains('unicode_test').click(); - cy.get('[data-test="table-row"]').should('have.length', 1); - cy.get('[data-test="table-row"]').contains('Unicode Cloud').should('exist'); - cy.get('[data-test="filters-select"]').eq(3).click(); - cy.get('[data-test="filters-select"]') - .eq(3) - .type('energy_usage{enter}{enter}'); - cy.get('[data-test="table-row"]').its('length').should('be.gt', 0); + it('should filter by datasource correctly', () => { + setFilter('Dataset', 'energy_usage'); + cy.getBySel('table-row').should('have.length', 3); + setFilter('Dataset', 'unicode_test'); + cy.getBySel('table-row').should('have.length', 1); + }); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/list.test.ts b/superset-frontend/cypress-base/cypress/integration/chart_list/list.test.ts new file mode 100644 index 0000000000000..e3837445d9522 --- /dev/null +++ b/superset-frontend/cypress-base/cypress/integration/chart_list/list.test.ts @@ -0,0 +1,240 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 { CHART_LIST } from 'cypress/utils/urls'; +import { setGridMode, toggleBulkSelect } from 'cypress/utils'; +import { + setFilter, + interceptBulkDelete, + interceptUpdate, + interceptDelete, +} from '../explore/utils'; + +function orderAlphabetical() { + setFilter('Sort', 'Alphabetical'); +} + +function openProperties() { + cy.get('[aria-label="more-vert"]').eq(1).click(); + cy.getBySel('chart-list-edit-option').click(); +} + +function openMenu() { + cy.get('[aria-label="more-vert"]').eq(1).click(); +} + +function confirmDelete() { + cy.getBySel('delete-modal-input').type('DELETE'); + cy.getBySel('modal-confirm-button').click(); +} + +describe('Charts list', () => { + beforeEach(() => { + cy.preserveLogin(); + }); + + describe('list mode', () => { + before(() => { + cy.visit(CHART_LIST); + setGridMode('list'); + }); + + it('should load rows in list mode', () => { + cy.getBySel('listview-table').should('be.visible'); + cy.getBySel('sort-header').eq(1).contains('Chart'); + cy.getBySel('sort-header').eq(2).contains('Visualization type'); + cy.getBySel('sort-header').eq(3).contains('Dataset'); + cy.getBySel('sort-header').eq(4).contains('Modified by'); + cy.getBySel('sort-header').eq(5).contains('Last modified'); + cy.getBySel('sort-header').eq(6).contains('Created by'); + cy.getBySel('sort-header').eq(7).contains('Actions'); + }); + + it('should sort correctly in list mode', () => { + cy.getBySel('sort-header').eq(1).click(); + cy.getBySel('table-row').first().contains('% Rural'); + cy.getBySel('sort-header').eq(1).click(); + cy.getBySel('table-row').first().contains("World's Population"); + cy.getBySel('sort-header').eq(1).click(); + }); + + it('should bulk select in list mode', () => { + toggleBulkSelect(); + cy.get('#header-toggle-all').click(); + cy.get('[aria-label="checkbox-on"]').should('have.length', 26); + cy.getBySel('bulk-select-copy').contains('25 Selected'); + cy.getBySel('bulk-select-action') + .should('have.length', 2) + .then($btns => { + expect($btns).to.contain('Delete'); + expect($btns).to.contain('Export'); + }); + cy.getBySel('bulk-select-deselect-all').click(); + cy.get('[aria-label="checkbox-on"]').should('have.length', 0); + cy.getBySel('bulk-select-copy').contains('0 Selected'); + cy.getBySel('bulk-select-action').should('not.exist'); + }); + }); + + describe('card mode', () => { + before(() => { + cy.visit(CHART_LIST); + setGridMode('card'); + }); + + it('should load rows in card mode', () => { + cy.getBySel('listview-table').should('not.exist'); + cy.getBySel('styled-card').should('have.length', 25); + }); + + it('should bulk select in card mode', () => { + toggleBulkSelect(); + cy.getBySel('styled-card').click({ multiple: true }); + cy.getBySel('bulk-select-copy').contains('25 Selected'); + cy.getBySel('bulk-select-action') + .should('have.length', 2) + .then($btns => { + expect($btns).to.contain('Delete'); + expect($btns).to.contain('Export'); + }); + cy.getBySel('bulk-select-deselect-all').click(); + cy.getBySel('bulk-select-copy').contains('0 Selected'); + cy.getBySel('bulk-select-action').should('not.exist'); + }); + + it('should sort in card mode', () => { + orderAlphabetical(); + cy.getBySel('styled-card').first().contains('% Rural'); + }); + }); + + describe('common actions', () => { + beforeEach(() => { + cy.createSampleCharts(); + cy.visit(CHART_LIST); + }); + + it('should allow to favorite/unfavorite', () => { + cy.intercept(`/superset/favstar/slice/*/select/`).as('select'); + cy.intercept(`/superset/favstar/slice/*/unselect/`).as('unselect'); + + setGridMode('card'); + orderAlphabetical(); + + cy.getBySel('styled-card').first().contains('% Rural'); + cy.getBySel('styled-card') + .first() + .find("[aria-label='favorite-unselected']") + .click(); + cy.wait('@select'); + cy.getBySel('styled-card') + .first() + .find("[aria-label='favorite-selected']") + .click(); + cy.wait('@unselect'); + cy.getBySel('styled-card') + .first() + .find("[aria-label='favorite-selected']") + .should('not.exist'); + }); + + it('should bulk delete correctly', () => { + interceptBulkDelete(); + toggleBulkSelect(); + + // bulk deletes in card-view + setGridMode('card'); + orderAlphabetical(); + + cy.getBySel('styled-card').eq(1).contains('1 - Sample chart').click(); + cy.getBySel('styled-card').eq(2).contains('2 - Sample chart').click(); + cy.getBySel('bulk-select-action').eq(0).contains('Delete').click(); + confirmDelete(); + cy.wait('@bulkDelete'); + cy.getBySel('styled-card') + .eq(1) + .should('not.contain', '1 - Sample chart'); + cy.getBySel('styled-card') + .eq(2) + .should('not.contain', '2 - Sample chart'); + + // bulk deletes in list-view + setGridMode('list'); + cy.getBySel('table-row').eq(1).contains('3 - Sample chart'); + cy.getBySel('table-row').eq(2).contains('4 - Sample chart'); + cy.get('[data-test="table-row"] input[type="checkbox"]').eq(1).click(); + cy.get('[data-test="table-row"] input[type="checkbox"]').eq(2).click(); + cy.getBySel('bulk-select-action').eq(0).contains('Delete').click(); + confirmDelete(); + cy.wait('@bulkDelete'); + cy.getBySel('table-row').eq(1).should('not.contain', '3 - Sample chart'); + cy.getBySel('table-row').eq(2).should('not.contain', '4 - Sample chart'); + }); + + it('should delete correctly', () => { + interceptDelete(); + + // deletes in card-view + setGridMode('card'); + orderAlphabetical(); + + cy.getBySel('styled-card').eq(1).contains('1 - Sample chart'); + openMenu(); + cy.getBySel('chart-list-delete-option').click(); + confirmDelete(); + cy.wait('@delete'); + cy.getBySel('styled-card') + .eq(1) + .should('not.contain', '1 - Sample chart'); + + // deletes in list-view + setGridMode('list'); + cy.getBySel('table-row').eq(1).contains('2 - Sample chart'); + cy.getBySel('trash').eq(1).click(); + confirmDelete(); + cy.wait('@delete'); + cy.getBySel('table-row').eq(1).should('not.contain', '2 - Sample chart'); + }); + + it('should edit correctly', () => { + interceptUpdate(); + + // edits in card-view + setGridMode('card'); + orderAlphabetical(); + cy.getBySel('styled-card').eq(1).contains('1 - Sample chart'); + + // change title + openProperties(); + cy.getBySel('properties-modal-name-input').type(' | EDITED'); + cy.get('button:contains("Save")').click(); + cy.wait('@update'); + cy.getBySel('styled-card').eq(1).contains('1 - Sample chart | EDITED'); + + // edits in list-view + setGridMode('list'); + cy.getBySel('edit-alt').eq(1).click(); + cy.getBySel('properties-modal-name-input') + .clear() + .type('1 - Sample chart'); + cy.get('button:contains("Save")').click(); + cy.wait('@update'); + cy.getBySel('table-row').eq(1).contains('1 - Sample chart'); + }); + }); +}); diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/list_view.test.ts b/superset-frontend/cypress-base/cypress/integration/chart_list/list_view.test.ts deleted file mode 100644 index 42313d78495f4..0000000000000 --- a/superset-frontend/cypress-base/cypress/integration/chart_list/list_view.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF 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 { CHART_LIST } from './chart_list.helper'; - -describe('chart list view', () => { - beforeEach(() => { - cy.login(); - }); - - it('should load rows', () => { - cy.visit(CHART_LIST); - cy.get('[aria-label="list-view"]').click(); - - cy.get('[data-test="listview-table"]').should('be.visible'); - // check chart list view header - cy.get('[data-test="sort-header"]').eq(1).contains('Chart'); - cy.get('[data-test="sort-header"]').eq(2).contains('Visualization type'); - cy.get('[data-test="sort-header"]').eq(3).contains('Dataset'); - cy.get('[data-test="sort-header"]').eq(4).contains('Modified by'); - cy.get('[data-test="sort-header"]').eq(5).contains('Last modified'); - cy.get('[data-test="sort-header"]').eq(6).contains('Created by'); - cy.get('[data-test="sort-header"]').eq(7).contains('Actions'); - cy.get('[data-test="table-row"]').should('have.length', 25); - }); - - xit('should sort correctly', () => { - cy.get('[data-test="sort-header"]').eq(2).click(); - cy.get('[data-test="sort-header"]').eq(2).click(); - cy.get('[data-test="table-row"]') - .first() - .find('[data-test="table-row-cell"]') - .find('[data-test="cell-text"]') - .contains('Location of Current Developers'); - }); - - it('should bulk delete correctly', () => { - // Load the chart list order by name asc. - // This will ensure the tests stay consistent, and the - // same charts get deleted every time - cy.visit(CHART_LIST, { - qs: { - sortColumn: 'slice_name', - sortOrder: 'asc', - }, - }); - cy.get('[aria-label="list-view"]').click(); - - cy.get('[data-test="listview-table"]').should('be.visible'); - cy.get('[data-test="bulk-select"]').eq(0).click(); - cy.get('[aria-label="checkbox-off"]').eq(1).siblings('input').click(); - cy.get('[aria-label="checkbox-off"]').eq(2).siblings('input').click(); - cy.get('[data-test="bulk-select-action"]').eq(0).click(); - cy.get('[data-test="delete-modal-input"]').eq(0).type('DELETE'); - cy.get('[data-test="modal-confirm-button"]').eq(0).click(); - cy.get('[aria-label="checkbox-on"]').should('not.exist'); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/_skip.controls.test.ts similarity index 92% rename from superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.ts rename to superset-frontend/cypress-base/cypress/integration/dashboard/_skip.controls.test.ts index 961e71bfd6397..085ebb15876f9 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/controls.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/_skip.controls.test.ts @@ -17,22 +17,22 @@ * under the License. */ import { - WORLD_HEALTH_CHARTS, - WORLD_HEALTH_DASHBOARD, waitForChartLoad, ChartSpec, getChartAliasesBySpec, -} from './dashboard.helper'; +} from 'cypress/utils'; +import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls'; +import { WORLD_HEALTH_CHARTS } from './utils'; import { isLegacyResponse } from '../../utils/vizPlugins'; -describe('Dashboard top-level controls', () => { +describe.skip('Dashboard top-level controls', () => { beforeEach(() => { cy.login(); cy.visit(WORLD_HEALTH_DASHBOARD); }); // flaky test - xit('should allow chart level refresh', () => { + it('should allow chart level refresh', () => { const mapSpec = WORLD_HEALTH_CHARTS.find( ({ viz }) => viz === 'world_map', ) as ChartSpec; @@ -58,7 +58,7 @@ describe('Dashboard top-level controls', () => { }); }); - xit('should allow dashboard level force refresh', () => { + it('should allow dashboard level force refresh', () => { // when charts are not start loading, for example, under a secondary tab, // should allow force refresh WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/_skip.filter.test.ts similarity index 91% rename from superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts rename to superset-frontend/cypress-base/cypress/integration/dashboard/_skip.filter.test.ts index e1dd45cf3c30c..f5b617393a3c7 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/filter.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/_skip.filter.test.ts @@ -16,21 +16,22 @@ * specific language governing permissions and limitations * under the License. */ -import { isLegacyResponse, parsePostForm } from 'cypress/utils'; import { - WORLD_HEALTH_CHARTS, - WORLD_HEALTH_DASHBOARD, + isLegacyResponse, + parsePostForm, getChartAliasesBySpec, waitForChartLoad, -} from './dashboard.helper'; +} from 'cypress/utils'; +import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls'; +import { WORLD_HEALTH_CHARTS } from './utils'; -describe('Dashboard filter', () => { +describe.skip('Dashboard filter', () => { before(() => { cy.login(); cy.visit(WORLD_HEALTH_DASHBOARD); }); - xit('should apply filter', () => { + it('should apply filter', () => { WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); getChartAliasesBySpec( WORLD_HEALTH_CHARTS.filter(({ viz }) => viz !== 'filter_box'), diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/key_value.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/_skip.key_value.test.ts similarity index 90% rename from superset-frontend/cypress-base/cypress/integration/dashboard/key_value.test.ts rename to superset-frontend/cypress-base/cypress/integration/dashboard/_skip.key_value.test.ts index 1738ea5bd3d0d..52bc4cd60b321 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/key_value.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/_skip.key_value.test.ts @@ -17,17 +17,15 @@ * under the License. */ import qs from 'querystringify'; -import { - WORLD_HEALTH_DASHBOARD, - WORLD_HEALTH_CHARTS, - waitForChartLoad, -} from './dashboard.helper'; +import { waitForChartLoad } from 'cypress/utils'; +import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls'; +import { WORLD_HEALTH_CHARTS } from './utils'; interface QueryString { native_filters_key: string; } -xdescribe('nativefilter url param key', () => { +describe.skip('nativefilter url param key', () => { // const urlParams = { param1: '123', param2: 'abc' }; before(() => { cy.login(); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/url_params.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/_skip.url_params.test.ts similarity index 83% rename from superset-frontend/cypress-base/cypress/integration/dashboard/url_params.test.ts rename to superset-frontend/cypress-base/cypress/integration/dashboard/_skip.url_params.test.ts index 5f9ad7382e15c..a072cf1207db2 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/url_params.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/_skip.url_params.test.ts @@ -16,14 +16,11 @@ * specific language governing permissions and limitations * under the License. */ -import { parsePostForm, JsonObject } from 'cypress/utils'; -import { - WORLD_HEALTH_DASHBOARD, - WORLD_HEALTH_CHARTS, - waitForChartLoad, -} from './dashboard.helper'; +import { parsePostForm, JsonObject, waitForChartLoad } from 'cypress/utils'; +import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls'; +import { WORLD_HEALTH_CHARTS } from './utils'; -describe('Dashboard form data', () => { +describe.skip('Dashboard form data', () => { const urlParams = { param1: '123', param2: 'abc' }; before(() => { cy.login(); @@ -31,7 +28,7 @@ describe('Dashboard form data', () => { cy.visit(WORLD_HEALTH_DASHBOARD, { qs: urlParams }); }); - xit('should apply url params to slice requests', () => { + it('should apply url params to slice requests', () => { cy.intercept('/api/v1/chart/data?*', request => { // TODO: export url params to chart data API request.body.queries.forEach((query: { url_params: JsonObject }) => { diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/actions.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/actions.test.js new file mode 100644 index 0000000000000..de7c41293460d --- /dev/null +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/actions.test.js @@ -0,0 +1,44 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 { SAMPLE_DASHBOARD_1 } from 'cypress/utils/urls'; +import { interceptFav, interceptUnfav } from './utils'; + +describe('Dashboard actions', () => { + beforeEach(() => { + cy.createSampleDashboards(); + cy.visit(SAMPLE_DASHBOARD_1); + }); + + it('should allow to favorite/unfavorite dashboard', () => { + interceptFav(); + interceptUnfav(); + + cy.getBySel('dashboard-header-container') + .find("[aria-label='favorite-unselected']") + .click(); + cy.wait('@select'); + cy.getBySel('dashboard-header-container') + .find("[aria-label='favorite-selected']") + .click(); + cy.wait('@unselect'); + cy.getBySel('dashboard-header-container') + .find("[aria-label='favorite-selected']") + .should('not.exist'); + }); +}); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.applitools.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.applitools.test.ts index d492175a5e3a6..3269334e8c406 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.applitools.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.applitools.test.ts @@ -16,11 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import { - waitForChartLoad, - WORLD_HEALTH_CHARTS, - WORLD_HEALTH_DASHBOARD, -} from './dashboard.helper'; +import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls'; +import { waitForChartLoad } from 'cypress/utils'; +import { WORLD_HEALTH_CHARTS } from './utils'; describe('Dashboard load', () => { beforeEach(() => { diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts deleted file mode 100644 index 486833c72be65..0000000000000 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/dashboard.helper.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { getChartAlias, Slice } from 'cypress/utils/vizPlugins'; -import { dashboardView } from 'cypress/support/directories'; - -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -export const WORLD_HEALTH_DASHBOARD = '/superset/dashboard/world_health/'; -export const USA_BIRTH_NAMES_DASHBOARD = '/superset/dashboard/births/'; -export const testDashboard = '/superset/dashboard/538/'; -export const TABBED_DASHBOARD = '/superset/dashboard/tabbed_dash/'; -export const ECHARTS_DASHBOARD = '/superset/dashboard/echarts_dash/'; - -export const testItems = { - dashboard: 'Cypress test Dashboard', - dataset: 'Vehicle Sales', - datasetForNativeFilter: 'wb_health_population', - chart: 'Cypress chart', - newChart: 'New Cypress Chart', - createdDashboard: 'New Dashboard', - defaultNameDashboard: '[ untitled dashboard ]', - newDashboardTitle: `Test dashboard [NEW TEST]`, - bulkFirstNameDashboard: 'First Dash', - bulkSecondNameDashboard: 'Second Dash', - worldBanksDataCopy: `World Bank's Data [copy]`, - filterType: { - value: 'Value', - numerical: 'Numerical range', - timeColumn: 'Time column', - timeGrain: 'Time grain', - timeRange: 'Time range', - }, - topTenChart: { - name: 'Most Populated Countries', - filterColumn: 'country_name', - filterColumnYear: 'year', - filterColumnRegion: 'region', - filterColumnCountryCode: 'country_code', - }, - filterDefaultValue: 'United States', - filterOtherCountry: 'China', - filterTimeGrain: 'Month', - filterTimeColumn: 'created', - filterNumericalColumn: 'SP_RUR_TOTL_ZS', -}; - -export const CHECK_DASHBOARD_FAVORITE_ENDPOINT = - '/superset/favstar/Dashboard/*/count'; - -export const WORLD_HEALTH_CHARTS = [ - { name: '% Rural', viz: 'world_map' }, - { name: 'Most Populated Countries', viz: 'table' }, - { name: 'Region Filter', viz: 'filter_box' }, - { name: "World's Population", viz: 'big_number' }, - { name: 'Growth Rate', viz: 'line' }, - { name: 'Rural Breakdown', viz: 'sunburst' }, - { name: "World's Pop Growth", viz: 'area' }, - { name: 'Life Expectancy VS Rural %', viz: 'bubble' }, - { name: 'Treemap', viz: 'treemap' }, - { name: 'Box plot', viz: 'box_plot' }, -] as const; - -export const ECHARTS_CHARTS = [ - { name: 'Number of Girls', viz: 'big_number_total' }, - { name: 'Participants', viz: 'big_number' }, - { name: 'Box plot', viz: 'box_plot' }, - { name: 'Genders', viz: 'pie' }, - { name: 'Energy Force Layout', viz: 'graph_chart' }, -] as const; - -/** Used to specify charts expected by the test suite */ -export interface ChartSpec { - name: string; - viz: string; -} - -export function getChartGridComponent({ name, viz }: ChartSpec) { - return cy - .get(`[data-test-chart-name="${name}"]`) - .should('have.attr', 'data-test-viz-type', viz); -} - -export function waitForChartLoad(chart: ChartSpec) { - return getChartGridComponent(chart).then(gridComponent => { - const chartId = gridComponent.attr('data-test-chart-id'); - // the chart should load in under half a minute - return ( - cy - // this id only becomes visible when the chart is loaded - .get(`#chart-id-${chartId}`, { - timeout: 30000, - }) - .should('be.visible') - // return the chart grid component - .then(() => gridComponent) - ); - }); -} - -const toSlicelike = ($chart: JQuery): Slice => ({ - slice_id: parseInt($chart.attr('data-test-chart-id')!, 10), - form_data: { - viz_type: $chart.attr('data-test-viz-type')!, - }, -}); - -export function getChartAliasBySpec(chart: ChartSpec) { - return getChartGridComponent(chart).then($chart => - cy.wrap(getChartAlias(toSlicelike($chart))), - ); -} - -export function getChartAliasesBySpec(charts: readonly ChartSpec[]) { - const aliases: string[] = []; - charts.forEach(chart => - getChartAliasBySpec(chart).then(alias => { - aliases.push(alias); - }), - ); - // Wrapping the aliases is key. - // That way callers can chain off this function - // and actually get the list of aliases. - return cy.wrap(aliases); -} - -/** - * Drag an element and drop it to another element. - * Usage: - * drag(source).to(target); - */ -export function drag(selector: string, content: string | number | RegExp) { - const dataTransfer = { data: {} }; - return { - to(target: string | Cypress.Chainable) { - cy.get('.dragdroppable') - .contains(selector, content) - .trigger('mousedown', { which: 1 }) - .trigger('dragstart', { dataTransfer }) - .trigger('drag', {}); - - (typeof target === 'string' ? cy.get(target) : target) - .trigger('dragover', { dataTransfer }) - .trigger('drop', { dataTransfer }) - .trigger('dragend', { dataTransfer }) - .trigger('mouseup', { which: 1 }); - }, - }; -} - -export function resize(selector: string) { - return { - to(cordX: number, cordY: number) { - cy.get(selector) - .trigger('mousedown', { which: 1, force: true }) - .trigger('mousemove', { which: 1, cordX, cordY, force: true }) - .trigger('mouseup', { which: 1, force: true }); - }, - }; -} - -export function cleanUp() { - cy.deleteDashboardByName(testItems.dashboard); - cy.deleteDashboardByName(testItems.defaultNameDashboard); - cy.deleteDashboardByName(''); - cy.deleteDashboardByName(testItems.newDashboardTitle); - cy.deleteDashboardByName(testItems.bulkFirstNameDashboard); - cy.deleteDashboardByName(testItems.bulkSecondNameDashboard); - cy.deleteDashboardByName(testItems.createdDashboard); - cy.deleteDashboardByName(testItems.worldBanksDataCopy); - cy.deleteChartByName(testItems.chart); - cy.deleteChartByName(testItems.newChart); -} - -/** ************************************************************************ - * Copy dashboard for testing purpose - * @returns {None} - * @summary helper for copy dashboard for testing purpose - ************************************************************************* */ -export function copyTestDashboard(dashboard: string) { - cy.intercept('POST', '**/copy_dash/**').as('copy'); - cy.intercept('GET', '**/api/v1/dataset/**').as('datasetLoad'); - cy.intercept('**/api/v1/dashboard/?q=**').as('dashboardsList'); - cy.intercept('**/api/v1/dashboard/**').as('dashboard'); - cy.visit('dashboard/list/'); - cy.contains('Actions'); - cy.wait('@dashboardsList').then(xhr => { - const dashboards = xhr.response?.body.result; - /* eslint-disable no-unused-expressions */ - expect(dashboards).not.to.be.undefined; - const testDashboard = dashboards.find( - (d: { dashboard_title: string }) => d.dashboard_title === `${dashboard}`, - ); - cy.visit(testDashboard.url); - }); - cy.get(dashboardView.threeDotsMenuIcon).should('be.visible').click(); - cy.get(dashboardView.saveAsMenuOption).click(); - cy.get(dashboardView.saveModal.dashboardNameInput) - .should('be.visible') - .clear() - .type(testItems.dashboard); - cy.get(dashboardView.saveModal.saveButton).click(); - cy.wait('@copy', { timeout: 45000 }) - .its('response.statusCode') - .should('eq', 200); -} diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/drilltodetail.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/drilltodetail.test.ts index b3dc40bc3500f..a0d0a86434804 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/drilltodetail.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/drilltodetail.test.ts @@ -16,11 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -import { - waitForChartLoad, - ECHARTS_CHARTS, - ECHARTS_DASHBOARD, -} from './dashboard.helper'; +import { waitForChartLoad } from 'cypress/utils'; +import { ECHARTS_DASHBOARD } from 'cypress/utils/urls'; +import { ECHARTS_CHARTS } from './utils'; function interceptSamples() { cy.intercept(`/datasource/samples*`).as('samples'); @@ -52,110 +50,121 @@ function openModalFromChartContext(targetMenuItem: string) { cy.wait('@samples'); } +function closeModal() { + cy.get('body').then($body => { + if ($body.find('[data-test="close-drilltodetail-modal"]').length) { + cy.getBySel('close-drilltodetail-modal').click({ force: true }); + } + }); +} + describe('Drill to detail modal', () => { - beforeEach(() => { - cy.login(); + before(() => { cy.visit(ECHARTS_DASHBOARD); ECHARTS_CHARTS.forEach(waitForChartLoad); }); - it('opens the modal from the context menu', () => { - openModalFromMenu('big_number_total'); - - cy.get("[role='dialog'] .draggable-trigger").should( - 'contain', - 'Drill to detail: Number of Girls', - ); - }); - - it('refreshes the data', () => { - openModalFromMenu('big_number_total'); - // move to the last page - cy.get(".pagination-container [role='navigation'] [role='button']") - .eq(7) - .click(); - cy.wait('@samples'); - // reload - cy.get("[aria-label='reload']").click(); - cy.wait('@samples'); - // make sure it started back from first page - cy.get(".pagination-container [role='navigation'] li.active").should( - 'contain', - '1', - ); - }); - - it('paginates', () => { - openModalFromMenu('big_number_total'); - // checking the data - cy.get("[data-test='row-count-label']").should('contain', '36.4k rows'); - cy.get("[role='rowgroup'] [role='row']") - .should('have.length', 50) - .then($rows => { - expect($rows).to.contain('Amy'); - }); - // checking the paginated data - cy.get(".pagination-container [role='navigation'] [role='button']") - .should('have.length', 9) - .then($pages => { - expect($pages).to.contain('1'); - expect($pages).to.contain('729'); - }); - cy.get(".pagination-container [role='navigation'] [role='button']") - .eq(7) - .click(); - cy.wait('@samples'); - cy.get("[role='rowgroup'] [role='row']") - .should('have.length', 46) - .then($rows => { - expect($rows).to.contain('Victoria'); - }); + beforeEach(() => { + cy.preserveLogin(); + closeModal(); }); - it('clears filters', () => { - interceptSamples(); - - // opens the modal by clicking on the box on the chart - cy.get("[data-test-viz-type='box_plot'] canvas").then($canvas => { - const canvasWidth = $canvas.width() || 0; - const canvasHeight = $canvas.height() || 0; - const canvasCenterX = canvasWidth / 6; - const canvasCenterY = canvasHeight / 6; + describe('Modal actions', () => { + it('opens the modal from the context menu', () => { + openModalFromMenu('big_number_total'); - cy.wrap($canvas) - .scrollIntoView() - .rightclick(canvasCenterX, canvasCenterY, { force: true }); - - openModalFromChartContext('Drill to detail by East Asia & Pacific'); - - // checking the filter - cy.get("[data-test='filter-val']").should( + cy.get("[role='dialog'] .draggable-trigger").should( 'contain', - 'East Asia & Pacific', + 'Drill to detail: Number of Girls', ); - cy.get("[data-test='row-count-label']").should('contain', '1.98k rows'); - cy.get(".pagination-container [role='navigation'] [role='button']") - .should('have.length', 9) - .then($pages => { - expect($pages).to.contain('1'); - expect($pages).to.contain('40'); - }); + }); - // close the filter and test that data was reloaded - cy.get("[data-test='filter-col']").find("[aria-label='close']").click(); + it('refreshes the data', () => { + openModalFromMenu('big_number_total'); + // move to the last page + cy.get(".pagination-container [role='navigation'] [role='button']") + .eq(7) + .click(); cy.wait('@samples'); - cy.get("[data-test='row-count-label']").should('contain', '11.8k rows'); + // reload + cy.get("[aria-label='reload']").click(); + cy.wait('@samples'); + // make sure it started back from first page cy.get(".pagination-container [role='navigation'] li.active").should( 'contain', '1', ); + }); + + it('paginates', () => { + openModalFromMenu('big_number_total'); + // checking the data + cy.getBySel('row-count-label').should('contain', '36.4k rows'); + cy.get("[role='rowgroup'] [role='row']") + .should('have.length', 50) + .then($rows => { + expect($rows).to.contain('Amy'); + }); + // checking the paginated data cy.get(".pagination-container [role='navigation'] [role='button']") .should('have.length', 9) .then($pages => { expect($pages).to.contain('1'); - expect($pages).to.contain('236'); + expect($pages).to.contain('729'); + }); + cy.get(".pagination-container [role='navigation'] [role='button']") + .eq(7) + .click(); + cy.wait('@samples'); + cy.get("[role='rowgroup'] [role='row']") + .should('have.length', 46) + .then($rows => { + expect($rows).to.contain('Victoria'); }); }); + + it('clears filters', () => { + interceptSamples(); + + // opens the modal by clicking on the box on the chart + cy.get("[data-test-viz-type='box_plot'] canvas").then($canvas => { + const canvasWidth = $canvas.width() || 0; + const canvasHeight = $canvas.height() || 0; + const canvasCenterX = canvasWidth / 6; + const canvasCenterY = canvasHeight / 6; + + cy.wrap($canvas) + .scrollIntoView() + .rightclick(canvasCenterX, canvasCenterY, { force: true }); + + openModalFromChartContext('Drill to detail by East Asia & Pacific'); + + // checking the filter + cy.getBySel('filter-val').should('contain', 'East Asia & Pacific'); + cy.getBySel('row-count-label').should('contain', '1.98k rows'); + cy.get(".pagination-container [role='navigation'] [role='button']") + .should('have.length', 9) + .then($pages => { + expect($pages).to.contain('1'); + expect($pages).to.contain('40'); + }); + + // close the filter and test that data was reloaded + cy.getBySel('filter-col').find("[aria-label='close']").click(); + cy.wait('@samples'); + cy.getBySel('row-count-label').should('contain', '11.8k rows'); + cy.get(".pagination-container [role='navigation'] li.active").should( + 'contain', + '1', + ); + cy.get(".pagination-container [role='navigation'] [role='button']") + .should('have.length', 9) + .then($pages => { + expect($pages).to.contain('1'); + expect($pages).to.contain('236'); + }); + }); + }); }); describe('Time-series Bar Chart V2', () => { @@ -180,7 +189,7 @@ describe('Drill to detail modal', () => { .click(); cy.wait('@samples'); - cy.get("[data-test='filter-val']").then($filters => { + cy.getBySel('filter-val').then($filters => { expect($filters).to.contain('1965'); expect($filters).to.contain('boy'); }); @@ -207,10 +216,7 @@ describe('Drill to detail modal', () => { openModalFromChartContext('Drill to detail by East Asia & Pacific'); // checking the filter - cy.get("[data-test='filter-val']").should( - 'contain', - 'East Asia & Pacific', - ); + cy.getBySel('filter-val').should('contain', 'East Asia & Pacific'); }); }); }); @@ -233,7 +239,7 @@ describe('Drill to detail modal', () => { openModalFromChartContext('Drill to detail by boy'); // checking the filtered and paginated data - cy.get("[data-test='filter-val']").should('contain', 'boy'); + cy.getBySel('filter-val').should('contain', 'boy'); }); }); }); @@ -249,7 +255,7 @@ describe('Drill to detail modal', () => { openModalFromChartContext('Drill to detail'); - cy.get("[data-test='filter-val']").should('not.exist'); + cy.getBySel('filter-val').should('not.exist'); }); }); @@ -262,7 +268,7 @@ describe('Drill to detail modal', () => { openModalFromChartContext('Drill to detail'); - cy.get("[data-test='filter-val']").should('not.exist'); + cy.getBySel('filter-val').should('not.exist'); // TODO: test clicking on a trendline // Cypress is refusing to rightclick on the dot diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js deleted file mode 100644 index 10b8a4a40de1f..0000000000000 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_mode.test.js +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF 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 { WORLD_HEALTH_DASHBOARD, drag } from './dashboard.helper'; - -describe('Dashboard edit mode', () => { - beforeEach(() => { - cy.login(); - cy.visit(WORLD_HEALTH_DASHBOARD); - cy.get('.header-with-actions') - .find('[aria-label="Edit dashboard"]') - .click(); - }); - - it('remove, and add chart flow', () => { - // wait for box plot to appear - cy.get('[data-test="grid-container"]').find('.box_plot', { - timeout: 10000, - }); - const elementsCount = 10; - - cy.get('[data-test="dashboard-component-chart-holder"]') - .find('[data-test="dashboard-delete-component-button"]') - .last() - .then($el => { - cy.wrap($el).invoke('show').click(); - // box plot should be gone - cy.get('[data-test="grid-container"]') - .find('.box_plot') - .should('not.exist'); - }); - - // find box plot is available from list - cy.get('[data-test="dashboard-charts-filter-search-input"]').type( - 'Box plot', - ); - cy.get('[data-test="card-title"]').should('have.length', 1); - - drag('[data-test="card-title"]', 'Box plot').to( - '.grid-row.background--transparent:last', - ); - - // add back to dashboard - cy.get('[data-test="grid-container"]') - .find('.box_plot') - .should('be.visible'); - - // should show Save changes button - cy.get('[data-test="header-save-button"]').should('be.visible'); - - // undo first step and expect deleted item - cy.get('[data-test="undo-action"]').click(); - cy.get('[data-test="grid-container"]') - .find('[data-test="chart-container"]') - .should('have.length', elementsCount - 1); - - // Box plot chart should be gone - cy.get('[data-test="grid-container"]') - .find('.box_plot') - .should('not.exist'); - - // undo second step and expect initial items count - cy.get('[data-test="undo-action"]').click(); - cy.get('[data-test="grid-container"]') - .find('[data-test="chart-container"]') - .should('have.length', elementsCount); - cy.get('[data-test="card-title"]').contains('Box plot', { timeout: 5000 }); - - // save changes button should be disabled - cy.get('[data-test="header-save-button"]').should('be.disabled'); - - // no changes, can switch to view mode - cy.get('[data-test="dashboard-edit-actions"]') - .find('[data-test="discard-changes-button"]') - .should('be.visible') - .click(); - cy.get('.header-with-actions').within(() => { - cy.get('[data-test="dashboard-edit-actions"]').should('not.be.visible'); - cy.get('[aria-label="Edit dashboard"]').should('be.visible'); - }); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts deleted file mode 100644 index 57839ebc2cf76..0000000000000 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/edit_properties.test.ts +++ /dev/null @@ -1,263 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// eslint-disable-next-line import/no-extraneous-dependencies -import * as ace from 'brace'; -import * as shortid from 'shortid'; -import { USA_BIRTH_NAMES_DASHBOARD } from './dashboard.helper'; - -function selectColorScheme(color: string) { - // open color scheme dropdown - cy.get('.ant-modal-body') - .contains('Color scheme') - .parents('.ControlHeader') - .next('.ant-select') - .click() - .then($colorSelect => { - // select a new color scheme - cy.wrap($colorSelect).find(`[data-test="${color}"]`).click(); - }); -} - -function assertMetadata(text: string) { - const regex = new RegExp(text); - cy.get('.ant-modal-body') - .find('#json_metadata') - .should('be.visible') - .then(() => { - const metadata = cy.$$('#json_metadata')[0]; - - // cypress can read this locally, but not in ci - // so we have to use the ace module directly to fetch the value - expect(ace.edit(metadata).getValue()).to.match(regex); - }); -} -function clear(input: string) { - cy.get(input).type('{selectall}{backspace}'); -} -function type(input: string, text: string) { - cy.get(input).type(text, { parseSpecialCharSequences: false }); -} - -function openAdvancedProperties() { - return cy - .get('.ant-modal-body') - .contains('Advanced') - .should('be.visible') - .click(); -} - -function openDashboardEditProperties() { - // open dashboard properties edit modal - cy.get( - '.header-with-actions .right-button-panel .ant-dropdown-trigger', - ).trigger('click', { - force: true, - }); - cy.get('[data-test=header-actions-menu]') - .contains('Edit properties') - .click({ force: true }); -} - -describe('Dashboard edit action', () => { - beforeEach(() => { - cy.login(); - cy.visit(USA_BIRTH_NAMES_DASHBOARD); - cy.intercept(`/api/v1/dashboard/births`).as('dashboardGet'); - cy.get('.dashboard-grid', { timeout: 50000 }) - .should('be.visible') // wait for 50 secs to load dashboard - .then(() => { - cy.get('.header-with-actions [aria-label="Edit dashboard"]') - .should('be.visible') - .click(); - openDashboardEditProperties(); - }); - }); - - it('should update the title', () => { - const dashboardTitle = `Test dashboard [${shortid.generate()}]`; - - // update title - cy.get('.ant-modal-body') - .should('be.visible') - .contains('Title') - .get('[data-test="dashboard-title-input"]') - .type(`{selectall}{backspace}${dashboardTitle}`); - - // save edit changes - cy.get('.ant-modal-footer') - .contains('Apply') - .click() - .then(() => { - // assert that modal edit window has closed - cy.get('.ant-modal-body').should('not.exist'); - - // assert title has been updated - cy.get('[data-test="editable-title-input"]').should( - 'have.value', - dashboardTitle, - ); - }); - }); - describe('the color picker is changed', () => { - describe('the metadata has a color scheme', () => { - describe('the advanced tab is open', () => { - it('should overwrite the color scheme', () => { - openAdvancedProperties(); - selectColorScheme('d3Category20b'); - assertMetadata('d3Category20b'); - }); - }); - describe('the advanced tab is not open', () => { - it('should overwrite the color scheme', () => { - selectColorScheme('bnbColors'); - openAdvancedProperties(); - assertMetadata('bnbColors'); - }); - }); - }); - }); - describe('a valid colorScheme is entered', () => { - it('should save json metadata color change to dropdown', () => { - // edit json metadata - openAdvancedProperties().then(() => { - clear('#json_metadata'); - type('#json_metadata', '{"color_scheme":"d3Category20"}'); - }); - - // save edit changes - cy.get('.ant-modal-footer') - .contains('Apply') - .click() - .then(() => { - // assert that modal edit window has closed - cy.get('.ant-modal-body').should('not.exist'); - - // assert color has been updated - openDashboardEditProperties(); - openAdvancedProperties().then(() => { - assertMetadata('d3Category20'); - }); - cy.get('.ant-select-selection-item .color-scheme-option').should( - 'have.attr', - 'data-test', - 'd3Category20', - ); - }); - }); - }); - describe('an invalid colorScheme is entered', () => { - it('should throw an error', () => { - // edit json metadata - openAdvancedProperties().then(() => { - clear('#json_metadata'); - type('#json_metadata', '{"color_scheme":"THIS_DOES_NOT_WORK"}'); - }); - - // save edit changes - cy.get('.ant-modal-footer') - .contains('Apply') - .click() - .then(() => { - // assert that modal edit window has closed - cy.get('.ant-modal-body') - .contains('A valid color scheme is required') - .should('be.visible'); - }); - - cy.on('uncaught:exception', err => { - expect(err.message).to.include('something about the error'); - - // return false to prevent the error from - // failing this test - return false; - }); - }); - }); - describe.skip('the color scheme affects the chart colors', () => { - it('should change the chart colors', () => { - openAdvancedProperties().then(() => { - clear('#json_metadata'); - type( - '#json_metadata', - '{"color_scheme":"lyftColors", "label_colors": {}}', - ); - }); - - cy.get('.ant-modal-footer') - .contains('Apply') - .click() - .then(() => { - cy.get('.ant-modal-body').should('not.exist'); - // assert that the chart has changed colors - cy.get('.line .nv-legend-symbol') - .first() - .should('have.css', 'fill', 'rgb(117, 96, 170)'); - }); - }); - it('the label colors should take precedence over the scheme', () => { - openAdvancedProperties().then(() => { - clear('#json_metadata'); - type( - '#json_metadata', - '{"color_scheme":"lyftColors","label_colors":{"Amanda":"red"}}', - ); - }); - - cy.get('.ant-modal-footer') - .contains('Apply') - .click() - .then(() => { - cy.get('.ant-modal-body').should('not.exist'); - // assert that the chart has changed colors - cy.get('.line .nv-legend-symbol') - .first() - .should('have.css', 'fill', 'rgb(255, 0, 0)'); - }); - }); - it('the shared label colors and label colors are applied correctly', () => { - openAdvancedProperties().then(() => { - clear('#json_metadata'); - type( - '#json_metadata', - '{"color_scheme":"lyftColors","label_colors":{"Amanda":"red"}}', - ); - }); - - cy.get('.ant-modal-footer') - .contains('Apply') - .click() - .then(() => { - cy.get('.ant-modal-body').should('not.exist'); - // assert that the chart has changed colors - cy.get('.line .nv-legend-symbol') - .first() - .should('have.css', 'fill', 'rgb(255, 0, 0)'); // label: amanda - cy.get('.line .nv-legend-symbol') - .eq(11) - .should('have.css', 'fill', 'rgb(234, 11, 140)'); // label: jennifer - cy.get('.word_cloud') - .first() - .find('svg text') - .first() - .should('have.css', 'fill', 'rgb(234, 11, 140)'); // label: jennifer - }); - }); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/editmode.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/editmode.test.ts new file mode 100644 index 0000000000000..c7b5f312880ea --- /dev/null +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/editmode.test.ts @@ -0,0 +1,308 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 { SAMPLE_DASHBOARD_1 } from 'cypress/utils/urls'; +import { drag, resize } from 'cypress/utils'; +import * as ace from 'brace'; +import { interceptGet, interceptUpdate } from './utils'; +import { interceptFiltering as interceptCharts } from '../explore/utils'; + +function editDashboard() { + cy.getBySel('edit-dashboard-button').click(); +} + +function closeModal() { + cy.getBySel('properties-modal-cancel-button').click({ force: true }); +} + +function openProperties() { + cy.get('body').then($body => { + if ($body.find('[data-test="properties-modal-cancel-button"]').length) { + closeModal(); + } + cy.getBySel('actions-trigger').click({ force: true }); + cy.getBySel('header-actions-menu') + .contains('Edit properties') + .click({ force: true }); + cy.wait(500); + }); +} + +function openAdvancedProperties() { + cy.get('.ant-modal-body') + .contains('Advanced') + .should('be.visible') + .click({ force: true }); +} + +function dragComponent(component = 'Unicode Cloud', target = 'card-title') { + drag(`[data-test="${target}"]`, component).to( + '[data-test="grid-content"] [data-test="dragdroppable-object"]', + ); +} + +function discardChanges() { + cy.getBySel('undo-action').click({ force: true }); +} + +function visitEdit() { + interceptCharts(); + interceptGet(); + + cy.visit(SAMPLE_DASHBOARD_1); + cy.wait('@get'); + editDashboard(); + cy.wait('@filtering'); + cy.wait(500); +} + +function selectColorScheme(color: string) { + cy.get( + '[data-test="dashboard-edit-properties-form"] [aria-label="Select color scheme"]', + ) + .first() + .click(); + cy.getBySel(color).click(); +} + +function applyChanges() { + cy.getBySel('properties-modal-apply-button').click(); +} + +function saveChanges() { + interceptUpdate(); + cy.getBySel('header-save-button').click({ force: true }); + cy.wait('@update'); +} + +function assertMetadata(text: string) { + const regex = new RegExp(text); + cy.get('#json_metadata') + .should('be.visible') + .then(() => { + const metadata = cy.$$('#json_metadata')[0]; + + // cypress can read this locally, but not in ci + // so we have to use the ace module directly to fetch the value + expect(ace.edit(metadata).getValue()).to.match(regex); + }); +} +function clearMetadata() { + cy.get('#json_metadata').then($jsonmetadata => { + cy.wrap($jsonmetadata).type('{selectall} {backspace}'); + }); +} + +function writeMetadata(metadata: string) { + cy.get('#json_metadata').then($jsonmetadata => { + cy.wrap($jsonmetadata).type(metadata, { parseSpecialCharSequences: false }); + }); +} + +describe('Dashboard edit', () => { + beforeEach(() => { + cy.preserveLogin(); + }); + + describe('Edit properties', () => { + before(() => { + cy.createSampleDashboards(); + visitEdit(); + }); + + beforeEach(() => { + openProperties(); + }); + + it('should accept a valid color scheme', () => { + openAdvancedProperties(); + clearMetadata(); + writeMetadata('{"color_scheme":"lyftColors"}'); + applyChanges(); + openProperties(); + openAdvancedProperties(); + assertMetadata('lyftColors'); + applyChanges(); + }); + + it('should overwrite the color scheme when advanced is closed', () => { + selectColorScheme('d3Category20b'); + openAdvancedProperties(); + assertMetadata('d3Category20b'); + applyChanges(); + }); + + it('should overwrite the color scheme when advanced is open', () => { + openAdvancedProperties(); + selectColorScheme('googleCategory10c'); + assertMetadata('googleCategory10c'); + applyChanges(); + }); + + it('should not accept an invalid color scheme', () => { + openAdvancedProperties(); + clearMetadata(); + writeMetadata('{"color_scheme":"wrongcolorscheme"}'); + applyChanges(); + cy.get('.ant-modal-body') + .contains('A valid color scheme is required') + .should('be.visible'); + }); + + it('should edit the title', () => { + cy.getBySel('dashboard-title-input').clear().type('Edited title'); + applyChanges(); + cy.getBySel('editable-title-input').should('have.value', 'Edited title'); + }); + }); + + describe('Edit mode', () => { + before(() => { + cy.createSampleDashboards(); + visitEdit(); + }); + + beforeEach(() => { + discardChanges(); + }); + + it('should enable edit mode', () => { + cy.getBySel('dashboard-builder-sidepane').should('be.visible'); + }); + + it('should edit the title inline', () => { + cy.getBySel('editable-title-input').clear().type('Edited title{enter}'); + cy.getBySel('header-save-button').should('be.enabled'); + }); + + it('should filter charts', () => { + interceptCharts(); + cy.getBySel('dashboard-charts-filter-search-input').type('Unicode'); + cy.wait('@filtering'); + cy.getBySel('chart-card') + .should('have.length', 1) + .contains('Unicode Cloud'); + cy.getBySel('dashboard-charts-filter-search-input').clear(); + }); + + it('should disable the Save button when undoing', () => { + dragComponent(); + cy.getBySel('header-save-button').should('be.enabled'); + discardChanges(); + cy.getBySel('header-save-button').should('be.disabled'); + }); + }); + + describe('Components', () => { + before(() => { + cy.createSampleDashboards(); + }); + + beforeEach(() => { + visitEdit(); + }); + + it('should add charts', () => { + dragComponent(); + cy.getBySel('dashboard-component-chart-holder').should('have.length', 1); + }); + + it('should remove added charts', () => { + dragComponent('Pivot Table'); + cy.getBySel('dashboard-component-chart-holder').should('have.length', 1); + cy.getBySel('dashboard-delete-component-button').click(); + cy.getBySel('dashboard-component-chart-holder').should('have.length', 0); + }); + + it('should add markdown component to dashboard', () => { + cy.getBySel('dashboard-builder-component-pane-tabs-navigation') + .find('#tabs-tab-2') + .click(); + + // add new markdown component + dragComponent('Markdown', 'new-component'); + + cy.get('[data-test="dashboard-markdown-editor"]') + .should( + 'have.text', + '✨Markdown✨Markdown✨MarkdownClick here to edit markdown', + ) + .click(); + + cy.getBySel('dashboard-component-chart-holder').contains( + 'Click here to edit [markdown](https://bit.ly/1dQOfRK)', + ); + + cy.getBySel('dashboard-markdown-editor').click().type('Test resize'); + + resize( + '[data-test="dashboard-markdown-editor"] .resizable-container span div:last-child', + ).to(500, 600); + + cy.getBySel('dashboard-markdown-editor').contains('Test resize'); + }); + }); + + describe('Color schemes', () => { + beforeEach(() => { + cy.createSampleDashboards(); + visitEdit(); + }); + + it('should apply a valid color scheme', () => { + dragComponent('Top 10 California Names Timeseries'); + openProperties(); + selectColorScheme('lyftColors'); + applyChanges(); + saveChanges(); + cy.get('.line .nv-legend-symbol') + .first() + .should('have.css', 'fill', 'rgb(234, 11, 140)'); + }); + + it('label colors should take the precedence', () => { + dragComponent('Top 10 California Names Timeseries'); + openProperties(); + openAdvancedProperties(); + clearMetadata(); + writeMetadata( + '{"color_scheme":"lyftColors","label_colors":{"Anthony":"red"}}', + ); + applyChanges(); + saveChanges(); + cy.get('.line .nv-legend-symbol') + .first() + .should('have.css', 'fill', 'rgb(255, 0, 0)'); + }); + }); + + describe('Save', () => { + beforeEach(() => { + cy.createSampleDashboards(); + visitEdit(); + }); + + it('should save', () => { + dragComponent(); + cy.getBySel('header-save-button').should('be.enabled'); + saveChanges(); + cy.getBySel('dashboard-component-chart-holder').should('have.length', 1); + cy.getBySel('edit-dashboard-button').should('be.visible'); + }); + }); +}); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.test.js deleted file mode 100644 index a20b1eb3f5974..0000000000000 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/fav_star.test.js +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF 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 { - WORLD_HEALTH_DASHBOARD, - CHECK_DASHBOARD_FAVORITE_ENDPOINT, -} from './dashboard.helper'; - -describe('Dashboard add to favorite', () => { - let isFavoriteDashboard = false; - - beforeEach(() => { - cy.login(); - - cy.intercept(CHECK_DASHBOARD_FAVORITE_ENDPOINT).as('countFavStar'); - cy.visit(WORLD_HEALTH_DASHBOARD); - - cy.wait('@countFavStar').then(xhr => { - isFavoriteDashboard = xhr.response.body.count === 1; - }); - }); - - it('should allow favor/unfavor', () => { - if (!isFavoriteDashboard) { - cy.get('[data-test="fave-unfave-icon"]') - .find('span') - .should('have.attr', 'aria-label', 'favorite-unselected'); - cy.get('[data-test="fave-unfave-icon"]').trigger('click'); - cy.get('[data-test="fave-unfave-icon"]') - .find('span') - .should('have.attr', 'aria-label', 'favorite-selected') - .and('not.have.attr', 'aria-label', 'favorite-unselected'); - } else { - cy.get('[data-test="fave-unfave-icon"]') - .find('span') - .should('have.attr', 'aria-label', 'favorite-unselected') - .and('not.have.attr', 'aria-label', 'favorite-selected'); - cy.get('[data-test="fave-unfave-icon"]').trigger('click'); - cy.get('[data-test="fave-unfave-icon"]') - .find('span') - .should('have.attr', 'aria-label', 'favorite-unselected') - .and('not.have.attr', 'aria-label', 'favorite-selected'); - } - - // reset to original fav state - cy.get('[data-test="fave-unfave-icon"]').trigger('click'); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts index 9fb84f70c93d8..cd8ab210c622e 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/load.test.ts @@ -16,17 +16,19 @@ * specific language governing permissions and limitations * under the License. */ -import { - waitForChartLoad, - WORLD_HEALTH_CHARTS, - WORLD_HEALTH_DASHBOARD, -} from './dashboard.helper'; +import { WORLD_HEALTH_DASHBOARD } from 'cypress/utils/urls'; +import { waitForChartLoad } from 'cypress/utils'; +import { WORLD_HEALTH_CHARTS, interceptLog } from './utils'; describe('Dashboard load', () => { - beforeEach(() => { + before(() => { cy.login(); }); + beforeEach(() => { + cy.preserveLogin(); + }); + it('should load dashboard', () => { cy.visit(WORLD_HEALTH_DASHBOARD); WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); @@ -34,7 +36,7 @@ describe('Dashboard load', () => { it('should load in edit mode', () => { cy.visit(`${WORLD_HEALTH_DASHBOARD}?edit=true&standalone=true`); - cy.get('[data-test="discard-changes-button"]').should('be.visible'); + cy.getBySel('discard-changes-button').should('be.visible'); }); it('should load in standalone mode', () => { @@ -44,12 +46,13 @@ describe('Dashboard load', () => { it('should load in edit/standalone mode', () => { cy.visit(`${WORLD_HEALTH_DASHBOARD}?edit=true&standalone=true`); - cy.get('[data-test="discard-changes-button"]').should('be.visible'); + cy.getBySel('discard-changes-button').should('be.visible'); cy.get('#app-menu').should('not.exist'); }); it('should send log data', () => { + interceptLog(); cy.visit(WORLD_HEALTH_DASHBOARD); - cy.intercept('/superset/log/?explode=events&dashboard_id=*'); + cy.wait('@logs'); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts deleted file mode 100644 index a27382933b532..0000000000000 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/markdown.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF 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 { TABBED_DASHBOARD, drag, resize } from './dashboard.helper'; - -describe('Dashboard edit markdown', () => { - beforeEach(() => { - cy.login(); - cy.visit(TABBED_DASHBOARD); - }); - - it('should add markdown component to dashboard', () => { - cy.get('.header-with-actions') - .find('[aria-label="Edit dashboard"]') - .click(); - - cy.get('[data-test="dashboard-builder-component-pane-tabs-navigation"]') - .find('.ant-tabs-tab') - .last() - .click(); - - // lazy load - need to open dropdown for the scripts to load - cy.get('.header-with-actions').find('[aria-label="more-horiz"]').click(); - cy.get('[data-test="grid-row-background--transparent"]') - .first() - .as('component-background-first'); - // add new markdown component - drag('[data-test="new-component"]', 'Markdown').to( - '@component-background-first', - ); - cy.get('[data-test="dashboard-markdown-editor"]') - .should( - 'have.text', - '✨Markdown✨Markdown✨MarkdownClick here to edit markdown', - ) - .click(); - - cy.get('[data-test="dashboard-component-chart-holder"]') - .find('.ace_content') - .contains('Click here to edit [markdown](https://bit.ly/1dQOfRK)'); - - cy.get('[data-test="dashboard-markdown-editor"]') - .click() - .type('Test resize'); - - resize( - '[data-test="dashboard-markdown-editor"] .resizable-container span div:last-child', - ).to(500, 600); - - cy.get('[data-test="dashboard-markdown-editor"]').contains('Test resize'); - - cy.get('[data-test="nav-list"]:first').click('right', { force: true }); - cy.get('[data-test="dashboard-component-chart-holder"]') - .find('.ace_content') - .should('not.exist'); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts index b409aa06d0a2e..403faf1668e25 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilters.test.ts @@ -23,25 +23,16 @@ import { exploreView, dataTestChartName, } from 'cypress/support/directories'; +import { SAMPLE_DASHBOARD_1 } from 'cypress/utils/urls'; + import { - cleanUp, - copyTestDashboard, - testItems, - waitForChartLoad, - WORLD_HEALTH_DASHBOARD, - WORLD_HEALTH_CHARTS, -} from './dashboard.helper'; -import { - addCountryCodeFilter, addCountryNameFilter, addParentFilterWithValue, - addRegionFilter, applyAdvancedTimeRangeFilterOnDashboard, applyNativeFilterValueWithIndex, cancelNativeFilterSettings, checkNativeFilterTooltip, clickOnAddFilterInModal, - closeDashboardToastMessage, collapseFilterOnLeftPanel, deleteNativeFilter, enterNativeFilterEditModal, @@ -55,734 +46,608 @@ import { validateFilterContentOnDashboard, valueNativeFilterOptions, validateFilterNameOnDashboard, -} from './nativeFilter.helper'; -import { DASHBOARD_LIST } from '../dashboard_list/dashboard_list.helper'; -import { CHART_LIST } from '../chart_list/chart_list.helper'; - -// TODO: fix flaky init logic and re-enable -const milliseconds = new Date().getTime(); -const dashboard = `Test Dashboard${milliseconds}`; - -describe('Nativefilters tests initial state required', () => { + testItems, + WORLD_HEALTH_CHARTS, + interceptGet, + interceptCharts, + interceptDatasets, +} from './utils'; + +const SAMPLE_CHART = { name: 'Most Populated Countries', viz: 'table' }; + +function visitDashboard() { + interceptCharts(); + interceptGet(); + interceptDatasets(); + + cy.visit(SAMPLE_DASHBOARD_1); + cy.wait('@get'); + cy.wait('@getCharts'); + cy.wait('@getDatasets'); + cy.wait(500); +} + +function prepareDashboardFilters( + filters: { name: string; column: string; datasetId: number }[], +) { + cy.request({ + method: 'GET', + url: `api/v1/dashboard/1-sample-dashboard`, + }).then(res => { + const { body } = res; + const dashboardId = body.result.id; + const allFilters: Record[] = []; + filters.forEach((f, i) => { + allFilters.push({ + id: `NATIVE_FILTER-fLH0pxFQ${i}`, + controlValues: { + enableEmptyFilter: false, + defaultToFirstItem: false, + multiSelect: true, + searchAllOptions: false, + inverseSelection: false, + }, + name: f.name, + filterType: 'filter_select', + targets: [ + { + datasetId: f.datasetId, + column: { name: f.column }, + }, + ], + defaultDataMask: { + extraFormData: {}, + filterState: {}, + ownState: {}, + }, + cascadeParentIds: [], + scope: { + rootPath: ['ROOT_ID'], + excluded: [], + }, + type: 'NATIVE_FILTER', + description: '', + chartsInScope: [6], + tabsInScope: [], + }); + }); + if (dashboardId) { + const jsonMetadata = { + show_native_filters: true, + native_filter_configuration: allFilters, + timed_refresh_immune_slices: [], + expanded_slices: {}, + refresh_frequency: 0, + color_scheme: '', + label_colors: {}, + shared_label_colors: {}, + color_scheme_domain: [], + positions: { + DASHBOARD_VERSION_KEY: 'v2', + ROOT_ID: { type: 'ROOT', id: 'ROOT_ID', children: ['GRID_ID'] }, + GRID_ID: { + type: 'GRID', + id: 'GRID_ID', + children: ['ROW-0rHnUz4nMA'], + parents: ['ROOT_ID'], + }, + HEADER_ID: { + id: 'HEADER_ID', + type: 'HEADER', + meta: { text: '1 - Sample dashboard' }, + }, + 'CHART-DF6EfI55F-': { + type: 'CHART', + id: 'CHART-DF6EfI55F-', + children: [], + parents: ['ROOT_ID', 'GRID_ID', 'ROW-0rHnUz4nMA'], + meta: { + width: 4, + height: 50, + chartId: 6, + sliceName: 'Most Populated Countries', + }, + }, + 'ROW-0rHnUz4nMA': { + type: 'ROW', + id: 'ROW-0rHnUz4nMA', + children: ['CHART-DF6EfI55F-'], + parents: ['ROOT_ID', 'GRID_ID'], + meta: { background: 'BACKGROUND_TRANSPARENT' }, + }, + }, + default_filters: '{}', + filter_scopes: {}, + chart_configuration: {}, + }; + + return cy + .request({ + method: 'PUT', + url: `api/v1/dashboard/${dashboardId}`, + body: { + json_metadata: JSON.stringify(jsonMetadata), + }, + }) + .then(() => visitDashboard()); + } + return cy; + }); +} + +function selectFilter(index: number) { + cy.get("[data-test='filter-title-container'] [draggable='true']") + .eq(index) + .click(); +} + +function closeFilterModal() { + cy.get('body').then($body => { + if ($body.find('[data-test="native-filter-modal-cancel-button"]').length) { + cy.getBySel('native-filter-modal-cancel-button').click(); + } + }); +} + +describe('Native filters', () => { beforeEach(() => { - cy.login(); - cleanUp(); - copyTestDashboard("World Bank's Data"); - WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); - closeDashboardToastMessage(); - }); - afterEach(() => { - cleanUp(); + cy.preserveLogin(); }); - it('Verify that default value is respected after revisit', () => { - expandFilterOnLeftPanel(); - enterNativeFilterEditModal(); - addCountryNameFilter(); - inputNativeFilterDefaultValue(testItems.filterDefaultValue); - saveNativeFilterSettings(); - WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); - cy.get(nativeFilters.filterItem) - .contains(testItems.filterDefaultValue) - .should('be.visible'); - cy.get(dataTestChartName(testItems.topTenChart.name)).within(() => { - cy.contains(testItems.filterDefaultValue).should('be.visible'); - cy.contains(testItems.filterOtherCountry).should('not.exist'); - }); - cy.request( - 'api/v1/dashboard/?q=(order_column:changed_on_delta_humanized,order_direction:desc,page:0,page_size:100)', - ).then(xhr => { - const dashboards = xhr.body.result; - const testDashboard = dashboards.find( - (d: { dashboard_title: string }) => - d.dashboard_title === testItems.dashboard, - ); - cy.visit(testDashboard.url); + describe('Nativefilters tests initial state required', () => { + beforeEach(() => { + cy.createSampleDashboards(); }); - WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); - cy.get(dataTestChartName(testItems.topTenChart.name)).within(() => { - cy.contains(testItems.filterDefaultValue).should('be.visible'); - cy.contains(testItems.filterOtherCountry).should('not.exist'); - }); - validateFilterContentOnDashboard(testItems.filterDefaultValue); - }); - it('User can create parent filters using "Values are dependent on other filters"', () => { - enterNativeFilterEditModal(); - // Create parent filter 'region'. - addRegionFilter(); - // Create filter 'country_name' depend on region filter. - clickOnAddFilterInModal(); - addCountryNameFilter(); - cy.get(nativeFilters.filterConfigurationSections.displayedSection).within( - () => { - cy.contains('Values are dependent on other filters') - .should('be.visible') - .click(); - }, - ); - addParentFilterWithValue(0, testItems.topTenChart.filterColumnRegion); - cy.wait(1000); - saveNativeFilterSettings(); - // Validate both filter in dashboard view. - WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); - [ - testItems.topTenChart.filterColumnRegion, - testItems.topTenChart.filterColumn, - ].forEach(it => { - cy.get(nativeFilters.filterFromDashboardView.filterName) - .contains(it) + it('Verify that default value is respected after revisit', () => { + prepareDashboardFilters([ + { name: 'country_name', column: 'country_name', datasetId: 2 }, + ]); + enterNativeFilterEditModal(); + inputNativeFilterDefaultValue(testItems.filterDefaultValue); + saveNativeFilterSettings([SAMPLE_CHART]); + cy.get(nativeFilters.filterItem) + .contains(testItems.filterDefaultValue) .should('be.visible'); - }); - getNativeFilterPlaceholderWithIndex(1) - .invoke('text') - .should('equal', '214 options', { timeout: 20000 }); - // apply first filter value and validate 2nd filter is depden on 1st filter. - applyNativeFilterValueWithIndex(0, 'North America'); - getNativeFilterPlaceholderWithIndex(0).should('have.text', '3 options', { - timeout: 20000, - }); - }); - - it('user can delete dependent filter', () => { - enterNativeFilterEditModal(); - addRegionFilter(); - clickOnAddFilterInModal(); - addCountryNameFilter(); - cy.get(nativeFilters.filterConfigurationSections.displayedSection).within( - () => { - cy.contains('Values are dependent on other filters') - .should('be.visible') - .click(); - }, - ); - addParentFilterWithValue(0, testItems.topTenChart.filterColumnRegion); - // remove year native filter to cause it disappears from parent filter input in global sales - cy.get(nativeFilters.modal.tabsList.removeTab) - .should('be.visible') - .first() - .click(); - // make sure you are seeing global sales filter which had parent filter - cy.get(nativeFilters.modal.tabsList.filterItemsContainer) - .children() - .last() - .click(); - // - cy.wait(1000); - cy.get(nativeFilters.filterConfigurationSections.displayedSection).within( - () => { - cy.contains('Values are dependent on other filters').should( - 'not.exist', - ); - }, - ); - }); + cy.get(dataTestChartName(testItems.topTenChart.name)).within(() => { + cy.contains(testItems.filterDefaultValue).should('be.visible'); + cy.contains(testItems.filterOtherCountry).should('not.exist'); + }); - it('User can create filter depend on 2 other filters', () => { - enterNativeFilterEditModal(); - // add first filter - addRegionFilter(); - // add second filter - clickOnAddFilterInModal(); - addCountryNameFilter(); - // add third filter - clickOnAddFilterInModal(); - addCountryCodeFilter(); - cy.wait(1000); - cy.get(nativeFilters.filterConfigurationSections.displayedSection).within( - () => { - cy.contains('Values are dependent on other filters') - .should('be.visible') - .click(); - cy.get(exploreView.controlPanel.addFieldValue).click(); - }, - ); - // add value to the first input - addParentFilterWithValue(0, testItems.topTenChart.filterColumnRegion); - // add value to the second input - addParentFilterWithValue(1, testItems.topTenChart.filterColumn); - saveNativeFilterSettings(); - // wait for charts load - WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); - // filters should be displayed in the left panel - [ - testItems.topTenChart.filterColumnRegion, - testItems.topTenChart.filterColumn, - testItems.topTenChart.filterColumnCountryCode, - ].forEach(it => { - validateFilterNameOnDashboard(it); + // reload dashboard + cy.reload(); + cy.get(dataTestChartName(testItems.topTenChart.name)).within(() => { + cy.contains(testItems.filterDefaultValue).should('be.visible'); + cy.contains(testItems.filterOtherCountry).should('not.exist'); + }); + validateFilterContentOnDashboard(testItems.filterDefaultValue); }); - // initially first filter shows 39 options - getNativeFilterPlaceholderWithIndex(0).should('have.text', '7 options'); - // initially second filter shows 409 options - getNativeFilterPlaceholderWithIndex(1).should('have.text', '214 options'); - // verify third filter shows 409 options - getNativeFilterPlaceholderWithIndex(2).should('have.text', '214 options'); - - // apply first filter value - applyNativeFilterValueWithIndex(0, 'North America'); - - // verify second filter shows 409 options available still - getNativeFilterPlaceholderWithIndex(0).should('have.text', '214 options'); - - // verify second filter shows 69 options available still - getNativeFilterPlaceholderWithIndex(1).should('have.text', '3 options'); - - // apply second filter value - applyNativeFilterValueWithIndex(1, 'United States'); - - // verify number of available options for third filter - should be decreased to only one - getNativeFilterPlaceholderWithIndex(0).should('have.text', '1 option'); - }); - - it('User can remove parent filters', () => { - enterNativeFilterEditModal(); - addRegionFilter(); - clickOnAddFilterInModal(); - addCountryNameFilter(); - cy.wait(1000); - // Select dependdent option and auto use platform for genre - cy.get(nativeFilters.filterConfigurationSections.displayedSection).within( - () => { - cy.contains('Values are dependent on other filters') - .should('be.visible') - .click(); - }, - ); - saveNativeFilterSettings(); - enterNativeFilterEditModal(); - cy.get(nativeFilters.modal.tabsList.removeTab) - .should('be.visible') - .first() - .click({ - force: true, + it('User can create parent filters using "Values are dependent on other filters"', () => { + prepareDashboardFilters([ + { name: 'region', column: 'region', datasetId: 2 }, + { name: 'country_name', column: 'country_name', datasetId: 2 }, + ]); + enterNativeFilterEditModal(); + selectFilter(1); + cy.get(nativeFilters.filterConfigurationSections.displayedSection).within( + () => { + cy.contains('Values are dependent on other filters') + .should('be.visible') + .click(); + }, + ); + addParentFilterWithValue(0, testItems.topTenChart.filterColumnRegion); + saveNativeFilterSettings([SAMPLE_CHART]); + [ + testItems.topTenChart.filterColumnRegion, + testItems.topTenChart.filterColumn, + ].forEach(it => { + cy.get(nativeFilters.filterFromDashboardView.filterName) + .contains(it) + .should('be.visible'); + }); + getNativeFilterPlaceholderWithIndex(1) + .invoke('text') + .should('equal', '214 options', { timeout: 20000 }); + // apply first filter value and validate 2nd filter is depden on 1st filter. + applyNativeFilterValueWithIndex(0, 'North America'); + getNativeFilterPlaceholderWithIndex(0).should('have.text', '3 options', { + timeout: 20000, }); - saveNativeFilterSettings(); - WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); - cy.get(dataTestChartName(testItems.topTenChart.name)).within(() => { - cy.contains(testItems.filterDefaultValue).should('be.visible'); - cy.contains(testItems.filterOtherCountry).should('be.visible'); }); - }); -}); -describe('Nativefilters initial state not required', () => { - beforeEach(() => { - cy.login(); - cy.visit(WORLD_HEALTH_DASHBOARD); - }); - - after(() => { - enterNativeFilterEditModal(); - deleteNativeFilter(); - }); + it('user can delete dependent filter', () => { + prepareDashboardFilters([ + { name: 'region', column: 'region', datasetId: 2 }, + { name: 'country_name', column: 'country_name', datasetId: 2 }, + ]); + enterNativeFilterEditModal(); + selectFilter(1); + cy.get(nativeFilters.filterConfigurationSections.displayedSection).within( + () => { + cy.contains('Values are dependent on other filters') + .should('be.visible') + .click(); + }, + ); + addParentFilterWithValue(0, testItems.topTenChart.filterColumnRegion); + // remove year native filter to cause it disappears from parent filter input in global sales + cy.get(nativeFilters.modal.tabsList.removeTab) + .should('be.visible') + .first() + .click(); + // make sure you are seeing global sales filter which had parent filter + cy.get(nativeFilters.modal.tabsList.filterItemsContainer) + .children() + .last() + .click(); + // + cy.wait(1000); + cy.get(nativeFilters.filterConfigurationSections.displayedSection).within( + () => { + cy.contains('Values are dependent on other filters').should( + 'not.exist', + ); + }, + ); + }); - it('User can expand / retract native filter sidebar on a dashboard', () => { - cy.get(nativeFilters.addFilterButton.button).should('not.exist'); - expandFilterOnLeftPanel(); - cy.get(nativeFilters.filterFromDashboardView.createFilterButton).should( - 'be.visible', - ); - cy.get(nativeFilters.filterFromDashboardView.expand).should( - 'not.be.visible', - ); - collapseFilterOnLeftPanel(); - }); + it('User can create filter depend on 2 other filters', () => { + prepareDashboardFilters([ + { name: 'region', column: 'region', datasetId: 2 }, + { name: 'country_name', column: 'country_name', datasetId: 2 }, + { name: 'country_code', column: 'country_code', datasetId: 2 }, + ]); + enterNativeFilterEditModal(); + selectFilter(2); + cy.get(nativeFilters.filterConfigurationSections.displayedSection).within( + () => { + cy.contains('Values are dependent on other filters') + .should('be.visible') + .click(); + cy.get(exploreView.controlPanel.addFieldValue).click(); + }, + ); + // add value to the first input + addParentFilterWithValue(0, testItems.topTenChart.filterColumnRegion); + // add value to the second input + addParentFilterWithValue(1, testItems.topTenChart.filterColumn); + saveNativeFilterSettings([SAMPLE_CHART]); + // filters should be displayed in the left panel + [ + testItems.topTenChart.filterColumnRegion, + testItems.topTenChart.filterColumn, + testItems.topTenChart.filterColumnCountryCode, + ].forEach(it => { + validateFilterNameOnDashboard(it); + }); - it('User can enter filter edit pop-up by clicking on native filter edit icon', () => { - enterNativeFilterEditModal(); - }); + // initially first filter shows 39 options + getNativeFilterPlaceholderWithIndex(0).should('have.text', '7 options'); + // initially second filter shows 409 options + getNativeFilterPlaceholderWithIndex(1).should('have.text', '214 options'); + // verify third filter shows 409 options + getNativeFilterPlaceholderWithIndex(2).should('have.text', '214 options'); - it('User can delete a native filter', () => { - enterNativeFilterEditModal(); - cy.get(nativeFilters.filtersList.removeIcon).first().click(); - cy.contains('Restore Filter').should('not.exist', { timeout: 10000 }); - saveNativeFilterSettings(); - }); + // apply first filter value + applyNativeFilterValueWithIndex(0, 'North America'); - it('User can cancel creating a new filter', () => { - enterNativeFilterEditModal(); - cancelNativeFilterSettings(); - }); + // verify second filter shows 409 options available still + getNativeFilterPlaceholderWithIndex(0).should('have.text', '214 options'); - it('Verify setting options and tooltips for value filter', () => { - enterNativeFilterEditModal(); - cy.contains('Filter value is required').should('be.visible').click(); - checkNativeFilterTooltip(0, nativeFilterTooltips.defaultValue); - cy.get(nativeFilters.modal.container).should('be.visible'); - valueNativeFilterOptions.forEach(el => { - cy.contains(el); - }); - cy.contains('Values are dependent on other filters').should('not.exist'); - cy.get(nativeFilters.filterConfigurationSections.checkedCheckbox).contains( - 'Can select multiple values', - ); - checkNativeFilterTooltip(1, nativeFilterTooltips.required); - checkNativeFilterTooltip(2, nativeFilterTooltips.defaultToFirstItem); - checkNativeFilterTooltip(3, nativeFilterTooltips.searchAllFilterOptions); - checkNativeFilterTooltip(4, nativeFilterTooltips.inverseSelection); - clickOnAddFilterInModal(); - cy.contains('Values are dependent on other filters').should('exist'); - }); + // verify second filter shows 69 options available still + getNativeFilterPlaceholderWithIndex(1).should('have.text', '3 options'); - it("User can check 'Filter has default value'", () => { - enterNativeFilterEditModal(); - addCountryNameFilter(); - inputNativeFilterDefaultValue(testItems.filterDefaultValue); - }); + // apply second filter value + applyNativeFilterValueWithIndex(1, 'United States'); - it('User can add a new native filter', () => { - let filterKey: string; - const removeFirstChar = (search: string) => - search.split('').slice(1, search.length).join(''); - cy.wait(3000); - cy.location().then(loc => { - const queryParams = qs.parse(removeFirstChar(loc.search)); - filterKey = queryParams.native_filters_key as string; - expect(typeof filterKey).eq('string'); + // verify number of available options for third filter - should be decreased to only one + getNativeFilterPlaceholderWithIndex(0).should('have.text', '1 option'); }); - enterNativeFilterEditModal(); - addCountryNameFilter(); - saveNativeFilterSettings(); - cy.wait(3000); - cy.location().then(loc => { - const queryParams = qs.parse(removeFirstChar(loc.search)); - const newfilterKey = queryParams.native_filters_key; - expect(newfilterKey).eq(filterKey); - }); - cy.wait(3000); - cy.get(nativeFilters.modal.container).should('not.exist'); - }); - it('User can undo deleting a native filter', () => { - enterNativeFilterEditModal(); - addCountryCodeFilter(); - saveNativeFilterSettings(); - WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); - validateFilterNameOnDashboard( - testItems.topTenChart.filterColumnCountryCode, - ); - enterNativeFilterEditModal(); - cy.get(nativeFilters.filtersList.removeIcon).first().click(); - cy.get('[data-test="restore-filter-button"]').should('be.visible').click(); - cy.get(nativeFilters.modal.container) - .find(nativeFilters.filtersPanel.filterName) - .should( - 'have.attr', - 'value', - testItems.topTenChart.filterColumnCountryCode, + it('User can remove parent filters', () => { + prepareDashboardFilters([ + { name: 'region', column: 'region', datasetId: 2 }, + { name: 'country_name', column: 'country_name', datasetId: 2 }, + ]); + enterNativeFilterEditModal(); + selectFilter(1); + // Select dependdent option and auto use platform for genre + cy.get(nativeFilters.filterConfigurationSections.displayedSection).within( + () => { + cy.contains('Values are dependent on other filters') + .should('be.visible') + .click(); + }, ); - }); - - it('User can create a time grain filter', () => { - enterNativeFilterEditModal(); - fillNativeFilterForm( - testItems.filterType.timeGrain, - testItems.filterType.timeGrain, - testItems.datasetForNativeFilter, - ); - saveNativeFilterSettings(); - applyNativeFilterValueWithIndex(0, testItems.filterTimeGrain); - cy.get(nativeFilters.applyFilter).click(); - cy.url().then(u => { - const ur = new URL(u); - expect(ur.search).to.include('native_filters'); + saveNativeFilterSettings([SAMPLE_CHART]); + enterNativeFilterEditModal(); + cy.get(nativeFilters.modal.tabsList.removeTab) + .should('be.visible') + .first() + .click({ + force: true, + }); + saveNativeFilterSettings([SAMPLE_CHART]); + cy.get(dataTestChartName(testItems.topTenChart.name)).within(() => { + cy.contains(testItems.filterDefaultValue).should('be.visible'); + cy.contains(testItems.filterOtherCountry).should('be.visible'); + }); }); - validateFilterNameOnDashboard(testItems.filterType.timeGrain); - validateFilterContentOnDashboard(testItems.filterTimeGrain); }); - it('User can create a time range filter', () => { - enterNativeFilterEditModal(); - fillNativeFilterForm( - testItems.filterType.timeRange, - testItems.filterType.timeRange, - ); - saveNativeFilterSettings(); - cy.get(dashboardView.salesDashboardSpecific.vehicleSalesFilterTimeRange) - .should('be.visible') - .click(); - applyAdvancedTimeRangeFilterOnDashboard('2005-12-17', '2006-12-17'); - cy.url().then(u => { - const ur = new URL(u); - expect(ur.search).to.include('native_filters'); + describe('Nativefilters basic interactions', () => { + before(() => { + cy.createSampleDashboards(); + visitDashboard(); }); - validateFilterNameOnDashboard(testItems.filterType.timeRange); - cy.get(nativeFilters.filterFromDashboardView.timeRangeFilterContent) - .contains('2005-12-17') - .should('be.visible'); - }); - it('User can create a time column filter', () => { - enterNativeFilterEditModal(); - fillNativeFilterForm( - testItems.filterType.timeColumn, - testItems.filterType.timeColumn, - testItems.datasetForNativeFilter, - ); - saveNativeFilterSettings(); - cy.intercept(`/api/v1/chart/data?form_data=**`).as('chart'); - cy.get(nativeFilters.modal.container).should('not.exist'); - // assert that native filter is created - validateFilterNameOnDashboard(testItems.filterType.timeColumn); - applyNativeFilterValueWithIndex(0, testItems.topTenChart.filterColumnYear); - cy.get(nativeFilters.applyFilter).click({ force: true }); - cy.wait('@chart'); - validateFilterContentOnDashboard(testItems.topTenChart.filterColumnYear); - }); + beforeEach(() => { + closeFilterModal(); + }); - it('User can create a numerical range filter', () => { - enterNativeFilterEditModal(); - fillNativeFilterForm( - testItems.filterType.numerical, - testItems.filterNumericalColumn, - testItems.datasetForNativeFilter, - testItems.filterNumericalColumn, - ); - saveNativeFilterSettings(); - // assertions - cy.get(nativeFilters.slider.slider).should('be.visible').click('center'); - // cy.get(sqlLabView.tooltip).should('be.visible'); - cy.intercept(`/superset/explore_json/*`).as('slices'); - cy.get(nativeFilters.applyFilter).click(); - cy.wait('@slices'); - // assert that the url contains 'native_filters' in the url - cy.url().then(u => { - const ur = new URL(u); - expect(ur.search).to.include('native_filters'); - // assert that the start handle has a value - cy.get(nativeFilters.slider.startHandle) - .invoke('attr', 'aria-valuenow') - .should('exist'); - // assert that the end handle has a value - cy.get(nativeFilters.slider.endHandle) - .invoke('attr', 'aria-valuenow') - .should('exist'); - // assert slider text matches what we should have - cy.get(nativeFilters.slider.sliderText).should('have.text', '49'); + it('User can expand / retract native filter sidebar on a dashboard', () => { + cy.get(nativeFilters.addFilterButton.button).should('not.exist'); + expandFilterOnLeftPanel(); + cy.get(nativeFilters.filterFromDashboardView.createFilterButton).should( + 'be.visible', + ); + cy.get(nativeFilters.filterFromDashboardView.expand).should( + 'not.be.visible', + ); + collapseFilterOnLeftPanel(); }); - }); - it('User can undo deleting a native filter', () => { - enterNativeFilterEditModal(); - addCountryNameFilter(); - saveNativeFilterSettings(); - WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); - validateFilterNameOnDashboard(testItems.topTenChart.filterColumn); - enterNativeFilterEditModal(); - undoDeleteNativeFilter(); - }); + it('User can enter filter edit pop-up by clicking on native filter edit icon', () => { + enterNativeFilterEditModal(false); + }); - it('User can cancel changes in native filter', () => { - enterNativeFilterEditModal(); - fillNativeFilterForm( - testItems.filterType.value, - 'suffix', - testItems.datasetForNativeFilter, - ); - cancelNativeFilterSettings(); - enterNativeFilterEditModal(); - cy.get(nativeFilters.filtersList.removeIcon).first().click(); - cy.contains('You have removed this filter.').should('be.visible'); - saveNativeFilterSettings(); - }); + it('User can delete a native filter', () => { + enterNativeFilterEditModal(false); + cy.get(nativeFilters.filtersList.removeIcon).first().click(); + cy.contains('Restore Filter').should('not.exist', { timeout: 10000 }); + }); - it('User can create a value filter', () => { - enterNativeFilterEditModal(); - addCountryNameFilter(); - cy.get(nativeFilters.filtersPanel.filterTypeInput) - .find(nativeFilters.filtersPanel.filterTypeItem) - .should('have.text', testItems.filterType.value); - saveNativeFilterSettings(); - validateFilterNameOnDashboard(testItems.topTenChart.filterColumn); - }); + it('User can cancel creating a new filter', () => { + enterNativeFilterEditModal(false); + cancelNativeFilterSettings(); + }); - it('User can apply value filter with selected values', () => { - enterNativeFilterEditModal(); - addCountryNameFilter(); - saveNativeFilterSettings(); - WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); - applyNativeFilterValueWithIndex(0, testItems.filterDefaultValue); - cy.get(nativeFilters.applyFilter).click(); - cy.get(dataTestChartName(testItems.topTenChart.name)).within(() => { - cy.contains(testItems.filterDefaultValue).should('be.visible'); - cy.contains(testItems.filterOtherCountry).should('not.exist'); + it('Verify setting options and tooltips for value filter', () => { + enterNativeFilterEditModal(false); + cy.contains('Filter value is required').should('be.visible').click(); + checkNativeFilterTooltip(0, nativeFilterTooltips.defaultValue); + cy.get(nativeFilters.modal.container).should('be.visible'); + valueNativeFilterOptions.forEach(el => { + cy.contains(el); + }); + cy.contains('Values are dependent on other filters').should('not.exist'); + cy.get( + nativeFilters.filterConfigurationSections.checkedCheckbox, + ).contains('Can select multiple values'); + checkNativeFilterTooltip(1, nativeFilterTooltips.required); + checkNativeFilterTooltip(2, nativeFilterTooltips.defaultToFirstItem); + checkNativeFilterTooltip(3, nativeFilterTooltips.searchAllFilterOptions); + checkNativeFilterTooltip(4, nativeFilterTooltips.inverseSelection); + clickOnAddFilterInModal(); + cy.contains('Values are dependent on other filters').should('exist'); }); }); - it('User can stop filtering when filter is removed', () => { - enterNativeFilterEditModal(); - addCountryNameFilter(); - inputNativeFilterDefaultValue(testItems.filterDefaultValue); - saveNativeFilterSettings(); - WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); - cy.get(dataTestChartName(testItems.topTenChart.name)).within(() => { - cy.contains(testItems.filterDefaultValue).should('be.visible'); - cy.contains(testItems.filterOtherCountry).should('not.exist'); - }); - cy.get(nativeFilters.filterItem) - .contains(testItems.filterDefaultValue) - .should('be.visible'); - validateFilterNameOnDashboard(testItems.topTenChart.filterColumn); - enterNativeFilterEditModal(); - deleteNativeFilter(); - saveNativeFilterSettings(); - WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); - cy.get(dataTestChartName(testItems.topTenChart.name)).within(() => { - cy.contains(testItems.filterDefaultValue).should('be.visible'); - cy.contains(testItems.filterOtherCountry).should('be.visible'); + describe('Nativefilters initial state not required', () => { + beforeEach(() => { + cy.createSampleDashboards(); }); - }); -}); -xdescribe('Nativefilters', () => { - before(() => { - cy.login(); - cy.visit(DASHBOARD_LIST); - cy.get('[data-test="new-dropdown"]').click(); - cy.get('[data-test="menu-item-Dashboard"]').click({ force: true }); - cy.get('[data-test="editable-title-input"]') - .click() - .clear() - .type(`${dashboard}{enter}`); - cy.get('[data-test="header-save-button"]').click(); - cy.visit(CHART_LIST); - cy.get('[data-test="search-input"]').type('Treemap{enter}'); - cy.get('[data-test="Treemap-list-chart-title"]') - .should('be.visible', { timeout: 5000 }) - .click(); - cy.get('[data-test="query-save-button"]').click(); - cy.get('[data-test="save-chart-modal-select-dashboard-form"]') - .find('input[aria-label="Select a dashboard"]') - .type(`${dashboard}`, { force: true }); - cy.get('[data-test="btn-modal-save"]').click(); - }); - beforeEach(() => { - cy.login(); - cy.visit(DASHBOARD_LIST); - cy.get('[data-test="search-input"]').click().type(`${dashboard}{enter}`); - cy.contains('[data-test="cell-text"]', `${dashboard}`).click(); - }); + it("User can check 'Filter has default value'", () => { + prepareDashboardFilters([ + { name: 'country_name', column: 'country_name', datasetId: 2 }, + ]); + enterNativeFilterEditModal(); + inputNativeFilterDefaultValue(testItems.filterDefaultValue); + }); - it('should show filter bar and allow user to create filters ', () => { - cy.get('[data-test="filter-bar"]').should('be.visible'); - cy.get('[data-test="filter-bar__expand-button"]').click(); - cy.get('[data-test="filter-bar__create-filter"]').click(); - cy.get('.ant-modal').should('be.visible'); - - cy.get('.ant-modal') - .find('[data-test="filters-config-modal__name-input"]') - .click() - .type('Country name'); - - cy.get('.ant-modal') - .find('[data-test="filters-config-modal__datasource-input"]') - .click() - .type('wb_health_population'); - - cy.get( - '.ant-modal [data-test="filters-config-modal__datasource-input"] .Select__menu', - ) - .contains('wb_health_population') - .click(); - - // hack for unclickable country_name - cy.get('.ant-modal').find('[data-test="field-input"]').type('country_name'); - cy.get('.ant-modal') - .find('[data-test="field-input"]') - .type('{downarrow}{downarrow}{enter}'); - cy.get('[data-test="apply-changes-instantly-checkbox"]').check(); - cy.get('.ant-modal-footer') - .find('[data-test="native-filter-modal-save-button"]') - .should('be.visible') - .click(); - }); + it('User can add a new native filter', () => { + prepareDashboardFilters([]); - it('should show newly added filter in filter bar menu', () => { - cy.get('[data-test="filter-bar"]').should('be.visible'); - cy.get('[data-test="filter-control-name"]').should('be.visible'); - cy.get('[data-test="form-item-value"]').should('be.visible'); - }); - it('should filter dashboard with selected filter value', () => { - cy.get('[data-test="form-item-value"]').should('be.visible').click(); - cy.get('.ant-select-selection-search').type('Hong Kong{enter}'); - cy.get('[data-test="filter-bar__apply-button"]').click(); - cy.get('.treemap').within(() => { - cy.contains('HKG').should('be.visible'); - cy.contains('USA').should('not.exist'); - }); - }); - xit('default value is respected after revisit', () => { - cy.get('[data-test="filter-bar__create-filter"]').click(); - cy.get('.ant-modal').should('be.visible'); - // TODO: replace with proper wait for filter to finish loading - cy.wait(1000); - cy.get('[data-test="default-input"]').click(); - cy.get('.ant-modal') - .find('[data-test="default-input"]') - .type('Sweden{enter}'); - cy.get('[data-test="native-filter-modal-save-button"]') - .should('be.visible') - .click(); - cy.visit(DASHBOARD_LIST); - cy.get('[data-test="search-input"]').click().type(`${dashboard}{enter}`); - cy.contains('[data-test="cell-text"]', `${dashboard}`).click(); - cy.get('.treemap').within(() => { - cy.contains('SWE').should('be.visible'); - cy.contains('USA').should('not.exist'); - }); - cy.contains('Sweden'); - }); - it('should allow for deleted filter restore', () => { - cy.get('[data-test="filter-bar__create-filter"]').click(); - cy.get('.ant-modal').should('be.visible'); - cy.get('.ant-tabs-nav-list').within(() => { - cy.get('.ant-tabs-tab-remove').click(); + let filterKey: string; + const removeFirstChar = (search: string) => + search.split('').slice(1, search.length).join(''); + cy.location().then(loc => { + const queryParams = qs.parse(removeFirstChar(loc.search)); + filterKey = queryParams.native_filters_key as string; + expect(typeof filterKey).eq('string'); + }); + enterNativeFilterEditModal(); + addCountryNameFilter(); + saveNativeFilterSettings([SAMPLE_CHART]); + cy.location().then(loc => { + const queryParams = qs.parse(removeFirstChar(loc.search)); + const newfilterKey = queryParams.native_filters_key; + expect(newfilterKey).eq(filterKey); + }); + cy.get(nativeFilters.modal.container).should('not.exist'); }); - cy.get('[data-test="undo-button"]').should('be.visible').click(); - cy.get('.ant-tabs-nav-list').within(() => { - cy.get('.ant-tabs-tab-remove').click(); + it('User can restore a deleted native filter', () => { + prepareDashboardFilters([ + { name: 'country_code', column: 'country_code', datasetId: 2 }, + ]); + enterNativeFilterEditModal(); + cy.get(nativeFilters.filtersList.removeIcon).first().click(); + cy.get('[data-test="restore-filter-button"]') + .should('be.visible') + .click(); + cy.get(nativeFilters.modal.container) + .find(nativeFilters.filtersPanel.filterName) + .should( + 'have.attr', + 'value', + testItems.topTenChart.filterColumnCountryCode, + ); }); - cy.get('[data-test="restore-filter-button"]').should('be.visible').click(); - }); - it('should stop filtering when filter is removed', () => { - cy.get('[data-test="filter-bar__create-filter"]').click(); - cy.get('.ant-modal').should('be.visible'); - cy.get('.ant-tabs-nav-list').within(() => { - cy.get('.ant-tabs-tab-remove').click(); - }); - cy.get('.ant-modal-footer') - .find('[data-test="native-filter-modal-save-button"]') - .should('be.visible') - .click(); - cy.get('.treemap').within(() => { - cy.contains('HKG').should('be.visible'); - cy.contains('USA').should('be.visible'); + it('User can create a time grain filter', () => { + prepareDashboardFilters([]); + enterNativeFilterEditModal(); + fillNativeFilterForm( + testItems.filterType.timeGrain, + testItems.filterType.timeGrain, + testItems.datasetForNativeFilter, + ); + saveNativeFilterSettings([SAMPLE_CHART]); + applyNativeFilterValueWithIndex(0, testItems.filterTimeGrain); + cy.get(nativeFilters.applyFilter).click(); + cy.url().then(u => { + const ur = new URL(u); + expect(ur.search).to.include('native_filters'); + }); + validateFilterNameOnDashboard(testItems.filterType.timeGrain); + validateFilterContentOnDashboard(testItems.filterTimeGrain); }); - }); - describe('Parent Filters', () => { - it('should allow for creating parent filters ', () => { - cy.get('[data-test="filter-bar"]').should('be.visible'); - cy.get('[data-test="filter-bar__expand-button"]').click(); - cy.get('[data-test="filter-bar__create-filter"]').click(); - cy.get('.ant-modal').should('be.visible'); - - cy.get('.ant-modal') - .find('[data-test="filters-config-modal__name-input"]') - .click() - .type('Country name'); - - cy.get('.ant-modal') - .find('[data-test="filters-config-modal__datasource-input"]') - .click() - .type('wb_health_population'); - cy.get( - '.ant-modal [data-test="filters-config-modal__datasource-input"] .Select__menu', - ) - .contains('wb_health_population') - .click(); - - // hack for unclickable country_name - cy.get('.ant-modal') - .find('[data-test="field-input"]') - .type('country_name'); - cy.get('.ant-modal') - .find('[data-test="field-input"]') - .type('{downarrow}{downarrow}{enter}'); - cy.get('[data-test="apply-changes-instantly-checkbox"]').check(); - cy.get('.ant-modal-footer') - .find('[data-test="native-filter-modal-save-button"]') + it.skip('User can create a time range filter', () => { + enterNativeFilterEditModal(); + fillNativeFilterForm( + testItems.filterType.timeRange, + testItems.filterType.timeRange, + ); + saveNativeFilterSettings(WORLD_HEALTH_CHARTS); + cy.get(dashboardView.salesDashboardSpecific.vehicleSalesFilterTimeRange) .should('be.visible') .click(); + applyAdvancedTimeRangeFilterOnDashboard('2005-12-17', '2006-12-17'); + cy.url().then(u => { + const ur = new URL(u); + expect(ur.search).to.include('native_filters'); + }); + validateFilterNameOnDashboard(testItems.filterType.timeRange); + cy.get(nativeFilters.filterFromDashboardView.timeRangeFilterContent) + .contains('2005-12-17') + .should('be.visible'); + }); - cy.get('[data-test="filter-bar__create-filter"]').click(); - cy.get('.ant-modal').first().should('be.visible'); - cy.get('[data-test=add-filter-button]').first().click(); - - cy.get('.ant-modal') - .find('[data-test="filters-config-modal__name-input"]') - .last() - .click() - .type('Region Name'); - - cy.get('.ant-modal') - .find('[data-test="filters-config-modal__datasource-input"]') - .last() - .click() - .type('wb_health_population'); + it.skip('User can create a time column filter', () => { + enterNativeFilterEditModal(); + fillNativeFilterForm( + testItems.filterType.timeColumn, + testItems.filterType.timeColumn, + testItems.datasetForNativeFilter, + ); + saveNativeFilterSettings(WORLD_HEALTH_CHARTS); + cy.intercept(`/api/v1/chart/data?form_data=**`).as('chart'); + cy.get(nativeFilters.modal.container).should('not.exist'); + // assert that native filter is created + validateFilterNameOnDashboard(testItems.filterType.timeColumn); + applyNativeFilterValueWithIndex( + 0, + testItems.topTenChart.filterColumnYear, + ); + cy.get(nativeFilters.applyFilter).click({ force: true }); + cy.wait('@chart'); + validateFilterContentOnDashboard(testItems.topTenChart.filterColumnYear); + }); - cy.get( - '.ant-modal [data-test="filters-config-modal__datasource-input"] .Select__menu', - ) - .last() - .contains('wb_health_population') - .click(); + it('User can create a numerical range filter', () => { + visitDashboard(); + enterNativeFilterEditModal(false); + fillNativeFilterForm( + testItems.filterType.numerical, + testItems.filterNumericalColumn, + testItems.datasetForNativeFilter, + testItems.filterNumericalColumn, + ); + saveNativeFilterSettings([]); + // assertions + cy.get(nativeFilters.slider.slider).should('be.visible').click('center'); + cy.get(nativeFilters.applyFilter).click(); + // assert that the url contains 'native_filters' in the url + cy.url().then(u => { + const ur = new URL(u); + expect(ur.search).to.include('native_filters'); + // assert that the start handle has a value + cy.get(nativeFilters.slider.startHandle) + .invoke('attr', 'aria-valuenow') + .should('exist'); + // assert that the end handle has a value + cy.get(nativeFilters.slider.endHandle) + .invoke('attr', 'aria-valuenow') + .should('exist'); + // assert slider text matches what we should have + cy.get(nativeFilters.slider.sliderText).should('have.text', '49'); + }); + }); - // hack for unclickable country_name - cy.get('.ant-modal') - .find('[data-test="field-input"]') - .last() - .type('region'); - cy.get('.ant-modal') - .find('[data-test="field-input"]') - .last() - .type('{downarrow}{downarrow}{downarrow}{downarrow}{enter}'); + it('User can undo deleting a native filter', () => { + prepareDashboardFilters([ + { name: 'country_name', column: 'country_name', datasetId: 2 }, + ]); + enterNativeFilterEditModal(); + undoDeleteNativeFilter(); + cy.get(nativeFilters.modal.container) + .find(nativeFilters.filtersPanel.filterName) + .should('have.attr', 'value', testItems.topTenChart.filterColumn); + }); - cy.get('[data-test="apply-changes-instantly-checkbox"]').last().check(); - cy.get('.ant-modal') - .find('[data-test="parent-filter-input"]') - .last() - .type('{downarrow}{enter}'); + it('User can cancel changes in native filter', () => { + visitDashboard(); + enterNativeFilterEditModal(false); + fillNativeFilterForm( + testItems.filterType.value, + 'suffix', + testItems.datasetForNativeFilter, + ); + cancelNativeFilterSettings(); + enterNativeFilterEditModal(); + cy.get(nativeFilters.filtersList.removeIcon).first().click(); + cy.contains('You have removed this filter.').should('be.visible'); + }); - cy.get('.ant-modal-footer') - .find('[data-test="native-filter-modal-save-button"]') - .first() - .should('be.visible') - .click(); - cy.get('[data-test="filter-icon"]').should('be.visible'); + it('User can create a value filter', () => { + visitDashboard(); + enterNativeFilterEditModal(false); + addCountryNameFilter(); + cy.get(nativeFilters.filtersPanel.filterTypeInput) + .find(nativeFilters.filtersPanel.filterTypeItem) + .should('have.text', testItems.filterType.value); + saveNativeFilterSettings([]); + validateFilterNameOnDashboard(testItems.topTenChart.filterColumn); }); - xit('should parent filter be working', () => { - cy.get('.treemap').within(() => { - cy.contains('SMR').should('be.visible'); - cy.contains('Europe & Central Asia').should('be.visible'); - cy.contains('South Asia').should('be.visible'); - }); - cy.get('[data-test="form-item-value"]').should('be.visible').click(); - cy.get('.ant-popover-inner-content').within(() => { - cy.get('[data-test="form-item-value"]') - .should('be.visible') - .first() - .type('San Marino{enter}'); - cy.get('[data-test="form-item-value"]') - .should('be.visible') - .last() - .type('Europe & Central Asia{enter}'); - }); - cy.get('.treemap').within(() => { - cy.contains('SMR').should('be.visible'); - cy.contains('Europe & Central Asia').should('be.visible'); - cy.contains('South Asia').should('not.exist'); + it('User can apply value filter with selected values', () => { + prepareDashboardFilters([ + { name: 'country_name', column: 'country_name', datasetId: 2 }, + ]); + applyNativeFilterValueWithIndex(0, testItems.filterDefaultValue); + cy.get(nativeFilters.applyFilter).click(); + cy.get(dataTestChartName(testItems.topTenChart.name)).within(() => { + cy.contains(testItems.filterDefaultValue).should('be.visible'); + cy.contains(testItems.filterOtherCountry).should('not.exist'); }); }); - it('should stop filtering when parent filter is removed', () => { - cy.get('[data-test="filter-bar__create-filter"]').click(); - cy.get('.ant-modal').should('be.visible'); - cy.get('.ant-tabs-nav-list').within(() => { - cy.get('.ant-tabs-tab-remove').click({ multiple: true }); + it('User can stop filtering when filter is removed', () => { + prepareDashboardFilters([ + { name: 'country_name', column: 'country_name', datasetId: 2 }, + ]); + enterNativeFilterEditModal(); + inputNativeFilterDefaultValue(testItems.filterDefaultValue); + saveNativeFilterSettings([SAMPLE_CHART]); + cy.get(dataTestChartName(testItems.topTenChart.name)).within(() => { + cy.contains(testItems.filterDefaultValue).should('be.visible'); + cy.contains(testItems.filterOtherCountry).should('not.exist'); }); - cy.get('.ant-modal-footer') - .find('[data-test="native-filter-modal-save-button"]') - .should('be.visible') - .click(); - cy.get('.treemap').within(() => { - cy.contains('HKG').should('be.visible'); - cy.contains('USA').should('be.visible'); + cy.get(nativeFilters.filterItem) + .contains(testItems.filterDefaultValue) + .should('be.visible'); + validateFilterNameOnDashboard(testItems.topTenChart.filterColumn); + enterNativeFilterEditModal(); + deleteNativeFilter(); + saveNativeFilterSettings([SAMPLE_CHART]); + cy.get(dataTestChartName(testItems.topTenChart.name)).within(() => { + cy.contains(testItems.filterDefaultValue).should('be.visible'); + cy.contains(testItems.filterOtherCountry).should('be.visible'); }); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js b/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js deleted file mode 100644 index 419346cf98ebb..0000000000000 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/save.test.js +++ /dev/null @@ -1,163 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF 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 shortid from 'shortid'; -import { - waitForChartLoad, - WORLD_HEALTH_CHARTS, - WORLD_HEALTH_DASHBOARD, -} from './dashboard.helper'; - -function openDashboardEditProperties() { - // open dashboard properties edit modal - cy.get('.header-with-actions [aria-label="Edit dashboard"]') - .should('be.visible') - .click(); - cy.get( - '.header-with-actions .right-button-panel .ant-dropdown-trigger', - ).trigger('click', { - force: true, - }); - cy.get('[data-test=header-actions-menu]') - .contains('Edit properties') - .click({ force: true }); -} - -describe('Dashboard save action', () => { - beforeEach(() => { - cy.login(); - cy.visit(WORLD_HEALTH_DASHBOARD); - cy.get('#app').then(() => { - cy.get('.dashboard-header-container').then(headerContainerElement => { - const dashboardId = headerContainerElement.attr('data-test-id'); - - cy.intercept('POST', `/superset/copy_dash/${dashboardId}/`).as( - 'copyRequest', - ); - - cy.get('[aria-label="more-horiz"]').trigger('click', { force: true }); - cy.get('[data-test="save-as-menu-item"]').trigger('click', { - force: true, - }); - cy.get('[data-test="modal-save-dashboard-button"]').trigger('click', { - force: true, - }); - }); - }); - }); - - // change to what the title should be - it('should save as new dashboard', () => { - cy.wait('@copyRequest').then(() => { - cy.get('[data-test="editable-title"]').then(element => { - const dashboardTitle = element.attr('title'); - expect(dashboardTitle).to.not.equal(`World Bank's Data`); - }); - }); - }); - - it('should save/overwrite dashboard', () => { - // should load chart - WORLD_HEALTH_CHARTS.forEach(waitForChartLoad); - - // remove box_plot chart from dashboard - cy.get('[aria-label="Edit dashboard"]').click({ timeout: 5000 }); - cy.get('[data-test="dashboard-delete-component-button"]') - .last() - .trigger('mouseenter') - .click(); - - cy.get('[data-test="grid-container"]') - .find('.box_plot') - .should('not.exist'); - - cy.intercept('PUT', '/api/v1/dashboard/**').as('putDashboardRequest'); - cy.get('.header-with-actions') - .find('[data-test="header-save-button"]') - .contains('Save') - .click(); - - // go back to view mode - cy.wait('@putDashboardRequest'); - cy.get('.header-with-actions') - .find('[aria-label="Edit dashboard"]') - .click(); - - // deleted boxplot should still not exist - cy.get('[data-test="grid-container"]') - .find('.box_plot', { timeout: 20000 }) - .should('not.exist'); - }); - - it('should save after edit', () => { - cy.get('.dashboard-grid', { timeout: 50000 }) // wait for 50 secs to load dashboard - .then(() => { - const dashboardTitle = `Test dashboard [${shortid.generate()}]`; - - openDashboardEditProperties(); - - // open color scheme dropdown - cy.get('.ant-modal-body') - .contains('Color scheme') - .parents('.ControlHeader') - .next('.ant-select') - .click() - .then(() => { - // select a new color scheme - cy.get('.ant-modal-body') - .find('.ant-select-item-option-active') - .first() - .click(); - }); - - // remove json metadata - cy.get('.ant-modal-body') - .contains('Advanced') - .click() - .then(() => { - cy.get('#json_metadata').type('{selectall}{backspace}'); - }); - - // update title - cy.get('[data-test="dashboard-title-input"]').type( - `{selectall}{backspace}${dashboardTitle}`, - ); - - // save edit changes - cy.get('.ant-modal-footer') - .contains('Apply') - .click() - .then(() => { - // assert that modal edit window has closed - cy.get('.ant-modal-body').should('not.exist'); - - // save dashboard changes - cy.get('.header-with-actions').contains('Save').click(); - - // assert success flash - cy.contains('saved successfully').should('be.visible'); - - // assert title has been updated - cy.get( - '.header-with-actions .title-panel [data-test="editable-title"]', - ).should('have.text', dashboardTitle); - }); - }); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.ts index 4dbb8c712bccf..6c8c559df18df 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/tabs.test.ts @@ -16,42 +16,48 @@ * specific language governing permissions and limitations * under the License. */ -import { parsePostForm } from 'cypress/utils'; import { - TABBED_DASHBOARD, + parsePostForm, waitForChartLoad, getChartAliasBySpec, -} from './dashboard.helper'; +} from 'cypress/utils'; +import { TABBED_DASHBOARD } from 'cypress/utils/urls'; const TREEMAP = { name: 'Treemap', viz: 'treemap' }; const FILTER_BOX = { name: 'Region Filter', viz: 'filter_box' }; const LINE_CHART = { name: 'Growth Rate', viz: 'line' }; const BOX_PLOT = { name: 'Box plot', viz: 'box_plot' }; +const BIG_NUMBER = { name: 'Number of Girls', viz: 'big_number_total' }; +const TABLE = { name: 'Names Sorted by Num in California', viz: 'table' }; + +function topLevelTabs() { + cy.getBySel('dashboard-component-tabs') + .first() + .find('[data-test="nav-list"] .ant-tabs-nav-list > .ant-tabs-tab') + .as('top-level-tabs'); +} + +function resetTabs() { + topLevelTabs(); + cy.get('@top-level-tabs').first().click(); + waitForChartLoad(FILTER_BOX); + waitForChartLoad(TREEMAP); + waitForChartLoad(BIG_NUMBER); + waitForChartLoad(TABLE); +} describe('Dashboard tabs', () => { - // cypress can not handle window.scrollTo - // https://github.com/cypress-io/cypress/issues/2761 - // add this exception handler to pass test - const handleException = () => { - // return false to prevent the error from - // failing this test - cy.on('uncaught:exception', () => false); - }; - - beforeEach(() => { - cy.login(); - + before(() => { cy.visit(TABBED_DASHBOARD); }); - it('should switch active tab on click', () => { - waitForChartLoad(FILTER_BOX); - waitForChartLoad(TREEMAP); + beforeEach(() => { + cy.preserveLogin(); + resetTabs(); + }); - cy.get('[data-test="dashboard-component-tabs"]') - .first() - .find('[data-test="nav-list"] .ant-tabs-nav-list > .ant-tabs-tab') - .as('top-level-tabs'); + it('should switch tabs', () => { + topLevelTabs(); cy.get('@top-level-tabs') .first() @@ -61,6 +67,9 @@ describe('Dashboard tabs', () => { .last() .should('not.have.class', 'ant-tabs-tab-active'); + cy.getBySel('grid-container').find('.box_plot').should('not.exist'); + cy.getBySel('grid-container').find('.line').should('not.exist'); + cy.get('@top-level-tabs') .last() .click() @@ -68,42 +77,23 @@ describe('Dashboard tabs', () => { cy.get('@top-level-tabs') .first() .should('not.have.class', 'ant-tabs-tab-active'); - }); + waitForChartLoad(BOX_PLOT); + cy.getBySel('grid-container').find('.box_plot').should('be.visible'); - it('should load charts when tab is visible', () => { - // landing in first tab, should see 2 charts - waitForChartLoad(FILTER_BOX); - waitForChartLoad(TREEMAP); - cy.get('[data-test="grid-container"]') - .find('.box_plot') - .should('not.exist'); - cy.get('[data-test="grid-container"]').find('.line').should('not.exist'); + resetTabs(); // click row level tab, see 1 more chart - cy.get('[data-test="dashboard-component-tabs"]') - .last() + cy.getBySel('dashboard-component-tabs') + .eq(2) .find('[data-test="nav-list"] .ant-tabs-nav-list > .ant-tabs-tab') .as('row-level-tabs'); cy.get('@row-level-tabs').last().click(); - waitForChartLoad(LINE_CHART); - cy.get('[data-test="grid-container"]').find('.line').should('be.visible'); - - // click top level tab, see 1 more chart - handleException(); - cy.get('[data-test="dashboard-component-tabs"]') - .first() - .find('[data-test="nav-list"] .ant-tabs-nav-list > .ant-tabs-tab') - .as('top-level-tabs'); - - cy.get('@top-level-tabs').last().click(); - - // should exist a visible box_plot element - cy.get('[data-test="grid-container"]').find('.box_plot'); + cy.getBySel('grid-container').find('.line').should('be.visible'); }); - xit('should send new queries when tab becomes visible', () => { + it.skip('should send new queries when tab becomes visible', () => { // landing in first tab waitForChartLoad(FILTER_BOX); waitForChartLoad(TREEMAP); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilter.helper.ts b/superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts similarity index 79% rename from superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilter.helper.ts rename to superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts index 5f43b330ceeac..f633cae9b2448 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard/nativeFilter.helper.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard/utils.ts @@ -16,8 +16,63 @@ * specific language governing permissions and limitations * under the License. */ + import { dashboardView, nativeFilters } from 'cypress/support/directories'; -import { testItems } from './dashboard.helper'; +import { ChartSpec, waitForChartLoad } from 'cypress/utils'; + +export const WORLD_HEALTH_CHARTS = [ + { name: '% Rural', viz: 'world_map' }, + { name: 'Most Populated Countries', viz: 'table' }, + { name: 'Region Filter', viz: 'filter_box' }, + { name: "World's Population", viz: 'big_number' }, + { name: 'Growth Rate', viz: 'line' }, + { name: 'Rural Breakdown', viz: 'sunburst' }, + { name: "World's Pop Growth", viz: 'area' }, + { name: 'Life Expectancy VS Rural %', viz: 'bubble' }, + { name: 'Treemap', viz: 'treemap' }, + { name: 'Box plot', viz: 'box_plot' }, +] as ChartSpec[]; + +export const ECHARTS_CHARTS = [ + { name: 'Number of Girls', viz: 'big_number_total' }, + { name: 'Participants', viz: 'big_number' }, + { name: 'Box plot', viz: 'box_plot' }, + { name: 'Genders', viz: 'pie' }, + { name: 'Energy Force Layout', viz: 'graph_chart' }, +] as ChartSpec[]; + +export const testItems = { + dashboard: 'Cypress test Dashboard', + dataset: 'Vehicle Sales', + datasetForNativeFilter: 'wb_health_population', + chart: 'Cypress chart', + newChart: 'New Cypress Chart', + createdDashboard: 'New Dashboard', + defaultNameDashboard: '[ untitled dashboard ]', + newDashboardTitle: `Test dashboard [NEW TEST]`, + bulkFirstNameDashboard: 'First Dash', + bulkSecondNameDashboard: 'Second Dash', + worldBanksDataCopy: `World Bank's Data [copy]`, + filterType: { + value: 'Value', + numerical: 'Numerical range', + timeColumn: 'Time column', + timeGrain: 'Time grain', + timeRange: 'Time range', + }, + topTenChart: { + name: 'Most Populated Countries', + filterColumn: 'country_name', + filterColumnYear: 'year', + filterColumnRegion: 'region', + filterColumnCountryCode: 'country_code', + }, + filterDefaultValue: 'United States', + filterOtherCountry: 'China', + filterTimeGrain: 'Month', + filterTimeColumn: 'created', + filterNumericalColumn: 'SP_RUR_TOTL_ZS', +}; export const nativeFilterTooltips = { searchAllFilterOptions: @@ -53,6 +108,63 @@ export const valueNativeFilterOptions = [ 'Filter value is required', ]; +export function interceptGet() { + cy.intercept('/api/v1/dashboard/*').as('get'); +} + +export function interceptFiltering() { + cy.intercept('GET', `/api/v1/dashboard/?q=*`).as('filtering'); +} + +export function interceptBulkDelete() { + cy.intercept('DELETE', `/api/v1/dashboard/?q=*`).as('bulkDelete'); +} + +export function interceptDelete() { + cy.intercept('DELETE', `/api/v1/dashboard/*`).as('delete'); +} + +export function interceptUpdate() { + cy.intercept('PUT', `/api/v1/dashboard/*`).as('update'); +} + +export function interceptPost() { + cy.intercept('POST', `/api/v1/dashboard/`).as('post'); +} + +export function interceptLog() { + cy.intercept('/superset/log/?explode=events&dashboard_id=*').as('logs'); +} + +export function interceptFav() { + cy.intercept(`/superset/favstar/Dashboard/*/select/`).as('select'); +} + +export function interceptUnfav() { + cy.intercept(`/superset/favstar/Dashboard/*/unselect/`).as('unselect'); +} + +export function interceptDataset() { + cy.intercept('GET', `/api/v1/dataset/*`).as('getDataset'); +} + +export function interceptCharts() { + cy.intercept('GET', `/api/v1/dashboard/*/charts`).as('getCharts'); +} + +export function interceptDatasets() { + cy.intercept('GET', `/api/v1/dashboard/*/datasets`).as('getDatasets'); +} + +export function setFilter(filter: string, option: string) { + interceptFiltering(); + + cy.get(`[aria-label="${filter}"]`).first().click(); + cy.get(`[aria-label="${filter}"] [title="${option}"]`).click(); + + cy.wait('@filtering'); +} + /** ************************************************************************ * Expend Native filter from the left panel on dashboard * @returns {None} @@ -84,11 +196,15 @@ export function collapseFilterOnLeftPanel() { * @returns {None} * @summary helper for enter native filter edit modal ************************************************************************* */ -export function enterNativeFilterEditModal() { +export function enterNativeFilterEditModal(waitForDataset = true) { + interceptDataset(); cy.get(nativeFilters.filterFromDashboardView.createFilterButton).click({ force: true, }); cy.get(nativeFilters.modal.container).should('be.visible'); + if (waitForDataset) { + cy.wait('@getDataset'); + } } /** ************************************************************************ @@ -208,12 +324,13 @@ export function addParentFilterWithValue(index: number, value: string) { * @returns {None} * @summary helper for save native filters settings ************************************************************************* */ -export function saveNativeFilterSettings() { +export function saveNativeFilterSettings(charts: ChartSpec[]) { cy.get(nativeFilters.modal.footer) .contains('Save') .should('be.visible') .click(); cy.get(nativeFilters.modal.container).should('not.exist'); + charts.forEach(waitForChartLoad); } /** ************************************************************************ @@ -232,34 +349,10 @@ export function cancelNativeFilterSettings() { cy.get(nativeFilters.modal.footer) .find(nativeFilters.modal.yesCancelButton) .contains('cancel') - .should('be.visible') - .click(); + .click({ force: true }); cy.get(nativeFilters.modal.container).should('not.exist'); } -/** ************************************************************************ - * Close dashboard toast message - * @returns {None} - * @summary helper for close dashboard toast message in order to make test stable - ************************************************************************* */ -export function closeDashboardToastMessage() { - cy.get('body').then($body => { - if ($body.find(dashboardView.dashboardAlert.modal).length > 0) { - // evaluates as true if button exists at all - cy.get(dashboardView.dashboardAlert.modal).then($header => { - if ($header.is(':visible')) { - cy.get(dashboardView.dashboardAlert.closeButton).click({ - force: true, - }); - cy.get(dashboardView.dashboardAlert.closeButton).should('not.exist', { - timeout: 10000, - }); - } - }); - } - }); -} - /** ************************************************************************ * Validate filter name on dashboard * @param name: filter name to validate @@ -383,31 +476,3 @@ export function addCountryNameFilter() { testItems.topTenChart.filterColumn, ); } - -/** ************************************************************************ - * add filter for test column 'Region' - * @return {null} - * @summary helper for add filter for test column 'Region' - ************************************************************************* */ -export function addRegionFilter() { - fillNativeFilterForm( - testItems.filterType.value, - testItems.topTenChart.filterColumnRegion, - testItems.datasetForNativeFilter, - testItems.topTenChart.filterColumnRegion, - ); -} - -/** ************************************************************************ - * add filter for test column 'Country Code' - * @return {null} - * @summary helper for add filter for test column 'Country Code' - ************************************************************************* */ -export function addCountryCodeFilter() { - fillNativeFilterForm( - testItems.filterType.value, - testItems.topTenChart.filterColumnCountryCode, - testItems.datasetForNativeFilter, - testItems.topTenChart.filterColumnCountryCode, - ); -} diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/card_view.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/card_view.test.ts deleted file mode 100644 index 8bfc35d71c846..0000000000000 --- a/superset-frontend/cypress-base/cypress/integration/dashboard_list/card_view.test.ts +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF 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 { DASHBOARD_LIST } from './dashboard_list.helper'; - -describe('Dashboard card view', () => { - beforeEach(() => { - cy.login(); - cy.visit(DASHBOARD_LIST); - cy.get('[aria-label="card-view"]').click(); - }); - - xit('should load cards', () => { - cy.get('[data-test="dashboard-list-view"]'); - cy.get('[data-test="styled-card"]').should('be.visible'); - cy.get('[data-test="styled-card"]').should('have.length', 4); // failed, xit-ed - }); - - it('should allow to favorite/unfavorite dashboard card', () => { - cy.get("[data-test='card-actions']") - .first() - .find("[aria-label='favorite-selected']") - .should('not.exist'); - cy.get("[data-test='card-actions']") - .find("[aria-label='favorite-unselected']") - .first() - .click(); - cy.get("[data-test='card-actions']") - .first() - .find("[aria-label='favorite-selected']") - .should('be.visible'); - cy.get("[data-test='card-actions']") - .first() - .find("[aria-label='favorite-unselected']") - .should('not.exist'); - - cy.get("[data-test='card-actions']") - .first() - .find("[aria-label='favorite-unselected']") - .should('not.exist'); - cy.get("[data-test='card-actions']") - .first() - .find("[aria-label='favorite-selected']") - .click(); - cy.get("[data-test='card-actions']") - .first() - .find("[aria-label='favorite-unselected']") - .should('be.visible'); - cy.get("[data-test='card-actions']") - .first() - .find("[aria-label='favorite-selected']") - .should('not.exist'); - }); - - xit('should sort correctly', () => { - // sort alphabetical - cy.get('.Select__control').last().should('be.visible'); - cy.get('.Select__control').last().click({ force: true }); - cy.get('.Select__menu').contains('Alphabetical').click(); - cy.get('[data-test="dashboard-list-view"]').should('be.visible'); - // TODO this line was flaky - cy.get('[data-test="styled-card"]').first().contains('Tabbed Dashboard'); - cy.get('[data-test="styled-card"]').last().contains("World Bank's Data"); - - // sort recently modified - cy.get('.Select__control').last().should('be.visible'); - cy.get('.Select__control').last().click({ force: true }); - cy.get('.Select__menu').contains('Recently Modified').click(); - cy.get('[data-test="dashboard-list-view"]').should('be.visible'); - cy.get('[data-test="styled-card"]').first().contains('Tabbed Dashboard'); - cy.get('[data-test="styled-card"]').last().contains("World Bank's Data"); - }); - - // real flaky - xit('should delete correctly', () => { - // show delete modal - cy.get('[data-test="more-horiz"]').last().trigger('mouseover'); - cy.get('[data-test="dashboard-card-option-delete-button"]') - .last() - .should('be.visible') - .click(); - cy.get('[data-test="modal-confirm-button"]').should( - 'have.attr', - 'disabled', - ); - cy.get('[data-test="Please Confirm-modal"]').should('be.visible'); - cy.get("[data-test='delete-modal-input']").type('DELETE'); - cy.get('[data-test="modal-confirm-button"]').should( - 'not.have.attr', - 'disabled', - ); - cy.get('[data-test="modal-cancel-button"]').click(); - }); - - // real flaky - xit('should edit correctly', () => { - // show edit modal - cy.get('[data-test="more-horiz"]').last().trigger('mouseover'); - cy.get('[data-test="dashboard-card-option-edit-button"]') - .last() - .should('be.visible') - .click(); - cy.get('[data-test="dashboard-edit-properties-form"]').should('be.visible'); - cy.get('[data-test="dashboard-title-input"]').should('not.have.value'); - cy.get('[data-test="properties-modal-cancel-button"]') - .contains('Cancel') - .click(); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/dashboardlist.applitools.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/dashboardlist.applitools.test.ts index dd95eefdb2ff2..5f457ff49ae42 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard_list/dashboardlist.applitools.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard_list/dashboardlist.applitools.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { DASHBOARD_LIST } from './dashboard_list.helper'; +import { DASHBOARD_LIST } from 'cypress/utils/urls'; describe('dashboard list view', () => { beforeEach(() => { @@ -33,7 +33,7 @@ describe('dashboard list view', () => { cy.eyesOpen({ testName: 'Dashboards list-view', }); - cy.eyesCheckWindow('Dashboards loaded'); + cy.eyesCheckWindow('Dashboards list-view loaded'); }); it('should load the Dashboards card list', () => { @@ -41,6 +41,6 @@ describe('dashboard list view', () => { cy.eyesOpen({ testName: 'Dashboards card-view', }); - cy.eyesCheckWindow('Dashboards loaded'); + cy.eyesCheckWindow('Dashboards card-view loaded'); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts index bf852fc62558f..2949267b4306d 100644 --- a/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dashboard_list/filter.test.ts @@ -16,86 +16,71 @@ * specific language governing permissions and limitations * under the License. */ -import { DASHBOARD_LIST } from './dashboard_list.helper'; +import { DASHBOARD_LIST } from 'cypress/utils/urls'; +import { setGridMode, clearAllInputs } from 'cypress/utils'; +import { setFilter } from '../dashboard/utils'; -describe('dashboard filters card view', () => { - beforeEach(() => { - cy.login(); +describe('Dashboards filters', () => { + before(() => { cy.visit(DASHBOARD_LIST); - cy.get('[aria-label="card-view"]').click(); }); - it('should filter by owners correctly', () => { - // filter by owners - cy.get('[data-test="filters-select"]').first().click(); - cy.get('.rc-virtual-list').contains('alpha user').click(); - cy.get('[data-test="styled-card"]').should('not.exist'); - cy.get('[data-test="filters-select"]').first().click(); - cy.get('.rc-virtual-list').contains('gamma user').click(); - cy.get('[data-test="styled-card"]').should('not.exist'); + beforeEach(() => { + cy.preserveLogin(); + clearAllInputs(); }); - it('should filter by created by correctly', () => { - // filter by created by - cy.get('[data-test="filters-select"]').eq(1).click(); - cy.get('.rc-virtual-list').contains('alpha user').click(); - cy.get('.ant-card').should('not.exist'); - cy.get('[data-test="filters-select"]').eq(1).click(); - cy.get('.rc-virtual-list').contains('gamma user').click(); - cy.get('.ant-card').should('not.exist'); - }); + describe('card-view', () => { + before(() => { + setGridMode('card'); + }); - it('should filter by published correctly', () => { - // filter by published - cy.get('[data-test="filters-select"]').eq(2).click(); - cy.get('.rc-virtual-list').contains('Published').click({ timeout: 5000 }); - cy.get('[data-test="styled-card"]').should('have.length', 3); - cy.get('[data-test="styled-card"]') - .contains('USA Births Names') - .should('be.visible'); - cy.get('[data-test="filters-select"]').eq(1).click(); - cy.get('[data-test="filters-select"]').eq(1).type('unpub{enter}'); - cy.get('[data-test="styled-card"]').should('have.length', 3); - }); -}); + it('should filter by owners correctly', () => { + setFilter('Owner', 'alpha user'); + cy.getBySel('styled-card').should('not.exist'); + setFilter('Owner', 'admin user'); + cy.getBySel('styled-card').should('exist'); + }); -describe('dashboard filters list view', () => { - beforeEach(() => { - cy.login(); - cy.visit(DASHBOARD_LIST); - cy.get('[aria-label="list-view"]').click(); - }); + it('should filter by created by correctly', () => { + setFilter('Created by', 'alpha user'); + cy.getBySel('styled-card').should('not.exist'); + setFilter('Created by', 'admin user'); + cy.getBySel('styled-card').should('exist'); + }); - it('should filter by owners correctly', () => { - // filter by owners - cy.get('[data-test="filters-select"]').first().click(); - cy.get('.rc-virtual-list').contains('alpha user').click(); - cy.get('[data-test="table-row"]').should('not.exist'); - cy.get('[data-test="filters-select"]').first().click(); - cy.get('.rc-virtual-list').contains('gamma user').click(); - cy.get('[data-test="table-row"]').should('not.exist'); + it('should filter by published correctly', () => { + setFilter('Status', 'Published'); + cy.getBySel('styled-card').should('have.length', 3); + setFilter('Status', 'Draft'); + cy.getBySel('styled-card').should('have.length', 2); + }); }); - it('should filter by created by correctly', () => { - // filter by created by - cy.get('[data-test="filters-select"]').eq(1).click(); - cy.get('.rc-virtual-list').contains('alpha user').click(); - cy.get('[data-test="table-row"]').should('not.exist'); - cy.get('[data-test="filters-select"]').eq(1).click(); - cy.get('.rc-virtual-list').contains('gamma user').click(); - cy.get('[data-test="table-row"]').should('not.exist'); - }); + describe('list-view', () => { + before(() => { + setGridMode('list'); + }); + + it('should filter by created by correctly', () => { + setFilter('Owner', 'alpha user'); + cy.getBySel('table-row').should('not.exist'); + setFilter('Owner', 'admin user'); + cy.getBySel('table-row').should('exist'); + }); + + it('should filter by created by correctly', () => { + setFilter('Created by', 'alpha user'); + cy.getBySel('table-row').should('not.exist'); + setFilter('Created by', 'admin user'); + cy.getBySel('table-row').should('exist'); + }); - it('should filter by published correctly', () => { - // filter by published - cy.get('[data-test="filters-select"]').eq(2).click(); - cy.get('.rc-virtual-list').contains('Published').click(); - cy.get('[data-test="table-row"]').should('have.length', 3); - cy.get('[data-test="table-row"]') - .contains('USA Births Names') - .should('be.visible'); - cy.get('[data-test="filters-select"]').eq(2).click(); - cy.get('[data-test="filters-select"]').eq(2).type('unpub{enter}'); - cy.get('[data-test="table-row"]').should('have.length', 3); + it('should filter by published correctly', () => { + setFilter('Status', 'Published'); + cy.getBySel('table-row').should('have.length', 3); + setFilter('Status', 'Draft'); + cy.getBySel('table-row').should('have.length', 2); + }); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/list.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/list.test.ts new file mode 100644 index 0000000000000..28caab8753ccb --- /dev/null +++ b/superset-frontend/cypress-base/cypress/integration/dashboard_list/list.test.ts @@ -0,0 +1,248 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 { DASHBOARD_LIST } from 'cypress/utils/urls'; +import { setGridMode, toggleBulkSelect } from 'cypress/utils'; +import { + setFilter, + interceptBulkDelete, + interceptUpdate, + interceptDelete, + interceptFav, + interceptUnfav, +} from '../dashboard/utils'; + +function orderAlphabetical() { + setFilter('Sort', 'Alphabetical'); +} + +function openProperties() { + cy.get('[aria-label="more-vert"]').first().click(); + cy.getBySel('dashboard-card-option-edit-button').click(); +} + +function openMenu() { + cy.get('[aria-label="more-vert"]').first().click(); +} + +function confirmDelete() { + cy.getBySel('delete-modal-input').type('DELETE'); + cy.getBySel('modal-confirm-button').click(); +} + +describe('Dashboards list', () => { + beforeEach(() => { + cy.preserveLogin(); + }); + + describe('list mode', () => { + before(() => { + cy.visit(DASHBOARD_LIST); + setGridMode('list'); + }); + + it('should load rows in list mode', () => { + cy.getBySel('listview-table').should('be.visible'); + cy.getBySel('sort-header').eq(1).contains('Title'); + cy.getBySel('sort-header').eq(2).contains('Modified by'); + cy.getBySel('sort-header').eq(3).contains('Status'); + cy.getBySel('sort-header').eq(4).contains('Modified'); + cy.getBySel('sort-header').eq(5).contains('Created by'); + cy.getBySel('sort-header').eq(6).contains('Owners'); + cy.getBySel('sort-header').eq(7).contains('Actions'); + }); + + it('should sort correctly in list mode', () => { + cy.getBySel('sort-header').eq(1).click(); + cy.getBySel('table-row').first().contains('ECharts Dashboard'); + cy.getBySel('sort-header').eq(1).click(); + cy.getBySel('table-row').first().contains("World Bank's Data"); + cy.getBySel('sort-header').eq(1).click(); + }); + + it('should bulk select in list mode', () => { + toggleBulkSelect(); + cy.get('#header-toggle-all').click(); + cy.get('[aria-label="checkbox-on"]').should('have.length', 6); + cy.getBySel('bulk-select-copy').contains('5 Selected'); + cy.getBySel('bulk-select-action') + .should('have.length', 2) + .then($btns => { + expect($btns).to.contain('Delete'); + expect($btns).to.contain('Export'); + }); + cy.getBySel('bulk-select-deselect-all').click(); + cy.get('[aria-label="checkbox-on"]').should('have.length', 0); + cy.getBySel('bulk-select-copy').contains('0 Selected'); + cy.getBySel('bulk-select-action').should('not.exist'); + }); + }); + + describe('card mode', () => { + before(() => { + cy.visit(DASHBOARD_LIST); + setGridMode('card'); + }); + + it('should load rows in card mode', () => { + cy.getBySel('listview-table').should('not.exist'); + cy.getBySel('styled-card').should('have.length', 5); + }); + + it('should bulk select in card mode', () => { + toggleBulkSelect(); + cy.getBySel('styled-card').click({ multiple: true }); + cy.getBySel('bulk-select-copy').contains('5 Selected'); + cy.getBySel('bulk-select-action') + .should('have.length', 2) + .then($btns => { + expect($btns).to.contain('Delete'); + expect($btns).to.contain('Export'); + }); + cy.getBySel('bulk-select-deselect-all').click(); + cy.getBySel('bulk-select-copy').contains('0 Selected'); + cy.getBySel('bulk-select-action').should('not.exist'); + }); + + it('should sort in card mode', () => { + orderAlphabetical(); + cy.getBySel('styled-card').first().contains('ECharts Dashboard'); + }); + }); + + describe('common actions', () => { + beforeEach(() => { + cy.createSampleDashboards(); + cy.visit(DASHBOARD_LIST); + }); + + it('should allow to favorite/unfavorite dashboard', () => { + interceptFav(); + interceptUnfav(); + + setGridMode('card'); + orderAlphabetical(); + + cy.getBySel('styled-card').first().contains('1 - Sample dashboard'); + cy.getBySel('styled-card') + .first() + .find("[aria-label='favorite-unselected']") + .click(); + cy.wait('@select'); + cy.getBySel('styled-card') + .first() + .find("[aria-label='favorite-selected']") + .click(); + cy.wait('@unselect'); + cy.getBySel('styled-card') + .first() + .find("[aria-label='favorite-selected']") + .should('not.exist'); + }); + + it('should bulk delete correctly', () => { + interceptBulkDelete(); + toggleBulkSelect(); + + // bulk deletes in card-view + setGridMode('card'); + orderAlphabetical(); + + cy.getBySel('styled-card').eq(0).contains('1 - Sample dashboard').click(); + cy.getBySel('styled-card').eq(1).contains('2 - Sample dashboard').click(); + cy.getBySel('bulk-select-action').eq(0).contains('Delete').click(); + confirmDelete(); + cy.wait('@bulkDelete'); + cy.getBySel('styled-card') + .eq(0) + .should('not.contain', '1 - Sample dashboard'); + cy.getBySel('styled-card') + .eq(1) + .should('not.contain', '2 - Sample dashboard'); + + // bulk deletes in list-view + setGridMode('list'); + cy.getBySel('table-row').eq(0).contains('3 - Sample dashboard'); + cy.getBySel('table-row').eq(1).contains('4 - Sample dashboard'); + cy.get('[data-test="table-row"] input[type="checkbox"]').eq(0).click(); + cy.get('[data-test="table-row"] input[type="checkbox"]').eq(1).click(); + cy.getBySel('bulk-select-action').eq(0).contains('Delete').click(); + confirmDelete(); + cy.wait('@bulkDelete'); + cy.getBySel('table-row') + .eq(0) + .should('not.contain', '3 - Sample dashboard'); + cy.getBySel('table-row') + .eq(1) + .should('not.contain', '4 - Sample dashboard'); + }); + + it('should delete correctly', () => { + interceptDelete(); + + // deletes in card-view + setGridMode('card'); + orderAlphabetical(); + + cy.getBySel('styled-card').eq(0).contains('1 - Sample dashboard'); + openMenu(); + cy.getBySel('dashboard-card-option-delete-button').click(); + confirmDelete(); + cy.wait('@delete'); + cy.getBySel('styled-card') + .eq(0) + .should('not.contain', '1 - Sample dashboard'); + + // deletes in list-view + setGridMode('list'); + cy.getBySel('table-row').eq(0).contains('2 - Sample dashboard'); + cy.getBySel('dashboard-list-trash-icon').eq(0).click(); + confirmDelete(); + cy.wait('@delete'); + cy.getBySel('table-row') + .eq(0) + .should('not.contain', '2 - Sample dashboard'); + }); + + it('should edit correctly', () => { + interceptUpdate(); + + // edits in card-view + setGridMode('card'); + orderAlphabetical(); + cy.getBySel('styled-card').eq(0).contains('1 - Sample dashboard'); + + // change title + openProperties(); + cy.getBySel('dashboard-title-input').type(' | EDITED'); + cy.get('button:contains("Save")').click(); + cy.wait('@update'); + cy.getBySel('styled-card') + .eq(0) + .contains('1 - Sample dashboard | EDITED'); + + // edits in list-view + setGridMode('list'); + cy.getBySel('edit-alt').eq(0).click(); + cy.getBySel('dashboard-title-input').clear().type('1 - Sample dashboard'); + cy.get('button:contains("Save")').click(); + cy.wait('@update'); + cy.getBySel('table-row').eq(0).contains('1 - Sample dashboard'); + }); + }); +}); diff --git a/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts b/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts deleted file mode 100644 index a758552481f90..0000000000000 --- a/superset-frontend/cypress-base/cypress/integration/dashboard_list/list_view.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF 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 { DASHBOARD_LIST } from './dashboard_list.helper'; - -describe('dashboard list view', () => { - beforeEach(() => { - cy.login(); - cy.visit(DASHBOARD_LIST); - cy.get('[aria-label="list-view"]').click(); - }); - - xit('should load rows', () => { - cy.get('[data-test="listview-table"]').should('be.visible'); - // check dashboard list view header - cy.get('[data-test="sort-header"]').eq(1).contains('Title'); - cy.get('[data-test="sort-header"]').eq(2).contains('Modified by'); - cy.get('[data-test="sort-header"]').eq(3).contains('Status'); - cy.get('[data-test="sort-header"]').eq(4).contains('Modified'); - cy.get('[data-test="sort-header"]').eq(5).contains('Created by'); - cy.get('[data-test="sort-header"]').eq(6).contains('Owners'); - cy.get('[data-test="sort-header"]').eq(7).contains('Actions'); - cy.get('[data-test="table-row"]').should('have.length', 4); // failed, xit-ed - }); - - xit('should sort correctly', () => { - cy.get('[data-test="sort-header"]').eq(1).click(); - cy.get('[data-test="sort-header"]').eq(1).click(); - cy.get('[data-test="table-row"]') - .first() - .find('[data-test="table-row-cell"]') - .find('[data-test="cell-text"]') - .contains("World Bank's Data"); - }); - - it('should bulk delete correctly', () => { - cy.get('[data-test="listview-table"]').should('be.visible'); - cy.get('[data-test="bulk-select"]').eq(0).click(); - cy.get('[aria-label="checkbox-off"]').eq(1).siblings('input').click(); - cy.get('[aria-label="checkbox-off"]').eq(2).siblings('input').click(); - cy.get('[data-test="bulk-select-action"]').eq(0).click(); - cy.get('[data-test="delete-modal-input"]').eq(0).type('DELETE'); - cy.get('[data-test="modal-confirm-button"]').eq(0).click(); - cy.get('[aria-label="checkbox-on"]').should('not.exist'); - }); -}); diff --git a/superset-frontend/cypress-base/cypress/integration/database/modal.test.ts b/superset-frontend/cypress-base/cypress/integration/database/modal.test.ts index 2255021676560..c94adcc2bd74a 100644 --- a/superset-frontend/cypress-base/cypress/integration/database/modal.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/database/modal.test.ts @@ -16,14 +16,25 @@ * specific language governing permissions and limitations * under the License. */ -import { DATABASE_LIST } from './helper'; +import { DATABASE_LIST } from 'cypress/utils/urls'; + +function closeModal() { + cy.get('body').then($body => { + if ($body.find('[data-test="database-modal"]').length) { + cy.get('[aria-label="Close"]').eq(1).click(); + } + }); +} describe('Add database', () => { - beforeEach(() => { - cy.login(); + before(() => { cy.visit(DATABASE_LIST); - cy.wait(3000); - cy.get('[data-test="btn-create-database"]').click(); + }); + + beforeEach(() => { + cy.preserveLogin(); + closeModal(); + cy.getBySel('btn-create-database').click(); }); it('should open dynamic form', () => { @@ -42,11 +53,11 @@ describe('Add database', () => { // click postgres dynamic form cy.get('.preferred > :nth-child(1)').click(); - cy.get('[data-test="sqla-connect-btn"]').click(); + cy.getBySel('sqla-connect-btn').click(); // check if the sqlalchemy form is showing up - cy.get('[data-test=database-name-input]').should('be.visible'); - cy.get('[data-test="sqlalchemy-uri-input"]').should('be.visible'); + cy.getBySel('database-name-input').should('be.visible'); + cy.getBySel('sqlalchemy-uri-input').should('be.visible'); }); it('show error alerts on dynamic form for bad host', () => { diff --git a/superset-frontend/cypress-base/cypress/integration/dataset/dataset_list.test.ts b/superset-frontend/cypress-base/cypress/integration/dataset/dataset_list.test.ts index 8dda9f2764d6f..9e55d01c3c3f5 100644 --- a/superset-frontend/cypress-base/cypress/integration/dataset/dataset_list.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/dataset/dataset_list.test.ts @@ -17,14 +17,17 @@ * under the License. */ -const DATASET_LIST_PATH = 'tablemodelview/list'; +import { DATASET_LIST_PATH } from 'cypress/utils/urls'; describe('Dataset list', () => { - beforeEach(() => { - cy.login(); + before(() => { cy.visit(DATASET_LIST_PATH); }); + beforeEach(() => { + cy.preserveLogin(); + }); + it('should open Explore on dataset name click', () => { cy.intercept('**/api/v1/explore/**').as('explore'); cy.get('[data-test="listview-table"] [data-test="internal-link"]') diff --git a/superset-frontend/cypress-base/cypress/integration/explore/AdhocFilters.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/_skip.AdhocFilters.test.ts similarity index 95% rename from superset-frontend/cypress-base/cypress/integration/explore/AdhocFilters.test.ts rename to superset-frontend/cypress-base/cypress/integration/explore/_skip.AdhocFilters.test.ts index 6ae5aead2b0c3..1dca8f6e10460 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/AdhocFilters.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/explore/_skip.AdhocFilters.test.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -describe('AdhocFilters', () => { +describe.skip('AdhocFilters', () => { beforeEach(() => { cy.login(); cy.intercept('GET', '/superset/filter/table/*/name').as('filterValues'); @@ -28,7 +28,7 @@ describe('AdhocFilters', () => { let numScripts = 0; - xit('Should load AceEditor scripts when needed', () => { + it('Should load AceEditor scripts when needed', () => { cy.get('script').then(nodes => { numScripts = nodes.length; }); @@ -51,7 +51,7 @@ describe('AdhocFilters', () => { }); }); - xit('Set simple adhoc filter', () => { + it('Set simple adhoc filter', () => { cy.get('[aria-label="Comparator option"] .Select__control').click(); cy.get('[data-test=adhoc-filter-simple-value] input[type=text]') .focus() @@ -70,7 +70,7 @@ describe('AdhocFilters', () => { }); }); - xit('Set custom adhoc filter', () => { + it('Set custom adhoc filter', () => { const filterType = 'name'; const filterContent = "'Amy' OR name = 'Donald'"; diff --git a/superset-frontend/cypress-base/cypress/integration/explore/link.test.ts b/superset-frontend/cypress-base/cypress/integration/explore/link.test.ts index fb3445fc6366f..7a31d7cbb81f1 100644 --- a/superset-frontend/cypress-base/cypress/integration/explore/link.test.ts +++ b/superset-frontend/cypress-base/cypress/integration/explore/link.test.ts @@ -101,8 +101,8 @@ describe('Test explore links', () => { cy.request(apiURL('/api/v1/chart/', query)).then(response => { expect(response.body.count).equals(1); - cy.request('DELETE', `/api/v1/chart/${response.body.ids[0]}`); }); + cy.deleteChartByName(newChartName, true); }); }); @@ -183,5 +183,6 @@ describe('Test explore links', () => { cy.request(apiURL('/api/v1/dashboard/', query)).then(response => { expect(response.body.count).equals(1); }); + cy.deleteDashboardByName(dashboardTitle, true); }); }); diff --git a/superset-frontend/cypress-base/cypress/integration/explore/utils.ts b/superset-frontend/cypress-base/cypress/integration/explore/utils.ts new file mode 100644 index 0000000000000..0972bac552749 --- /dev/null +++ b/superset-frontend/cypress-base/cypress/integration/explore/utils.ts @@ -0,0 +1,47 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export function interceptFiltering() { + cy.intercept('GET', `/api/v1/chart/?q=*`).as('filtering'); +} + +export function interceptBulkDelete() { + cy.intercept('DELETE', `/api/v1/chart/?q=*`).as('bulkDelete'); +} + +export function interceptDelete() { + cy.intercept('DELETE', `/api/v1/chart/*`).as('delete'); +} + +export function interceptUpdate() { + cy.intercept('PUT', `/api/v1/chart/*`).as('update'); +} + +export function interceptPost() { + cy.intercept('POST', `/api/v1/chart/`).as('post'); +} + +export function setFilter(filter: string, option: string) { + interceptFiltering(); + + cy.get(`[aria-label="${filter}"]`).first().click(); + cy.get(`[aria-label="${filter}"] [title="${option}"]`).click(); + + cy.wait('@filtering'); +} diff --git a/superset-frontend/cypress-base/cypress/integration/sqllab/sourcePanel.index.test.js b/superset-frontend/cypress-base/cypress/integration/sqllab/_skip.sourcePanel.index.test.js similarity index 94% rename from superset-frontend/cypress-base/cypress/integration/sqllab/sourcePanel.index.test.js rename to superset-frontend/cypress-base/cypress/integration/sqllab/_skip.sourcePanel.index.test.js index ec0db332afd94..00f4c1988c761 100644 --- a/superset-frontend/cypress-base/cypress/integration/sqllab/sourcePanel.index.test.js +++ b/superset-frontend/cypress-base/cypress/integration/sqllab/_skip.sourcePanel.index.test.js @@ -18,7 +18,7 @@ */ import { selectResultsTab } from './sqllab.helper'; -describe('SqlLab datasource panel', () => { +describe.skip('SqlLab datasource panel', () => { beforeEach(() => { cy.login(); cy.visit('/superset/sqllab'); @@ -26,7 +26,7 @@ describe('SqlLab datasource panel', () => { // TODO the test bellow is flaky, and has been disabled for the time being // (notice the `it.skip`) - it.skip('creates a table preview when a database, schema, and table are selected', () => { + it('creates a table preview when a database, schema, and table are selected', () => { cy.intercept('/superset/table/**').as('tableMetadata'); // it should have dropdowns to select database, schema, and table diff --git a/superset-frontend/cypress-base/cypress/support/index.d.ts b/superset-frontend/cypress-base/cypress/support/index.d.ts index eca68a7ced7fe..c60580247e569 100644 --- a/superset-frontend/cypress-base/cypress/support/index.d.ts +++ b/superset-frontend/cypress-base/cypress/support/index.d.ts @@ -29,6 +29,19 @@ declare namespace Cypress { * Login test user. */ login(): void; + preserveLogin(): void; + + /** + * + * Utils + */ + + getBySel(selector: string): cy; + getBySelLike(selector: string): cy; + cleanCharts(): cy; + cleanDashboards(): cy; + loadChartFixtures(): cy; + loadDashboardFixtures(): cy; visitChartByParams(params: string | Record): cy; visitChartByName(name: string): cy; @@ -54,13 +67,19 @@ declare namespace Cypress { getDashboards(): cy; getCharts(): cy; + /** + * Create + */ + createSampleDashboards(indexes?: number[]): void; + createSampleCharts(indexes?: number[]): void; + /** * Delete */ - deleteDashboard(id: number): cy; - deleteDashboardByName(name: string): cy; - deleteChartByName(name: string): cy; - deleteChart(id: number): cy; + deleteDashboard(id: number, failOnStatusCode: boolean): cy; + deleteDashboardByName(dashboardName: string, failOnStatusCode: boolean): cy; + deleteChartByName(name: string, failOnStatusCode: boolean): cy; + deleteChart(id: number, failOnStatusCode: boolean): cy; } } diff --git a/superset-frontend/cypress-base/cypress/support/index.ts b/superset-frontend/cypress-base/cypress/support/index.ts index 7ededd67a5fee..80a51fc409b5f 100644 --- a/superset-frontend/cypress-base/cypress/support/index.ts +++ b/superset-frontend/cypress-base/cypress/support/index.ts @@ -19,10 +19,102 @@ import '@cypress/code-coverage/support'; import '@applitools/eyes-cypress/commands'; +require('cy-verify-downloads').addCustomCommand(); + const BASE_EXPLORE_URL = '/explore/?form_data='; const TokenName = Cypress.env('TOKEN_NAME'); +let DASHBOARD_FIXTURES: Record[] = []; +let CHART_FIXTURES: Record[] = []; -require('cy-verify-downloads').addCustomCommand(); +Cypress.Commands.add('loadChartFixtures', () => + cy.fixture('charts.json').then(charts => { + CHART_FIXTURES = charts; + }), +); + +Cypress.Commands.add('loadDashboardFixtures', () => + cy.fixture('dashboards.json').then(dashboards => { + DASHBOARD_FIXTURES = dashboards; + }), +); + +before(() => { + cy.login(); + cy.loadChartFixtures(); + cy.loadDashboardFixtures(); + cy.cleanDashboards(); + cy.cleanCharts(); +}); + +Cypress.Commands.add('cleanDashboards', () => + cy.getDashboards().then((sampleDashboards?: Record[]) => { + const deletableDashboards = []; + for (let i = 0; i < DASHBOARD_FIXTURES.length; i += 1) { + const fixture = DASHBOARD_FIXTURES[i]; + const isInDb = sampleDashboards?.find( + d => d.dashboard_title === fixture.dashboard_title, + ); + if (isInDb) { + deletableDashboards.push(isInDb.id); + } + } + if (deletableDashboards.length) { + cy.request({ + failOnStatusCode: false, + method: 'DELETE', + url: `api/v1/dashboard/?q=!(${deletableDashboards.join(',')})`, + headers: { + Cookie: `csrf_access_token=${window.localStorage.getItem( + 'access_token', + )}`, + 'Content-Type': 'application/json', + Authorization: `Bearer ${TokenName}`, + 'X-CSRFToken': `${window.localStorage.getItem('access_token')}`, + Referer: `${Cypress.config().baseUrl}/`, + }, + }).then(resp => resp); + } + }), +); + +Cypress.Commands.add('cleanCharts', () => + cy.getCharts().then((sampleCharts?: Record[]) => { + const deletableCharts = []; + for (let i = 0; i < CHART_FIXTURES.length; i += 1) { + const fixture = CHART_FIXTURES[i]; + const isInDb = sampleCharts?.find( + c => c.slice_name === fixture.slice_name, + ); + if (isInDb) { + deletableCharts.push(isInDb.id); + } + } + if (deletableCharts) { + cy.request({ + failOnStatusCode: false, + method: 'DELETE', + url: `api/v1/chart/?q=!(${deletableCharts.join(',')})`, + headers: { + Cookie: `csrf_access_token=${window.localStorage.getItem( + 'access_token', + )}`, + 'Content-Type': 'application/json', + Authorization: `Bearer ${TokenName}`, + 'X-CSRFToken': `${window.localStorage.getItem('access_token')}`, + Referer: `${Cypress.config().baseUrl}/`, + }, + }).then(resp => resp); + } + }), +); + +Cypress.Commands.add('getBySel', (selector, ...args) => + cy.get(`[data-test=${selector}]`, ...args), +); + +Cypress.Commands.add('getBySelLike', (selector, ...args) => + cy.get(`[data-test*=${selector}]`, ...args), +); /* eslint-disable consistent-return */ Cypress.on('uncaught:exception', err => { @@ -45,6 +137,10 @@ Cypress.Commands.add('login', () => { }); }); +Cypress.Commands.add('preserveLogin', () => { + Cypress.Cookies.preserveOnce('session'); +}); + Cypress.Commands.add('visitChartByName', name => { cy.request(`/chart/api/read?_flt_3_slice_name=${name}`).then(response => { cy.visit(`${BASE_EXPLORE_URL}{"slice_id": ${response.body.pks[0]}}`); @@ -141,33 +237,86 @@ Cypress.Commands.add( }, ); -Cypress.Commands.add('deleteDashboardByName', (name: string) => - cy.getDashboards().then((dashboards: any) => { - dashboards?.forEach((element: any) => { - if (element.dashboard_title === name) { - const elementId = element.id; - cy.deleteDashboard(elementId); +Cypress.Commands.add('createSampleDashboards', (indexes?: number[]) => + cy.cleanDashboards().then(() => { + for (let i = 0; i < DASHBOARD_FIXTURES.length; i += 1) { + if (indexes?.includes(i) || !indexes) { + cy.request({ + method: 'POST', + url: `/api/v1/dashboard/`, + body: DASHBOARD_FIXTURES[i], + failOnStatusCode: false, + headers: { + Cookie: `csrf_access_token=${window.localStorage.getItem( + 'access_token', + )}`, + 'Content-Type': 'application/json', + Authorization: `Bearer ${TokenName}`, + 'X-CSRFToken': `${window.localStorage.getItem('access_token')}`, + Referer: `${Cypress.config().baseUrl}/`, + }, + }); } - }); + } }), ); -Cypress.Commands.add('deleteDashboard', (id: number) => - cy - .request({ - method: 'DELETE', - url: `api/v1/dashboard/${id}`, - headers: { - Cookie: `csrf_access_token=${window.localStorage.getItem( - 'access_token', - )}`, - 'Content-Type': 'application/json', - Authorization: `Bearer ${TokenName}`, - 'X-CSRFToken': `${window.localStorage.getItem('access_token')}`, - Referer: `${Cypress.config().baseUrl}/`, - }, - }) - .then(resp => resp), +Cypress.Commands.add('createSampleCharts', (indexes?: number[]) => + cy.cleanCharts().then(() => { + for (let i = 0; i < CHART_FIXTURES.length; i += 1) { + if (indexes?.includes(i) || !indexes) { + cy.request({ + method: 'POST', + url: `/api/v1/chart/`, + body: CHART_FIXTURES[i], + failOnStatusCode: false, + headers: { + Cookie: `csrf_access_token=${window.localStorage.getItem( + 'access_token', + )}`, + 'Content-Type': 'application/json', + Authorization: `Bearer ${TokenName}`, + 'X-CSRFToken': `${window.localStorage.getItem('access_token')}`, + Referer: `${Cypress.config().baseUrl}/`, + }, + }); + } + } + }), +); + +Cypress.Commands.add( + 'deleteDashboardByName', + (dashboardName: string, failOnStatusCode = false) => + cy.getDashboards().then((sampleDashboards?: Record[]) => { + const dashboard = sampleDashboards?.find( + d => d.dashboard_title === dashboardName, + ); + if (dashboard) { + cy.deleteDashboard(dashboard.id, failOnStatusCode); + } + }), +); + +Cypress.Commands.add( + 'deleteDashboard', + (id: number, failOnStatusCode = false) => + cy + .request({ + failOnStatusCode, + method: 'DELETE', + url: `api/v1/dashboard/${id}`, + headers: { + Cookie: `csrf_access_token=${window.localStorage.getItem( + 'access_token', + )}`, + 'Content-Type': 'application/json', + Authorization: `Bearer ${TokenName}`, + 'X-CSRFToken': `${window.localStorage.getItem('access_token')}`, + Referer: `${Cypress.config().baseUrl}/`, + }, + }) + .then(resp => resp), ); Cypress.Commands.add('getDashboards', () => @@ -183,9 +332,10 @@ Cypress.Commands.add('getDashboards', () => .then(resp => resp.body.result), ); -Cypress.Commands.add('deleteChart', (id: number) => +Cypress.Commands.add('deleteChart', (id: number, failOnStatusCode = false) => cy .request({ + failOnStatusCode, method: 'DELETE', url: `api/v1/chart/${id}`, headers: { @@ -197,7 +347,6 @@ Cypress.Commands.add('deleteChart', (id: number) => 'X-CSRFToken': `${window.localStorage.getItem('access_token')}`, Referer: `${Cypress.config().baseUrl}/`, }, - failOnStatusCode: false, }) .then(resp => resp), ); @@ -215,13 +364,13 @@ Cypress.Commands.add('getCharts', () => .then(resp => resp.body.result), ); -Cypress.Commands.add('deleteChartByName', (name: string) => - cy.getCharts().then((slices: any) => { - slices?.forEach((element: any) => { - if (element.slice_name === name) { - const elementId = element.id; - cy.deleteChart(elementId); +Cypress.Commands.add( + 'deleteChartByName', + (sliceName: string, failOnStatusCode = false) => + cy.getCharts().then((sampleCharts?: Record[]) => { + const chart = sampleCharts?.find(c => c.slice_name === sliceName); + if (chart) { + cy.deleteChart(chart.id, failOnStatusCode); } - }); - }), + }), ); diff --git a/superset-frontend/cypress-base/cypress/utils/index.ts b/superset-frontend/cypress-base/cypress/utils/index.ts index ea0bbdcf437b3..b685cc01e898a 100644 --- a/superset-frontend/cypress-base/cypress/utils/index.ts +++ b/superset-frontend/cypress-base/cypress/utils/index.ts @@ -16,5 +16,107 @@ * specific language governing permissions and limitations * under the License. */ +import { getChartAlias, Slice } from 'cypress/utils/vizPlugins'; + export * from './vizPlugins'; export { default as parsePostForm } from './parsePostForm'; +export interface ChartSpec { + name: string; + viz: string; +} + +export function setGridMode(type: 'card' | 'list') { + cy.get(`[aria-label="${type}-view"]`).click(); +} + +export function toggleBulkSelect() { + cy.getBySel('bulk-select').click(); +} + +export function clearAllInputs() { + cy.get('[aria-label="close-circle"]').click({ multiple: true, force: true }); +} + +const toSlicelike = ($chart: JQuery): Slice => ({ + slice_id: parseInt($chart.attr('data-test-chart-id')!, 10), + form_data: { + viz_type: $chart.attr('data-test-viz-type')!, + }, +}); + +export function getChartAliasBySpec(chart: ChartSpec) { + return getChartGridComponent(chart).then($chart => + cy.wrap(getChartAlias(toSlicelike($chart))), + ); +} + +export function getChartAliasesBySpec(charts: readonly ChartSpec[]) { + const aliases: string[] = []; + charts.forEach(chart => + getChartAliasBySpec(chart).then(alias => { + aliases.push(alias); + }), + ); + // Wrapping the aliases is key. + // That way callers can chain off this function + // and actually get the list of aliases. + return cy.wrap(aliases); +} + +export function getChartGridComponent({ name, viz }: ChartSpec) { + return cy + .get(`[data-test-chart-name="${name}"]`) + .should('have.attr', 'data-test-viz-type', viz); +} + +export function waitForChartLoad(chart: ChartSpec) { + return getChartGridComponent(chart).then(gridComponent => { + const chartId = gridComponent.attr('data-test-chart-id'); + // the chart should load in under half a minute + return ( + cy + // this id only becomes visible when the chart is loaded + .get(`#chart-id-${chartId}`, { + timeout: 30000, + }) + .should('be.visible') + // return the chart grid component + .then(() => gridComponent) + ); + }); +} + +/** + * Drag an element and drop it to another element. + * Usage: + * drag(source).to(target); + */ +export function drag(selector: string, content: string | number | RegExp) { + const dataTransfer = { data: {} }; + return { + to(target: string | Cypress.Chainable) { + cy.get('.dragdroppable') + .contains(selector, content) + .trigger('mousedown', { which: 1, force: true }) + .trigger('dragstart', { dataTransfer, force: true }) + .trigger('drag', { force: true }); + + (typeof target === 'string' ? cy.get(target) : target) + .trigger('dragover', { dataTransfer, force: true }) + .trigger('drop', { dataTransfer, force: true }) + .trigger('dragend', { dataTransfer, force: true }) + .trigger('mouseup', { which: 1, force: true }); + }, + }; +} + +export function resize(selector: string) { + return { + to(cordX: number, cordY: number) { + cy.get(selector) + .trigger('mousedown', { which: 1, force: true }) + .trigger('mousemove', { which: 1, cordX, cordY, force: true }) + .trigger('mouseup', { which: 1, force: true }); + }, + }; +} diff --git a/superset-frontend/cypress-base/cypress/integration/alerts_and_reports/alert_report.helper.ts b/superset-frontend/cypress-base/cypress/utils/urls.ts similarity index 64% rename from superset-frontend/cypress-base/cypress/integration/alerts_and_reports/alert_report.helper.ts rename to superset-frontend/cypress-base/cypress/utils/urls.ts index dcf8b2b4a2931..63fa6684d7e6f 100644 --- a/superset-frontend/cypress-base/cypress/integration/alerts_and_reports/alert_report.helper.ts +++ b/superset-frontend/cypress-base/cypress/utils/urls.ts @@ -16,5 +16,14 @@ * specific language governing permissions and limitations * under the License. */ + +export const DASHBOARD_LIST = '/dashboard/list/'; +export const CHART_LIST = '/chart/list/'; +export const WORLD_HEALTH_DASHBOARD = '/superset/dashboard/world_health/'; +export const SAMPLE_DASHBOARD_1 = '/superset/dashboard/1-sample-dashboard/'; +export const ECHARTS_DASHBOARD = '/superset/dashboard/echarts_dash/'; +export const TABBED_DASHBOARD = '/superset/dashboard/tabbed_dash/'; +export const DATABASE_LIST = '/databaseview/list'; +export const DATASET_LIST_PATH = 'tablemodelview/list'; export const ALERT_LIST = '/alert/list/'; export const REPORT_LIST = '/report/list/'; diff --git a/superset-frontend/cypress-base/package-lock.json b/superset-frontend/cypress-base/package-lock.json index 873900f546502..7b5b69eddbecd 100644 --- a/superset-frontend/cypress-base/package-lock.json +++ b/superset-frontend/cypress-base/package-lock.json @@ -12,6 +12,7 @@ "@applitools/eyes-cypress": "^3.25.3", "@cypress/code-coverage": "^3.9.11", "@superset-ui/core": "^0.18.8", + "brace": "^0.11.1", "cy-verify-downloads": "^0.1.6", "querystringify": "^2.2.0", "react-dom": "^16.13.0", @@ -3577,6 +3578,11 @@ "node": ">=0.6" } }, + "node_modules/brace": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz", + "integrity": "sha512-Fc8Ne62jJlKHiG/ajlonC4Sd66Pq68fFwK4ihJGNZpGqboc324SQk+lRvMzpPRuJOmfrJefdG8/7JdWX4bzJ2Q==" + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -13216,6 +13222,11 @@ } } }, + "brace": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/brace/-/brace-0.11.1.tgz", + "integrity": "sha512-Fc8Ne62jJlKHiG/ajlonC4Sd66Pq68fFwK4ihJGNZpGqboc324SQk+lRvMzpPRuJOmfrJefdG8/7JdWX4bzJ2Q==" + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", diff --git a/superset-frontend/cypress-base/package.json b/superset-frontend/cypress-base/package.json index 02c126406cb2c..b28c684544a54 100644 --- a/superset-frontend/cypress-base/package.json +++ b/superset-frontend/cypress-base/package.json @@ -13,6 +13,7 @@ "@applitools/eyes-cypress": "^3.25.3", "@cypress/code-coverage": "^3.9.11", "@superset-ui/core": "^0.18.8", + "brace": "^0.11.1", "cy-verify-downloads": "^0.1.6", "querystringify": "^2.2.0", "react-dom": "^16.13.0", diff --git a/superset-frontend/package-lock.json b/superset-frontend/package-lock.json index 4fe95d0d65ad5..98a773389d3c1 100644 --- a/superset-frontend/package-lock.json +++ b/superset-frontend/package-lock.json @@ -111,6 +111,7 @@ "react-lines-ellipsis": "^0.15.0", "react-loadable": "^5.5.0", "react-markdown": "^4.3.1", + "react-query": "^3.39.2", "react-redux": "^7.2.0", "react-resize-detector": "^6.7.6", "react-reverse-portal": "^2.0.1", @@ -5749,7 +5750,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -10198,7 +10199,7 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-4.0.5.tgz", "integrity": "sha512-WR2cqxzjsvmHJ9sKCdqBYG/qeiAXB9ev1iq1W2Rry7LxeJ7eDtTr4mOWe/TBvp6xFzevGecQc2YEWwExTuLZLg==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@isaacs/string-locale-compare": "^1.1.0", @@ -10245,7 +10246,7 @@ "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@npmcli/fs": "^1.0.0", @@ -10275,7 +10276,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=10" @@ -10285,7 +10286,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "yallist": "^4.0.0" @@ -10298,7 +10299,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "devOptional": true, "peer": true, "bin": { "mkdirp": "bin/cmd.js" @@ -10311,7 +10312,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "lru-cache": "^6.0.0" @@ -10327,7 +10328,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "minipass": "^3.1.1" @@ -10340,7 +10341,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/@npmcli/ci-detect": { @@ -10392,7 +10393,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-2.1.0.tgz", "integrity": "sha512-/hBFX/QG1b+N7PZBFs0bi+evgRZcK9nWBxQKZkGoXUT5hJSwl5c4d7y8/hm+NQZRPhQ67RzFaj5UM9YeyKoryw==", - "dev": true, + "devOptional": true, "dependencies": { "@npmcli/promise-spawn": "^1.3.2", "lru-cache": "^6.0.0", @@ -10408,7 +10409,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -10420,7 +10421,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "devOptional": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -10432,7 +10433,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -10447,7 +10448,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "dependencies": { "isexe": "^2.0.0" }, @@ -10462,13 +10463,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true }, "node_modules/@npmcli/installed-package-contents": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz", "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==", - "dev": true, + "devOptional": true, "dependencies": { "npm-bundled": "^1.1.1", "npm-normalize-package-bin": "^1.0.1" @@ -10484,7 +10485,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-2.0.0.tgz", "integrity": "sha512-QBJfpCY1NOAkkW3lFfru9VTdqvMB2TN0/vrevl5xBCv5Fi0XDVcA6rqqSau4Ysi4Iw3fBzyXV7hzyTBDfadf7g==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@npmcli/name-from-folder": "^1.0.1", @@ -10500,7 +10501,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-2.0.0.tgz", "integrity": "sha512-VVW+JhWCKRwCTE+0xvD6p3uV4WpqocNYYtzyvenqL/u1Q3Xx6fGTJ+6UoIoii07fbuEO9U3IIyuGY0CYHDv1sg==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "cacache": "^15.0.5", @@ -10516,7 +10517,7 @@ "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@npmcli/fs": "^1.0.0", @@ -10546,7 +10547,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=10" @@ -10556,7 +10557,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "yallist": "^4.0.0" @@ -10569,7 +10570,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "devOptional": true, "peer": true, "bin": { "mkdirp": "bin/cmd.js" @@ -10582,7 +10583,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "lru-cache": "^6.0.0" @@ -10598,7 +10599,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "minipass": "^3.1.1" @@ -10611,7 +10612,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/@npmcli/move-file": { @@ -10641,20 +10642,20 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz", "integrity": "sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/@npmcli/node-gyp": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-1.0.3.tgz", "integrity": "sha512-fnkhw+fmX65kiLqk6E3BFLXNC26rUhK90zVwe2yncPliVT/Qos3xjhTLE59Df8KnPlcwIERXKVlU1bXoUQ+liA==", - "dev": true + "devOptional": true }, "node_modules/@npmcli/package-json": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-1.0.1.tgz", "integrity": "sha512-y6jnu76E9C23osz8gEMBayZmaZ69vFOIk8vR1FJL/wbEJ54+9aVG9rLTjQKSXfgYZEr50nw1txBBFfBZZe+bYg==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "json-parse-even-better-errors": "^2.3.1" @@ -10664,7 +10665,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz", "integrity": "sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg==", - "dev": true, + "devOptional": true, "dependencies": { "infer-owner": "^1.0.4" } @@ -10673,7 +10674,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-2.0.0.tgz", "integrity": "sha512-fSan/Pu11xS/TdaTpTB0MRn9guwGU8dye+x56mEVgBEd/QsybBbYcAL0phPXi8SGWFEChkQd6M9qL4y6VOpFig==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@npmcli/node-gyp": "^1.0.2", @@ -10686,7 +10687,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" @@ -10696,7 +10697,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "delegates": "^1.0.0", @@ -10710,7 +10711,7 @@ "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@npmcli/fs": "^1.0.0", @@ -10740,7 +10741,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=10" @@ -10750,7 +10751,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "ms": "2.1.2" @@ -10768,7 +10769,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", "integrity": "sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "ansi-regex": "^5.0.1", @@ -10789,7 +10790,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" @@ -10799,7 +10800,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "yallist": "^4.0.0" @@ -10812,7 +10813,7 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "agentkeepalive": "^4.1.3", @@ -10840,7 +10841,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "devOptional": true, "peer": true, "bin": { "mkdirp": "bin/cmd.js" @@ -10853,14 +10854,14 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/@npmcli/run-script/node_modules/negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">= 0.6" @@ -10870,7 +10871,7 @@ "version": "8.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "env-paths": "^2.2.0", @@ -10895,7 +10896,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.0.tgz", "integrity": "sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "are-we-there-yet": "^2.0.0", @@ -10911,7 +10912,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "inherits": "^2.0.3", @@ -10926,7 +10927,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "lru-cache": "^6.0.0" @@ -10942,7 +10943,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "agent-base": "^6.0.2", @@ -10957,7 +10958,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "minipass": "^3.1.1" @@ -10970,7 +10971,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "emoji-regex": "^8.0.0", @@ -10985,7 +10986,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "ansi-regex": "^5.0.1" @@ -10998,7 +10999,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "isexe": "^2.0.0" @@ -11014,14 +11015,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/@octokit/auth-token": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", - "dev": true, "dependencies": { "@octokit/types": "^6.0.3" } @@ -11030,7 +11030,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", - "dev": true, "dependencies": { "@octokit/auth-token": "^2.4.4", "@octokit/graphql": "^4.5.8", @@ -11045,7 +11044,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "dev": true, "dependencies": { "@octokit/types": "^6.0.3", "deprecation": "^2.0.0", @@ -11055,14 +11053,12 @@ "node_modules/@octokit/core/node_modules/universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", - "dev": true + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" }, "node_modules/@octokit/endpoint": { "version": "6.0.12", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", - "dev": true, "dependencies": { "@octokit/types": "^6.0.3", "is-plain-object": "^5.0.0", @@ -11073,7 +11069,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -11081,14 +11076,12 @@ "node_modules/@octokit/endpoint/node_modules/universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", - "dev": true + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" }, "node_modules/@octokit/graphql": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", - "dev": true, "dependencies": { "@octokit/request": "^5.6.0", "@octokit/types": "^6.0.3", @@ -11098,14 +11091,12 @@ "node_modules/@octokit/graphql/node_modules/universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", - "dev": true + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" }, "node_modules/@octokit/openapi-types": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", - "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==", - "dev": true + "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==" }, "node_modules/@octokit/plugin-enterprise-rest": { "version": "6.0.1", @@ -11117,7 +11108,6 @@ "version": "2.17.0", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz", "integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==", - "dev": true, "dependencies": { "@octokit/types": "^6.34.0" }, @@ -11129,7 +11119,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", - "dev": true, "peerDependencies": { "@octokit/core": ">=3" } @@ -11138,7 +11127,6 @@ "version": "5.13.0", "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz", "integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==", - "dev": true, "dependencies": { "@octokit/types": "^6.34.0", "deprecation": "^2.3.1" @@ -11151,7 +11139,6 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", - "dev": true, "dependencies": { "@octokit/endpoint": "^6.0.1", "@octokit/request-error": "^2.1.0", @@ -11165,7 +11152,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "dev": true, "dependencies": { "@octokit/types": "^6.0.3", "deprecation": "^2.0.0", @@ -11176,7 +11162,6 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -11184,14 +11169,12 @@ "node_modules/@octokit/request/node_modules/universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", - "dev": true + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" }, "node_modules/@octokit/rest": { "version": "18.12.0", "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", - "dev": true, "dependencies": { "@octokit/core": "^3.5.1", "@octokit/plugin-paginate-rest": "^2.16.8", @@ -11203,7 +11186,6 @@ "version": "6.34.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", - "dev": true, "dependencies": { "@octokit/openapi-types": "^11.2.0" } @@ -17816,7 +17798,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 6" } @@ -18047,7 +18029,7 @@ "version": "1.20.4", "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/@types/fetch-mock": { @@ -18610,7 +18592,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.6.tgz", "integrity": "sha512-ayJ0iOCDNHnKpKTgBG6Q6JOnHTj9zFta+3j2b8Ejza0e4cvRyMn0ZoLEmbPrTHe5YYRlDYPvPWVdV4cTaRyH7g==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@types/expect": "^1.20.4", @@ -19926,7 +19908,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, + "devOptional": true, "dependencies": { "debug": "4" }, @@ -19938,7 +19920,7 @@ "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, + "devOptional": true, "dependencies": { "ms": "2.1.2" }, @@ -19955,13 +19937,13 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true }, "node_modules/agentkeepalive": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.0.tgz", "integrity": "sha512-0PhAp58jZNw13UJv7NVdTGb0ZcghHUb3DrZ046JiiJY/BOaTTpbwdHq2VObPCBV8M2GPh7sgrJ3AQ8Ey468LJw==", - "dev": true, + "devOptional": true, "dependencies": { "debug": "^4.1.0", "depd": "^1.1.2", @@ -19975,7 +19957,7 @@ "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, + "devOptional": true, "dependencies": { "ms": "2.1.2" }, @@ -19992,7 +19974,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true }, "node_modules/aggregate-error": { "version": "3.0.1", @@ -20577,6 +20559,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "devOptional": true, "engines": { "node": ">=8" } @@ -20830,7 +20813,8 @@ "node_modules/async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "devOptional": true }, "node_modules/async-each": { "version": "1.0.3", @@ -20955,7 +20939,7 @@ "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "devOptional": true, + "dev": true, "dependencies": { "follow-redirects": "^1.14.0" } @@ -21731,8 +21715,7 @@ "node_modules/before-after-hook": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", - "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==", - "dev": true + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" }, "node_modules/better-opn": { "version": "2.1.1", @@ -21773,7 +21756,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-2.3.0.tgz", "integrity": "sha512-JzrOLHLwX2zMqKdyYZjkDgQGT+kHDkIhv2/IK2lJ00qLxV4TmFoHi8drDBb6H5Zrz1YfgHkai4e2MGPqnoUhqA==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "cmd-shim": "^4.0.1", @@ -21800,7 +21783,7 @@ "version": "4.18.0", "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-4.18.0.tgz", "integrity": "sha512-PQu3Kyv9dM4FnwB7XGj1+HucW+ShvJzJqjuw1JkKVs1mWdwOKVcRjOi+pV9X52A0tNvrPCsPkbFFQb+wE1EAXw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.8" }, @@ -21821,7 +21804,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, + "devOptional": true, "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -21832,7 +21815,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, + "devOptional": true, "funding": [ { "type": "github", @@ -21856,13 +21839,13 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "devOptional": true }, "node_modules/bl/node_modules/readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, + "devOptional": true, "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -22355,7 +22338,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", - "dev": true + "devOptional": true }, "node_modules/byline": { "version": "5.0.0", @@ -22808,14 +22791,6 @@ "node": "6.* || 8.* || >= 10.*" } }, - "node_modules/capture-stack-trace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", - "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/cardinal": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-0.4.4.tgz", @@ -23212,7 +23187,7 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" }, @@ -23224,6 +23199,8 @@ "version": "0.3.9", "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.9.tgz", "integrity": "sha512-7eA6hFtAZwVx3dWAGoaBqTrzWko5jRUFKpHT64ZHkJpaA3y5wf5NlLjguqTRmqycatJZiwftODYYyGNLbQ7MuA==", + "devOptional": true, + "peer": true, "dependencies": { "colors": "1.0.3", "strip-ansi": "^6.0.1" @@ -23233,6 +23210,8 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "devOptional": true, + "peer": true, "engines": { "node": ">=8" } @@ -23241,6 +23220,8 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "devOptional": true, + "peer": true, "engines": { "node": ">=0.1.90" } @@ -23249,6 +23230,8 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, + "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -23453,6 +23436,8 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "devOptional": true, + "peer": true, "engines": { "node": ">=0.8" } @@ -23461,6 +23446,8 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "devOptional": true, + "peer": true, "engines": { "node": ">= 0.10" } @@ -23489,12 +23476,16 @@ "node_modules/clone-stats": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=" + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "devOptional": true, + "peer": true }, "node_modules/cloneable-readable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "devOptional": true, + "peer": true, "dependencies": { "inherits": "^2.0.1", "process-nextick-args": "^2.0.0", @@ -23505,7 +23496,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-4.1.0.tgz", "integrity": "sha512-lb9L7EM4I/ZRVuljLPEtUJOP+xiQVknZ4ZMpMgEp4JzNldPb27HU03hi6K1/6CoIuit/Zm/LQXySErFeXxDprw==", - "dev": true, + "devOptional": true, "dependencies": { "mkdirp-infer-owner": "^2.0.0" }, @@ -23772,7 +23763,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/common-path-prefix": { @@ -24716,17 +24707,6 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==" }, - "node_modules/create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "dependencies": { - "capture-stack-trace": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", @@ -24780,6 +24760,7 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, "dependencies": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -26397,7 +26378,6 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", - "dev": true, "engines": { "node": ">=8" } @@ -26521,6 +26501,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true, "engines": { "node": "*" } @@ -26542,7 +26523,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", - "dev": true, + "devOptional": true, "engines": { "node": "*" } @@ -26842,7 +26823,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, + "devOptional": true, "dependencies": { "clone": "^1.0.2" } @@ -26851,7 +26832,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.8" } @@ -26955,8 +26936,7 @@ "node_modules/deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", - "dev": true + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" }, "node_modules/des.js": { "version": "1.0.1", @@ -27033,7 +27013,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", - "dev": true, + "devOptional": true, "dependencies": { "asap": "^2.0.0", "wrappy": "1" @@ -27043,6 +27023,7 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, "engines": { "node": ">=0.3.1" } @@ -27302,20 +27283,6 @@ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" }, - "node_modules/download-stats": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/download-stats/-/download-stats-0.3.4.tgz", - "integrity": "sha512-ic2BigbyUWx7/CBbsfGjf71zUNZB4edBGC3oRliSzsoNmvyVx3Ycfp1w3vp2Y78Ee0eIIkjIEO5KzW0zThDGaA==", - "optional": true, - "dependencies": { - "JSONStream": "^1.2.1", - "lazy-cache": "^2.0.1", - "moment": "^2.15.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/downshift": { "version": "6.1.7", "resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.7.tgz", @@ -27360,11 +27327,6 @@ "readable-stream": "^2.0.2" } }, - "node_modules/duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, "node_modules/duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -27405,29 +27367,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.0.tgz", "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==" }, - "node_modules/editions": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/editions/-/editions-2.3.1.tgz", - "integrity": "sha512-ptGvkwTvGdGfC0hfhKg0MT+TRLRKGtUiWGBInxOm5pz7ssADezahjCUaYuZ8Dr+C05FW0AECIIPt4WBxVINEhA==", - "dependencies": { - "errlop": "^2.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=0.8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/editions/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/editorconfig": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", @@ -27465,6 +27404,7 @@ "version": "3.1.8", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", + "devOptional": true, "dependencies": { "jake": "^10.8.5" }, @@ -27859,7 +27799,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true, + "devOptional": true, "engines": { "node": ">=6" } @@ -28001,18 +27941,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, - "node_modules/errlop": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/errlop/-/errlop-2.2.0.tgz", - "integrity": "sha512-e64Qj9+4aZzjzzFpZC7p5kmm/ccCrbLhAJplhsDXQFs87XTsXwOpH4s1Io2s90Tau/8r2j9f4l/thhDevRjzxw==", - "engines": { - "node": ">=0.8" - }, - "funding": { - "url": "https://bevry.me/fund" - } + "devOptional": true }, "node_modules/errno": { "version": "0.1.7", @@ -28029,7 +27958,7 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/error/-/error-10.4.0.tgz", "integrity": "sha512-YxIFEJuhgcICugOUvRx5th0UM+ActZ9sjY0QJmeVwsQdvosZ7kYzc9QqS0Da3R5iUmgU5meGIxh0xBeZpMVeLw==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/error-ex": { @@ -30154,6 +30083,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "devOptional": true, "dependencies": { "minimatch": "^5.0.1" } @@ -30162,6 +30092,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "devOptional": true, "dependencies": { "balanced-match": "^1.0.0" } @@ -30170,6 +30101,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "devOptional": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -30442,7 +30374,7 @@ "version": "1.2.16", "resolved": "https://registry.npmjs.org/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz", "integrity": "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "micromatch": "^4.0.2", @@ -30453,7 +30385,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "fill-range": "^7.0.1" @@ -30466,7 +30398,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -30479,7 +30411,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "locate-path": "^5.0.0", @@ -30493,7 +30425,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=0.12.0" @@ -30503,7 +30435,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "p-locate": "^4.1.0" @@ -30516,7 +30448,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "braces": "^3.0.1", @@ -30530,7 +30462,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "p-try": "^2.0.0" @@ -30546,7 +30478,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "p-limit": "^2.2.0" @@ -30559,7 +30491,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=6" @@ -30569,7 +30501,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" @@ -30579,7 +30511,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "find-up": "^4.0.0" @@ -30592,7 +30524,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "is-number": "^7.0.0" @@ -30606,6 +30538,7 @@ "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", "devOptional": true, + "peer": true, "dependencies": { "readable-stream": "^2.0.2" }, @@ -31409,18 +31342,6 @@ "assert-plus": "^1.0.0" } }, - "node_modules/gh-got": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-5.0.0.tgz", - "integrity": "sha1-7pW+NxBv2HSKlvjR20uuqJ4b+oo=", - "dependencies": { - "got": "^6.2.0", - "is-plain-obj": "^1.1.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/gh-pages": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz", @@ -31759,14 +31680,17 @@ "dev": true }, "node_modules/github-username": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/github-username/-/github-username-3.0.0.tgz", - "integrity": "sha1-CnciGbMTB0NCnyRW0L3T21Xc57E=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/github-username/-/github-username-6.0.0.tgz", + "integrity": "sha512-7TTrRjxblSI5l6adk9zd+cV5d6i1OrJSo3Vr9xdGqFLBQo0mz5P9eIfKCDJ7eekVGGFLbce0qbPSnktXV2BjDQ==", "dependencies": { - "gh-got": "^5.0.0" + "@octokit/rest": "^18.0.6" }, "engines": { - "node": ">=4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/gl-matrix": { @@ -31932,35 +31856,6 @@ "node": ">=8" } }, - "node_modules/got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "dependencies": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/got/node_modules/get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "engines": { - "node": ">=4" - } - }, "node_modules/graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", @@ -31983,7 +31878,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-2.0.0.tgz", "integrity": "sha512-/PiFUa7WIsl48dUeCvhIHnwNmAAzlI/eHoJl0vu3nsFA366JleY7Ff8EVTplZu5kO0MIdZjKTTnzItL61ahbnw==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8.0.0" @@ -32849,7 +32744,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true + "devOptional": true }, "node_modules/http-deceiver": { "version": "1.2.7", @@ -32896,7 +32791,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, + "devOptional": true, "dependencies": { "@tootallnate/once": "1", "agent-base": "6", @@ -32910,7 +32805,7 @@ "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, + "devOptional": true, "dependencies": { "ms": "2.1.2" }, @@ -32927,7 +32822,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true }, "node_modules/http-proxy-middleware": { "version": "2.0.1", @@ -33039,7 +32934,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, + "devOptional": true, "dependencies": { "agent-base": "6", "debug": "4" @@ -33052,7 +32947,7 @@ "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, + "devOptional": true, "dependencies": { "ms": "2.1.2" }, @@ -33069,13 +32964,13 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true }, "node_modules/human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "devOptional": true, + "dev": true, "engines": { "node": ">=8.12.0" } @@ -33084,7 +32979,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", - "dev": true, + "devOptional": true, "dependencies": { "ms": "^2.0.0" } @@ -33487,7 +33382,7 @@ "version": "7.3.3", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "devOptional": true, + "dev": true, "dependencies": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.0", @@ -33511,7 +33406,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "devOptional": true, + "dev": true, "engines": { "node": ">=8" } @@ -33520,7 +33415,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "devOptional": true, + "dev": true, "engines": { "node": ">=8" } @@ -33529,7 +33424,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "devOptional": true, + "dev": true, "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -33543,7 +33438,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "devOptional": true, + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -33936,7 +33831,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -33966,7 +33861,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", - "dev": true + "devOptional": true }, "node_modules/is-map": { "version": "2.0.2", @@ -34101,14 +33996,6 @@ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", "dev": true }, - "node_modules/is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -34135,19 +34022,11 @@ "resolved": "https://registry.npmjs.org/is-retina/-/is-retina-1.0.3.tgz", "integrity": "sha1-10AbKGvqKuN/Ykd1iN5QTQuGR+M=" }, - "node_modules/is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/is-scoped": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-2.1.0.tgz", "integrity": "sha512-Cv4OpPTHAK9kHYzkzCrof3VJh7H/PrG2MBUMvvJebaaUMbqhm0YAtXnvh0I3Hnj2tMZWwrRROWLSgfJrKqWmlQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "scoped-regex": "^2.0.0" @@ -34238,13 +34117,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "devOptional": true }, "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" }, @@ -34256,7 +34135,8 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "devOptional": true + "devOptional": true, + "peer": true }, "node_modules/is-weakref": { "version": "1.0.1", @@ -34317,6 +34197,7 @@ "version": "4.0.8", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", + "devOptional": true, "engines": { "node": ">= 8.0.0" }, @@ -34621,44 +34502,6 @@ "node": ">=8" } }, - "node_modules/istextorbinary": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.6.0.tgz", - "integrity": "sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==", - "dependencies": { - "binaryextensions": "^2.1.2", - "editions": "^2.2.0", - "textextensions": "^2.5.0" - }, - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/istextorbinary/node_modules/binaryextensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", - "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==", - "engines": { - "node": ">=0.8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/istextorbinary/node_modules/textextensions": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.6.0.tgz", - "integrity": "sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==", - "engines": { - "node": ">=0.8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, "node_modules/iterate-iterator": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.2.tgz", @@ -34683,6 +34526,7 @@ "version": "10.8.5", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", + "devOptional": true, "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", @@ -37284,6 +37128,11 @@ "node": ">=0.10.0" } }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "node_modules/js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", @@ -37732,7 +37581,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", - "dev": true, + "devOptional": true, "peer": true, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -37792,7 +37641,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "devOptional": true, + "dev": true, "dependencies": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" @@ -37871,14 +37720,14 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-3.1.1.tgz", "integrity": "sha512-sdMWKjRq8qWZEjDcVA6llnUT8RDEBIfOiGpYFPYa9u+2c39JCsejktSP7mj5eRid5EIvTzIpQ2kDOCw1Nq9BjQ==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/just-diff-apply": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-3.1.2.tgz", "integrity": "sha512-TCa7ZdxCeq6q3Rgms2JCRHTCfWAETPZ8SzYUbkYF6KR3I03sN29DaOIC+xyWboIcMvjAsD5iG2u/RWzHD8XpgQ==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/just-extend": { @@ -37948,18 +37797,6 @@ "node": "> 0.8" } }, - "node_modules/lazy-cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", - "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", - "optional": true, - "dependencies": { - "set-getter": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/lazy-universal-dotenv": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-3.0.1.tgz", @@ -38739,7 +38576,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/load-yaml-file/-/load-yaml-file-0.2.0.tgz", "integrity": "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "graceful-fs": "^4.1.5", @@ -38755,7 +38592,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=6" @@ -38996,7 +38833,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, + "devOptional": true, "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -39059,14 +38896,6 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" }, - "node_modules/lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/lowlight": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", @@ -39589,7 +39418,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-2.2.1.tgz", "integrity": "sha512-yiAivd4xFOH/WXlUi6v/nKopBh1QLzwjFi36NK88cGt/PRXI8WeBASqY+YSjIVWvQTx3hR8zHKDBMV6hWmglNA==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@types/node": "^15.6.1", @@ -39605,7 +39434,7 @@ "version": "9.3.0", "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-9.3.0.tgz", "integrity": "sha512-QKFbPwGCh1ypmc2H8BUYpbapwT/x2AOCYZQogzSui4rUNes7WVMagQXsirPIfp18EarX0SSY9Fpg426nSjew4Q==", - "dev": true, + "devOptional": true, "dependencies": { "binaryextensions": "^4.16.0", "commondir": "^1.0.1", @@ -39634,7 +39463,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -39643,7 +39472,7 @@ "version": "15.14.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz", "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/mem/node_modules/mimic-fn": { @@ -39943,7 +39772,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "devOptional": true, "engines": { "node": ">=6" } @@ -40138,7 +39966,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "dev": true, + "devOptional": true, "dependencies": { "minipass": "^3.1.0", "minipass-sized": "^1.0.3", @@ -40166,7 +39994,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", - "dev": true, + "devOptional": true, "dependencies": { "jsonparse": "^1.3.1", "minipass": "^3.0.0" @@ -40187,7 +40015,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, + "devOptional": true, "dependencies": { "minipass": "^3.0.0" }, @@ -40288,7 +40116,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz", "integrity": "sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw==", - "dev": true, + "devOptional": true, "dependencies": { "chownr": "^2.0.0", "infer-owner": "^1.0.4", @@ -40302,7 +40130,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" } @@ -40311,7 +40139,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "devOptional": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -40477,7 +40305,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", - "dev": true, + "devOptional": true, "dependencies": { "@types/minimatch": "^3.0.3", "array-differ": "^3.0.0", @@ -40496,7 +40324,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -40646,7 +40474,8 @@ "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true }, "node_modules/nise": { "version": "4.1.0", @@ -41118,28 +40947,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-api": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-api/-/npm-api-1.0.1.tgz", - "integrity": "sha512-4sITrrzEbPcr0aNV28QyOmgn6C9yKiF8k92jn4buYAK8wmA5xo1qL3II5/gT1r7wxbXBflSduZ2K3FbtOrtGkA==", - "optional": true, - "dependencies": { - "clone-deep": "^4.0.1", - "download-stats": "^0.3.4", - "JSONStream": "^1.3.5", - "moment": "^2.24.0", - "node-fetch": "^2.6.0", - "paged-request": "^2.0.1" - }, - "engines": { - "node": ">=10.0" - } - }, "node_modules/npm-bundled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", - "dev": true, + "devOptional": true, "dependencies": { "npm-normalize-package-bin": "^1.0.1" } @@ -41148,7 +40960,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-4.0.0.tgz", "integrity": "sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w==", - "dev": true, + "devOptional": true, "dependencies": { "semver": "^7.1.1" }, @@ -41160,7 +40972,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -41172,7 +40984,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -41187,7 +40999,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true }, "node_modules/npm-lifecycle": { "version": "3.1.5", @@ -41209,13 +41021,13 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true + "devOptional": true }, "node_modules/npm-package-arg": { "version": "8.1.5", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.5.tgz", "integrity": "sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q==", - "dev": true, + "devOptional": true, "dependencies": { "hosted-git-info": "^4.0.1", "semver": "^7.3.4", @@ -41229,7 +41041,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, + "devOptional": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -41241,7 +41053,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -41253,7 +41065,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -41268,7 +41080,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true }, "node_modules/npm-packlist": { "version": "2.2.2", @@ -41292,7 +41104,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz", "integrity": "sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA==", - "dev": true, + "devOptional": true, "dependencies": { "npm-install-checks": "^4.0.0", "npm-normalize-package-bin": "^1.0.1", @@ -41304,7 +41116,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -41316,7 +41128,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "dependencies": { "lru-cache": "^6.0.0" }, @@ -41331,13 +41143,13 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true }, "node_modules/npm-registry-fetch": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz", "integrity": "sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA==", - "dev": true, + "devOptional": true, "dependencies": { "make-fetch-happen": "^9.0.1", "minipass": "^3.1.3", @@ -41354,7 +41166,7 @@ "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, + "devOptional": true, "dependencies": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", @@ -41383,7 +41195,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=10" } @@ -41392,7 +41204,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, + "devOptional": true, "dependencies": { "ms": "2.1.2" }, @@ -41409,7 +41221,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "dependencies": { "yallist": "^4.0.0" }, @@ -41421,7 +41233,7 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "dev": true, + "devOptional": true, "dependencies": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", @@ -41448,7 +41260,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "devOptional": true, "bin": { "mkdirp": "bin/cmd.js" }, @@ -41460,13 +41272,13 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true }, "node_modules/npm-registry-fetch/node_modules/negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.6" } @@ -41475,7 +41287,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", - "dev": true, + "devOptional": true, "dependencies": { "agent-base": "^6.0.2", "debug": "^4.3.1", @@ -41489,7 +41301,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, + "devOptional": true, "dependencies": { "minipass": "^3.1.1" }, @@ -41501,7 +41313,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true }, "node_modules/npm-run-path": { "version": "2.0.2", @@ -41984,7 +41796,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "devOptional": true, "dependencies": { "mimic-fn": "^2.1.0" }, @@ -42072,7 +41883,7 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, + "devOptional": true, "dependencies": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -42095,7 +41906,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">=8" } @@ -42104,7 +41915,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -42349,7 +42160,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-transform/-/p-transform-1.3.0.tgz", "integrity": "sha512-UJKdSzgd3KOnXXAtqN5+/eeHcvTn1hBkesEmElVgvO/NAYcxAvmjzIGmnNd3Tb/gRAvMBdNRFD4qAWdHxY6QXg==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "debug": "^4.3.2", @@ -42363,7 +42174,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "ms": "2.1.2" @@ -42381,7 +42192,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/p-try": { @@ -42428,7 +42239,7 @@ "version": "12.0.2", "resolved": "https://registry.npmjs.org/pacote/-/pacote-12.0.2.tgz", "integrity": "sha512-Ar3mhjcxhMzk+OVZ8pbnXdb0l8+pimvlsqBGRNkble2NVgyqOGE3yrCGi/lAYq7E7NRDMz89R1Wx5HIMCGgeYg==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@npmcli/git": "^2.1.0", @@ -42462,7 +42273,7 @@ "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@npmcli/fs": "^1.0.0", @@ -42492,7 +42303,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=10" @@ -42502,7 +42313,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-4.0.1.tgz", "integrity": "sha512-rzDQLaW4jQbh2YrOFlJdCtX8qgJTehFRYiUB2r1osqTeDzV/3+Jh8fz1oAPzUThf3iku8Ds4IDqawI5d8mUiQw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "minimatch": "^3.0.4" @@ -42515,7 +42326,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "yallist": "^4.0.0" @@ -42528,7 +42339,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "devOptional": true, "peer": true, "bin": { "mkdirp": "bin/cmd.js" @@ -42541,7 +42352,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-3.0.0.tgz", "integrity": "sha512-L/cbzmutAwII5glUcf2DBRNY/d0TFd4e/FnaZigJV6JD85RHZXJFGwCndjMWiiViiWSsWt3tiOLpI3ByTnIdFQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "glob": "^7.1.6", @@ -42560,7 +42371,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "minipass": "^3.1.1" @@ -42573,7 +42384,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/pad-component": { @@ -42592,18 +42403,6 @@ "node": ">=0.10.0" } }, - "node_modules/paged-request": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/paged-request/-/paged-request-2.0.2.tgz", - "integrity": "sha512-NWrGqneZImDdcMU/7vMcAOo1bIi5h/pmpJqe7/jdsy85BA/s5MSaU/KlpxwW/IVPmIwBcq2uKPrBWWhEWhtxag==", - "optional": true, - "dependencies": { - "axios": "^0.21.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -42660,7 +42459,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-1.1.1.tgz", "integrity": "sha512-4gySviBiW5TRl7XHvp1agcS7SOe0KZOjC//71dzZVWJrY9hCrgtvl5v3SyIxCZ4fZF47TxD9nfzmxcx76xmbUw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "json-parse-even-better-errors": "^2.3.0", @@ -42819,6 +42618,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true, "engines": { "node": ">=4" } @@ -43325,7 +43125,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.0.3.tgz", "integrity": "sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "find-up": "^5.0.0", @@ -43341,7 +43141,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "locate-path": "^6.0.0", @@ -43358,7 +43158,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "p-locate": "^5.0.0" @@ -43374,7 +43174,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "yocto-queue": "^0.1.0" @@ -43390,7 +43190,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "p-limit": "^3.0.2" @@ -43406,7 +43206,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" @@ -43420,14 +43220,6 @@ "node": ">= 0.8.0" } }, - "node_modules/prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/prettier": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", @@ -43468,6 +43260,8 @@ "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "devOptional": true, + "peer": true, "engines": { "node": ">=6" }, @@ -43554,7 +43348,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-1.0.0.tgz", "integrity": "sha512-aCk8AO51s+4JyuYGg3Q/a6gnrlDO09NpVWePtjp7xwphcoQ04x5WAfCyugcsbLooWcMJ87CLkD4+604IckEdhg==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/process": { @@ -43614,7 +43408,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", - "dev": true, + "devOptional": true, "peer": true, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -43624,7 +43418,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.1.tgz", "integrity": "sha512-3+hgaa19jzCGLuSCbieeRsu5C2joKfYn8pY6JAuXFRVfF4IO+L7UPpFWNTeWT9pM7uhskvbPPd/oEOktCn317Q==", - "dev": true, + "devOptional": true, "peer": true, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -43639,7 +43433,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, + "devOptional": true, "dependencies": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -43652,7 +43446,7 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true, + "devOptional": true, "engines": { "node": ">= 4" } @@ -45578,6 +45372,55 @@ "react-dom": "^16.6.0 || ^17.0.0" } }, + "node_modules/react-query": { + "version": "3.39.2", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.2.tgz", + "integrity": "sha512-F6hYDKyNgDQfQOuR1Rsp3VRzJnWHx6aRnnIZHMNGGgbL3SBgpZTDg8MQwmxOgpCAoqZJA+JSNCydF1xGJqKOCA==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/react-query/node_modules/broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, + "node_modules/react-query/node_modules/unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "node_modules/react-redux": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz", @@ -46092,31 +45935,11 @@ "node": ">=0.8" } }, - "node_modules/read-chunk": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-3.2.0.tgz", - "integrity": "sha512-CEjy9LCzhmD7nUpJ1oVOE6s/hBkejlcJEgLQHVnQznOSilOPb+kpKktlLfFDK3/WP43+F80xkUTM2VOkYoSYvQ==", - "dependencies": { - "pify": "^4.0.1", - "with-open-file": "^0.1.6" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/read-chunk/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "engines": { - "node": ">=6" - } - }, "node_modules/read-cmd-shim": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz", "integrity": "sha512-HJpV9bQpkl6KwjxlJcBoqu9Ba0PQg8TqSNIOrulGt54a0uup0HtevreFHzYzkm0lpnleRdNBzXznKrgxglEHQw==", - "dev": true + "devOptional": true }, "node_modules/read-package-json": { "version": "3.0.1", @@ -46137,7 +45960,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", - "dev": true, + "devOptional": true, "dependencies": { "json-parse-even-better-errors": "^2.3.0", "npm-normalize-package-bin": "^1.0.1" @@ -46367,7 +46190,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", - "dev": true, + "devOptional": true, "dependencies": { "debuglog": "^1.0.1", "dezalgo": "^1.0.0", @@ -47162,7 +46985,8 @@ "node_modules/remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "devOptional": true }, "node_modules/renderkid": { "version": "2.0.7", @@ -47616,7 +47440,7 @@ "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "devOptional": true, + "dev": true, "dependencies": { "tslib": "^1.9.0" }, @@ -47715,7 +47539,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-2.1.0.tgz", "integrity": "sha512-g3WxHrqSWCZHGHlSrF51VXFdjImhwvH8ZO/pryFH56Qi0cDsZfylQa/t0jCzVQFNbNvM00HfHjkDPEuarKDSWQ==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" @@ -47904,18 +47728,6 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, - "node_modules/set-getter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.1.tgz", - "integrity": "sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw==", - "optional": true, - "dependencies": { - "to-object-path": "^0.3.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -48032,6 +47844,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, "dependencies": { "shebang-regex": "^1.0.0" }, @@ -48043,6 +47856,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -48214,7 +48028,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 6.0.0", "npm": ">= 3.0.0" @@ -48353,7 +48167,7 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", - "dev": true, + "devOptional": true, "dependencies": { "ip": "^1.1.5", "smart-buffer": "^4.1.0" @@ -48404,7 +48218,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-4.2.0.tgz", "integrity": "sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg==", - "dev": true, "dependencies": { "is-plain-obj": "^2.0.0" }, @@ -48419,7 +48232,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, "engines": { "node": ">=8" } @@ -49188,11 +49000,6 @@ "node": ">=8" } }, - "node_modules/string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" - }, "node_modules/string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -49324,7 +49131,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true, + "devOptional": true, "engines": { "node": ">=4" } @@ -49334,6 +49141,7 @@ "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz", "integrity": "sha1-HLRar1dTD0yvhsf3UXnSyaUd1XI=", "devOptional": true, + "peer": true, "dependencies": { "is-utf8": "^0.2.1" }, @@ -49346,6 +49154,7 @@ "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", "devOptional": true, + "peer": true, "dependencies": { "first-chunk-stream": "^2.0.0", "strip-bom": "^2.0.0" @@ -49359,6 +49168,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "devOptional": true, + "peer": true, "dependencies": { "is-utf8": "^0.2.0" }, @@ -49388,7 +49198,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "devOptional": true, "engines": { "node": ">=6" } @@ -50318,7 +50127,7 @@ "version": "5.14.0", "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-5.14.0.tgz", "integrity": "sha512-4cAYwNFNYlIAHBUo7p6zw8POUvWbZor+/R0Tanv+rIhsauEyV9QSrEXL40pI+GfTQxKX8k6Tyw6CmdSDSmASrg==", - "dev": true, + "devOptional": true, "engines": { "node": ">=0.8" }, @@ -50419,14 +50228,6 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "devOptional": true }, - "node_modules/timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/timers-browserify": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", @@ -50670,7 +50471,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-1.0.4.tgz", "integrity": "sha512-whw60l7r+8ZU8Tu/Uc2yxtc4ZTZbR/PF3u1IPNKGQ6p8EICLb3Z2lAgoqw9bqYd8IkgnsaOcLzYHFckjqNsf0g==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/trim": { @@ -51041,7 +50842,7 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, + "devOptional": true, "dependencies": { "is-typedarray": "^1.0.0" } @@ -51447,20 +51248,12 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" } }, - "node_modules/unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", - "engines": { - "node": ">=4" - } - }, "node_modules/upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -51589,17 +51382,6 @@ "requires-port": "^1.0.0" } }, - "node_modules/url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "dependencies": { - "prepend-http": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/url/node_modules/punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", @@ -51793,7 +51575,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", - "dev": true, + "devOptional": true, "dependencies": { "builtins": "^1.0.3" } @@ -51876,6 +51658,8 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "devOptional": true, + "peer": true, "dependencies": { "clone": "^2.1.1", "clone-buffer": "^1.0.0", @@ -51893,6 +51677,7 @@ "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-3.0.0.tgz", "integrity": "sha1-sQTZ5ECf+jJfqt1SBkLQo7SIs2U=", "devOptional": true, + "peer": true, "dependencies": { "graceful-fs": "^4.1.2", "pify": "^2.3.0", @@ -51909,6 +51694,7 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "devOptional": true, + "peer": true, "engines": { "node": ">=0.10.0" } @@ -51967,7 +51753,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-1.0.0.tgz", "integrity": "sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/walker": { @@ -52094,7 +51880,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, + "devOptional": true, "dependencies": { "defaults": "^1.0.3" } @@ -53073,6 +52859,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -53104,7 +52891,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-pm/-/which-pm-2.0.0.tgz", "integrity": "sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "load-yaml-file": "^0.2.0", @@ -53118,7 +52905,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" @@ -53189,35 +52976,6 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, - "node_modules/with-open-file": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/with-open-file/-/with-open-file-0.1.7.tgz", - "integrity": "sha512-ecJS2/oHtESJ1t3ZfMI3B7KIDKyfN0O16miWxdn30zdh66Yd3LsRFebXZXq6GU4xfxLf6nVxp9kIqElb5fqczA==", - "dependencies": { - "p-finally": "^1.0.0", - "p-try": "^2.1.0", - "pify": "^4.0.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/with-open-file/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/with-open-file/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "engines": { - "node": ">=6" - } - }, "node_modules/word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -53330,7 +53088,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, + "devOptional": true, "dependencies": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -53819,7 +53577,7 @@ "version": "3.8.0", "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-3.8.0.tgz", "integrity": "sha512-BPo3btCxefe8NzDMk59QRDNBXMC4Ra6SHhFfEsV2DTmAp/6ZoovMANlJiWrXu41rtFQBmjH/rT2tSiHGowt38w==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@npmcli/arborist": "^4.0.4", @@ -53873,7 +53631,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" @@ -53883,7 +53641,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "delegates": "^1.0.0", @@ -53897,7 +53655,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" @@ -53907,7 +53665,7 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.1.0.tgz", "integrity": "sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">= 10" @@ -53917,7 +53675,7 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "path-key": "^3.1.0", @@ -53932,7 +53690,7 @@ "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": "*" @@ -53942,7 +53700,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "ms": "2.1.2" @@ -53960,7 +53718,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=0.3.1" @@ -53970,7 +53728,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=10" @@ -53983,7 +53741,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "cross-spawn": "^7.0.3", @@ -54007,7 +53765,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "locate-path": "^6.0.0", @@ -54024,7 +53782,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.1.tgz", "integrity": "sha512-6STz6KdQgxO4S/ko+AbjlFGGdGcknluoqU+79GOFCDqqyYj5OanQf9AjxwN0jCidtT+ziPMmPSt9E4hfQ0CwIQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "aproba": "^1.0.3 || ^2.0.0", @@ -54045,7 +53803,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=4" @@ -54055,7 +53813,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "is-fullwidth-code-point": "^2.0.0", @@ -54069,7 +53827,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "ansi-regex": "^3.0.0" @@ -54082,7 +53840,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=10" @@ -54095,7 +53853,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=10.17.0" @@ -54105,7 +53863,7 @@ "version": "8.2.0", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz", "integrity": "sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "ansi-escapes": "^4.2.1", @@ -54131,7 +53889,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" @@ -54144,7 +53902,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "p-locate": "^5.0.0" @@ -54160,7 +53918,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "yallist": "^4.0.0" @@ -54173,14 +53931,14 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/yeoman-environment/node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "path-key": "^3.0.0" @@ -54193,7 +53951,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "are-we-there-yet": "^2.0.0", @@ -54206,7 +53964,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "yocto-queue": "^0.1.0" @@ -54222,7 +53980,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "p-limit": "^3.0.2" @@ -54238,7 +53996,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" @@ -54248,7 +54006,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" @@ -54258,7 +54016,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "inherits": "^2.0.3", @@ -54273,7 +54031,7 @@ "version": "7.4.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "tslib": "~2.1.0" @@ -54283,7 +54041,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "lru-cache": "^6.0.0" @@ -54299,7 +54057,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "shebang-regex": "^3.0.0" @@ -54312,7 +54070,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" @@ -54322,7 +54080,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" @@ -54332,7 +54090,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "emoji-regex": "^8.0.0", @@ -54347,7 +54105,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=8" @@ -54357,7 +54115,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "ansi-regex": "^5.0.1" @@ -54370,14 +54128,14 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/yeoman-environment/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "isexe": "^2.0.0" @@ -54393,111 +54151,51 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/yeoman-generator": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-4.13.0.tgz", - "integrity": "sha512-f2/5N5IR3M2Ozm+QocvZQudlQITv2DwI6Mcxfy7R7gTTzaKgvUpgo/pQMJ+WQKm0KN0YMWCFOZpj0xFGxevc1w==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-5.7.0.tgz", + "integrity": "sha512-z9ZwgKoDOd+llPDCwn8Ax2l4In5FMhlslxdeByW4AMxhT+HbTExXKEAahsClHSbwZz1i5OzRwLwRIUdOJBr5Bw==", "dependencies": { - "async": "^2.6.2", - "chalk": "^2.4.2", - "cli-table": "^0.3.1", - "cross-spawn": "^6.0.5", - "dargs": "^6.1.0", - "dateformat": "^3.0.3", + "chalk": "^4.1.0", + "dargs": "^7.0.0", "debug": "^4.1.1", - "diff": "^4.0.1", - "error": "^7.0.2", - "find-up": "^3.0.0", - "github-username": "^3.0.0", - "istextorbinary": "^2.5.1", + "execa": "^5.1.1", + "github-username": "^6.0.0", "lodash": "^4.17.11", - "make-dir": "^3.0.0", - "mem-fs-editor": "^7.0.1", "minimist": "^1.2.5", - "pretty-bytes": "^5.2.0", - "read-chunk": "^3.2.0", - "read-pkg-up": "^5.0.0", - "rimraf": "^2.6.3", + "read-pkg-up": "^7.0.1", "run-async": "^2.0.0", "semver": "^7.2.1", - "shelljs": "^0.8.4", - "text-table": "^0.2.0", - "through2": "^3.0.1" + "shelljs": "^0.8.5", + "sort-keys": "^4.2.0", + "text-table": "^0.2.0" }, "engines": { - "node": ">=10" + "node": ">=12.10.0" }, - "optionalDependencies": { - "grouped-queue": "^1.1.0", - "yeoman-environment": "^2.9.5" - } - }, - "node_modules/yeoman-generator/node_modules/@nodelib/fs.stat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", - "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yeoman-generator/node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/yeoman-generator/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" + "peerDependencies": { + "yeoman-environment": "^3.2.0" }, - "engines": { - "node": ">=4" - } - }, - "node_modules/yeoman-generator/node_modules/arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "engines": { - "node": ">=8" - } - }, - "node_modules/yeoman-generator/node_modules/async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "dependencies": { - "lodash": "^4.17.14" + "peerDependenciesMeta": { + "yeoman-environment": { + "optional": true + } } }, - "node_modules/yeoman-generator/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "node_modules/yeoman-generator/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": ">=4" - } - }, - "node_modules/yeoman-generator/node_modules/dargs": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-6.1.0.tgz", - "integrity": "sha512-5dVBvpBLBnPwSsYXqfybFyehMmC/EenKEcf23AhCTgTf48JFBbmJKqoZBsERDnjL0FyiVTYWdFsRfTLHxLyKdQ==", - "engines": { - "node": ">=6" + "node": ">= 8" } }, "node_modules/yeoman-generator/node_modules/debug": { @@ -54516,39 +54214,19 @@ } } }, - "node_modules/yeoman-generator/node_modules/dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "dependencies": { - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/yeoman-generator/node_modules/error": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", - "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", - "dependencies": { - "string-template": "~0.2.1" - } - }, "node_modules/yeoman-generator/node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "optional": true, + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" }, "engines": { @@ -54558,142 +54236,29 @@ "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/yeoman-generator/node_modules/execa/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "optional": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/yeoman-generator/node_modules/fast-glob": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", - "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" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/yeoman-generator/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/yeoman-generator/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "optional": true, - "dependencies": { - "pump": "^3.0.0" - }, + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yeoman-generator/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/yeoman-generator/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yeoman-generator/node_modules/globby": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", - "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" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yeoman-generator/node_modules/globby/node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yeoman-generator/node_modules/grouped-queue": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-1.1.0.tgz", - "integrity": "sha512-rZOFKfCqLhsu5VqjBjEWiwrYqJR07KxIkH4mLZlNlGDfntbb4FbMyGFP14TlvRPrU9S3Hnn/sgxbC5ZeN0no3Q==", - "optional": true, - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/yeoman-generator/node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/yeoman-generator/node_modules/is-scoped": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-1.0.0.tgz", - "integrity": "sha1-RJypgpnnEwOCViieyytUDcQ3yzA=", - "optional": true, - "dependencies": { - "scoped-regex": "^1.0.0" - }, + "node_modules/yeoman-generator/node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "engines": { - "node": ">=4" + "node": ">=10.17.0" } }, "node_modules/yeoman-generator/node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "optional": true, "engines": { "node": ">=8" }, @@ -54701,30 +54266,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/yeoman-generator/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yeoman-generator/node_modules/log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "optional": true, - "dependencies": { - "chalk": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/yeoman-generator/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -54736,110 +54277,15 @@ "node": ">=10" } }, - "node_modules/yeoman-generator/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yeoman-generator/node_modules/make-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/yeoman-generator/node_modules/mem-fs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-1.2.0.tgz", - "integrity": "sha512-b8g0jWKdl8pM0LqAPdK9i8ERL7nYrzmJfRhxMiWH2uYdfYnb7uXnmwVb0ZGe7xyEl4lj+nLIU3yf4zPUT+XsVQ==", - "optional": true, - "dependencies": { - "through2": "^3.0.0", - "vinyl": "^2.0.1", - "vinyl-file": "^3.0.0" - } - }, - "node_modules/yeoman-generator/node_modules/mem-fs-editor": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-7.1.0.tgz", - "integrity": "sha512-BH6QEqCXSqGeX48V7zu+e3cMwHU7x640NB8Zk8VNvVZniz+p4FK60pMx/3yfkzo6miI6G3a8pH6z7FeuIzqrzA==", - "dependencies": { - "commondir": "^1.0.1", - "deep-extend": "^0.6.0", - "ejs": "^3.1.5", - "glob": "^7.1.4", - "globby": "^9.2.0", - "isbinaryfile": "^4.0.0", - "mkdirp": "^1.0.0", - "multimatch": "^4.0.0", - "rimraf": "^3.0.0", - "through2": "^3.0.2", - "vinyl": "^2.2.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/yeoman-generator/node_modules/mem-fs-editor/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/yeoman-generator/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/yeoman-generator/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/yeoman-generator/node_modules/multimatch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", - "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", - "dependencies": { - "@types/minimatch": "^3.0.3", - "array-differ": "^3.0.0", - "array-union": "^2.1.0", - "arrify": "^2.0.1", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/yeoman-generator/node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "optional": true, "dependencies": { "path-key": "^3.0.0" }, @@ -54847,92 +54293,18 @@ "node": ">=8" } }, - "node_modules/yeoman-generator/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yeoman-generator/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yeoman-generator/node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, "node_modules/yeoman-generator/node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "optional": true, "engines": { "node": ">=8" } }, - "node_modules/yeoman-generator/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "engines": { - "node": ">=6" - } - }, - "node_modules/yeoman-generator/node_modules/read-pkg-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-5.0.0.tgz", - "integrity": "sha512-XBQjqOBtTzyol2CpsQOw8LHV0XbDZVG7xMMjmXAJomlVY03WOBRmYgDJETlvcg0H63AJvPRwT7GFi5rvOzUOKg==", - "dependencies": { - "find-up": "^3.0.0", - "read-pkg": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yeoman-generator/node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/yeoman-generator/node_modules/scoped-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-1.0.0.tgz", - "integrity": "sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=", - "optional": true, - "engines": { - "node": ">=4" - } - }, "node_modules/yeoman-generator/node_modules/semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "dependencies": { "lru-cache": "^6.0.0" }, @@ -54947,7 +54319,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "optional": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -54959,57 +54330,14 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "optional": true, "engines": { "node": ">=8" } }, - "node_modules/yeoman-generator/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "optional": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/yeoman-generator/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/yeoman-generator/node_modules/through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - }, - "node_modules/yeoman-generator/node_modules/untildify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", - "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==", - "optional": true, - "engines": { - "node": ">=4" - } - }, "node_modules/yeoman-generator/node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "optional": true, "dependencies": { "isexe": "^2.0.0" }, @@ -55025,226 +54353,6 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, - "node_modules/yeoman-generator/node_modules/yeoman-environment": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-2.10.3.tgz", - "integrity": "sha512-pLIhhU9z/G+kjOXmJ2bPFm3nejfbH+f1fjYRSOteEXDBrv1EoJE/e+kuHixSXfCYfTkxjYsvRaDX+1QykLCnpQ==", - "optional": true, - "dependencies": { - "chalk": "^2.4.1", - "debug": "^3.1.0", - "diff": "^3.5.0", - "escape-string-regexp": "^1.0.2", - "execa": "^4.0.0", - "globby": "^8.0.1", - "grouped-queue": "^1.1.0", - "inquirer": "^7.1.0", - "is-scoped": "^1.0.0", - "lodash": "^4.17.10", - "log-symbols": "^2.2.0", - "mem-fs": "^1.1.0", - "mem-fs-editor": "^6.0.0", - "npm-api": "^1.0.0", - "semver": "^7.1.3", - "strip-ansi": "^4.0.0", - "text-table": "^0.2.0", - "untildify": "^3.0.3", - "yeoman-generator": "^4.8.2" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "optional": true, - "dependencies": { - "array-uniq": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "optional": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "optional": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", - "optional": true, - "dependencies": { - "arrify": "^1.0.1", - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/ejs": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", - "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", - "hasInstallScript": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/globby": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", - "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", - "optional": true, - "dependencies": { - "array-union": "^1.0.1", - "dir-glob": "2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "optional": true - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/mem-fs-editor": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-6.0.0.tgz", - "integrity": "sha512-e0WfJAMm8Gv1mP5fEq/Blzy6Lt1VbLg7gNnZmZak7nhrBTibs+c6nQ4SKs/ZyJYHS1mFgDJeopsLAv7Ow0FMFg==", - "optional": true, - "dependencies": { - "commondir": "^1.0.1", - "deep-extend": "^0.6.0", - "ejs": "^2.6.1", - "glob": "^7.1.4", - "globby": "^9.2.0", - "isbinaryfile": "^4.0.0", - "mkdirp": "^0.5.0", - "multimatch": "^4.0.0", - "rimraf": "^2.6.3", - "through2": "^3.0.1", - "vinyl": "^2.2.0" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/mem-fs-editor/node_modules/dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "optional": true, - "dependencies": { - "path-type": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/mem-fs-editor/node_modules/globby": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", - "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", - "optional": true, - "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" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/mem-fs-editor/node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "optional": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/mem-fs-editor/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/mem-fs-editor/node_modules/slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "optional": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "optional": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "optional": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/yeoman-generator/node_modules/yeoman-environment/node_modules/slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/yeoman-test": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/yeoman-test/-/yeoman-test-6.2.0.tgz", @@ -55535,7 +54643,7 @@ "dependencies": { "chalk": "^4.0.0", "lodash": "^4.17.11", - "yeoman-generator": "^4.0.0", + "yeoman-generator": "^5.7.0", "yosay": "^2.0.2" }, "devDependencies": { @@ -61398,7 +60506,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/@isaacs/string-locale-compare/-/string-locale-compare-1.1.0.tgz", "integrity": "sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==", - "dev": true, + "devOptional": true, "peer": true }, "@istanbuljs/load-nyc-config": { @@ -64924,7 +64032,7 @@ "version": "4.0.5", "resolved": "https://registry.npmjs.org/@npmcli/arborist/-/arborist-4.0.5.tgz", "integrity": "sha512-WR2cqxzjsvmHJ9sKCdqBYG/qeiAXB9ev1iq1W2Rry7LxeJ7eDtTr4mOWe/TBvp6xFzevGecQc2YEWwExTuLZLg==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "@isaacs/string-locale-compare": "^1.1.0", @@ -64965,7 +64073,7 @@ "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "@npmcli/fs": "^1.0.0", @@ -64992,14 +64100,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, + "devOptional": true, "peer": true }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "yallist": "^4.0.0" @@ -65009,14 +64117,14 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "devOptional": true, "peer": true }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "lru-cache": "^6.0.0" @@ -65026,7 +64134,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "minipass": "^3.1.1" @@ -65036,7 +64144,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, + "devOptional": true, "peer": true } } @@ -65083,7 +64191,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@npmcli/git/-/git-2.1.0.tgz", "integrity": "sha512-/hBFX/QG1b+N7PZBFs0bi+evgRZcK9nWBxQKZkGoXUT5hJSwl5c4d7y8/hm+NQZRPhQ67RzFaj5UM9YeyKoryw==", - "dev": true, + "devOptional": true, "requires": { "@npmcli/promise-spawn": "^1.3.2", "lru-cache": "^6.0.0", @@ -65099,7 +64207,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "requires": { "yallist": "^4.0.0" } @@ -65108,13 +64216,13 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true + "devOptional": true }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "requires": { "lru-cache": "^6.0.0" } @@ -65123,7 +64231,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "requires": { "isexe": "^2.0.0" } @@ -65132,7 +64240,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true } } }, @@ -65140,7 +64248,7 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-1.0.7.tgz", "integrity": "sha512-9rufe0wnJusCQoLpV9ZPKIVP55itrM5BxOXs10DmdbRfgWtHy1LDyskbwRnBghuB0PrF7pNPOqREVtpz4HqzKw==", - "dev": true, + "devOptional": true, "requires": { "npm-bundled": "^1.1.1", "npm-normalize-package-bin": "^1.0.1" @@ -65150,7 +64258,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@npmcli/map-workspaces/-/map-workspaces-2.0.0.tgz", "integrity": "sha512-QBJfpCY1NOAkkW3lFfru9VTdqvMB2TN0/vrevl5xBCv5Fi0XDVcA6rqqSau4Ysi4Iw3fBzyXV7hzyTBDfadf7g==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "@npmcli/name-from-folder": "^1.0.1", @@ -65163,7 +64271,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@npmcli/metavuln-calculator/-/metavuln-calculator-2.0.0.tgz", "integrity": "sha512-VVW+JhWCKRwCTE+0xvD6p3uV4WpqocNYYtzyvenqL/u1Q3Xx6fGTJ+6UoIoii07fbuEO9U3IIyuGY0CYHDv1sg==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "cacache": "^15.0.5", @@ -65176,7 +64284,7 @@ "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "@npmcli/fs": "^1.0.0", @@ -65203,14 +64311,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, + "devOptional": true, "peer": true }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "yallist": "^4.0.0" @@ -65220,14 +64328,14 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "devOptional": true, "peer": true }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "lru-cache": "^6.0.0" @@ -65237,7 +64345,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "minipass": "^3.1.1" @@ -65247,7 +64355,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, + "devOptional": true, "peer": true } } @@ -65272,20 +64380,20 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/@npmcli/name-from-folder/-/name-from-folder-1.0.1.tgz", "integrity": "sha512-qq3oEfcLFwNfEYOQ8HLimRGKlD8WSeGEdtUa7hmzpR8Sa7haL1KVQrvgO6wqMjhWFFVjgtrh1gIxDz+P8sjUaA==", - "dev": true, + "devOptional": true, "peer": true }, "@npmcli/node-gyp": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@npmcli/node-gyp/-/node-gyp-1.0.3.tgz", "integrity": "sha512-fnkhw+fmX65kiLqk6E3BFLXNC26rUhK90zVwe2yncPliVT/Qos3xjhTLE59Df8KnPlcwIERXKVlU1bXoUQ+liA==", - "dev": true + "devOptional": true }, "@npmcli/package-json": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@npmcli/package-json/-/package-json-1.0.1.tgz", "integrity": "sha512-y6jnu76E9C23osz8gEMBayZmaZ69vFOIk8vR1FJL/wbEJ54+9aVG9rLTjQKSXfgYZEr50nw1txBBFfBZZe+bYg==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "json-parse-even-better-errors": "^2.3.1" @@ -65295,7 +64403,7 @@ "version": "1.3.2", "resolved": "https://registry.npmjs.org/@npmcli/promise-spawn/-/promise-spawn-1.3.2.tgz", "integrity": "sha512-QyAGYo/Fbj4MXeGdJcFzZ+FkDkomfRBrPM+9QYJSg+PxgAUL+LU3FneQk37rKR2/zjqkCV1BLHccX98wRXG3Sg==", - "dev": true, + "devOptional": true, "requires": { "infer-owner": "^1.0.4" } @@ -65304,7 +64412,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-2.0.0.tgz", "integrity": "sha512-fSan/Pu11xS/TdaTpTB0MRn9guwGU8dye+x56mEVgBEd/QsybBbYcAL0phPXi8SGWFEChkQd6M9qL4y6VOpFig==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "@npmcli/node-gyp": "^1.0.2", @@ -65317,14 +64425,14 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, + "devOptional": true, "peer": true }, "are-we-there-yet": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "delegates": "^1.0.0", @@ -65335,7 +64443,7 @@ "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "@npmcli/fs": "^1.0.0", @@ -65362,14 +64470,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, + "devOptional": true, "peer": true }, "debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "ms": "2.1.2" @@ -65379,7 +64487,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.0.tgz", "integrity": "sha512-F8sU45yQpjQjxKkm1UOAhf0U/O0aFt//Fl7hsrNVto+patMHjs7dPI9mFOGUKbhrgKm0S3EjW3scMFuQmWSROw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "ansi-regex": "^5.0.1", @@ -65397,14 +64505,14 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, + "devOptional": true, "peer": true }, "lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "yallist": "^4.0.0" @@ -65414,7 +64522,7 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "agentkeepalive": "^4.1.3", @@ -65439,28 +64547,28 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "devOptional": true, "peer": true }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, + "devOptional": true, "peer": true }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true, + "devOptional": true, "peer": true }, "node-gyp": { "version": "8.4.1", "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "env-paths": "^2.2.0", @@ -65479,7 +64587,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.0.tgz", "integrity": "sha512-03ppFRGlsyUaQFbGC2C8QWJN/C/K7PsfyD9aQdhVKAQIH4sQBc8WASqFBP7O+Ut4d2oo5LoeoboB3cGdBZSp6Q==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "are-we-there-yet": "^2.0.0", @@ -65492,7 +64600,7 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "inherits": "^2.0.3", @@ -65504,7 +64612,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "lru-cache": "^6.0.0" @@ -65514,7 +64622,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "agent-base": "^6.0.2", @@ -65526,7 +64634,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "minipass": "^3.1.1" @@ -65536,7 +64644,7 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "emoji-regex": "^8.0.0", @@ -65548,7 +64656,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "ansi-regex": "^5.0.1" @@ -65558,7 +64666,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "isexe": "^2.0.0" @@ -65568,7 +64676,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, + "devOptional": true, "peer": true } } @@ -65577,7 +64685,6 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", - "dev": true, "requires": { "@octokit/types": "^6.0.3" } @@ -65586,7 +64693,6 @@ "version": "3.6.0", "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", - "dev": true, "requires": { "@octokit/auth-token": "^2.4.4", "@octokit/graphql": "^4.5.8", @@ -65601,7 +64707,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "dev": true, "requires": { "@octokit/types": "^6.0.3", "deprecation": "^2.0.0", @@ -65611,8 +64716,7 @@ "universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", - "dev": true + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" } } }, @@ -65620,7 +64724,6 @@ "version": "6.0.12", "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", - "dev": true, "requires": { "@octokit/types": "^6.0.3", "is-plain-object": "^5.0.0", @@ -65630,14 +64733,12 @@ "is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" }, "universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", - "dev": true + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" } } }, @@ -65645,7 +64746,6 @@ "version": "4.8.0", "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", - "dev": true, "requires": { "@octokit/request": "^5.6.0", "@octokit/types": "^6.0.3", @@ -65655,16 +64755,14 @@ "universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", - "dev": true + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" } } }, "@octokit/openapi-types": { "version": "11.2.0", "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", - "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==", - "dev": true + "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==" }, "@octokit/plugin-enterprise-rest": { "version": "6.0.1", @@ -65676,7 +64774,6 @@ "version": "2.17.0", "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz", "integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==", - "dev": true, "requires": { "@octokit/types": "^6.34.0" } @@ -65685,14 +64782,12 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", - "dev": true, "requires": {} }, "@octokit/plugin-rest-endpoint-methods": { "version": "5.13.0", "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.13.0.tgz", "integrity": "sha512-uJjMTkN1KaOIgNtUPMtIXDOjx6dGYysdIFhgA52x4xSadQCz3b/zJexvITDVpANnfKPW/+E0xkOvLntqMYpviA==", - "dev": true, "requires": { "@octokit/types": "^6.34.0", "deprecation": "^2.3.1" @@ -65702,7 +64797,6 @@ "version": "5.6.3", "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", - "dev": true, "requires": { "@octokit/endpoint": "^6.0.1", "@octokit/request-error": "^2.1.0", @@ -65716,7 +64810,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "dev": true, "requires": { "@octokit/types": "^6.0.3", "deprecation": "^2.0.0", @@ -65726,14 +64819,12 @@ "is-plain-object": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", - "dev": true + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" }, "universal-user-agent": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==", - "dev": true + "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" } } }, @@ -65741,7 +64832,6 @@ "version": "18.12.0", "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz", "integrity": "sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q==", - "dev": true, "requires": { "@octokit/core": "^3.5.1", "@octokit/plugin-paginate-rest": "^2.16.8", @@ -65753,7 +64843,6 @@ "version": "6.34.0", "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", - "dev": true, "requires": { "@octokit/openapi-types": "^11.2.0" } @@ -70658,7 +69747,7 @@ "fs-extra": "^10.0.0", "lodash": "^4.17.11", "yeoman-assert": "^3.1.0", - "yeoman-generator": "^4.0.0", + "yeoman-generator": "^5.7.0", "yeoman-test": "^6.2.0", "yosay": "^2.0.2" } @@ -71447,7 +70536,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true + "devOptional": true }, "@trysound/sax": { "version": "0.1.1", @@ -71672,7 +70761,7 @@ "version": "1.20.4", "resolved": "https://registry.npmjs.org/@types/expect/-/expect-1.20.4.tgz", "integrity": "sha512-Q5Vn3yjTDyCMV50TB6VRIbQNxSE4OmZR86VSbGaNpfUolm0iePBB4KdEEHmxoY5sT2+2DIvXW0rvMDP2nHZ4Mg==", - "dev": true, + "devOptional": true, "peer": true }, "@types/fetch-mock": { @@ -72235,7 +71324,7 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/vinyl/-/vinyl-2.0.6.tgz", "integrity": "sha512-ayJ0iOCDNHnKpKTgBG6Q6JOnHTj9zFta+3j2b8Ejza0e4cvRyMn0ZoLEmbPrTHe5YYRlDYPvPWVdV4cTaRyH7g==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "@types/expect": "^1.20.4", @@ -73251,7 +72340,7 @@ "version": "6.0.2", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, + "devOptional": true, "requires": { "debug": "4" }, @@ -73260,7 +72349,7 @@ "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, + "devOptional": true, "requires": { "ms": "2.1.2" } @@ -73269,7 +72358,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true } } }, @@ -73277,7 +72366,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.0.tgz", "integrity": "sha512-0PhAp58jZNw13UJv7NVdTGb0ZcghHUb3DrZ046JiiJY/BOaTTpbwdHq2VObPCBV8M2GPh7sgrJ3AQ8Ey468LJw==", - "dev": true, + "devOptional": true, "requires": { "debug": "^4.1.0", "depd": "^1.1.2", @@ -73288,7 +72377,7 @@ "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, + "devOptional": true, "requires": { "ms": "2.1.2" } @@ -73297,7 +72386,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true } } }, @@ -73763,7 +72852,8 @@ "array-differ": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", - "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==" + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "devOptional": true }, "array-equal": { "version": "1.0.0", @@ -73969,7 +73059,8 @@ "async": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", - "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==", + "devOptional": true }, "async-each": { "version": "1.0.3", @@ -74064,7 +73155,7 @@ "version": "0.21.4", "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "devOptional": true, + "dev": true, "requires": { "follow-redirects": "^1.14.0" } @@ -74663,8 +73754,7 @@ "before-after-hook": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", - "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==", - "dev": true + "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" }, "better-opn": { "version": "2.1.1", @@ -74693,7 +73783,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/bin-links/-/bin-links-2.3.0.tgz", "integrity": "sha512-JzrOLHLwX2zMqKdyYZjkDgQGT+kHDkIhv2/IK2lJ00qLxV4TmFoHi8drDBb6H5Zrz1YfgHkai4e2MGPqnoUhqA==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "cmd-shim": "^4.0.1", @@ -74714,7 +73804,7 @@ "version": "4.18.0", "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-4.18.0.tgz", "integrity": "sha512-PQu3Kyv9dM4FnwB7XGj1+HucW+ShvJzJqjuw1JkKVs1mWdwOKVcRjOi+pV9X52A0tNvrPCsPkbFFQb+wE1EAXw==", - "dev": true + "devOptional": true }, "bindings": { "version": "1.5.0", @@ -74729,7 +73819,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, + "devOptional": true, "requires": { "buffer": "^5.5.0", "inherits": "^2.0.4", @@ -74740,7 +73830,7 @@ "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, + "devOptional": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -74750,13 +73840,13 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "devOptional": true }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, + "devOptional": true, "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -75155,7 +74245,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", - "dev": true + "devOptional": true }, "byline": { "version": "5.0.0", @@ -75489,11 +74579,6 @@ "rsvp": "^4.8.4" } }, - "capture-stack-trace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", - "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" - }, "cardinal": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/cardinal/-/cardinal-0.4.4.tgz", @@ -75806,12 +74891,14 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.1.tgz", "integrity": "sha512-x/5fWmGMnbKQAaNwN+UZlV79qBLM9JFnJuJ03gIi5whrob0xV0ofNVHy9DhwGdsMJQc2OKv0oGmLzvaqvAVv+g==", - "dev": true + "devOptional": true }, "cli-table": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.9.tgz", "integrity": "sha512-7eA6hFtAZwVx3dWAGoaBqTrzWko5jRUFKpHT64ZHkJpaA3y5wf5NlLjguqTRmqycatJZiwftODYYyGNLbQ7MuA==", + "devOptional": true, + "peer": true, "requires": { "colors": "1.0.3", "strip-ansi": "^6.0.1" @@ -75820,17 +74907,23 @@ "ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "devOptional": true, + "peer": true }, "colors": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", + "devOptional": true, + "peer": true }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "devOptional": true, + "peer": true, "requires": { "ansi-regex": "^5.0.1" } @@ -75986,12 +75079,16 @@ "clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "devOptional": true, + "peer": true }, "clone-buffer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=" + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "devOptional": true, + "peer": true }, "clone-deep": { "version": "4.0.1", @@ -76013,12 +75110,16 @@ "clone-stats": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=" + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "devOptional": true, + "peer": true }, "cloneable-readable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "devOptional": true, + "peer": true, "requires": { "inherits": "^2.0.1", "process-nextick-args": "^2.0.0", @@ -76029,7 +75130,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/cmd-shim/-/cmd-shim-4.1.0.tgz", "integrity": "sha512-lb9L7EM4I/ZRVuljLPEtUJOP+xiQVknZ4ZMpMgEp4JzNldPb27HU03hi6K1/6CoIuit/Zm/LQXySErFeXxDprw==", - "dev": true, + "devOptional": true, "requires": { "mkdirp-infer-owner": "^2.0.0" } @@ -76241,7 +75342,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", "integrity": "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==", - "dev": true, + "devOptional": true, "peer": true }, "common-path-prefix": { @@ -77003,14 +76104,6 @@ } } }, - "create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "requires": { - "capture-stack-trace": "^1.0.0" - } - }, "create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", @@ -77057,6 +76150,7 @@ "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, "requires": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -78267,8 +77361,7 @@ "dargs": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/dargs/-/dargs-7.0.0.tgz", - "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==", - "dev": true + "integrity": "sha512-2iy1EkLdlBzQGvbweYRFxmFath8+K7+AKB0TlhHWkNuH+TmovaMH/Wp7V7R4u7f4SnX3OgLsU9t1NI9ioDnUpg==" }, "dashdash": { "version": "1.14.1", @@ -78364,7 +77457,8 @@ "dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==", + "dev": true }, "dayjs": { "version": "1.10.7", @@ -78383,7 +77477,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", - "dev": true + "devOptional": true }, "decamelize": { "version": "1.2.0", @@ -78616,7 +77710,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true, + "devOptional": true, "requires": { "clone": "^1.0.2" }, @@ -78625,7 +77719,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true + "devOptional": true } } }, @@ -78703,8 +77797,7 @@ "deprecation": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==", - "dev": true + "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" }, "des.js": { "version": "1.0.1", @@ -78764,7 +77857,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", - "dev": true, + "devOptional": true, "requires": { "asap": "^2.0.0", "wrappy": "1" @@ -78773,7 +77866,8 @@ "diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true }, "diff-match-patch": { "version": "1.0.5", @@ -79009,17 +78103,6 @@ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" }, - "download-stats": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/download-stats/-/download-stats-0.3.4.tgz", - "integrity": "sha512-ic2BigbyUWx7/CBbsfGjf71zUNZB4edBGC3oRliSzsoNmvyVx3Ycfp1w3vp2Y78Ee0eIIkjIEO5KzW0zThDGaA==", - "optional": true, - "requires": { - "JSONStream": "^1.2.1", - "lazy-cache": "^2.0.1", - "moment": "^2.15.1" - } - }, "downshift": { "version": "6.1.7", "resolved": "https://registry.npmjs.org/downshift/-/downshift-6.1.7.tgz", @@ -79063,11 +78146,6 @@ "readable-stream": "^2.0.2" } }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -79110,22 +78188,6 @@ } } }, - "editions": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/editions/-/editions-2.3.1.tgz", - "integrity": "sha512-ptGvkwTvGdGfC0hfhKg0MT+TRLRKGtUiWGBInxOm5pz7ssADezahjCUaYuZ8Dr+C05FW0AECIIPt4WBxVINEhA==", - "requires": { - "errlop": "^2.0.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, "editorconfig": { "version": "0.15.3", "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-0.15.3.tgz", @@ -79162,6 +78224,7 @@ "version": "3.1.8", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz", "integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==", + "devOptional": true, "requires": { "jake": "^10.8.5" } @@ -79481,7 +78544,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "dev": true + "devOptional": true }, "envinfo": { "version": "7.8.1", @@ -79600,12 +78663,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", - "dev": true - }, - "errlop": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/errlop/-/errlop-2.2.0.tgz", - "integrity": "sha512-e64Qj9+4aZzjzzFpZC7p5kmm/ccCrbLhAJplhsDXQFs87XTsXwOpH4s1Io2s90Tau/8r2j9f4l/thhDevRjzxw==" + "devOptional": true }, "errno": { "version": "0.1.7", @@ -79619,7 +78677,7 @@ "version": "10.4.0", "resolved": "https://registry.npmjs.org/error/-/error-10.4.0.tgz", "integrity": "sha512-YxIFEJuhgcICugOUvRx5th0UM+ActZ9sjY0QJmeVwsQdvosZ7kYzc9QqS0Da3R5iUmgU5meGIxh0xBeZpMVeLw==", - "dev": true, + "devOptional": true, "peer": true }, "error-ex": { @@ -81241,6 +80299,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "devOptional": true, "requires": { "minimatch": "^5.0.1" }, @@ -81249,6 +80308,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "devOptional": true, "requires": { "balanced-match": "^1.0.0" } @@ -81257,6 +80317,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz", "integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==", + "devOptional": true, "requires": { "brace-expansion": "^2.0.1" } @@ -81479,7 +80540,7 @@ "version": "1.2.16", "resolved": "https://registry.npmjs.org/find-yarn-workspace-root2/-/find-yarn-workspace-root2-1.2.16.tgz", "integrity": "sha512-hr6hb1w8ePMpPVUK39S4RlwJzi+xPLuVuG8XlwXU3KD5Yn3qgBWVfy3AzNlDhWvE1EORCE65/Qm26rFQt3VLVA==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "micromatch": "^4.0.2", @@ -81490,7 +80551,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "fill-range": "^7.0.1" @@ -81500,7 +80561,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "to-regex-range": "^5.0.1" @@ -81510,7 +80571,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "locate-path": "^5.0.0", @@ -81521,14 +80582,14 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, + "devOptional": true, "peer": true }, "locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "p-locate": "^4.1.0" @@ -81538,7 +80599,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "braces": "^3.0.1", @@ -81549,7 +80610,7 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "p-try": "^2.0.0" @@ -81559,7 +80620,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "p-limit": "^2.2.0" @@ -81569,21 +80630,21 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, + "devOptional": true, "peer": true }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "peer": true }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "find-up": "^4.0.0" @@ -81593,7 +80654,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "is-number": "^7.0.0" @@ -81606,6 +80667,7 @@ "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", "devOptional": true, + "peer": true, "requires": { "readable-stream": "^2.0.2" } @@ -82204,15 +81266,6 @@ "assert-plus": "^1.0.0" } }, - "gh-got": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-5.0.0.tgz", - "integrity": "sha1-7pW+NxBv2HSKlvjR20uuqJ4b+oo=", - "requires": { - "got": "^6.2.0", - "is-plain-obj": "^1.1.0" - } - }, "gh-pages": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz", @@ -82471,11 +81524,11 @@ "dev": true }, "github-username": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/github-username/-/github-username-3.0.0.tgz", - "integrity": "sha1-CnciGbMTB0NCnyRW0L3T21Xc57E=", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/github-username/-/github-username-6.0.0.tgz", + "integrity": "sha512-7TTrRjxblSI5l6adk9zd+cV5d6i1OrJSo3Vr9xdGqFLBQo0mz5P9eIfKCDJ7eekVGGFLbce0qbPSnktXV2BjDQ==", "requires": { - "gh-got": "^5.0.0" + "@octokit/rest": "^18.0.6" } }, "gl-matrix": { @@ -82597,31 +81650,6 @@ } } }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - }, - "dependencies": { - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - } - } - }, "graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", @@ -82644,7 +81672,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-2.0.0.tgz", "integrity": "sha512-/PiFUa7WIsl48dUeCvhIHnwNmAAzlI/eHoJl0vu3nsFA366JleY7Ff8EVTplZu5kO0MIdZjKTTnzItL61ahbnw==", - "dev": true, + "devOptional": true, "peer": true }, "growly": { @@ -83290,7 +82318,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz", "integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==", - "dev": true + "devOptional": true }, "http-deceiver": { "version": "1.2.7", @@ -83331,7 +82359,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, + "devOptional": true, "requires": { "@tootallnate/once": "1", "agent-base": "6", @@ -83342,7 +82370,7 @@ "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, + "devOptional": true, "requires": { "ms": "2.1.2" } @@ -83351,7 +82379,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true } } }, @@ -83439,7 +82467,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, + "devOptional": true, "requires": { "agent-base": "6", "debug": "4" @@ -83449,7 +82477,7 @@ "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", - "dev": true, + "devOptional": true, "requires": { "ms": "2.1.2" } @@ -83458,7 +82486,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true } } }, @@ -83466,13 +82494,13 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "devOptional": true + "dev": true }, "humanize-ms": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", "integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=", - "dev": true, + "devOptional": true, "requires": { "ms": "^2.0.0" } @@ -83767,7 +82795,7 @@ "version": "7.3.3", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "devOptional": true, + "dev": true, "requires": { "ansi-escapes": "^4.2.1", "chalk": "^4.1.0", @@ -83788,19 +82816,19 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "devOptional": true + "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "devOptional": true + "dev": true }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "devOptional": true, + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -83811,7 +82839,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "devOptional": true, + "dev": true, "requires": { "ansi-regex": "^5.0.1" } @@ -84103,7 +83131,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true + "devOptional": true }, "is-ip": { "version": "3.1.0", @@ -84126,7 +83154,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", "integrity": "sha1-PZh3iZ5qU+/AFgUEzeFfgubwYdU=", - "dev": true + "devOptional": true }, "is-map": { "version": "2.0.2", @@ -84221,11 +83249,6 @@ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==", "dev": true }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" - }, "is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -84246,16 +83269,11 @@ "resolved": "https://registry.npmjs.org/is-retina/-/is-retina-1.0.3.tgz", "integrity": "sha1-10AbKGvqKuN/Ykd1iN5QTQuGR+M=" }, - "is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" - }, "is-scoped": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-2.1.0.tgz", "integrity": "sha512-Cv4OpPTHAK9kHYzkzCrof3VJh7H/PrG2MBUMvvJebaaUMbqhm0YAtXnvh0I3Hnj2tMZWwrRROWLSgfJrKqWmlQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "scoped-regex": "^2.0.0" @@ -84319,19 +83337,20 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true + "devOptional": true }, "is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true + "devOptional": true }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "devOptional": true + "devOptional": true, + "peer": true }, "is-weakref": { "version": "1.0.1", @@ -84374,7 +83393,8 @@ "isbinaryfile": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.8.tgz", - "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==" + "integrity": "sha512-53h6XFniq77YdW+spoRrebh0mnmTxRPTlcuIArO57lmMdq4uBKFKaeTjnb92oYWrSn/LVL+LT+Hap2tFQj8V+w==", + "devOptional": true }, "isexe": { "version": "2.0.0", @@ -84606,28 +83626,6 @@ "istanbul-lib-report": "^3.0.0" } }, - "istextorbinary": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.6.0.tgz", - "integrity": "sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==", - "requires": { - "binaryextensions": "^2.1.2", - "editions": "^2.2.0", - "textextensions": "^2.5.0" - }, - "dependencies": { - "binaryextensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", - "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==" - }, - "textextensions": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.6.0.tgz", - "integrity": "sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==" - } - } - }, "iterate-iterator": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/iterate-iterator/-/iterate-iterator-1.0.2.tgz", @@ -84646,6 +83644,7 @@ "version": "10.8.5", "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz", "integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==", + "devOptional": true, "requires": { "async": "^3.2.3", "chalk": "^4.0.2", @@ -86674,6 +85673,11 @@ "resolved": "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz", "integrity": "sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==" }, + "js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "js-string-escape": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz", @@ -86995,7 +85999,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/json-stringify-nice/-/json-stringify-nice-1.1.4.tgz", "integrity": "sha512-5Z5RFW63yxReJ7vANgW6eZFGWaQvnPE3WNmZoOJrSkGju2etKA2L5rrOa1sm877TVTFt57A80BH1bArcmlLfPw==", - "dev": true, + "devOptional": true, "peer": true }, "json-stringify-pretty-compact": { @@ -87044,7 +86048,7 @@ "version": "1.3.5", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "devOptional": true, + "dev": true, "requires": { "jsonparse": "^1.2.0", "through": ">=2.2.7 <3" @@ -87104,14 +86108,14 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/just-diff/-/just-diff-3.1.1.tgz", "integrity": "sha512-sdMWKjRq8qWZEjDcVA6llnUT8RDEBIfOiGpYFPYa9u+2c39JCsejktSP7mj5eRid5EIvTzIpQ2kDOCw1Nq9BjQ==", - "dev": true, + "devOptional": true, "peer": true }, "just-diff-apply": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/just-diff-apply/-/just-diff-apply-3.1.2.tgz", "integrity": "sha512-TCa7ZdxCeq6q3Rgms2JCRHTCfWAETPZ8SzYUbkYF6KR3I03sN29DaOIC+xyWboIcMvjAsD5iG2u/RWzHD8XpgQ==", - "dev": true, + "devOptional": true, "peer": true }, "just-extend": { @@ -87169,15 +86173,6 @@ "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=", "dev": true }, - "lazy-cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", - "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", - "optional": true, - "requires": { - "set-getter": "^0.1.0" - } - }, "lazy-universal-dotenv": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lazy-universal-dotenv/-/lazy-universal-dotenv-3.0.1.tgz", @@ -87774,7 +86769,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/load-yaml-file/-/load-yaml-file-0.2.0.tgz", "integrity": "sha512-OfCBkGEw4nN6JLtgRidPX6QxjBQGQf72q3si2uvqyFEMbycSFFHwAZeXx6cJgFM9wmLrf9zBwCP3Ivqa+LLZPw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "graceful-fs": "^4.1.5", @@ -87787,7 +86782,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, + "devOptional": true, "peer": true } } @@ -88018,7 +87013,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, + "devOptional": true, "requires": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" @@ -88065,11 +87060,6 @@ } } }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, "lowlight": { "version": "1.20.0", "resolved": "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz", @@ -88499,7 +87489,7 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-2.2.1.tgz", "integrity": "sha512-yiAivd4xFOH/WXlUi6v/nKopBh1QLzwjFi36NK88cGt/PRXI8WeBASqY+YSjIVWvQTx3hR8zHKDBMV6hWmglNA==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "@types/node": "^15.6.1", @@ -88512,7 +87502,7 @@ "version": "15.14.9", "resolved": "https://registry.npmjs.org/@types/node/-/node-15.14.9.tgz", "integrity": "sha512-qjd88DrCxupx/kJD5yQgZdcYKZKSIGBVDIBE1/LTGcNm3d2Np/jxojkdePDdfnBHJc5W7vSMpbJ1aB7p/Py69A==", - "dev": true, + "devOptional": true, "peer": true } } @@ -88521,7 +87511,7 @@ "version": "9.3.0", "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-9.3.0.tgz", "integrity": "sha512-QKFbPwGCh1ypmc2H8BUYpbapwT/x2AOCYZQogzSui4rUNes7WVMagQXsirPIfp18EarX0SSY9Fpg426nSjew4Q==", - "dev": true, + "devOptional": true, "requires": { "binaryextensions": "^4.16.0", "commondir": "^1.0.1", @@ -88539,7 +87529,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "devOptional": true } } }, @@ -88770,8 +87760,7 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "devOptional": true + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" }, "min-document": { "version": "2.19.0", @@ -88924,7 +87913,7 @@ "version": "1.4.1", "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", - "dev": true, + "devOptional": true, "requires": { "encoding": "^0.1.12", "minipass": "^3.1.0", @@ -88944,7 +87933,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/minipass-json-stream/-/minipass-json-stream-1.0.1.tgz", "integrity": "sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==", - "dev": true, + "devOptional": true, "requires": { "jsonparse": "^1.3.1", "minipass": "^3.0.0" @@ -88962,7 +87951,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", - "dev": true, + "devOptional": true, "requires": { "minipass": "^3.0.0" } @@ -89040,7 +88029,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/mkdirp-infer-owner/-/mkdirp-infer-owner-2.0.0.tgz", "integrity": "sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw==", - "dev": true, + "devOptional": true, "requires": { "chownr": "^2.0.0", "infer-owner": "^1.0.4", @@ -89051,13 +88040,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true + "devOptional": true }, "mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true + "devOptional": true } } }, @@ -89197,7 +88186,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz", "integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==", - "dev": true, + "devOptional": true, "requires": { "@types/minimatch": "^3.0.3", "array-differ": "^3.0.0", @@ -89210,7 +88199,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "dev": true + "devOptional": true } } }, @@ -89334,7 +88323,8 @@ "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true }, "nise": { "version": "4.1.0", @@ -89726,25 +88716,11 @@ "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", "dev": true }, - "npm-api": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-api/-/npm-api-1.0.1.tgz", - "integrity": "sha512-4sITrrzEbPcr0aNV28QyOmgn6C9yKiF8k92jn4buYAK8wmA5xo1qL3II5/gT1r7wxbXBflSduZ2K3FbtOrtGkA==", - "optional": true, - "requires": { - "clone-deep": "^4.0.1", - "download-stats": "^0.3.4", - "JSONStream": "^1.3.5", - "moment": "^2.24.0", - "node-fetch": "^2.6.0", - "paged-request": "^2.0.1" - } - }, "npm-bundled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", - "dev": true, + "devOptional": true, "requires": { "npm-normalize-package-bin": "^1.0.1" } @@ -89753,7 +88729,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/npm-install-checks/-/npm-install-checks-4.0.0.tgz", "integrity": "sha512-09OmyDkNLYwqKPOnbI8exiOZU2GVVmQp7tgez2BPi5OZC8M82elDAps7sxC4l//uSUtotWqoEIDwjRvWH4qz8w==", - "dev": true, + "devOptional": true, "requires": { "semver": "^7.1.1" }, @@ -89762,7 +88738,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "requires": { "yallist": "^4.0.0" } @@ -89771,7 +88747,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "requires": { "lru-cache": "^6.0.0" } @@ -89780,7 +88756,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true } } }, @@ -89804,13 +88780,13 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", - "dev": true + "devOptional": true }, "npm-package-arg": { "version": "8.1.5", "resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-8.1.5.tgz", "integrity": "sha512-LhgZrg0n0VgvzVdSm1oiZworPbTxYHUJCgtsJW8mGvlDpxTM1vSJc3m5QZeUkhAHIzbz3VCHd/R4osi1L1Tg/Q==", - "dev": true, + "devOptional": true, "requires": { "hosted-git-info": "^4.0.1", "semver": "^7.3.4", @@ -89821,7 +88797,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", - "dev": true, + "devOptional": true, "requires": { "lru-cache": "^6.0.0" } @@ -89830,7 +88806,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "requires": { "yallist": "^4.0.0" } @@ -89839,7 +88815,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "requires": { "lru-cache": "^6.0.0" } @@ -89848,7 +88824,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true } } }, @@ -89868,7 +88844,7 @@ "version": "6.1.1", "resolved": "https://registry.npmjs.org/npm-pick-manifest/-/npm-pick-manifest-6.1.1.tgz", "integrity": "sha512-dBsdBtORT84S8V8UTad1WlUyKIY9iMsAmqxHbLdeEeBNMLQDlDWWra3wYUx9EBEIiG/YwAy0XyNHDd2goAsfuA==", - "dev": true, + "devOptional": true, "requires": { "npm-install-checks": "^4.0.0", "npm-normalize-package-bin": "^1.0.1", @@ -89880,7 +88856,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "requires": { "yallist": "^4.0.0" } @@ -89889,7 +88865,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "requires": { "lru-cache": "^6.0.0" } @@ -89898,7 +88874,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true } } }, @@ -89906,7 +88882,7 @@ "version": "11.0.0", "resolved": "https://registry.npmjs.org/npm-registry-fetch/-/npm-registry-fetch-11.0.0.tgz", "integrity": "sha512-jmlgSxoDNuhAtxUIG6pVwwtz840i994dL14FoNVZisrmZW5kWd63IUTNv1m/hyRSGSqWjCUp/YZlS1BJyNp9XA==", - "dev": true, + "devOptional": true, "requires": { "make-fetch-happen": "^9.0.1", "minipass": "^3.1.3", @@ -89920,7 +88896,7 @@ "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, + "devOptional": true, "requires": { "@npmcli/fs": "^1.0.0", "@npmcli/move-file": "^1.0.1", @@ -89946,13 +88922,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true + "devOptional": true }, "debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, + "devOptional": true, "requires": { "ms": "2.1.2" } @@ -89961,7 +88937,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "requires": { "yallist": "^4.0.0" } @@ -89970,7 +88946,7 @@ "version": "9.1.0", "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", - "dev": true, + "devOptional": true, "requires": { "agentkeepalive": "^4.1.3", "cacache": "^15.2.0", @@ -89994,25 +88970,25 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true + "devOptional": true }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "devOptional": true }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", - "dev": true + "devOptional": true }, "socks-proxy-agent": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", - "dev": true, + "devOptional": true, "requires": { "agent-base": "^6.0.2", "debug": "^4.3.1", @@ -90023,7 +88999,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, + "devOptional": true, "requires": { "minipass": "^3.1.1" } @@ -90032,7 +89008,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true + "devOptional": true } } }, @@ -90405,7 +89381,6 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "devOptional": true, "requires": { "mimic-fn": "^2.1.0" } @@ -90473,7 +89448,7 @@ "version": "5.4.1", "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, + "devOptional": true, "requires": { "bl": "^4.1.0", "chalk": "^4.1.0", @@ -90490,13 +89465,13 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "devOptional": true }, "strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "requires": { "ansi-regex": "^5.0.1" } @@ -90672,7 +89647,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-transform/-/p-transform-1.3.0.tgz", "integrity": "sha512-UJKdSzgd3KOnXXAtqN5+/eeHcvTn1hBkesEmElVgvO/NAYcxAvmjzIGmnNd3Tb/gRAvMBdNRFD4qAWdHxY6QXg==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "debug": "^4.3.2", @@ -90683,7 +89658,7 @@ "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "ms": "2.1.2" @@ -90693,7 +89668,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, + "devOptional": true, "peer": true } } @@ -90730,7 +89705,7 @@ "version": "12.0.2", "resolved": "https://registry.npmjs.org/pacote/-/pacote-12.0.2.tgz", "integrity": "sha512-Ar3mhjcxhMzk+OVZ8pbnXdb0l8+pimvlsqBGRNkble2NVgyqOGE3yrCGi/lAYq7E7NRDMz89R1Wx5HIMCGgeYg==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "@npmcli/git": "^2.1.0", @@ -90758,7 +89733,7 @@ "version": "15.3.0", "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "@npmcli/fs": "^1.0.0", @@ -90785,14 +89760,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, + "devOptional": true, "peer": true }, "ignore-walk": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-4.0.1.tgz", "integrity": "sha512-rzDQLaW4jQbh2YrOFlJdCtX8qgJTehFRYiUB2r1osqTeDzV/3+Jh8fz1oAPzUThf3iku8Ds4IDqawI5d8mUiQw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "minimatch": "^3.0.4" @@ -90802,7 +89777,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "yallist": "^4.0.0" @@ -90812,14 +89787,14 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, + "devOptional": true, "peer": true }, "npm-packlist": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-3.0.0.tgz", "integrity": "sha512-L/cbzmutAwII5glUcf2DBRNY/d0TFd4e/FnaZigJV6JD85RHZXJFGwCndjMWiiViiWSsWt3tiOLpI3ByTnIdFQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "glob": "^7.1.6", @@ -90832,7 +89807,7 @@ "version": "8.0.1", "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "minipass": "^3.1.1" @@ -90842,7 +89817,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, + "devOptional": true, "peer": true } } @@ -90860,15 +89835,6 @@ "repeat-string": "^1.5.4" } }, - "paged-request": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/paged-request/-/paged-request-2.0.2.tgz", - "integrity": "sha512-NWrGqneZImDdcMU/7vMcAOo1bIi5h/pmpJqe7/jdsy85BA/s5MSaU/KlpxwW/IVPmIwBcq2uKPrBWWhEWhtxag==", - "optional": true, - "requires": { - "axios": "^0.21.1" - } - }, "pako": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", @@ -90924,7 +89890,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/parse-conflict-json/-/parse-conflict-json-1.1.1.tgz", "integrity": "sha512-4gySviBiW5TRl7XHvp1agcS7SOe0KZOjC//71dzZVWJrY9hCrgtvl5v3SyIxCZ4fZF47TxD9nfzmxcx76xmbUw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "json-parse-even-better-errors": "^2.3.0", @@ -91059,7 +90025,8 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true }, "path-parse": { "version": "1.0.7", @@ -91428,7 +90395,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/preferred-pm/-/preferred-pm-3.0.3.tgz", "integrity": "sha512-+wZgbxNES/KlJs9q40F/1sfOd/j7f1O9JaHcW5Dsn3aUUOZg3L2bjpVUcKV2jvtElYfoTuQiNeMfQJ4kwUAhCQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "find-up": "^5.0.0", @@ -91441,7 +90408,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "locate-path": "^6.0.0", @@ -91452,7 +90419,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "p-locate": "^5.0.0" @@ -91462,7 +90429,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "yocto-queue": "^0.1.0" @@ -91472,7 +90439,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "p-limit": "^3.0.2" @@ -91482,7 +90449,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "peer": true } } @@ -91492,11 +90459,6 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" - }, "prettier": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.4.1.tgz", @@ -91524,7 +90486,9 @@ "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==" + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "devOptional": true, + "peer": true }, "pretty-error": { "version": "3.0.4", @@ -91589,7 +90553,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/proc-log/-/proc-log-1.0.0.tgz", "integrity": "sha512-aCk8AO51s+4JyuYGg3Q/a6gnrlDO09NpVWePtjp7xwphcoQ04x5WAfCyugcsbLooWcMJ87CLkD4+604IckEdhg==", - "dev": true, + "devOptional": true, "peer": true }, "process": { @@ -91640,14 +90604,14 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-all-reject-late/-/promise-all-reject-late-1.0.1.tgz", "integrity": "sha512-vuf0Lf0lOxyQREH7GDIOUMLS7kz+gs8i6B+Yi8dC68a2sychGrHTJYghMBD6k7eUcH0H5P73EckCA48xijWqXw==", - "dev": true, + "devOptional": true, "peer": true }, "promise-call-limit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-call-limit/-/promise-call-limit-1.0.1.tgz", "integrity": "sha512-3+hgaa19jzCGLuSCbieeRsu5C2joKfYn8pY6JAuXFRVfF4IO+L7UPpFWNTeWT9pM7uhskvbPPd/oEOktCn317Q==", - "dev": true, + "devOptional": true, "peer": true }, "promise-inflight": { @@ -91659,7 +90623,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", - "dev": true, + "devOptional": true, "requires": { "err-code": "^2.0.2", "retry": "^0.12.0" @@ -91669,7 +90633,7 @@ "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true + "devOptional": true } } }, @@ -93174,6 +92138,42 @@ "react-popper": "^2.2.4" } }, + "react-query": { + "version": "3.39.2", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.2.tgz", + "integrity": "sha512-F6hYDKyNgDQfQOuR1Rsp3VRzJnWHx6aRnnIZHMNGGgbL3SBgpZTDg8MQwmxOgpCAoqZJA+JSNCydF1xGJqKOCA==", + "requires": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + }, + "dependencies": { + "broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "requires": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, + "unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "requires": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + } + } + }, "react-redux": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-7.2.0.tgz", @@ -93581,27 +92581,11 @@ "mute-stream": "~0.0.4" } }, - "read-chunk": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-3.2.0.tgz", - "integrity": "sha512-CEjy9LCzhmD7nUpJ1oVOE6s/hBkejlcJEgLQHVnQznOSilOPb+kpKktlLfFDK3/WP43+F80xkUTM2VOkYoSYvQ==", - "requires": { - "pify": "^4.0.1", - "with-open-file": "^0.1.6" - }, - "dependencies": { - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - } - } - }, "read-cmd-shim": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-cmd-shim/-/read-cmd-shim-2.0.0.tgz", "integrity": "sha512-HJpV9bQpkl6KwjxlJcBoqu9Ba0PQg8TqSNIOrulGt54a0uup0HtevreFHzYzkm0lpnleRdNBzXznKrgxglEHQw==", - "dev": true + "devOptional": true }, "read-package-json": { "version": "3.0.1", @@ -93666,7 +92650,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/read-package-json-fast/-/read-package-json-fast-2.0.3.tgz", "integrity": "sha512-W/BKtbL+dUjTuRL2vziuYhp76s5HZ9qQhd/dKfWIZveD0O40453QNyZhC0e63lqZrAQ4jiOapVoeJ7JrszenQQ==", - "dev": true, + "devOptional": true, "requires": { "json-parse-even-better-errors": "^2.3.0", "npm-normalize-package-bin": "^1.0.1" @@ -93799,7 +92783,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", - "dev": true, + "devOptional": true, "requires": { "debuglog": "^1.0.1", "dezalgo": "^1.0.0", @@ -94408,7 +93392,8 @@ "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "devOptional": true }, "renderkid": { "version": "2.0.7", @@ -94753,7 +93738,7 @@ "version": "6.6.7", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", - "devOptional": true, + "dev": true, "requires": { "tslib": "^1.9.0" } @@ -94832,7 +93817,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-2.1.0.tgz", "integrity": "sha512-g3WxHrqSWCZHGHlSrF51VXFdjImhwvH8ZO/pryFH56Qi0cDsZfylQa/t0jCzVQFNbNvM00HfHjkDPEuarKDSWQ==", - "dev": true, + "devOptional": true, "peer": true }, "scroll-into-view-if-needed": { @@ -94998,15 +93983,6 @@ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, - "set-getter": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.1.tgz", - "integrity": "sha512-9sVWOy+gthr+0G9DzqqLaYNA7+5OKkSmcqjL9cBpDEaZrr3ShQlyX2cZ/O/ozE41oxn/Tt0LGEM/w4Rub3A3gw==", - "optional": true, - "requires": { - "to-object-path": "^0.3.0" - } - }, "set-value": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", @@ -95106,6 +94082,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -95113,7 +94090,8 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true }, "shelljs": { "version": "0.8.5", @@ -95247,7 +94225,7 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true + "devOptional": true }, "snapdragon": { "version": "0.8.2", @@ -95358,7 +94336,7 @@ "version": "2.6.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", - "dev": true, + "devOptional": true, "requires": { "ip": "^1.1.5", "smart-buffer": "^4.1.0" @@ -95396,7 +94374,6 @@ "version": "4.2.0", "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-4.2.0.tgz", "integrity": "sha512-aUYIEU/UviqPgc8mHR6IW1EGxkAXpeRETYcrzg8cLAvUPZcpAlleSXHV2mY7G12GphSH6Gzv+4MMVSSkbdteHg==", - "dev": true, "requires": { "is-plain-obj": "^2.0.0" }, @@ -95404,8 +94381,7 @@ "is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==" } } }, @@ -96028,11 +95004,6 @@ } } }, - "string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -96130,13 +95101,14 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", - "dev": true + "devOptional": true }, "strip-bom-buf": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz", "integrity": "sha1-HLRar1dTD0yvhsf3UXnSyaUd1XI=", "devOptional": true, + "peer": true, "requires": { "is-utf8": "^0.2.1" } @@ -96146,6 +95118,7 @@ "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", "devOptional": true, + "peer": true, "requires": { "first-chunk-stream": "^2.0.0", "strip-bom": "^2.0.0" @@ -96156,6 +95129,7 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "devOptional": true, + "peer": true, "requires": { "is-utf8": "^0.2.0" } @@ -96177,8 +95151,7 @@ "strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "devOptional": true + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==" }, "strip-indent": { "version": "3.0.0", @@ -96882,7 +95855,7 @@ "version": "5.14.0", "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-5.14.0.tgz", "integrity": "sha512-4cAYwNFNYlIAHBUo7p6zw8POUvWbZor+/R0Tanv+rIhsauEyV9QSrEXL40pI+GfTQxKX8k6Tyw6CmdSDSmASrg==", - "dev": true + "devOptional": true }, "thread-loader": { "version": "3.0.4", @@ -96957,11 +95930,6 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "devOptional": true }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" - }, "timers-browserify": { "version": "2.0.12", "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", @@ -97159,7 +96127,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/treeverse/-/treeverse-1.0.4.tgz", "integrity": "sha512-whw60l7r+8ZU8Tu/Uc2yxtc4ZTZbR/PF3u1IPNKGQ6p8EICLb3Z2lAgoqw9bqYd8IkgnsaOcLzYHFckjqNsf0g==", - "dev": true, + "devOptional": true, "peer": true }, "trim": { @@ -97423,7 +96391,7 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, + "devOptional": true, "requires": { "is-typedarray": "^1.0.0" } @@ -97729,14 +96697,9 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, + "devOptional": true, "peer": true }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" - }, "upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -97830,14 +96793,6 @@ "requires-port": "^1.0.0" } }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "requires": { - "prepend-http": "^1.0.1" - } - }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -97984,7 +96939,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz", "integrity": "sha1-X6kS2B630MdK/BQN5zF/DKffQ34=", - "dev": true, + "devOptional": true, "requires": { "builtins": "^1.0.3" } @@ -98053,6 +97008,8 @@ "version": "2.2.1", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "devOptional": true, + "peer": true, "requires": { "clone": "^2.1.1", "clone-buffer": "^1.0.0", @@ -98067,6 +97024,7 @@ "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-3.0.0.tgz", "integrity": "sha1-sQTZ5ECf+jJfqt1SBkLQo7SIs2U=", "devOptional": true, + "peer": true, "requires": { "graceful-fs": "^4.1.2", "pify": "^2.3.0", @@ -98079,7 +97037,8 @@ "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "devOptional": true + "devOptional": true, + "peer": true } } }, @@ -98133,7 +97092,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-1.0.0.tgz", "integrity": "sha512-hwj/qMDUEjCU5h0xr90KGCf0tg0/LgJbmOWgrWKYlcJZM7XvquvUJZ0G/HMGr7F7OQMOUuPHWP9JpriinkAlkg==", - "dev": true, + "devOptional": true, "peer": true }, "walker": { @@ -98245,7 +97204,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", - "dev": true, + "devOptional": true, "requires": { "defaults": "^1.0.3" } @@ -98959,6 +97918,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -98984,7 +97944,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-pm/-/which-pm-2.0.0.tgz", "integrity": "sha512-Lhs9Pmyph0p5n5Z3mVnN0yWcbQYUAD7rbQUiMsQxOJ3T57k7RFe35SUwWMf7dsbDZks1uOmw4AecB/JMDj3v/w==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "load-yaml-file": "^0.2.0", @@ -98995,7 +97955,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "peer": true } } @@ -99052,28 +98012,6 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, - "with-open-file": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/with-open-file/-/with-open-file-0.1.7.tgz", - "integrity": "sha512-ecJS2/oHtESJ1t3ZfMI3B7KIDKyfN0O16miWxdn30zdh66Yd3LsRFebXZXq6GU4xfxLf6nVxp9kIqElb5fqczA==", - "requires": { - "p-finally": "^1.0.0", - "p-try": "^2.1.0", - "pify": "^4.0.1" - }, - "dependencies": { - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - } - } - }, "word-wrap": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", @@ -99166,7 +98104,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, + "devOptional": true, "requires": { "imurmurhash": "^0.1.4", "is-typedarray": "^1.0.0", @@ -99525,7 +98463,7 @@ "version": "3.8.0", "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-3.8.0.tgz", "integrity": "sha512-BPo3btCxefe8NzDMk59QRDNBXMC4Ra6SHhFfEsV2DTmAp/6ZoovMANlJiWrXu41rtFQBmjH/rT2tSiHGowt38w==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "@npmcli/arborist": "^4.0.4", @@ -99569,14 +98507,14 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, + "devOptional": true, "peer": true }, "are-we-there-yet": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "delegates": "^1.0.0", @@ -99587,21 +98525,21 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "dev": true, + "devOptional": true, "peer": true }, "commander": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-7.1.0.tgz", "integrity": "sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==", - "dev": true, + "devOptional": true, "peer": true }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "path-key": "^3.1.0", @@ -99613,14 +98551,14 @@ "version": "4.6.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", - "dev": true, + "devOptional": true, "peer": true }, "debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "ms": "2.1.2" @@ -99630,21 +98568,21 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, + "devOptional": true, "peer": true }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, + "devOptional": true, "peer": true }, "execa": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "cross-spawn": "^7.0.3", @@ -99662,7 +98600,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "locate-path": "^6.0.0", @@ -99673,7 +98611,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.1.tgz", "integrity": "sha512-6STz6KdQgxO4S/ko+AbjlFGGdGcknluoqU+79GOFCDqqyYj5OanQf9AjxwN0jCidtT+ziPMmPSt9E4hfQ0CwIQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "aproba": "^1.0.3 || ^2.0.0", @@ -99691,14 +98629,14 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true, + "devOptional": true, "peer": true }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "is-fullwidth-code-point": "^2.0.0", @@ -99709,7 +98647,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, + "devOptional": true, "peer": true, "requires": { "ansi-regex": "^3.0.0" @@ -99721,21 +98659,21 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, + "devOptional": true, "peer": true }, "human-signals": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", - "dev": true, + "devOptional": true, "peer": true }, "inquirer": { "version": "8.2.0", "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.0.tgz", "integrity": "sha512-0crLweprevJ02tTuA6ThpoAERAGyVILC4sS74uib58Xf/zSr1/ZWtmm7D5CI+bSQEaA04f0K7idaHpQbSWgiVQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "ansi-escapes": "^4.2.1", @@ -99758,14 +98696,14 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, + "devOptional": true, "peer": true }, "locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "p-locate": "^5.0.0" @@ -99775,7 +98713,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "yallist": "^4.0.0" @@ -99785,14 +98723,14 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true, + "devOptional": true, "peer": true }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "path-key": "^3.0.0" @@ -99802,7 +98740,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "are-we-there-yet": "^2.0.0", @@ -99815,7 +98753,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "yocto-queue": "^0.1.0" @@ -99825,7 +98763,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "p-limit": "^3.0.2" @@ -99835,21 +98773,21 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, + "devOptional": true, "peer": true }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, + "devOptional": true, "peer": true }, "readable-stream": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "inherits": "^2.0.3", @@ -99861,7 +98799,7 @@ "version": "7.4.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "tslib": "~2.1.0" @@ -99871,7 +98809,7 @@ "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "lru-cache": "^6.0.0" @@ -99881,7 +98819,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "shebang-regex": "^3.0.0" @@ -99891,21 +98829,21 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "devOptional": true, "peer": true }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, + "devOptional": true, "peer": true }, "string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "emoji-regex": "^8.0.0", @@ -99917,7 +98855,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, + "devOptional": true, "peer": true } } @@ -99926,7 +98864,7 @@ "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "ansi-regex": "^5.0.1" @@ -99936,14 +98874,14 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", - "dev": true, + "devOptional": true, "peer": true }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, + "devOptional": true, "peer": true, "requires": { "isexe": "^2.0.0" @@ -99953,92 +98891,41 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true, + "devOptional": true, "peer": true } } }, "yeoman-generator": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-4.13.0.tgz", - "integrity": "sha512-f2/5N5IR3M2Ozm+QocvZQudlQITv2DwI6Mcxfy7R7gTTzaKgvUpgo/pQMJ+WQKm0KN0YMWCFOZpj0xFGxevc1w==", + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-5.7.0.tgz", + "integrity": "sha512-z9ZwgKoDOd+llPDCwn8Ax2l4In5FMhlslxdeByW4AMxhT+HbTExXKEAahsClHSbwZz1i5OzRwLwRIUdOJBr5Bw==", "requires": { - "async": "^2.6.2", - "chalk": "^2.4.2", - "cli-table": "^0.3.1", - "cross-spawn": "^6.0.5", - "dargs": "^6.1.0", - "dateformat": "^3.0.3", + "chalk": "^4.1.0", + "dargs": "^7.0.0", "debug": "^4.1.1", - "diff": "^4.0.1", - "error": "^7.0.2", - "find-up": "^3.0.0", - "github-username": "^3.0.0", - "grouped-queue": "^1.1.0", - "istextorbinary": "^2.5.1", + "execa": "^5.1.1", + "github-username": "^6.0.0", "lodash": "^4.17.11", - "make-dir": "^3.0.0", - "mem-fs-editor": "^7.0.1", "minimist": "^1.2.5", - "pretty-bytes": "^5.2.0", - "read-chunk": "^3.2.0", - "read-pkg-up": "^5.0.0", - "rimraf": "^2.6.3", + "read-pkg-up": "^7.0.1", "run-async": "^2.0.0", "semver": "^7.2.1", - "shelljs": "^0.8.4", - "text-table": "^0.2.0", - "through2": "^3.0.1", - "yeoman-environment": "^2.9.5" + "shelljs": "^0.8.5", + "sort-keys": "^4.2.0", + "text-table": "^0.2.0" }, "dependencies": { - "@nodelib/fs.stat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", - "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==" - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "optional": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==" - }, - "async": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", - "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", - "requires": { - "lodash": "^4.17.14" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" } }, - "dargs": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-6.1.0.tgz", - "integrity": "sha512-5dVBvpBLBnPwSsYXqfybFyehMmC/EenKEcf23AhCTgTf48JFBbmJKqoZBsERDnjL0FyiVTYWdFsRfTLHxLyKdQ==" - }, "debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", @@ -100047,172 +98934,36 @@ "ms": "2.1.2" } }, - "dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "requires": { - "path-type": "^3.0.0" - } - }, - "error": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", - "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", - "requires": { - "string-template": "~0.2.1" - } - }, "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "optional": true, + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "optional": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - } - } - }, - "fast-glob": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", - "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", - "requires": { - "@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" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" } }, "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "optional": true, - "requires": { - "pump": "^3.0.0" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "globby": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", - "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", - "requires": { - "@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" - }, - "dependencies": { - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "requires": { - "array-uniq": "^1.0.1" - } - } - } - }, - "grouped-queue": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-1.1.0.tgz", - "integrity": "sha512-rZOFKfCqLhsu5VqjBjEWiwrYqJR07KxIkH4mLZlNlGDfntbb4FbMyGFP14TlvRPrU9S3Hnn/sgxbC5ZeN0no3Q==", - "optional": true, - "requires": { - "lodash": "^4.17.15" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==" }, - "is-scoped": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-1.0.0.tgz", - "integrity": "sha1-RJypgpnnEwOCViieyytUDcQ3yzA=", - "optional": true, - "requires": { - "scoped-regex": "^1.0.0" - } + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==" }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "optional": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "optional": true, - "requires": { - "chalk": "^2.0.1" - } + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" }, "lru-cache": { "version": "6.0.0", @@ -100222,150 +98973,28 @@ "yallist": "^4.0.0" } }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "mem-fs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-1.2.0.tgz", - "integrity": "sha512-b8g0jWKdl8pM0LqAPdK9i8ERL7nYrzmJfRhxMiWH2uYdfYnb7uXnmwVb0ZGe7xyEl4lj+nLIU3yf4zPUT+XsVQ==", - "optional": true, - "requires": { - "through2": "^3.0.0", - "vinyl": "^2.0.1", - "vinyl-file": "^3.0.0" - } - }, - "mem-fs-editor": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-7.1.0.tgz", - "integrity": "sha512-BH6QEqCXSqGeX48V7zu+e3cMwHU7x640NB8Zk8VNvVZniz+p4FK60pMx/3yfkzo6miI6G3a8pH6z7FeuIzqrzA==", - "requires": { - "commondir": "^1.0.1", - "deep-extend": "^0.6.0", - "ejs": "^3.1.5", - "glob": "^7.1.4", - "globby": "^9.2.0", - "isbinaryfile": "^4.0.0", - "mkdirp": "^1.0.0", - "multimatch": "^4.0.0", - "rimraf": "^3.0.0", - "through2": "^3.0.2", - "vinyl": "^2.2.1" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - } - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "multimatch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", - "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", - "requires": { - "@types/minimatch": "^3.0.3", - "array-differ": "^3.0.0", - "array-union": "^2.1.0", - "arrify": "^2.0.1", - "minimatch": "^3.0.4" - } - }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "optional": true, "requires": { "path-key": "^3.0.0" } }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "optional": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "read-pkg-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-5.0.0.tgz", - "integrity": "sha512-XBQjqOBtTzyol2CpsQOw8LHV0XbDZVG7xMMjmXAJomlVY03WOBRmYgDJETlvcg0H63AJvPRwT7GFi5rvOzUOKg==", - "requires": { - "find-up": "^3.0.0", - "read-pkg": "^5.0.0" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - }, - "scoped-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-1.0.0.tgz", - "integrity": "sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=", - "optional": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz", + "integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==", "requires": { "lru-cache": "^6.0.0" } @@ -100374,7 +99003,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "optional": true, "requires": { "shebang-regex": "^3.0.0" } @@ -100382,46 +99010,12 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "optional": true - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "optional": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - }, - "through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - }, - "untildify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", - "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==", - "optional": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "optional": true, "requires": { "isexe": "^2.0.0" } @@ -100430,187 +99024,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yeoman-environment": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-2.10.3.tgz", - "integrity": "sha512-pLIhhU9z/G+kjOXmJ2bPFm3nejfbH+f1fjYRSOteEXDBrv1EoJE/e+kuHixSXfCYfTkxjYsvRaDX+1QykLCnpQ==", - "optional": true, - "requires": { - "chalk": "^2.4.1", - "debug": "^3.1.0", - "diff": "^3.5.0", - "escape-string-regexp": "^1.0.2", - "execa": "^4.0.0", - "globby": "^8.0.1", - "grouped-queue": "^1.1.0", - "inquirer": "^7.1.0", - "is-scoped": "^1.0.0", - "lodash": "^4.17.10", - "log-symbols": "^2.2.0", - "mem-fs": "^1.1.0", - "mem-fs-editor": "^6.0.0", - "npm-api": "^1.0.0", - "semver": "^7.1.3", - "strip-ansi": "^4.0.0", - "text-table": "^0.2.0", - "untildify": "^3.0.3", - "yeoman-generator": "^4.8.2" - }, - "dependencies": { - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "optional": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "optional": true - }, - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "optional": true - }, - "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", - "optional": true, - "requires": { - "arrify": "^1.0.1", - "path-type": "^3.0.0" - } - }, - "ejs": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", - "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", - "optional": true - }, - "globby": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", - "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", - "optional": true, - "requires": { - "array-union": "^1.0.1", - "dir-glob": "2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - } - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "optional": true - }, - "mem-fs-editor": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-6.0.0.tgz", - "integrity": "sha512-e0WfJAMm8Gv1mP5fEq/Blzy6Lt1VbLg7gNnZmZak7nhrBTibs+c6nQ4SKs/ZyJYHS1mFgDJeopsLAv7Ow0FMFg==", - "optional": true, - "requires": { - "commondir": "^1.0.1", - "deep-extend": "^0.6.0", - "ejs": "^2.6.1", - "glob": "^7.1.4", - "globby": "^9.2.0", - "isbinaryfile": "^4.0.0", - "mkdirp": "^0.5.0", - "multimatch": "^4.0.0", - "rimraf": "^2.6.3", - "through2": "^3.0.1", - "vinyl": "^2.2.0" - }, - "dependencies": { - "dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "optional": true, - "requires": { - "path-type": "^3.0.0" - } - }, - "globby": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", - "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", - "optional": true, - "requires": { - "@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" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "optional": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "optional": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "optional": true - } - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "optional": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "optional": true - } - } } } }, diff --git a/superset-frontend/package.json b/superset-frontend/package.json index a40aaa1edfac4..cf442fd6b44d7 100644 --- a/superset-frontend/package.json +++ b/superset-frontend/package.json @@ -175,6 +175,7 @@ "react-lines-ellipsis": "^0.15.0", "react-loadable": "^5.5.0", "react-markdown": "^4.3.1", + "react-query": "^3.39.2", "react-redux": "^7.2.0", "react-resize-detector": "^6.7.6", "react-reverse-portal": "^2.0.1", diff --git a/superset-frontend/packages/generator-superset/package.json b/superset-frontend/packages/generator-superset/package.json index 2011a1b22e863..6e73ba3c1461d 100644 --- a/superset-frontend/packages/generator-superset/package.json +++ b/superset-frontend/packages/generator-superset/package.json @@ -2,40 +2,40 @@ "name": "@superset-ui/generator-superset", "version": "0.18.25", "description": "Scaffolder for Superset", + "keywords": [ + "yeoman", + "generator", + "superset", + "yeoman-generator" + ], + "homepage": "https://github.com/apache/superset.git#readme", "bugs": { - "url": "https://github.com/apache-superset/superset-ui/issues" + "url": "https://github.com/apache/superset.git/issues" }, - "homepage": "https://github.com/apache-superset/superset-ui#readme", "repository": { "type": "git", - "url": "git+https://github.com/apache-superset/superset-ui.git" + "url": "git+https://github.com/apache/superset.git" }, + "license": "Apache-2.0", "author": "Superset", + "main": "generators/index.js", "files": [ "generators" ], - "main": "generators/index.js", - "keywords": [ - "yeoman", - "generator", - "superset", - "yeoman-generator" - ], + "dependencies": { + "chalk": "^4.0.0", + "lodash": "^4.17.11", + "yeoman-generator": "^5.7.0", + "yosay": "^2.0.2" + }, "devDependencies": { + "fs-extra": "^10.0.0", "yeoman-assert": "^3.1.0", - "yeoman-test": "^6.2.0", - "fs-extra": "^10.0.0" + "yeoman-test": "^6.2.0" }, "engines": { "npm": ">= 4.0.0" }, - "dependencies": { - "chalk": "^4.0.0", - "lodash": "^4.17.11", - "yeoman-generator": "^4.0.0", - "yosay": "^2.0.2" - }, - "license": "Apache-2.0", "publishConfig": { "access": "public" } diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/index.ts b/superset-frontend/packages/superset-ui-chart-controls/src/index.ts index 8a151d10e2a4f..5ed1768e6983f 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/index.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/index.ts @@ -31,11 +31,7 @@ export * from './components/ColumnTypeLabel/ColumnTypeLabel'; export * from './components/MetricOption'; // React control components -export { - sharedControls, - dndEntity, - dndColumnsControl, -} from './shared-controls'; +export { default as sharedControls, withDndFallback } from './shared-controls'; export { default as sharedControlComponents } from './shared-controls/components'; export { legacySortBy } from './shared-controls/legacySortBy'; export * from './shared-controls/emitFilterControl'; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/operators/pivotOperator.ts b/superset-frontend/packages/superset-ui-chart-controls/src/operators/pivotOperator.ts index 564495fcbcffd..efd4f50a8bd0d 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/operators/pivotOperator.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/operators/pivotOperator.ts @@ -17,27 +17,26 @@ * under the License. */ import { - DTTM_ALIAS, ensureIsArray, getColumnLabel, getMetricLabel, PostProcessingPivot, } from '@superset-ui/core'; import { PostProcessingFactory } from './types'; +import { getAxis } from './utils'; export const pivotOperator: PostProcessingFactory = ( formData, queryObject, ) => { const metricLabels = ensureIsArray(queryObject.metrics).map(getMetricLabel); - const { x_axis: xAxis } = formData; + const xAxis = getAxis(formData); - if ((xAxis || queryObject.is_timeseries) && metricLabels.length) { - const index = [getColumnLabel(xAxis || DTTM_ALIAS)]; + if (xAxis && metricLabels.length) { return { operation: 'pivot', options: { - index, + index: [xAxis], columns: ensureIsArray(queryObject.columns).map(getColumnLabel), // Create 'dummy' mean aggregates to assign cell values in pivot table // use the 'mean' aggregates to avoid drop NaN. PR: https://github.com/apache-superset/superset-ui/pull/1231 diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/operators/prophetOperator.ts b/superset-frontend/packages/superset-ui-chart-controls/src/operators/prophetOperator.ts index ff0fa0fb6544c..5d8d7feea9345 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/operators/prophetOperator.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/operators/prophetOperator.ts @@ -16,20 +16,17 @@ * specific language governing permissions and limitationsxw * under the License. */ -import { - DTTM_ALIAS, - getColumnLabel, - PostProcessingProphet, -} from '@superset-ui/core'; +import { PostProcessingProphet } from '@superset-ui/core'; import { PostProcessingFactory } from './types'; +import { getAxis } from './utils'; /* eslint-disable @typescript-eslint/no-unused-vars */ export const prophetOperator: PostProcessingFactory = ( formData, queryObject, ) => { - const index = getColumnLabel(formData.x_axis || DTTM_ALIAS); - if (formData.forecastEnabled) { + const xAxis = getAxis(formData); + if (formData.forecastEnabled && xAxis) { return { operation: 'prophet', options: { @@ -39,7 +36,7 @@ export const prophetOperator: PostProcessingFactory = ( yearly_seasonality: formData.forecastSeasonalityYearly, weekly_seasonality: formData.forecastSeasonalityWeekly, daily_seasonality: formData.forecastSeasonalityDaily, - index, + index: xAxis, }, }; } diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/operators/timeComparePivotOperator.ts b/superset-frontend/packages/superset-ui-chart-controls/src/operators/timeComparePivotOperator.ts index a4c0f5eea6c6e..e1310a4e3aa32 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/operators/timeComparePivotOperator.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/operators/timeComparePivotOperator.ts @@ -18,20 +18,20 @@ * under the License. */ import { - DTTM_ALIAS, ensureIsArray, getColumnLabel, NumpyFunction, PostProcessingPivot, } from '@superset-ui/core'; -import { getMetricOffsetsMap, isTimeComparison } from './utils'; +import { getMetricOffsetsMap, isTimeComparison, getAxis } from './utils'; import { PostProcessingFactory } from './types'; export const timeComparePivotOperator: PostProcessingFactory = (formData, queryObject) => { const metricOffsetMap = getMetricOffsetsMap(formData, queryObject); + const xAxis = getAxis(formData); - if (isTimeComparison(formData, queryObject)) { + if (isTimeComparison(formData, queryObject) && xAxis) { const aggregates = Object.fromEntries( [...metricOffsetMap.values(), ...metricOffsetMap.keys()].map(metric => [ metric, @@ -39,12 +39,11 @@ export const timeComparePivotOperator: PostProcessingFactory { + // The formData should be "raw form_data" -- the snake_case version of formData rather than camelCase. + if (!(formData.granularity_sqla || formData.x_axis)) { + return undefined; + } + + return isDefined(formData.x_axis) + ? getColumnLabel(formData.x_axis) + : DTTM_ALIAS; +}; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/operators/utils/index.ts b/superset-frontend/packages/superset-ui-chart-controls/src/operators/utils/index.ts index 8d65ca1e590fb..809ebe06ed499 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/operators/utils/index.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/operators/utils/index.ts @@ -21,3 +21,4 @@ export { getMetricOffsetsMap } from './getMetricOffsetsMap'; export { isTimeComparison } from './isTimeComparison'; export { isDerivedSeries } from './isDerivedSeries'; export { TIME_COMPARISON_SEPARATOR } from './constants'; +export { getAxis } from './getAxis'; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/sections/echartsTimeSeriesQuery.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/sections/echartsTimeSeriesQuery.tsx index 66d9fb7682dbf..6fe14d3457f67 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/sections/echartsTimeSeriesQuery.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/sections/echartsTimeSeriesQuery.tsx @@ -30,6 +30,11 @@ export const echartsTimeSeriesQuery: ControlPanelSectionConfig = { expanded: true, controlSetRows: [ [isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) ? 'x_axis' : null], + [ + isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) + ? 'time_grain_sqla' + : null, + ], ['metrics'], ['groupby'], [ diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/sections/sections.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/sections/sections.tsx index fee5f990b6bc9..4f4efdb82ff34 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/sections/sections.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/sections/sections.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { t } from '@superset-ui/core'; +import { FeatureFlag, isFeatureEnabled, t } from '@superset-ui/core'; import { ControlPanelSectionConfig } from '../types'; // A few standard controls sections that are used internally. @@ -38,6 +38,19 @@ export const legacyTimeseriesTime: ControlPanelSectionConfig = { ], }; +export const genericTime: ControlPanelSectionConfig = { + ...baseTimeSection, + controlSetRows: [ + ['granularity_sqla'], + [ + isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) + ? null + : 'time_grain_sqla', + ], + ['time_range'], + ], +}; + export const legacyRegularTime: ControlPanelSectionConfig = { ...baseTimeSection, controlSetRows: [['granularity_sqla'], ['time_range']], diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/constants.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/constants.tsx index cdd07f15e0930..91427e14612ec 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/constants.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/constants.tsx @@ -19,48 +19,35 @@ import { FeatureFlag, isFeatureEnabled, + QueryFormData, t, validateNonEmpty, } from '@superset-ui/core'; import { ControlPanelState, ControlState } from '../types'; -export const xAxisControlConfig = { - label: (state: ControlPanelState) => { - if ( - isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) && - state?.form_data?.orientation === 'horizontal' - ) { - return t('Y-axis'); - } +const getAxisLabel = ( + formData: QueryFormData, +): Record<'label' | 'description', string> => + formData?.orientation === 'horizontal' + ? { label: t('Y-axis'), description: t('Dimension to use on y-axis.') } + : { label: t('X-axis'), description: t('Dimension to use on x-axis.') }; - return t('X-axis'); - }, - default: ( - control: ControlState, - controlPanel: Partial, - ) => { - // default to the chosen time column if x-axis is unset and the - // GENERIC_CHART_AXES feature flag is enabled - const { value } = control; - if (value) { - return value; - } - const timeColumn = controlPanel?.form_data?.granularity_sqla; - if (isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) && timeColumn) { - return timeColumn; - } - return null; - }, +export const xAxisControlConfig = { + label: (state: ControlPanelState) => getAxisLabel(state?.form_data).label, multi: false, - description: (state: ControlPanelState) => { + description: (state: ControlPanelState) => + getAxisLabel(state?.form_data).description, + validators: [validateNonEmpty], + initialValue: (control: ControlState, state: ControlPanelState) => { if ( isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) && - state?.form_data?.orientation === 'horizontal' + state?.form_data?.granularity_sqla && + !state.form_data?.x_axis && + !control?.value ) { - return t('Dimension to use on y-axis.'); + return state.form_data.granularity_sqla; } - - return t('Dimension to use on x-axis.'); + return undefined; }, - validators: [validateNonEmpty], + default: undefined, }; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx index 63aa64bf461ca..679ac940a5bec 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/dndControls.tsx @@ -17,6 +17,7 @@ * specific language governing permissions and limitations * under the License. */ +import React, { useMemo } from 'react'; import { FeatureFlag, isFeatureEnabled, @@ -25,43 +26,83 @@ import { t, validateNonEmpty, } from '@superset-ui/core'; -import { ExtraControlProps, SharedControlConfig, Dataset } from '../types'; +import { + ExtraControlProps, + SharedControlConfig, + Dataset, + Metric, +} from '../types'; import { DATASET_TIME_COLUMN_OPTION, TIME_FILTER_LABELS } from '../constants'; -import { QUERY_TIME_COLUMN_OPTION, defineSavedMetrics } from '..'; +import { + QUERY_TIME_COLUMN_OPTION, + defineSavedMetrics, + ColumnOption, + ColumnMeta, + FilterOption, +} from '..'; import { xAxisControlConfig } from './constants'; -export const dndGroupByControl: SharedControlConfig<'DndColumnSelect'> = { +type Control = { + savedMetrics?: Metric[] | null; + default?: unknown; +}; + +/* + * Note: Previous to the commit that introduced this comment, the shared controls module + * would check feature flags at module execution time and expose a different control + * configuration (component + props) depending on the status of drag-and-drop feature + * flags. This commit combines those configs, merging the required props for both the + * drag-and-drop and non-drag-and-drop components, and renders a wrapper component that + * checks feature flags at component render time to avoid race conditions between when + * feature flags are set and when they're checked. + */ + +export const dndGroupByControl: SharedControlConfig< + 'DndColumnSelect' | 'SelectControl', + ColumnMeta +> = { type: 'DndColumnSelect', label: t('Dimensions'), + multi: true, + freeForm: true, + clearable: true, default: [], + includeTime: false, description: t( - 'One or many columns to group by. High cardinality groupings should include a series limit ' + - 'to limit the number of fetched and rendered series.', + 'One or many columns to group by. High cardinality groupings should include a sort by metric ' + + 'and series limit to limit the number of fetched and rendered series.', ), - mapStateToProps(state, { includeTime }) { + optionRenderer: (c: ColumnMeta) => , + valueRenderer: (c: ColumnMeta) => , + valueKey: 'column_name', + allowAll: true, + filterOption: ({ data: opt }: FilterOption, text: string) => + (opt.column_name && + opt.column_name.toLowerCase().includes(text.toLowerCase())) || + (opt.verbose_name && + opt.verbose_name.toLowerCase().includes(text.toLowerCase())) || + false, + promptTextCreator: (label: unknown) => label, + mapStateToProps(state, controlState) { const newState: ExtraControlProps = {}; const { datasource } = state; if (datasource?.columns[0]?.hasOwnProperty('groupby')) { const options = (datasource as Dataset).columns.filter(c => c.groupby); - if (includeTime) { + if (controlState?.includeTime) { options.unshift(DATASET_TIME_COLUMN_OPTION); } - newState.options = Object.fromEntries( - options.map(option => [option.column_name, option]), - ); + newState.options = options; newState.savedMetrics = (datasource as Dataset).metrics || []; } else { - const options = datasource?.columns; - if (includeTime) { - (options as QueryColumn[])?.unshift(QUERY_TIME_COLUMN_OPTION); + const options = (datasource?.columns as QueryColumn[]) || []; + if (controlState?.includeTime) { + options.unshift(QUERY_TIME_COLUMN_OPTION); } - newState.options = Object.fromEntries( - (options as QueryColumn[])?.map(option => [option.name, option]), - ); - newState.options = datasource?.columns; + newState.options = options; } return newState; }, + commaChoosesOption: false, }; export const dndColumnsControl: typeof dndGroupByControl = { @@ -70,7 +111,7 @@ export const dndColumnsControl: typeof dndGroupByControl = { description: t('One or many columns to pivot as columns'), }; -export const dndSeries: typeof dndGroupByControl = { +export const dndSeriesControl: typeof dndGroupByControl = { ...dndGroupByControl, label: t('Dimension'), multi: false, @@ -82,7 +123,7 @@ export const dndSeries: typeof dndGroupByControl = { ), }; -export const dndEntity: typeof dndGroupByControl = { +export const dndEntityControl: typeof dndGroupByControl = { ...dndGroupByControl, label: t('Entity'), default: null, @@ -91,7 +132,9 @@ export const dndEntity: typeof dndGroupByControl = { description: t('This defines the element to be plotted on the chart'), }; -export const dnd_adhoc_filters: SharedControlConfig<'DndFilterSelect'> = { +export const dndAdhocFilterControl: SharedControlConfig< + 'DndFilterSelect' | 'AdhocFilterControl' +> = { type: 'DndFilterSelect', label: t('Filters'), default: [], @@ -109,7 +152,9 @@ export const dnd_adhoc_filters: SharedControlConfig<'DndFilterSelect'> = { provideFormDataToProps: true, }; -export const dnd_adhoc_metrics: SharedControlConfig<'DndMetricSelect'> = { +export const dndAdhocMetricsControl: SharedControlConfig< + 'DndMetricSelect' | 'MetricsControl' +> = { type: 'DndMetricSelect', multi: true, label: t('Metrics'), @@ -123,20 +168,23 @@ export const dnd_adhoc_metrics: SharedControlConfig<'DndMetricSelect'> = { description: t('One or many metrics to display'), }; -export const dnd_adhoc_metric: SharedControlConfig<'DndMetricSelect'> = { - ...dnd_adhoc_metrics, +export const dndAdhocMetricControl: typeof dndAdhocMetricsControl = { + ...dndAdhocMetricsControl, multi: false, label: t('Metric'), description: t('Metric'), }; -export const dnd_adhoc_metric_2: SharedControlConfig<'DndMetricSelect'> = { - ...dnd_adhoc_metric, +export const dndAdhocMetricControl2: typeof dndAdhocMetricControl = { + ...dndAdhocMetricControl, label: t('Right Axis Metric'), + clearable: true, description: t('Choose a metric for right axis'), }; -export const dnd_sort_by: SharedControlConfig<'DndMetricSelect'> = { +export const dndSortByControl: SharedControlConfig< + 'DndMetricSelect' | 'MetricsControl' +> = { type: 'DndMetricSelect', label: t('Sort by'), default: null, @@ -152,33 +200,37 @@ export const dnd_sort_by: SharedControlConfig<'DndMetricSelect'> = { }), }; -export const dnd_size: SharedControlConfig<'DndMetricSelect'> = { - ...dnd_adhoc_metric, +export const dndSizeControl: typeof dndAdhocMetricControl = { + ...dndAdhocMetricControl, label: t('Bubble Size'), description: t('Metric used to calculate bubble size'), + default: null, }; -export const dnd_x: SharedControlConfig<'DndMetricSelect'> = { - ...dnd_adhoc_metric, +export const dndXControl: typeof dndAdhocMetricControl = { + ...dndAdhocMetricControl, label: t('X Axis'), description: t('Metric assigned to the [X] axis'), + default: null, }; -export const dnd_y: SharedControlConfig<'DndMetricSelect'> = { - ...dnd_adhoc_metric, +export const dndYControl: typeof dndAdhocMetricControl = { + ...dndAdhocMetricControl, label: t('Y Axis'), description: t('Metric assigned to the [Y] axis'), + default: null, }; -export const dnd_secondary_metric: SharedControlConfig<'DndMetricSelect'> = { - ...dnd_adhoc_metric, +export const dndSecondaryMetricControl: typeof dndAdhocMetricControl = { + ...dndAdhocMetricControl, label: t('Color Metric'), + default: null, validators: [], description: t('A metric to use for color'), }; -export const dnd_granularity_sqla: typeof dndGroupByControl = { - ...dndSeries, +export const dndGranularitySqlaControl: typeof dndSeriesControl = { + ...dndSeriesControl, label: TIME_FILTER_LABELS.granularity_sqla, description: t( 'The time column for the visualization. Note that you ' + @@ -187,21 +239,20 @@ export const dnd_granularity_sqla: typeof dndGroupByControl = { 'filter below is applied against this column or ' + 'expression', ), + default: (c: Control) => c.default, + clearable: false, canDelete: false, - ghostButtonText: t( - isFeatureEnabled(FeatureFlag.ENABLE_DND_WITH_CLICK_UX) - ? 'Drop a temporal column here or click' - : 'Drop temporal column here', - ), + ghostButtonText: t('Drop temporal column here'), + clickEnabledGhostButtonText: t('Drop a temporal column here or click'), + optionRenderer: (c: ColumnMeta) => , + valueRenderer: (c: ColumnMeta) => , + valueKey: 'column_name', mapStateToProps: ({ datasource }) => { if (datasource?.columns[0]?.hasOwnProperty('column_name')) { const temporalColumns = (datasource as Dataset)?.columns?.filter(c => c.is_dttm) ?? []; - const options = Object.fromEntries( - temporalColumns.map(option => [option.column_name, option]), - ); return { - options, + options: temporalColumns, default: (datasource as Dataset)?.main_dttm_col || temporalColumns[0]?.column_name || @@ -209,22 +260,36 @@ export const dnd_granularity_sqla: typeof dndGroupByControl = { isTemporal: true, }; } - const sortedQueryColumns = (datasource as QueryResponse)?.columns?.sort( query => (query?.is_dttm ? -1 : 1), ); - const options = Object.fromEntries( - sortedQueryColumns.map(option => [option.name, option]), - ); return { - options, + options: sortedQueryColumns, default: sortedQueryColumns[0]?.name || null, isTemporal: true, }; }, }; -export const dnd_x_axis: SharedControlConfig<'DndColumnSelect'> = { +export const dndXAxisControl: typeof dndGroupByControl = { ...dndGroupByControl, ...xAxisControlConfig, }; + +export function withDndFallback( + DndComponent: React.ComponentType, + FallbackComponent: React.ComponentType, +) { + return function DndControl(props: any) { + const enableExploreDnd = useMemo( + () => isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP), + [], + ); + + return enableExploreDnd ? ( + + ) : ( + + ); + }; +} diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.tsx index 1d99fa77ea8aa..9b06bb3b9b075 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/shared-controls/index.tsx @@ -33,7 +33,6 @@ * here's a list of the keys that are common to all controls, and as a result define the * control interface. */ -import React from 'react'; import { isEmpty } from 'lodash'; import { FeatureFlag, @@ -43,10 +42,9 @@ import { isFeatureEnabled, SequentialScheme, legacyValidateInteger, - validateNonEmpty, ComparisionType, - QueryResponse, - QueryColumn, + isAdhocColumn, + isPhysicalColumn, } from '@superset-ui/core'; import { @@ -57,38 +55,29 @@ import { D3_TIME_FORMAT_DOCS, DEFAULT_TIME_FORMAT, DEFAULT_NUMBER_FORMAT, - defineSavedMetrics, } from '../utils'; -import { TIME_FILTER_LABELS, DATASET_TIME_COLUMN_OPTION } from '../constants'; -import { - Metric, - SharedControlConfig, - ColumnMeta, - ExtraControlProps, - SelectControlConfig, - Dataset, -} from '../types'; -import { ColumnOption } from '../components/ColumnOption'; +import { TIME_FILTER_LABELS } from '../constants'; +import { SharedControlConfig, Dataset, ColumnMeta } from '../types'; import { - dnd_adhoc_filters, - dnd_adhoc_metric, - dnd_adhoc_metrics, - dnd_granularity_sqla, - dnd_sort_by, - dnd_secondary_metric, - dnd_size, - dnd_x, - dnd_y, + dndAdhocFilterControl, + dndAdhocMetricControl, + dndAdhocMetricsControl, + dndGranularitySqlaControl, + dndSortByControl, + dndSecondaryMetricControl, + dndSizeControl, + dndXControl, + dndYControl, dndColumnsControl, - dndEntity, + dndEntityControl, dndGroupByControl, - dndSeries, - dnd_adhoc_metric_2, - dnd_x_axis, + dndSeriesControl, + dndAdhocMetricControl2, + dndXAxisControl, } from './dndControls'; -import { QUERY_TIME_COLUMN_OPTION } from '..'; -import { xAxisControlConfig } from './constants'; + +export { withDndFallback } from './dndControls'; const categoricalSchemeRegistry = getCategoricalSchemeRegistry(); const sequentialSchemeRegistry = getSequentialSchemeRegistry(); @@ -103,77 +92,11 @@ const { user } = JSON.parse( appContainer?.getAttribute('data-bootstrap') || '{}', ); -type Control = { - savedMetrics?: Metric[] | null; - default?: unknown; -}; - type SelectDefaultOption = { label: string; value: string; }; -const groupByControl: SharedControlConfig<'SelectControl', ColumnMeta> = { - type: 'SelectControl', - label: t('Dimensions'), - multi: true, - freeForm: true, - clearable: true, - default: [], - includeTime: false, - description: t( - 'One or many columns to group by. High cardinality groupings should include a sort by metric ' + - 'and series limit to limit the number of fetched and rendered series.', - ), - optionRenderer: c => , - valueRenderer: c => , - valueKey: 'column_name', - allowAll: true, - filterOption: ({ data: opt }, text: string) => - (opt.column_name && - opt.column_name.toLowerCase().includes(text.toLowerCase())) || - (opt.verbose_name && - opt.verbose_name.toLowerCase().includes(text.toLowerCase())) || - false, - promptTextCreator: (label: unknown) => label, - mapStateToProps(state, { includeTime }) { - const newState: ExtraControlProps = {}; - const { datasource } = state; - if (datasource?.columns[0]?.hasOwnProperty('groupby')) { - const options = (datasource as Dataset).columns.filter(c => c.groupby); - if (includeTime) options.unshift(DATASET_TIME_COLUMN_OPTION); - newState.options = options; - } else { - const options = (datasource as QueryResponse).columns; - if (includeTime) options.unshift(QUERY_TIME_COLUMN_OPTION); - newState.options = options; - } - return newState; - }, - commaChoosesOption: false, -}; - -const metrics: SharedControlConfig<'MetricsControl'> = { - type: 'MetricsControl', - multi: true, - label: t('Metrics'), - validators: [validateNonEmpty], - mapStateToProps: ({ datasource }) => ({ - columns: datasource?.columns || [], - savedMetrics: defineSavedMetrics(datasource), - datasource, - datasourceType: datasource?.type, - }), - description: t('One or many metrics to display'), -}; - -const metric: SharedControlConfig<'MetricsControl'> = { - ...metrics, - multi: false, - label: t('Metric'), - description: t('Metric'), -}; - const datasourceControl: SharedControlConfig<'DatasourceControl'> = { type: 'DatasourceControl', label: t('Datasource'), @@ -201,13 +124,6 @@ const color_picker: SharedControlConfig<'ColorPickerControl'> = { renderTrigger: true, }; -const metric_2: SharedControlConfig<'MetricsControl'> = { - ...metric, - label: t('Right Axis Metric'), - clearable: true, - description: t('Choose a metric for right axis'), -}; - const linear_color_scheme: SharedControlConfig<'ColorSchemeControl'> = { type: 'ColorSchemeControl', label: t('Linear Color Scheme'), @@ -227,20 +143,6 @@ const linear_color_scheme: SharedControlConfig<'ColorSchemeControl'> = { }), }; -const secondary_metric: SharedControlConfig<'MetricsControl'> = { - ...metric, - label: t('Color Metric'), - default: null, - validators: [], - description: t('A metric to use for color'), -}; - -const columnsControl: typeof groupByControl = { - ...groupByControl, - label: t('Columns'), - description: t('One or many columns to pivot as columns'), -}; - const granularity: SharedControlConfig<'SelectControl'> = { type: 'SelectControl', freeForm: true, @@ -271,44 +173,6 @@ const granularity: SharedControlConfig<'SelectControl'> = { ), }; -const granularity_sqla: SharedControlConfig<'SelectControl', ColumnMeta> = { - type: 'SelectControl', - label: TIME_FILTER_LABELS.granularity_sqla, - description: t( - 'The time column for the visualization. Note that you ' + - 'can define arbitrary expression that return a DATETIME ' + - 'column in the table. Also note that the ' + - 'filter below is applied against this column or ' + - 'expression', - ), - default: (c: Control) => c.default, - clearable: false, - optionRenderer: c => , - valueRenderer: c => , - valueKey: 'column_name', - mapStateToProps: state => { - const props: Partial> = {}; - const { datasource } = state; - if (datasource?.hasOwnProperty('main_dttm_col')) { - const dataset = datasource as Dataset; - props.options = dataset.columns.filter((c: ColumnMeta) => c.is_dttm); - props.default = null; - if (dataset.main_dttm_col) { - props.default = dataset.main_dttm_col; - } else if (props?.options) { - props.default = (props.options[0] as ColumnMeta)?.column_name; - } - } else { - const sortedQueryColumns = (datasource as QueryResponse)?.columns?.sort( - query => (query?.is_dttm ? -1 : 1), - ); - props.options = sortedQueryColumns; - if (props?.options) props.default = props.options[0]?.name; - } - return props; - }, -}; - const time_grain_sqla: SharedControlConfig<'SelectControl'> = { type: 'SelectControl', label: TIME_FILTER_LABELS.time_grain_sqla, @@ -323,6 +187,23 @@ const time_grain_sqla: SharedControlConfig<'SelectControl'> = { mapStateToProps: ({ datasource }) => ({ choices: (datasource as Dataset)?.time_grain_sqla || null, }), + visibility: ({ controls }) => { + if (!isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)) { + return true; + } + + const xAxis = controls?.x_axis; + const xAxisValue = xAxis?.value; + if (isAdhocColumn(xAxisValue)) { + return true; + } + if (isPhysicalColumn(xAxisValue)) { + return !!(xAxis?.options ?? []).find( + (col: ColumnMeta) => col?.column_name === xAxisValue, + )?.is_dttm; + } + return false; + }, }; const time_range: SharedControlConfig<'DateFilterControl'> = { @@ -394,64 +275,6 @@ const series_limit: SharedControlConfig<'SelectControl'> = { ), }; -const sort_by: SharedControlConfig<'MetricsControl'> = { - type: 'MetricsControl', - label: t('Sort by'), - default: null, - description: t( - 'Metric used to define how the top series are sorted if a series or row limit is present. ' + - 'If undefined reverts to the first metric (where appropriate).', - ), - mapStateToProps: ({ datasource }) => ({ - columns: datasource?.columns || [], - savedMetrics: defineSavedMetrics(datasource), - datasource, - datasourceType: datasource?.type, - }), -}; - -const series: typeof groupByControl = { - ...groupByControl, - label: t('Dimensions'), - multi: false, - default: null, - description: t( - 'Defines the grouping of entities. ' + - 'Each series is shown as a specific color on the chart and ' + - 'has a legend toggle', - ), -}; - -const entity: typeof groupByControl = { - ...groupByControl, - label: t('Entity'), - default: null, - multi: false, - validators: [validateNonEmpty], - description: t('This defines the element to be plotted on the chart'), -}; - -const x: SharedControlConfig<'MetricsControl'> = { - ...metric, - label: t('X Axis'), - description: t('Metric assigned to the [X] axis'), - default: null, -}; - -const y: SharedControlConfig<'MetricsControl'> = { - ...metric, - label: t('Y Axis'), - default: null, - description: t('Metric assigned to the [Y] axis'), -}; - -const size: SharedControlConfig<'MetricsControl'> = { - ...metric, - label: t('Bubble Size'), - description: t('Metric used to calculate bubble size'), - default: null, -}; - const y_axis_format: SharedControlConfig<'SelectControl', SelectDefaultOption> = { type: 'SelectControl', @@ -490,23 +313,6 @@ const x_axis_time_format: SharedControlConfig< option.label.includes(search) || option.value.includes(search), }; -const adhoc_filters: SharedControlConfig<'AdhocFilterControl'> = { - type: 'AdhocFilterControl', - label: t('Filters'), - default: [], - description: '', - mapStateToProps: ({ datasource, form_data }) => ({ - columns: datasource?.columns[0]?.hasOwnProperty('filterable') - ? (datasource as Dataset)?.columns.filter(c => c.filterable) - : datasource?.columns || [], - savedMetrics: defineSavedMetrics(datasource), - // current active adhoc metrics - selectedMetrics: - form_data.metrics || (form_data.metric ? [form_data.metric] : []), - datasource, - }), -}; - const color_scheme: SharedControlConfig<'ColorSchemeControl'> = { type: 'ColorSchemeControl', label: t('Color Scheme'), @@ -534,51 +340,40 @@ const show_empty_columns: SharedControlConfig<'CheckboxControl'> = { description: t('Show empty columns'), }; -const x_axis: SharedControlConfig<'SelectControl', ColumnMeta> = { - ...groupByControl, - ...xAxisControlConfig, -}; - -const enableExploreDnd = isFeatureEnabled( - FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP, -); - -const sharedControls = { - metrics: enableExploreDnd ? dnd_adhoc_metrics : metrics, - metric: enableExploreDnd ? dnd_adhoc_metric : metric, +export default { + metrics: dndAdhocMetricsControl, + metric: dndAdhocMetricControl, datasource: datasourceControl, viz_type, color_picker, - metric_2: enableExploreDnd ? dnd_adhoc_metric_2 : metric_2, + metric_2: dndAdhocMetricControl2, linear_color_scheme, - secondary_metric: enableExploreDnd ? dnd_secondary_metric : secondary_metric, - groupby: enableExploreDnd ? dndGroupByControl : groupByControl, - columns: enableExploreDnd ? dndColumnsControl : columnsControl, + secondary_metric: dndSecondaryMetricControl, + groupby: dndGroupByControl, + columns: dndColumnsControl, granularity, - granularity_sqla: enableExploreDnd ? dnd_granularity_sqla : granularity_sqla, + granularity_sqla: dndGranularitySqlaControl, time_grain_sqla, time_range, row_limit, limit, - timeseries_limit_metric: enableExploreDnd ? dnd_sort_by : sort_by, - orderby: enableExploreDnd ? dnd_sort_by : sort_by, + timeseries_limit_metric: dndSortByControl, + orderby: dndSortByControl, order_desc, - series: enableExploreDnd ? dndSeries : series, - entity: enableExploreDnd ? dndEntity : entity, - x: enableExploreDnd ? dnd_x : x, - y: enableExploreDnd ? dnd_y : y, - size: enableExploreDnd ? dnd_size : size, + series: dndSeriesControl, + entity: dndEntityControl, + x: dndXControl, + y: dndYControl, + size: dndSizeControl, y_axis_format, x_axis_time_format, - adhoc_filters: enableExploreDnd ? dnd_adhoc_filters : adhoc_filters, + adhoc_filters: dndAdhocFilterControl, color_scheme, - series_columns: enableExploreDnd ? dndColumnsControl : columnsControl, + series_columns: dndColumnsControl, series_limit, - series_limit_metric: enableExploreDnd ? dnd_sort_by : sort_by, - legacy_order_by: enableExploreDnd ? dnd_sort_by : sort_by, + series_limit_metric: dndSortByControl, + legacy_order_by: dndSortByControl, truncate_metric, - x_axis: enableExploreDnd ? dnd_x_axis : x_axis, + x_axis: dndXAxisControl, show_empty_columns, }; - -export { sharedControls, dndEntity, dndColumnsControl }; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts index c2b4366ff5c5a..99ea6a5325e95 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/types.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/src/types.ts @@ -29,7 +29,7 @@ import type { QueryFormMetric, QueryResponse, } from '@superset-ui/core'; -import { sharedControls } from './shared-controls'; +import sharedControls from './shared-controls'; import sharedControlComponents from './shared-controls/components'; export type { Metric } from '@superset-ui/core'; @@ -221,6 +221,7 @@ export interface BaseControlConfig< chartState?: AnyDict, ) => ReactNode); default?: V; + initialValue?: V; renderTrigger?: boolean; validators?: ControlValueValidator[]; warning?: ReactNode; @@ -237,7 +238,7 @@ export interface BaseControlConfig< ) => boolean; mapStateToProps?: ( state: ControlPanelState, - controlState: ControlState, + controlState?: ControlState, // TODO: add strict `chartState` typing (see superset-frontend/src/explore/types) chartState?: AnyDict, ) => ExtraControlProps; diff --git a/superset-frontend/packages/superset-ui-chart-controls/src/utils/expandControlConfig.tsx b/superset-frontend/packages/superset-ui-chart-controls/src/utils/expandControlConfig.tsx index d06ad0d5127da..161dd5ad07aae 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/src/utils/expandControlConfig.tsx +++ b/superset-frontend/packages/superset-ui-chart-controls/src/utils/expandControlConfig.tsx @@ -17,7 +17,7 @@ * under the License. */ import React, { ReactElement } from 'react'; -import { sharedControls } from '../shared-controls'; +import sharedControls from '../shared-controls'; import sharedControlComponents from '../shared-controls/components'; import { ControlType, diff --git a/superset-frontend/packages/superset-ui-chart-controls/test/operators/pivotOperator.test.ts b/superset-frontend/packages/superset-ui-chart-controls/test/operators/pivotOperator.test.ts index 41c294b405a88..c6c7f905d20ec 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/test/operators/pivotOperator.test.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/test/operators/pivotOperator.test.ts @@ -56,13 +56,9 @@ const queryObject: QueryObject = { test('skip pivot', () => { expect(pivotOperator(formData, queryObject)).toEqual(undefined); - expect( - pivotOperator(formData, { ...queryObject, is_timeseries: false }), - ).toEqual(undefined); expect( pivotOperator(formData, { ...queryObject, - is_timeseries: true, metrics: [], }), ).toEqual(undefined); @@ -70,7 +66,10 @@ test('skip pivot', () => { test('pivot by __timestamp without groupby', () => { expect( - pivotOperator(formData, { ...queryObject, is_timeseries: true }), + pivotOperator( + { ...formData, granularity_sqla: 'time_column' }, + queryObject, + ), ).toEqual({ operation: 'pivot', options: { @@ -87,11 +86,13 @@ test('pivot by __timestamp without groupby', () => { test('pivot by __timestamp with groupby', () => { expect( - pivotOperator(formData, { - ...queryObject, - columns: ['foo', 'bar'], - is_timeseries: true, - }), + pivotOperator( + { ...formData, granularity_sqla: 'time_column' }, + { + ...queryObject, + columns: ['foo', 'bar'], + }, + ), ).toEqual({ operation: 'pivot', options: { diff --git a/superset-frontend/packages/superset-ui-chart-controls/test/operators/prophetOperator.test.ts b/superset-frontend/packages/superset-ui-chart-controls/test/operators/prophetOperator.test.ts index 824efe5c15cc5..2f3c63ddf86fb 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/test/operators/prophetOperator.test.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/test/operators/prophetOperator.test.ts @@ -47,6 +47,7 @@ test('should do prophetOperator with default index', () => { prophetOperator( { ...formData, + granularity_sqla: 'time_column', forecastEnabled: true, forecastPeriods: '3', forecastInterval: '5', diff --git a/superset-frontend/packages/superset-ui-chart-controls/test/operators/timeComparePivotOperator.test.ts b/superset-frontend/packages/superset-ui-chart-controls/test/operators/timeComparePivotOperator.test.ts index 304756dee7637..bf46940d713ec 100644 --- a/superset-frontend/packages/superset-ui-chart-controls/test/operators/timeComparePivotOperator.test.ts +++ b/superset-frontend/packages/superset-ui-chart-controls/test/operators/timeComparePivotOperator.test.ts @@ -72,10 +72,10 @@ test('should pivot on any type of timeCompare', () => { ...formData, comparison_type: cType, time_compare: ['1 year ago', '1 year later'], + granularity_sqla: 'time_column', }, { ...queryObject, - is_timeseries: true, }, ), ).toEqual({ diff --git a/superset-frontend/packages/superset-ui-core/src/query/buildQueryContext.ts b/superset-frontend/packages/superset-ui-core/src/query/buildQueryContext.ts index dbc1289c5460d..4ab69ab40e8b6 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/buildQueryContext.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/buildQueryContext.ts @@ -23,6 +23,8 @@ import { QueryFieldAliases, QueryFormData } from './types/QueryFormData'; import { QueryContext, QueryObject } from './types/Query'; import { SetDataMaskHook } from '../chart'; import { JsonObject } from '../connection'; +import { isFeatureEnabled, FeatureFlag } from '../utils'; +import { normalizeTimeColumn } from './normalizeTimeColumn'; const WRAP_IN_ARRAY = (baseQueryObject: QueryObject) => [baseQueryObject]; @@ -45,13 +47,16 @@ export default function buildQueryContext( typeof options === 'function' ? { buildQuery: options, queryFields: {} } : options || {}; - const queries = buildQuery(buildQueryObject(formData, queryFields)); + let queries = buildQuery(buildQueryObject(formData, queryFields)); queries.forEach(query => { if (Array.isArray(query.post_processing)) { // eslint-disable-next-line no-param-reassign query.post_processing = query.post_processing.filter(Boolean); } }); + if (isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES)) { + queries = queries.map(query => normalizeTimeColumn(formData, query)); + } return { datasource: new DatasourceKey(formData.datasource).toObject(), force: formData.force || false, diff --git a/superset-frontend/packages/superset-ui-core/src/query/index.ts b/superset-frontend/packages/superset-ui-core/src/query/index.ts index 9bbfbc59fba86..a539267f9d56b 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/index.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/index.ts @@ -28,6 +28,7 @@ export { default as getColumnLabel } from './getColumnLabel'; export { default as getMetricLabel } from './getMetricLabel'; export { default as DatasourceKey } from './DatasourceKey'; export { default as normalizeOrderBy } from './normalizeOrderBy'; +export { normalizeTimeColumn } from './normalizeTimeColumn'; export * from './types/AnnotationLayer'; export * from './types/QueryFormData'; diff --git a/superset-frontend/packages/superset-ui-core/src/query/normalizeTimeColumn.ts b/superset-frontend/packages/superset-ui-core/src/query/normalizeTimeColumn.ts new file mode 100644 index 0000000000000..9879e9fe0a512 --- /dev/null +++ b/superset-frontend/packages/superset-ui-core/src/query/normalizeTimeColumn.ts @@ -0,0 +1,84 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 omit from 'lodash/omit'; + +import { + AdhocColumn, + isAdhocColumn, + isPhysicalColumn, + QueryFormColumn, + QueryFormData, + QueryObject, +} from './types'; +import { FeatureFlag, isFeatureEnabled } from '../utils'; + +export function normalizeTimeColumn( + formData: QueryFormData, + queryObject: QueryObject, +): QueryObject { + // The formData should be "raw form_data" -- the snake_case version of formData rather than camelCase. + if (!(isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) && formData.x_axis)) { + return queryObject; + } + + const { columns: _columns, extras: _extras } = queryObject; + const mutatedColumns: QueryFormColumn[] = [...(_columns || [])]; + const axisIdx = _columns?.findIndex( + col => + (isPhysicalColumn(col) && + isPhysicalColumn(formData.x_axis) && + col === formData.x_axis) || + (isAdhocColumn(col) && + isAdhocColumn(formData.x_axis) && + col.sqlExpression === formData.x_axis.sqlExpression), + ); + if ( + axisIdx !== undefined && + axisIdx > -1 && + formData.x_axis && + Array.isArray(_columns) + ) { + if (isAdhocColumn(_columns[axisIdx])) { + mutatedColumns[axisIdx] = { + timeGrain: _extras?.time_grain_sqla, + columnType: 'BASE_AXIS', + ...(_columns[axisIdx] as AdhocColumn), + }; + } else { + mutatedColumns[axisIdx] = { + timeGrain: _extras?.time_grain_sqla, + columnType: 'BASE_AXIS', + sqlExpression: formData.x_axis, + label: formData.x_axis, + expressionType: 'SQL', + }; + } + + const newQueryObject = omit(queryObject, [ + 'extras.time_grain_sqla', + 'is_timeseries', + ]); + newQueryObject.columns = mutatedColumns; + + return newQueryObject; + } + + // fallback, return original queryObject + return queryObject; +} diff --git a/superset-frontend/packages/superset-ui-core/src/query/types/Column.ts b/superset-frontend/packages/superset-ui-core/src/query/types/Column.ts index 7afe031bcfe3e..693bf5e54aace 100644 --- a/superset-frontend/packages/superset-ui-core/src/query/types/Column.ts +++ b/superset-frontend/packages/superset-ui-core/src/query/types/Column.ts @@ -27,6 +27,8 @@ export interface AdhocColumn { optionName?: string; sqlExpression: string; expressionType: 'SQL'; + columnType?: 'BASE_AXIS' | 'SERIES'; + timeGrain?: string; } /** diff --git a/superset-frontend/packages/superset-ui-core/test/query/buildQueryContext.test.ts b/superset-frontend/packages/superset-ui-core/test/query/buildQueryContext.test.ts index baae438321aec..3cfbb4e3bbd1c 100644 --- a/superset-frontend/packages/superset-ui-core/test/query/buildQueryContext.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/query/buildQueryContext.test.ts @@ -17,6 +17,7 @@ * under the License. */ import { buildQueryContext } from '@superset-ui/core'; +import * as queryModule from '../../src/query/normalizeTimeColumn'; describe('buildQueryContext', () => { it('should build datasource for table sources and apply defaults', () => { @@ -122,4 +123,50 @@ describe('buildQueryContext', () => { }, ]); }); + it('should call normalizeTimeColumn if GENERIC_CHART_AXES is enabled', () => { + // @ts-ignore + const spy = jest.spyOn(window, 'window', 'get').mockImplementation(() => ({ + featureFlags: { + GENERIC_CHART_AXES: true, + }, + })); + const spyNormalizeTimeColumn = jest.spyOn( + queryModule, + 'normalizeTimeColumn', + ); + + buildQueryContext( + { + datasource: '5__table', + viz_type: 'table', + }, + () => [{}], + ); + expect(spyNormalizeTimeColumn).toBeCalled(); + spy.mockRestore(); + spyNormalizeTimeColumn.mockRestore(); + }); + it("shouldn't call normalizeTimeColumn if GENERIC_CHART_AXES is disabled", () => { + // @ts-ignore + const spy = jest.spyOn(window, 'window', 'get').mockImplementation(() => ({ + featureFlags: { + GENERIC_CHART_AXES: false, + }, + })); + const spyNormalizeTimeColumn = jest.spyOn( + queryModule, + 'normalizeTimeColumn', + ); + + buildQueryContext( + { + datasource: '5__table', + viz_type: 'table', + }, + () => [{}], + ); + expect(spyNormalizeTimeColumn).not.toBeCalled(); + spy.mockRestore(); + spyNormalizeTimeColumn.mockRestore(); + }); }); diff --git a/superset-frontend/packages/superset-ui-core/test/query/normalizeTimeColumn.test.ts b/superset-frontend/packages/superset-ui-core/test/query/normalizeTimeColumn.test.ts new file mode 100644 index 0000000000000..1466d10619835 --- /dev/null +++ b/superset-frontend/packages/superset-ui-core/test/query/normalizeTimeColumn.test.ts @@ -0,0 +1,247 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 { + normalizeTimeColumn, + QueryObject, + SqlaFormData, +} from '@superset-ui/core'; + +describe('disabled GENERIC_CHART_AXES', () => { + let windowSpy: any; + + beforeAll(() => { + // @ts-ignore + windowSpy = jest.spyOn(window, 'window', 'get').mockImplementation(() => ({ + featureFlags: { + GENERIC_CHART_AXES: false, + }, + })); + }); + + afterAll(() => { + windowSpy.mockRestore(); + }); + + it('should return original QueryObject if disabled GENERIC_CHART_AXES', () => { + const formData: SqlaFormData = { + datasource: '5__table', + viz_type: 'table', + granularity: 'time_column', + time_grain_sqla: 'P1Y', + time_range: '1 year ago : 2013', + columns: ['col1'], + metrics: ['count(*)'], + x_axis: 'time_column', + }; + const query: QueryObject = { + datasource: '5__table', + viz_type: 'table', + granularity: 'time_column', + extras: { + time_grain_sqla: 'P1Y', + }, + time_range: '1 year ago : 2013', + orderby: [['count(*)', true]], + columns: ['col1'], + metrics: ['count(*)'], + is_timeseries: true, + }; + expect(normalizeTimeColumn(formData, query)).toEqual(query); + }); +}); + +describe('enabled GENERIC_CHART_AXES', () => { + let windowSpy: any; + + beforeAll(() => { + // @ts-ignore + windowSpy = jest.spyOn(window, 'window', 'get').mockImplementation(() => ({ + featureFlags: { + GENERIC_CHART_AXES: true, + }, + })); + }); + + afterAll(() => { + windowSpy.mockRestore(); + }); + + it('should return original QueryObject if x_axis is empty', () => { + const formData: SqlaFormData = { + datasource: '5__table', + viz_type: 'table', + granularity: 'time_column', + time_grain_sqla: 'P1Y', + time_range: '1 year ago : 2013', + columns: ['col1'], + metrics: ['count(*)'], + }; + const query: QueryObject = { + datasource: '5__table', + viz_type: 'table', + granularity: 'time_column', + extras: { + time_grain_sqla: 'P1Y', + }, + time_range: '1 year ago : 2013', + orderby: [['count(*)', true]], + columns: ['col1'], + metrics: ['count(*)'], + is_timeseries: true, + }; + expect(normalizeTimeColumn(formData, query)).toEqual(query); + }); + + it('should support different columns for x-axis and granularity', () => { + const formData: SqlaFormData = { + datasource: '5__table', + viz_type: 'table', + granularity: 'time_column', + time_grain_sqla: 'P1Y', + time_range: '1 year ago : 2013', + x_axis: 'time_column_in_x_axis', + columns: ['col1'], + metrics: ['count(*)'], + }; + const query: QueryObject = { + datasource: '5__table', + viz_type: 'table', + granularity: 'time_column', + extras: { + time_grain_sqla: 'P1Y', + where: '', + having: '', + }, + time_range: '1 year ago : 2013', + orderby: [['count(*)', true]], + columns: ['time_column_in_x_axis', 'col1'], + metrics: ['count(*)'], + is_timeseries: true, + }; + expect(normalizeTimeColumn(formData, query)).toEqual({ + datasource: '5__table', + viz_type: 'table', + granularity: 'time_column', + extras: { where: '', having: '' }, + time_range: '1 year ago : 2013', + orderby: [['count(*)', true]], + columns: [ + { + timeGrain: 'P1Y', + columnType: 'BASE_AXIS', + sqlExpression: 'time_column_in_x_axis', + label: 'time_column_in_x_axis', + expressionType: 'SQL', + }, + 'col1', + ], + metrics: ['count(*)'], + }); + }); + + it('should support custom SQL in x-axis', () => { + const formData: SqlaFormData = { + datasource: '5__table', + viz_type: 'table', + granularity: 'time_column', + time_grain_sqla: 'P1Y', + time_range: '1 year ago : 2013', + x_axis: { + expressionType: 'SQL', + label: 'Order Data + 1 year', + sqlExpression: '"Order Date" + interval \'1 year\'', + }, + columns: ['col1'], + metrics: ['count(*)'], + }; + const query: QueryObject = { + datasource: '5__table', + viz_type: 'table', + granularity: 'time_column', + extras: { + time_grain_sqla: 'P1Y', + where: '', + having: '', + }, + time_range: '1 year ago : 2013', + orderby: [['count(*)', true]], + columns: [ + { + expressionType: 'SQL', + label: 'Order Data + 1 year', + sqlExpression: '"Order Date" + interval \'1 year\'', + }, + 'col1', + ], + metrics: ['count(*)'], + is_timeseries: true, + }; + expect(normalizeTimeColumn(formData, query)).toEqual({ + datasource: '5__table', + viz_type: 'table', + granularity: 'time_column', + extras: { where: '', having: '' }, + time_range: '1 year ago : 2013', + orderby: [['count(*)', true]], + columns: [ + { + timeGrain: 'P1Y', + columnType: 'BASE_AXIS', + expressionType: 'SQL', + label: 'Order Data + 1 year', + sqlExpression: `"Order Date" + interval '1 year'`, + }, + 'col1', + ], + metrics: ['count(*)'], + }); + }); + + it('fallback and invalid columns value', () => { + const formData: SqlaFormData = { + datasource: '5__table', + viz_type: 'table', + granularity: 'time_column', + time_grain_sqla: 'P1Y', + time_range: '1 year ago : 2013', + x_axis: { + expressionType: 'SQL', + label: 'Order Data + 1 year', + sqlExpression: '"Order Date" + interval \'1 year\'', + }, + columns: ['col1'], + metrics: ['count(*)'], + }; + const query: QueryObject = { + datasource: '5__table', + viz_type: 'table', + granularity: 'time_column', + extras: { + time_grain_sqla: 'P1Y', + where: '', + having: '', + }, + time_range: '1 year ago : 2013', + orderby: [['count(*)', true]], + metrics: ['count(*)'], + is_timeseries: true, + }; + expect(normalizeTimeColumn(formData, query)).toEqual(query); + }); +}); diff --git a/superset-frontend/packages/superset-ui-core/test/utils/featureFlag.test.ts b/superset-frontend/packages/superset-ui-core/test/utils/featureFlag.test.ts index 52a57909aa386..b2a273d2ed78b 100644 --- a/superset-frontend/packages/superset-ui-core/test/utils/featureFlag.test.ts +++ b/superset-frontend/packages/superset-ui-core/test/utils/featureFlag.test.ts @@ -19,17 +19,45 @@ import { FeatureFlag, isFeatureEnabled } from '@superset-ui/core'; -describe('isFeatureFlagEnabled', () => { +const originalFeatureFlags = window.featureFlags; +// eslint-disable-next-line no-console +const originalConsoleError = console.error; +const reset = () => { + window.featureFlags = originalFeatureFlags; + // eslint-disable-next-line no-console + console.error = originalConsoleError; +}; + +it('returns false and raises console error if feature flags have not been initialized', () => { + // eslint-disable-next-line no-console + console.error = jest.fn(); + delete (window as any).featureFlags; + expect(isFeatureEnabled(FeatureFlag.ALLOW_DASHBOARD_DOMAIN_SHARDING)).toEqual( + false, + ); + + // eslint-disable-next-line no-console + expect(console.error).toHaveBeenNthCalledWith( + 1, + 'Failed to query feature flag ALLOW_DASHBOARD_DOMAIN_SHARDING (see error below)', + ); + + reset(); +}); + +it('returns false for unset feature flag', () => { + expect(isFeatureEnabled(FeatureFlag.ALLOW_DASHBOARD_DOMAIN_SHARDING)).toEqual( + false, + ); + + reset(); +}); + +it('returns true for set feature flag', () => { window.featureFlags = { [FeatureFlag.CLIENT_CACHE]: true, }; - it('returns false for unset feature flag', () => { - expect( - isFeatureEnabled(FeatureFlag.ALLOW_DASHBOARD_DOMAIN_SHARDING), - ).toEqual(false); - }); - - it('returns true for set feature flag', () => { - expect(isFeatureEnabled(FeatureFlag.CLIENT_CACHE)).toEqual(true); - }); + + expect(isFeatureEnabled(FeatureFlag.CLIENT_CACHE)).toEqual(true); + reset(); }); diff --git a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-table/birthNames.json b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-table/birthNames.json index cecb37e02627e..c35d3a80665fc 100644 --- a/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-table/birthNames.json +++ b/superset-frontend/packages/superset-ui-demo/storybook/stories/plugins/plugin-chart-table/birthNames.json @@ -13,7 +13,6 @@ "id": 1, "name": "examples", "backend": "postgresql", - "allow_multi_schema_metadata_fetch": false, "allows_subquery": true, "allows_cost_estimate": null, "allows_virtual_table_explore": true, diff --git a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx index 3ba3548fc7633..09df0427e2084 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx +++ b/superset-frontend/plugins/legacy-plugin-chart-heatmap/src/controlPanel.tsx @@ -30,7 +30,7 @@ import { formatSelectOptions, formatSelectOptionsForRange, sections, - dndEntity, + sharedControls, getStandardizedControls, } from '@superset-ui/chart-controls'; @@ -52,7 +52,7 @@ const allColumns = { }; const dndAllColumns = { - ...dndEntity, + ...sharedControls.entity, description: t('Columns to display'), }; diff --git a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/controlPanel.ts b/superset-frontend/plugins/legacy-plugin-chart-histogram/src/controlPanel.ts index 6c5a13ce6a79e..d976b16bd2420 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-histogram/src/controlPanel.ts +++ b/superset-frontend/plugins/legacy-plugin-chart-histogram/src/controlPanel.ts @@ -16,44 +16,28 @@ * specific language governing permissions and limitations * under the License. */ -import { - FeatureFlag, - isFeatureEnabled, - t, - validateNonEmpty, -} from '@superset-ui/core'; +import { t, validateNonEmpty } from '@superset-ui/core'; import { columnChoices, ControlPanelConfig, ControlPanelState, formatSelectOptions, sections, - dndColumnsControl, getStandardizedControls, + sharedControls, } from '@superset-ui/chart-controls'; -const allColumns = { - type: 'SelectControl', +const columnsConfig = { + ...sharedControls.columns, label: t('Columns'), - default: null, description: t('Select the numeric columns to draw the histogram'), mapStateToProps: (state: ControlPanelState) => ({ + ...(sharedControls.columns.mapStateToProps?.(state) || {}), choices: columnChoices(state.datasource), }), - multi: true, - validators: [validateNonEmpty], -}; - -const dndAllColumns = { - ...dndColumnsControl, - description: t('Select the numeric columns to draw the histogram'), validators: [validateNonEmpty], }; -const columnsConfig = isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP) - ? dndAllColumns - : allColumns; - const config: ControlPanelConfig = { controlPanelSections: [ sections.legacyRegularTime, diff --git a/superset-frontend/plugins/legacy-plugin-chart-map-box/src/controlPanel.ts b/superset-frontend/plugins/legacy-plugin-chart-map-box/src/controlPanel.ts index c5ee94aaf73cc..8d05cda078289 100644 --- a/superset-frontend/plugins/legacy-plugin-chart-map-box/src/controlPanel.ts +++ b/superset-frontend/plugins/legacy-plugin-chart-map-box/src/controlPanel.ts @@ -23,7 +23,7 @@ import { ControlPanelState, formatSelectOptions, sections, - dndEntity, + sharedControls, getStandardizedControls, } from '@superset-ui/chart-controls'; @@ -36,7 +36,7 @@ const allColumns = { }; const columnsConfig = isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP) - ? dndEntity + ? sharedControls.entity : allColumns; const colorChoices = [ diff --git a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/sharedDndControls.jsx b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/sharedDndControls.jsx index 6027b87f10204..30ffd27287ada 100644 --- a/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/sharedDndControls.jsx +++ b/superset-frontend/plugins/legacy-preset-chart-deckgl/src/utilities/sharedDndControls.jsx @@ -18,12 +18,12 @@ */ import { t } from '@superset-ui/core'; -import { dndEntity } from '@superset-ui/chart-controls'; +import { sharedControls } from '@superset-ui/chart-controls'; export const dndLineColumn = { name: 'line_column', config: { - ...dndEntity, + ...sharedControls.entity, label: t('Lines column'), description: t('The database columns that contains lines information'), }, @@ -32,7 +32,7 @@ export const dndLineColumn = { export const dndGeojsonColumn = { name: 'geojson', config: { - ...dndEntity, + ...sharedControls.entity, label: t('GeoJson Column'), description: t('Select the geojson column'), }, diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/controlPanel.ts index 45c145d82b1f8..47fbbd442247b 100644 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/controlPanel.ts +++ b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/Bar/controlPanel.ts @@ -64,7 +64,7 @@ const config: ControlPanelConfig = { controlState, ) || {}; timeserieslimitProps.value = state.controls?.limit?.value - ? controlState.value + ? controlState?.value : []; return timeserieslimitProps; }, diff --git a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/TimePivot/controlPanel.ts b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/TimePivot/controlPanel.ts index 2d1f765c31f96..36d6189c054c0 100644 --- a/superset-frontend/plugins/legacy-preset-chart-nvd3/src/TimePivot/controlPanel.ts +++ b/superset-frontend/plugins/legacy-preset-chart-nvd3/src/TimePivot/controlPanel.ts @@ -20,6 +20,7 @@ import { t } from '@superset-ui/core'; import { ControlPanelConfig, D3_FORMAT_OPTIONS, + getStandardizedControls, sections, } from '@superset-ui/chart-controls'; import { @@ -123,6 +124,10 @@ const config: ControlPanelConfig = { clearable: false, }, }, + formDataOverrides: formData => ({ + ...formData, + metric: getStandardizedControls().shiftMetric, + }), }; export default config; diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts index f179c6299b8f3..daacaa283ae28 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/BigNumber/BigNumberTotal/controlPanel.ts @@ -28,7 +28,7 @@ import { headerFontSize, subheaderFontSize } from '../sharedControls'; export default { controlPanelSections: [ - sections.legacyTimeseriesTime, + sections.legacyRegularTime, { label: t('Query'), expanded: true, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/controlPanel.tsx index 7f5e1c8c28536..00f2d9ed8f924 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/controlPanel.tsx @@ -291,12 +291,12 @@ function createAdvancedAnalyticsSection( const config: ControlPanelConfig = { controlPanelSections: [ - sections.legacyTimeseriesTime, + sections.genericTime, isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) ? { label: t('Shared query fields'), expanded: true, - controlSetRows: [['x_axis']], + controlSetRows: [['x_axis'], ['time_grain_sqla']], } : null, createQuerySection(t('Query A'), ''), diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts index fc4f1f82dfb6d..1300f66201ff6 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/MixedTimeseries/transformProps.ts @@ -167,7 +167,7 @@ export default function transformProps( }); const dataTypes = getColtypesMapping(queriesData[0]); - const xAxisDataType = dataTypes?.[xAxisCol]; + const xAxisDataType = dataTypes?.[xAxisCol] ?? dataTypes?.[xAxisOrig]; const xAxisType = getAxisType(xAxisDataType); const series: SeriesOption[] = []; const formatter = getNumberFormatter(contributionMode ? ',.0%' : yAxisFormat); diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx index 520772375e273..66bc2d0e79bc8 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Area/controlPanel.tsx @@ -52,7 +52,7 @@ const { } = DEFAULT_FORM_DATA; const config: ControlPanelConfig = { controlPanelSections: [ - sections.legacyTimeseriesTime, + sections.genericTime, sections.echartsTimeSeriesQuery, sections.advancedAnalyticsControls, sections.annotationsAndLayersControls, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx index d5e8fcdee90af..91b0b3ca60e29 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Bar/controlPanel.tsx @@ -259,7 +259,7 @@ function createAxisControl(axis: 'x' | 'y'): ControlSetRow[] { const config: ControlPanelConfig = { controlPanelSections: [ - sections.legacyTimeseriesTime, + sections.genericTime, sections.echartsTimeSeriesQuery, sections.advancedAnalyticsControls, sections.annotationsAndLayersControls, @@ -345,7 +345,7 @@ const config: ControlPanelConfig = { controlState, ) || {}; timeserieslimitProps.value = state.controls?.limit?.value - ? controlState.value + ? controlState?.value : []; return timeserieslimitProps; }, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx index 736d8b1054a97..1b7357eaaa184 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/controlPanel.tsx @@ -51,7 +51,7 @@ const { } = DEFAULT_FORM_DATA; const config: ControlPanelConfig = { controlPanelSections: [ - sections.legacyTimeseriesTime, + sections.genericTime, sections.echartsTimeSeriesQuery, sections.advancedAnalyticsControls, sections.annotationsAndLayersControls, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/index.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/index.ts index 0d89373c95a42..957d460547164 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/index.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Line/index.ts @@ -74,7 +74,7 @@ export default class EchartsTimeseriesLineChartPlugin extends ChartPlugin< AnnotationType.Timeseries, ], name: isFeatureEnabled(FeatureFlag.GENERIC_CHART_AXES) - ? t('Line Chart') + ? t('Line Chart v2') : t('Time-series Line Chart'), tags: [ t('ECharts'), diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Scatter/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Scatter/controlPanel.tsx index 3ffdb4489e2cd..379840ae3cbf3 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Scatter/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/Scatter/controlPanel.tsx @@ -47,7 +47,7 @@ const { } = DEFAULT_FORM_DATA; const config: ControlPanelConfig = { controlPanelSections: [ - sections.legacyTimeseriesTime, + sections.genericTime, sections.echartsTimeSeriesQuery, sections.advancedAnalyticsControls, sections.annotationsAndLayersControls, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/SmoothLine/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/SmoothLine/controlPanel.tsx index 88a8b1a2fefee..f758249572f12 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/SmoothLine/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Regular/SmoothLine/controlPanel.tsx @@ -47,7 +47,7 @@ const { } = DEFAULT_FORM_DATA; const config: ControlPanelConfig = { controlPanelSections: [ - sections.legacyTimeseriesTime, + sections.genericTime, sections.echartsTimeSeriesQuery, sections.advancedAnalyticsControls, sections.annotationsAndLayersControls, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Step/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Step/controlPanel.tsx index 5e02cbc59b5d3..8001acc5220ce 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Step/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/Step/controlPanel.tsx @@ -50,7 +50,7 @@ const { } = DEFAULT_FORM_DATA; const config: ControlPanelConfig = { controlPanelSections: [ - sections.legacyTimeseriesTime, + sections.genericTime, sections.echartsTimeSeriesQuery, sections.advancedAnalyticsControls, sections.annotationsAndLayersControls, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts index 5a90818e9c9f9..5d49b657e847c 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/Timeseries/transformProps.ts @@ -20,9 +20,7 @@ import { AnnotationLayer, CategoricalColorNamespace, - DTTM_ALIAS, GenericDataType, - getColumnLabel, getNumberFormatter, isEventAnnotationLayer, isFormulaAnnotationLayer, @@ -30,8 +28,9 @@ import { isTimeseriesAnnotationLayer, TimeseriesChartDataResponseResult, t, + DTTM_ALIAS, } from '@superset-ui/core'; -import { isDerivedSeries } from '@superset-ui/chart-controls'; +import { getAxis, isDerivedSeries } from '@superset-ui/chart-controls'; import { EChartsCoreOption, SeriesOption } from 'echarts'; import { ZRLineType } from 'echarts/types/src/util/types'; import { @@ -149,8 +148,9 @@ export default function transformProps( const colorScale = CategoricalColorNamespace.getScale(colorScheme as string); const rebasedData = rebaseForecastDatum(data, verboseMap); + // todo: if the both granularity_sqla and x_axis are `null`, should throw an error const xAxisCol = - verboseMap[xAxisOrig] || getColumnLabel(xAxisOrig || DTTM_ALIAS); + verboseMap[xAxisOrig] || getAxis(chartProps.rawFormData) || DTTM_ALIAS; const isHorizontal = orientation === OrientationType.horizontal; const { totalStackedValues, thresholdValues } = extractDataTotalValues( rebasedData, diff --git a/superset-frontend/plugins/plugin-chart-echarts/src/utils/forecast.ts b/superset-frontend/plugins/plugin-chart-echarts/src/utils/forecast.ts index 94e4630bf455e..485e9fb8968a6 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/src/utils/forecast.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/src/utils/forecast.ts @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ +import { isNumber } from 'lodash'; import { DataRecord, DTTM_ALIAS, NumberFormatter } from '@superset-ui/core'; import { OptionName } from 'echarts/types/src/util/types'; import { TooltipMarker } from 'echarts/types/src/util/format'; @@ -60,7 +61,7 @@ export const extractForecastValuesFromTooltipParams = ( const { marker, seriesId, value } = param; const context = extractForecastSeriesContext(seriesId); const numericValue = isHorizontal ? value[0] : value[1]; - if (numericValue) { + if (isNumber(numericValue)) { if (!(context.name in values)) values[context.name] = { marker: marker || '', @@ -94,7 +95,7 @@ export const formatForecastTooltipSeries = ({ }): string => { let row = `${marker}${sanitizeHtml(seriesName)}: `; let isObservation = false; - if (observation) { + if (isNumber(observation)) { isObservation = true; row += `${formatter(observation)}`; } diff --git a/superset-frontend/plugins/plugin-chart-echarts/test/utils/forecast.test.ts b/superset-frontend/plugins/plugin-chart-echarts/test/utils/forecast.test.ts index 819b2b85b137e..f3d6f60267207 100644 --- a/superset-frontend/plugins/plugin-chart-echarts/test/utils/forecast.test.ts +++ b/superset-frontend/plugins/plugin-chart-echarts/test/utils/forecast.test.ts @@ -154,103 +154,148 @@ describe('rebaseForecastDatum', () => { }); }); -describe('extractForecastValuesFromTooltipParams', () => { - it('should extract the proper data from tooltip params', () => { - expect( - extractForecastValuesFromTooltipParams([ - { - marker: '', - seriesId: 'abc', - value: [new Date(0), 10], - }, - { - marker: '', - seriesId: 'abc__yhat', - value: [new Date(0), 1], - }, - { - marker: '', - seriesId: 'abc__yhat_lower', - value: [new Date(0), 5], - }, - { - marker: '', - seriesId: 'abc__yhat_upper', - value: [new Date(0), 6], - }, - { - marker: '', - seriesId: 'qwerty', - value: [new Date(0), 2], - }, - ]), - ).toEqual({ - abc: { +test('extractForecastValuesFromTooltipParams should extract the proper data from tooltip params', () => { + expect( + extractForecastValuesFromTooltipParams([ + { marker: '', - observation: 10, - forecastTrend: 1, - forecastLower: 5, - forecastUpper: 6, + seriesId: 'abc', + value: [new Date(0), 10], }, - qwerty: { + { marker: '', - observation: 2, + seriesId: 'abc__yhat', + value: [new Date(0), 1], }, - }); - }); -}); - -const formatter = getNumberFormatter(NumberFormats.INTEGER); - -describe('formatForecastTooltipSeries', () => { - it('should generate a proper series tooltip', () => { - expect( - formatForecastTooltipSeries({ - seriesName: 'abc', + { marker: '', - observation: 10.1, - formatter, - }), - ).toEqual('abc: 10'); - expect( - formatForecastTooltipSeries({ - seriesName: 'qwerty', + seriesId: 'abc__yhat_lower', + value: [new Date(0), 5], + }, + { marker: '', - observation: 10.1, - forecastTrend: 20.1, - forecastLower: 5.1, - forecastUpper: 7.1, - formatter, - }), - ).toEqual('qwerty: 10, ŷ = 20 (5, 12)'); - expect( - formatForecastTooltipSeries({ - seriesName: 'qwerty', + seriesId: 'abc__yhat_upper', + value: [new Date(0), 6], + }, + { marker: '', - forecastTrend: 20, - forecastLower: 5, - forecastUpper: 7, - formatter, - }), - ).toEqual('qwerty: ŷ = 20 (5, 12)'); - expect( - formatForecastTooltipSeries({ - seriesName: 'qwerty', + seriesId: 'qwerty', + value: [new Date(0), 2], + }, + ]), + ).toEqual({ + abc: { + marker: '', + observation: 10, + forecastTrend: 1, + forecastLower: 5, + forecastUpper: 6, + }, + qwerty: { + marker: '', + observation: 2, + }, + }); +}); + +test('extractForecastValuesFromTooltipParams should extract valid values', () => { + expect( + extractForecastValuesFromTooltipParams([ + { marker: '', - observation: 10.1, - forecastLower: 6, - forecastUpper: 7, - formatter, - }), - ).toEqual('qwerty: 10 (6, 13)'); - expect( - formatForecastTooltipSeries({ - seriesName: 'qwerty', + seriesId: 'foo', + value: [0, 10], + }, + { marker: '', - forecastLower: 7, - forecastUpper: 8, - formatter, - }), - ).toEqual('qwerty: (7, 15)'); + seriesId: 'bar', + value: [100, 0], + }, + ]), + ).toEqual({ + foo: { + marker: '', + observation: 10, + }, + bar: { + marker: '', + observation: 0, + }, }); }); + +const formatter = getNumberFormatter(NumberFormats.INTEGER); + +test('formatForecastTooltipSeries should apply format to value', () => { + expect( + formatForecastTooltipSeries({ + seriesName: 'abc', + marker: '', + observation: 10.1, + formatter, + }), + ).toEqual('abc: 10'); +}); + +test('formatForecastTooltipSeries should show falsy value', () => { + expect( + formatForecastTooltipSeries({ + seriesName: 'abc', + marker: '', + observation: 0, + formatter, + }), + ).toEqual('abc: 0'); +}); + +test('formatForecastTooltipSeries should format full forecast', () => { + expect( + formatForecastTooltipSeries({ + seriesName: 'qwerty', + marker: '', + observation: 10.1, + forecastTrend: 20.1, + forecastLower: 5.1, + forecastUpper: 7.1, + formatter, + }), + ).toEqual('qwerty: 10, ŷ = 20 (5, 12)'); +}); + +test('formatForecastTooltipSeries should format forecast without observation', () => { + expect( + formatForecastTooltipSeries({ + seriesName: 'qwerty', + marker: '', + forecastTrend: 20, + forecastLower: 5, + forecastUpper: 7, + formatter, + }), + ).toEqual('qwerty: ŷ = 20 (5, 12)'); +}); + +test('formatForecastTooltipSeries should format forecast without point estimate', () => { + expect( + formatForecastTooltipSeries({ + seriesName: 'qwerty', + marker: '', + observation: 10.1, + forecastLower: 6, + forecastUpper: 7, + formatter, + }), + ).toEqual('qwerty: 10 (6, 13)'); +}); + +test('formatForecastTooltipSeries should format forecast with only confidence band', () => { + expect( + formatForecastTooltipSeries({ + seriesName: 'qwerty', + marker: '', + forecastLower: 7, + forecastUpper: 8, + formatter, + }), + ).toEqual('qwerty: (7, 15)'); +}); diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/columns.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/columns.tsx index 3aec61dc4060a..3c2434ac4063c 100644 --- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/columns.tsx +++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/columns.tsx @@ -49,7 +49,7 @@ const allColumns: typeof sharedControls.groupby = { options: datasource?.columns || [], queryMode: getQueryMode(controls), externalValidationErrors: - isRawMode({ controls }) && ensureIsArray(controlState.value).length === 0 + isRawMode({ controls }) && ensureIsArray(controlState?.value).length === 0 ? [t('must have a value')] : [], }), @@ -74,7 +74,7 @@ const dndAllColumns: typeof sharedControls.groupby = { } newState.queryMode = getQueryMode(controls); newState.externalValidationErrors = - isRawMode({ controls }) && ensureIsArray(controlState.value).length === 0 + isRawMode({ controls }) && ensureIsArray(controlState?.value).length === 0 ? [t('must have a value')] : []; return newState; diff --git a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/metrics.tsx b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/metrics.tsx index 96eab55f92974..c6aad5e320ba3 100644 --- a/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/metrics.tsx +++ b/superset-frontend/plugins/plugin-chart-handlebars/src/plugin/controls/metrics.tsx @@ -46,7 +46,7 @@ const percentMetrics: typeof sharedControls.metrics = { externalValidationErrors: validateAggControlValues(controls, [ controls.groupby?.value, controls.metrics?.value, - controlState.value, + controlState?.value, ]), }), rerender: ['groupby', 'metrics'], diff --git a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx index ee9ec5c1f39a1..03082cb21c07a 100644 --- a/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx +++ b/superset-frontend/plugins/plugin-chart-table/src/controlPanel.tsx @@ -40,7 +40,6 @@ import { sections, sharedControls, ControlPanelState, - ExtraControlProps, ControlState, emitFilterControl, Dataset, @@ -96,15 +95,14 @@ const queryMode: ControlConfig<'RadioButtonControl'> = { rerender: ['all_columns', 'groupby', 'metrics', 'percent_metrics'], }; -const all_columns: typeof sharedControls.groupby = { - type: 'SelectControl', +const allColumnsControl: typeof sharedControls.groupby = { + ...sharedControls.groupby, label: t('Columns'), description: t('Columns to display'), multi: true, freeForm: true, allowAll: true, commaChoosesOption: false, - default: [], optionRenderer: c => , valueRenderer: c => , valueKey: 'column_name', @@ -112,7 +110,7 @@ const all_columns: typeof sharedControls.groupby = { options: datasource?.columns || [], queryMode: getQueryMode(controls), externalValidationErrors: - isRawMode({ controls }) && ensureIsArray(controlState.value).length === 0 + isRawMode({ controls }) && ensureIsArray(controlState?.value).length === 0 ? [t('must have a value')] : [], }), @@ -120,37 +118,12 @@ const all_columns: typeof sharedControls.groupby = { resetOnHide: false, }; -const dnd_all_columns: typeof sharedControls.groupby = { - type: 'DndColumnSelect', - label: t('Columns'), - description: t('Columns to display'), - default: [], - mapStateToProps({ datasource, controls }, controlState) { - const newState: ExtraControlProps = {}; - if (datasource?.columns[0]?.hasOwnProperty('column_name')) { - const options = (datasource as Dataset).columns; - newState.options = Object.fromEntries( - options.map((option: ColumnMeta) => [option.column_name, option]), - ); - } else newState.options = datasource?.columns; - newState.queryMode = getQueryMode(controls); - newState.externalValidationErrors = - isRawMode({ controls }) && ensureIsArray(controlState.value).length === 0 - ? [t('must have a value')] - : []; - return newState; - }, - visibility: isRawMode, - resetOnHide: false, -}; - -const percent_metrics: typeof sharedControls.metrics = { - type: 'MetricsControl', +const percentMetricsControl: typeof sharedControls.metrics = { + ...sharedControls.metrics, label: t('Percentage metrics'), description: t( 'Metrics for which percentage of total are to be displayed. Calculated from only data within the row limit.', ), - multi: true, visibility: isAggMode, resetOnHide: false, mapStateToProps: ({ datasource, controls }, controlState) => ({ @@ -162,7 +135,7 @@ const percent_metrics: typeof sharedControls.metrics = { externalValidationErrors: validateAggControlValues(controls, [ controls.groupby?.value, controls.metrics?.value, - controlState.value, + controlState?.value, ]), }), rerender: ['groupby', 'metrics'], @@ -170,11 +143,6 @@ const percent_metrics: typeof sharedControls.metrics = { validators: [], }; -const dnd_percent_metrics = { - ...percent_metrics, - type: 'DndMetricSelect', -}; - const config: ControlPanelConfig = { controlPanelSections: [ sections.legacyTimeseriesTime, @@ -251,19 +219,13 @@ const config: ControlPanelConfig = { }, { name: 'all_columns', - config: isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP) - ? dnd_all_columns - : all_columns, + config: allColumnsControl, }, ], [ { name: 'percent_metrics', - config: { - ...(isFeatureEnabled(FeatureFlag.ENABLE_EXPLORE_DRAG_AND_DROP) - ? dnd_percent_metrics - : percent_metrics), - }, + config: percentMetricsControl, }, ], ['adhoc_filters'], diff --git a/superset-frontend/spec/fixtures/mockDatasource.js b/superset-frontend/spec/fixtures/mockDatasource.js index 30513fc126748..21a5805519b67 100644 --- a/superset-frontend/spec/fixtures/mockDatasource.js +++ b/superset-frontend/spec/fixtures/mockDatasource.js @@ -171,7 +171,6 @@ export default { name: 'birth_names', owners: [{ first_name: 'joe', last_name: 'man', id: 1 }], database: { - allow_multi_schema_metadata_fetch: null, name: 'main', backend: 'sqlite', }, diff --git a/superset-frontend/spec/helpers/shim.ts b/superset-frontend/spec/helpers/shim.ts index 7955ee15e0f0f..cf11e106b7548 100644 --- a/superset-frontend/spec/helpers/shim.ts +++ b/superset-frontend/spec/helpers/shim.ts @@ -53,6 +53,7 @@ g.window.performance = { now: () => new Date().getTime() }; g.window.Worker = Worker; g.window.IntersectionObserver = IntersectionObserver; g.window.ResizeObserver = ResizeObserver; +g.window.featureFlags = {}; g.URL.createObjectURL = () => ''; g.caches = new CacheStorage(); diff --git a/superset-frontend/spec/helpers/testing-library.tsx b/superset-frontend/spec/helpers/testing-library.tsx index b0e04902c2e16..d489ec2deaf2a 100644 --- a/superset-frontend/spec/helpers/testing-library.tsx +++ b/superset-frontend/spec/helpers/testing-library.tsx @@ -34,12 +34,14 @@ import { DndProvider } from 'react-dnd'; import { HTML5Backend } from 'react-dnd-html5-backend'; import reducerIndex from 'spec/helpers/reducerIndex'; import { QueryParamProvider } from 'use-query-params'; +import QueryProvider from 'src/views/QueryProvider'; type Options = Omit & { useRedux?: boolean; useDnd?: boolean; useQueryParams?: boolean; useRouter?: boolean; + useQuery?: boolean; initialState?: {}; reducers?: {}; store?: Store; @@ -50,6 +52,7 @@ function createWrapper(options?: Options) { useDnd, useRedux, useQueryParams, + useQuery = true, useRouter, initialState, reducers, @@ -85,6 +88,10 @@ function createWrapper(options?: Options) { result = {result}; } + if (useQuery) { + result = {result}; + } + return result; }; } diff --git a/superset-frontend/src/SqlLab/App.jsx b/superset-frontend/src/SqlLab/App.jsx index 39f784a5c57f9..ce768fee8c3d4 100644 --- a/superset-frontend/src/SqlLab/App.jsx +++ b/superset-frontend/src/SqlLab/App.jsx @@ -23,6 +23,7 @@ import thunkMiddleware from 'redux-thunk'; import { hot } from 'react-hot-loader/root'; import { ThemeProvider } from '@superset-ui/core'; import { GlobalStyles } from 'src/GlobalStyles'; +import QueryProvider from 'src/views/QueryProvider'; import { initFeatureFlags, isFeatureEnabled, @@ -134,12 +135,14 @@ if (sqlLabMenu) { } const Application = () => ( - - - - - - + + + + + + + + ); export default hot(Application); diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.js b/superset-frontend/src/SqlLab/actions/sqlLab.js index bac563436a327..5e5c530c28e2a 100644 --- a/superset-frontend/src/SqlLab/actions/sqlLab.js +++ b/superset-frontend/src/SqlLab/actions/sqlLab.js @@ -1137,8 +1137,9 @@ function getTableExtendedMetadata(table, query, dispatch) { ); } -export function addTable(query, database, tableName, schemaName) { - return function (dispatch) { +export function addTable(queryEditor, database, tableName, schemaName) { + return function (dispatch, getState) { + const query = getUpToDateQuery(getState(), queryEditor, queryEditor.id); const table = { dbId: query.dbId, queryEditorId: query.id, @@ -1425,7 +1426,10 @@ export function createDatasource(vizOptions) { return Promise.resolve(json); }) - .catch(() => { + .catch(error => { + getClientErrorObject(error).then(e => { + dispatch(addDangerToast(e.error)); + }); dispatch( createDatasourceFailed( t('An error occurred while creating the data source'), diff --git a/superset-frontend/src/SqlLab/actions/sqlLab.test.js b/superset-frontend/src/SqlLab/actions/sqlLab.test.js index 4dadb9ea89f8c..c0f3c8cd4bd3c 100644 --- a/superset-frontend/src/SqlLab/actions/sqlLab.test.js +++ b/superset-frontend/src/SqlLab/actions/sqlLab.test.js @@ -736,29 +736,71 @@ describe('async actions', () => { const database = { disable_data_preview: true }; const tableName = 'table'; const schemaName = 'schema'; - const store = mockStore({}); + const store = mockStore(initialState); const expectedActionTypes = [ actions.MERGE_TABLE, // addTable actions.MERGE_TABLE, // getTableMetadata actions.MERGE_TABLE, // getTableExtendedMetadata actions.MERGE_TABLE, // addTable ]; - return store - .dispatch(actions.addTable(query, database, tableName, schemaName)) - .then(() => { - expect(store.getActions().map(a => a.type)).toEqual( - expectedActionTypes, - ); - expect(store.getActions()[0].prepend).toBeTruthy(); - expect(fetchMock.calls(updateTableSchemaEndpoint)).toHaveLength(1); - expect(fetchMock.calls(getTableMetadataEndpoint)).toHaveLength(1); - expect(fetchMock.calls(getExtraTableMetadataEndpoint)).toHaveLength( - 1, - ); + const request = actions.addTable( + query, + database, + tableName, + schemaName, + ); + return request(store.dispatch, store.getState).then(() => { + expect(store.getActions().map(a => a.type)).toEqual( + expectedActionTypes, + ); + expect(store.getActions()[0].prepend).toBeTruthy(); + expect(fetchMock.calls(updateTableSchemaEndpoint)).toHaveLength(1); + expect(fetchMock.calls(getTableMetadataEndpoint)).toHaveLength(1); + expect(fetchMock.calls(getExtraTableMetadataEndpoint)).toHaveLength( + 1, + ); - // tab state is not updated, since no query was run - expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(0); - }); + // tab state is not updated, since no query was run + expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(0); + }); + }); + + it('fetches table schema state from unsaved change', () => { + const database = { disable_data_preview: true }; + const tableName = 'table'; + const schemaName = 'schema'; + const expectedDbId = 473892; + const store = mockStore({ + ...initialState, + sqlLab: { + ...initialState.sqlLab, + unsavedQueryEditor: { + id: query.id, + dbId: expectedDbId, + }, + }, + }); + const request = actions.addTable( + query, + database, + tableName, + schemaName, + ); + return request(store.dispatch, store.getState).then(() => { + expect( + fetchMock.calls( + `glob:**/api/v1/database/${expectedDbId}/table/*/*/`, + ), + ).toHaveLength(1); + expect( + fetchMock.calls( + `glob:**/api/v1/database/${expectedDbId}/table_extra/*/*/`, + ), + ).toHaveLength(1); + + // tab state is not updated, since no query was run + expect(fetchMock.calls(updateTabStateEndpoint)).toHaveLength(0); + }); }); it('updates and runs data preview query when configured', () => { diff --git a/superset-frontend/src/SqlLab/components/AceEditorWrapper/AceEditorWrapper.test.tsx b/superset-frontend/src/SqlLab/components/AceEditorWrapper/AceEditorWrapper.test.tsx index 6bfefc0a3db4f..2bbc90d947b06 100644 --- a/superset-frontend/src/SqlLab/components/AceEditorWrapper/AceEditorWrapper.test.tsx +++ b/superset-frontend/src/SqlLab/components/AceEditorWrapper/AceEditorWrapper.test.tsx @@ -34,7 +34,7 @@ import { AsyncAceEditorProps } from 'src/components/AsyncAceEditor'; const middlewares = [thunk]; const mockStore = configureStore(middlewares); -jest.mock('src/components/Select', () => () => ( +jest.mock('src/components/DeprecatedSelect', () => () => (
)); jest.mock('src/components/Select/Select', () => () => ( diff --git a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx index 5a8eaeaa11e37..59a6a5a118c1a 100644 --- a/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx +++ b/superset-frontend/src/SqlLab/components/EstimateQueryCostButton/EstimateQueryCostButton.test.tsx @@ -30,7 +30,7 @@ import EstimateQueryCostButton, { const middlewares = [thunk]; const mockStore = configureStore(middlewares); -jest.mock('src/components/Select', () => () => ( +jest.mock('src/components/DeprecatedSelect', () => () => (
)); jest.mock('src/components/Select/Select', () => () => ( diff --git a/superset-frontend/src/SqlLab/components/ExploreCtasResultsButton/index.tsx b/superset-frontend/src/SqlLab/components/ExploreCtasResultsButton/index.tsx index fbcdc15bc5a37..ac9e8b2fb453c 100644 --- a/superset-frontend/src/SqlLab/components/ExploreCtasResultsButton/index.tsx +++ b/superset-frontend/src/SqlLab/components/ExploreCtasResultsButton/index.tsx @@ -17,19 +17,19 @@ * under the License. */ import React from 'react'; -import { useSelector } from 'react-redux'; -import { t } from '@superset-ui/core'; +import { useSelector, useDispatch } from 'react-redux'; +import { t, JsonObject } from '@superset-ui/core'; +import { + createCtasDatasource, + addInfoToast, + addDangerToast, +} from 'src/SqlLab/actions/sqlLab'; import { InfoTooltipWithTrigger } from '@superset-ui/chart-controls'; import Button from 'src/components/Button'; import { exploreChart } from 'src/explore/exploreUtils'; import { SqlLabRootState } from 'src/SqlLab/types'; interface ExploreCtasResultsButtonProps { - actions: { - createCtasDatasource: Function; - addInfoToast: Function; - addDangerToast: Function; - }; table: string; schema?: string | null; dbId: number; @@ -37,16 +37,15 @@ interface ExploreCtasResultsButtonProps { } const ExploreCtasResultsButton = ({ - actions, table, schema, dbId, templateParams, }: ExploreCtasResultsButtonProps) => { - const { createCtasDatasource, addInfoToast, addDangerToast } = actions; const errorMessage = useSelector( (state: SqlLabRootState) => state.sqlLab.errorMessage, ); + const dispatch = useDispatch<(dispatch: any) => Promise>(); const buildVizOptions = { datasourceName: table, @@ -56,7 +55,7 @@ const ExploreCtasResultsButton = ({ }; const visualize = () => { - createCtasDatasource(buildVizOptions) + dispatch(createCtasDatasource(buildVizOptions)) .then((data: { table_id: number }) => { const formData = { datasource: `${data.table_id}__table`, @@ -67,12 +66,14 @@ const ExploreCtasResultsButton = ({ all_columns: [], row_limit: 1000, }; - addInfoToast(t('Creating a data source and creating a new tab')); + dispatch( + addInfoToast(t('Creating a data source and creating a new tab')), + ); // open new window for data visualization exploreChart(formData); }) .catch(() => { - addDangerToast(errorMessage || t('An error occurred')); + dispatch(addDangerToast(errorMessage || t('An error occurred'))); }); }; diff --git a/superset-frontend/src/SqlLab/components/ExploreResultsButton/index.tsx b/superset-frontend/src/SqlLab/components/ExploreResultsButton/index.tsx index 24d5e8686f71b..4ab77777367fc 100644 --- a/superset-frontend/src/SqlLab/components/ExploreResultsButton/index.tsx +++ b/superset-frontend/src/SqlLab/components/ExploreResultsButton/index.tsx @@ -39,6 +39,7 @@ const ExploreResultsButton = ({ onClick={onClick} disabled={!allowsSubquery} tooltip={t('Explore the result set in the data exploration view')} + data-test="explore-results-button" > () => ( +jest.mock('src/components/DeprecatedSelect', () => () => (
)); jest.mock('src/components/Select/Select', () => () => ( diff --git a/superset-frontend/src/SqlLab/components/QuerySearch/index.tsx b/superset-frontend/src/SqlLab/components/QuerySearch/index.tsx index 635603e255f14..c36ab374784cc 100644 --- a/superset-frontend/src/SqlLab/components/QuerySearch/index.tsx +++ b/superset-frontend/src/SqlLab/components/QuerySearch/index.tsx @@ -18,7 +18,7 @@ */ import React, { useState, useEffect } from 'react'; import Button from 'src/components/Button'; -import Select from 'src/components/Select'; +import Select from 'src/components/DeprecatedSelect'; import { styled, t, SupersetClient, QueryResponse } from '@superset-ui/core'; import { debounce } from 'lodash'; import Loading from 'src/components/Loading'; diff --git a/superset-frontend/src/SqlLab/components/QueryTable/index.tsx b/superset-frontend/src/SqlLab/components/QueryTable/index.tsx index 54edb7f97e8d3..d7ef5ed51c028 100644 --- a/superset-frontend/src/SqlLab/components/QueryTable/index.tsx +++ b/superset-frontend/src/SqlLab/components/QueryTable/index.tsx @@ -245,7 +245,6 @@ const QueryTable = ({ showSql user={user} query={query} - actions={actions} height={400} displayLimit={displayLimit} defaultQueryLimit={1000} diff --git a/superset-frontend/src/SqlLab/components/ResultSet/ResultSet.test.jsx b/superset-frontend/src/SqlLab/components/ResultSet/ResultSet.test.jsx deleted file mode 100644 index c04e236133b72..0000000000000 --- a/superset-frontend/src/SqlLab/components/ResultSet/ResultSet.test.jsx +++ /dev/null @@ -1,219 +0,0 @@ -/** - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import React from 'react'; -import { shallow } from 'enzyme'; -import { styledMount } from 'spec/helpers/theming'; -import { render, screen } from 'spec/helpers/testing-library'; -import { Provider } from 'react-redux'; -import sinon from 'sinon'; -import Alert from 'src/components/Alert'; -import ProgressBar from 'src/components/ProgressBar'; -import Loading from 'src/components/Loading'; -import configureStore from 'redux-mock-store'; -import thunk from 'redux-thunk'; -import fetchMock from 'fetch-mock'; -import FilterableTable from 'src/components/FilterableTable'; -import ExploreResultsButton from 'src/SqlLab/components/ExploreResultsButton'; -import ResultSet from 'src/SqlLab/components/ResultSet'; -import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace'; -import { - cachedQuery, - failedQueryWithErrorMessage, - failedQueryWithErrors, - queries, - runningQuery, - stoppedQuery, - initialState, - user, - queryWithNoQueryLimit, -} from 'src/SqlLab/fixtures'; - -const mockStore = configureStore([thunk]); -const store = mockStore(initialState); -const clearQuerySpy = sinon.spy(); -const fetchQuerySpy = sinon.spy(); -const reRunQuerySpy = sinon.spy(); -const mockedProps = { - actions: { - clearQueryResults: clearQuerySpy, - fetchQueryResults: fetchQuerySpy, - reRunQuery: reRunQuerySpy, - }, - cache: true, - query: queries[0], - height: 140, - database: { allows_virtual_table_explore: true }, - user, - defaultQueryLimit: 1000, -}; -const stoppedQueryProps = { ...mockedProps, query: stoppedQuery }; -const runningQueryProps = { ...mockedProps, query: runningQuery }; -const fetchingQueryProps = { - ...mockedProps, - query: { - dbId: 1, - cached: false, - ctas: false, - id: 'ryhHUZCGb', - progress: 100, - state: 'fetching', - startDttm: Date.now() - 500, - }, -}; -const cachedQueryProps = { ...mockedProps, query: cachedQuery }; -const failedQueryWithErrorMessageProps = { - ...mockedProps, - query: failedQueryWithErrorMessage, -}; -const failedQueryWithErrorsProps = { - ...mockedProps, - query: failedQueryWithErrors, -}; -const newProps = { - query: { - cached: false, - resultsKey: 'new key', - results: { - data: [{ a: 1 }], - }, - }, -}; -fetchMock.get('glob:*/api/v1/dataset?*', { result: [] }); - -test('is valid', () => { - expect(React.isValidElement()).toBe(true); -}); - -test('renders a Table', () => { - const wrapper = shallow(); - expect(wrapper.find(FilterableTable)).toExist(); -}); - -describe('componentDidMount', () => { - const propsWithError = { - ...mockedProps, - query: { ...queries[0], errorMessage: 'Your session timed out' }, - }; - let spy; - beforeEach(() => { - reRunQuerySpy.resetHistory(); - spy = sinon.spy(ResultSet.prototype, 'componentDidMount'); - }); - afterEach(() => { - spy.restore(); - }); - it('should call reRunQuery if timed out', () => { - shallow(); - expect(reRunQuerySpy.callCount).toBe(1); - }); - - it('should not call reRunQuery if no error', () => { - shallow(); - expect(reRunQuerySpy.callCount).toBe(0); - }); -}); - -describe('UNSAFE_componentWillReceiveProps', () => { - const wrapper = shallow(); - let spy; - beforeEach(() => { - clearQuerySpy.resetHistory(); - fetchQuerySpy.resetHistory(); - spy = sinon.spy(ResultSet.prototype, 'UNSAFE_componentWillReceiveProps'); - }); - afterEach(() => { - spy.restore(); - }); - it('should update cached data', () => { - wrapper.setProps(newProps); - - expect(wrapper.state().data).toEqual(newProps.query.results.data); - expect(clearQuerySpy.callCount).toBe(1); - expect(clearQuerySpy.getCall(0).args[0]).toEqual(newProps.query); - expect(fetchQuerySpy.callCount).toBe(1); - expect(fetchQuerySpy.getCall(0).args[0]).toEqual(newProps.query); - }); -}); - -test('should render success query', () => { - const wrapper = shallow(); - const filterableTable = wrapper.find(FilterableTable); - expect(filterableTable.props().data).toBe(mockedProps.query.results.data); - expect(wrapper.find(ExploreResultsButton)).toExist(); -}); -test('should render empty results', () => { - const props = { - ...mockedProps, - query: { ...mockedProps.query, results: { data: [] } }, - }; - const wrapper = styledMount( - - - , - ); - expect(wrapper.find(FilterableTable)).not.toExist(); - expect(wrapper.find(Alert)).toExist(); - expect(wrapper.find(Alert).render().text()).toBe( - 'The query returned no data', - ); -}); - -test('should render cached query', () => { - const wrapper = shallow(); - const cachedData = [{ col1: 'a', col2: 'b' }]; - wrapper.setState({ data: cachedData }); - const filterableTable = wrapper.find(FilterableTable); - expect(filterableTable.props().data).toBe(cachedData); -}); - -test('should render stopped query', () => { - const wrapper = shallow(); - expect(wrapper.find(Alert)).toExist(); -}); - -test('should render running/pending/fetching query', () => { - const wrapper = shallow(); - expect(wrapper.find(ProgressBar)).toExist(); -}); - -test('should render fetching w/ 100 progress query', () => { - const wrapper = shallow(); - expect(wrapper.find(Loading)).toExist(); -}); - -test('should render a failed query with an error message', () => { - const wrapper = shallow(); - expect(wrapper.find(ErrorMessageWithStackTrace)).toExist(); -}); - -test('should render a failed query with an errors object', () => { - const wrapper = shallow(); - expect(wrapper.find(ErrorMessageWithStackTrace)).toExist(); -}); - -test('renders if there is no limit in query.results but has queryLimit', () => { - render(, { useRedux: true }); - expect(screen.getByRole('grid')).toBeInTheDocument(); -}); - -test('renders if there is a limit in query.results but not queryLimit', () => { - const props = { ...mockedProps, query: queryWithNoQueryLimit }; - render(, { useRedux: true }); - expect(screen.getByRole('grid')).toBeInTheDocument(); -}); diff --git a/superset-frontend/src/SqlLab/components/ResultSet/ResultSet.test.tsx b/superset-frontend/src/SqlLab/components/ResultSet/ResultSet.test.tsx new file mode 100644 index 0000000000000..2818ed279c7d8 --- /dev/null +++ b/superset-frontend/src/SqlLab/components/ResultSet/ResultSet.test.tsx @@ -0,0 +1,216 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { render, screen, waitFor } from 'spec/helpers/testing-library'; +import configureStore from 'redux-mock-store'; +import { Store } from 'redux'; +import thunk from 'redux-thunk'; +import fetchMock from 'fetch-mock'; +import ResultSet from 'src/SqlLab/components/ResultSet'; +import { + cachedQuery, + failedQueryWithErrorMessage, + failedQueryWithErrors, + queries, + runningQuery, + stoppedQuery, + initialState, + user, + queryWithNoQueryLimit, +} from 'src/SqlLab/fixtures'; + +const mockedProps = { + cache: true, + query: queries[0], + height: 140, + database: { allows_virtual_table_explore: true }, + user, + defaultQueryLimit: 1000, +}; +const stoppedQueryProps = { ...mockedProps, query: stoppedQuery }; +const runningQueryProps = { ...mockedProps, query: runningQuery }; +const fetchingQueryProps = { + ...mockedProps, + query: { + dbId: 1, + cached: false, + ctas: false, + id: 'ryhHUZCGb', + progress: 100, + state: 'fetching', + startDttm: Date.now() - 500, + }, +}; +const cachedQueryProps = { ...mockedProps, query: cachedQuery }; +const failedQueryWithErrorMessageProps = { + ...mockedProps, + query: failedQueryWithErrorMessage, +}; +const failedQueryWithErrorsProps = { + ...mockedProps, + query: failedQueryWithErrors, +}; +const newProps = { + query: { + cached: false, + resultsKey: 'new key', + results: { + data: [{ a: 1 }], + }, + }, +}; +fetchMock.get('glob:*/api/v1/dataset?*', { result: [] }); + +const middlewares = [thunk]; +const mockStore = configureStore(middlewares); +const setup = (props?: any, store?: Store) => + render(, { + useRedux: true, + ...(store && { store }), + }); + +describe('ResultSet', () => { + it('renders a Table', async () => { + const { getByTestId } = setup(mockedProps, mockStore(initialState)); + const table = getByTestId('table-container'); + expect(table).toBeInTheDocument(); + }); + + it('should render success query', async () => { + const { queryAllByText, getByTestId } = setup( + mockedProps, + mockStore(initialState), + ); + + const table = getByTestId('table-container'); + expect(table).toBeInTheDocument(); + + const firstColumn = queryAllByText( + mockedProps.query.results?.columns[0].name ?? '', + )[0]; + const secondColumn = queryAllByText( + mockedProps.query.results?.columns[1].name ?? '', + )[0]; + expect(firstColumn).toBeInTheDocument(); + expect(secondColumn).toBeInTheDocument(); + + const exploreButton = getByTestId('explore-results-button'); + expect(exploreButton).toBeInTheDocument(); + }); + + it('should render empty results', async () => { + const props = { + ...mockedProps, + query: { ...mockedProps.query, results: { data: [] } }, + }; + await waitFor(() => { + setup(props, mockStore(initialState)); + }); + + const alert = screen.getByRole('alert'); + expect(alert).toBeInTheDocument(); + expect(alert).toHaveTextContent('The query returned no data'); + }); + + it('should call reRunQuery if timed out', async () => { + const store = mockStore(initialState); + const propsWithError = { + ...mockedProps, + query: { ...queries[0], errorMessage: 'Your session timed out' }, + }; + + setup(propsWithError, store); + expect(store.getActions()).toHaveLength(1); + expect(store.getActions()[0].query.errorMessage).toEqual( + 'Your session timed out', + ); + expect(store.getActions()[0].type).toEqual('START_QUERY'); + }); + + it('should not call reRunQuery if no error', async () => { + const store = mockStore(initialState); + setup(mockedProps, store); + expect(store.getActions()).toEqual([]); + }); + + it('should render cached query', async () => { + const store = mockStore(initialState); + const { rerender } = setup(cachedQueryProps, store); + + // @ts-ignore + rerender(); + expect(store.getActions()).toHaveLength(1); + expect(store.getActions()[0].query.results).toEqual( + cachedQueryProps.query.results, + ); + expect(store.getActions()[0].type).toEqual('CLEAR_QUERY_RESULTS'); + }); + + it('should render stopped query', async () => { + await waitFor(() => { + setup(stoppedQueryProps, mockStore(initialState)); + }); + + const alert = screen.getByRole('alert'); + expect(alert).toBeInTheDocument(); + }); + + it('should render running/pending/fetching query', async () => { + const { getByTestId } = setup(runningQueryProps, mockStore(initialState)); + const progressBar = getByTestId('progress-bar'); + expect(progressBar).toBeInTheDocument(); + }); + + it('should render fetching w/ 100 progress query', async () => { + const { getByRole, getByText } = setup( + fetchingQueryProps, + mockStore(initialState), + ); + const loading = getByRole('status'); + expect(loading).toBeInTheDocument(); + expect(getByText('fetching')).toBeInTheDocument(); + }); + + it('should render a failed query with an error message', async () => { + await waitFor(() => { + setup(failedQueryWithErrorMessageProps, mockStore(initialState)); + }); + + expect(screen.getByText('Database error')).toBeInTheDocument(); + expect(screen.getByText('Something went wrong')).toBeInTheDocument(); + }); + + it('should render a failed query with an errors object', async () => { + await waitFor(() => { + setup(failedQueryWithErrorsProps, mockStore(initialState)); + }); + expect(screen.getByText('Database error')).toBeInTheDocument(); + }); + + it('renders if there is no limit in query.results but has queryLimit', async () => { + const { getByRole } = setup(mockedProps, mockStore(initialState)); + expect(getByRole('grid')).toBeInTheDocument(); + }); + + it('renders if there is a limit in query.results but not queryLimit', async () => { + const props = { ...mockedProps, query: queryWithNoQueryLimit }; + const { getByRole } = setup(props, mockStore(initialState)); + expect(getByRole('grid')).toBeInTheDocument(); + }); +}); diff --git a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx index 27913d7fc4a9d..78387f6dc44d4 100644 --- a/superset-frontend/src/SqlLab/components/ResultSet/index.tsx +++ b/superset-frontend/src/SqlLab/components/ResultSet/index.tsx @@ -16,12 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; +import { useDispatch } from 'react-redux'; import ButtonGroup from 'src/components/ButtonGroup'; import Alert from 'src/components/Alert'; import Button from 'src/components/Button'; import shortid from 'shortid'; import { styled, t, QueryResponse } from '@superset-ui/core'; +import { usePrevious } from 'src/hooks/usePrevious'; import ErrorMessageWithStackTrace from 'src/components/ErrorMessage/ErrorMessageWithStackTrace'; import { ISaveableDatasource, @@ -40,7 +42,14 @@ import FilterableTable, { import CopyToClipboard from 'src/components/CopyToClipboard'; import { addDangerToast } from 'src/components/MessageToasts/actions'; import { prepareCopyToClipboardTabularData } from 'src/utils/common'; -import { CtasEnum } from 'src/SqlLab/actions/sqlLab'; +import { + CtasEnum, + clearQueryResults, + addQueryEditor, + fetchQueryResults, + reFetchQueryResults, + reRunQuery, +} from 'src/SqlLab/actions/sqlLab'; import { URL_PARAMS } from 'src/constants'; import ExploreCtasResultsButton from '../ExploreCtasResultsButton'; import ExploreResultsButton from '../ExploreResultsButton'; @@ -54,9 +63,7 @@ enum LIMITING_FACTOR { NOT_LIMITED = 'NOT_LIMITED', } -interface ResultSetProps { - showControls?: boolean; - actions: Record; +export interface ResultSetProps { cache?: boolean; csv?: boolean; database?: Record; @@ -70,17 +77,9 @@ interface ResultSetProps { defaultQueryLimit: number; } -interface ResultSetState { - searchText: string; - showExploreResultsButton: boolean; - data: Record[]; - showSaveDatasetModal: boolean; - alertIsOpen: boolean; -} - const ResultlessStyles = styled.div` position: relative; - min-height: 100px; + min-height: ${({ theme }) => theme.gridUnit * 25}px; [role='alert'] { margin-top: ${({ theme }) => theme.gridUnit * 2}px; } @@ -100,8 +99,8 @@ const MonospaceDiv = styled.div` `; const ReturnedRows = styled.div` - font-size: 13px; - line-height: 24px; + font-size: ${({ theme }) => theme.typography.sizes.s}px; + line-height: ${({ theme }) => theme.gridUnit * 6}px; `; const ResultSetControls = styled.div` @@ -121,115 +120,84 @@ const LimitMessage = styled.span` margin-left: ${({ theme }) => theme.gridUnit * 2}px; `; -export default class ResultSet extends React.PureComponent< - ResultSetProps, - ResultSetState -> { - static defaultProps = { - cache: false, - csv: true, - database: {}, - search: true, - showSql: false, - visualize: true, - }; - - constructor(props: ResultSetProps) { - super(props); - this.state = { - searchText: '', - showExploreResultsButton: false, - data: [], - showSaveDatasetModal: false, - alertIsOpen: false, - }; - this.changeSearch = this.changeSearch.bind(this); - this.fetchResults = this.fetchResults.bind(this); - this.popSelectStar = this.popSelectStar.bind(this); - this.reFetchQueryResults = this.reFetchQueryResults.bind(this); - this.toggleExploreResultsButton = - this.toggleExploreResultsButton.bind(this); - } +const ResultSet = ({ + cache = false, + csv = true, + database = {}, + displayLimit, + height, + query, + search = true, + showSql = false, + visualize = true, + user, + defaultQueryLimit, +}: ResultSetProps) => { + const [searchText, setSearchText] = useState(''); + const [cachedData, setCachedData] = useState[]>([]); + const [showSaveDatasetModal, setShowSaveDatasetModal] = useState(false); + const [alertIsOpen, setAlertIsOpen] = useState(false); + + const dispatch = useDispatch(); + + const reRunQueryIfSessionTimeoutErrorOnMount = useCallback(() => { + if ( + query.errorMessage && + query.errorMessage.indexOf('session timed out') > 0 + ) { + dispatch(reRunQuery(query)); + } + }, []); - async componentDidMount() { + useEffect(() => { // only do this the first time the component is rendered/mounted - this.reRunQueryIfSessionTimeoutErrorOnMount(); - } + reRunQueryIfSessionTimeoutErrorOnMount(); + }, [reRunQueryIfSessionTimeoutErrorOnMount]); - UNSAFE_componentWillReceiveProps(nextProps: ResultSetProps) { - // when new results comes in, save them locally and clear in store - if ( - this.props.cache && - !nextProps.query.cached && - nextProps.query.results && - nextProps.query.results.data && - nextProps.query.results.data.length > 0 - ) { - this.setState({ data: nextProps.query.results.data }, () => - this.clearQueryResults(nextProps.query), - ); + const fetchResults = (query: QueryResponse) => { + dispatch(fetchQueryResults(query, displayLimit)); + }; + + const prevQuery = usePrevious(query); + useEffect(() => { + if (cache && query.cached && query?.results?.data?.length > 0) { + setCachedData(query.results.data); + dispatch(clearQueryResults(query)); } if ( - nextProps.query.resultsKey && - nextProps.query.resultsKey !== this.props.query.resultsKey + query.resultsKey && + prevQuery?.resultsKey && + query.resultsKey !== prevQuery.resultsKey ) { - this.fetchResults(nextProps.query); + fetchResults(query); } - } + }, [query, cache]); - calculateAlertRefHeight = (alertElement: HTMLElement | null) => { + const calculateAlertRefHeight = (alertElement: HTMLElement | null) => { if (alertElement) { - this.setState({ alertIsOpen: true }); + setAlertIsOpen(true); } else { - this.setState({ alertIsOpen: false }); + setAlertIsOpen(false); } }; - clearQueryResults(query: QueryResponse) { - this.props.actions.clearQueryResults(query); - } - - popSelectStar(tempSchema: string | null, tempTable: string) { + const popSelectStar = (tempSchema: string | null, tempTable: string) => { const qe = { id: shortid.generate(), name: tempTable, autorun: false, - dbId: this.props.query.dbId, + dbId: query.dbId, sql: `SELECT * FROM ${tempSchema ? `${tempSchema}.` : ''}${tempTable}`, }; - this.props.actions.addQueryEditor(qe); - } - - toggleExploreResultsButton() { - this.setState(prevState => ({ - showExploreResultsButton: !prevState.showExploreResultsButton, - })); - } - - changeSearch(event: React.ChangeEvent) { - this.setState({ searchText: event.target.value }); - } - - fetchResults(query: QueryResponse) { - this.props.actions.fetchQueryResults(query, this.props.displayLimit); - } - - reFetchQueryResults(query: QueryResponse) { - this.props.actions.reFetchQueryResults(query); - } + dispatch(addQueryEditor(qe)); + }; - reRunQueryIfSessionTimeoutErrorOnMount() { - const { query } = this.props; - if ( - query.errorMessage && - query.errorMessage.indexOf('session timed out') > 0 - ) { - this.props.actions.reRunQuery(query); - } - } + const changeSearch = (event: React.ChangeEvent) => { + setSearchText(event.target.value); + }; - createExploreResultsOnClick = async () => { - const { results } = this.props.query; + const createExploreResultsOnClick = async () => { + const { results } = query; if (results?.query_id) { const key = await postFormData(results.query_id, 'query', { @@ -248,16 +216,14 @@ export default class ResultSet extends React.PureComponent< } }; - renderControls() { - if (this.props.search || this.props.visualize || this.props.csv) { - let { data } = this.props.query.results; - if (this.props.cache && this.props.query.cached) { - ({ data } = this.state); + const renderControls = () => { + if (search || visualize || csv) { + let { data } = query.results; + if (cache && query.cached) { + data = cachedData; } - const { columns } = this.props.query.results; + const { columns } = query.results; // Added compute logic to stop user from being able to Save & Explore - const { showSaveDatasetModal } = this.state; - const { query } = this.props; const datasource: ISaveableDatasource = { columns: query.results.columns as ISimpleColumn[], @@ -272,7 +238,7 @@ export default class ResultSet extends React.PureComponent< this.setState({ showSaveDatasetModal: false })} + onHide={() => setShowSaveDatasetModal(false)} buttonTextOnSave={t('Save & Explore')} buttonTextOnOverwrite={t('Overwrite & Explore')} modalDescription={t( @@ -281,14 +247,13 @@ export default class ResultSet extends React.PureComponent< datasource={datasource} /> - {this.props.visualize && - this.props.database?.allows_virtual_table_explore && ( - - )} - {this.props.csv && ( + {visualize && database?.allows_virtual_table_explore && ( + + )} + {csv && ( @@ -305,11 +270,11 @@ export default class ResultSet extends React.PureComponent< hideTooltip /> - {this.props.search && ( + {search && ( MAX_COLUMNS_FOR_TABLE} placeholder={ @@ -323,14 +288,14 @@ export default class ResultSet extends React.PureComponent< ); } return
; - } + }; - renderRowsReturned() { - const { results, rows, queryLimit, limitingFactor } = this.props.query; + const renderRowsReturned = () => { + const { results, rows, queryLimit, limitingFactor } = query; let limitMessage; const limitReached = results?.displayLimitReached; const limit = queryLimit || results.query.limit; - const isAdmin = !!this.props.user?.roles?.Admin; + const isAdmin = !!user?.roles?.Admin; const rowsCount = Math.min(rows || 0, results?.data?.length || 0); const displayMaxRowsReachedMessage = { @@ -348,10 +313,10 @@ export default class ResultSet extends React.PureComponent< ), }; const shouldUseDefaultDropdownAlert = - limit === this.props.defaultQueryLimit && + limit === defaultQueryLimit && limitingFactor === LIMITING_FACTOR.DROPDOWN; - if (limitingFactor === LIMITING_FACTOR.QUERY && this.props.csv) { + if (limitingFactor === LIMITING_FACTOR.QUERY && csv) { limitMessage = t( 'The number of rows displayed is limited to %(rows)d by the query', { rows }, @@ -386,11 +351,11 @@ export default class ResultSet extends React.PureComponent< )} {!limitReached && shouldUseDefaultDropdownAlert && ( -
+
this.setState({ alertIsOpen: false })} + onClose={() => setAlertIsOpen(false)} description={t( 'The number of rows displayed is limited to %s by the dropdown.', rows, @@ -399,10 +364,10 @@ export default class ResultSet extends React.PureComponent<
)} {limitReached && ( -
+
this.setState({ alertIsOpen: false })} + onClose={() => setAlertIsOpen(false)} message={t('%(rows)d rows returned', { rows: rowsCount })} description={ isAdmin @@ -414,193 +379,191 @@ export default class ResultSet extends React.PureComponent< )} ); + }; + + const limitReached = query?.results?.displayLimitReached; + let sql; + let exploreDBId = query.dbId; + if (database?.explore_database_id) { + exploreDBId = database.explore_database_id; } - render() { - const { query } = this.props; - const limitReached = query?.results?.displayLimitReached; - let sql; - let exploreDBId = query.dbId; - if (this.props.database && this.props.database.explore_database_id) { - exploreDBId = this.props.database.explore_database_id; - } - let trackingUrl; - if ( - query.trackingUrl && - query.state !== 'success' && - query.state !== 'fetching' - ) { - trackingUrl = ( - - ); - } + let trackingUrl; + if ( + query.trackingUrl && + query.state !== 'success' && + query.state !== 'fetching' + ) { + trackingUrl = ( + + ); + } - if (this.props.showSql) sql = ; + if (showSql) { + sql = ; + } + + if (query.state === 'stopped') { + return ; + } + + if (query.state === 'failed') { + return ( + + {query.errorMessage}} + copyText={query.errorMessage || undefined} + link={query.link} + source="sqllab" + /> + {trackingUrl} + + ); + } - if (query.state === 'stopped') { - return ; + if (query.state === 'success' && query.ctas) { + const { tempSchema, tempTable } = query; + let object = 'Table'; + if (query.ctas_method === CtasEnum.VIEW) { + object = 'View'; } - if (query.state === 'failed') { - return ( - - {query.errorMessage}} - copyText={query.errorMessage || undefined} - link={query.link} - source="sqllab" - /> - {trackingUrl} - - ); + return ( +
+ + {t(object)} [ + + {tempSchema ? `${tempSchema}.` : ''} + {tempTable} + + ] {t('was created')}   + + + + + + } + /> +
+ ); + } + + if (query.state === 'success' && query.results) { + const { results } = query; + // Accounts for offset needed for height of ResultSetRowsReturned component if !limitReached + const rowMessageHeight = !limitReached ? 32 : 0; + // Accounts for offset needed for height of Alert if this.state.alertIsOpen + const alertContainerHeight = 70; + // We need to calculate the height of this.renderRowsReturned() + // if we want results panel to be proper height because the + // FilterTable component needs an explicit height to render + // react-virtualized Table component + const rowsHeight = alertIsOpen + ? height - alertContainerHeight + : height - rowMessageHeight; + let data; + if (cache && query.cached) { + data = cachedData; + } else if (results?.data) { + ({ data } = results); } - if (query.state === 'success' && query.ctas) { - const { tempSchema, tempTable } = query; - let object = 'Table'; - if (query.ctas_method === CtasEnum.VIEW) { - object = 'View'; - } + if (data && data.length > 0) { + const expandedColumns = results.expanded_columns + ? results.expanded_columns.map(col => col.name) + : []; return ( -
- - {t(object)} [ - - {tempSchema ? `${tempSchema}.` : ''} - {tempTable} - - ] {t('was created')}   - - - - - - } + <> + {renderControls()} + {renderRowsReturned()} + {sql} + col.name)} + height={rowsHeight} + filterText={searchText} + expandedColumns={expandedColumns} /> -
+ ); } - if (query.state === 'success' && query.results) { - const { results } = query; - // Accounts for offset needed for height of ResultSetRowsReturned component if !limitReached - const rowMessageHeight = !limitReached ? 32 : 0; - // Accounts for offset needed for height of Alert if this.state.alertIsOpen - const alertContainerHeight = 70; - // We need to calculate the height of this.renderRowsReturned() - // if we want results panel to be propper height because the - // FilterTable component nedds an explcit height to render - // react-virtualized Table component - const height = this.state.alertIsOpen - ? this.props.height - alertContainerHeight - : this.props.height - rowMessageHeight; - let data; - if (this.props.cache && query.cached) { - ({ data } = this.state); - } else if (results && results.data) { - ({ data } = results); - } - if (data && data.length > 0) { - const expandedColumns = results.expanded_columns - ? results.expanded_columns.map(col => col.name) - : []; - return ( - <> - {this.renderControls()} - {this.renderRowsReturned()} - {sql} - col.name)} - height={height} - filterText={this.state.searchText} - expandedColumns={expandedColumns} - /> - - ); - } - if (data && data.length === 0) { - return ( - - ); - } + if (data && data.length === 0) { + return ; } - if (query.cached || (query.state === 'success' && !query.results)) { - if (query.isDataPreview) { - return ( - - ); - } - if (query.resultsKey) { - return ( - - ); - } + }), + ) + } + > + {t('Fetch data preview')} + + ); } - let progressBar; - if (query.progress > 0) { - progressBar = ( - + if (query.resultsKey) { + return ( + ); } - const progressMsg = - query && query.extra && query.extra.progress - ? query.extra.progress - : null; + } - return ( - -
{!progressBar && }
- {/* show loading bar whenever progress bar is completed but needs time to render */} -
{query.progress === 100 && }
- -
- {progressMsg && } -
-
{query.progress !== 100 && progressBar}
- {trackingUrl &&
{trackingUrl}
} -
+ let progressBar; + if (query.progress > 0) { + progressBar = ( + ); } -} + + const progressMsg = query?.extra?.progress ?? null; + + return ( + +
{!progressBar && }
+ {/* show loading bar whenever progress bar is completed but needs time to render */} +
{query.progress === 100 && }
+ +
{progressMsg && }
+
{query.progress !== 100 && progressBar}
+ {trackingUrl &&
{trackingUrl}
} +
+ ); +}; + +export default ResultSet; diff --git a/superset-frontend/src/SqlLab/components/RunQueryActionButton/RunQueryActionButton.test.tsx b/superset-frontend/src/SqlLab/components/RunQueryActionButton/RunQueryActionButton.test.tsx index 780298968995d..189a5fd2f7ab9 100644 --- a/superset-frontend/src/SqlLab/components/RunQueryActionButton/RunQueryActionButton.test.tsx +++ b/superset-frontend/src/SqlLab/components/RunQueryActionButton/RunQueryActionButton.test.tsx @@ -30,7 +30,7 @@ import RunQueryActionButton, { const middlewares = [thunk]; const mockStore = configureStore(middlewares); -jest.mock('src/components/Select', () => () => ( +jest.mock('src/components/DeprecatedSelect', () => () => (
)); jest.mock('src/components/Select/Select', () => () => ( diff --git a/superset-frontend/src/SqlLab/components/SaveDatasetActionButton/SaveDatasetActionButton.test.tsx b/superset-frontend/src/SqlLab/components/SaveDatasetActionButton/SaveDatasetActionButton.test.tsx index d6e0d7aa6caa2..316404e3e4f3a 100644 --- a/superset-frontend/src/SqlLab/components/SaveDatasetActionButton/SaveDatasetActionButton.test.tsx +++ b/superset-frontend/src/SqlLab/components/SaveDatasetActionButton/SaveDatasetActionButton.test.tsx @@ -29,7 +29,7 @@ const overlayMenu = ( ); describe('SaveDatasetActionButton', () => { - it('renders a split save button', () => { + test('renders a split save button', async () => { render( true} @@ -40,11 +40,14 @@ describe('SaveDatasetActionButton', () => { const saveBtn = screen.getByRole('button', { name: /save/i }); const caretBtn = screen.getByRole('button', { name: /caret-down/i }); + expect( + await screen.findByRole('button', { name: /save/i }), + ).toBeInTheDocument(); expect(saveBtn).toBeVisible(); expect(caretBtn).toBeVisible(); }); - it('renders a "save dataset" dropdown menu item when user clicks caret button', () => { + test('renders a "save dataset" dropdown menu item when user clicks caret button', async () => { render( true} @@ -53,6 +56,9 @@ describe('SaveDatasetActionButton', () => { ); const caretBtn = screen.getByRole('button', { name: /caret-down/i }); + expect( + await screen.findByRole('button', { name: /caret-down/i }), + ).toBeInTheDocument(); userEvent.click(caretBtn); const saveDatasetMenuItem = screen.getByText(/save dataset/i); diff --git a/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx b/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx index 6d6acf8af9787..31facf4401c0e 100644 --- a/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx +++ b/superset-frontend/src/SqlLab/components/SaveDatasetModal/index.tsx @@ -304,13 +304,12 @@ export const SaveDatasetModal: FunctionComponent = ({ [URL_PARAMS.formDataKey.name]: key, }); createWindow(url); + setDatasetName(getDefaultDatasetName()); + onHide(); }) .catch(() => { addDangerToast(t('An error occurred saving dataset')); }); - - setDatasetName(getDefaultDatasetName()); - onHide(); }; const handleOverwriteDatasetOption = (value: SelectValue, option: any) => { diff --git a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/ShareSqlLabQuery.test.jsx b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/ShareSqlLabQuery.test.jsx index 8d87334e0fdb0..22913894fbc39 100644 --- a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/ShareSqlLabQuery.test.jsx +++ b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/ShareSqlLabQuery.test.jsx @@ -28,9 +28,10 @@ import '@testing-library/jest-dom/extend-expect'; import userEvent from '@testing-library/user-event'; import * as utils from 'src/utils/common'; import ShareSqlLabQuery from 'src/SqlLab/components/ShareSqlLabQuery'; +import { initialState } from 'src/SqlLab/fixtures'; const mockStore = configureStore([thunk]); -const store = mockStore({}); +const store = mockStore(initialState); let isFeatureEnabledMock; const standardProvider = ({ children }) => ( @@ -41,6 +42,7 @@ const standardProvider = ({ children }) => ( const defaultProps = { queryEditor: { + id: 'qe1', dbId: 0, name: 'query title', schema: 'query_schema', @@ -51,6 +53,31 @@ const defaultProps = { addDangerToast: jest.fn(), }; +const unsavedQueryEditor = { + id: defaultProps.queryEditor.id, + dbId: 9888, + name: 'query title changed', + schema: 'query_schema_updated', + sql: 'SELECT * FROM Updated Limit 100', + autorun: true, +}; + +const standardProviderWithUnsaved = ({ children }) => ( + + + {children} + + +); + describe('ShareSqlLabQuery', () => { const storeQueryUrl = 'glob:*/kv/store/'; const storeQueryMockId = '123'; @@ -83,9 +110,26 @@ describe('ShareSqlLabQuery', () => { }); }); const button = screen.getByRole('button'); + const { id, remoteId, ...expected } = defaultProps.queryEditor; + const storeQuerySpy = jest.spyOn(utils, 'storeQuery'); + userEvent.click(button); + expect(storeQuerySpy.mock.calls).toHaveLength(1); + expect(storeQuerySpy).toBeCalledWith(expected); + storeQuerySpy.mockRestore(); + }); + + it('calls storeQuery() with unsaved changes', async () => { + await act(async () => { + render(, { + wrapper: standardProviderWithUnsaved, + }); + }); + const button = screen.getByRole('button'); + const { id, ...expected } = unsavedQueryEditor; const storeQuerySpy = jest.spyOn(utils, 'storeQuery'); userEvent.click(button); expect(storeQuerySpy.mock.calls).toHaveLength(1); + expect(storeQuerySpy).toBeCalledWith(expected); storeQuerySpy.mockRestore(); }); }); diff --git a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx index c521cb5dc75d9..37481bdee9249 100644 --- a/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx +++ b/superset-frontend/src/SqlLab/components/ShareSqlLabQuery/index.tsx @@ -17,6 +17,7 @@ * under the License. */ import React from 'react'; +import { shallowEqual, useSelector } from 'react-redux'; import { t, useTheme, styled } from '@superset-ui/core'; import Button from 'src/components/Button'; import Icons from 'src/components/Icons'; @@ -25,7 +26,7 @@ import CopyToClipboard from 'src/components/CopyToClipboard'; import { storeQuery } from 'src/utils/common'; import { getClientErrorObject } from 'src/utils/getClientErrorObject'; import { FeatureFlag, isFeatureEnabled } from 'src/featureFlags'; -import { QueryEditor } from 'src/SqlLab/types'; +import { QueryEditor, SqlLabRootState } from 'src/SqlLab/types'; interface ShareSqlLabQueryPropTypes { queryEditor: QueryEditor; @@ -48,8 +49,18 @@ function ShareSqlLabQuery({ }: ShareSqlLabQueryPropTypes) { const theme = useTheme(); + const { dbId, name, schema, autorun, sql, remoteId } = useSelector< + SqlLabRootState, + Partial + >(({ sqlLab: { unsavedQueryEditor } }) => { + const { dbId, name, schema, autorun, sql, remoteId } = { + ...queryEditor, + ...(unsavedQueryEditor.id === queryEditor.id && unsavedQueryEditor), + }; + return { dbId, name, schema, autorun, sql, remoteId }; + }, shallowEqual); + const getCopyUrlForKvStore = (callback: Function) => { - const { dbId, name, schema, autorun, sql } = queryEditor; const sharedQuery = { dbId, name, schema, autorun, sql }; return storeQuery(sharedQuery) @@ -66,10 +77,10 @@ function ShareSqlLabQuery({ const getCopyUrlForSavedQuery = (callback: Function) => { let savedQueryToastContent; - if (queryEditor.remoteId) { + if (remoteId) { savedQueryToastContent = `${ window.location.origin + window.location.pathname - }?savedQueryId=${queryEditor.remoteId}`; + }?savedQueryId=${remoteId}`; callback(savedQueryToastContent); } else { savedQueryToastContent = t('Please save the query to enable sharing'); @@ -101,8 +112,7 @@ function ShareSqlLabQuery({ }; const canShare = - !!queryEditor.remoteId || - isFeatureEnabled(FeatureFlag.SHARE_QUERIES_VIA_KV_STORE); + !!remoteId || isFeatureEnabled(FeatureFlag.SHARE_QUERIES_VIA_KV_STORE); return ( <> diff --git a/superset-frontend/src/SqlLab/components/SouthPane/index.tsx b/superset-frontend/src/SqlLab/components/SouthPane/index.tsx index ddcd972f9828b..2be88f6fe2189 100644 --- a/superset-frontend/src/SqlLab/components/SouthPane/index.tsx +++ b/superset-frontend/src/SqlLab/components/SouthPane/index.tsx @@ -164,10 +164,8 @@ export default function SouthPane({ if (Date.now() - latestQuery.startDttm <= LOCALSTORAGE_MAX_QUERY_AGE_MS) { results = ( { const buildWrapper = (props = {}) => mount( - - - , + + + + + , { wrappingComponent: ThemeProvider, wrappingComponentProps: { theme: supersetTheme }, diff --git a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx index d813b3b52d158..c48594d304841 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx +++ b/superset-frontend/src/SqlLab/components/SqlEditor/index.jsx @@ -31,6 +31,7 @@ import StyledModal from 'src/components/Modal'; import Mousetrap from 'mousetrap'; import Button from 'src/components/Button'; import Timer from 'src/components/Timer'; +import ResizableSidebar from 'src/components/ResizableSidebar'; import { AntdDropdown, AntdSwitch } from 'src/components'; import { Input } from 'src/components/Input'; import { Menu } from 'src/components/Menu'; @@ -60,6 +61,7 @@ import { SQL_EDITOR_GUTTER_HEIGHT, SQL_EDITOR_GUTTER_MARGIN, SQL_TOOLBAR_HEIGHT, + SQL_EDITOR_LEFTBAR_WIDTH, } from 'src/SqlLab/constants'; import { getItem, @@ -127,6 +129,15 @@ const StyledToolbar = styled.div` } `; +const StyledSidebar = styled.div` + flex: 0 0 ${({ width }) => width}px; + width: ${({ width }) => width}px; + padding: ${({ hide }) => (hide ? 0 : 10)}px; + border-right: 1px solid + ${({ theme, hide }) => + hide ? 'transparent' : theme.colors.grayscale.light2}; +`; + const propTypes = { actions: PropTypes.object.isRequired, database: PropTypes.object, @@ -674,7 +685,6 @@ class SqlEditor extends React.PureComponent { this.state.createAs === CtasEnum.VIEW ? 'Specify name to CREATE VIEW AS schema in: public' : 'Specify name to CREATE TABLE AS schema in: public'; - const leftBarStateClass = this.props.hideLeftBar ? 'schemaPane-exit-done' : 'schemaPane-enter-done'; @@ -685,15 +695,28 @@ class SqlEditor extends React.PureComponent { in={!this.props.hideLeftBar} timeout={300} > -
- -
+ + {adjustedWidth => ( + + + + )} + {this.state.showEmptyState ? ( { - let wrapper; +fetchMock.get('glob:*/superset/tables/**', { + json: { + options: [ + { + label: 'ab_user', + value: 'ab_user', + }, + ], + tableLength: 1, + }, +}); - beforeEach(() => { - wrapper = shallow(, { - context: { store }, - }); +describe('Left Panel Expansion', () => { + test('is valid', () => { + expect( + React.isValidElement( + + + , + ), + ).toBe(true); }); - afterEach(() => { - wrapper.unmount(); - }); + test('renders a TableElement', async () => { + render(, { + useRedux: true, + initialState, + }); - it('is valid', () => { - expect(React.isValidElement()).toBe( - true, - ); + expect( + await screen.findByText(/select database or type database name/i), + ).toBeInTheDocument(); + expect( + screen.queryAllByTestId('table-element').length, + ).toBeGreaterThanOrEqual(1); }); - it('renders a TableElement', () => { - expect(wrapper.find(TableElement)).toExist(); - }); -}); + test('table should be visible when expanded is true', async () => { + const { container } = render(, { + useRedux: true, + initialState, + }); -describe('Left Panel Expansion', () => { - it('table should be visible when expanded is true', () => { - const { container } = render( - - - - - , - ); const dbSelect = screen.getByRole('combobox', { name: 'Select database or type database name', }); @@ -88,6 +96,10 @@ describe('Left Panel Expansion', () => { }); const dropdown = screen.getByText(/Select table or type table name/i); const abUser = screen.getByText(/ab_user/i); + + expect( + await screen.findByText(/select database or type database name/i), + ).toBeInTheDocument(); expect(dbSelect).toBeInTheDocument(); expect(schemaSelect).toBeInTheDocument(); expect(dropdown).toBeInTheDocument(); @@ -97,21 +109,23 @@ describe('Left Panel Expansion', () => { ).toBeInTheDocument(); }); - it('should toggle the table when the header is clicked', async () => { + test('should toggle the table when the header is clicked', async () => { const collapseMock = jest.fn(); render( - - - - - , + , + { + useRedux: true, + initialState, + }, ); + + expect(await screen.findByText(/ab_user/)).toBeInTheDocument(); const header = screen.getByText(/ab_user/); userEvent.click(header); expect(collapseMock).toHaveBeenCalled(); diff --git a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx index 97d80a4e88ec3..b3c1bc9f63cdf 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorLeftBar/index.tsx @@ -25,6 +25,7 @@ import React, { Dispatch, SetStateAction, } from 'react'; +import { useSelector } from 'react-redux'; import querystring from 'query-string'; import Button from 'src/components/Button'; import { t, styled, css, SupersetTheme } from '@superset-ui/core'; @@ -32,7 +33,7 @@ import Collapse from 'src/components/Collapse'; import Icons from 'src/components/Icons'; import { TableSelectorMultiple } from 'src/components/TableSelector'; import { IconTooltip } from 'src/components/IconTooltip'; -import { QueryEditor, SchemaOption } from 'src/SqlLab/types'; +import { QueryEditor, SchemaOption, SqlLabRootState } from 'src/SqlLab/types'; import { DatabaseObject } from 'src/components/DatabaseSelector'; import { EmptyStateSmall } from 'src/components/EmptyState'; import { @@ -116,6 +117,15 @@ export default function SqlEditorLeftBar({ const [userSelectedDb, setUserSelected] = useState( null, ); + const schema = useSelector( + ({ sqlLab: { unsavedQueryEditor } }) => { + const updatedQueryEditor = { + ...queryEditor, + ...(unsavedQueryEditor.id === queryEditor.id && unsavedQueryEditor), + }; + return updatedQueryEditor.schema; + }, + ); useEffect(() => { const bool = querystring.parse(window.location.search).db; @@ -263,7 +273,7 @@ export default function SqlEditorLeftBar({ onSchemasLoad={handleSchemasLoad} onTableSelectChange={onTablesChange} onTablesLoad={handleTablesLoad} - schema={queryEditor.schema} + schema={schema} tableValue={selectedTableNames} sqlLabMode /> diff --git a/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/SqlEditorTabHeader.test.tsx b/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/SqlEditorTabHeader.test.tsx index 5216129bdf3c6..6c231401c7053 100644 --- a/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/SqlEditorTabHeader.test.tsx +++ b/superset-frontend/src/SqlLab/components/SqlEditorTabHeader/SqlEditorTabHeader.test.tsx @@ -42,7 +42,7 @@ import { } from 'src/SqlLab/actions/sqlLab'; import SqlEditorTabHeader from 'src/SqlLab/components/SqlEditorTabHeader'; -jest.mock('src/components/Select', () => () => ( +jest.mock('src/components/DeprecatedSelect', () => () => (
)); jest.mock('src/components/Select/Select', () => () => ( diff --git a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.jsx b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.jsx index bc290673be6c2..c007458252495 100644 --- a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.jsx +++ b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/TabbedSqlEditors.test.jsx @@ -31,6 +31,7 @@ import TabbedSqlEditors from 'src/SqlLab/components/TabbedSqlEditors'; import SqlEditor from 'src/SqlLab/components/SqlEditor'; import { table, initialState } from 'src/SqlLab/fixtures'; import { newQueryTabName } from 'src/SqlLab/utils/newQueryTabName'; +import QueryProvider from 'src/views/QueryProvider'; fetchMock.get('glob:*/api/v1/database/*', {}); fetchMock.get('glob:*/savedqueryviewapi/api/get/*', {}); @@ -89,9 +90,11 @@ describe('TabbedSqlEditors', () => { const mountWithAct = async () => act(async () => { mount( - - - , + + + + + , { wrappingComponent: ThemeProvider, wrappingComponentProps: { theme: supersetTheme }, diff --git a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx index c5b94f8b3544a..e25f179716ba5 100644 --- a/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx +++ b/superset-frontend/src/SqlLab/components/TabbedSqlEditors/index.jsx @@ -262,6 +262,9 @@ class TabbedSqlEditors extends React.PureComponent { const qeid = this.props.tabHistory[this.props.tabHistory.length - 1]; if (key !== qeid) { const queryEditor = this.props.queryEditors.find(qe => qe.id === key); + if (!queryEditor) { + return; + } this.props.actions.switchQueryEditor( queryEditor, this.props.displayLimit, @@ -302,7 +305,6 @@ class TabbedSqlEditors extends React.PureComponent { editorQueries={this.state.queriesArray} dataPreviewQueries={this.state.dataPreviewQueries} actions={this.props.actions} - hideLeftBar={qe.hideLeftBar} defaultQueryLimit={this.props.defaultQueryLimit} maxRow={this.props.maxRow} displayLimit={this.props.displayLimit} diff --git a/superset-frontend/src/SqlLab/components/TableElement/index.tsx b/superset-frontend/src/SqlLab/components/TableElement/index.tsx index b05c875e1206f..1ecc7822203fa 100644 --- a/superset-frontend/src/SqlLab/components/TableElement/index.tsx +++ b/superset-frontend/src/SqlLab/components/TableElement/index.tsx @@ -270,6 +270,7 @@ const TableElement = ({ table, actions, ...props }: TableElementProps) => { const metadata = (
setHover(true)} onMouseLeave={() => setHover(false)} css={{ paddingTop: 6 }} diff --git a/superset-frontend/src/SqlLab/components/TemplateParamsEditor/TemplateParamsEditor.test.tsx b/superset-frontend/src/SqlLab/components/TemplateParamsEditor/TemplateParamsEditor.test.tsx index a82b1e5859cac..886edb7afc120 100644 --- a/superset-frontend/src/SqlLab/components/TemplateParamsEditor/TemplateParamsEditor.test.tsx +++ b/superset-frontend/src/SqlLab/components/TemplateParamsEditor/TemplateParamsEditor.test.tsx @@ -33,7 +33,7 @@ import TemplateParamsEditor, { Props, } from 'src/SqlLab/components/TemplateParamsEditor'; -jest.mock('src/components/Select', () => () => ( +jest.mock('src/components/DeprecatedSelect', () => () => (
)); jest.mock('src/components/Select/Select', () => () => ( diff --git a/superset-frontend/src/SqlLab/constants.ts b/superset-frontend/src/SqlLab/constants.ts index 7d0ea09c18264..11d990032d0db 100644 --- a/superset-frontend/src/SqlLab/constants.ts +++ b/superset-frontend/src/SqlLab/constants.ts @@ -48,6 +48,7 @@ export const TIME_OPTIONS = [ export const SQL_EDITOR_GUTTER_HEIGHT = 5; export const SQL_EDITOR_GUTTER_MARGIN = 3; export const SQL_TOOLBAR_HEIGHT = 51; +export const SQL_EDITOR_LEFTBAR_WIDTH = 400; // kilobyte storage export const KB_STORAGE = 1024; diff --git a/superset-frontend/src/SqlLab/main.less b/superset-frontend/src/SqlLab/main.less index 7822b91d3cc86..bec202b7bceba 100644 --- a/superset-frontend/src/SqlLab/main.less +++ b/superset-frontend/src/SqlLab/main.less @@ -283,17 +283,14 @@ div.Workspace { display: flex; flex-direction: row; height: 100%; - padding: 10px; .schemaPane { - flex: 0 0 400px; - max-width: 400px; transition: transform @timing-normal ease-in-out; } .queryPane { flex: 1 1 auto; - padding-left: 10px; + padding: 10px; overflow-y: none; overflow-x: scroll; } @@ -370,8 +367,8 @@ div.tablePopover { border: 1px solid @gray-light; font-feature-settings: @font-feature-settings; // Fira Code causes problem with Ace under Firefox - font-family: 'Menlo', 'Lucida Console', 'Courier New', 'Ubuntu Mono', - 'Consolas', 'source-code-pro', monospace; + font-family: 'Menlo', 'Consolas', 'Courier New', 'Ubuntu Mono', + 'source-code-pro', 'Lucida Console', monospace; &.ace_autocomplete { // Use !important because Ace Editor applies extra CSS at the last second diff --git a/superset-frontend/src/SqlLab/reducers/sqlLab.js b/superset-frontend/src/SqlLab/reducers/sqlLab.js index a12db71a37dde..bab832d6c61f9 100644 --- a/superset-frontend/src/SqlLab/reducers/sqlLab.js +++ b/superset-frontend/src/SqlLab/reducers/sqlLab.js @@ -110,7 +110,12 @@ export default function sqlLabReducer(state = {}, action) { ); }, [actions.REMOVE_QUERY_EDITOR]() { - let newState = removeFromArr(state, 'queryEditors', action.queryEditor); + const queryEditor = { + ...action.queryEditor, + ...(action.queryEditor.id === state.unsavedQueryEditor.id && + state.unsavedQueryEditor), + }; + let newState = removeFromArr(state, 'queryEditors', queryEditor); // List of remaining queryEditor ids const qeIds = newState.queryEditors.map(qe => qe.id); @@ -127,10 +132,19 @@ export default function sqlLabReducer(state = {}, action) { // Remove associated table schemas const tables = state.tables.filter( - table => table.queryEditorId !== action.queryEditor.id, + table => table.queryEditorId !== queryEditor.id, ); - newState = { ...newState, tabHistory, tables, queries }; + newState = { + ...newState, + tabHistory, + tables, + queries, + unsavedQueryEditor: { + ...(action.queryEditor.id !== state.unsavedQueryEditor.id && + state.unsavedQueryEditor), + }, + }; return newState; }, [actions.REMOVE_QUERY]() { diff --git a/superset-frontend/src/SqlLab/reducers/sqlLab.test.js b/superset-frontend/src/SqlLab/reducers/sqlLab.test.js index 82483712f8847..088d01031a598 100644 --- a/superset-frontend/src/SqlLab/reducers/sqlLab.test.js +++ b/superset-frontend/src/SqlLab/reducers/sqlLab.test.js @@ -75,6 +75,28 @@ describe('sqlLabReducer', () => { initialState.queryEditors.length, ); }); + it('should remove a query editor including unsaved changes', () => { + expect(newState.queryEditors).toHaveLength( + initialState.queryEditors.length + 1, + ); + let action = { + type: actions.QUERY_EDITOR_SETDB, + queryEditor: qe, + dbId: 123, + }; + newState = sqlLabReducer(newState, action); + expect(newState.unsavedQueryEditor.dbId).toEqual(action.dbId); + action = { + type: actions.REMOVE_QUERY_EDITOR, + queryEditor: qe, + }; + newState = sqlLabReducer(newState, action); + expect(newState.queryEditors).toHaveLength( + initialState.queryEditors.length, + ); + expect(newState.unsavedQueryEditor.dbId).toBeUndefined(); + expect(newState.unsavedQueryEditor.id).toBeUndefined(); + }); it('should set q query editor active', () => { const expectedTitle = 'new updated title'; const addQueryEditorAction = { diff --git a/superset-frontend/src/SqlLab/utils/newQueryTabName.ts b/superset-frontend/src/SqlLab/utils/newQueryTabName.ts index c068fdd3d5cbb..ac0728339c934 100644 --- a/superset-frontend/src/SqlLab/utils/newQueryTabName.ts +++ b/superset-frontend/src/SqlLab/utils/newQueryTabName.ts @@ -31,7 +31,7 @@ export const newQueryTabName = ( if (queryEditors.length > 0) { const mappedUntitled = queryEditors.filter(qe => - qe.name.match(untitledQueryRegex), + qe.name?.match(untitledQueryRegex), ); const untitledQueryNumbers = mappedUntitled.map( qe => +qe.name.replace(untitledQuery, ''), diff --git a/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.js b/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.js index 3a4288180dbf0..66e33b07c5f4c 100644 --- a/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.js +++ b/superset-frontend/src/SqlLab/utils/reduxStateToLocalStorageHelper.js @@ -32,7 +32,7 @@ const PERSISTENT_QUERY_EDITOR_KEYS = new Set([ 'southPercent', 'sql', 'templateParams', - 'title', + 'name', 'hideLeftBar', ]); diff --git a/superset-frontend/src/components/Alert/Alert.test.tsx b/superset-frontend/src/components/Alert/Alert.test.tsx index 8a59469180d19..9d921d0301479 100644 --- a/superset-frontend/src/components/Alert/Alert.test.tsx +++ b/superset-frontend/src/components/Alert/Alert.test.tsx @@ -64,13 +64,14 @@ test('renders without icon', async () => { }); }); -test('renders message', () => { +test('renders message', async () => { render(); - expect(screen.getByRole('alert')).toHaveTextContent('Message'); + expect(await screen.findByRole('alert')).toHaveTextContent('Message'); }); -test('renders message and description', () => { +test('renders message and description', async () => { render(); - expect(screen.getByRole('alert')).toHaveTextContent('Message'); - expect(screen.getByRole('alert')).toHaveTextContent('Description'); + const alert = await screen.findByRole('alert'); + expect(alert).toHaveTextContent('Message'); + expect(alert).toHaveTextContent('Description'); }); diff --git a/superset-frontend/src/components/AlteredSliceTag/AlteredSliceTag.test.jsx b/superset-frontend/src/components/AlteredSliceTag/AlteredSliceTag.test.jsx index 7501ce6382a4c..87ae7307a84ed 100644 --- a/superset-frontend/src/components/AlteredSliceTag/AlteredSliceTag.test.jsx +++ b/superset-frontend/src/components/AlteredSliceTag/AlteredSliceTag.test.jsx @@ -33,6 +33,8 @@ import { fakePluginControls, } from './AlteredSliceTagMocks'; +jest.mock('src/components/Icons/Icon', () => () => ); + const getTableWrapperFromModalBody = modalBody => modalBody.find(TableView).find(TableCollection); diff --git a/superset-frontend/src/components/AsyncSelect/AsyncSelect.test.jsx b/superset-frontend/src/components/AsyncSelect/AsyncSelect.test.jsx index 0f11fe2709b8f..f3bce12713308 100644 --- a/superset-frontend/src/components/AsyncSelect/AsyncSelect.test.jsx +++ b/superset-frontend/src/components/AsyncSelect/AsyncSelect.test.jsx @@ -19,7 +19,7 @@ import React from 'react'; import { shallow } from 'enzyme'; import fetchMock from 'fetch-mock'; -import Select from 'src/components/Select'; +import Select from 'src/components/DeprecatedSelect'; import AsyncSelect from 'src/components/AsyncSelect'; describe('AsyncSelect', () => { diff --git a/superset-frontend/src/components/AsyncSelect/index.jsx b/superset-frontend/src/components/AsyncSelect/index.jsx index 1f2fb66e53b06..69799eadeae46 100644 --- a/superset-frontend/src/components/AsyncSelect/index.jsx +++ b/superset-frontend/src/components/AsyncSelect/index.jsx @@ -19,7 +19,7 @@ import React from 'react'; import PropTypes from 'prop-types'; // TODO: refactor this with `import { AsyncSelect } from src/components/Select` -import { Select } from 'src/components/Select'; +import { Select } from 'src/components/DeprecatedSelect'; import { t, SupersetClient } from '@superset-ui/core'; import { getClientErrorObject } from '../../utils/getClientErrorObject'; diff --git a/superset-frontend/src/components/CertifiedBadge/CertifiedBadge.test.tsx b/superset-frontend/src/components/CertifiedBadge/CertifiedBadge.test.tsx index dd7ebb8bf9371..faec666f6013b 100644 --- a/superset-frontend/src/components/CertifiedBadge/CertifiedBadge.test.tsx +++ b/superset-frontend/src/components/CertifiedBadge/CertifiedBadge.test.tsx @@ -17,31 +17,36 @@ * under the License. */ import React from 'react'; -import { render, screen } from 'spec/helpers/testing-library'; +import { render, screen, waitFor } from 'spec/helpers/testing-library'; import userEvent from '@testing-library/user-event'; -import CertifiedBadge from 'src/components/CertifiedBadge'; +import CertifiedBadge, { + CertifiedBadgeProps, +} from 'src/components/CertifiedBadge'; -test('renders with default props', () => { - render(); +const asyncRender = (props?: CertifiedBadgeProps) => + waitFor(() => render()); + +test('renders with default props', async () => { + await asyncRender(); expect(screen.getByRole('img')).toBeInTheDocument(); }); test('renders a tooltip when hovered', async () => { - render(); + await asyncRender(); userEvent.hover(screen.getByRole('img')); expect(await screen.findByRole('tooltip')).toBeInTheDocument(); }); test('renders with certified by', async () => { const certifiedBy = 'Trusted Authority'; - render(); + await asyncRender({ certifiedBy }); userEvent.hover(screen.getByRole('img')); expect(await screen.findByRole('tooltip')).toHaveTextContent(certifiedBy); }); test('renders with details', async () => { const details = 'All requirements have been met.'; - render(); + await asyncRender({ details }); userEvent.hover(screen.getByRole('img')); expect(await screen.findByRole('tooltip')).toHaveTextContent(details); }); diff --git a/superset-frontend/src/components/Chart/ChartContextMenu.tsx b/superset-frontend/src/components/Chart/ChartContextMenu.tsx index 612bd455d0018..2eba7bbaad95e 100644 --- a/superset-frontend/src/components/Chart/ChartContextMenu.tsx +++ b/superset-frontend/src/components/Chart/ChartContextMenu.tsx @@ -26,6 +26,7 @@ import React, { import { QueryObjectFilterClause, t, styled } from '@superset-ui/core'; import { Menu } from 'src/components/Menu'; import { AntdDropdown as Dropdown } from 'src/components'; +import ReactDOM from 'react-dom'; const MENU_ITEM_HEIGHT = 32; const MENU_VERTICAL_SPACING = 32; @@ -116,7 +117,7 @@ const ChartContextMenu = ( [open], ); - return ( + return ReactDOM.createPortal( - + , + document.body, ); }; diff --git a/superset-frontend/src/components/Chart/DrillDetailModal.tsx b/superset-frontend/src/components/Chart/DrillDetailModal.tsx index 128359741b1f2..448cc2566f5d3 100644 --- a/superset-frontend/src/components/Chart/DrillDetailModal.tsx +++ b/superset-frontend/src/components/Chart/DrillDetailModal.tsx @@ -91,7 +91,12 @@ const DrillDetailModal: React.FC<{ > {t('Edit chart')} - diff --git a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx index 272249b549600..0b2a7b521f2a3 100644 --- a/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx +++ b/superset-frontend/src/components/DatabaseSelector/DatabaseSelector.test.tsx @@ -31,7 +31,6 @@ const createProps = (): DatabaseSelectorProps => ({ id: 1, database_name: 'test', backend: 'test-postgresql', - allow_multi_schema_metadata_fetch: false, }, formMode: false, isDatabaseSelectEnabled: true, @@ -69,8 +68,6 @@ beforeEach(() => { allow_ctas: 'Allow Ctas', allow_cvas: 'Allow Cvas', allow_dml: 'Allow Dml', - allow_multi_schema_metadata_fetch: - 'Allow Multi Schema Metadata Fetch', allow_run_async: 'Allow Run Async', allows_cost_estimate: 'Allows Cost Estimate', allows_subquery: 'Allows Subquery', @@ -92,7 +89,6 @@ beforeEach(() => { 'allow_ctas', 'allow_cvas', 'allow_dml', - 'allow_multi_schema_metadata_fetch', 'allow_run_async', 'allows_cost_estimate', 'allows_subquery', @@ -126,7 +122,6 @@ beforeEach(() => { allow_ctas: false, allow_cvas: false, allow_dml: false, - allow_multi_schema_metadata_fetch: false, allow_run_async: false, allows_cost_estimate: null, allows_subquery: true, @@ -147,7 +142,6 @@ beforeEach(() => { allow_ctas: false, allow_cvas: false, allow_dml: false, - allow_multi_schema_metadata_fetch: false, allow_run_async: false, allows_cost_estimate: null, allows_subquery: true, @@ -272,7 +266,6 @@ test('Sends the correct db when changing the database', async () => { id: 2, database_name: 'test-mysql', backend: 'mysql', - allow_multi_schema_metadata_fetch: false, }), ), ); diff --git a/superset-frontend/src/components/DatabaseSelector/index.tsx b/superset-frontend/src/components/DatabaseSelector/index.tsx index 1df7f78a3bea9..59109d3b5c0ce 100644 --- a/superset-frontend/src/components/DatabaseSelector/index.tsx +++ b/superset-frontend/src/components/DatabaseSelector/index.tsx @@ -74,14 +74,12 @@ type DatabaseValue = { id: number; database_name: string; backend: string; - allow_multi_schema_metadata_fetch: boolean; }; export type DatabaseObject = { id: number; database_name: string; backend: string; - allow_multi_schema_metadata_fetch: boolean; }; type SchemaValue = { label: string; value: string }; @@ -199,8 +197,6 @@ export default function DatabaseSelector({ id: row.id, database_name: row.database_name, backend: row.backend, - allow_multi_schema_metadata_fetch: - row.allow_multi_schema_metadata_fetch, })); return { diff --git a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx index 260cf5eab73fc..c5a21f65e4cbe 100644 --- a/superset-frontend/src/components/Datasource/DatasourceEditor.jsx +++ b/superset-frontend/src/components/Datasource/DatasourceEditor.jsx @@ -540,10 +540,12 @@ function OwnersSelector({ datasource, onChange }) { return SupersetClient.get({ endpoint: `/api/v1/dataset/related/owners?q=${query}`, }).then(response => ({ - data: response.json.result.map(item => ({ - value: item.value, - label: item.text, - })), + data: response.json.result + .filter(item => item.extra.active) + .map(item => ({ + value: item.value, + label: item.text, + })), totalCount: response.json.count, })); }, []); diff --git a/superset-frontend/src/components/Datasource/DatasourceEditor.test.jsx b/superset-frontend/src/components/Datasource/DatasourceEditor.test.jsx index b6e43d2913592..8d592d2d6eff7 100644 --- a/superset-frontend/src/components/Datasource/DatasourceEditor.test.jsx +++ b/superset-frontend/src/components/Datasource/DatasourceEditor.test.jsx @@ -19,11 +19,19 @@ import React from 'react'; import fetchMock from 'fetch-mock'; import userEvent from '@testing-library/user-event'; -import { render, screen } from 'spec/helpers/testing-library'; +import { render, screen, waitFor } from 'spec/helpers/testing-library'; import DatasourceEditor from 'src/components/Datasource/DatasourceEditor'; import mockDatasource from 'spec/fixtures/mockDatasource'; import * as featureFlags from 'src/featureFlags'; +jest.mock('src/components/Icons/Icon', () => ({ fileName, role, ...rest }) => ( + +)); + const props = { datasource: mockDatasource['7__table'], addSuccessToast: () => {}, @@ -32,24 +40,19 @@ const props = { }; const DATASOURCE_ENDPOINT = 'glob:*/datasource/external_metadata_by_name/*'; +const asyncRender = props => + waitFor(() => render(, { useRedux: true })); + describe('DatasourceEditor', () => { fetchMock.get(DATASOURCE_ENDPOINT, []); - let el; let isFeatureEnabledMock; - beforeEach(() => { - el = ( - - ); - render(el, { useRedux: true }); - }); - - it('is valid', () => { - expect(React.isValidElement(el)).toBe(true); + beforeEach(async () => { + await asyncRender({ + ...props, + datasource: { ...props.datasource, table_name: 'Vehicle Sales +' }, + }); }); it('renders Tabs', () => { @@ -193,11 +196,8 @@ describe('DatasourceEditor', () => { .mockImplementation(() => true); }); - beforeEach(() => { - render(el, { useRedux: true }); - }); - - it('disable edit Source tab', () => { + it('disable edit Source tab', async () => { + await asyncRender(props); expect( screen.queryByRole('img', { name: /lock-locked/i }), ).not.toBeInTheDocument(); @@ -208,7 +208,7 @@ describe('DatasourceEditor', () => { describe('DatasourceEditor RTL', () => { it('properly renders the metric information', async () => { - render(, { useRedux: true }); + await asyncRender(props); const metricButton = screen.getByTestId('collection-tab-Metrics'); userEvent.click(metricButton); const expandToggle = await screen.findAllByLabelText(/toggle expand/i); @@ -221,9 +221,7 @@ describe('DatasourceEditor RTL', () => { expect(warningMarkdown.value).toEqual('someone'); }); it('properly updates the metric information', async () => { - render(, { - useRedux: true, - }); + await asyncRender(props); const metricButton = screen.getByTestId('collection-tab-Metrics'); userEvent.click(metricButton); const expandToggle = await screen.findAllByLabelText(/toggle expand/i); @@ -238,26 +236,22 @@ describe('DatasourceEditor RTL', () => { expect(certificationDetails.value).toEqual('I am typing something new'); }); it('shows the default datetime column', async () => { - render(, { useRedux: true }); + await asyncRender(props); const metricButton = screen.getByTestId('collection-tab-Columns'); userEvent.click(metricButton); - const dsDefaultDatetimeRadio = screen.getByTestId('radio-default-dttm-ds'); expect(dsDefaultDatetimeRadio).toBeChecked(); - const genderDefaultDatetimeRadio = screen.getByTestId( 'radio-default-dttm-gender', ); expect(genderDefaultDatetimeRadio).not.toBeChecked(); }); it('allows choosing only temporal columns as the default datetime', async () => { - render(, { useRedux: true }); + await asyncRender(props); const metricButton = screen.getByTestId('collection-tab-Columns'); userEvent.click(metricButton); - const dsDefaultDatetimeRadio = screen.getByTestId('radio-default-dttm-ds'); expect(dsDefaultDatetimeRadio).toBeEnabled(); - const genderDefaultDatetimeRadio = screen.getByTestId( 'radio-default-dttm-gender', ); diff --git a/superset-frontend/src/components/Datasource/DatasourceModal.test.jsx b/superset-frontend/src/components/Datasource/DatasourceModal.test.jsx index 9743e3a325f36..12be350521515 100644 --- a/superset-frontend/src/components/Datasource/DatasourceModal.test.jsx +++ b/superset-frontend/src/components/Datasource/DatasourceModal.test.jsx @@ -32,6 +32,7 @@ import { DatasourceModal } from 'src/components/Datasource'; import DatasourceEditor from 'src/components/Datasource/DatasourceEditor'; import * as featureFlags from 'src/featureFlags'; import mockDatasource from 'spec/fixtures/mockDatasource'; +import QueryProvider from 'src/views/QueryProvider'; const mockStore = configureStore([thunk]); const store = mockStore({}); @@ -53,9 +54,11 @@ const mockedProps = { async function mountAndWait(props = mockedProps) { const mounted = mount( - - - , + + + + + , { wrappingComponent: ThemeProvider, wrappingComponentProps: { theme: supersetTheme }, diff --git a/superset-frontend/src/components/Select/DeprecatedSelect.stories.tsx b/superset-frontend/src/components/DeprecatedSelect/DeprecatedSelect.stories.tsx similarity index 100% rename from superset-frontend/src/components/Select/DeprecatedSelect.stories.tsx rename to superset-frontend/src/components/DeprecatedSelect/DeprecatedSelect.stories.tsx diff --git a/superset-frontend/src/components/Select/DeprecatedSelect.tsx b/superset-frontend/src/components/DeprecatedSelect/DeprecatedSelect.tsx similarity index 100% rename from superset-frontend/src/components/Select/DeprecatedSelect.tsx rename to superset-frontend/src/components/DeprecatedSelect/DeprecatedSelect.tsx index 690f6379aaeed..95a2556ff13da 100644 --- a/superset-frontend/src/components/Select/DeprecatedSelect.tsx +++ b/superset-frontend/src/components/DeprecatedSelect/DeprecatedSelect.tsx @@ -40,6 +40,7 @@ import { } from 'react-sortable-hoc'; import arrayMove from 'array-move'; import { useTheme } from '@superset-ui/core'; +import { findValue } from './utils'; import { WindowedSelectComponentType, WindowedSelectProps, @@ -59,7 +60,6 @@ import { InputProps, defaultTheme, } from './styles'; -import { findValue } from './utils'; type AnyReactSelect = | BasicSelect diff --git a/superset-frontend/src/components/Select/NativeSelect.tsx b/superset-frontend/src/components/DeprecatedSelect/NativeSelect.tsx similarity index 100% rename from superset-frontend/src/components/Select/NativeSelect.tsx rename to superset-frontend/src/components/DeprecatedSelect/NativeSelect.tsx diff --git a/superset-frontend/src/components/Select/OnPasteSelect.jsx b/superset-frontend/src/components/DeprecatedSelect/OnPasteSelect.jsx similarity index 98% rename from superset-frontend/src/components/Select/OnPasteSelect.jsx rename to superset-frontend/src/components/DeprecatedSelect/OnPasteSelect.jsx index e178a0a4c0672..bffa5428a60df 100644 --- a/superset-frontend/src/components/Select/OnPasteSelect.jsx +++ b/superset-frontend/src/components/DeprecatedSelect/OnPasteSelect.jsx @@ -18,7 +18,7 @@ */ import React from 'react'; import PropTypes from 'prop-types'; -import { Select } from 'src/components/Select'; +import { Select } from 'src/components/DeprecatedSelect'; export default class OnPasteSelect extends React.Component { constructor(props) { diff --git a/superset-frontend/src/components/Select/OnPasteSelect.test.jsx b/superset-frontend/src/components/DeprecatedSelect/OnPasteSelect.test.jsx similarity index 98% rename from superset-frontend/src/components/Select/OnPasteSelect.test.jsx rename to superset-frontend/src/components/DeprecatedSelect/OnPasteSelect.test.jsx index 9c6af28d7a109..3aa1641b28a74 100644 --- a/superset-frontend/src/components/Select/OnPasteSelect.test.jsx +++ b/superset-frontend/src/components/DeprecatedSelect/OnPasteSelect.test.jsx @@ -20,7 +20,11 @@ import React from 'react'; import sinon from 'sinon'; import { shallow } from 'enzyme'; -import { Select, OnPasteSelect, CreatableSelect } from 'src/components/Select'; +import { + Select, + OnPasteSelect, + CreatableSelect, +} from 'src/components/DeprecatedSelect'; const defaultProps = { onChange: sinon.spy(), diff --git a/superset-frontend/src/components/Select/WindowedSelect/WindowedMenuList.tsx b/superset-frontend/src/components/DeprecatedSelect/WindowedSelect/WindowedMenuList.tsx similarity index 98% rename from superset-frontend/src/components/Select/WindowedSelect/WindowedMenuList.tsx rename to superset-frontend/src/components/DeprecatedSelect/WindowedSelect/WindowedMenuList.tsx index 8eb94b55d3927..f29466b33988c 100644 --- a/superset-frontend/src/components/Select/WindowedSelect/WindowedMenuList.tsx +++ b/superset-frontend/src/components/DeprecatedSelect/WindowedSelect/WindowedMenuList.tsx @@ -55,7 +55,7 @@ export type WindowedMenuListProps = { * grouped options just yet. */ -type MenuListPropsChildren = +type MenuListPropsChildren = | Component>[] | ReactElement[]; diff --git a/superset-frontend/src/components/Select/WindowedSelect/index.tsx b/superset-frontend/src/components/DeprecatedSelect/WindowedSelect/index.tsx similarity index 100% rename from superset-frontend/src/components/Select/WindowedSelect/index.tsx rename to superset-frontend/src/components/DeprecatedSelect/WindowedSelect/index.tsx diff --git a/superset-frontend/src/components/Select/WindowedSelect/windowed.tsx b/superset-frontend/src/components/DeprecatedSelect/WindowedSelect/windowed.tsx similarity index 100% rename from superset-frontend/src/components/Select/WindowedSelect/windowed.tsx rename to superset-frontend/src/components/DeprecatedSelect/WindowedSelect/windowed.tsx diff --git a/superset-frontend/src/components/Select/index.ts b/superset-frontend/src/components/DeprecatedSelect/index.ts similarity index 100% rename from superset-frontend/src/components/Select/index.ts rename to superset-frontend/src/components/DeprecatedSelect/index.ts diff --git a/superset-frontend/src/components/Select/styles.tsx b/superset-frontend/src/components/DeprecatedSelect/styles.tsx similarity index 100% rename from superset-frontend/src/components/Select/styles.tsx rename to superset-frontend/src/components/DeprecatedSelect/styles.tsx diff --git a/superset-frontend/src/components/Select/utils.ts b/superset-frontend/src/components/DeprecatedSelect/utils.ts similarity index 66% rename from superset-frontend/src/components/Select/utils.ts rename to superset-frontend/src/components/DeprecatedSelect/utils.ts index 9836b9ddd2eaa..497791b62d75a 100644 --- a/superset-frontend/src/components/Select/utils.ts +++ b/superset-frontend/src/components/DeprecatedSelect/utils.ts @@ -16,23 +16,12 @@ * specific language governing permissions and limitations * under the License. */ -import { ReactNode } from 'react'; -import { ensureIsArray } from '@superset-ui/core'; import { OptionTypeBase, ValueType, OptionsType, GroupedOptionsType, } from 'react-select'; -import { LabeledValue as AntdLabeledValue } from 'antd/lib/select'; - -export function isObject(value: unknown): value is Record { - return ( - value !== null && - typeof value === 'object' && - Array.isArray(value) === false - ); -} /** * Find Option value that matches a possibly string value. @@ -68,32 +57,3 @@ export function findValue( // empty: https://github.com/JedWatson/react-select/blob/32ad5c040bdd96cd1ca71010c2558842d684629c/packages/react-select/src/utils.js#L64 return (Array.isArray(value) ? value : [value]).map(find); } - -export function isLabeledValue(value: unknown): value is AntdLabeledValue { - return isObject(value) && 'value' in value && 'label' in value; -} - -export function getValue( - option: string | number | AntdLabeledValue | null | undefined, -) { - return isLabeledValue(option) ? option.value : option; -} - -type LabeledValue = { label?: ReactNode; value?: V }; - -export function hasOption( - value: V, - options?: V | LabeledValue | (V | LabeledValue)[], - checkLabel = false, -): boolean { - const optionsArray = ensureIsArray(options); - return ( - optionsArray.find( - x => - x === value || - (isObject(x) && - (('value' in x && x.value === value) || - (checkLabel && 'label' in x && x.label === value))), - ) !== undefined - ); -} diff --git a/superset-frontend/src/components/ErrorBoundary/ErrorBoundary.test.tsx b/superset-frontend/src/components/ErrorBoundary/ErrorBoundary.test.tsx index 75ddc1c6fa9dd..0ab1e44a802e6 100644 --- a/superset-frontend/src/components/ErrorBoundary/ErrorBoundary.test.tsx +++ b/superset-frontend/src/components/ErrorBoundary/ErrorBoundary.test.tsx @@ -20,6 +20,8 @@ import React from 'react'; import { render, screen } from 'spec/helpers/testing-library'; import ErrorBoundary from '.'; +jest.mock('src/components/Icons/Icon', () => () => ); + const mockedProps = { children: Error children, onError: () => null, diff --git a/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.test.tsx b/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.test.tsx index b6be9a1b9f8cf..dd2187c9315e1 100644 --- a/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/BasicErrorAlert.test.tsx @@ -23,6 +23,13 @@ import { supersetTheme } from '@superset-ui/core'; import BasicErrorAlert from './BasicErrorAlert'; import { ErrorLevel } from './types'; +jest.mock( + 'src/components/Icons/Icon', + () => + ({ fileName }: { fileName: string }) => + , +); + const mockedProps = { body: 'Error body', level: 'warning' as ErrorLevel, diff --git a/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.test.tsx b/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.test.tsx index 6959c5351a7ee..c8dbe8ba345f0 100644 --- a/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/DatabaseErrorMessage.test.tsx @@ -23,6 +23,13 @@ import userEvent from '@testing-library/user-event'; import DatabaseErrorMessage from './DatabaseErrorMessage'; import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types'; +jest.mock( + 'src/components/Icons/Icon', + () => + ({ fileName }: { fileName: string }) => + , +); + const mockedProps = { error: { error_type: ErrorTypeEnum.DATABASE_SECURITY_ACCESS_ERROR, diff --git a/superset-frontend/src/components/ErrorMessage/DatasetNotFoundErrorMessage.test.tsx b/superset-frontend/src/components/ErrorMessage/DatasetNotFoundErrorMessage.test.tsx index 087b08fa8f865..e73d2eb93b6a9 100644 --- a/superset-frontend/src/components/ErrorMessage/DatasetNotFoundErrorMessage.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/DatasetNotFoundErrorMessage.test.tsx @@ -22,6 +22,13 @@ import { render, screen } from 'spec/helpers/testing-library'; import DatasetNotFoundErrorMessage from './DatasetNotFoundErrorMessage'; import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types'; +jest.mock( + 'src/components/Icons/Icon', + () => + ({ fileName }: { fileName: string }) => + , +); + const mockedProps = { error: { error_type: ErrorTypeEnum.FAILED_FETCHING_DATASOURCE_INFO_ERROR, diff --git a/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx b/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx index d9fdf5efc8f71..38006c2ec409e 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorAlert.test.tsx @@ -24,6 +24,13 @@ import { supersetTheme } from '@superset-ui/core'; import ErrorAlert from './ErrorAlert'; import { ErrorLevel, ErrorSource } from './types'; +jest.mock( + 'src/components/Icons/Icon', + () => + ({ fileName }: { fileName: string }) => + , +); + const mockedProps = { body: 'Error body', level: 'warning' as ErrorLevel, diff --git a/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.test.tsx b/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.test.tsx index 6e343eba7ff20..ae30e5cb991bd 100644 --- a/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/ErrorMessageWithStackTrace.test.tsx @@ -23,6 +23,13 @@ import userEvent from '@testing-library/user-event'; import ErrorMessageWithStackTrace from './ErrorMessageWithStackTrace'; import { ErrorLevel, ErrorSource } from './types'; +jest.mock( + 'src/components/Icons/Icon', + () => + ({ fileName }: { fileName: string }) => + , +); + const mockedProps = { level: 'warning' as ErrorLevel, link: 'https://sample.com', diff --git a/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx b/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx index 17f38c4d23c57..e8be71b7e3079 100644 --- a/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/ParameterErrorMessage.test.tsx @@ -23,6 +23,13 @@ import { render, screen } from 'spec/helpers/testing-library'; import ParameterErrorMessage from './ParameterErrorMessage'; import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types'; +jest.mock( + 'src/components/Icons/Icon', + () => + ({ fileName }: { fileName: string }) => + , +); + const mockedProps = { error: { error_type: ErrorTypeEnum.MISSING_TEMPLATE_PARAMS_ERROR, diff --git a/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.test.tsx b/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.test.tsx index e41308f5381b5..cc3a8a9a599e4 100644 --- a/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.test.tsx +++ b/superset-frontend/src/components/ErrorMessage/TimeoutErrorMessage.test.tsx @@ -23,6 +23,13 @@ import { render, screen } from 'spec/helpers/testing-library'; import TimeoutErrorMessage from './TimeoutErrorMessage'; import { ErrorLevel, ErrorSource, ErrorTypeEnum } from './types'; +jest.mock( + 'src/components/Icons/Icon', + () => + ({ fileName }: { fileName: string }) => + , +); + const mockedProps = { error: { error_type: ErrorTypeEnum.FRONTEND_TIMEOUT_ERROR, diff --git a/superset-frontend/src/components/FaveStar/FaveStar.test.tsx b/superset-frontend/src/components/FaveStar/FaveStar.test.tsx index 68433db96ea4e..ab2fa9fa0ed1e 100644 --- a/superset-frontend/src/components/FaveStar/FaveStar.test.tsx +++ b/superset-frontend/src/components/FaveStar/FaveStar.test.tsx @@ -26,13 +26,13 @@ jest.mock('src/components/Tooltip', () => ({ Tooltip: (props: any) =>
, })); -test('render right content', () => { +test('render right content', async () => { const props = { itemId: 3, saveFaveStar: jest.fn(), }; - const { rerender } = render(); + const { rerender, findByRole } = render(); expect(screen.getByRole('button')).toBeInTheDocument(); expect( screen.getByRole('img', { name: 'favorite-selected' }), @@ -45,7 +45,7 @@ test('render right content', () => { rerender(); expect( - screen.getByRole('img', { name: 'favorite-unselected' }), + await findByRole('img', { name: 'favorite-unselected' }), ).toBeInTheDocument(); expect(props.saveFaveStar).toBeCalledTimes(1); @@ -54,7 +54,7 @@ test('render right content', () => { expect(props.saveFaveStar).toBeCalledWith(props.itemId, false); }); -test('render content on tooltip', () => { +test('render content on tooltip', async () => { const props = { itemId: 3, showTooltip: true, @@ -63,7 +63,7 @@ test('render content on tooltip', () => { render(); - expect(screen.getByTestId('tooltip')).toBeInTheDocument(); + expect(await screen.findByTestId('tooltip')).toBeInTheDocument(); expect(screen.getByTestId('tooltip')).toHaveAttribute( 'id', 'fave-unfave-tooltip', @@ -75,7 +75,7 @@ test('render content on tooltip', () => { expect(screen.getByRole('button')).toBeInTheDocument(); }); -test('Call fetchFaveStar only on the first render', () => { +test('Call fetchFaveStar only on the first render', async () => { const props = { itemId: 3, fetchFaveStar: jest.fn(), @@ -84,7 +84,10 @@ test('Call fetchFaveStar only on the first render', () => { showTooltip: false, }; - const { rerender } = render(); + const { rerender, findByRole } = render(); + expect( + await findByRole('img', { name: 'favorite-unselected' }), + ).toBeInTheDocument(); expect(props.fetchFaveStar).toBeCalledTimes(1); expect(props.fetchFaveStar).toBeCalledWith(props.itemId); diff --git a/superset-frontend/src/components/FilterableTable/index.tsx b/superset-frontend/src/components/FilterableTable/index.tsx index 2a56bd7dca164..16ae37e671e76 100644 --- a/superset-frontend/src/components/FilterableTable/index.tsx +++ b/superset-frontend/src/components/FilterableTable/index.tsx @@ -563,6 +563,7 @@ const FilterableTable = ({ }; const renderGrid = () => { + totalTableHeight.current = height; if ( container.current && totalTableWidth.current > container.current.clientWidth @@ -577,7 +578,7 @@ const FilterableTable = ({ // fix height of filterable table return ( - + {({ onScroll, scrollLeft }) => ( <> @@ -643,6 +644,7 @@ const FilterableTable = ({ ); } + totalTableHeight.current = height; if ( container.current && totalTableWidth.current > container.current.clientWidth @@ -657,6 +659,7 @@ const FilterableTable = ({ return ( {fitted && ( diff --git a/superset-frontend/src/components/Icons/index.tsx b/superset-frontend/src/components/Icons/index.tsx index 0761890e4c05b..29ccb1dd36480 100644 --- a/superset-frontend/src/components/Icons/index.tsx +++ b/superset-frontend/src/components/Icons/index.tsx @@ -166,7 +166,7 @@ const IconFileNames = [ 'redo', ]; -const iconOverrides: Record = {}; +const iconOverrides: Record> = {}; IconFileNames.forEach(fileName => { const keyName = _.startCase(fileName).replace(/ /g, ''); iconOverrides[keyName] = (props: IconType) => ( diff --git a/superset-frontend/src/components/IndeterminateCheckbox/IndeterminateCheckbox.test.tsx b/superset-frontend/src/components/IndeterminateCheckbox/IndeterminateCheckbox.test.tsx index 7bced98f8eb81..08fdbe2fa4dd2 100644 --- a/superset-frontend/src/components/IndeterminateCheckbox/IndeterminateCheckbox.test.tsx +++ b/superset-frontend/src/components/IndeterminateCheckbox/IndeterminateCheckbox.test.tsx @@ -18,11 +18,11 @@ */ import React from 'react'; -import { render, screen } from 'spec/helpers/testing-library'; +import { render, screen, waitFor } from 'spec/helpers/testing-library'; import userEvent from '@testing-library/user-event'; -import IndeterminateCheckbox from '.'; +import IndeterminateCheckbox, { IndeterminateCheckboxProps } from '.'; -const mockedProps = { +const mockedProps: IndeterminateCheckboxProps = { checked: false, id: 'checkbox-id', indeterminate: false, @@ -30,27 +30,30 @@ const mockedProps = { onChange: jest.fn(), }; -test('should render', () => { - const { container } = render(); +const asyncRender = (props = mockedProps) => + waitFor(() => render()); + +test('should render', async () => { + const { container } = await asyncRender(); expect(container).toBeInTheDocument(); }); -test('should render the label', () => { - render(); +test('should render the label', async () => { + await asyncRender(); expect(screen.getByTitle('Checkbox title')).toBeInTheDocument(); }); -test('should render the checkbox', () => { - render(); +test('should render the checkbox', async () => { + await asyncRender(); expect(screen.getByRole('checkbox')).toBeInTheDocument(); }); -test('should render the checkbox-half icon', () => { +test('should render the checkbox-half icon', async () => { const indeterminateProps = { ...mockedProps, indeterminate: true, }; - render(); + await asyncRender(indeterminateProps); expect(screen.getByRole('img')).toBeInTheDocument(); expect(screen.getByRole('img')).toHaveAttribute( 'aria-label', @@ -58,24 +61,24 @@ test('should render the checkbox-half icon', () => { ); }); -test('should render the checkbox-off icon', () => { - render(); +test('should render the checkbox-off icon', async () => { + await asyncRender(); expect(screen.getByRole('img')).toBeInTheDocument(); expect(screen.getByRole('img')).toHaveAttribute('aria-label', 'checkbox-off'); }); -test('should render the checkbox-on icon', () => { +test('should render the checkbox-on icon', async () => { const checkboxOnProps = { ...mockedProps, checked: true, }; - render(); + await asyncRender(checkboxOnProps); expect(screen.getByRole('img')).toBeInTheDocument(); expect(screen.getByRole('img')).toHaveAttribute('aria-label', 'checkbox-on'); }); -test('should call the onChange', () => { - render(); +test('should call the onChange', async () => { + await asyncRender(); const label = screen.getByTitle('Checkbox title'); userEvent.click(label); expect(mockedProps.onChange).toHaveBeenCalledTimes(1); diff --git a/superset-frontend/src/components/LastUpdated/LastUpdated.test.tsx b/superset-frontend/src/components/LastUpdated/LastUpdated.test.tsx index 8306959180571..1df3f60ee4766 100644 --- a/superset-frontend/src/components/LastUpdated/LastUpdated.test.tsx +++ b/superset-frontend/src/components/LastUpdated/LastUpdated.test.tsx @@ -20,6 +20,7 @@ import React from 'react'; import { ReactWrapper } from 'enzyme'; import { styledMount as mount } from 'spec/helpers/theming'; +import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint'; import LastUpdated from '.'; describe('LastUpdated', () => { @@ -31,9 +32,10 @@ describe('LastUpdated', () => { expect(/^Last Updated .+$/.test(wrapper.text())).toBe(true); }); - it('renders a refresh action', () => { + it('renders a refresh action', async () => { const mockAction = jest.fn(); wrapper = mount(); + await waitForComponentToPaint(wrapper); const props = wrapper.find('[aria-label="refresh"]').first().props(); if (props.onClick) { props.onClick({} as React.MouseEvent); diff --git a/superset-frontend/src/components/ListView/ListView.test.jsx b/superset-frontend/src/components/ListView/ListView.test.jsx index 25ab1b63aea6b..5faaa6d3c3440 100644 --- a/superset-frontend/src/components/ListView/ListView.test.jsx +++ b/superset-frontend/src/components/ListView/ListView.test.jsx @@ -35,6 +35,8 @@ import Pagination from 'src/components/Pagination/Wrapper'; import waitForComponentToPaint from 'spec/helpers/waitForComponentToPaint'; +jest.mock('src/components/Icons/Icon', () => () => ); + function makeMockLocation(query) { const queryStr = encodeURIComponent(query); return { diff --git a/superset-frontend/src/components/ListView/types.ts b/superset-frontend/src/components/ListView/types.ts index f8bff90f0ee95..a0f495385e490 100644 --- a/superset-frontend/src/components/ListView/types.ts +++ b/superset-frontend/src/components/ListView/types.ts @@ -114,4 +114,6 @@ export enum FilterOperator { chartIsCertified = 'chart_is_certified', dashboardIsCertified = 'dashboard_is_certified', datasetIsCertified = 'dataset_is_certified', + dashboardHasCreatedBy = 'dashboard_has_created_by', + chartHasCreatedBy = 'chart_has_created_by', } diff --git a/superset-frontend/src/components/ListView/utils.ts b/superset-frontend/src/components/ListView/utils.ts index 78873f51f14a0..10b7035f2c218 100644 --- a/superset-frontend/src/components/ListView/utils.ts +++ b/superset-frontend/src/components/ListView/utils.ts @@ -35,7 +35,7 @@ import { import rison from 'rison'; import { isEqual } from 'lodash'; -import { PartialStylesConfig } from 'src/components/Select'; +import { PartialStylesConfig } from 'src/components/DeprecatedSelect'; import { FetchDataConfig, Filter, diff --git a/superset-frontend/src/components/MessageToasts/Toast.test.jsx b/superset-frontend/src/components/MessageToasts/Toast.test.jsx index f0280c4851e73..2254ffb00a9d1 100644 --- a/superset-frontend/src/components/MessageToasts/Toast.test.jsx +++ b/superset-frontend/src/components/MessageToasts/Toast.test.jsx @@ -23,6 +23,8 @@ import Toast from 'src/components/MessageToasts/Toast'; import { act } from 'react-dom/test-utils'; import mockMessageToasts from './mockMessageToasts'; +jest.mock('src/components/Icons/Icon', () => () => ); + const props = { toast: mockMessageToasts[0], onCloseToast() {}, diff --git a/superset-frontend/src/components/MetadataBar/ContentConfig.tsx b/superset-frontend/src/components/MetadataBar/ContentConfig.tsx index 23d1dec3d18d6..55dbf82166b91 100644 --- a/superset-frontend/src/components/MetadataBar/ContentConfig.tsx +++ b/superset-frontend/src/components/MetadataBar/ContentConfig.tsx @@ -20,7 +20,7 @@ import React from 'react'; import moment from 'moment'; import { ensureIsArray, styled, t } from '@superset-ui/core'; import Icons from 'src/components/Icons'; -import { ContentType, MetadataType } from './ContentType'; +import { ContentType, MetadataType } from '.'; const Header = styled.div` font-weight: ${({ theme }) => theme.typography.weights.bold}; diff --git a/superset-frontend/src/components/MetadataBar/MetadataBar.stories.tsx b/superset-frontend/src/components/MetadataBar/MetadataBar.stories.tsx index 5ad9d97851733..6501a1b64c54e 100644 --- a/superset-frontend/src/components/MetadataBar/MetadataBar.stories.tsx +++ b/superset-frontend/src/components/MetadataBar/MetadataBar.stories.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { css } from '@superset-ui/core'; import { useResizeDetector } from 'react-resize-detector'; -import MetadataBar, { MetadataBarProps } from './index'; +import MetadataBar, { MetadataBarProps, MetadataType } from '.'; export default { title: 'MetadataBar', @@ -72,26 +72,26 @@ Component.story = { Component.args = { items: [ { - type: 'sql', + type: MetadataType.SQL, title: 'Click to view query', }, { - type: 'owner', + type: MetadataType.OWNER, createdBy: 'Jane Smith', owners: ['John Doe', 'Mary Wilson'], createdOn: A_WEEK_AGO, }, { - type: 'lastModified', + type: MetadataType.LAST_MODIFIED, value: A_WEEK_AGO, modifiedBy: 'Jane Smith', }, { - type: 'tags', + type: MetadataType.TAGS, values: ['management', 'research', 'poc'], }, { - type: 'dashboards', + type: MetadataType.DASHBOARDS, title: 'Added to 452 dashboards', description: 'To preview the list of dashboards go to "More" settings on the right.', diff --git a/superset-frontend/src/components/MetadataBar/MetadataBar.test.tsx b/superset-frontend/src/components/MetadataBar/MetadataBar.test.tsx index c8a3f9e54603f..d045709482c02 100644 --- a/superset-frontend/src/components/MetadataBar/MetadataBar.test.tsx +++ b/superset-frontend/src/components/MetadataBar/MetadataBar.test.tsx @@ -23,8 +23,12 @@ import * as resizeDetector from 'react-resize-detector'; import moment from 'moment'; import { supersetTheme } from '@superset-ui/core'; import { hexToRgb } from 'src/utils/colorUtils'; -import MetadataBar, { MIN_NUMBER_ITEMS, MAX_NUMBER_ITEMS } from '.'; -import { ContentType, MetadataType } from './ContentType'; +import MetadataBar, { + MIN_NUMBER_ITEMS, + MAX_NUMBER_ITEMS, + ContentType, + MetadataType, +} from '.'; const DASHBOARD_TITLE = 'Added to 452 dashboards'; const DASHBOARD_DESCRIPTION = @@ -166,8 +170,8 @@ test('renders the items sorted', () => { const { container } = render(); const nodes = container.firstChild?.childNodes as NodeListOf; expect(within(nodes[0]).getByText(DASHBOARD_TITLE)).toBeInTheDocument(); - expect(within(nodes[1]).getByText(ROWS_TITLE)).toBeInTheDocument(); - expect(within(nodes[2]).getByText(SQL_TITLE)).toBeInTheDocument(); + expect(within(nodes[1]).getByText(SQL_TITLE)).toBeInTheDocument(); + expect(within(nodes[2]).getByText(ROWS_TITLE)).toBeInTheDocument(); expect(within(nodes[3]).getByText(DESCRIPTION_VALUE)).toBeInTheDocument(); expect(within(nodes[4]).getByText(CREATED_BY)).toBeInTheDocument(); }); @@ -217,7 +221,7 @@ test('correctly renders the owner tooltip', async () => { test('correctly renders the rows tooltip', async () => { await runWithBarCollapsed(async () => { render(); - userEvent.hover(screen.getAllByRole('img')[0]); + userEvent.hover(screen.getAllByRole('img')[2]); const tooltip = await screen.findByRole('tooltip'); expect(tooltip).toBeInTheDocument(); expect(within(tooltip).getByText(ROWS_TITLE)).toBeInTheDocument(); @@ -237,7 +241,7 @@ test('correctly renders the sql tooltip', async () => { test('correctly renders the table tooltip', async () => { await runWithBarCollapsed(async () => { render(); - userEvent.hover(screen.getAllByRole('img')[2]); + userEvent.hover(screen.getAllByRole('img')[0]); const tooltip = await screen.findByRole('tooltip'); expect(tooltip).toBeInTheDocument(); expect(within(tooltip).getByText(TABLE_TITLE)).toBeInTheDocument(); diff --git a/superset-frontend/src/components/MetadataBar/MetadataBar.tsx b/superset-frontend/src/components/MetadataBar/MetadataBar.tsx new file mode 100644 index 0000000000000..5217cbcc99214 --- /dev/null +++ b/superset-frontend/src/components/MetadataBar/MetadataBar.tsx @@ -0,0 +1,190 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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, { useEffect, useRef, useState } from 'react'; +import { useResizeDetector } from 'react-resize-detector'; +import { uniqWith } from 'lodash'; +import { styled } from '@superset-ui/core'; +import { Tooltip } from 'src/components/Tooltip'; +import { ContentType } from './ContentType'; +import { config } from './ContentConfig'; + +export const MIN_NUMBER_ITEMS = 2; +export const MAX_NUMBER_ITEMS = 6; + +const HORIZONTAL_PADDING = 12; +const VERTICAL_PADDING = 8; +const ICON_PADDING = 8; +const SPACE_BETWEEN_ITEMS = 16; +const ICON_WIDTH = 16; +const TEXT_MIN_WIDTH = 70; +const TEXT_MAX_WIDTH = 150; +const ORDER = { + dashboards: 0, + table: 1, + sql: 2, + rows: 3, + tags: 4, + description: 5, + owner: 6, + lastModified: 7, +}; + +const Bar = styled.div<{ count: number }>` + ${({ theme, count }) => ` + display: flex; + align-items: center; + padding: ${VERTICAL_PADDING}px ${HORIZONTAL_PADDING}px; + background-color: ${theme.colors.grayscale.light4}; + color: ${theme.colors.grayscale.base}; + font-size: ${theme.typography.sizes.s}px; + min-width: ${ + HORIZONTAL_PADDING * 2 + + (ICON_WIDTH + SPACE_BETWEEN_ITEMS) * count - + SPACE_BETWEEN_ITEMS + }px; + `} +`; + +const StyledItem = styled.div<{ + collapsed: boolean; + last: boolean; + onClick?: () => void; +}>` + ${({ theme, collapsed, last, onClick }) => ` + max-width: ${ + ICON_WIDTH + + ICON_PADDING + + TEXT_MAX_WIDTH + + (last ? 0 : SPACE_BETWEEN_ITEMS) + }px; + min-width: ${ICON_WIDTH + (last ? 0 : SPACE_BETWEEN_ITEMS)}px; + overflow: hidden; + text-overflow: ${collapsed ? 'unset' : 'ellipsis'}; + white-space: nowrap; + padding-right: ${last ? 0 : SPACE_BETWEEN_ITEMS}px; + text-decoration: ${onClick ? 'underline' : 'none'}; + cursor: ${onClick ? 'pointer' : 'default'}; + & > span { + color: ${onClick && collapsed ? theme.colors.primary.base : 'undefined'}; + padding-right: ${collapsed ? 0 : ICON_PADDING}px; + } + `} +`; + +// Make sure big tootips are truncated +const TootipContent = styled.div` + display: -webkit-box; + -webkit-line-clamp: 20; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; +`; + +const Item = ({ + barWidth, + contentType, + collapsed, + last = false, +}: { + barWidth: number | undefined; + contentType: ContentType; + collapsed: boolean; + last?: boolean; +}) => { + const { icon, title, tooltip = title } = config(contentType); + const [isTruncated, setIsTruncated] = useState(false); + const ref = useRef(null); + const Icon = icon; + const { type, onClick } = contentType; + + useEffect(() => { + setIsTruncated( + ref.current ? ref.current.scrollWidth > ref.current.clientWidth : false, + ); + }, [barWidth, setIsTruncated, contentType]); + + const content = ( + onClick(type) : undefined} + ref={ref} + > + + {!collapsed && title} + + ); + return isTruncated || collapsed || (tooltip && tooltip !== title) ? ( + {tooltip}}> + {content} + + ) : ( + content + ); +}; + +export interface MetadataBarProps { + /** + * Array of content type configurations. To see the available properties + * for each content type, check {@link ContentType} + */ + items: ContentType[]; +} + +/** + * The metadata bar component is used to display additional information about an entity. + * Content types are predefined and consistent across the whole app. This means that + * they will be displayed and behave in a consistent manner, keeping the same ordering, + * information formatting, and interactions. + * To extend the list of content types, a developer needs to request the inclusion of the new type in the design system. + * This process is important to make sure the new type is reviewed by the design team, improving Superset consistency. + */ +const MetadataBar = ({ items }: MetadataBarProps) => { + const { width, ref } = useResizeDetector(); + const uniqueItems = uniqWith(items, (a, b) => a.type === b.type); + const sortedItems = uniqueItems.sort((a, b) => ORDER[a.type] - ORDER[b.type]); + const count = sortedItems.length; + if (count < MIN_NUMBER_ITEMS) { + throw Error('The minimum number of items for the metadata bar is 2.'); + } + if (count > MAX_NUMBER_ITEMS) { + throw Error('The maximum number of items for the metadata bar is 6.'); + } + // Calculates the breakpoint width to collapse the bar. + // The last item does not have a space, so we subtract SPACE_BETWEEN_ITEMS from the total. + const breakpoint = + (ICON_WIDTH + ICON_PADDING + TEXT_MIN_WIDTH + SPACE_BETWEEN_ITEMS) * count - + SPACE_BETWEEN_ITEMS; + const collapsed = Boolean(width && width < breakpoint); + return ( + + {sortedItems.map((item, index) => ( + + ))} + + ); +}; + +export default MetadataBar; diff --git a/superset-frontend/src/components/MetadataBar/index.tsx b/superset-frontend/src/components/MetadataBar/index.tsx index c9f57395e72ac..e4398a3063b26 100644 --- a/superset-frontend/src/components/MetadataBar/index.tsx +++ b/superset-frontend/src/components/MetadataBar/index.tsx @@ -16,175 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useEffect, useRef, useState } from 'react'; -import { useResizeDetector } from 'react-resize-detector'; -import { uniqWith } from 'lodash'; -import { styled } from '@superset-ui/core'; -import { Tooltip } from 'src/components/Tooltip'; -import { ContentType } from './ContentType'; -import { config } from './ContentConfig'; +import MetadataBar, { + MetadataBarProps, + MIN_NUMBER_ITEMS, + MAX_NUMBER_ITEMS, +} from './MetadataBar'; -export const MIN_NUMBER_ITEMS = 2; -export const MAX_NUMBER_ITEMS = 6; - -const HORIZONTAL_PADDING = 12; -const VERTICAL_PADDING = 8; -const ICON_PADDING = 8; -const SPACE_BETWEEN_ITEMS = 16; -const ICON_WIDTH = 16; -const TEXT_MIN_WIDTH = 70; -const TEXT_MAX_WIDTH = 150; -const ORDER = { - dashboards: 0, - rows: 1, - sql: 2, - table: 3, - tags: 4, - description: 5, - owner: 6, - lastModified: 7, -}; - -const Bar = styled.div<{ count: number }>` - ${({ theme, count }) => ` - display: flex; - align-items: center; - padding: ${VERTICAL_PADDING}px ${HORIZONTAL_PADDING}px; - background-color: ${theme.colors.grayscale.light4}; - color: ${theme.colors.grayscale.base}; - font-size: ${theme.typography.sizes.s}px; - min-width: ${ - HORIZONTAL_PADDING * 2 + - (ICON_WIDTH + SPACE_BETWEEN_ITEMS) * count - - SPACE_BETWEEN_ITEMS - }px; - `} -`; - -const StyledItem = styled.div<{ - collapsed: boolean; - last: boolean; - onClick?: () => void; -}>` - ${({ theme, collapsed, last, onClick }) => ` - max-width: ${ - ICON_WIDTH + - ICON_PADDING + - TEXT_MAX_WIDTH + - (last ? 0 : SPACE_BETWEEN_ITEMS) - }px; - min-width: ${ICON_WIDTH + (last ? 0 : SPACE_BETWEEN_ITEMS)}px; - overflow: hidden; - text-overflow: ${collapsed ? 'unset' : 'ellipsis'}; - white-space: nowrap; - padding-right: ${last ? 0 : SPACE_BETWEEN_ITEMS}px; - text-decoration: ${onClick ? 'underline' : 'none'}; - cursor: ${onClick ? 'pointer' : 'default'}; - & > span { - color: ${onClick && collapsed ? theme.colors.primary.base : 'undefined'}; - padding-right: ${collapsed ? 0 : ICON_PADDING}px; - } - `} -`; - -// Make sure big tootips are truncated -const TootipContent = styled.div` - display: -webkit-box; - -webkit-line-clamp: 20; - -webkit-box-orient: vertical; - overflow: hidden; - text-overflow: ellipsis; -`; - -const Item = ({ - barWidth, - contentType, - collapsed, - last = false, -}: { - barWidth: number | undefined; - contentType: ContentType; - collapsed: boolean; - last?: boolean; -}) => { - const { icon, title, tooltip = title } = config(contentType); - const [isTruncated, setIsTruncated] = useState(false); - const ref = useRef(null); - const Icon = icon; - const { type, onClick } = contentType; - - useEffect(() => { - setIsTruncated( - ref.current ? ref.current.scrollWidth > ref.current.clientWidth : false, - ); - }, [barWidth, setIsTruncated, contentType]); - - const content = ( - onClick(type) : undefined} - ref={ref} - > - - {!collapsed && title} - - ); - return isTruncated || collapsed || (tooltip && tooltip !== title) ? ( - {tooltip}}> - {content} - - ) : ( - content - ); -}; - -export interface MetadataBarProps { - /** - * Array of content type configurations. To see the available properties - * for each content type, check {@link ContentType} - */ - items: ContentType[]; -} +export default MetadataBar; -/** - * The metadata bar component is used to display additional information about an entity. - * Content types are predefined and consistent across the whole app. This means that - * they will be displayed and behave in a consistent manner, keeping the same ordering, - * information formatting, and interactions. - * To extend the list of content types, a developer needs to request the inclusion of the new type in the design system. - * This process is important to make sure the new type is reviewed by the design team, improving Superset consistency. - */ -const MetadataBar = ({ items }: MetadataBarProps) => { - const { width, ref } = useResizeDetector(); - const uniqueItems = uniqWith(items, (a, b) => a.type === b.type); - const sortedItems = uniqueItems.sort((a, b) => ORDER[a.type] - ORDER[b.type]); - const count = sortedItems.length; - if (count < MIN_NUMBER_ITEMS) { - throw Error('The minimum number of items for the metadata bar is 2.'); - } - if (count > MAX_NUMBER_ITEMS) { - throw Error('The maximum number of items for the metadata bar is 6.'); - } - // Calculates the breakpoint width to collapse the bar. - // The last item does not have a space, so we subtract SPACE_BETWEEN_ITEMS from the total. - const breakpoint = - (ICON_WIDTH + ICON_PADDING + TEXT_MIN_WIDTH + SPACE_BETWEEN_ITEMS) * count - - SPACE_BETWEEN_ITEMS; - const collapsed = Boolean(width && width < breakpoint); - return ( - - {sortedItems.map((item, index) => ( - - ))} - - ); -}; +export { MetadataBarProps, MIN_NUMBER_ITEMS, MAX_NUMBER_ITEMS }; -export default MetadataBar; +export * from './ContentType'; diff --git a/superset-frontend/src/components/Modal/Modal.tsx b/superset-frontend/src/components/Modal/Modal.tsx index c12e6f6642026..2ccd8dcf5dabd 100644 --- a/superset-frontend/src/components/Modal/Modal.tsx +++ b/superset-frontend/src/components/Modal/Modal.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React, { useRef, useState } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import { isNil } from 'lodash'; import { styled, t } from '@superset-ui/core'; import { css } from '@emotion/react'; @@ -201,6 +201,24 @@ export const StyledModal = styled(BaseModal)` } `} `; +const defaultResizableConfig = (hideFooter: boolean | undefined) => ({ + maxHeight: RESIZABLE_MAX_HEIGHT, + maxWidth: RESIZABLE_MAX_WIDTH, + minHeight: hideFooter + ? RESIZABLE_MIN_HEIGHT + : RESIZABLE_MIN_HEIGHT + MODAL_FOOTER_HEIGHT, + minWidth: RESIZABLE_MIN_WIDTH, + enable: { + bottom: true, + bottomLeft: false, + bottomRight: true, + left: false, + top: false, + topLeft: false, + topRight: false, + right: true, + }, +}); const CustomModal = ({ children, @@ -222,24 +240,7 @@ const CustomModal = ({ wrapProps, draggable = false, resizable = false, - resizableConfig = { - maxHeight: RESIZABLE_MAX_HEIGHT, - maxWidth: RESIZABLE_MAX_WIDTH, - minHeight: hideFooter - ? RESIZABLE_MIN_HEIGHT - : RESIZABLE_MIN_HEIGHT + MODAL_FOOTER_HEIGHT, - minWidth: RESIZABLE_MIN_WIDTH, - enable: { - bottom: true, - bottomLeft: false, - bottomRight: true, - left: false, - top: false, - topLeft: false, - topRight: false, - right: true, - }, - }, + resizableConfig = defaultResizableConfig(hideFooter), draggableConfig, destroyOnClose, ...rest @@ -289,6 +290,13 @@ const CustomModal = ({ } }; + const getResizableConfig = useMemo(() => { + if (Object.keys(resizableConfig).length === 0) { + return defaultResizableConfig(hideFooter); + } + return resizableConfig; + }, [hideFooter, resizableConfig]); + const ModalTitle = () => draggable ? (
{resizable ? ( - +
{modal}
diff --git a/superset-frontend/src/components/PageHeaderWithActions/PageHeaderWithActions.test.tsx b/superset-frontend/src/components/PageHeaderWithActions/PageHeaderWithActions.test.tsx index 65158f3ac811e..a042fcc63d8a5 100644 --- a/superset-frontend/src/components/PageHeaderWithActions/PageHeaderWithActions.test.tsx +++ b/superset-frontend/src/components/PageHeaderWithActions/PageHeaderWithActions.test.tsx @@ -23,6 +23,8 @@ import userEvent from '@testing-library/user-event'; import { PageHeaderWithActions, PageHeaderWithActionsProps } from './index'; import { Menu } from '../Menu'; +jest.mock('src/components/Icons/Icon', () => () => ); + const defaultProps: PageHeaderWithActionsProps = { editableTitleProps: { title: 'Test title', diff --git a/superset-frontend/src/components/PageHeaderWithActions/index.tsx b/superset-frontend/src/components/PageHeaderWithActions/index.tsx index e85ccdfc82580..9209ab818d44f 100644 --- a/superset-frontend/src/components/PageHeaderWithActions/index.tsx +++ b/superset-frontend/src/components/PageHeaderWithActions/index.tsx @@ -19,6 +19,7 @@ import React, { ReactNode, ReactElement } from 'react'; import { css, SupersetTheme, t, useTheme } from '@superset-ui/core'; import { AntdDropdown, AntdDropdownProps } from 'src/components'; +import { TooltipPlacement } from 'src/components/Tooltip'; import { DynamicEditableTitle, DynamicEditableTitleProps, @@ -112,6 +113,10 @@ export type PageHeaderWithActionsProps = { rightPanelAdditionalItems: ReactNode; additionalActionsMenu: ReactElement; menuDropdownProps: Omit; + tooltipProps?: { + text?: string; + placement?: TooltipPlacement; + }; }; export const PageHeaderWithActions = ({ @@ -124,6 +129,7 @@ export const PageHeaderWithActions = ({ rightPanelAdditionalItems, additionalActionsMenu, menuDropdownProps, + tooltipProps, }: PageHeaderWithActionsProps) => { const theme = useTheme(); return ( @@ -152,6 +158,9 @@ export const PageHeaderWithActions = ({ css={menuTriggerStyles} buttonStyle="tertiary" aria-label={t('Menu actions trigger')} + tooltip={tooltipProps?.text} + placement={tooltipProps?.placement} + data-test="actions-trigger" > { +test('renders with default props', async () => { render(); - expect(screen.getByRole('button')).toBeInTheDocument(); + expect(await screen.findByRole('button')).toBeInTheDocument(); expect(screen.getByRole('button')).toHaveTextContent('Option 1'); }); -test('renders the menu on click', () => { +test('renders the menu on click', async () => { render(); userEvent.click(screen.getByRole('button')); - expect(screen.getByRole('menu')).toBeInTheDocument(); + expect(await screen.findByRole('menu')).toBeInTheDocument(); }); -test('renders with custom button', () => { +test('renders with custom button', async () => { render( { )} />, ); - expect(screen.getByText('Custom Option 1')).toBeInTheDocument(); + expect(await screen.findByText('Custom Option 1')).toBeInTheDocument(); }); -test('renders with custom option', () => { +test('renders with custom option', async () => { render( { />, ); userEvent.click(screen.getByRole('button')); - expect(screen.getByText('Custom Option 1')).toBeInTheDocument(); + expect(await screen.findByText('Custom Option 1')).toBeInTheDocument(); }); -test('triggers onChange', () => { +test('triggers onChange', async () => { render(); userEvent.click(screen.getByRole('button')); - expect(screen.getByText('Option 2')).toBeInTheDocument(); + expect(await screen.findByText('Option 2')).toBeInTheDocument(); userEvent.click(screen.getByText('Option 2')); expect(defaultProps.onChange).toHaveBeenCalled(); }); diff --git a/superset-frontend/src/components/PopoverSection/PopoverSection.test.tsx b/superset-frontend/src/components/PopoverSection/PopoverSection.test.tsx index 2135f6ba46ef3..16952834c16ce 100644 --- a/superset-frontend/src/components/PopoverSection/PopoverSection.test.tsx +++ b/superset-frontend/src/components/PopoverSection/PopoverSection.test.tsx @@ -21,23 +21,23 @@ import { render, screen } from 'spec/helpers/testing-library'; import userEvent from '@testing-library/user-event'; import PopoverSection from 'src/components/PopoverSection'; -test('renders with default props', () => { +test('renders with default props', async () => { render(
, ); - expect(screen.getByRole('form')).toBeInTheDocument(); - expect(screen.getAllByRole('img').length).toBe(1); + expect(await screen.findByRole('form')).toBeInTheDocument(); + expect((await screen.findAllByRole('img')).length).toBe(1); }); -test('renders tooltip icon', () => { +test('renders tooltip icon', async () => { render(
, ); - expect(screen.getAllByRole('img').length).toBe(2); + expect((await screen.findAllByRole('img')).length).toBe(2); }); test('renders a tooltip when hovered', async () => { @@ -50,13 +50,13 @@ test('renders a tooltip when hovered', async () => { expect(await screen.findByRole('tooltip')).toBeInTheDocument(); }); -test('calls onSelect when clicked', () => { +test('calls onSelect when clicked', async () => { const onSelect = jest.fn(); render(
, ); - userEvent.click(screen.getByRole('img')); + userEvent.click(await screen.findByRole('img')); expect(onSelect).toHaveBeenCalled(); }); diff --git a/superset-frontend/src/components/ProgressBar/index.tsx b/superset-frontend/src/components/ProgressBar/index.tsx index 93b4315e523d7..ba69fc90c6cab 100644 --- a/superset-frontend/src/components/ProgressBar/index.tsx +++ b/superset-frontend/src/components/ProgressBar/index.tsx @@ -27,7 +27,7 @@ export interface ProgressBarProps extends ProgressProps { // eslint-disable-next-line @typescript-eslint/no-unused-vars const ProgressBar = styled(({ striped, ...props }: ProgressBarProps) => ( - + ))` line-height: 0; position: static; diff --git a/superset-frontend/src/components/RefreshLabel/RefreshLabel.test.tsx b/superset-frontend/src/components/RefreshLabel/RefreshLabel.test.tsx index 55750fba828e2..a8d5d7e3c2e17 100644 --- a/superset-frontend/src/components/RefreshLabel/RefreshLabel.test.tsx +++ b/superset-frontend/src/components/RefreshLabel/RefreshLabel.test.tsx @@ -21,9 +21,9 @@ import { render, screen } from 'spec/helpers/testing-library'; import userEvent from '@testing-library/user-event'; import RefreshLabel from 'src/components/RefreshLabel'; -test('renders with default props', () => { +test('renders with default props', async () => { render(); - const refresh = screen.getByRole('button'); + const refresh = await screen.findByRole('button'); expect(refresh).toBeInTheDocument(); userEvent.hover(refresh); }); @@ -38,10 +38,10 @@ test('renders tooltip on hover', async () => { expect(tooltip).toHaveTextContent(tooltipText); }); -test('triggers on click event', () => { +test('triggers on click event', async () => { const onClick = jest.fn(); render(); - const refresh = screen.getByRole('button'); + const refresh = await screen.findByRole('button'); userEvent.click(refresh); expect(onClick).toHaveBeenCalled(); }); diff --git a/superset-frontend/src/components/ReportModal/HeaderReportDropdown/index.test.tsx b/superset-frontend/src/components/ReportModal/HeaderReportDropdown/index.test.tsx index 61b948c96fe5d..d978bd2bb8cec 100644 --- a/superset-frontend/src/components/ReportModal/HeaderReportDropdown/index.test.tsx +++ b/superset-frontend/src/components/ReportModal/HeaderReportDropdown/index.test.tsx @@ -24,6 +24,8 @@ import HeaderReportDropdown, { HeaderReportProps } from '.'; let isFeatureEnabledMock: jest.MockInstance; +jest.mock('src/components/Icons/Icon', () => () => ); + const createProps = () => ({ dashboardId: 1, useTextMenu: false, diff --git a/superset-frontend/src/components/ReportModal/ReportModal.test.tsx b/superset-frontend/src/components/ReportModal/ReportModal.test.tsx index 1db3aa0fd6c5e..d33d7d62e1919 100644 --- a/superset-frontend/src/components/ReportModal/ReportModal.test.tsx +++ b/superset-frontend/src/components/ReportModal/ReportModal.test.tsx @@ -20,7 +20,7 @@ import * as React from 'react'; import userEvent from '@testing-library/user-event'; import sinon from 'sinon'; import fetchMock from 'fetch-mock'; -import { render, screen } from 'spec/helpers/testing-library'; +import { render, screen, waitFor } from 'spec/helpers/testing-library'; import * as featureFlags from 'src/featureFlags'; import * as actions from 'src/reports/actions/reports'; import { FeatureFlag } from '@superset-ui/core'; @@ -33,6 +33,8 @@ fetchMock.get(REPORT_ENDPOINT, {}); const NOOP = () => {}; +jest.mock('src/components/Icons/Icon', () => () => ); + const defaultProps = { addDangerToast: NOOP, addSuccessToast: NOOP, @@ -156,7 +158,7 @@ describe('Email Report Modal', () => { // Click "Add" button to create a new email report const addButton = screen.getByRole('button', { name: /add/i }); - userEvent.click(addButton); + await waitFor(() => userEvent.click(addButton)); // Mock addReport from Redux const makeRequest = () => { @@ -164,16 +166,15 @@ describe('Email Report Modal', () => { return request(dispatch); }; - return makeRequest().then(() => { - // 🐞 ----- There are 2 POST calls at this point ----- 🐞 + await makeRequest(); + + // 🐞 ----- There are 2 POST calls at this point ----- 🐞 - // addReport's mocked POST return should match the mocked values - expect(fetchMock.lastOptions()?.body).toEqual(stringyReportValues); - // Dispatch should be called once for addReport - expect(dispatch.callCount).toBe(2); - const reportCalls = fetchMock.calls(REPORT_ENDPOINT); - expect(reportCalls).toHaveLength(2); - }); + // addReport's mocked POST return should match the mocked values + expect(fetchMock.lastOptions()?.body).toEqual(stringyReportValues); + expect(dispatch.callCount).toBe(2); + const reportCalls = fetchMock.calls(REPORT_ENDPOINT); + expect(reportCalls).toHaveLength(2); }); }); }); diff --git a/superset-frontend/src/components/ResizableSidebar/index.tsx b/superset-frontend/src/components/ResizableSidebar/index.tsx new file mode 100644 index 0000000000000..4abe56e526fd3 --- /dev/null +++ b/superset-frontend/src/components/ResizableSidebar/index.tsx @@ -0,0 +1,82 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import React from 'react'; +import { Resizable } from 're-resizable'; +import { styled } from '@superset-ui/core'; +import useStoredSidebarWidth from './useStoredSidebarWidth'; + +const ResizableWrapper = styled.div` + position: absolute; + height: 100%; + + :hover .sidebar-resizer::after { + background-color: ${({ theme }) => theme.colors.primary.base}; + } + + .sidebar-resizer { + // @z-index-above-sticky-header (100) + 1 = 101 + z-index: 101; + } + + .sidebar-resizer::after { + display: block; + content: ''; + width: 1px; + height: 100%; + margin: 0 auto; + } +`; + +type Props = { + id: string; + initialWidth: number; + enable: boolean; + minWidth?: number; + maxWidth?: number; + children: (width: number) => React.ReactNode; +}; + +const ResizableSidebar: React.FC = ({ + id, + initialWidth, + minWidth, + maxWidth, + enable, + children, +}) => { + const [width, setWidth] = useStoredSidebarWidth(id, initialWidth); + + return ( + <> + + setWidth(width + d.width)} + /> + + {children(width)} + + ); +}; + +export default ResizableSidebar; diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/useStoredFilterBarWidth.test.ts b/superset-frontend/src/components/ResizableSidebar/useStoredSidebarWidth.test.ts similarity index 68% rename from superset-frontend/src/dashboard/components/DashboardBuilder/useStoredFilterBarWidth.test.ts rename to superset-frontend/src/components/ResizableSidebar/useStoredSidebarWidth.test.ts index 1a9da6658a720..347cfd8b9ae30 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/useStoredFilterBarWidth.test.ts +++ b/superset-frontend/src/components/ResizableSidebar/useStoredSidebarWidth.test.ts @@ -22,10 +22,11 @@ import { setItem, getItem, } from 'src/utils/localStorageHelpers'; -import { OPEN_FILTER_BAR_WIDTH } from 'src/dashboard/constants'; -import useStoredFilterBarWidth from './useStoredFilterBarWidth'; +import useStoredSidebarWidth from './useStoredSidebarWidth'; -describe('useStoredFilterBarWidth', () => { +const INITIAL_WIDTH = 300; + +describe('useStoredSidebarWidth', () => { beforeEach(() => { localStorage.clear(); }); @@ -34,22 +35,26 @@ describe('useStoredFilterBarWidth', () => { localStorage.clear(); }); - it('returns a default filterBar width by OPEN_FILTER_BAR_WIDTH', () => { - const dashboardId = '123'; - const { result } = renderHook(() => useStoredFilterBarWidth(dashboardId)); + it('returns a default filterBar width by initialWidth', () => { + const id = '123'; + const { result } = renderHook(() => + useStoredSidebarWidth(id, INITIAL_WIDTH), + ); const [actualWidth] = result.current; - expect(actualWidth).toEqual(OPEN_FILTER_BAR_WIDTH); + expect(actualWidth).toEqual(INITIAL_WIDTH); }); it('returns a stored filterBar width from localStorage', () => { - const dashboardId = '123'; + const id = '123'; const expectedWidth = 378; - setItem(LocalStorageKeys.dashboard__custom_filter_bar_widths, { - [dashboardId]: expectedWidth, + setItem(LocalStorageKeys.common__resizable_sidebar_widths, { + [id]: expectedWidth, '456': 250, }); - const { result } = renderHook(() => useStoredFilterBarWidth(dashboardId)); + const { result } = renderHook(() => + useStoredSidebarWidth(id, INITIAL_WIDTH), + ); const [actualWidth] = result.current; expect(actualWidth).toEqual(expectedWidth); @@ -57,15 +62,17 @@ describe('useStoredFilterBarWidth', () => { }); it('returns a setter for filterBar width that stores the state in localStorage together', () => { - const dashboardId = '123'; + const id = '123'; const expectedWidth = 378; const otherDashboardId = '456'; const otherDashboardWidth = 253; - setItem(LocalStorageKeys.dashboard__custom_filter_bar_widths, { - [dashboardId]: 300, + setItem(LocalStorageKeys.common__resizable_sidebar_widths, { + [id]: 300, [otherDashboardId]: otherDashboardWidth, }); - const { result } = renderHook(() => useStoredFilterBarWidth(dashboardId)); + const { result } = renderHook(() => + useStoredSidebarWidth(id, INITIAL_WIDTH), + ); const [prevWidth, setter] = result.current; expect(prevWidth).toEqual(300); @@ -74,10 +81,10 @@ describe('useStoredFilterBarWidth', () => { const updatedWidth = result.current[0]; const widthsMap = getItem( - LocalStorageKeys.dashboard__custom_filter_bar_widths, + LocalStorageKeys.common__resizable_sidebar_widths, {}, ); - expect(widthsMap[dashboardId]).toEqual(expectedWidth); + expect(widthsMap[id]).toEqual(expectedWidth); expect(widthsMap[otherDashboardId]).toEqual(otherDashboardWidth); expect(updatedWidth).toEqual(expectedWidth); expect(updatedWidth).not.toEqual(250); diff --git a/superset-frontend/src/dashboard/components/DashboardBuilder/useStoredFilterBarWidth.ts b/superset-frontend/src/components/ResizableSidebar/useStoredSidebarWidth.ts similarity index 62% rename from superset-frontend/src/dashboard/components/DashboardBuilder/useStoredFilterBarWidth.ts rename to superset-frontend/src/components/ResizableSidebar/useStoredSidebarWidth.ts index 0cbdc74182222..4448d2ec52a3f 100644 --- a/superset-frontend/src/dashboard/components/DashboardBuilder/useStoredFilterBarWidth.ts +++ b/superset-frontend/src/components/ResizableSidebar/useStoredSidebarWidth.ts @@ -22,30 +22,30 @@ import { setItem, getItem, } from 'src/utils/localStorageHelpers'; -import { OPEN_FILTER_BAR_WIDTH } from 'src/dashboard/constants'; -export default function useStoredFilterBarWidth(dashboardId: string) { +export default function useStoredSidebarWidth( + id: string, + initialWidth: number, +) { const widthsMapRef = useRef>(); - const [filterBarWidth, setFilterBarWidth] = useState( - OPEN_FILTER_BAR_WIDTH, - ); + const [sidebarWidth, setSidebarWidth] = useState(initialWidth); useEffect(() => { widthsMapRef.current = widthsMapRef.current ?? - getItem(LocalStorageKeys.dashboard__custom_filter_bar_widths, {}); - if (widthsMapRef.current[dashboardId]) { - setFilterBarWidth(widthsMapRef.current[dashboardId]); + getItem(LocalStorageKeys.common__resizable_sidebar_widths, {}); + if (widthsMapRef.current[id]) { + setSidebarWidth(widthsMapRef.current[id]); } - }, [dashboardId]); + }, [id]); - function setStoredFilterBarWidth(updatedWidth: number) { - setFilterBarWidth(updatedWidth); - setItem(LocalStorageKeys.dashboard__custom_filter_bar_widths, { + function setStoredSidebarWidth(updatedWidth: number) { + setSidebarWidth(updatedWidth); + setItem(LocalStorageKeys.common__resizable_sidebar_widths, { ...widthsMapRef.current, - [dashboardId]: updatedWidth, + [id]: updatedWidth, }); } - return [filterBarWidth, setStoredFilterBarWidth] as const; + return [sidebarWidth, setStoredSidebarWidth] as const; } diff --git a/superset-frontend/src/components/Select/AsyncSelect.test.tsx b/superset-frontend/src/components/Select/AsyncSelect.test.tsx index 8a50002c866f8..b11dcde017c48 100644 --- a/superset-frontend/src/components/Select/AsyncSelect.test.tsx +++ b/superset-frontend/src/components/Select/AsyncSelect.test.tsx @@ -98,6 +98,11 @@ const findSelectOption = (text: string) => within(getElementByClassName('.rc-virtual-list')).getByText(text), ); +const querySelectOption = (text: string) => + waitFor(() => + within(getElementByClassName('.rc-virtual-list')).queryByText(text), + ); + const findAllSelectOptions = () => waitFor(() => getElementsByClassName('.ant-select-item-option-content')); @@ -206,13 +211,7 @@ test('sort the options using a custom sort comparator', async () => { option1: typeof OPTIONS[0], option2: typeof OPTIONS[0], ) => option1.gender.localeCompare(option2.gender); - render( - , - ); + render(); await open(); const options = await findAllSelectOptions(); const optionsPage = OPTIONS.slice(0, defaultProps.pageSize); @@ -294,7 +293,7 @@ test('searches for label or value', async () => { }); test('search order exact and startWith match first', async () => { - render(); + render(); await open(); await type('Her'); expect(await findSelectOption('Guilherme')).toBeInTheDocument(); @@ -333,7 +332,7 @@ test('same case should be ranked to the top', async () => { }); test('ignores special keys when searching', async () => { - render(); + render(); await type('{shift}'); expect(screen.queryByText(LOADING)).not.toBeInTheDocument(); }); @@ -434,7 +433,7 @@ test('clear all the values', async () => { }); test('does not add a new option if allowNewOptions is false', async () => { - render(); + render(); await open(); await type(NEW_OPTION); expect(await screen.findByText(NO_DATA)).toBeInTheDocument(); @@ -469,7 +468,7 @@ test('adds the null option when selected in multiple mode', async () => { }); test('renders the select with default props', () => { - render(); + render(); expect(getSelect()).toBeInTheDocument(); }); @@ -485,7 +484,7 @@ test('opens the select without any data', async () => { }); test('displays the loading indicator when opening', async () => { - render(); + render(); await waitFor(() => { userEvent.click(getSelect()); expect(screen.getByText(LOADING)).toBeInTheDocument(); @@ -494,7 +493,7 @@ test('displays the loading indicator when opening', async () => { }); test('makes a selection in single mode', async () => { - render(); + render(); const optionText = 'Emma'; await open(); userEvent.click(await findSelectOption(optionText)); @@ -502,9 +501,7 @@ test('makes a selection in single mode', async () => { }); test('multiple selections in multiple mode', async () => { - render( - , - ); + render(); await open(); const [firstOption, secondOption] = OPTIONS; userEvent.click(await findSelectOption(firstOption.label)); @@ -516,9 +513,7 @@ test('multiple selections in multiple mode', async () => { test('changes the selected item in single mode', async () => { const onChange = jest.fn(); - render( - , - ); + render(); await open(); const [firstOption, secondOption] = OPTIONS; userEvent.click(await findSelectOption(firstOption.label)); @@ -542,9 +537,7 @@ test('changes the selected item in single mode', async () => { }); test('deselects an item in multiple mode', async () => { - render( - , - ); + render(); await open(); const option3 = OPTIONS[2]; const option8 = OPTIONS[7]; @@ -578,18 +571,14 @@ test('deselects an item in multiple mode', async () => { }); test('adds a new option if none is available and allowNewOptions is true', async () => { - render( - , - ); + render(); await open(); await type(NEW_OPTION); expect(await findSelectOption(NEW_OPTION)).toBeInTheDocument(); }); test('does not add a new option if the option already exists', async () => { - render( - , - ); + render(); const option = OPTIONS[0].label; await open(); await type(option); @@ -602,32 +591,21 @@ test('does not add a new option if the option already exists', async () => { }); test('shows "No data" when allowNewOptions is false and a new option is entered', async () => { - render( - , - ); + render(); await open(); await type(NEW_OPTION); expect(await screen.findByText(NO_DATA)).toBeInTheDocument(); }); test('does not show "No data" when allowNewOptions is true and a new option is entered', async () => { - render( - , - ); + render(); await open(); await type(NEW_OPTION); expect(screen.queryByText(NO_DATA)).not.toBeInTheDocument(); }); test('sets a initial value in single mode', async () => { - render( - , - ); + render(); expect(await findSelectValue()).toHaveTextContent(OPTIONS[0].label); }); @@ -636,7 +614,6 @@ test('sets a initial value in multiple mode', async () => { , ); @@ -646,7 +623,7 @@ test('sets a initial value in multiple mode', async () => { }); test('searches for matches in both loaded and unloaded pages', async () => { - render(); + render(); await open(); await type('and'); @@ -750,6 +727,35 @@ test('fires a new request if all values have not been fetched', async () => { expect(mock).toHaveBeenCalledTimes(2); }); +test('does not render a helper text by default', async () => { + render(); + await open(); + expect(screen.queryByRole('note')).not.toBeInTheDocument(); +}); + +test('renders a helper text when one is provided', async () => { + const helperText = 'Helper text'; + render(); + await open(); + expect(screen.getByRole('note')).toBeInTheDocument(); + expect(screen.queryByText(helperText)).toBeInTheDocument(); +}); + +test('finds an element with a numeric value and does not duplicate the options', async () => { + const options = jest.fn(async () => ({ + data: [ + { label: 'a', value: 11 }, + { label: 'b', value: 12 }, + ], + totalCount: 2, + })); + render(); + await open(); + await type('11'); + expect(await findSelectOption('a')).toBeInTheDocument(); + expect(await querySelectOption('11')).not.toBeInTheDocument(); +}); + /* TODO: Add tests that require scroll interaction. Needs further investigation. - Fetches more data when scrolling and more data is available diff --git a/superset-frontend/src/components/Select/AsyncSelect.tsx b/superset-frontend/src/components/Select/AsyncSelect.tsx index 643981ac19346..e3118f228498d 100644 --- a/superset-frontend/src/components/Select/AsyncSelect.tsx +++ b/superset-frontend/src/components/Select/AsyncSelect.tsx @@ -19,7 +19,6 @@ import React, { forwardRef, ReactElement, - ReactNode, RefObject, UIEvent, useEffect, @@ -30,78 +29,62 @@ import React, { useImperativeHandle, } from 'react'; import { ensureIsArray, styled, t } from '@superset-ui/core'; -import AntdSelect, { - SelectProps as AntdSelectProps, - SelectValue as AntdSelectValue, - LabeledValue as AntdLabeledValue, -} from 'antd/lib/select'; -import { DownOutlined, SearchOutlined } from '@ant-design/icons'; -import { Spin } from 'antd'; +import { LabeledValue as AntdLabeledValue } from 'antd/lib/select'; import debounce from 'lodash/debounce'; import { isEqual } from 'lodash'; import Icons from 'src/components/Icons'; import { getClientErrorObject } from 'src/utils/getClientErrorObject'; import { SLOW_DEBOUNCE } from 'src/constants'; -import { rankedSearchCompare } from 'src/utils/rankedSearchCompare'; -import { getValue, hasOption, isLabeledValue } from './utils'; - -const { Option } = AntdSelect; - -type AntdSelectAllProps = AntdSelectProps; - -type PickedSelectProps = Pick< - AntdSelectAllProps, - | 'allowClear' - | 'autoFocus' - | 'disabled' - | 'filterOption' - | 'loading' - | 'notFoundContent' - | 'onChange' - | 'onClear' - | 'onFocus' - | 'onBlur' - | 'onDropdownVisibleChange' - | 'placeholder' - | 'showSearch' - | 'tokenSeparators' - | 'value' - | 'getPopupContainer' ->; - -export type OptionsType = Exclude; - -export type OptionsTypePage = { - data: OptionsType; - totalCount: number; -}; - -export type OptionsPagePromise = ( - search: string, - page: number, - pageSize: number, -) => Promise; +import { + getValue, + hasOption, + isLabeledValue, + DEFAULT_SORT_COMPARATOR, + EMPTY_OPTIONS, + MAX_TAG_COUNT, + SelectOptionsPagePromise, + SelectOptionsType, + SelectOptionsTypePage, + StyledCheckOutlined, + StyledStopOutlined, + TOKEN_SEPARATORS, + renderSelectOptions, + StyledContainer, + StyledSelect, + hasCustomLabels, + BaseSelectProps, + sortSelectedFirstHelper, + sortComparatorWithSearchHelper, + sortComparatorForNoSearchHelper, + getSuffixIcon, + dropDownRenderHelper, + handleFilterOptionHelper, +} from './utils'; + +const StyledError = styled.div` + ${({ theme }) => ` + display: flex; + justify-content: center; + align-items: flex-start; + width: 100%; + padding: ${theme.gridUnit * 2}px; + color: ${theme.colors.error.base}; + & svg { + margin-right: ${theme.gridUnit * 2}px; + } + `} +`; + +const StyledErrorMessage = styled.div` + overflow: hidden; + text-overflow: ellipsis; +`; + +const DEFAULT_PAGE_SIZE = 100; export type AsyncSelectRef = HTMLInputElement & { clearCache: () => void }; -export interface AsyncSelectProps extends PickedSelectProps { - /** - * It enables the user to create new options. - * Can be used with standard or async select types. - * Can be used with any mode, single or multiple. - * False by default. - * */ - allowNewOptions?: boolean; - /** - * It adds the aria-label tag for accessibility standards. - * Must be plain English and localized. - */ - ariaLabel: string; - /** - * It adds a header on top of the Select. - * Can be any ReactNode. - */ - header?: ReactNode; +export interface AsyncSelectProps extends BaseSelectProps { /** * It fires a request against the server after * the first interaction and not on render. @@ -109,43 +92,18 @@ export interface AsyncSelectProps extends PickedSelectProps { * True by default. */ lazyLoading?: boolean; - /** - * It defines whether the Select should allow for the - * selection of multiple options or single. - * Single by default. - */ - mode?: 'single' | 'multiple'; - /** - * Deprecated. - * Prefer ariaLabel instead. - */ - name?: string; // discourage usage - /** - * It allows to define which properties of the option object - * should be looked for when searching. - * By default label and value. - */ - optionFilterProps?: string[]; /** * It defines the options of the Select. * The options are async, a promise that returns * an array of options. */ - options: OptionsPagePromise; + options: SelectOptionsPagePromise; /** * It defines how many results should be included * in the query response. * Works in async mode only (See the options property). */ pageSize?: number; - /** - * It shows a stop-outlined icon at the far right of a selected - * option instead of the default checkmark. - * Useful to better indicate to the user that by clicking on a selected - * option it will be de-selected. - * False by default. - */ - invertSelection?: boolean; /** * It fires a request against the server only after * searching. @@ -159,118 +117,14 @@ export interface AsyncSelectProps extends PickedSelectProps { * Works in async mode only (See the options property). */ onError?: (error: string) => void; - /** - * Customize how filtered options are sorted while users search. - * Will not apply to predefined `options` array when users are not searching. - */ - sortComparator?: typeof DEFAULT_SORT_COMPARATOR; } -const StyledContainer = styled.div` - display: flex; - flex-direction: column; - width: 100%; -`; - -const StyledSelect = styled(AntdSelect)` - ${({ theme }) => ` - && .ant-select-selector { - border-radius: ${theme.gridUnit}px; - } - // Open the dropdown when clicking on the suffix - // This is fixed in version 4.16 - .ant-select-arrow .anticon:not(.ant-select-suffix) { - pointer-events: none; - } - `} -`; - -const StyledStopOutlined = styled(Icons.StopOutlined)` - vertical-align: 0; -`; - -const StyledCheckOutlined = styled(Icons.CheckOutlined)` - vertical-align: 0; -`; - -const StyledError = styled.div` - ${({ theme }) => ` - display: flex; - justify-content: center; - align-items: flex-start; - width: 100%; - padding: ${theme.gridUnit * 2}px; - color: ${theme.colors.error.base}; - & svg { - margin-right: ${theme.gridUnit * 2}px; - } - `} -`; - -const StyledErrorMessage = styled.div` - overflow: hidden; - text-overflow: ellipsis; -`; - -const StyledSpin = styled(Spin)` - margin-top: ${({ theme }) => -theme.gridUnit}px; -`; - -const StyledLoadingText = styled.div` - ${({ theme }) => ` - margin-left: ${theme.gridUnit * 3}px; - line-height: ${theme.gridUnit * 8}px; - color: ${theme.colors.grayscale.light1}; - `} -`; - -const MAX_TAG_COUNT = 4; -const TOKEN_SEPARATORS = [',', '\n', '\t', ';']; -const DEFAULT_PAGE_SIZE = 100; -const EMPTY_OPTIONS: OptionsType = []; - const Error = ({ error }: { error: string }) => ( {error} ); -export const DEFAULT_SORT_COMPARATOR = ( - a: AntdLabeledValue, - b: AntdLabeledValue, - search?: string, -) => { - let aText: string | undefined; - let bText: string | undefined; - if (typeof a.label === 'string' && typeof b.label === 'string') { - aText = a.label; - bText = b.label; - } else if (typeof a.value === 'string' && typeof b.value === 'string') { - aText = a.value; - bText = b.value; - } - // sort selected options first - if (typeof aText === 'string' && typeof bText === 'string') { - if (search) { - return rankedSearchCompare(aText, bText, search); - } - return aText.localeCompare(bText); - } - return (a.value as number) - (b.value as number); -}; - -/** - * It creates a comparator to check for a specific property. - * Can be used with string and number property values. - * */ -export const propertyComparator = - (property: string) => (a: AntdLabeledValue, b: AntdLabeledValue) => { - if (typeof a[property] === 'string' && typeof b[property] === 'string') { - return a[property].localeCompare(b[property]); - } - return (a[property] as number) - (b[property] as number); - }; - const getQueryCacheKey = (value: string, page: number, pageSize: number) => `${value};${page};${pageSize}`; @@ -297,6 +151,7 @@ const AsyncSelect = forwardRef( fetchOnlyOnSearch, filterOption = true, header = null, + helperText, invertSelection = false, lazyLoading = true, loading, @@ -340,23 +195,30 @@ const AsyncSelect = forwardRef( const sortSelectedFirst = useCallback( (a: AntdLabeledValue, b: AntdLabeledValue) => - selectValue && a.value !== undefined && b.value !== undefined - ? Number(hasOption(b.value, selectValue)) - - Number(hasOption(a.value, selectValue)) - : 0, + sortSelectedFirstHelper(a, b, selectValue), [selectValue], ); + const sortComparatorWithSearch = useCallback( (a: AntdLabeledValue, b: AntdLabeledValue) => - sortSelectedFirst(a, b) || sortComparator(a, b, inputValue), + sortComparatorWithSearchHelper( + a, + b, + inputValue, + sortSelectedFirst, + sortComparator, + ), [inputValue, sortComparator, sortSelectedFirst], ); + const sortComparatorForNoSearch = useCallback( (a: AntdLabeledValue, b: AntdLabeledValue) => - sortSelectedFirst(a, b) || - // Only apply the custom sorter in async mode because we should - // preserve the options order as much as possible. - sortComparator(a, b, ''), + sortComparatorForNoSearchHelper( + a, + b, + sortSelectedFirst, + sortComparator, + ), [sortComparator, sortSelectedFirst], ); @@ -371,11 +233,11 @@ const AsyncSelect = forwardRef( ); const [selectOptions, setSelectOptions] = - useState(initialOptionsSorted); + useState(initialOptionsSorted); // add selected values to options list if they are not in it const fullSelectOptions = useMemo(() => { - const missingValues: OptionsType = ensureIsArray(selectValue) + const missingValues: SelectOptionsType = ensureIsArray(selectValue) .filter(opt => !hasOption(getValue(opt), selectOptions)) .map(opt => isLabeledValue(opt) ? opt : { value: opt, label: String(opt) }, @@ -385,8 +247,6 @@ const AsyncSelect = forwardRef( : selectOptions; }, [selectOptions, selectValue]); - const hasCustomLabels = fullSelectOptions.some(opt => !!opt?.customLabel); - const handleOnSelect = ( selectedItem: string | number | AntdLabeledValue | undefined, ) => { @@ -440,8 +300,8 @@ const AsyncSelect = forwardRef( ); const mergeData = useCallback( - (data: OptionsType) => { - let mergedData: OptionsType = []; + (data: SelectOptionsType) => { + let mergedData: SelectOptionsType = []; if (data && Array.isArray(data) && data.length) { // unique option values should always be case sensitive so don't lowercase const dataValues = new Set(data.map(opt => opt.value)); @@ -474,9 +334,9 @@ const AsyncSelect = forwardRef( return; } setIsLoading(true); - const fetchOptions = options as OptionsPagePromise; + const fetchOptions = options as SelectOptionsPagePromise; fetchOptions(search, page, pageSize) - .then(({ data, totalCount }: OptionsTypePage) => { + .then(({ data, totalCount }: SelectOptionsTypePage) => { const mergedData = mergeData(data); fetchedQueries.current.set(key, totalCount); setTotalCount(totalCount); @@ -550,25 +410,8 @@ const AsyncSelect = forwardRef( } }; - const handleFilterOption = (search: string, option: AntdLabeledValue) => { - if (typeof filterOption === 'function') { - return filterOption(search, option); - } - - if (filterOption) { - const searchValue = search.trim().toLowerCase(); - if (optionFilterProps && optionFilterProps.length) { - return optionFilterProps.some(prop => { - const optionProp = option?.[prop] - ? String(option[prop]).trim().toLowerCase() - : ''; - return optionProp.includes(searchValue); - }); - } - } - - return false; - }; + const handleFilterOption = (search: string, option: AntdLabeledValue) => + handleFilterOptionHelper(search, option, optionFilterProps, filterOption); const handleOnDropdownVisibleChange = (isDropdownVisible: boolean) => { setIsDropdownVisible(isDropdownVisible); @@ -605,27 +448,15 @@ const AsyncSelect = forwardRef( const dropdownRender = ( originNode: ReactElement & { ref?: RefObject }, - ) => { - if (!isDropdownVisible) { - originNode.ref?.current?.scrollTo({ top: 0 }); - } - if (isLoading && fullSelectOptions.length === 0) { - return {t('Loading...')}; - } - return error ? : originNode; - }; - - // use a function instead of component since every rerender of the - // Select component will create a new component - const getSuffixIcon = () => { - if (isLoading) { - return ; - } - if (showSearch && isDropdownVisible) { - return ; - } - return ; - }; + ) => + dropDownRenderHelper( + originNode, + isDropdownVisible, + isLoading, + fullSelectOptions.length, + helperText, + error ? : undefined, + ); const handleClear = () => { setSelectValue(undefined); @@ -681,6 +512,10 @@ const AsyncSelect = forwardRef( [ref], ); + useEffect(() => { + setSelectValue(value); + }, [value]); + return ( {header} @@ -704,13 +539,15 @@ const AsyncSelect = forwardRef( onSelect={handleOnSelect} onClear={handleClear} onChange={onChange} - options={hasCustomLabels ? undefined : fullSelectOptions} + options={ + hasCustomLabels(fullSelectOptions) ? undefined : fullSelectOptions + } placeholder={placeholder} showSearch={showSearch} showArrow tokenSeparators={tokenSeparators || TOKEN_SEPARATORS} value={selectValue} - suffixIcon={getSuffixIcon()} + suffixIcon={getSuffixIcon(isLoading, showSearch, isDropdownVisible)} menuItemSelectedIcon={ invertSelection ? ( @@ -718,21 +555,11 @@ const AsyncSelect = forwardRef( ) } - ref={ref} {...props} + ref={ref} > - {hasCustomLabels && - fullSelectOptions.map(opt => { - const isOptObject = typeof opt === 'object'; - const label = isOptObject ? opt?.label || opt.value : opt; - const value = isOptObject ? opt.value : opt; - const { customLabel, ...optProps } = opt; - return ( - - ); - })} + {hasCustomLabels(fullSelectOptions) && + renderSelectOptions(fullSelectOptions)} ); diff --git a/superset-frontend/src/components/Select/Select.stories.tsx b/superset-frontend/src/components/Select/Select.stories.tsx index b75e1ff28bd00..e9a03fe5634b6 100644 --- a/superset-frontend/src/components/Select/Select.stories.tsx +++ b/superset-frontend/src/components/Select/Select.stories.tsx @@ -25,13 +25,10 @@ import React, { } from 'react'; import Button from 'src/components/Button'; import ControlHeader from 'src/explore/components/ControlHeader'; -import AsyncSelect, { - AsyncSelectProps, - AsyncSelectRef, - OptionsTypePage, -} from './AsyncSelect'; +import AsyncSelect, { AsyncSelectProps, AsyncSelectRef } from './AsyncSelect'; +import { SelectOptionsType, SelectOptionsTypePage } from './utils'; -import Select, { SelectProps, OptionsType } from './Select'; +import Select, { SelectProps } from './Select'; export default { title: 'Select', @@ -40,7 +37,7 @@ export default { const DEFAULT_WIDTH = 200; -const options: OptionsType = [ +const options: SelectOptionsType = [ { label: 'Such an incredibly awesome long long label', value: 'Such an incredibly awesome long long label', @@ -160,7 +157,7 @@ const mountHeader = (type: String) => { return header; }; -const generateOptions = (opts: OptionsType, count: number) => { +const generateOptions = (opts: SelectOptionsType, count: number) => { let generated = opts.slice(); let iteration = 0; while (generated.length < count) { @@ -440,7 +437,7 @@ export const AsynchronousSelect = ({ search: string, page: number, pageSize: number, - ): Promise => { + ): Promise => { const username = search.trim().toLowerCase(); return new Promise(resolve => { let results = getResults(username); @@ -458,7 +455,7 @@ export const AsynchronousSelect = ({ [responseTime], ); - const fetchUserListError = async (): Promise => + const fetchUserListError = async (): Promise => new Promise((_, reject) => { reject(new Error('Error while fetching the names from the server')); }); diff --git a/superset-frontend/src/components/Select/Select.test.tsx b/superset-frontend/src/components/Select/Select.test.tsx index 18111f30ca19e..06f5f7b8e57c1 100644 --- a/superset-frontend/src/components/Select/Select.test.tsx +++ b/superset-frontend/src/components/Select/Select.test.tsx @@ -77,6 +77,11 @@ const findSelectOption = (text: string) => within(getElementByClassName('.rc-virtual-list')).getByText(text), ); +const querySelectOption = (text: string) => + waitFor(() => + within(getElementByClassName('.rc-virtual-list')).queryByText(text), + ); + const findAllSelectOptions = () => waitFor(() => getElementsByClassName('.ant-select-item-option-content')); @@ -535,6 +540,32 @@ test('triggers getPopupContainer if passed', async () => { expect(getPopupContainer).toHaveBeenCalled(); }); +test('does not render a helper text by default', async () => { + render(); + await open(); + expect(screen.getByRole('note')).toBeInTheDocument(); + expect(screen.queryByText(helperText)).toBeInTheDocument(); +}); + +test('finds an element with a numeric value and does not duplicate the options', async () => { + const options = [ + { label: 'a', value: 11 }, + { label: 'b', value: 12 }, + ]; + render(} + onChange={evt => { + search(evt.target.value); + setSearchVal(evt.target.value); + }} + className="table-form" + placeholder={t('Search tables')} + /> + )} + +
+ {!refresh && + tableOptions.map((o, i) => ( +
+ {o.label} +
+ ))} +
+ + )} + ); } diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/index.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/index.tsx index 677f3f52ae7f1..79c6e5f4c3ad3 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/index.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/index.tsx @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import React from 'react'; +import React, { useReducer, Reducer } from 'react'; import Header from './Header'; import DatasetPanel from './DatasetPanel'; import LeftPanel from './LeftPanel'; @@ -24,13 +24,18 @@ import Footer from './Footer'; import { DatasetActionType, DatasetObject, DSReducerActionType } from './types'; import DatasetLayout from '../DatasetLayout'; +type Schema = { + schema: string; +}; + export function datasetReducer( - state: Partial | null, + state: DatasetObject | null, action: DSReducerActionType, -): Partial | null { +): Partial | Schema | null { const trimmedState = { ...(state || {}), }; + switch (action.type) { case DatasetActionType.selectDatabase: return { @@ -42,7 +47,7 @@ export function datasetReducer( case DatasetActionType.selectSchema: return { ...trimmedState, - ...action.payload, + [action.payload.name]: action.payload.value, table_name: null, }; case DatasetActionType.selectTable: @@ -61,16 +66,26 @@ export function datasetReducer( } export default function AddDataset() { - // this is commented out for now, but can be commented in as the component - // is built up. Uncomment the useReducer in imports too - // const [dataset, setDataset] = useReducer< - // Reducer | null, DSReducerActionType> - // >(datasetReducer, null); + const [dataset, setDataset] = useReducer< + Reducer | null, DSReducerActionType> + >(datasetReducer, null); + + const HeaderComponent = () => ( +
+ ); + + const LeftPanelComponent = () => ( + + ); return ( diff --git a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/types.tsx b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/types.tsx index 3d5d67f7e144d..b6f04469b8fdf 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/AddDataset/types.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/AddDataset/types.tsx @@ -24,30 +24,29 @@ export enum DatasetActionType { } export interface DatasetObject { - database: { - id: string; - database_name: string; - }; - owners: number[]; + id: number; + database_name?: string; + owners?: number[]; schema?: string | null; dataset_name: string; table_name?: string | null; } -interface DatasetReducerPayloadType { +export interface DatasetReducerPayloadType { name: string; value?: string; } +export type Schema = { + schema?: string | null | undefined; +}; + export type DSReducerActionType = | { - type: - | DatasetActionType.selectDatabase - | DatasetActionType.selectSchema - | DatasetActionType.selectTable; + type: DatasetActionType.selectDatabase | DatasetActionType.selectTable; payload: Partial; } | { - type: DatasetActionType.changeDataset; + type: DatasetActionType.changeDataset | DatasetActionType.selectSchema; payload: DatasetReducerPayloadType; }; diff --git a/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/DatasetLayout.test.tsx b/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/DatasetLayout.test.tsx index 59c3ee3ed1535..cf57c34360c62 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/DatasetLayout.test.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/DatasetLayout.test.tsx @@ -17,7 +17,7 @@ * under the License. */ import React from 'react'; -import { render, screen } from 'spec/helpers/testing-library'; +import { render, screen, waitFor } from 'spec/helpers/testing-library'; import DatasetLayout from 'src/views/CRUD/data/dataset/DatasetLayout'; import Header from 'src/views/CRUD/data/dataset/AddDataset/Header'; import LeftPanel from 'src/views/CRUD/data/dataset/AddDataset/LeftPanel'; @@ -33,17 +33,33 @@ describe('DatasetLayout', () => { expect(layoutWrapper).toHaveTextContent(''); }); - it('renders a Header when passed in', () => { - render(); + const mockSetDataset = jest.fn(); - expect(screen.getByText(/header/i)).toBeVisible(); + const waitForRender = () => + waitFor(() => + render(
), + ); + + it('renders a Header when passed in', async () => { + await waitForRender(); + + expect( + screen.getByRole('textbox', { + name: /dataset name/i, + }), + ).toBeVisible(); }); - it('renders a LeftPanel when passed in', () => { - render(); + it('renders a LeftPanel when passed in', async () => { + render( + null} />} />, + { useRedux: true }, + ); - expect(screen.getByRole('img', { name: /empty/i })).toBeVisible(); - expect(screen.getByText(/no database tables found/i)).toBeVisible(); + expect( + await screen.findByText(/select database & schema/i), + ).toBeInTheDocument(); + expect(LeftPanel).toBeTruthy(); }); it('renders a DatasetPanel when passed in', () => { diff --git a/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/index.tsx b/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/index.tsx index 8587b25a4a95c..b5691fe5cb1d8 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/index.tsx +++ b/superset-frontend/src/views/CRUD/data/dataset/DatasetLayout/index.tsx @@ -24,11 +24,11 @@ import { OuterRow, PanelRow, FooterRow, - StyledHeader, - StyledLeftPanel, - StyledDatasetPanel, - StyledRightPanel, - StyledFooter, + StyledLayoutHeader, + StyledLayoutLeftPanel, + StyledLayoutDatasetPanel, + StyledLayoutRightPanel, + StyledLayoutFooter, } from 'src/views/CRUD/data/dataset/styles'; interface DatasetLayoutProps { @@ -48,22 +48,28 @@ export default function DatasetLayout({ }: DatasetLayoutProps) { return ( - {header && {header}} + {header && {header}} - {leftPanel && {leftPanel}} + {leftPanel && ( + {leftPanel} + )} {datasetPanel && ( - {datasetPanel} + + {datasetPanel} + + )} + {rightPanel && ( + {rightPanel} )} - {rightPanel && {rightPanel}} - {footer && {footer}} + {footer && {footer}} diff --git a/superset-frontend/src/views/CRUD/data/dataset/styles.ts b/superset-frontend/src/views/CRUD/data/dataset/styles.ts index 8d9f771dce280..757837f221c80 100644 --- a/superset-frontend/src/views/CRUD/data/dataset/styles.ts +++ b/superset-frontend/src/views/CRUD/data/dataset/styles.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { styled } from '@superset-ui/core'; +import { styled, css, SupersetTheme } from '@superset-ui/core'; export const StyledLayoutWrapper = styled.div` flex-grow: 1; @@ -64,32 +64,58 @@ export const FooterRow = styled(Row)` height: ${({ theme }) => theme.gridUnit * 16}px; `; -export const StyledHeader = styled.div` +export const StyledLayoutHeader = styled.div` flex: 0 0 auto; height: ${({ theme }) => theme.gridUnit * 16}px; border-bottom: 2px solid ${({ theme }) => theme.colors.grayscale.light2}; - color: ${({ theme }) => theme.colors.error.base}; + + .header-with-actions { + height: ${({ theme }) => theme.gridUnit * 15.5}px; + } `; -export const StyledLeftPanel = styled.div` +export const StyledLayoutLeftPanel = styled.div` width: ${({ theme }) => theme.gridUnit * 80}px; height: 100%; border-right: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; `; -export const StyledDatasetPanel = styled.div` +export const StyledLayoutDatasetPanel = styled.div` width: 100%; `; -export const StyledRightPanel = styled.div` +export const StyledLayoutRightPanel = styled.div` border-left: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; color: ${({ theme }) => theme.colors.success.base}; `; -export const StyledFooter = styled.div` +export const StyledLayoutFooter = styled.div` height: ${({ theme }) => theme.gridUnit * 16}px; width: 100%; border-top: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; border-bottom: 1px solid ${({ theme }) => theme.colors.grayscale.light2}; color: ${({ theme }) => theme.colors.info.base}; `; + +export const HeaderComponentStyles = styled.div` + .ant-btn { + span { + margin-right: 0; + } + + &:disabled { + svg { + color: ${({ theme }) => theme.colors.grayscale.light1}; + } + } + } +`; + +export const disabledSaveBtnStyles = (theme: SupersetTheme) => css` + width: ${theme.gridUnit * 21.5}px; + + &:disabled { + background-color: ${theme.colors.grayscale.light3}; + color: ${theme.colors.grayscale.light1}; + } +`; diff --git a/superset-frontend/src/views/CRUD/utils.tsx b/superset-frontend/src/views/CRUD/utils.tsx index 64df8743035eb..b82e5b4e587d9 100644 --- a/superset-frontend/src/views/CRUD/utils.tsx +++ b/superset-frontend/src/views/CRUD/utils.tsx @@ -92,8 +92,9 @@ const createFetchResourceMethod = : undefined; const data: { label: string; value: string | number }[] = []; - json?.result?.forEach( - ({ text, value }: { text: string; value: string | number }) => { + json?.result + ?.filter(({ text }: { text: string }) => text.trim().length > 0) + .forEach(({ text, value }: { text: string; value: string | number }) => { if ( loggedUser && value === loggedUser.value && @@ -106,8 +107,7 @@ const createFetchResourceMethod = value, }); } - }, - ); + }); if (loggedUser && (!filterValue || fetchedLoggedUser)) { data.unshift(loggedUser); diff --git a/superset-frontend/cypress-base/cypress/integration/chart_list/chart_list.helper.ts b/superset-frontend/src/views/QueryProvider.tsx similarity index 58% rename from superset-frontend/cypress-base/cypress/integration/chart_list/chart_list.helper.ts rename to superset-frontend/src/views/QueryProvider.tsx index 0d66010cf4eb7..9fef2022d4c33 100644 --- a/superset-frontend/cypress-base/cypress/integration/chart_list/chart_list.helper.ts +++ b/superset-frontend/src/views/QueryProvider.tsx @@ -16,4 +16,28 @@ * specific language governing permissions and limitations * under the License. */ -export const CHART_LIST = '/chart/list/'; +import React from 'react'; +import { QueryClient, QueryClientProvider } from 'react-query'; + +export const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: Infinity, + retry: false, + retryOnMount: false, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, + }, + }, +}); + +type Props = { + children: React.ReactNode; +}; + +const Queryprovider: React.FC = ({ children }) => ( + {children} +); + +export default Queryprovider; diff --git a/superset-frontend/src/views/components/LanguagePicker.test.tsx b/superset-frontend/src/views/components/LanguagePicker.test.tsx index 230c89d18b48c..a427c4e91ae0c 100644 --- a/superset-frontend/src/views/components/LanguagePicker.test.tsx +++ b/superset-frontend/src/views/components/LanguagePicker.test.tsx @@ -38,22 +38,23 @@ const mockedProps = { }, }; -test('should render', () => { +test('should render', async () => { const { container } = render( , ); + expect(await screen.findByRole('button')).toBeInTheDocument(); expect(container).toBeInTheDocument(); }); -test('should render the language picker', () => { +test('should render the language picker', async () => { render( , ); - expect(screen.getByLabelText('Languages')).toBeInTheDocument(); + expect(await screen.findByLabelText('Languages')).toBeInTheDocument(); }); test('should render the items', async () => { diff --git a/superset-frontend/src/views/components/Menu.test.tsx b/superset-frontend/src/views/components/Menu.test.tsx index 0c32d24a184ea..b40a5ab075252 100644 --- a/superset-frontend/src/views/components/Menu.test.tsx +++ b/superset-frontend/src/views/components/Menu.test.tsx @@ -252,27 +252,28 @@ beforeEach(() => { useSelectorMock.mockClear(); }); -test('should render', () => { +test('should render', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); const { container } = render(, { useRedux: true, useQueryParams: true, useRouter: true, }); + expect(await screen.findByText(/sources/i)).toBeInTheDocument(); expect(container).toBeInTheDocument(); }); -test('should render the navigation', () => { +test('should render the navigation', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); render(, { useRedux: true, useQueryParams: true, useRouter: true, }); - expect(screen.getByRole('navigation')).toBeInTheDocument(); + expect(await screen.findByRole('navigation')).toBeInTheDocument(); }); -test('should render the brand', () => { +test('should render the brand', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); const { data: { @@ -284,20 +285,21 @@ test('should render the brand', () => { useQueryParams: true, useRouter: true, }); + expect(await screen.findByAltText(alt)).toBeInTheDocument(); const image = screen.getByAltText(alt); expect(image).toHaveAttribute('src', icon); }); -test('should render the environment tag', () => { +test('should render the environment tag', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); const { data: { environment_tag }, } = mockedProps; - render(, { useRedux: true }); - expect(screen.getByText(environment_tag.text)).toBeInTheDocument(); + render(, { useRedux: true, useQueryParams: true }); + expect(await screen.findByText(environment_tag.text)).toBeInTheDocument(); }); -test('should render all the top navbar menu items', () => { +test('should render all the top navbar menu items', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); const { data: { menu }, @@ -307,6 +309,7 @@ test('should render all the top navbar menu items', () => { useQueryParams: true, useRouter: true, }); + expect(await screen.findByText(menu[0].label)).toBeInTheDocument(); menu.forEach(item => { expect(screen.getByText(item.label)).toBeInTheDocument(); }); @@ -401,23 +404,24 @@ test('should render the Settings dropdown child menu items', async () => { expect(listUsers).toHaveAttribute('href', settings[0].childs[0].url); }); -test('should render the plus menu (+) when user is not anonymous', () => { +test('should render the plus menu (+) when user is not anonymous', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); render(, { useRedux: true, useQueryParams: true, useRouter: true, }); - expect(screen.getByTestId('new-dropdown')).toBeInTheDocument(); + expect(await screen.findByTestId('new-dropdown')).toBeInTheDocument(); }); -test('should NOT render the plus menu (+) when user is anonymous', () => { +test('should NOT render the plus menu (+) when user is anonymous', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); render(, { useRedux: true, useQueryParams: true, useRouter: true, }); + expect(await screen.findByText(/sources/i)).toBeInTheDocument(); expect(screen.queryByTestId('new-dropdown')).not.toBeInTheDocument(); }); @@ -445,13 +449,14 @@ test('should render the user actions when user is not anonymous', async () => { expect(logout).toHaveAttribute('href', user_logout_url); }); -test('should NOT render the user actions when user is anonymous', () => { +test('should NOT render the user actions when user is anonymous', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); render(, { useRedux: true, useQueryParams: true, useRouter: true, }); + expect(await screen.findByText(/sources/i)).toBeInTheDocument(); expect(screen.queryByText('User')).not.toBeInTheDocument(); }); @@ -532,7 +537,7 @@ test('should render the Bug Report link when available', async () => { expect(bugReport).toHaveAttribute('href', bug_report_url); }); -test('should render the Login link when user is anonymous', () => { +test('should render the Login link when user is anonymous', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); const { data: { @@ -545,37 +550,43 @@ test('should render the Login link when user is anonymous', () => { useQueryParams: true, useRouter: true, }); - const login = screen.getByText('Login'); + const login = await screen.findByText('Login'); expect(login).toHaveAttribute('href', user_login_url); }); -test('should render the Language Picker', () => { +test('should render the Language Picker', async () => { useSelectorMock.mockReturnValue({ roles: user.roles }); render(, { useRedux: true, useQueryParams: true, useRouter: true, }); - expect(screen.getByLabelText('Languages')).toBeInTheDocument(); + expect(await screen.findByLabelText('Languages')).toBeInTheDocument(); }); -test('should hide create button without proper roles', () => { +test('should hide create button without proper roles', async () => { useSelectorMock.mockReturnValue({ roles: [] }); render(, { useRedux: true, useQueryParams: true, useRouter: true, }); + expect(await screen.findByText(/sources/i)).toBeInTheDocument(); expect(screen.queryByTestId('new-dropdown')).not.toBeInTheDocument(); }); -test('should render without QueryParamProvider', () => { +test('should render without QueryParamProvider', async () => { useSelectorMock.mockReturnValue({ roles: [] }); - render(, { useRedux: true, useRouter: true }); + render(, { + useRedux: true, + useRouter: true, + useQueryParams: true, + }); + expect(await screen.findByText(/sources/i)).toBeInTheDocument(); expect(screen.queryByTestId('new-dropdown')).not.toBeInTheDocument(); }); -test('should render an extension component if one is supplied', () => { +test('should render an extension component if one is supplied', async () => { const extensionsRegistry = getExtensionsRegistry(); extensionsRegistry.set('navbar.right', () => ( @@ -584,9 +595,9 @@ test('should render an extension component if one is supplied', () => { setupExtensions(); - render(, { useRouter: true }); + render(, { useRouter: true, useQueryParams: true }); expect( - screen.getByText('navbar.right extension component'), + await screen.findByText('navbar.right extension component'), ).toBeInTheDocument(); }); diff --git a/superset-frontend/src/views/components/Menu.tsx b/superset-frontend/src/views/components/Menu.tsx index 320eea4e16d9f..1566187fe8c14 100644 --- a/superset-frontend/src/views/components/Menu.tsx +++ b/superset-frontend/src/views/components/Menu.tsx @@ -355,6 +355,7 @@ export default function MenuWrapper({ data, ...rest }: MenuProps) { }; // Menu items that should go into settings dropdown const settingsMenus = { + Data: true, Security: true, Manage: true, }; diff --git a/superset-frontend/src/views/components/RightMenu.tsx b/superset-frontend/src/views/components/RightMenu.tsx index fabf071eb60b9..27f84a0be697d 100644 --- a/superset-frontend/src/views/components/RightMenu.tsx +++ b/superset-frontend/src/views/components/RightMenu.tsx @@ -281,7 +281,7 @@ const RightMenu = ({ onDatabaseAdd={handleDatabaseAdd} /> )} - {environmentTag.text && ( + {environmentTag && environmentTag.text && (