From 8f7b1835df17b6fa165a4db105b2d7fe4aa56320 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 27 May 2022 11:02:31 +0000 Subject: [PATCH 01/56] chore(deps): update dependency msw to ^0.41.0 Signed-off-by: Renovate Bot --- .changeset/renovate-9363c67.md | 98 ++++++++++++++ packages/backend-common/package.json | 2 +- packages/backend-test-utils/package.json | 2 +- packages/catalog-client/package.json | 2 +- packages/cli/package.json | 2 +- packages/config-loader/package.json | 2 +- packages/core-app-api/package.json | 2 +- packages/core-components/package.json | 2 +- packages/core-plugin-api/package.json | 2 +- packages/integration-react/package.json | 2 +- packages/integration/package.json | 2 +- packages/release-manifests/package.json | 2 +- packages/test-utils/package.json | 2 +- plugins/adr-backend/package.json | 2 +- plugins/adr/package.json | 2 +- plugins/airbrake-backend/package.json | 2 +- plugins/airbrake/package.json | 2 +- plugins/allure/package.json | 2 +- plugins/analytics-module-ga/package.json | 2 +- plugins/apache-airflow/package.json | 2 +- plugins/api-docs/package.json | 2 +- plugins/app-backend/package.json | 2 +- plugins/auth-backend/package.json | 2 +- plugins/auth-node/package.json | 2 +- plugins/azure-devops-backend/package.json | 2 +- plugins/azure-devops/package.json | 2 +- plugins/badges/package.json | 2 +- plugins/bitrise/package.json | 2 +- .../catalog-backend-module-azure/package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- .../package.json | 2 +- plugins/catalog-backend/package.json | 2 +- plugins/catalog-graphql/package.json | 2 +- plugins/catalog-import/package.json | 2 +- plugins/circleci/package.json | 2 +- plugins/cloudbuild/package.json | 2 +- plugins/code-climate/package.json | 2 +- plugins/code-coverage-backend/package.json | 2 +- plugins/code-coverage/package.json | 2 +- plugins/codescene/package.json | 2 +- plugins/config-schema/package.json | 2 +- plugins/cost-insights/package.json | 2 +- .../example-todo-list-backend/package.json | 2 +- plugins/example-todo-list-common/package.json | 2 +- plugins/example-todo-list/package.json | 2 +- plugins/explore-react/package.json | 2 +- plugins/explore/package.json | 2 +- plugins/firehydrant/package.json | 2 +- plugins/fossa/package.json | 2 +- plugins/gcalendar/package.json | 2 +- plugins/gcp-projects/package.json | 2 +- plugins/git-release-manager/package.json | 2 +- plugins/github-actions/package.json | 2 +- plugins/github-deployments/package.json | 2 +- .../github-pull-requests-board/package.json | 2 +- plugins/gitops-profiles/package.json | 2 +- plugins/gocd/package.json | 2 +- plugins/graphiql/package.json | 2 +- plugins/graphql-backend/package.json | 2 +- plugins/home/package.json | 2 +- plugins/ilert/package.json | 2 +- plugins/jenkins-backend/package.json | 2 +- plugins/jenkins/package.json | 2 +- plugins/kafka/package.json | 2 +- plugins/kubernetes/package.json | 2 +- plugins/lighthouse/package.json | 2 +- plugins/newrelic/package.json | 2 +- plugins/org/package.json | 2 +- plugins/pagerduty/package.json | 2 +- plugins/periskop-backend/package.json | 2 +- plugins/periskop/package.json | 2 +- plugins/permission-backend/package.json | 2 +- plugins/permission-common/package.json | 2 +- plugins/permission-node/package.json | 2 +- plugins/rollbar-backend/package.json | 2 +- plugins/rollbar/package.json | 2 +- .../package.json | 2 +- plugins/scaffolder-backend/package.json | 2 +- plugins/scaffolder/package.json | 2 +- plugins/search/package.json | 2 +- plugins/sentry/package.json | 2 +- plugins/shortcuts/package.json | 2 +- plugins/sonarqube/package.json | 2 +- plugins/splunk-on-call/package.json | 2 +- plugins/stack-overflow/package.json | 2 +- plugins/tech-insights/package.json | 2 +- plugins/tech-radar/package.json | 2 +- .../techdocs-addons-test-utils/package.json | 2 +- plugins/techdocs-backend/package.json | 2 +- .../package.json | 2 +- plugins/techdocs/package.json | 2 +- plugins/todo-backend/package.json | 2 +- plugins/todo/package.json | 2 +- plugins/user-settings/package.json | 2 +- plugins/xcmetrics/package.json | 2 +- yarn.lock | 120 +++--------------- 99 files changed, 215 insertions(+), 197 deletions(-) create mode 100644 .changeset/renovate-9363c67.md diff --git a/.changeset/renovate-9363c67.md b/.changeset/renovate-9363c67.md new file mode 100644 index 0000000000..85115241de --- /dev/null +++ b/.changeset/renovate-9363c67.md @@ -0,0 +1,98 @@ +--- +'@backstage/backend-common': patch +'@backstage/backend-test-utils': patch +'@backstage/catalog-client': patch +'@backstage/cli': patch +'@backstage/config-loader': patch +'@backstage/core-app-api': patch +'@backstage/core-components': patch +'@backstage/core-plugin-api': patch +'@backstage/integration-react': patch +'@backstage/integration': patch +'@backstage/release-manifests': patch +'@backstage/test-utils': patch +'@backstage/plugin-adr-backend': patch +'@backstage/plugin-adr': patch +'@backstage/plugin-airbrake-backend': patch +'@backstage/plugin-airbrake': patch +'@backstage/plugin-allure': patch +'@backstage/plugin-analytics-module-ga': patch +'@backstage/plugin-apache-airflow': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-app-backend': patch +'@backstage/plugin-auth-backend': patch +'@backstage/plugin-auth-node': patch +'@backstage/plugin-azure-devops-backend': patch +'@backstage/plugin-azure-devops': patch +'@backstage/plugin-badges': patch +'@backstage/plugin-bitrise': patch +'@backstage/plugin-catalog-backend-module-azure': patch +'@backstage/plugin-catalog-backend-module-bitbucket': patch +'@backstage/plugin-catalog-backend-module-gerrit': patch +'@backstage/plugin-catalog-backend-module-github': patch +'@backstage/plugin-catalog-backend-module-gitlab': patch +'@backstage/plugin-catalog-backend-module-msgraph': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog-graphql': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-circleci': patch +'@backstage/plugin-cloudbuild': patch +'@backstage/plugin-code-climate': patch +'@backstage/plugin-code-coverage-backend': patch +'@backstage/plugin-code-coverage': patch +'@backstage/plugin-codescene': patch +'@backstage/plugin-config-schema': patch +'@backstage/plugin-cost-insights': patch +'@backstage/plugin-explore-react': patch +'@backstage/plugin-explore': patch +'@backstage/plugin-firehydrant': patch +'@backstage/plugin-fossa': patch +'@backstage/plugin-gcalendar': patch +'@backstage/plugin-gcp-projects': patch +'@backstage/plugin-git-release-manager': patch +'@backstage/plugin-github-actions': patch +'@backstage/plugin-github-deployments': patch +'@backstage/plugin-github-pull-requests-board': patch +'@backstage/plugin-gitops-profiles': patch +'@backstage/plugin-gocd': patch +'@backstage/plugin-graphiql': patch +'@backstage/plugin-graphql-backend': patch +'@backstage/plugin-home': patch +'@backstage/plugin-ilert': patch +'@backstage/plugin-jenkins-backend': patch +'@backstage/plugin-jenkins': patch +'@backstage/plugin-kafka': patch +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-lighthouse': patch +'@backstage/plugin-newrelic': patch +'@backstage/plugin-org': patch +'@backstage/plugin-pagerduty': patch +'@backstage/plugin-periskop-backend': patch +'@backstage/plugin-periskop': patch +'@backstage/plugin-permission-backend': patch +'@backstage/plugin-permission-common': patch +'@backstage/plugin-permission-node': patch +'@backstage/plugin-rollbar-backend': patch +'@backstage/plugin-rollbar': patch +'@backstage/plugin-scaffolder-backend-module-cookiecutter': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-search': patch +'@backstage/plugin-sentry': patch +'@backstage/plugin-shortcuts': patch +'@backstage/plugin-sonarqube': patch +'@backstage/plugin-splunk-on-call': patch +'@backstage/plugin-stack-overflow': patch +'@backstage/plugin-tech-insights': patch +'@backstage/plugin-tech-radar': patch +'@backstage/plugin-techdocs-addons-test-utils': patch +'@backstage/plugin-techdocs-backend': patch +'@backstage/plugin-techdocs-module-addons-contrib': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-todo-backend': patch +'@backstage/plugin-todo': patch +'@backstage/plugin-user-settings': patch +'@backstage/plugin-xcmetrics': patch +--- + +Updated dependency `msw` to `^0.41.0`. diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 6114505127..a89e06306d 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -112,7 +112,7 @@ "better-sqlite3": "^7.5.0", "http-errors": "^2.0.0", "mock-fs": "^5.1.0", - "msw": "^0.35.0", + "msw": "^0.41.0", "mysql2": "^2.2.5", "recursive-readdir": "^2.2.2", "supertest": "^6.1.3" diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 80bab74f52..23bfc5e13b 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -39,7 +39,7 @@ "@backstage/config": "^1.0.1", "better-sqlite3": "^7.5.0", "knex": "^1.0.2", - "msw": "^0.35.0", + "msw": "^0.41.0", "mysql2": "^2.2.5", "pg": "^8.3.0", "testcontainers": "^8.1.2", diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 0aa0f3a220..974e6e5263 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -40,7 +40,7 @@ "devDependencies": { "@backstage/cli": "^0.17.2-next.0", "@types/jest": "^26.0.7", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/packages/cli/package.json b/packages/cli/package.json index f562cc8772..647a01c126 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -152,7 +152,7 @@ "@types/yarnpkg__lockfile": "^1.1.4", "del": "^6.0.0", "mock-fs": "^5.1.0", - "msw": "^0.35.0", + "msw": "^0.41.0", "nodemon": "^2.0.2", "ts-node": "^10.0.0" }, diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 00629bf2fb..11582eda1f 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -57,7 +57,7 @@ "@types/node": "^16.11.26", "@types/yup": "^0.29.13", "mock-fs": "^5.1.0", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index c9f12431e9..354d55f499 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -59,7 +59,7 @@ "@types/node": "^16.11.26", "@types/zen-observable": "^0.8.0", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 8ccadf7db5..c628ec3730 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -101,7 +101,7 @@ "@types/react-window": "^1.8.5", "@types/zen-observable": "^0.8.0", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index e0ea4b425a..aa4cfbf655 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -58,7 +58,7 @@ "@types/prop-types": "^15.7.3", "@types/zen-observable": "^0.8.0", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index ff0646dbcf..08ea77c022 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -46,7 +46,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.35.0", + "msw": "^0.41.0", "cross-fetch": "^3.1.5" }, "files": [ diff --git a/packages/integration/package.json b/packages/integration/package.json index a27e07d7a9..d69d58e7b8 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -48,7 +48,7 @@ "@backstage/test-utils": "^1.1.0", "@types/jest": "^26.0.7", "@types/luxon": "^2.0.4", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/packages/release-manifests/package.json b/packages/release-manifests/package.json index ef968e7a2d..b52f86cc8c 100644 --- a/packages/release-manifests/package.json +++ b/packages/release-manifests/package.json @@ -37,7 +37,7 @@ }, "devDependencies": { "@backstage/test-utils": "^1.1.0", - "msw": "^0.35.0", + "msw": "^0.41.0", "@types/jest": "^26.0.7", "@types/node": "^16.0.0" }, diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index a532b78ccb..e27af59363 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -58,7 +58,7 @@ "@backstage/cli": "^0.17.2-next.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/adr-backend/package.json b/plugins/adr-backend/package.json index ecdd9dce29..190dd7fef4 100644 --- a/plugins/adr-backend/package.json +++ b/plugins/adr-backend/package.json @@ -48,7 +48,7 @@ "@types/marked": "^4.0.0", "@types/supertest": "^2.0.8", "supertest": "^6.1.3", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/adr/package.json b/plugins/adr/package.json index b63f5818ed..f6b3db212a 100644 --- a/plugins/adr/package.json +++ b/plugins/adr/package.json @@ -55,7 +55,7 @@ "@types/jest": "*", "@types/node": "*", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json index 5b788ecfff..cfa6bc15ca 100644 --- a/plugins/airbrake-backend/package.json +++ b/plugins/airbrake-backend/package.json @@ -37,7 +37,7 @@ "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index b2e8d21eca..4dfab9c336 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -51,7 +51,7 @@ "@types/node": "^16.11.26", "@types/object-hash": "^2.2.1", "cross-fetch": "^3.1.5", - "msw": "^0.35.0", + "msw": "^0.41.0", "react-router": "6.0.0-beta.0" }, "files": [ diff --git a/plugins/allure/package.json b/plugins/allure/package.json index 60a27654c1..c6257144a2 100644 --- a/plugins/allure/package.json +++ b/plugins/allure/package.json @@ -50,7 +50,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index 2c764e740c..a69c355840 100644 --- a/plugins/analytics-module-ga/package.json +++ b/plugins/analytics-module-ga/package.json @@ -48,7 +48,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json index 32bc6e21dd..45e5c4a13e 100644 --- a/plugins/apache-airflow/package.json +++ b/plugins/apache-airflow/package.json @@ -45,7 +45,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index dcace08715..6cdc6e668f 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -68,7 +68,7 @@ "@types/node": "^16.11.26", "@types/swagger-ui-react": "^4.1.1", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 68aa3301a4..a56efaa5b6 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -55,7 +55,7 @@ "@backstage/types": "^1.0.0", "@types/supertest": "^2.0.8", "mock-fs": "^5.1.0", - "msw": "^0.35.0", + "msw": "^0.41.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 99ce0ff6bd..5a3a1bd996 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -88,7 +88,7 @@ "@types/passport-saml": "^1.1.3", "@types/passport-strategy": "^0.2.35", "@types/xml2js": "^0.4.7", - "msw": "^0.35.0", + "msw": "^0.41.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 27d0575417..c03f2d46df 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -33,7 +33,7 @@ "devDependencies": { "@backstage/cli": "^0.17.2-next.0", "lodash": "^4.17.21", - "msw": "^0.35.0", + "msw": "^0.41.0", "uuid": "^8.0.0" }, "files": [ diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index 03c91c6394..ec14384b47 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -38,7 +38,7 @@ "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index 5990a9501a..f0465828da 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -59,7 +59,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/badges/package.json b/plugins/badges/package.json index b7be771e19..a51867c113 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -56,7 +56,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 81bd158b7d..92e8013342 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -53,7 +53,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "@types/recharts": "^1.8.15", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 2fd95ef97b..ae3b264155 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -41,7 +41,7 @@ "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@backstage/types": "^1.0.0", "lodash": "^4.17.21", - "msw": "^0.35.0", + "msw": "^0.41.0", "node-fetch": "^2.6.7", "winston": "^3.2.1" }, diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json index 4e40fa9400..47a1a81954 100644 --- a/plugins/catalog-backend-module-bitbucket/package.json +++ b/plugins/catalog-backend-module-bitbucket/package.json @@ -42,7 +42,7 @@ "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@backstage/types": "^1.0.0", "lodash": "^4.17.21", - "msw": "^0.35.0", + "msw": "^0.41.0", "node-fetch": "^2.6.7", "winston": "^3.2.1" }, diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 3d8c51a9c3..67ba042c76 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -36,7 +36,7 @@ "@backstage/integration": "^1.2.1-next.0", "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "fs-extra": "10.1.0", - "msw": "^0.35.0", + "msw": "^0.41.0", "node-fetch": "^2.6.7", "uuid": "^8.0.0", "winston": "^3.2.1" diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 12dfaf8e4e..7d48dc07e7 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -43,7 +43,7 @@ "@backstage/types": "^1.0.0", "@octokit/graphql": "^4.5.8", "lodash": "^4.17.21", - "msw": "^0.35.0", + "msw": "^0.41.0", "node-fetch": "^2.6.7", "uuid": "^8.0.0", "winston": "^3.2.1" diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index a273f936cf..94991d6dd3 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -41,7 +41,7 @@ "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@backstage/types": "^1.0.0", "lodash": "^4.17.21", - "msw": "^0.35.0", + "msw": "^0.41.0", "node-fetch": "^2.6.7", "winston": "^3.2.1" }, diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index c9c9b75962..86445a6e84 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -52,7 +52,7 @@ "@backstage/backend-test-utils": "^0.1.25-next.0", "@backstage/cli": "^0.17.2-next.0", "@types/lodash": "^4.14.151", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index e35590838f..6ae605ab6f 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -78,7 +78,7 @@ "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", "better-sqlite3": "^7.5.0", - "msw": "^0.35.0", + "msw": "^0.41.0", "supertest": "^6.1.3", "wait-for-expect": "^3.0.2", "luxon": "^2.0.2" diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 848037bf53..cb878f3487 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -53,7 +53,7 @@ "@graphql-codegen/typescript": "^2.4.2", "@graphql-codegen/typescript-resolvers": "^2.4.3", "@graphql-tools/schema": "^8.3.1", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index b15faada11..f7dcb5cd32 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -70,7 +70,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index ee8ee6d27f..9d9eac38ec 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -66,7 +66,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 924b9ce21e..906e4f423e 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -62,7 +62,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/code-climate/package.json b/plugins/code-climate/package.json index 0765c7aec4..c2314ae029 100644 --- a/plugins/code-climate/package.json +++ b/plugins/code-climate/package.json @@ -51,7 +51,7 @@ "@types/luxon": "^2.0.9", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index f3bd764000..34b75ed91c 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -42,7 +42,7 @@ "@backstage/cli": "^0.17.2-next.0", "@types/express-xml-bodyparser": "^0.3.2", "@types/supertest": "^2.0.8", - "msw": "^0.35.0", + "msw": "^0.41.0", "supertest": "^6.1.6", "xml2js": "^0.4.23" }, diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index b01de562eb..62680ecfce 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -58,7 +58,7 @@ "@types/node": "^16.11.26", "@types/recharts": "^1.8.15", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/codescene/package.json b/plugins/codescene/package.json index 670231908b..6be112f2f8 100644 --- a/plugins/codescene/package.json +++ b/plugins/codescene/package.json @@ -47,7 +47,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "*", "@types/node": "*", - "msw": "^0.35.0", + "msw": "^0.41.0", "cross-fetch": "^3.1.5" }, "files": [ diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 4c2b64eb2b..5829cde044 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -51,7 +51,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 46fd3f4bd8..53ef8b4b43 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -75,7 +75,7 @@ "@types/yup": "^0.29.13", "canvas": "^2.6.1", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index e9df66e183..9a0d8addba 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -36,7 +36,7 @@ "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", - "msw": "^0.35.0", + "msw": "^0.41.0", "supertest": "^6.1.6" }, "files": [ diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index ca91f926ed..7cadd61ae9 100644 --- a/plugins/example-todo-list-common/package.json +++ b/plugins/example-todo-list-common/package.json @@ -31,7 +31,7 @@ "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@types/node": "^16.11.26", - "msw": "^0.35.0", + "msw": "^0.41.0", "cross-fetch": "^3.1.5" }, "files": [ diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index 36b9a72048..c63e3a500c 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -42,7 +42,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index c925e024d2..a1651a9d12 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -45,7 +45,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/explore/package.json b/plugins/explore/package.json index a4114d9cee..cb867c42c7 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -63,7 +63,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json index 6af1ec094c..a6e85de484 100644 --- a/plugins/firehydrant/package.json +++ b/plugins/firehydrant/package.json @@ -49,7 +49,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 6763f32711..9de5c75b34 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -62,7 +62,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/gcalendar/package.json b/plugins/gcalendar/package.json index 3bdad9c3b7..74d1f4bcc3 100644 --- a/plugins/gcalendar/package.json +++ b/plugins/gcalendar/package.json @@ -55,7 +55,7 @@ "@types/node": "^16.11.26", "@types/sanitize-html": "^2.6.2", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 2edff98b4f..ca1b197381 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -57,7 +57,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index d811a20779..2e29c29c09 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -55,7 +55,7 @@ "@types/node": "^16.11.26", "@types/recharts": "^1.8.15", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index c86dd6b8a0..f22eff2dd9 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -65,7 +65,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index 03ce5d840e..76ba61d300 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -53,7 +53,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json index 402e21f94e..de10efbdbc 100644 --- a/plugins/github-pull-requests-board/package.json +++ b/plugins/github-pull-requests-board/package.json @@ -58,7 +58,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "peerDependencies": { "@types/react": "^16.13.1 || ^17.0.0", diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 3f35eaee17..6dea00969d 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -58,7 +58,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index d4dd8e1c87..c97ace1b6b 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -61,7 +61,7 @@ "@types/luxon": "^2.0.4", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 0a33313b28..c18a815032 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -60,7 +60,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0", + "msw": "^0.41.0", "react-router-dom": "6.0.0-beta.0" }, "files": [ diff --git a/plugins/graphql-backend/package.json b/plugins/graphql-backend/package.json index 4987de1b23..b88198acc3 100644 --- a/plugins/graphql-backend/package.json +++ b/plugins/graphql-backend/package.json @@ -53,7 +53,7 @@ "devDependencies": { "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", - "msw": "^0.35.0", + "msw": "^0.41.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/home/package.json b/plugins/home/package.json index 48b92442db..045e79222b 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -63,7 +63,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index b428b2213f..100b8f2e64 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -53,7 +53,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index 05a1564cae..1801b60c2e 100644 --- a/plugins/jenkins-backend/package.json +++ b/plugins/jenkins-backend/package.json @@ -44,7 +44,7 @@ "@backstage/cli": "^0.17.2-next.0", "@types/jenkins": "^0.23.1", "@types/supertest": "^2.0.8", - "msw": "^0.35.0", + "msw": "^0.41.0", "supertest": "^6.1.6" }, "files": [ diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index c58eac65af..9af135d7c3 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -65,7 +65,7 @@ "@types/node": "^16.11.26", "@types/testing-library__jest-dom": "^5.9.1", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 492bbe48d5..3c77eba248 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -51,7 +51,7 @@ "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", "jest-when": "^3.1.0", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 341a5c086d..2c010c8424 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -68,7 +68,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 1a02356fe2..13bcc8a209 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -63,7 +63,7 @@ "@types/node": "^16.11.26", "@types/react": "^16.13.1 || ^17.0.0", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 056447c90b..16e855e16b 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -57,7 +57,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/org/package.json b/plugins/org/package.json index abe9ce76e7..8acfed7b33 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -59,7 +59,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index fa8f15e35e..6179b71345 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -62,7 +62,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/periskop-backend/package.json b/plugins/periskop-backend/package.json index e569d1cca2..0a95432aa9 100644 --- a/plugins/periskop-backend/package.json +++ b/plugins/periskop-backend/package.json @@ -37,7 +37,7 @@ "devDependencies": { "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", - "msw": "^0.35.0", + "msw": "^0.41.0", "supertest": "^6.1.6" }, "files": [ diff --git a/plugins/periskop/package.json b/plugins/periskop/package.json index 32ed926e56..df8cd557d2 100644 --- a/plugins/periskop/package.json +++ b/plugins/periskop/package.json @@ -53,7 +53,7 @@ "@types/luxon": "^2.0.4", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 1ac11779c4..e6dc551424 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -43,7 +43,7 @@ "@types/lodash": "^4.14.151", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index 19b0742a93..8ce0ad28e7 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -50,6 +50,6 @@ "devDependencies": { "@backstage/cli": "^0.17.2-next.0", "@types/jest": "^26.0.7", - "msw": "^0.35.0" + "msw": "^0.41.0" } } diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 670b79806d..1a011751c4 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", - "msw": "^0.35.0", + "msw": "^0.41.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 14fdbc3b78..18d804fb4f 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -53,7 +53,7 @@ "@backstage/backend-test-utils": "^0.1.25-next.0", "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", - "msw": "^0.36.3", + "msw": "^0.41.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 06cf4c5f65..36aec8376a 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -65,7 +65,7 @@ "@types/node": "^16.11.26", "@types/react": "^16.13.1 || ^17.0.0", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 05d4eb4511..c56262d94c 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -41,7 +41,7 @@ "@types/jest": "^26.0.7", "@types/command-exists": "^1.2.0", "mock-fs": "^5.1.0", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index b2ba72dc2a..32fa9dd91d 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -86,7 +86,7 @@ "esbuild": "^0.14.1", "jest-when": "^3.1.0", "mock-fs": "^5.1.0", - "msw": "^0.35.0", + "msw": "^0.41.0", "supertest": "^6.1.3", "yaml": "^1.10.0" }, diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index edc9185488..287ff99336 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -93,7 +93,7 @@ "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", "event-source-polyfill": "1.0.25", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/search/package.json b/plugins/search/package.json index 5724c2af0c..5d10055f75 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -68,7 +68,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 40566e1f2c..ab3264d8f7 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -65,7 +65,7 @@ "@types/node": "^16.11.26", "@types/react": "^16.13.1 || ^17.0.0", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index 987be389ef..67bac21bd0 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -52,7 +52,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 8f872c4e1c..c265f51942 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -62,7 +62,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 2bcd01dc9c..12b09308bc 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -62,7 +62,7 @@ "@types/luxon": "^2.0.4", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "configSchema": "config.d.ts", "files": [ diff --git a/plugins/stack-overflow/package.json b/plugins/stack-overflow/package.json index 2632d113af..db5d605c27 100644 --- a/plugins/stack-overflow/package.json +++ b/plugins/stack-overflow/package.json @@ -50,7 +50,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/tech-insights/package.json b/plugins/tech-insights/package.json index 461151659c..5af19b162b 100644 --- a/plugins/tech-insights/package.json +++ b/plugins/tech-insights/package.json @@ -57,7 +57,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 99975b5542..9ca173c3df 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -62,7 +62,7 @@ "@types/node": "^16.11.26", "@types/react": "^16.13.1 || ^17.0.0", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index 81e05d18c2..9a56db3972 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -63,7 +63,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.35.0", + "msw": "^0.41.0", "cross-fetch": "^3.1.5" }, "files": [ diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 9bca7b7a81..9f7d9ab33b 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -60,7 +60,7 @@ "@backstage/cli": "^0.17.2-next.0", "@backstage/plugin-search-backend-node": "0.6.2-next.0", "@types/dockerode": "^3.3.0", - "msw": "^0.35.0", + "msw": "^0.41.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index eb6f4c9c2d..f064e35612 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -62,7 +62,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "@types/react": "^16.13.1 || ^17.0.0", - "msw": "^0.35.0", + "msw": "^0.41.0", "cross-fetch": "^3.1.5" }, "files": [ diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 49dd1a61fb..a09a527975 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -81,7 +81,7 @@ "@types/node": "^16.11.26", "canvas": "^2.6.1", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist", diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index c050bbf6ca..b5d98ef781 100644 --- a/plugins/todo-backend/package.json +++ b/plugins/todo-backend/package.json @@ -45,7 +45,7 @@ "devDependencies": { "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", - "msw": "^0.35.0", + "msw": "^0.41.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/todo/package.json b/plugins/todo/package.json index 8df4c8b31b..377dc236ce 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -55,7 +55,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0", + "msw": "^0.41.0", "react-router": "6.0.0-beta.0" }, "files": [ diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index dae4e4b360..b4146ed6ad 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -58,7 +58,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index da57f71fef..a070e2b18c 100644 --- a/plugins/xcmetrics/package.json +++ b/plugins/xcmetrics/package.json @@ -51,7 +51,7 @@ "@types/luxon": "^2.0.4", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.35.0" + "msw": "^0.41.0" }, "files": [ "dist" diff --git a/yarn.lock b/yarn.lock index 4ed8ee03bf..97ef19170e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4176,14 +4176,6 @@ resolved "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.1.tgz#155ef21065427901994e765da8a0ba0eaae8b8bd" integrity sha512-6Wci+Tp3CgPt/B9B0a3J4s3yMgLNSku6w5TV6mN+61C71UqsRBv2FUibBf3tPGlNxebgPHMEUzKpb1ggE8KCKw== -"@mswjs/cookies@^0.1.6", "@mswjs/cookies@^0.1.7": - version "0.1.7" - resolved "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.1.7.tgz#d334081b2c51057a61c1dd7b76ca3cac02251651" - integrity sha512-bDg1ReMBx+PYDB4Pk7y1Q07Zz1iKIEUWQpkEXiA2lEWg9gvOZ8UBmGXilCEUvyYoRFlmr/9iXTRR69TrgSwX/Q== - dependencies: - "@types/set-cookie-parser" "^2.4.0" - set-cookie-parser "^2.4.6" - "@mswjs/cookies@^0.2.0": version "0.2.0" resolved "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.2.0.tgz#7ef2b5d7e444498bb27cf57720e61f76a4ce9f23" @@ -4192,18 +4184,6 @@ "@types/set-cookie-parser" "^2.4.0" set-cookie-parser "^2.4.6" -"@mswjs/interceptors@^0.12.6", "@mswjs/interceptors@^0.12.7": - version "0.12.7" - resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.12.7.tgz#0d1cd4cd31a0f663e0455993951201faa09d0909" - integrity sha512-eGjZ3JRAt0Fzi5FgXiV/P3bJGj0NqsN7vBS0J0FO2AQRQ0jCKQS4lEFm4wvlSgKQNfeuc/Vz6d81VtU3Gkx/zg== - dependencies: - "@open-draft/until" "^1.0.3" - "@xmldom/xmldom" "^0.7.2" - debug "^4.3.2" - headers-utils "^3.0.2" - outvariant "^1.2.0" - strict-event-emitter "^0.2.0" - "@mswjs/interceptors@^0.15.1": version "0.15.1" resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.15.1.tgz#4a0009f56e51bc2cd3176f1507065c7d2f6c0d5e" @@ -5975,14 +5955,6 @@ resolved "https://registry.npmjs.org/@types/humanize-duration/-/humanize-duration-3.27.1.tgz#f14740d1f585a0a8e3f46359b62fda8b0eaa31e7" integrity sha512-K3e+NZlpCKd6Bd/EIdqjFJRFHbrq5TzPPLwREk5Iv/YoIjQrs6ljdAUCo+Lb2xFlGNOjGSE0dqsVD19cZL137w== -"@types/inquirer@^7.3.3": - version "7.3.3" - resolved "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.3.tgz#92e6676efb67fa6925c69a2ee638f67a822952ac" - integrity sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ== - dependencies: - "@types/through" "*" - rxjs "^6.4.0" - "@types/inquirer@^8.1.3": version "8.2.1" resolved "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.1.tgz#28a139be3105a1175e205537e8ac10830e38dbf4" @@ -6059,11 +6031,6 @@ resolved "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.6.tgz#f1a1cb35aff47bc5cfb05cb0c441ca91e914c26f" integrity sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw== -"@types/js-levenshtein@^1.1.0": - version "1.1.0" - resolved "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.0.tgz#9541eec4ad6e3ec5633270a3a2b55d981edc44a9" - integrity sha512-14t0v1ICYRtRVcHASzes0v/O+TIeASb8aD55cWF1PidtInhFWSXcmhzhHqGjUWf9SUq1w70cvd1cWKUULubAfQ== - "@types/js-levenshtein@^1.1.1": version "1.1.1" resolved "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.1.tgz#ba05426a43f9e4e30b631941e0aa17bf0c890ed5" @@ -7184,7 +7151,7 @@ "@webassemblyjs/ast" "1.11.1" "@xtuc/long" "4.2.2" -"@xmldom/xmldom@^0.7.0", "@xmldom/xmldom@^0.7.2", "@xmldom/xmldom@^0.7.5": +"@xmldom/xmldom@^0.7.0", "@xmldom/xmldom@^0.7.5": version "0.7.5" resolved "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.7.5.tgz#09fa51e356d07d0be200642b0e4f91d8e6dd408d" integrity sha512-V3BIhmY36fXZ1OtVcI9W+FxQqxVLsPKcNjWigIaa81dLC9IolJl5Mt4Cvhmr0flUnjSpTdrbMTSbXqYqV5dT6A== @@ -9848,7 +9815,7 @@ cookie@0.4.1: resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz#afd713fe26ebd21ba95ceb61f9a8116e50a537d1" integrity sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA== -cookie@0.4.2, cookie@^0.4.1, cookie@^0.4.2, cookie@~0.4.1: +cookie@0.4.2, cookie@^0.4.2, cookie@~0.4.1: version "0.4.2" resolved "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" integrity sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA== @@ -13577,11 +13544,6 @@ graphql-ws@^5.4.1: resolved "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.5.5.tgz#f375486d3f196e2a2527b503644693ae3a8670a9" integrity sha512-hvyIS71vs4Tu/yUYHPvGXsTgo0t3arU820+lT5VjZS2go0ewp2LqyCgxEN56CzOG7Iys52eRhHBiD1gGRdiQtw== -graphql@^15.5.1: - version "15.8.0" - resolved "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38" - integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== - graphql@^16.0.0, graphql@^16.3.0: version "16.5.0" resolved "https://registry.npmjs.org/graphql/-/graphql-16.5.0.tgz#41b5c1182eaac7f3d47164fb247f61e4dfb69c85" @@ -13808,11 +13770,6 @@ headers-polyfill@^3.0.4: resolved "https://registry.npmjs.org/headers-polyfill/-/headers-polyfill-3.0.7.tgz#725c4f591e6748f46b036197eae102c92b959ff4" integrity sha512-JoLCAdCEab58+2/yEmSnOlficyHFpIl0XJqwu3l+Unkm1gXpFUYsThz6Yha3D6tNhocWkCPfyW0YVIGWFqTi7w== -headers-utils@^3.0.2: - version "3.0.2" - resolved "https://registry.npmjs.org/headers-utils/-/headers-utils-3.0.2.tgz#dfc65feae4b0e34357308aefbcafa99c895e59ef" - integrity sha512-xAxZkM1dRyGV2Ou5bzMxBPNLoRCjcX+ya7KSWybQD2KwLphxsapUVK6x/02o7f4VU6GPSXch9vNY2+gkU8tYWQ== - helmet@^5.0.2: version "5.0.2" resolved "https://registry.npmjs.org/helmet/-/helmet-5.0.2.tgz#3264ec6bab96c82deaf65e3403c369424cb2366c" @@ -14392,7 +14349,7 @@ inquirer@^7.3.3: strip-ansi "^6.0.0" through "^2.3.6" -inquirer@^8.0.0, inquirer@^8.1.1, inquirer@^8.2.0: +inquirer@^8.0.0, inquirer@^8.2.0: version "8.2.4" resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.4.tgz#ddbfe86ca2f67649a67daa6f1051c128f684f0b4" integrity sha512-nn4F01dxU8VeKfq192IjLsxu0/OmMZ4Lg3xKAns148rCaXP6ntAoEkVYZThWjwON8AlzdZZi6oqnhNbxUG9hVg== @@ -18177,48 +18134,21 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -msw@^0.35.0: - version "0.35.0" - resolved "https://registry.npmjs.org/msw/-/msw-0.35.0.tgz#18a4ceb6c822ef226a30421d434413bc45030d38" - integrity sha512-V7A6PqaS31F1k//fPS0OnO7vllfaqBUFsMEu3IpYixyWpiUInfyglodnbXhhtDyytkQikpkPZv8TZi/CvZzv/w== +msw@^0.39.2: + version "0.39.2" + resolved "https://registry.npmjs.org/msw/-/msw-0.39.2.tgz#832e9274db62c43cb79854d5a69dce031c700de8" + integrity sha512-ju/HpqQpE4/qCxZ23t5Gaau0KREn4QuFzdG28nP1EpidMrymMJuIvNd32+2uGTGG031PMwrC41YW7vCxHOwyHA== dependencies: - "@mswjs/cookies" "^0.1.6" - "@mswjs/interceptors" "^0.12.6" + "@mswjs/cookies" "^0.2.0" + "@mswjs/interceptors" "^0.15.1" "@open-draft/until" "^1.0.3" "@types/cookie" "^0.4.1" - "@types/inquirer" "^7.3.3" - "@types/js-levenshtein" "^1.1.0" - chalk "^4.1.1" - chokidar "^3.4.2" - cookie "^0.4.1" - graphql "^15.5.1" - headers-utils "^3.0.2" - inquirer "^8.1.1" - is-node-process "^1.0.1" - js-levenshtein "^1.1.6" - node-fetch "^2.6.1" - node-match-path "^0.6.3" - statuses "^2.0.0" - strict-event-emitter "^0.2.0" - type-fest "^1.2.2" - yargs "^17.0.1" - -msw@^0.36.3: - version "0.36.8" - resolved "https://registry.npmjs.org/msw/-/msw-0.36.8.tgz#33ff8bfb0299626a95f43d0e4c3dc2c73c17f1ba" - integrity sha512-K7lOQoYqhGhTSChsmHMQbf/SDCsxh/m0uhN6Ipt206lGoe81fpTmaGD0KLh4jUxCONMOUnwCSj0jtX2CM4pEdw== - dependencies: - "@mswjs/cookies" "^0.1.7" - "@mswjs/interceptors" "^0.12.7" - "@open-draft/until" "^1.0.3" - "@types/cookie" "^0.4.1" - "@types/inquirer" "^8.1.3" - "@types/js-levenshtein" "^1.1.0" + "@types/js-levenshtein" "^1.1.1" chalk "4.1.1" chokidar "^3.4.2" - cookie "^0.4.1" - graphql "^15.5.1" - headers-utils "^3.0.2" + cookie "^0.4.2" + graphql "^16.3.0" + headers-polyfill "^3.0.4" inquirer "^8.2.0" is-node-process "^1.0.1" js-levenshtein "^1.1.6" @@ -18227,12 +18157,12 @@ msw@^0.36.3: statuses "^2.0.0" strict-event-emitter "^0.2.0" type-fest "^1.2.2" - yargs "^17.3.0" + yargs "^17.3.1" -msw@^0.39.2: - version "0.39.2" - resolved "https://registry.npmjs.org/msw/-/msw-0.39.2.tgz#832e9274db62c43cb79854d5a69dce031c700de8" - integrity sha512-ju/HpqQpE4/qCxZ23t5Gaau0KREn4QuFzdG28nP1EpidMrymMJuIvNd32+2uGTGG031PMwrC41YW7vCxHOwyHA== +msw@^0.41.0: + version "0.41.0" + resolved "https://registry.npmjs.org/msw/-/msw-0.41.0.tgz#1508c15bd284da106b9f618030991996825a0924" + integrity sha512-l6AmpuYq96/iiG1KZ4ZPLd8nlDfj7aD0eIbW+4IjfXgqY7lZgYfYJPCULo5hD0eOn0oMg8lZFXHCwrIuLA+PoA== dependencies: "@mswjs/cookies" "^0.2.0" "@mswjs/interceptors" "^0.15.1" @@ -18547,11 +18477,6 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-match-path@^0.6.3: - version "0.6.3" - resolved "https://registry.npmjs.org/node-match-path/-/node-match-path-0.6.3.tgz#55dd8443d547f066937a0752dce462ea7dc27551" - integrity sha512-fB1reOHKLRZCJMAka28hIxCwQLxGmd7WewOCBDYKpyA1KXi68A7vaGgdZAPhY2E6SXoYt3KqYCCvXLJ+O0Fu/Q== - node-modules-regexp@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" @@ -19161,11 +19086,6 @@ outdent@^0.5.0: resolved "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz#9e10982fdc41492bb473ad13840d22f9655be2ff" integrity sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q== -outvariant@^1.2.0: - version "1.2.1" - resolved "https://registry.npmjs.org/outvariant/-/outvariant-1.2.1.tgz#e630f6cdc1dbf398ed857e36f219de4a005ccd35" - integrity sha512-bcILvFkvpMXh66+Ubax/inxbKRyWTUiiFIW2DWkiS79wakrLGn3Ydy+GvukadiyfZjaL6C7YhIem4EZSM282wA== - outvariant@^1.2.1: version "1.3.0" resolved "https://registry.npmjs.org/outvariant/-/outvariant-1.3.0.tgz#c39723b1d2cba729c930b74bf962317a81b9b1c9" @@ -22200,7 +22120,7 @@ rxjs@7.5.5, rxjs@^7.1.0, rxjs@^7.2.0, rxjs@^7.5.1, rxjs@^7.5.5: dependencies: tslib "^2.1.0" -rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.6.0, rxjs@^6.6.3: +rxjs@^6.3.3, rxjs@^6.6.0, rxjs@^6.6.3: version "6.6.7" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== @@ -25815,7 +25735,7 @@ yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.0.0, yargs@^17.0.1, yargs@^17.1.1, yargs@^17.2.1, yargs@^17.3.0: +yargs@^17.0.0, yargs@^17.1.1, yargs@^17.2.1: version "17.3.1" resolved "https://registry.npmjs.org/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9" integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA== From 2bbf763a23a5c91e92aee5d7552f46eae872b2d6 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 30 May 2022 11:03:10 +0200 Subject: [PATCH 02/56] chore: updating yarn.lock to bump to latest version Signed-off-by: blam --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index 97ef19170e..f748204ca3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4185,9 +4185,9 @@ set-cookie-parser "^2.4.6" "@mswjs/interceptors@^0.15.1": - version "0.15.1" - resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.15.1.tgz#4a0009f56e51bc2cd3176f1507065c7d2f6c0d5e" - integrity sha512-D5B+ZJNlfvBm6ZctAfRBdNJdCHYAe2Ix4My5qfbHV5WH+3lkt3mmsjiWJzEh5ZwGDauzY487TldI275If7DJVw== + version "0.15.3" + resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.15.3.tgz#bcd17b5d7558d4f598007a4bb383b42dc9264f8d" + integrity sha512-GJ1qzBq82EQ3bwhsvw5nScbrLzOSI5H/TyB2CGd1K7dDqX58DJDLJHexiN+S5Ucvl6/84FjRdIysz0RxE/L8MA== dependencies: "@open-draft/until" "^1.0.3" "@xmldom/xmldom" "^0.7.5" @@ -18160,9 +18160,9 @@ msw@^0.39.2: yargs "^17.3.1" msw@^0.41.0: - version "0.41.0" - resolved "https://registry.npmjs.org/msw/-/msw-0.41.0.tgz#1508c15bd284da106b9f618030991996825a0924" - integrity sha512-l6AmpuYq96/iiG1KZ4ZPLd8nlDfj7aD0eIbW+4IjfXgqY7lZgYfYJPCULo5hD0eOn0oMg8lZFXHCwrIuLA+PoA== + version "0.41.1" + resolved "https://registry.npmjs.org/msw/-/msw-0.41.1.tgz#215f835d5c2b1f4a8aca2e92fa7608b0086b6891" + integrity sha512-BNz8u1ORjyAXGaFEJ8mDhJr3nVaACSP4WR7V5XU8I2BnkTuAx93+gR13ovtH1gT66TO8rntOSSfRKZkWpSVufg== dependencies: "@mswjs/cookies" "^0.2.0" "@mswjs/interceptors" "^0.15.1" From 6c8474c4a78abf509a5db0a5e90ab79816ca60a2 Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 31 May 2022 09:42:47 +0200 Subject: [PATCH 03/56] chore: bumping the versions Signed-off-by: blam Signed-off-by: blam --- packages/backend-common/package.json | 2 +- packages/backend-test-utils/package.json | 2 +- packages/catalog-client/package.json | 2 +- packages/cli/package.json | 2 +- packages/cli/templates/default-backend-plugin/package.json.hbs | 2 +- packages/cli/templates/default-plugin/package.json.hbs | 2 +- packages/config-loader/package.json | 2 +- packages/core-app-api/package.json | 2 +- packages/core-components/package.json | 2 +- packages/core-plugin-api/package.json | 2 +- packages/integration-react/package.json | 2 +- packages/integration/package.json | 2 +- packages/release-manifests/package.json | 2 +- packages/test-utils/package.json | 2 +- plugins/adr-backend/package.json | 2 +- plugins/adr/package.json | 2 +- plugins/airbrake-backend/package.json | 2 +- plugins/airbrake/package.json | 2 +- plugins/allure/package.json | 2 +- plugins/analytics-module-ga/package.json | 2 +- plugins/apache-airflow/package.json | 2 +- plugins/api-docs/package.json | 2 +- plugins/app-backend/package.json | 2 +- plugins/auth-backend/package.json | 2 +- plugins/auth-node/package.json | 2 +- plugins/azure-devops-backend/package.json | 2 +- plugins/azure-devops/package.json | 2 +- plugins/badges/package.json | 2 +- plugins/bitrise/package.json | 2 +- plugins/catalog-backend-module-azure/package.json | 2 +- plugins/catalog-backend-module-bitbucket/package.json | 2 +- plugins/catalog-backend-module-gerrit/package.json | 2 +- plugins/catalog-backend-module-github/package.json | 2 +- plugins/catalog-backend-module-gitlab/package.json | 2 +- plugins/catalog-backend-module-msgraph/package.json | 2 +- plugins/catalog-backend/package.json | 2 +- plugins/catalog-graphql/package.json | 2 +- plugins/catalog-import/package.json | 2 +- plugins/circleci/package.json | 2 +- plugins/cloudbuild/package.json | 2 +- plugins/code-climate/package.json | 2 +- plugins/code-coverage-backend/package.json | 2 +- plugins/code-coverage/package.json | 2 +- plugins/codescene/package.json | 2 +- plugins/config-schema/package.json | 2 +- plugins/cost-insights/package.json | 2 +- plugins/example-todo-list-backend/package.json | 2 +- plugins/example-todo-list-common/package.json | 2 +- plugins/example-todo-list/package.json | 2 +- plugins/explore-react/package.json | 2 +- plugins/explore/package.json | 2 +- plugins/firehydrant/package.json | 2 +- plugins/fossa/package.json | 2 +- plugins/gcalendar/package.json | 2 +- plugins/gcp-projects/package.json | 2 +- plugins/git-release-manager/package.json | 2 +- plugins/github-actions/package.json | 2 +- plugins/github-deployments/package.json | 2 +- plugins/github-pull-requests-board/package.json | 2 +- plugins/gitops-profiles/package.json | 2 +- plugins/gocd/package.json | 2 +- plugins/graphiql/package.json | 2 +- plugins/graphql-backend/package.json | 2 +- plugins/home/package.json | 2 +- plugins/ilert/package.json | 2 +- plugins/jenkins-backend/package.json | 2 +- plugins/jenkins/package.json | 2 +- plugins/kafka/package.json | 2 +- plugins/kubernetes/package.json | 2 +- plugins/lighthouse/package.json | 2 +- plugins/newrelic/package.json | 2 +- plugins/org/package.json | 2 +- plugins/pagerduty/package.json | 2 +- plugins/periskop-backend/package.json | 2 +- plugins/periskop/package.json | 2 +- plugins/permission-backend/package.json | 2 +- plugins/permission-common/package.json | 2 +- plugins/permission-node/package.json | 2 +- plugins/rollbar-backend/package.json | 2 +- plugins/rollbar/package.json | 2 +- plugins/scaffolder-backend-module-cookiecutter/package.json | 2 +- plugins/scaffolder-backend/package.json | 2 +- plugins/scaffolder/package.json | 2 +- plugins/search/package.json | 2 +- plugins/sentry/package.json | 2 +- plugins/shortcuts/package.json | 2 +- plugins/sonarqube/package.json | 2 +- plugins/splunk-on-call/package.json | 2 +- plugins/stack-overflow/package.json | 2 +- plugins/tech-insights/package.json | 2 +- plugins/tech-radar/package.json | 2 +- plugins/techdocs-addons-test-utils/package.json | 2 +- plugins/techdocs-backend/package.json | 2 +- plugins/techdocs-module-addons-contrib/package.json | 2 +- plugins/techdocs/package.json | 2 +- plugins/todo-backend/package.json | 2 +- plugins/todo/package.json | 2 +- plugins/user-settings/package.json | 2 +- plugins/xcmetrics/package.json | 2 +- 99 files changed, 99 insertions(+), 99 deletions(-) diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index a89e06306d..d3c21e2945 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -112,7 +112,7 @@ "better-sqlite3": "^7.5.0", "http-errors": "^2.0.0", "mock-fs": "^5.1.0", - "msw": "^0.41.0", + "msw": "^0.42.0", "mysql2": "^2.2.5", "recursive-readdir": "^2.2.2", "supertest": "^6.1.3" diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index 23bfc5e13b..9a7f8fc189 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -39,7 +39,7 @@ "@backstage/config": "^1.0.1", "better-sqlite3": "^7.5.0", "knex": "^1.0.2", - "msw": "^0.41.0", + "msw": "^0.42.0", "mysql2": "^2.2.5", "pg": "^8.3.0", "testcontainers": "^8.1.2", diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 974e6e5263..9fe812926e 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -40,7 +40,7 @@ "devDependencies": { "@backstage/cli": "^0.17.2-next.0", "@types/jest": "^26.0.7", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/packages/cli/package.json b/packages/cli/package.json index 647a01c126..76c31ca724 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -152,7 +152,7 @@ "@types/yarnpkg__lockfile": "^1.1.4", "del": "^6.0.0", "mock-fs": "^5.1.0", - "msw": "^0.41.0", + "msw": "^0.42.0", "nodemon": "^2.0.2", "ts-node": "^10.0.0" }, diff --git a/packages/cli/templates/default-backend-plugin/package.json.hbs b/packages/cli/templates/default-backend-plugin/package.json.hbs index dacc1d601a..2416fd75cb 100644 --- a/packages/cli/templates/default-backend-plugin/package.json.hbs +++ b/packages/cli/templates/default-backend-plugin/package.json.hbs @@ -41,7 +41,7 @@ "@backstage/cli": "{{versionQuery '@backstage/cli'}}", "@types/supertest": "{{versionQuery '@types/supertest' '2.0.8'}}", "supertest": "{{versionQuery 'supertest' '4.0.2'}}", - "msw": "{{versionQuery 'msw' '0.35.0'}}" + "msw": "{{versionQuery 'msw' '0.42.0'}}" }, "files": [ "dist" diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index a174c1bafd..34a0548b29 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -49,7 +49,7 @@ "@testing-library/user-event": "{{versionQuery '@testing-library/user-event' '14.0.0'}}", "@types/jest": "{{versionQuery '@types/jest' '26.0.7'}}", "@types/node": "{{versionQuery '@types/node' '16.11.26'}}", - "msw": "{{versionQuery 'msw' '0.35.0'}}", + "msw": "{{versionQuery 'msw' '0.42.0'}}", "cross-fetch": "{{versionQuery 'cross-fetch' '3.1.5'}}" }, "files": [ diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 11582eda1f..818a7b0eb6 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -57,7 +57,7 @@ "@types/node": "^16.11.26", "@types/yup": "^0.29.13", "mock-fs": "^5.1.0", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 354d55f499..38f3f2663d 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -59,7 +59,7 @@ "@types/node": "^16.11.26", "@types/zen-observable": "^0.8.0", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/packages/core-components/package.json b/packages/core-components/package.json index c628ec3730..e09cec6fdf 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -101,7 +101,7 @@ "@types/react-window": "^1.8.5", "@types/zen-observable": "^0.8.0", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index aa4cfbf655..ae220e6e09 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -58,7 +58,7 @@ "@types/prop-types": "^15.7.3", "@types/zen-observable": "^0.8.0", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index 08ea77c022..6f3db876c0 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -46,7 +46,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.41.0", + "msw": "^0.42.0", "cross-fetch": "^3.1.5" }, "files": [ diff --git a/packages/integration/package.json b/packages/integration/package.json index d69d58e7b8..da5a033010 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -48,7 +48,7 @@ "@backstage/test-utils": "^1.1.0", "@types/jest": "^26.0.7", "@types/luxon": "^2.0.4", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/packages/release-manifests/package.json b/packages/release-manifests/package.json index b52f86cc8c..527740c85d 100644 --- a/packages/release-manifests/package.json +++ b/packages/release-manifests/package.json @@ -37,7 +37,7 @@ }, "devDependencies": { "@backstage/test-utils": "^1.1.0", - "msw": "^0.41.0", + "msw": "^0.42.0", "@types/jest": "^26.0.7", "@types/node": "^16.0.0" }, diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index e27af59363..24b446d055 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -58,7 +58,7 @@ "@backstage/cli": "^0.17.2-next.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/adr-backend/package.json b/plugins/adr-backend/package.json index 190dd7fef4..c7a47b69a9 100644 --- a/plugins/adr-backend/package.json +++ b/plugins/adr-backend/package.json @@ -48,7 +48,7 @@ "@types/marked": "^4.0.0", "@types/supertest": "^2.0.8", "supertest": "^6.1.3", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/adr/package.json b/plugins/adr/package.json index f6b3db212a..b645c77daf 100644 --- a/plugins/adr/package.json +++ b/plugins/adr/package.json @@ -55,7 +55,7 @@ "@types/jest": "*", "@types/node": "*", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json index cfa6bc15ca..a87c73799a 100644 --- a/plugins/airbrake-backend/package.json +++ b/plugins/airbrake-backend/package.json @@ -37,7 +37,7 @@ "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index 4dfab9c336..6ced2e37a7 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -51,7 +51,7 @@ "@types/node": "^16.11.26", "@types/object-hash": "^2.2.1", "cross-fetch": "^3.1.5", - "msw": "^0.41.0", + "msw": "^0.42.0", "react-router": "6.0.0-beta.0" }, "files": [ diff --git a/plugins/allure/package.json b/plugins/allure/package.json index c6257144a2..668912cb04 100644 --- a/plugins/allure/package.json +++ b/plugins/allure/package.json @@ -50,7 +50,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index a69c355840..0dbc3fd70f 100644 --- a/plugins/analytics-module-ga/package.json +++ b/plugins/analytics-module-ga/package.json @@ -48,7 +48,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json index 45e5c4a13e..e22796fdd0 100644 --- a/plugins/apache-airflow/package.json +++ b/plugins/apache-airflow/package.json @@ -45,7 +45,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 6cdc6e668f..690926d2bd 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -68,7 +68,7 @@ "@types/node": "^16.11.26", "@types/swagger-ui-react": "^4.1.1", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index a56efaa5b6..abce3493d2 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -55,7 +55,7 @@ "@backstage/types": "^1.0.0", "@types/supertest": "^2.0.8", "mock-fs": "^5.1.0", - "msw": "^0.41.0", + "msw": "^0.42.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 5a3a1bd996..16d85acfa3 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -88,7 +88,7 @@ "@types/passport-saml": "^1.1.3", "@types/passport-strategy": "^0.2.35", "@types/xml2js": "^0.4.7", - "msw": "^0.41.0", + "msw": "^0.42.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index c03f2d46df..f8dd6af0b3 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -33,7 +33,7 @@ "devDependencies": { "@backstage/cli": "^0.17.2-next.0", "lodash": "^4.17.21", - "msw": "^0.41.0", + "msw": "^0.42.0", "uuid": "^8.0.0" }, "files": [ diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index ec14384b47..8d63228430 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -38,7 +38,7 @@ "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index f0465828da..aa0544d120 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -59,7 +59,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/badges/package.json b/plugins/badges/package.json index a51867c113..a0a0320fbd 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -56,7 +56,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 92e8013342..15d4e40ab2 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -53,7 +53,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "@types/recharts": "^1.8.15", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index ae3b264155..fb2e30d875 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -41,7 +41,7 @@ "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@backstage/types": "^1.0.0", "lodash": "^4.17.21", - "msw": "^0.41.0", + "msw": "^0.42.0", "node-fetch": "^2.6.7", "winston": "^3.2.1" }, diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json index 47a1a81954..d313555ab3 100644 --- a/plugins/catalog-backend-module-bitbucket/package.json +++ b/plugins/catalog-backend-module-bitbucket/package.json @@ -42,7 +42,7 @@ "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@backstage/types": "^1.0.0", "lodash": "^4.17.21", - "msw": "^0.41.0", + "msw": "^0.42.0", "node-fetch": "^2.6.7", "winston": "^3.2.1" }, diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 67ba042c76..354aae55fa 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -36,7 +36,7 @@ "@backstage/integration": "^1.2.1-next.0", "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "fs-extra": "10.1.0", - "msw": "^0.41.0", + "msw": "^0.42.0", "node-fetch": "^2.6.7", "uuid": "^8.0.0", "winston": "^3.2.1" diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 7d48dc07e7..224e86ab07 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -43,7 +43,7 @@ "@backstage/types": "^1.0.0", "@octokit/graphql": "^4.5.8", "lodash": "^4.17.21", - "msw": "^0.41.0", + "msw": "^0.42.0", "node-fetch": "^2.6.7", "uuid": "^8.0.0", "winston": "^3.2.1" diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 94991d6dd3..1aa8f4dc06 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -41,7 +41,7 @@ "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@backstage/types": "^1.0.0", "lodash": "^4.17.21", - "msw": "^0.41.0", + "msw": "^0.42.0", "node-fetch": "^2.6.7", "winston": "^3.2.1" }, diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 86445a6e84..a7c737b3fe 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -52,7 +52,7 @@ "@backstage/backend-test-utils": "^0.1.25-next.0", "@backstage/cli": "^0.17.2-next.0", "@types/lodash": "^4.14.151", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 6ae605ab6f..33a345734a 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -78,7 +78,7 @@ "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", "better-sqlite3": "^7.5.0", - "msw": "^0.41.0", + "msw": "^0.42.0", "supertest": "^6.1.3", "wait-for-expect": "^3.0.2", "luxon": "^2.0.2" diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index cb878f3487..e5ea11ebff 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -53,7 +53,7 @@ "@graphql-codegen/typescript": "^2.4.2", "@graphql-codegen/typescript-resolvers": "^2.4.3", "@graphql-tools/schema": "^8.3.1", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index f7dcb5cd32..02cee7f016 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -70,7 +70,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 9d9eac38ec..3fa3812939 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -66,7 +66,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 906e4f423e..0c7b4caf4a 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -62,7 +62,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/code-climate/package.json b/plugins/code-climate/package.json index c2314ae029..8053169b1b 100644 --- a/plugins/code-climate/package.json +++ b/plugins/code-climate/package.json @@ -51,7 +51,7 @@ "@types/luxon": "^2.0.9", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index 34b75ed91c..797861d948 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -42,7 +42,7 @@ "@backstage/cli": "^0.17.2-next.0", "@types/express-xml-bodyparser": "^0.3.2", "@types/supertest": "^2.0.8", - "msw": "^0.41.0", + "msw": "^0.42.0", "supertest": "^6.1.6", "xml2js": "^0.4.23" }, diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index 62680ecfce..98a2b152d2 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -58,7 +58,7 @@ "@types/node": "^16.11.26", "@types/recharts": "^1.8.15", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/codescene/package.json b/plugins/codescene/package.json index 6be112f2f8..a57d96b469 100644 --- a/plugins/codescene/package.json +++ b/plugins/codescene/package.json @@ -47,7 +47,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "*", "@types/node": "*", - "msw": "^0.41.0", + "msw": "^0.42.0", "cross-fetch": "^3.1.5" }, "files": [ diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 5829cde044..2662a52cde 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -51,7 +51,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index 53ef8b4b43..b6402ff9fc 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -75,7 +75,7 @@ "@types/yup": "^0.29.13", "canvas": "^2.6.1", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 9a0d8addba..b32dbf48c4 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -36,7 +36,7 @@ "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", - "msw": "^0.41.0", + "msw": "^0.42.0", "supertest": "^6.1.6" }, "files": [ diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index 7cadd61ae9..2ad8e71eb0 100644 --- a/plugins/example-todo-list-common/package.json +++ b/plugins/example-todo-list-common/package.json @@ -31,7 +31,7 @@ "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@types/node": "^16.11.26", - "msw": "^0.41.0", + "msw": "^0.42.0", "cross-fetch": "^3.1.5" }, "files": [ diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index c63e3a500c..6781312fd4 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -42,7 +42,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index a1651a9d12..16f56cb853 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -45,7 +45,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/explore/package.json b/plugins/explore/package.json index cb867c42c7..8fad2b98ab 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -63,7 +63,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json index a6e85de484..d290c6fa83 100644 --- a/plugins/firehydrant/package.json +++ b/plugins/firehydrant/package.json @@ -49,7 +49,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 9de5c75b34..11a0503055 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -62,7 +62,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/gcalendar/package.json b/plugins/gcalendar/package.json index 74d1f4bcc3..6b77165b7a 100644 --- a/plugins/gcalendar/package.json +++ b/plugins/gcalendar/package.json @@ -55,7 +55,7 @@ "@types/node": "^16.11.26", "@types/sanitize-html": "^2.6.2", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index ca1b197381..409746e4d4 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -57,7 +57,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 2e29c29c09..163cf91da9 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -55,7 +55,7 @@ "@types/node": "^16.11.26", "@types/recharts": "^1.8.15", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index f22eff2dd9..0b26f90bf5 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -65,7 +65,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index 76ba61d300..c78fa62971 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -53,7 +53,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json index de10efbdbc..f9ef5ef854 100644 --- a/plugins/github-pull-requests-board/package.json +++ b/plugins/github-pull-requests-board/package.json @@ -58,7 +58,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "peerDependencies": { "@types/react": "^16.13.1 || ^17.0.0", diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 6dea00969d..a55c7afa05 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -58,7 +58,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index c97ace1b6b..b7f56267d2 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -61,7 +61,7 @@ "@types/luxon": "^2.0.4", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index c18a815032..2637e86d49 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -60,7 +60,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0", + "msw": "^0.42.0", "react-router-dom": "6.0.0-beta.0" }, "files": [ diff --git a/plugins/graphql-backend/package.json b/plugins/graphql-backend/package.json index b88198acc3..723900e86f 100644 --- a/plugins/graphql-backend/package.json +++ b/plugins/graphql-backend/package.json @@ -53,7 +53,7 @@ "devDependencies": { "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", - "msw": "^0.41.0", + "msw": "^0.42.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/home/package.json b/plugins/home/package.json index 045e79222b..05fa728b13 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -63,7 +63,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index 100b8f2e64..cb3cd7e77b 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -53,7 +53,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index 1801b60c2e..8f51cf6217 100644 --- a/plugins/jenkins-backend/package.json +++ b/plugins/jenkins-backend/package.json @@ -44,7 +44,7 @@ "@backstage/cli": "^0.17.2-next.0", "@types/jenkins": "^0.23.1", "@types/supertest": "^2.0.8", - "msw": "^0.41.0", + "msw": "^0.42.0", "supertest": "^6.1.6" }, "files": [ diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index 9af135d7c3..9d7eceb156 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -65,7 +65,7 @@ "@types/node": "^16.11.26", "@types/testing-library__jest-dom": "^5.9.1", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 3c77eba248..63d153e409 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -51,7 +51,7 @@ "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", "jest-when": "^3.1.0", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 2c010c8424..014528dacd 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -68,7 +68,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 13bcc8a209..59858282a7 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -63,7 +63,7 @@ "@types/node": "^16.11.26", "@types/react": "^16.13.1 || ^17.0.0", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 16e855e16b..9e66da99be 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -57,7 +57,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/org/package.json b/plugins/org/package.json index 8acfed7b33..6389854bf1 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -59,7 +59,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 6179b71345..45f022acca 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -62,7 +62,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/periskop-backend/package.json b/plugins/periskop-backend/package.json index 0a95432aa9..4cfda542ac 100644 --- a/plugins/periskop-backend/package.json +++ b/plugins/periskop-backend/package.json @@ -37,7 +37,7 @@ "devDependencies": { "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", - "msw": "^0.41.0", + "msw": "^0.42.0", "supertest": "^6.1.6" }, "files": [ diff --git a/plugins/periskop/package.json b/plugins/periskop/package.json index df8cd557d2..329164ddf0 100644 --- a/plugins/periskop/package.json +++ b/plugins/periskop/package.json @@ -53,7 +53,7 @@ "@types/luxon": "^2.0.4", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index e6dc551424..fa43e6a263 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -43,7 +43,7 @@ "@types/lodash": "^4.14.151", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index 8ce0ad28e7..cecdd2e03a 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -50,6 +50,6 @@ "devDependencies": { "@backstage/cli": "^0.17.2-next.0", "@types/jest": "^26.0.7", - "msw": "^0.41.0" + "msw": "^0.42.0" } } diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 1a011751c4..1137b7859b 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -46,7 +46,7 @@ "devDependencies": { "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", - "msw": "^0.41.0", + "msw": "^0.42.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 18d804fb4f..5e9d6f5382 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -53,7 +53,7 @@ "@backstage/backend-test-utils": "^0.1.25-next.0", "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", - "msw": "^0.41.0", + "msw": "^0.42.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 36aec8376a..38a344e759 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -65,7 +65,7 @@ "@types/node": "^16.11.26", "@types/react": "^16.13.1 || ^17.0.0", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index c56262d94c..16271f1046 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -41,7 +41,7 @@ "@types/jest": "^26.0.7", "@types/command-exists": "^1.2.0", "mock-fs": "^5.1.0", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 32fa9dd91d..4fed67cc54 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -86,7 +86,7 @@ "esbuild": "^0.14.1", "jest-when": "^3.1.0", "mock-fs": "^5.1.0", - "msw": "^0.41.0", + "msw": "^0.42.0", "supertest": "^6.1.3", "yaml": "^1.10.0" }, diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 287ff99336..91f52d9df0 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -93,7 +93,7 @@ "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", "event-source-polyfill": "1.0.25", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/search/package.json b/plugins/search/package.json index 5d10055f75..57c8cacfa9 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -68,7 +68,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index ab3264d8f7..fecf38c165 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -65,7 +65,7 @@ "@types/node": "^16.11.26", "@types/react": "^16.13.1 || ^17.0.0", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index 67bac21bd0..6b007c4261 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -52,7 +52,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index c265f51942..b6640c2551 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -62,7 +62,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 12b09308bc..79d6147407 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -62,7 +62,7 @@ "@types/luxon": "^2.0.4", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "configSchema": "config.d.ts", "files": [ diff --git a/plugins/stack-overflow/package.json b/plugins/stack-overflow/package.json index db5d605c27..ce500e7886 100644 --- a/plugins/stack-overflow/package.json +++ b/plugins/stack-overflow/package.json @@ -50,7 +50,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/tech-insights/package.json b/plugins/tech-insights/package.json index 5af19b162b..da889a6a79 100644 --- a/plugins/tech-insights/package.json +++ b/plugins/tech-insights/package.json @@ -57,7 +57,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 9ca173c3df..050f9a9877 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -62,7 +62,7 @@ "@types/node": "^16.11.26", "@types/react": "^16.13.1 || ^17.0.0", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index 9a56db3972..26449881a0 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -63,7 +63,7 @@ "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", - "msw": "^0.41.0", + "msw": "^0.42.0", "cross-fetch": "^3.1.5" }, "files": [ diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 9f7d9ab33b..d3563f5165 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -60,7 +60,7 @@ "@backstage/cli": "^0.17.2-next.0", "@backstage/plugin-search-backend-node": "0.6.2-next.0", "@types/dockerode": "^3.3.0", - "msw": "^0.41.0", + "msw": "^0.42.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index f064e35612..28eb02c0ca 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -62,7 +62,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "@types/react": "^16.13.1 || ^17.0.0", - "msw": "^0.41.0", + "msw": "^0.42.0", "cross-fetch": "^3.1.5" }, "files": [ diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index a09a527975..368fcb538e 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -81,7 +81,7 @@ "@types/node": "^16.11.26", "canvas": "^2.6.1", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist", diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index b5d98ef781..0120940fe0 100644 --- a/plugins/todo-backend/package.json +++ b/plugins/todo-backend/package.json @@ -45,7 +45,7 @@ "devDependencies": { "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", - "msw": "^0.41.0", + "msw": "^0.42.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/todo/package.json b/plugins/todo/package.json index 377dc236ce..f5a0c512b5 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -55,7 +55,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0", + "msw": "^0.42.0", "react-router": "6.0.0-beta.0" }, "files": [ diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index b4146ed6ad..acc3c211ef 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -58,7 +58,7 @@ "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index a070e2b18c..f5fe1e0f2a 100644 --- a/plugins/xcmetrics/package.json +++ b/plugins/xcmetrics/package.json @@ -51,7 +51,7 @@ "@types/luxon": "^2.0.4", "@types/node": "^16.11.26", "cross-fetch": "^3.1.5", - "msw": "^0.41.0" + "msw": "^0.42.0" }, "files": [ "dist" From 9b42562a1b64777fd98cbadaf42743f08c1b672d Mon Sep 17 00:00:00 2001 From: blam Date: Tue, 31 May 2022 10:24:32 +0200 Subject: [PATCH 04/56] chore: fix two problematic tests Signed-off-by: blam --- .../release-manifests/src/manifest.test.ts | 120 +++++---- .../search/DefaultTechDocsCollator.test.ts | 255 +++++++++--------- yarn.lock | 32 ++- 3 files changed, 215 insertions(+), 192 deletions(-) diff --git a/packages/release-manifests/src/manifest.test.ts b/packages/release-manifests/src/manifest.test.ts index 70894e5431..272555df70 100644 --- a/packages/release-manifests/src/manifest.test.ts +++ b/packages/release-manifests/src/manifest.test.ts @@ -14,73 +14,75 @@ * limitations under the License. */ -import { setupRequestMockHandlers } from '@backstage/test-utils'; -import { getManifestByReleaseLine, getManifestByVersion } from './manifest'; import { setupServer } from 'msw/node'; import { rest } from 'msw'; +import { setupRequestMockHandlers } from '@backstage/test-utils'; +import { getManifestByReleaseLine, getManifestByVersion } from './manifest'; -describe('getManifestByVersion', () => { +describe('Release Manifests', () => { const worker = setupServer(); setupRequestMockHandlers(worker); - it('should return a list of packages in a release', async () => { - worker.use( - rest.get('*/v1/releases/0.0.0/manifest.json', (_, res, ctx) => - res( - ctx.status(200), - ctx.json({ - packages: [{ name: '@backstage/core', version: '1.2.3' }], - }), + describe('getManifestByVersion', () => { + it('should return a list of packages in a release', async () => { + worker.use( + rest.get('*/v1/releases/0.0.0/manifest.json', (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + packages: [{ name: '@backstage/core', version: '1.2.3' }], + }), + ), ), - ), - rest.get('*/v1/releases/999.0.1/manifest.json', (_, res, ctx) => - res(ctx.status(404), ctx.json({})), - ), - ); + rest.get('*/v1/releases/999.0.1/manifest.json', (_, res, ctx) => + res(ctx.status(404), ctx.json({})), + ), + ); - const pkgs = await getManifestByVersion({ version: '0.0.0' }); - expect(pkgs.packages).toEqual([ - { - name: '@backstage/core', - version: '1.2.3', - }, - ]); + const pkgs = await getManifestByVersion({ version: '0.0.0' }); + expect(pkgs.packages).toEqual([ + { + name: '@backstage/core', + version: '1.2.3', + }, + ]); - await expect(getManifestByVersion({ version: '999.0.1' })).rejects.toThrow( - 'No release found for 999.0.1 version', - ); - }); -}); - -describe('getManifestByReleaseLine', () => { - const worker = setupServer(); - setupRequestMockHandlers(worker); - - it('should return a list of packages in a release', async () => { - worker.use( - rest.get('*/v1/tags/main/manifest.json', (_, res, ctx) => - res( - ctx.status(200), - ctx.json({ - packages: [{ name: '@backstage/core', version: '1.2.3' }], - }), - ), - ), - rest.get('*/v1/tags/foo/manifest.json', (_, res, ctx) => - res(ctx.status(404), ctx.json({})), - ), - ); - - const pkgs = await getManifestByReleaseLine({ releaseLine: 'main' }); - expect(pkgs.packages).toEqual([ - { - name: '@backstage/core', - version: '1.2.3', - }, - ]); - - await expect( - getManifestByReleaseLine({ releaseLine: 'foo' }), - ).rejects.toThrow("No 'foo' release line found"); + await expect( + getManifestByVersion({ version: '999.0.1' }), + ).rejects.toThrow('No release found for 999.0.1 version'); + }); + }); + + describe('getManifestByReleaseLine', () => { + it('should return a list of packages in a release', async () => { + worker.use( + rest.get( + 'https://versions.backstage.io/v1/tags/main/manifest.json', + (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + packages: [{ name: '@backstage/core', version: '1.2.3' }], + }), + ), + ), + rest.get( + 'https://versions.backstage.io/v1/tags/foo/manifest.json', + (_, res, ctx) => res(ctx.status(404), ctx.json({})), + ), + ); + + const pkgs = await getManifestByReleaseLine({ releaseLine: 'main' }); + expect(pkgs.packages).toEqual([ + { + name: '@backstage/core', + version: '1.2.3', + }, + ]); + + await expect( + getManifestByReleaseLine({ releaseLine: 'foo' }), + ).rejects.toThrow("No 'foo' release line found"); + }); }); }); diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts index 684d5729a2..3f1a857c4d 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.test.ts @@ -74,144 +74,145 @@ const expectedEntities: Entity[] = [ }, ]; -describe('DefaultTechDocsCollator with legacyPathCasing configuration', () => { - let mockDiscoveryApi: jest.Mocked; - let mockTokenManager: jest.Mocked; - let collator: DefaultTechDocsCollator; - +describe('TechDocs Collator', () => { const worker = setupServer(); setupRequestMockHandlers(worker); - beforeEach(() => { - mockDiscoveryApi = { - getBaseUrl: jest.fn().mockResolvedValue('http://test-backend'), - getExternalBaseUrl: jest.fn(), - }; - mockTokenManager = { - getToken: jest.fn().mockResolvedValue({ token: '' }), - authenticate: jest.fn(), - }; - const mockConfig = new ConfigReader({ - techdocs: { - legacyUseCaseSensitiveTripletPaths: true, - }, - }); - collator = DefaultTechDocsCollator.fromConfig(mockConfig, { - discovery: mockDiscoveryApi, - tokenManager: mockTokenManager, - logger, - legacyPathCasing: true, - }); - worker.use( - rest.get( - 'http://test-backend/static/docs/default/Component/test-entity-with-docs/search/search_index.json', - (_, res, ctx) => res(ctx.status(200), ctx.json(mockSearchDocIndex)), - ), - rest.get('http://test-backend/entities', (_, res, ctx) => - res(ctx.status(200), ctx.json(expectedEntities)), - ), - ); - }); + describe('DefaultTechDocsCollator with legacyPathCasing configuration', () => { + let mockDiscoveryApi: jest.Mocked; + let mockTokenManager: jest.Mocked; + let collator: DefaultTechDocsCollator; - it('fetches from the configured catalog and tech docs services', async () => { - const documents = await collator.execute(); - expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('catalog'); - expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('techdocs'); - expect(documents).toHaveLength(mockSearchDocIndex.docs.length); - }); - - it('should create documents for each tech docs search index', async () => { - const documents = await collator.execute(); - const entity = expectedEntities[0]; - documents.forEach((document, idx) => { - expect(document).toMatchObject({ - title: mockSearchDocIndex.docs[idx].title, - location: `/docs/default/Component/${entity.metadata.name}/${mockSearchDocIndex.docs[idx].location}`, - text: mockSearchDocIndex.docs[idx].text, - namespace: 'default', - entityTitle: entity!.metadata.title, - componentType: entity!.spec!.type, - lifecycle: entity!.spec!.lifecycle, - owner: '', - kind: entity.kind, - name: entity.metadata.name, - }); - }); - }); -}); - -describe('DefaultTechDocsCollator', () => { - let mockDiscoveryApi: jest.Mocked; - let mockTokenManager: jest.Mocked; - let collator: DefaultTechDocsCollator; - - const worker = setupServer(); - setupRequestMockHandlers(worker); - beforeEach(() => { - mockDiscoveryApi = { - getBaseUrl: jest.fn().mockResolvedValue('http://test-backend'), - getExternalBaseUrl: jest.fn(), - }; - mockTokenManager = { - getToken: jest.fn().mockResolvedValue({ token: '' }), - authenticate: jest.fn(), - }; - collator = DefaultTechDocsCollator.fromConfig(new ConfigReader({}), { - discovery: mockDiscoveryApi, - tokenManager: mockTokenManager, - logger, - }); - - worker.use( - rest.get( - 'http://test-backend/static/docs/default/component/test-entity-with-docs/search/search_index.json', - (_, res, ctx) => res(ctx.status(200), ctx.json(mockSearchDocIndex)), - ), - rest.get('http://test-backend/entities', (_, res, ctx) => - res(ctx.status(200), ctx.json(expectedEntities)), - ), - ); - }); - - it('should create documents for each tech docs search index', async () => { - const documents = await collator.execute(); - const entity = expectedEntities[0]; - documents.forEach((document, idx) => { - expect(document).toMatchObject({ - title: mockSearchDocIndex.docs[idx].title, - location: `/docs/default/component/${entity.metadata.name}/${mockSearchDocIndex.docs[idx].location}`, - text: mockSearchDocIndex.docs[idx].text, - namespace: 'default', - entityTitle: entity!.metadata.title, - componentType: entity!.spec!.type, - lifecycle: entity!.spec!.lifecycle, - owner: '', - kind: entity.kind.toLocaleLowerCase('en-US'), - name: entity.metadata.name.toLocaleLowerCase('en-US'), - authorization: { - resourceRef: `component:default/${entity.metadata.name}`, + beforeEach(() => { + mockDiscoveryApi = { + getBaseUrl: jest.fn().mockResolvedValue('http://test-backend'), + getExternalBaseUrl: jest.fn(), + }; + mockTokenManager = { + getToken: jest.fn().mockResolvedValue({ token: '' }), + authenticate: jest.fn(), + }; + const mockConfig = new ConfigReader({ + techdocs: { + legacyUseCaseSensitiveTripletPaths: true, }, }); + collator = DefaultTechDocsCollator.fromConfig(mockConfig, { + discovery: mockDiscoveryApi, + tokenManager: mockTokenManager, + logger, + legacyPathCasing: true, + }); + + worker.use( + rest.get( + 'http://test-backend/static/docs/default/Component/test-entity-with-docs/search/search_index.json', + (_, res, ctx) => res(ctx.status(200), ctx.json(mockSearchDocIndex)), + ), + rest.get('http://test-backend/entities', (_, res, ctx) => + res(ctx.status(200), ctx.json(expectedEntities)), + ), + ); + }); + + it('fetches from the configured catalog and tech docs services', async () => { + const documents = await collator.execute(); + expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('catalog'); + expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('techdocs'); + expect(documents).toHaveLength(mockSearchDocIndex.docs.length); + }); + + it('should create documents for each tech docs search index', async () => { + const documents = await collator.execute(); + const entity = expectedEntities[0]; + documents.forEach((document, idx) => { + expect(document).toMatchObject({ + title: mockSearchDocIndex.docs[idx].title, + location: `/docs/default/Component/${entity.metadata.name}/${mockSearchDocIndex.docs[idx].location}`, + text: mockSearchDocIndex.docs[idx].text, + namespace: 'default', + entityTitle: entity!.metadata.title, + componentType: entity!.spec!.type, + lifecycle: entity!.spec!.lifecycle, + owner: '', + kind: entity.kind, + name: entity.metadata.name, + }); + }); }); }); - it('maps a returned entity with a custom locationTemplate', async () => { - const mockConfig = new ConfigReader({ - techdocs: { - legacyUseCaseSensitiveTripletPaths: true, - }, - }); - // Provide an alternate location template. - collator = DefaultTechDocsCollator.fromConfig(mockConfig, { - discovery: mockDiscoveryApi, - tokenManager: mockTokenManager, - locationTemplate: '/software/:name', - logger, + describe('DefaultTechDocsCollator', () => { + let mockDiscoveryApi: jest.Mocked; + let mockTokenManager: jest.Mocked; + let collator: DefaultTechDocsCollator; + + beforeEach(() => { + mockDiscoveryApi = { + getBaseUrl: jest.fn().mockResolvedValue('http://test-backend'), + getExternalBaseUrl: jest.fn(), + }; + mockTokenManager = { + getToken: jest.fn().mockResolvedValue({ token: '' }), + authenticate: jest.fn(), + }; + collator = DefaultTechDocsCollator.fromConfig(new ConfigReader({}), { + discovery: mockDiscoveryApi, + tokenManager: mockTokenManager, + logger, + }); + + worker.use( + rest.get( + 'http://test-backend/static/docs/default/component/test-entity-with-docs/search/search_index.json', + (_, res, ctx) => res(ctx.status(200), ctx.json(mockSearchDocIndex)), + ), + rest.get('http://test-backend/entities', (_, res, ctx) => + res(ctx.status(200), ctx.json(expectedEntities)), + ), + ); }); - const documents = await collator.execute(); - expect(documents[0]).toMatchObject({ - location: '/software/test-entity-with-docs', + it('should create documents for each tech docs search index', async () => { + const documents = await collator.execute(); + const entity = expectedEntities[0]; + documents.forEach((document, idx) => { + expect(document).toMatchObject({ + title: mockSearchDocIndex.docs[idx].title, + location: `/docs/default/component/${entity.metadata.name}/${mockSearchDocIndex.docs[idx].location}`, + text: mockSearchDocIndex.docs[idx].text, + namespace: 'default', + entityTitle: entity!.metadata.title, + componentType: entity!.spec!.type, + lifecycle: entity!.spec!.lifecycle, + owner: '', + kind: entity.kind.toLocaleLowerCase('en-US'), + name: entity.metadata.name.toLocaleLowerCase('en-US'), + authorization: { + resourceRef: `component:default/${entity.metadata.name}`, + }, + }); + }); + }); + + it('maps a returned entity with a custom locationTemplate', async () => { + const mockConfig = new ConfigReader({ + techdocs: { + legacyUseCaseSensitiveTripletPaths: true, + }, + }); + // Provide an alternate location template. + collator = DefaultTechDocsCollator.fromConfig(mockConfig, { + discovery: mockDiscoveryApi, + tokenManager: mockTokenManager, + locationTemplate: '/software/:name', + logger, + }); + + const documents = await collator.execute(); + expect(documents[0]).toMatchObject({ + location: '/software/test-entity-with-docs', + }); }); }); }); diff --git a/yarn.lock b/yarn.lock index f748204ca3..aeac084923 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4196,6 +4196,18 @@ outvariant "^1.2.1" strict-event-emitter "^0.2.0" +"@mswjs/interceptors@^0.16.3": + version "0.16.4" + resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.16.4.tgz#fa41de240dd094e6ad19e3a6829651589551d159" + integrity sha512-McPKUFlZNS/wo+OAor15k0fv2skK+EdWl9CEcdxAqsN4vKajlxCxDU4B5W/pn1y0TJPSAOmxR6LYFe/8esePrg== + dependencies: + "@open-draft/until" "^1.0.3" + "@xmldom/xmldom" "^0.7.5" + debug "^4.3.3" + headers-polyfill "^3.0.4" + outvariant "^1.2.1" + strict-event-emitter "^0.2.4" + "@n1ru4l/graphql-live-query@^0.9.0": version "0.9.0" resolved "https://registry.npmjs.org/@n1ru4l/graphql-live-query/-/graphql-live-query-0.9.0.tgz#defaebdd31f625bee49e6745934f36312532b2bc" @@ -18159,13 +18171,13 @@ msw@^0.39.2: type-fest "^1.2.2" yargs "^17.3.1" -msw@^0.41.0: - version "0.41.1" - resolved "https://registry.npmjs.org/msw/-/msw-0.41.1.tgz#215f835d5c2b1f4a8aca2e92fa7608b0086b6891" - integrity sha512-BNz8u1ORjyAXGaFEJ8mDhJr3nVaACSP4WR7V5XU8I2BnkTuAx93+gR13ovtH1gT66TO8rntOSSfRKZkWpSVufg== +msw@^0.42.0: + version "0.42.0" + resolved "https://registry.npmjs.org/msw/-/msw-0.42.0.tgz#2286aefad82808888914e2bc5e40666e82b2824b" + integrity sha512-vB9rzgiGHoQGfkKpp3QZHxobzfuuQOJk+0bff0wtbK8k3P3CaUSt8bCwvExours682AY4mUfTjIkCsxy0JoS3w== dependencies: "@mswjs/cookies" "^0.2.0" - "@mswjs/interceptors" "^0.15.1" + "@mswjs/interceptors" "^0.16.3" "@open-draft/until" "^1.0.3" "@types/cookie" "^0.4.1" "@types/js-levenshtein" "^1.1.1" @@ -18178,6 +18190,7 @@ msw@^0.41.0: is-node-process "^1.0.1" js-levenshtein "^1.1.6" node-fetch "^2.6.7" + outvariant "^1.3.0" path-to-regexp "^6.2.0" statuses "^2.0.0" strict-event-emitter "^0.2.0" @@ -19086,7 +19099,7 @@ outdent@^0.5.0: resolved "https://registry.npmjs.org/outdent/-/outdent-0.5.0.tgz#9e10982fdc41492bb473ad13840d22f9655be2ff" integrity sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q== -outvariant@^1.2.1: +outvariant@^1.2.1, outvariant@^1.3.0: version "1.3.0" resolved "https://registry.npmjs.org/outvariant/-/outvariant-1.3.0.tgz#c39723b1d2cba729c930b74bf962317a81b9b1c9" integrity sha512-yeWM9k6UPfG/nzxdaPlJkB2p08hCg4xP6Lx99F+vP8YF7xyZVfTmJjrrNalkmzudD4WFvNLVudQikqUmF8zhVQ== @@ -23176,6 +23189,13 @@ strict-event-emitter@^0.2.0: dependencies: events "^3.3.0" +strict-event-emitter@^0.2.4: + version "0.2.4" + resolved "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.4.tgz#365714f0c95f059db31064ca745d5b33e5b30f6e" + integrity sha512-xIqTLS5azUH1djSUsLH9DbP6UnM/nI18vu8d43JigCQEoVsnY+mrlE+qv6kYqs6/1OkMnMIiL6ffedQSZStuoQ== + dependencies: + events "^3.3.0" + strict-uri-encode@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" From 91ae2f46d29e8d10b953c996d5a7b8293fff27d7 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 4 Apr 2022 10:09:05 +0200 Subject: [PATCH 05/56] scaffolder-backend: add directory serialization utilities Signed-off-by: Patrik Oldsberg --- plugins/scaffolder-backend/package.json | 1 + .../deserializeDirectoryContents.test.ts | 81 ++++++++ .../lib/files/deserializeDirectoryContents.ts | 39 ++++ .../scaffolder-backend/src/lib/files/index.ts | 19 ++ .../files/serializeDirectoryContents.test.ts | 179 ++++++++++++++++++ .../lib/files/serializeDirectoryContents.ts | 62 ++++++ .../scaffolder-backend/src/lib/files/types.ts | 21 ++ 7 files changed, 402 insertions(+) create mode 100644 plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.test.ts create mode 100644 plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.ts create mode 100644 plugins/scaffolder-backend/src/lib/files/index.ts create mode 100644 plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts create mode 100644 plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.ts create mode 100644 plugins/scaffolder-backend/src/lib/files/types.ts diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 4fed67cc54..528d84a299 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -67,6 +67,7 @@ "nunjucks": "^3.2.3", "octokit": "^1.7.1", "octokit-plugin-create-pull-request": "^3.10.0", + "p-limit": "^3.1.0", "uuid": "^8.2.0", "winston": "^3.2.1", "yaml": "^1.10.0", diff --git a/plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.test.ts b/plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.test.ts new file mode 100644 index 0000000000..0e7de7f88d --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.test.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 mockFs from 'mock-fs'; +import { deserializeDirectoryContents } from './deserializeDirectoryContents'; +import { serializeDirectoryContents } from './serializeDirectoryContents'; + +describe('deserializeDirectoryContents', () => { + beforeEach(() => { + mockFs({ + root: {}, + }); + }); + + afterEach(() => { + mockFs.restore(); + }); + + it('deserializes contents into a directory', async () => { + await deserializeDirectoryContents('root', [ + { + path: 'a.txt', + content: Buffer.from('a', 'utf8'), + }, + ]); + await expect(serializeDirectoryContents('root')).resolves.toEqual([ + { + path: 'a.txt', + content: Buffer.from('a', 'utf8'), + executable: false, + }, + ]); + }); + + it('deserializes contents into a deep directory structure', async () => { + await deserializeDirectoryContents('root', [ + { + path: 'a.txt', + content: Buffer.from('a', 'utf8'), + }, + { + path: 'a/b.txt', + content: Buffer.from('b', 'utf8'), + }, + { + path: 'a/b/c.txt', + content: Buffer.from('c', 'utf8'), + }, + ]); + await expect(serializeDirectoryContents('root')).resolves.toEqual([ + { + path: 'a.txt', + content: Buffer.from('a', 'utf8'), + executable: false, + }, + { + path: 'a/b.txt', + content: Buffer.from('b', 'utf8'), + executable: false, + }, + { + path: 'a/b/c.txt', + content: Buffer.from('c', 'utf8'), + executable: false, + }, + ]); + }); +}); diff --git a/plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.ts b/plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.ts new file mode 100644 index 0000000000..cb567eed9d --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/files/deserializeDirectoryContents.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 fs from 'fs-extra'; +import { dirname } from 'path'; +import { resolveSafeChildPath } from '@backstage/backend-common'; +import { SerializedFile } from './types'; + +/** + * Deserializes a list of serialized files into the target directory. + * + * This method uses `resolveSafeChildPath` to make sure that files are + * not written outside of the target directory. + * + * @internal + */ +export async function deserializeDirectoryContents( + targetPath: string, + files: SerializedFile[], +): Promise { + for (const file of files) { + const filePath = resolveSafeChildPath(targetPath, file.path); + await fs.ensureDir(dirname(filePath)); + await fs.writeFile(filePath, file.content); // Ignore file mode + } +} diff --git a/plugins/scaffolder-backend/src/lib/files/index.ts b/plugins/scaffolder-backend/src/lib/files/index.ts new file mode 100644 index 0000000000..ab6975532f --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/files/index.ts @@ -0,0 +1,19 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { serializeDirectoryContents } from './serializeDirectoryContents'; +export { deserializeDirectoryContents } from './deserializeDirectoryContents'; +export type { SerializedFile } from './types'; diff --git a/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts b/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts new file mode 100644 index 0000000000..a58274d694 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts @@ -0,0 +1,179 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { serializeDirectoryContents } from './serializeDirectoryContents'; +import mockFs from 'mock-fs'; + +describe('serializeDirectoryContents', () => { + afterEach(() => { + mockFs.restore(); + }); + + it('should life files in this directory', async () => { + await expect(serializeDirectoryContents(__dirname)).resolves.toEqual( + expect.arrayContaining([ + { + path: 'index.ts', + executable: false, + content: expect.any(Buffer), + }, + { + path: 'types.ts', + executable: false, + content: expect.any(Buffer), + }, + { + path: 'serializeDirectoryContents.ts', + executable: false, + content: expect.any(Buffer), + }, + { + path: 'serializeDirectoryContents.test.ts', + executable: false, + content: expect.any(Buffer), + }, + ]), + ); + }); + + it('should list files in a mock directory', async () => { + mockFs({ + root: { + 'a.txt': 'a', + b: { + 'b1.txt': 'b1', + 'b2.txt': 'b2', + }, + c: { + c1: { + 'c11.txt': 'c11', + c11: { + 'c111.txt': 'c111', + }, + }, + }, + }, + }); + + await expect(serializeDirectoryContents('root')).resolves.toEqual([ + { + path: 'a.txt', + executable: false, + content: Buffer.from('a', 'utf8'), + }, + { + path: 'b/b1.txt', + executable: false, + content: Buffer.from('b1', 'utf8'), + }, + { + path: 'b/b2.txt', + executable: false, + content: Buffer.from('b2', 'utf8'), + }, + { + path: 'c/c1/c11.txt', + executable: false, + content: Buffer.from('c11', 'utf8'), + }, + { + path: 'c/c1/c11/c111.txt', + executable: false, + content: Buffer.from('c111', 'utf8'), + }, + ]); + }); + + it('should ignore gitignored files', async () => { + mockFs({ + root: { + '.gitignore': '*.txt', + 'a.txt': 'a', + 'a.log': 'a', + }, + }); + + await expect( + serializeDirectoryContents('root', { + gitignore: true, + }), + ).resolves.toEqual([ + { + path: '.gitignore', + executable: false, + content: Buffer.from('*.txt', 'utf8'), + }, + { + path: 'a.log', + executable: false, + content: Buffer.from('a', 'utf8'), + }, + ]); + }); + + it('should use custom glob patterns', async () => { + mockFs({ + root: { + '.a': 'a', + 'a.log': 'a', + 'a.txt': 'a', + b: { + '.b': 'b', + 'b.log': 'b', + 'b.txt': 'b', + }, + c: { + '.c': 'c', + 'c.log': 'c', + 'c.txt': 'c', + }, + }, + }); + + await expect( + serializeDirectoryContents('root', { + gitignore: true, + globPatterns: ['**/*.txt', '*/.?', '*/*.log', '!c/**/.*', '!b/*.log'], + }).then(files => files.sort((a, b) => a.path.localeCompare(b.path))), + ).resolves.toEqual([ + { + path: 'a.txt', + executable: false, + content: Buffer.from('a', 'utf8'), + }, + { + path: 'b/.b', + executable: false, + content: Buffer.from('b', 'utf8'), + }, + { + path: 'b/b.txt', + executable: false, + content: Buffer.from('b', 'utf8'), + }, + { + path: 'c/c.log', + executable: false, + content: Buffer.from('c', 'utf8'), + }, + { + path: 'c/c.txt', + executable: false, + content: Buffer.from('c', 'utf8'), + }, + ]); + }); +}); diff --git a/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.ts b/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.ts new file mode 100644 index 0000000000..608b9e71a5 --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.ts @@ -0,0 +1,62 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 fs from 'fs-extra'; +import globby from 'globby'; +import limiterFactory from 'p-limit'; +import { join as joinPath } from 'path'; +import { SerializedFile } from './types'; + +const DEFAULT_GLOB_PATTERNS = ['./**', '!.git']; + +export const isExecutable = (fileMode: number | undefined) => { + if (!fileMode) { + return false; + } + + const executeBitMask = 0o000111; + const res = fileMode & executeBitMask; + return res > 0; +}; + +export async function serializeDirectoryContents( + sourcePath: string, + options?: { + gitignore?: boolean; + globPatterns?: string[]; + }, +): Promise { + const paths = await globby(options?.globPatterns ?? DEFAULT_GLOB_PATTERNS, { + cwd: sourcePath, + dot: true, + gitignore: options?.gitignore, + followSymbolicLinks: false, + objectMode: true, + stats: true, + }); + + const limiter = limiterFactory(10); + + return Promise.all( + paths.map(async ({ path, stats }) => ({ + path, + content: await limiter(async () => + fs.readFile(joinPath(sourcePath, path)), + ), + executable: isExecutable(stats?.mode), + })), + ); +} diff --git a/plugins/scaffolder-backend/src/lib/files/types.ts b/plugins/scaffolder-backend/src/lib/files/types.ts new file mode 100644 index 0000000000..390d6791ee --- /dev/null +++ b/plugins/scaffolder-backend/src/lib/files/types.ts @@ -0,0 +1,21 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface SerializedFile { + path: string; + content: Buffer; + executable?: boolean; +} From 10b7b2d9c79da96c59cac9a7ed78277ca47e3b6a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 4 Apr 2022 10:11:10 +0200 Subject: [PATCH 06/56] scaffolder-backend: add dry-run support for actions and runner Signed-off-by: Patrik Oldsberg --- .../src/scaffolder/actions/types.ts | 7 +++++ .../tasks/NunjucksWorkflowRunner.ts | 19 +++++++++++- .../src/scaffolder/tasks/helper.ts | 29 +++++++++++++++++++ .../src/scaffolder/tasks/types.ts | 1 + 4 files changed, 55 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/types.ts b/plugins/scaffolder-backend/src/scaffolder/actions/types.ts index 9a4ae8b2bf..ff15300b4a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/types.ts @@ -39,12 +39,19 @@ export type ActionContext = { createTemporaryDirectory(): Promise; templateInfo?: TemplateInfo; + + /** + * Whether this action invocation is a dry-run or not. + * This will only ever be true if the actions as marked as supporting dry-runs. + */ + isDryRun?: boolean; }; /** @public */ export type TemplateAction = { id: string; description?: string; + supportsDryRun?: boolean; schema?: { input?: Schema; output?: Schema; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index c9d0714392..3a066494b1 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -23,7 +23,7 @@ import nunjucks from 'nunjucks'; import { JsonObject, JsonValue } from '@backstage/types'; import { InputError } from '@backstage/errors'; import { PassThrough } from 'stream'; -import { isTruthy } from './helper'; +import { generateExampleOutput, isTruthy } from './helper'; import { validate as validateJsonSchema } from 'jsonschema'; import { parseRepoUrl } from '../actions/builtin/publish/util'; import { TemplateActionRegistry } from '../actions'; @@ -236,6 +236,23 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { const action = this.options.actionRegistry.get(step.action); const { taskLogger, streamLogger } = createStepLogger({ task, step }); + if (task.isDryRun && !action.supportsDryRun) { + task.emitLog( + `Skipping because ${action.id} does not support dry-run`, + ); + const outputSchema = action.schema?.output; + if (outputSchema) { + context.steps[step.id] = { + output: generateExampleOutput(outputSchema) as { + [name in string]: JsonValue; + }, + }; + } else { + context.steps[step.id] = { output: {} }; + } + continue; + } + // Secrets are only passed when templating the input to actions for security reasons const input = (step.input && diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts index 2058ce917f..76951afde4 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/helper.ts @@ -15,6 +15,7 @@ */ import { isArray } from 'lodash'; +import { Schema } from 'jsonschema'; /** * Returns true if the input is not `false`, `undefined`, `null`, `""`, `0`, or @@ -24,3 +25,31 @@ import { isArray } from 'lodash'; export function isTruthy(value: any): boolean { return isArray(value) ? value.length > 0 : !!value; } + +export function generateExampleOutput(schema: Schema): unknown { + const { examples } = schema as { examples?: unknown }; + if (examples && Array.isArray(examples)) { + return examples[0]; + } + if (schema.type === 'object') { + return Object.fromEntries( + Object.entries(schema.properties ?? {}).map(([key, value]) => [ + key, + generateExampleOutput(value), + ]), + ); + } else if (schema.type === 'array') { + const [firstSchema] = [schema.items]?.flat(); + if (firstSchema) { + return [generateExampleOutput(firstSchema)]; + } + return []; + } else if (schema.type === 'string') { + return ''; + } else if (schema.type === 'number') { + return 0; + } else if (schema.type === 'boolean') { + return false; + } + return ''; +} diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts index a3a9ef84d1..4a9d43aea6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/types.ts @@ -111,6 +111,7 @@ export interface TaskContext { secrets?: TaskSecrets; createdBy?: string; done: boolean; + isDryRun?: boolean; emitLog(message: string, logMetadata?: JsonObject): Promise; complete(result: TaskCompletionState, metadata?: JsonObject): Promise; getWorkspaceName(): Promise; From fe9a710adbccfbc2f8c201be4d373b26d5ec0471 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 4 Apr 2022 10:13:09 +0200 Subject: [PATCH 07/56] scaffolder-backend: enable dry-run support in some actions Signed-off-by: Patrik Oldsberg --- .../scaffolder/actions/builtin/catalog/register.ts | 12 ++++++++++++ .../src/scaffolder/actions/builtin/catalog/write.ts | 1 + .../src/scaffolder/actions/builtin/debug/log.ts | 1 + .../src/scaffolder/actions/builtin/fetch/plain.ts | 1 + .../src/scaffolder/actions/builtin/fetch/template.ts | 1 + .../scaffolder/actions/builtin/filesystem/delete.ts | 1 + .../scaffolder/actions/builtin/filesystem/rename.ts | 1 + 7 files changed, 18 insertions(+) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts index 1b30268d9d..4b9f883706 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.ts @@ -84,6 +84,18 @@ export function createCatalogRegisterAction(options: { }, ], }, + output: { + type: 'object', + required: ['catalogInfoUrl'], + properties: { + entityRef: { + type: 'string', + }, + catalogInfoUrl: { + type: 'string', + }, + }, + }, }, async handler(ctx) { const { input } = ctx; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts index b55e521784..da758898dc 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts @@ -46,6 +46,7 @@ export function createCatalogWriteAction() { }, }, }, + supportsDryRun: true, async handler(ctx) { ctx.logStream.write(`Writing catalog-info.yaml`); const { filePath, entity } = ctx.input; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts index 59d4c333c6..99d030b772 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/debug/log.ts @@ -51,6 +51,7 @@ export function createDebugLogAction() { }, }, }, + supportsDryRun: true, async handler(ctx) { ctx.logger.info(JSON.stringify(ctx.input, null, 2)); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts index 6dbe08f61b..9dc82068c0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.ts @@ -54,6 +54,7 @@ export function createFetchPlainAction(options: { }, }, }, + supportsDryRun: true, async handler(ctx) { ctx.logger.info('Fetching plain content from remote URL'); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 3d3d319c57..041263c90c 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -101,6 +101,7 @@ export function createFetchTemplateAction(options: { }, }, }, + supportsDryRun: true, async handler(ctx) { ctx.logger.info('Fetching template content from remote URL'); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts index bf2953e75a..757e9c682e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/delete.ts @@ -42,6 +42,7 @@ export const createFilesystemDeleteAction = () => { }, }, }, + supportsDryRun: true, async handler(ctx) { if (!Array.isArray(ctx.input?.files)) { throw new InputError('files must be an Array'); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts index 7c8dad7559..654ee7b150 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/filesystem/rename.ts @@ -66,6 +66,7 @@ export const createFilesystemRenameAction = () => { }, }, }, + supportsDryRun: true, async handler(ctx) { if (!Array.isArray(ctx.input?.files)) { throw new InputError('files must be an Array'); From 22318e7c98a29db22b558a29d6b110107ec45e81 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 4 Apr 2022 10:13:41 +0200 Subject: [PATCH 08/56] scaffolder-backend: hook up initial dry-run route Signed-off-by: Patrik Oldsberg --- .../scaffolder-backend/src/service/router.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 5453dcd234..09ad4f4360 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -42,6 +42,7 @@ import { TemplateAction, TemplateActionRegistry, } from '../scaffolder'; +import { createDryRunner } from '../scaffolder/dryrun'; import { StorageTaskBroker } from '../scaffolder/tasks/StorageTaskBroker'; import { getEntityBaseUrl, getWorkingDirectory, findTemplate } from './helpers'; @@ -129,6 +130,14 @@ export async function createRouter( actionsToRegister.forEach(action => actionRegistry.register(action)); workers.forEach(worker => worker.start()); + const dryRunner = createDryRunner({ + actionRegistry, + integrations, + logger, + workingDirectory, + additionalTemplateFilters, + }); + router .get( '/v2/templates/:namespace/:kind/:name/parameter-schema', @@ -324,6 +333,46 @@ export async function createRouter( subscription.unsubscribe(); clearTimeout(timeout); }); + }) + .post('/v2/dryrun', async (req, res) => { + const template = req.body.template as TemplateEntityV1beta3; + const values = req.body.values; + const token = getBearerToken(req.headers.authorization); + const content = req.body.content as { + path: string; + base64Content: string; + }[]; + + for (const parameters of [template.spec.parameters ?? []].flat()) { + const result = validate(values, parameters); + if (!result.valid) { + res.status(400).json({ errors: result.errors }); + return; + } + } + + const result = await dryRunner({ + spec: { + apiVersion: template.apiVersion, + steps: template.spec.steps.map((step, index) => ({ + ...step, + id: step.id ?? `step-${index + 1}`, + name: step.name ?? step.action, + })), + output: template.spec.output ?? {}, + parameters: values, + }, + content: (content ?? []).map(file => ({ + path: file.path, + content: Buffer.from(file.base64Content, 'base64'), + })), + secrets: { + ...req.body.secrets, + backstageToken: token, + }, + }); + + res.status(200).json(result); }); const app = express(); From f45a2abcf2895c41e1de28d9cbddee8d42d905aa Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 4 Apr 2022 10:14:41 +0200 Subject: [PATCH 09/56] scaffolder-backend: add dry-run implementation Signed-off-by: Patrik Oldsberg --- .../dryrun/DecoratedActionsRegistry.ts | 39 +++++ .../src/scaffolder/dryrun/createDryRunner.ts | 143 ++++++++++++++++++ .../src/scaffolder/dryrun/index.ts | 17 +++ 3 files changed, 199 insertions(+) create mode 100644 plugins/scaffolder-backend/src/scaffolder/dryrun/DecoratedActionsRegistry.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/dryrun/index.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/DecoratedActionsRegistry.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/DecoratedActionsRegistry.ts new file mode 100644 index 0000000000..6f11d3b6e8 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/DecoratedActionsRegistry.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { JsonObject } from '@backstage/types'; +import { TemplateAction, TemplateActionRegistry } from '../actions'; + +/** @internal */ +export class DecoratedActionsRegistry extends TemplateActionRegistry { + constructor( + private readonly innerRegistry: TemplateActionRegistry, + extraActions: Array>, + ) { + super(); + for (const action of extraActions) { + this.register(action); + } + } + + get(actionId: string): TemplateAction { + try { + return super.get(actionId); + } catch { + return this.innerRegistry.get(actionId); + } + } +} diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts new file mode 100644 index 0000000000..de3590c3ac --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -0,0 +1,143 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { ScmIntegrations } from '@backstage/integration'; +import { TaskSpec } from '@backstage/plugin-scaffolder-common'; +import { JsonObject } from '@backstage/types'; +import { v4 as uuid } from 'uuid'; +import { pathToFileURL } from 'url'; +import { Logger } from 'winston'; +import { + deserializeDirectoryContents, + SerializedFile, + serializeDirectoryContents, +} from '../../lib/files'; +import { TemplateFilter } from '../../lib/templating'; +import { createTemplateAction, TemplateActionRegistry } from '../actions'; +import { NunjucksWorkflowRunner } from '../tasks/NunjucksWorkflowRunner'; +import { TaskSecrets } from '../tasks/types'; +import { DecoratedActionsRegistry } from './DecoratedActionsRegistry'; +import fs from 'fs-extra'; +import { resolveSafeChildPath } from '@backstage/backend-common'; + +interface DryRunInput { + spec: TaskSpec; + secrets?: TaskSecrets; + content: SerializedFile[]; +} + +interface DryRunResult { + log: JsonObject[]; + content: SerializedFile[]; + output: JsonObject; +} + +/** @internal */ +export type TemplateTesterCreateOptions = { + logger: Logger; + integrations: ScmIntegrations; + actionRegistry: TemplateActionRegistry; + workingDirectory: string; + additionalTemplateFilters?: Record; +}; + +/** + * Executes a dry-run of the provided template. + * + * The provided content will be extracted into a temporary directory + * which is then use as the base for any relative file fetch paths. + * + * @internal + */ +export function createDryRunner(options: TemplateTesterCreateOptions) { + return async function dryRun(input: DryRunInput): Promise { + let contentPromise; + + const workflowRunner = new NunjucksWorkflowRunner({ + ...options, + actionRegistry: new DecoratedActionsRegistry(options.actionRegistry, [ + createTemplateAction({ + id: 'dry-run:extract', + supportsDryRun: true, + async handler(ctx) { + contentPromise = serializeDirectoryContents(ctx.workspacePath); + await contentPromise.catch(() => {}); + }, + }), + ]), + }); + + const dryRunId = uuid(); + const log = new Array(); + const contentsPath = resolveSafeChildPath( + options.workingDirectory, + `dry-run-content-${dryRunId}`, + ); + + try { + await deserializeDirectoryContents(contentsPath, input.content); + + const result = await workflowRunner.execute({ + spec: { + ...input.spec, + steps: [ + ...input.spec.steps, + { + id: dryRunId, + name: 'dry-run:extract', + action: 'dry-run:extract', + }, + ], + templateInfo: { + entityRef: 'template:default/dry-run', + baseUrl: pathToFileURL( + resolveSafeChildPath(contentsPath, 'template.yaml'), + ).toString(), + }, + }, + secrets: input.secrets, + done: false, + isDryRun: true, + getWorkspaceName: async () => `dry-run-${dryRunId}`, + async emitLog(message: string, logMetadata?: JsonObject) { + if (logMetadata?.stepId === dryRunId) { + return; + } + log.push({ + ...logMetadata, + message, + }); + }, + async complete() { + throw new Error('Not implemented'); + }, + }); + + if (!contentPromise) { + throw new Error('Content extraction step was skipped'); + } + const content = await contentPromise; + + return { + log, + content, + output: result.output, + }; + } finally { + await fs.remove(contentsPath); + } + }; +} diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/index.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/index.ts new file mode 100644 index 0000000000..9ae38e6df2 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { createDryRunner } from './createDryRunner'; From c3a24968b31bd158aea92a12c9c5a83cf4a70734 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 4 Apr 2022 10:15:27 +0200 Subject: [PATCH 10/56] scaffolder: add filesystem access helpers Signed-off-by: Patrik Oldsberg --- .../src/lib/filesystem/WebFileSystemAccess.ts | 105 ++++++++++++++++++ .../scaffolder/src/lib/filesystem/helpers.ts | 27 +++++ .../scaffolder/src/lib/filesystem/index.ts | 23 ++++ .../scaffolder/src/lib/filesystem/types.ts | 29 +++++ 4 files changed, 184 insertions(+) create mode 100644 plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts create mode 100644 plugins/scaffolder/src/lib/filesystem/helpers.ts create mode 100644 plugins/scaffolder/src/lib/filesystem/index.ts create mode 100644 plugins/scaffolder/src/lib/filesystem/types.ts diff --git a/plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts b/plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts new file mode 100644 index 0000000000..2de3bd296f --- /dev/null +++ b/plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts @@ -0,0 +1,105 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { + TemplateFileSystemAccess, + TemplateDirectoryAccess, + TemplateFileAccess, +} from './types'; + +type WritableFileHandle = FileSystemFileHandle & { + createWritable(): Promise<{ + write(data: string | Blob | BufferSource): Promise; + close(): Promise; + }>; +}; + +// A nicer type than the one from the TS lib +interface IterableDirectoryHandle extends FileSystemDirectoryHandle { + values(): AsyncIterable< + | ({ kind: 'file' } & WritableFileHandle) + | ({ kind: 'directory' } & IterableDirectoryHandle) + >; +} + +const showDirectoryPicker = (window as any).showDirectoryPicker as + | (() => Promise) + | undefined; + +class WebFileAccess implements TemplateFileAccess { + constructor( + readonly path: string, + private readonly handle: WritableFileHandle, + ) {} + + file(): Promise { + return this.handle.getFile(); + } + + async save(data: string | Blob | BufferSource): Promise { + const writable = await this.handle.createWritable(); + await writable.write(data); + await writable.close(); + } +} + +class WebDirectoryAccess implements TemplateDirectoryAccess { + constructor(private readonly handle: IterableDirectoryHandle) {} + + async listFiles(): Promise { + const content = []; + for await (const entry of this.listDirectoryContents(this.handle)) { + content.push(entry); + } + return content; + } + + private async *listDirectoryContents( + dirHandle: IterableDirectoryHandle, + basePath: string[] = [], + ): AsyncIterable { + for await (const handle of dirHandle.values()) { + if (handle.kind === 'file') { + yield new WebFileAccess([...basePath, handle.name].join('/'), handle); + } else if (handle.kind === 'directory') { + yield* this.listDirectoryContents(handle, [...basePath, handle.name]); + } + } + } +} + +/** @internal */ +export class WebFileSystemAccess implements TemplateFileSystemAccess { + private static instance = new WebFileSystemAccess(); + + static get(): TemplateFileSystemAccess { + return this.instance; + } + + static isSupported(): boolean { + return Boolean(showDirectoryPicker); + } + + private constructor() {} + + async requestDirectoryAccess(): Promise { + if (!showDirectoryPicker) { + throw new Error('File system access is not supported'); + } + const handle = await showDirectoryPicker(); + return new WebDirectoryAccess(handle); + } +} diff --git a/plugins/scaffolder/src/lib/filesystem/helpers.ts b/plugins/scaffolder/src/lib/filesystem/helpers.ts new file mode 100644 index 0000000000..530cf87277 --- /dev/null +++ b/plugins/scaffolder/src/lib/filesystem/helpers.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 async function blobToBase64(file: Blob) { + const reader = new FileReader(); + reader.readAsDataURL(file); + return new Promise((resolve, reject) => { + reader.onload = () => { + const uri = reader.result as string; + resolve(uri.slice(uri.indexOf(',') + 1)); + }; + reader.onerror = reject; + }); +} diff --git a/plugins/scaffolder/src/lib/filesystem/index.ts b/plugins/scaffolder/src/lib/filesystem/index.ts new file mode 100644 index 0000000000..c57fc35756 --- /dev/null +++ b/plugins/scaffolder/src/lib/filesystem/index.ts @@ -0,0 +1,23 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export type { + TemplateFileAccess, + TemplateDirectoryAccess, + TemplateFileSystemAccess, +} from './types'; +export { blobToBase64 } from './helpers'; +export { WebFileSystemAccess } from './WebFileSystemAccess'; diff --git a/plugins/scaffolder/src/lib/filesystem/types.ts b/plugins/scaffolder/src/lib/filesystem/types.ts new file mode 100644 index 0000000000..820ae5f99d --- /dev/null +++ b/plugins/scaffolder/src/lib/filesystem/types.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface TemplateFileAccess { + path: string; + file(): Promise; + save(data: string | BufferSource | Blob): Promise; +} + +export interface TemplateDirectoryAccess { + listFiles(): Promise>; +} + +export interface TemplateFileSystemAccess { + requestDirectoryAccess(): Promise; +} From 16f11352ab50dd71c65aac00135be5c72388da47 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 4 Apr 2022 10:16:22 +0200 Subject: [PATCH 11/56] scaffolder: allow tweaking of the MultiStepJsonForm finish button label Signed-off-by: Patrik Oldsberg --- .../MultistepJsonForm/MultistepJsonForm.tsx | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index fb1de5f46d..af817739ed 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -54,6 +54,7 @@ type Props = { onFinish?: () => Promise; widgets?: FormProps['widgets']; fields?: FormProps['fields']; + finishButtonLabel?: string; }; export function getUiSchemasFromSteps(steps: Step[]): UiSchema[] { @@ -110,7 +111,15 @@ export function getReviewData(formData: Record, steps: Step[]) { } export const MultistepJsonForm = (props: Props) => { - const { formData, onChange, onReset, onFinish, fields, widgets } = props; + const { + formData, + onChange, + onReset, + onFinish, + fields, + widgets, + finishButtonLabel, + } = props; const [activeStep, setActiveStep] = useState(0); const [disableButtons, setDisableButtons] = useState(false); const errorApi = useApi(errorApiRef); @@ -239,7 +248,7 @@ export const MultistepJsonForm = (props: Props) => { onClick={handleCreate} disabled={!onFinish || disableButtons} > - Create + {finishButtonLabel ?? 'Create'} From df4497ecd7a0fe80ca104a8ad0faf889c75fe9ae Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 8 Apr 2022 10:28:03 +0200 Subject: [PATCH 12/56] scaffolder: initial e2e editor + dry-run Signed-off-by: Patrik Oldsberg --- .../tasks/NunjucksWorkflowRunner.ts | 4 + .../scaffolder-backend/src/service/router.ts | 22 +- plugins/scaffolder/package.json | 1 + plugins/scaffolder/src/api.ts | 30 +- .../MultistepJsonForm/MultistepJsonForm.tsx | 3 +- .../src/components/TaskPage/TaskPage.tsx | 12 +- .../src/components/TaskPage/TaskPageLinks.tsx | 5 +- .../DirectoryEditorContext.tsx | 243 ++++++++++ .../TemplateEditorPage/DirectoryLoader.tsx | 53 +++ .../TemplateEditorPage/DryRunContext.tsx | 448 ++++++++++++++++++ .../TemplateEditorPage/EditorIntro.tsx | 127 +++++ .../TemplateEditorPage/FileBrowser.test.tsx | 95 ++++ .../TemplateEditorPage/FileBrowser.tsx | 185 ++++++++ .../TemplateEditorPage/TemplateEditor.tsx | 416 ++++++++++++++++ .../TemplateEditorDryRunResults.tsx | 364 ++++++++++++++ .../TemplateEditorPage/TemplateEditorPage.tsx | 367 +++++--------- .../TemplateEditorPage/TemplateFormEditor.tsx | 282 +++++++++++ .../components/TemplateEditorPage/index.ts | 1 + .../lib/filesystem/MockFileSystemAccess.ts | 57 +++ plugins/scaffolder/src/types.ts | 23 +- 20 files changed, 2483 insertions(+), 255 deletions(-) create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/DirectoryEditorContext.tsx create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/DirectoryLoader.tsx create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/EditorIntro.tsx create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/FileBrowser.test.tsx create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/FileBrowser.tsx create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormEditor.tsx create mode 100644 plugins/scaffolder/src/lib/filesystem/MockFileSystemAccess.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 3a066494b1..97e37fd3eb 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -239,6 +239,10 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { if (task.isDryRun && !action.supportsDryRun) { task.emitLog( `Skipping because ${action.id} does not support dry-run`, + { + stepId: step.id, + status: 'skipped', + }, ); const outputSchema = action.schema?.output; if (outputSchema) { diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 09ad4f4360..dcef37f8e4 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -351,14 +351,16 @@ export async function createRouter( } } + const steps = template.spec.steps.map((step, index) => ({ + ...step, + id: step.id ?? `step-${index + 1}`, + name: step.name ?? step.action, + })); + const result = await dryRunner({ spec: { apiVersion: template.apiVersion, - steps: template.spec.steps.map((step, index) => ({ - ...step, - id: step.id ?? `step-${index + 1}`, - name: step.name ?? step.action, - })), + steps, output: template.spec.output ?? {}, parameters: values, }, @@ -372,7 +374,15 @@ export async function createRouter( }, }); - res.status(200).json(result); + res.status(200).json({ + ...result, + steps, + content: result.content.map(file => ({ + path: file.path, + executable: file.executable, + base64Content: file.content.toString('base64'), + })), + }); }); const app = express(); diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 91f52d9df0..19d6e84399 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -55,6 +55,7 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", + "@react-hookz/web": "^13.0.0", "@rjsf/core": "^3.2.1", "@rjsf/material-ui": "^3.2.1", "@types/json-schema": "^7.0.9", diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 04103b9f8a..0c2dee8f7f 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -22,7 +22,8 @@ import { } from '@backstage/core-plugin-api'; import { ResponseError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { Observable } from '@backstage/types'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { JsonObject, Observable } from '@backstage/types'; import qs from 'qs'; import ObservableImpl from 'zen-observable'; import { @@ -172,6 +173,33 @@ export class ScaffolderClient implements ScaffolderApi { return this.streamLogsEventStream(options); } + async dryRun(options: { + template: TemplateEntityV1beta3; + values: JsonObject; + secrets: JsonObject; + content: { path: string; base64Content: string }[]; + }): Promise { + const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); + const res = await this.fetchApi.fetch(`${baseUrl}/v2/dryrun`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + template: options.template, + values: options.values, + secrets: options.secrets, + content: options.content, + }), + }); + + if (!res.ok) { + throw await ResponseError.fromResponse(res); + } + + return res.json(); + } + private streamLogsEventStream({ taskId, after, diff --git a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx index af817739ed..33e274934e 100644 --- a/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx +++ b/plugins/scaffolder/src/components/MultistepJsonForm/MultistepJsonForm.tsx @@ -180,8 +180,9 @@ export const MultistepJsonForm = (props: Props) => { try { await onFinish(); } catch (err) { - setDisableButtons(false); errorApi.post(err); + } finally { + setDisableButtons(false); } }; diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx index 27d89cb6a4..0662b0300f 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPage.tsx @@ -161,16 +161,16 @@ function TaskStepIconComponent(props: StepIconProps) { } export const TaskStatusStepper = memo( - ({ - steps, - currentStepId, - onUserStepChange, - }: { + (props: { steps: TaskStep[]; currentStepId: string | undefined; onUserStepChange: (id: string) => void; + classes?: { + root?: string; + }; }) => { - const classes = useStyles(); + const { steps, currentStepId, onUserStepChange } = props; + const classes = useStyles(props); return (
diff --git a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx index cb77689b6e..78d6a452ea 100644 --- a/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx +++ b/plugins/scaffolder/src/components/TaskPage/TaskPageLinks.tsx @@ -41,7 +41,10 @@ export const TaskPageLinks = ({ output }: TaskPageLinksProps) => { .filter(({ url, entityRef }) => url || entityRef) .map(({ url, entityRef, title, icon }) => { if (entityRef) { - const entityName = parseEntityRef(entityRef); + const entityName = parseEntityRef(entityRef, { + defaultKind: '', + defaultNamespace: '', + }); const target = entityRoute(entityName); return { title, icon, url: target }; } diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DirectoryEditorContext.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DirectoryEditorContext.tsx new file mode 100644 index 0000000000..0edf390bfe --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DirectoryEditorContext.tsx @@ -0,0 +1,243 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { ErrorPanel, Progress } from '@backstage/core-components'; +import { useAsync, useRerender } from '@react-hookz/web'; +import React, { createContext, ReactNode, useContext, useEffect } from 'react'; +import { + TemplateDirectoryAccess, + TemplateFileAccess, +} from '../../lib/filesystem'; + +const MAX_SIZE = 1024 * 1024; +const MAX_SIZE_MESSAGE = 'This file is too large to be displayed'; + +interface DirectoryEditorFile { + /** The path of the file relative to the root directory */ + path: string; + /** The staged content of the file */ + content: string; + /** Whether the staged content matches what is on disk */ + dirty: boolean; + + /** Update the staged content of the file without saving */ + updateContent(content: string): void; + /** Save the staged content of the file to disk */ + save(): Promise; + /** Reload the staged content of the file from disk */ + reload(): Promise; +} + +interface DirectoryEditor { + /** A list of all files in the edited directory */ + files: Array; + + /** The currently selected file */ + selectedFile: DirectoryEditorFile | undefined; + /** Switch the selected file */ + setSelectedFile(path: string | undefined): void; + + /** Save all files to disk */ + save(): Promise; + /** Reload all files from disk */ + reload(): Promise; + + subscribe(listener: () => void): () => void; +} + +class DirectoryEditorFileManager implements DirectoryEditorFile { + readonly #access: TemplateFileAccess; + readonly #signalUpdate: () => void; + + #content?: string; + #savedContent?: string; + + constructor(access: TemplateFileAccess, signalUpdate: () => void) { + this.#access = access; + this.#signalUpdate = signalUpdate; + } + + get path() { + return this.#access.path; + } + + get content() { + return this.#content ?? MAX_SIZE_MESSAGE; + } + + updateContent(content: string): void { + if (this.#content === undefined) { + return; + } + this.#content = content; + this.#signalUpdate(); + } + + get dirty() { + return this.#content !== this.#savedContent; + } + + async save(): Promise { + if (this.#content !== undefined) { + await this.#access.save(this.#content); + this.#savedContent = this.#content; + this.#signalUpdate(); + } + } + + async reload(): Promise { + const file = await this.#access.file(); + if (file.size > MAX_SIZE) { + if (this.#content !== undefined) { + this.#content = undefined; + this.#savedContent = undefined; + this.#signalUpdate(); + } + return; + } + + const content = await file.text(); + if (this.#content !== content) { + this.#content = content; + this.#savedContent = content; + this.#signalUpdate(); + } + } +} + +class DirectoryEditorManager implements DirectoryEditor { + readonly #access: TemplateDirectoryAccess; + readonly #listeners = new Set<() => void>(); + + #files: DirectoryEditorFile[] = []; + #selectedFile: DirectoryEditorFile | undefined; + + constructor(access: TemplateDirectoryAccess) { + this.#access = access; + } + + get files() { + return this.#files; + } + + get selectedFile() { + return this.#selectedFile; + } + + setSelectedFile = (path: string | undefined): void => { + const prev = this.#selectedFile; + const next = this.#files.find(file => file.path === path); + if (prev !== next) { + this.#selectedFile = next; + this.#signalUpdate(); + } + }; + + get dirty() { + return this.#files.some(file => file.dirty); + } + + async save(): Promise { + await Promise.all(this.#files.map(file => file.save())); + } + + async reload(): Promise { + const selectedPath = this.#selectedFile?.path; + + const files = await this.#access.listFiles(); + const fileManagers = await Promise.all( + files.map(async file => { + const manager = new DirectoryEditorFileManager( + file, + this.#signalUpdate, + ); + await manager.reload(); + return manager; + }), + ); + this.#files.length = 0; + this.#files.push(...fileManagers); + + this.setSelectedFile(selectedPath); + this.#signalUpdate(); + } + + subscribe(listener: () => void): () => void { + this.#listeners.add(listener); + return () => { + this.#listeners.delete(listener); + }; + } + + #signalUpdate = () => { + this.#listeners.forEach(listener => listener()); + }; +} + +const DirectoryEditorContext = createContext( + undefined, +); + +export function useDirectoryEditor(): DirectoryEditor { + const value = useContext(DirectoryEditorContext); + const rerender = useRerender(); + + useEffect(() => value?.subscribe(rerender), [value, rerender]); + + if (!value) { + throw new Error('must be used within a DirectoryEditorProvider'); + } + return value; +} + +interface DirectoryEditorProviderProps { + directory: TemplateDirectoryAccess; + children?: ReactNode; +} + +export function DirectoryEditorProvider(props: DirectoryEditorProviderProps) { + const { directory } = props; + + const [{ result, error }, { execute }] = useAsync( + async (dir: TemplateDirectoryAccess) => { + const manager = new DirectoryEditorManager(dir); + await manager.reload(); + + const firstYaml = manager.files.find(file => file.path.match(/\.ya?ml$/)); + if (firstYaml) { + manager.setSelectedFile(firstYaml.path); + } + + return manager; + }, + ); + + useEffect(() => { + execute(directory); + }, [execute, directory]); + + if (error) { + return ; + } else if (!result) { + return ; + } + + return ( + + {props.children} + + ); +} diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DirectoryLoader.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DirectoryLoader.tsx new file mode 100644 index 0000000000..65f0c21e32 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DirectoryLoader.tsx @@ -0,0 +1,53 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 Button from '@material-ui/core/Button'; +import React from 'react'; +import { useAsync } from '@react-hookz/web'; +import { + TemplateDirectoryAccess, + WebFileSystemAccess, +} from '../../lib/filesystem'; + +interface DirectoryLoaderProps { + onLoad(directory: TemplateDirectoryAccess): void; +} + +/** @internal */ +export function DirectoryLoader(props: DirectoryLoaderProps): JSX.Element { + const supportsWebAccess = WebFileSystemAccess.isSupported(); + + const [{ status, error }, { execute }] = useAsync(async () => { + if (!supportsWebAccess) { + return; + } + const directory = await WebFileSystemAccess.get().requestDirectoryAccess(); + props.onLoad(directory); + }); + + if (error) { + return
Fail: {error.message}
; + } + + return ( + + ); +} diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx new file mode 100644 index 0000000000..1b81b517d3 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx @@ -0,0 +1,448 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 yaml from 'yaml'; +import { useApi } from '@backstage/core-plugin-api'; +import { JsonObject } from '@backstage/types'; +import React, { + createContext, + ReactNode, + useContext, + useRef, + useState, +} from 'react'; +import { scaffolderApiRef } from '../../api'; +import { ScaffolderDryRunResponse } from '../../types'; + +interface DryRunOptions { + templateContent: string; + values: JsonObject; + files: Array<{ path: string; content: string }>; +} + +interface DryRunResult extends ScaffolderDryRunResponse { + id: number; +} + +interface DryRun { + results: DryRunResult[]; + selectedResult: DryRunResult | undefined; + + selectResult(id: number): void; + deleteResult(id: number): void; + execute(options: DryRunOptions): Promise; +} + +const DryRunContext = createContext(undefined); + +interface DryRunProviderProps { + children: ReactNode; +} + +const fakeResults: DryRunResult[] = [ + { + id: 1, + content: [ + { + path: 'catalog-info.yaml', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + { + path: 'catalog-info.yaml1', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + { + path: 'catalog-info.yaml2', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + { + path: 'catalog-info.yaml3', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + { + path: 'catalog-info.yaml4', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + { + path: 'catalog-info.yaml5', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + { + path: 'catalog-info.yaml6', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + { + path: 'catalog-info.yaml7', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + { + path: 'catalog-info.yaml8', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + { + path: 'catalog-info.yaml9', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + { + path: 'catalog-info.yaml10', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + { + path: 'catalog-info.yaml11', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + { + path: 'catalog-info.yaml12', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + { + path: 'catalog-info.yaml13', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + { + path: 'catalog-info.yaml14', + executable: false, + base64Content: + 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', + }, + ], + steps: [ + { + id: 'fetch-base', + name: 'Fetch stuff', + action: 'fetch:plain', + }, + { + id: 'fetch-base2', + name: 'Fetch stuff', + action: 'fetch:plain', + }, + { + id: 'fetch-base3', + name: 'Fetch stuff', + action: 'fetch:plain', + }, + { + id: 'fetch-base4', + name: 'Fetch stuff', + action: 'fetch:plain', + }, + { + id: 'fetch-base5', + name: 'Fetch stuff', + action: 'fetch:plain', + }, + { + id: 'publish', + name: 'Publish Stuff', + action: 'publish:github', + }, + { + id: 'register', + name: 'Register Stuff', + action: 'catalog:register', + }, + ], + log: [ + { + message: 'Starting up task with 4 steps', + }, + { + stepId: 'fetch-base', + status: 'processing', + message: 'Beginning step Fetch Base', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Fetching template content from remote URL {"timestamp":"2022-04-18T11:25:46.888Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Listing files and directories in template {"timestamp":"2022-04-18T11:25:46.889Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Processing 1 template files/directories with input values {"name":"asd","timestamp":"2022-04-18T11:25:46.890Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Writing file catalog-info.yaml to template output path with mode 33188. {"timestamp":"2022-04-18T11:25:46.932Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + message: + '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', + }, + { + stepId: 'fetch-base', + status: 'completed', + message: 'Finished step Fetch Base', + }, + { + stepId: 'fetch-base2', + status: 'completed', + message: 'Finished step Fetch Base', + }, + { + stepId: 'fetch-base3', + status: 'completed', + message: 'Finished step Fetch Base', + }, + { + stepId: 'fetch-base4', + status: 'completed', + message: 'Finished step Fetch Base', + }, + { + stepId: 'fetch-base5', + status: 'completed', + message: 'Finished step Fetch Base', + }, + { + stepId: 'publish', + status: 'processing', + message: 'Beginning step Publish', + }, + { + stepId: 'publish', + status: 'completed', + message: 'Skipping because publish:bitbucket does not support dry-run', + }, + { + stepId: 'register', + status: 'processing', + message: 'Beginning step Register', + }, + { + stepId: 'register', + status: 'completed', + message: 'Skipping because catalog:register does not support dry-run', + }, + ], + output: { + links: [ + { + title: 'Repository', + url: '', + }, + { + title: 'Open in catalog', + icon: 'catalog', + entityRef: 'entity', + }, + ], + }, + }, +]; + +export function DryRunProvider(props: DryRunProviderProps) { + const scaffolderApi = useApi(scaffolderApiRef); + + const [state, setState] = useState< + Pick + >({ + results: [ + { ...fakeResults[0], id: 1 }, + { ...fakeResults[0], id: 2 }, + { ...fakeResults[0], id: 3 }, + { ...fakeResults[0], id: 4 }, + { ...fakeResults[0], id: 5 }, + ], + selectedResult: fakeResults[0], + }); + const idRef = useRef(1); + + const dryRun = { + ...state, + selectResult: (id: number) => { + setState(prevState => { + const result = prevState.results.find(r => r.id === id); + if (result === prevState.selectedResult) { + return prevState; + } + return { + results: prevState.results, + selectedResult: result, + }; + }); + }, + deleteResult: (id: number) => { + setState(prevState => { + const index = prevState.results.findIndex(r => r.id === id); + if (index === -1) { + return prevState; + } + const newResults = prevState.results.slice(); + const [deleted] = newResults.splice(index, 1); + return { + results: newResults, + selectedResult: + prevState.selectedResult?.id === deleted.id + ? newResults[0] + : prevState.selectedResult, + }; + }); + }, + execute: async (options: DryRunOptions) => { + if (!scaffolderApi.dryRun) { + throw new Error('Scaffolder API does not support dry-run'); + } + + const parsed = yaml.parse(options.templateContent); + + const response = await scaffolderApi.dryRun({ + template: parsed, + values: options.values, + secrets: {}, + content: options.files.map(file => ({ + path: file.path, + base64Content: btoa(file.content), + })), + }); + + const result = { + ...response, + id: idRef.current++, + }; + + setState(prevState => ({ + results: [...prevState.results, result], + selectedResult: prevState.selectedResult ?? result, + })); + }, + }; + + return ( + + {props.children} + + ); +} + +export function useDryRun(): DryRun { + const value = useContext(DryRunContext); + if (!value) { + throw new Error('must be used within a DryRunProvider'); + } + return value; +} diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/EditorIntro.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/EditorIntro.tsx new file mode 100644 index 0000000000..1c07a78c9e --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/EditorIntro.tsx @@ -0,0 +1,127 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 Card from '@material-ui/core/Card'; +import CardActionArea from '@material-ui/core/CardActionArea'; +import CardContent from '@material-ui/core/CardContent'; +import Tooltip from '@material-ui/core/Tooltip'; +import Typography from '@material-ui/core/Typography'; +import InfoOutlinedIcon from '@material-ui/icons/InfoOutlined'; +import { makeStyles } from '@material-ui/core/styles'; +import { WebFileSystemAccess } from '../../lib/filesystem'; + +const useStyles = makeStyles(theme => ({ + introText: { + textAlign: 'center', + marginTop: theme.spacing(2), + }, + card: { + position: 'relative', + maxWidth: 340, + marginTop: theme.spacing(4), + margin: theme.spacing(0, 2), + }, + infoIcon: { + position: 'absolute', + top: theme.spacing(1), + right: theme.spacing(1), + }, +})); + +interface EditorIntroProps { + style?: JSX.IntrinsicElements['div']['style']; + onSelect?: (option: 'local' | 'form') => void; +} + +export const EditorIntro = (props: EditorIntroProps) => { + const classes = useStyles(); + const supportsLoad = WebFileSystemAccess.isSupported(); + + const cardLoadLocal = ( + + props.onSelect?.('local')} + > + + + Load Template Directory + + + Load a local template directory, allowing you to both edit and try + executing your own template. + + + + {!supportsLoad && ( +
+ + + +
+ )} +
+ ); + + const cardFormEditor = ( + + props.onSelect?.('form')}> + + + Edit Template Form + + + Preview and edit a template form, either using a sample template or + by loading a template from the catalog. + + + + + ); + + return ( +
+ + Get started by choosing one of the options below + +
+ {supportsLoad && cardLoadLocal} + {cardFormEditor} + {!supportsLoad && cardLoadLocal} +
+
+ ); +}; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/FileBrowser.test.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/FileBrowser.test.tsx new file mode 100644 index 0000000000..67d0034ba3 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/FileBrowser.test.tsx @@ -0,0 +1,95 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { FileEntry, parseFileEntires } from './FileBrowser'; + +function dir(path: string, ...children: FileEntry[]): FileEntry { + return { + type: 'directory', + path: path, + name: path.split('/').pop()!, + children: children, + }; +} + +function file(path: string): FileEntry { + return { + type: 'file', + path: path, + name: path.split('/').pop()!, + }; +} + +describe('parseFileEntires', () => { + it('parses an empty list', () => { + expect(parseFileEntires([])).toEqual([]); + }); + + it('parses a single file', () => { + expect(parseFileEntires(['a.txt'])).toEqual([file('a.txt')]); + expect(parseFileEntires(['a/b.txt'])).toEqual([dir('a', file('a/b.txt'))]); + expect(parseFileEntires(['a/b/c.txt'])).toEqual([ + dir('a', dir('a/b', file('a/b/c.txt'))), + ]); + }); + + it('parses multiple files', () => { + expect(parseFileEntires(['a.txt', 'b.txt'])).toEqual([ + file('a.txt'), + file('b.txt'), + ]); + expect(parseFileEntires(['a.txt', 'a/b.txt'])).toEqual([ + dir('a', file('a/b.txt')), + file('a.txt'), + ]); + expect(parseFileEntires(['a.txt', 'a/b.txt', 'a/c.txt'])).toEqual([ + dir('a', file('a/b.txt'), file('a/c.txt')), + file('a.txt'), + ]); + expect(parseFileEntires(['a.txt', 'a/b/c.txt', 'a/b/d.txt'])).toEqual([ + dir('a', dir('a/b', file('a/b/c.txt'), file('a/b/d.txt'))), + file('a.txt'), + ]); + }); + + it('throws an error on invalid filenames', () => { + expect(() => parseFileEntires([''])).toThrow(`Invalid path part: ''`); + expect(() => parseFileEntires(['/'])).toThrow(`Invalid path part: ''`); + expect(() => parseFileEntires(['a/'])).toThrow(`Invalid path part: ''`); + expect(() => parseFileEntires(['/a.txt'])).toThrow(`Invalid path part: ''`); + expect(() => parseFileEntires(['a//a.txt'])).toThrow( + `Invalid path part: ''`, + ); + }); + + it('throws an error on conflicting directory and filenames', () => { + expect(() => parseFileEntires(['a', 'a'])).toThrow( + `Duplicate filename at 'a'`, + ); + expect(() => parseFileEntires(['a', 'a/b'])).toThrow( + `Duplicate filename at 'a'`, + ); + expect(() => parseFileEntires(['a/b', 'a'])).toThrow( + `Duplicate filename at 'a'`, + ); + expect(() => parseFileEntires(['a/b', 'a/b/c'])).toThrow( + `Duplicate filename at 'a/b'`, + ); + expect(() => parseFileEntires(['a/b/c', 'a/b/c'])).toThrow( + `Duplicate filename at 'a/b/c'`, + ); + }); +}); diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/FileBrowser.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/FileBrowser.tsx new file mode 100644 index 0000000000..705f5776c9 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/FileBrowser.tsx @@ -0,0 +1,185 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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, { useMemo } from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import TreeView from '@material-ui/lab/TreeView'; +import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; +import ChevronRightIcon from '@material-ui/icons/ChevronRight'; +import TreeItem from '@material-ui/lab/TreeItem'; +import { + TemplateDirectoryAccess, + TemplateFileAccess, +} from '../../lib/filesystem'; +import { useAsync, useMountEffect } from '@react-hookz/web'; +import { ErrorPanel, Progress } from '@backstage/core-components'; + +const useStyles = makeStyles({ + root: { + whiteSpace: 'nowrap', + overflowY: 'auto', + }, +}); + +export type FileEntry = + | { + type: 'file'; + name: string; + path: string; + } + | { + type: 'directory'; + name: string; + path: string; + children: FileEntry[]; + }; + +export function parseFileEntires(paths: string[]): FileEntry[] { + const root: FileEntry = { + type: 'directory', + name: '', + path: '', + children: [], + }; + + for (const path of paths.slice().sort()) { + const parts = path.split('/'); + + let current = root; + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + if (part === '') { + throw new Error(`Invalid path part: ''`); + } + + const entryPath = parts.slice(0, i + 1).join('/'); + + const existing = current.children.find(child => child.name === part); + if (existing?.type === 'file') { + throw new Error(`Duplicate filename at '${entryPath}'`); + } else if (existing) { + current = existing; + } else { + if (i < parts.length - 1) { + const newEntry: FileEntry = { + type: 'directory', + name: part, + path: entryPath, + children: [], + }; + const firstFileIndex = current.children.findIndex( + child => child.type === 'file', + ); + current.children.splice(firstFileIndex, 0, newEntry); + current = newEntry; + } else { + current.children.push({ + type: 'file', + name: part, + path: entryPath, + }); + } + } + } + } + + return root.children; +} + +function FileTreeItem({ entry }: { entry: FileEntry }) { + if (entry.type === 'file') { + return ; + } + + return ( + + {entry.children.map(child => ( + + ))} + + ); +} + +interface FileBrowserProps { + selected?: string; + filePaths: string[]; + onSelect?(filePath: string): void; +} + +export function FileBrowser(props: FileBrowserProps) { + const classes = useStyles(); + + const fileTree = useMemo( + () => parseFileEntires(props.filePaths), + [props.filePaths], + ); + + return ( + } + defaultExpandIcon={} + onNodeSelect={(_e: unknown, nodeId: string) => { + if (props.onSelect && props.filePaths.includes(nodeId)) { + props.onSelect(nodeId); + } + }} + > + {fileTree.map(entry => ( + + ))} + + ); +} + +interface TemplateDirectoryAccessBrowserProps { + directory: TemplateDirectoryAccess; + onSelect?(file: TemplateFileAccess): void; +} + +function TemplateDirectoryAccessBrowser( + props: TemplateDirectoryAccessBrowserProps, +) { + const [state, { execute }] = useAsync(async () => { + const files = await props.directory.listFiles(); + return { + filePaths: files.map(file => file.path), + getFile: (path: string) => files.find(file => file.path === path), + }; + }); + + useMountEffect(execute); + + if (state.error) { + return ; + } else if (!state.result) { + return ; + } + + const handleSelect = (path: string) => { + const file = state.result?.getFile(path); + if (file) { + props.onSelect?.(file); + } + }; + + return ( + + ); +} + +FileBrowser.TemplateDirectoryAccess = TemplateDirectoryAccessBrowser; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx new file mode 100644 index 0000000000..3361e3ab5a --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx @@ -0,0 +1,416 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React, { + Component, + ReactNode, + useEffect, + useMemo, + useReducer, + useState, +} from 'react'; +import useDebounce from 'react-use/lib/useDebounce'; +import { useApiHolder } from '@backstage/core-plugin-api'; +import { JsonObject, JsonValue } from '@backstage/types'; +import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; +import { showPanel } from '@codemirror/view'; +import { StreamLanguage } from '@codemirror/language'; +import { IconButton, makeStyles, Divider } from '@material-ui/core'; +import SaveIcon from '@material-ui/icons/Save'; +import RefreshIcon from '@material-ui/icons/Refresh'; +import CloseIcon from '@material-ui/icons/Close'; +import CodeMirror from '@uiw/react-codemirror'; +import yaml from 'yaml'; +import { FieldExtensionOptions } from '../../extensions'; +import { TemplateParameterSchema } from '../../types'; +import { MultistepJsonForm } from '../MultistepJsonForm'; +import { createValidator } from '../TemplatePage'; +import { TemplateDirectoryAccess } from '../../lib/filesystem'; +import { FileBrowser } from './FileBrowser'; +import { + DirectoryEditorProvider, + useDirectoryEditor, +} from './DirectoryEditorContext'; +import { DryRunProvider, useDryRun } from './DryRunContext'; +import { TemplateEditorDryRunResults } from './TemplateEditorDryRunResults'; + +const useStyles = makeStyles(theme => ({ + // Reset and fix sizing to make sure scrolling behaves correctly + rootWrapper: { + gridArea: 'pageContent', + position: 'relative', + width: '100%', + }, + root: { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + + display: 'grid', + gridTemplateAreas: ` + "browser editor preview" + "results results results" + `, + gridTemplateColumns: '1fr 3fr 2fr', + gridTemplateRows: '1fr auto', + }, + browser: { + gridArea: 'browser', + overflow: 'auto', + }, + browserButton: { + padding: theme.spacing(1), + }, + browserButtons: { + display: 'flex', + flexFlow: 'row nowrap', + alignItems: 'center', + justifyContent: 'flex-start', + }, + browserButtonsGap: { + flex: '1 1 auto', + }, + browserButtonsDivider: { + marginBottom: theme.spacing(1), + }, + editor: { + position: 'relative', + gridArea: 'editor', + overflow: 'auto', + }, + editorCodeMirror: { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + }, + preview: { + gridArea: 'preview', + overflow: 'auto', + }, + results: { + gridArea: 'results', + }, +})); + +export const TemplateEditor = ({ + directory, + fieldExtensions = [], + onClose, +}: { + directory: TemplateDirectoryAccess; + fieldExtensions?: FieldExtensionOptions[]; + onClose?: () => void; +}) => { + const classes = useStyles(); + + const [errorText, setErrorText] = useState(); + + return ( + + +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+
+
+ ); +}; + +function TemplateEditorBrowser() { + const classes = useStyles(); + const directoryEditor = useDirectoryEditor(); + + return ( + <> +
+ + + + + + +
+ + + +
+ + file.path)} + /> + + ); +} + +function TemplateEditorTextArea(props: { errorText?: string }) { + const { errorText } = props; + const classes = useStyles(); + const directoryEditor = useDirectoryEditor(); + + const errorPanel = useMemo(() => { + const div = document.createElement('div'); + div.style.color = 'red'; + return div; + }, []); + + useEffect(() => { + errorPanel.textContent = errorText ?? ''; + }, [errorPanel, errorText]); + + return ( + ({ dom: errorPanel, top: true })), + ]} + value={directoryEditor.selectedFile?.content} + onChange={content => directoryEditor.selectedFile?.updateContent(content)} + /> + ); +} + +interface ErrorBoundaryProps { + generation: number; + setErrorText(errorText: string | undefined): void; + children: ReactNode; +} + +interface ErrorBoundaryState { + shouldRender: boolean; +} + +class ErrorBoundary extends Component { + state = { + shouldRender: true, + }; + + componentDidUpdate(prevProps: { generation: number }) { + if (prevProps.generation !== this.props.generation) { + this.setState({ shouldRender: true }); + } + } + + componentDidCatch(error: Error) { + this.props.setErrorText(error.message); + this.setState({ shouldRender: false }); + } + + render() { + return this.state.shouldRender ? this.props.children : null; + } +} + +interface TemplateFormState { + filePath?: string; + content?: string; + steps?: TemplateParameterSchema['steps']; + formData: JsonObject; + schemaGeneration: number; +} + +type TemplateFormAction = + | { + type: 'reset'; + } + | { + type: 'updateData'; + formData: JsonObject; + } + | { + type: 'updateSchema'; + steps: TemplateParameterSchema['steps']; + filePath: string; + }; + +const initialTemplateFormState: TemplateFormState = { + steps: undefined, + filePath: undefined, + formData: {}, + // Used to reset the error boundary in edit + schemaGeneration: 0, +}; + +function templateFormReducer( + state: TemplateFormState, + action: TemplateFormAction, +): TemplateFormState { + switch (action.type) { + case 'reset': { + return initialTemplateFormState; + } + case 'updateData': { + return { + ...state, + formData: action.formData, + }; + } + case 'updateSchema': { + const { filePath, steps } = action; + + return { + steps, + filePath, + formData: state.filePath === filePath ? state.formData : {}, + schemaGeneration: state.schemaGeneration + 1, + }; + } + default: + return state; + } +} + +interface TemplateEditorFormProps { + setErrorText: (errorText?: string) => void; + fieldExtensions?: FieldExtensionOptions[]; +} + +function isJsonObject(value: JsonValue | undefined): value is JsonObject { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +function TemplateEditorForm(props: TemplateEditorFormProps) { + const { setErrorText, fieldExtensions = [] } = props; + const dryRun = useDryRun(); + + const apiHolder = useApiHolder(); + const directoryEditor = useDirectoryEditor(); + const { selectedFile } = directoryEditor; + const [state, dispatch] = useReducer( + templateFormReducer, + initialTemplateFormState, + ); + + useDebounce( + () => { + try { + if (!selectedFile || !selectedFile.path.match(/\.ya?ml$/)) { + dispatch({ type: 'reset' }); + return; + } + const parsed: JsonValue = yaml.parse(selectedFile.content); + const isTemplate = + typeof parsed === 'object' && + parsed !== null && + 'kind' in parsed && + typeof parsed.kind === 'string' && + parsed.kind.toLocaleLowerCase('en-US') === 'template'; + + if (!isTemplate) { + dispatch({ type: 'reset' }); + return; + } + + const spec = parsed.spec; + const parameters = isJsonObject(spec) && spec.parameters; + if (!Array.isArray(parameters)) { + setErrorText('Template parameters must be an array'); + return; + } + + const fieldValidators = Object.fromEntries( + fieldExtensions.map(({ name, validation }) => [name, validation]), + ); + + setErrorText(); + + dispatch({ + type: 'updateSchema', + filePath: selectedFile.path, + steps: parameters.flatMap(param => + isJsonObject(param) + ? [ + { + title: String(param.title), + schema: param, + validate: createValidator(param, fieldValidators, { + apiHolder, + }), + }, + ] + : [], + ), + }); + } catch (e) { + setErrorText(e.message); + } + }, + 250, + [selectedFile?.path, selectedFile?.content, apiHolder], + ); + + const fields = useMemo(() => { + return Object.fromEntries( + fieldExtensions.map(({ name, component }) => [name, component]), + ); + }, [fieldExtensions]); + + if (!state.steps) { + return null; + } + + const handleDryRun = async () => { + if (!selectedFile) { + return; + } + + await dryRun.execute({ + templateContent: selectedFile.content, + values: state.formData, + files: directoryEditor.files, + }); + }; + + return ( + + dispatch({ type: 'updateData', formData: e.formData })} + onReset={() => dispatch({ type: 'updateData', formData: {} })} + finishButtonLabel="Try It" + onFinish={handleDryRun} + /> + + ); +} diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx new file mode 100644 index 0000000000..756bf8421c --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx @@ -0,0 +1,364 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { makeStyles } from '@material-ui/core/styles'; +import Divider from '@material-ui/core/Divider'; +import IconButton from '@material-ui/core/IconButton'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; +import ListItemText from '@material-ui/core/ListItemText'; +import Accordion from '@material-ui/core/Accordion'; +import AccordionSummary from '@material-ui/core/AccordionSummary'; +import AccordionDetails from '@material-ui/core/AccordionDetails'; +import Typography from '@material-ui/core/Typography'; +import Tabs from '@material-ui/core/Tabs'; +import Tab from '@material-ui/core/Tab'; +import Box from '@material-ui/core/Box'; +import React, { + Children, + ReactNode, + useEffect, + useMemo, + useState, +} from 'react'; +import classNames from 'classnames'; +import { useDryRun } from './DryRunContext'; +import DeleteIcon from '@material-ui/icons/Delete'; +import CheckIcon from '@material-ui/icons/Check'; +import CancelIcon from '@material-ui/icons/Cancel'; +import ExpandMoreIcon from '@material-ui/icons/ExpandLess'; +import { FileBrowser } from './FileBrowser'; +import CodeMirror from '@uiw/react-codemirror'; +import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; +import { StreamLanguage } from '@codemirror/stream-parser'; +import { LogViewer } from '@backstage/core-components'; +import { usePrevious } from '@react-hookz/web'; +import { TaskStatusStepper } from '../TaskPage/TaskPage'; +import { TaskPageLinks } from '../TaskPage/TaskPageLinks'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import { BackstageTheme } from '@backstage/theme'; + +const useStyles = makeStyles((theme: BackstageTheme) => ({ + accordionHeader: { + height: 48, + minHeight: 0, + '&.Mui-expanded': { + height: 48, + minHeight: 0, + }, + }, + accordionContent: { + display: 'grid', + background: theme.palette.background.default, + gridTemplateColumns: '180px auto 1fr', + gridTemplateRows: '1fr', + padding: 0, + height: 400, + }, + resultList: { + overflowY: 'auto', + background: theme.palette.background.default, + }, + resultListIconSuccess: { + minWidth: 0, + marginRight: theme.spacing(1), + color: theme.palette.status.ok, + }, + resultListIconFailure: { + minWidth: 0, + marginRight: theme.spacing(1), + color: theme.palette.status.error, + }, + resultView: { + display: 'flex', + flexFlow: 'column nowrap', + }, + resultViewItemWrapper: { + flex: 1, + position: 'relative', + }, + resultViewItem: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + + display: 'flex', + '& > *': { + flex: 1, + }, + }, + codeMirror: { + height: '100%', + overflowY: 'auto', + }, +})); + +export function TemplateEditorDryRunResults() { + const classes = useStyles(); + const dryRun = useDryRun(); + const [expanded, setExpanded] = useState(true); + const [hidden, setHidden] = useState(true); + + const resultsLength = dryRun.results.length; + const prevResultsLength = usePrevious(resultsLength); + useEffect(() => { + if (prevResultsLength === 0 && resultsLength === 1) { + setExpanded(true); + setHidden(false); + } else if (prevResultsLength === 1 && resultsLength === 0) { + setExpanded(false); + } + }, [prevResultsLength, resultsLength]); + + return ( + <> + + + ); +} + +function ResultList() { + const classes = useStyles(); + const dryRun = useDryRun(); + + return ( + + {dryRun.results.map(result => { + const failed = result.log.some(l => l.status === 'failed'); + return ( + dryRun.selectResult(result.id)} + > + + {failed ? : } + + + + dryRun.deleteResult(result.id)} + > + + + + + ); + })} + + ); +} + +function ResultView() { + const classes = useStyles(); + const [selectedTab, setSelectedTab] = useState<'files' | 'log' | 'output'>( + 'files', + ); + + return ( +
+ setSelectedTab(v)}> + + + + + + +
+
+ {selectedTab === 'files' && } + {selectedTab === 'log' && } + {selectedTab === 'output' && } +
+
+
+ ); +} + +const useSplitViewStyles = makeStyles(theme => ({ + root: { + display: 'grid', + gridTemplateColumns: '1fr auto 3fr', + gridTemplateRows: '1fr', + }, + child: { + overflowY: 'auto', + height: '100%', + minHeight: 0, + }, + childPaper: { + background: theme.palette.background.paper, + }, +})); + +function SplitView(props: { children: ReactNode }) { + const classes = useSplitViewStyles(); + const childArray = Children.toArray(props.children); + + if (childArray.length !== 2) { + throw new Error('SplitView must have exactly 2 children'); + } + + return ( +
+
+ {childArray[0]} +
+ +
{childArray[1]}
+
+ ); +} + +function FilesContent() { + const classes = useStyles(); + const { selectedResult } = useDryRun(); + const [selectedPath, setSelectedPath] = useState(''); + const selectedFile = selectedResult?.content.find( + f => f.path === selectedPath, + ); + + useEffect(() => { + if (selectedResult) { + const [firstFile] = selectedResult.content; + if (firstFile) { + setSelectedPath(firstFile.path); + } else { + setSelectedPath(''); + } + } + return undefined; + }, [selectedResult]); + + if (!selectedResult) { + return null; + } + return ( + + file.path)} + /> + + + ); +} +function LogContent() { + const { selectedResult } = useDryRun(); + const [currentStepId, setUserSelectedStepId] = useState(); + + const steps = useMemo(() => { + if (!selectedResult) { + return []; + } + return ( + selectedResult.steps.map(step => { + const stepLog = selectedResult.log.filter(l => l.stepId === step.id); + return { + id: step.id, + name: step.name, + logString: stepLog.map(l => l.message).join('\n'), + status: stepLog[stepLog.length - 1]?.status ?? 'completed', + }; + }) ?? [] + ); + }, [selectedResult]); + + if (!selectedResult) { + return null; + } + + const selectedStep = steps.find(s => s.id === currentStepId) ?? steps[0]; + + return ( + + + + + ); +} + +function OutputContent() { + const classes = useStyles(); + const { selectedResult } = useDryRun(); + + if (!selectedResult) { + return null; + } + + return ( + + + {selectedResult.output?.links?.length && ( + + )} + + + + ); +} diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx index 71c64a9fa4..20de52a266 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx @@ -13,258 +13,147 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useCallback, useState } from 'react'; -import useAsync from 'react-use/lib/useAsync'; -import useDebounce from 'react-use/lib/useDebounce'; -import { Entity } from '@backstage/catalog-model'; -import { Content, Header, InfoCard, Page } from '@backstage/core-components'; -import { alertApiRef, useApi, useApiHolder } from '@backstage/core-plugin-api'; +import React, { useState } from 'react'; +import { Content, Header, Page } from '@backstage/core-components'; import { - catalogApiRef, - humanizeEntityRef, -} from '@backstage/plugin-catalog-react'; -import { JsonObject } from '@backstage/types'; -import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; -import { showPanel } from '@codemirror/view'; -import { StreamLanguage } from '@codemirror/language'; - -import { - FormControl, - Grid, - InputLabel, - LinearProgress, - makeStyles, - MenuItem, - Select, -} from '@material-ui/core'; -import { IChangeEvent } from '@rjsf/core'; -import CodeMirror from '@uiw/react-codemirror'; -import yaml from 'yaml'; + TemplateDirectoryAccess, + WebFileSystemAccess, +} from '../../lib/filesystem'; +import { EditorIntro } from './EditorIntro'; +import { TemplateEditor } from './TemplateEditor'; +import { TemplateFormEditor } from './TemplateFormEditor'; import { FieldExtensionOptions } from '../../extensions'; -import { TemplateParameterSchema } from '../../types'; -import { MultistepJsonForm } from '../MultistepJsonForm'; -import { createValidator } from '../TemplatePage'; +import { MockFileSystemAccess } from '../../lib/filesystem/MockFileSystemAccess'; -const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI -parameters: - - title: Fill in some steps - required: - - name - properties: - name: - title: Name - type: string - description: Unique name of the component - owner: - title: Owner - type: string - description: Owner of the component - ui:field: OwnerPicker - ui:options: - allowedKinds: - - Group - - title: Choose a location - required: - - repoUrl - properties: - repoUrl: - title: Repository Location - type: string - ui:field: RepoUrlPicker - ui:options: - allowedHosts: - - github.com -`; +type Selection = + | { + type: 'local'; + directory: TemplateDirectoryAccess; + } + | { + type: 'form'; + }; -type TemplateOption = { - label: string; - value: Entity; -}; - -const useStyles = makeStyles({ - templateSelect: { - marginBottom: '10px', - }, - grid: { - height: '100%', - }, - codeMirror: { - height: '95%', - }, -}); - -export const TemplateEditorPage = ({ - defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML, - customFieldExtensions = [], -}: { +interface TemplateEditorPageProps { defaultPreviewTemplate?: string; customFieldExtensions?: FieldExtensionOptions[]; -}) => { - const classes = useStyles(); - const alertApi = useApi(alertApiRef); - const catalogApi = useApi(catalogApiRef); - const apiHolder = useApiHolder(); - const [selectedTemplate, setSelectedTemplate] = useState(''); - const [schema, setSchema] = useState({ - title: '', - steps: [], +} + +export function TemplateEditorPage(props: TemplateEditorPageProps) { + const [selection, setSelection] = useState({ + type: 'local', + directory: MockFileSystemAccess.createMockDirectory({ + 'template.yaml': ` +apiVersion: scaffolder.backstage.io/v1beta3 +kind: Template +metadata: + name: bitbucket-demo +spec: + type: service + + parameters: + - title: Choose a name and location + required: + - name + - repoUrl + properties: + name: + title: Name + type: string + repoUrl: + title: Repository Location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - github.com + + steps: + - id: fetch-base + name: Fetch Base + action: fetch:template + input: + url: ./template + values: + name: \${{ parameters.name }} + - id: publish + name: Publish + action: publish:bitbucket + input: + description: This is \${{ parameters.name }} + repoUrl: \${{ parameters.repoUrl }} + + - id: register + name: Register + action: catalog:register + input: + repoContentsUrl: \${{ steps.publish.output.repoContentsUrl }} + catalogInfoPath: '/catalog-info.yaml' + + output: + links: + - title: Repository + url: \${{ steps.publish.output.remoteUrl }} + - title: Open in catalog + icon: catalog + entityRef: \${{ steps.register.output.entityRef }} +`, + 'template/catalog-info.yaml': ` +apiVersion: backstage.io/v1alpha1 +kind: Component +metadata: + name: \${{values.name | dump}} +spec: + type: website + lifecycle: experimental + owner: \${{values.owner | dump}} +`, + }), }); - const [templateOptions, setTemplateOptions] = useState([]); - const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate); - const [formState, setFormState] = useState({}); - const { loading } = useAsync( - () => - catalogApi - .getEntities({ - filter: { kind: 'template' }, - fields: [ - 'kind', - 'metadata.namespace', - 'metadata.name', - 'metadata.title', - 'spec.parameters', - ], - }) - .then(({ items }) => - setTemplateOptions( - items.map(template => ({ - label: - template.metadata.title ?? - humanizeEntityRef(template, { defaultKind: 'template' }), - value: template, - })), - ), - ) - .catch(e => - alertApi.post({ - message: `Error loading exisiting templates: ${e.message}`, - severity: 'error', - }), - ), - [catalogApi], - ); - - const errorPanel = document.createElement('div'); - errorPanel.style.color = 'red'; - - useDebounce( - () => { - try { - const parsedTemplate = yaml.parse(templateYaml); - - setSchema({ - title: 'Preview', - steps: parsedTemplate.parameters.map((param: JsonObject) => ({ - title: param.title, - schema: param, - })), - }); - setFormState({}); - } catch (e) { - errorPanel.textContent = e.message; - } - }, - 250, - [setFormState, setSchema, templateYaml], - ); - - const handleSelectChange = useCallback( - selected => { - setSelectedTemplate(selected); - setTemplateYaml(yaml.stringify(selected.spec)); - }, - [setTemplateYaml], - ); - - const handleFormReset = () => setFormState({}); - const handleFormChange = useCallback( - (e: IChangeEvent) => setFormState(e.formData), - [setFormState], - ); - - const handleCodeChange = useCallback( - (code: string) => { - setTemplateYaml(code); - }, - [setTemplateYaml], - ); - - const customFieldComponents = Object.fromEntries( - customFieldExtensions.map(({ name, component }) => [name, component]), - ); - - const customFieldValidators = Object.fromEntries( - customFieldExtensions.map(({ name, validation }) => [name, validation]), - ); + let content: JSX.Element | null = null; + if (selection?.type === 'local') { + content = ( + setSelection(undefined)} + /> + ); + } else if (selection?.type === 'form') { + content = ( + setSelection(undefined)} + /> + ); + } else { + content = ( + + { + if (option === 'local') { + WebFileSystemAccess.get() + .requestDirectoryAccess() + .then(directory => setSelection({ type: 'local', directory })) + .catch(() => {}); + } else if (option === 'form') { + setSelection({ type: 'form' }); + } + }} + /> + + ); + } return (
- - {loading && } - - - - - Load Existing Template - - - - ({ dom: errorPanel, top: true })), - ]} - onChange={handleCodeChange} - /> - - - {schema && ( - - { - return { - ...step, - validate: createValidator( - step.schema, - customFieldValidators, - { apiHolder }, - ), - }; - })} - /> - - )} - - - + {content} ); -}; +} diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormEditor.tsx new file mode 100644 index 0000000000..43c7f10a32 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormEditor.tsx @@ -0,0 +1,282 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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, { useCallback, useState } from 'react'; +import useAsync from 'react-use/lib/useAsync'; +import useDebounce from 'react-use/lib/useDebounce'; +import { Entity } from '@backstage/catalog-model'; +import { InfoCard } from '@backstage/core-components'; +import { alertApiRef, useApi, useApiHolder } from '@backstage/core-plugin-api'; +import { + catalogApiRef, + humanizeEntityRef, +} from '@backstage/plugin-catalog-react'; +import { JsonObject } from '@backstage/types'; +import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; +import { showPanel } from '@codemirror/view'; +import { StreamLanguage } from '@codemirror/language'; +import { + FormControl, + Grid, + IconButton, + InputLabel, + LinearProgress, + makeStyles, + MenuItem, + Select, +} from '@material-ui/core'; +import CloseIcon from '@material-ui/icons/Close'; +import { IChangeEvent } from '@rjsf/core'; +import CodeMirror from '@uiw/react-codemirror'; +import yaml from 'yaml'; +import { FieldExtensionOptions } from '../../extensions'; +import { TemplateParameterSchema } from '../../types'; +import { MultistepJsonForm } from '../MultistepJsonForm'; +import { createValidator } from '../TemplatePage'; + +const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI +parameters: + - title: Fill in some steps + required: + - name + properties: + name: + title: Name + type: string + description: Unique name of the component + owner: + title: Owner + type: string + description: Owner of the component + ui:field: OwnerPicker + ui:options: + allowedKinds: + - Group + - title: Choose a location + required: + - repoUrl + properties: + repoUrl: + title: Repository Location + type: string + ui:field: RepoUrlPicker + ui:options: + allowedHosts: + - github.com +steps: + - id: fetch-base + name: Fetch Base + action: fetch:template + input: + url: ./template + values: + name: \${{parameters.name}} +`; + +type TemplateOption = { + label: string; + value: Entity; +}; + +const useStyles = makeStyles(theme => ({ + controls: { + display: 'flex', + flexFlow: 'row nowrap', + alignItems: 'center', + marginBottom: theme.spacing(1), + }, + grid: { + height: '100%', + }, + codeMirror: { + height: '95%', + }, +})); + +export const TemplateFormEditor = ({ + defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML, + customFieldExtensions = [], + onClose, +}: { + defaultPreviewTemplate?: string; + customFieldExtensions?: FieldExtensionOptions[]; + onClose?: () => void; +}) => { + const classes = useStyles(); + const alertApi = useApi(alertApiRef); + const catalogApi = useApi(catalogApiRef); + const apiHolder = useApiHolder(); + const [selectedTemplate, setSelectedTemplate] = useState(''); + const [schema, setSchema] = useState({ + title: '', + steps: [], + }); + const [templateOptions, setTemplateOptions] = useState([]); + const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate); + const [formState, setFormState] = useState({}); + + const { loading } = useAsync( + () => + catalogApi + .getEntities({ + filter: { kind: 'template' }, + fields: [ + 'kind', + 'metadata.namespace', + 'metadata.name', + 'metadata.title', + 'spec.parameters', + 'spec.steps', + 'spec.output', + ], + }) + .then(({ items }) => + setTemplateOptions( + items.map(template => ({ + label: + template.metadata.title ?? + humanizeEntityRef(template, { defaultKind: 'template' }), + value: template, + })), + ), + ) + .catch(e => + alertApi.post({ + message: `Error loading exisiting templates: ${e.message}`, + severity: 'error', + }), + ), + [catalogApi], + ); + + const errorPanel = document.createElement('div'); + errorPanel.style.color = 'red'; + + useDebounce( + () => { + try { + const parsedTemplate = yaml.parse(templateYaml); + + setSchema({ + title: 'Preview', + steps: parsedTemplate.parameters.map((param: JsonObject) => ({ + title: param.title, + schema: param, + })), + }); + setFormState({}); + } catch (e) { + errorPanel.textContent = e.message; + } + }, + 250, + [setFormState, setSchema, templateYaml], + ); + + const handleSelectChange = useCallback( + selected => { + setSelectedTemplate(selected); + setTemplateYaml(yaml.stringify(selected.spec)); + }, + [setTemplateYaml], + ); + + const handleFormReset = () => setFormState({}); + const handleFormChange = useCallback( + (e: IChangeEvent) => setFormState(e.formData), + [setFormState], + ); + + const handleCodeChange = useCallback( + (code: string) => { + setTemplateYaml(code); + }, + [setTemplateYaml], + ); + + const customFieldComponents = Object.fromEntries( + customFieldExtensions.map(({ name, component }) => [name, component]), + ); + + const customFieldValidators = Object.fromEntries( + customFieldExtensions.map(({ name, validation }) => [name, validation]), + ); + + return ( + <> + {loading && } + + +
+ + + Load Existing Template + + + + + + + +
+ ({ dom: errorPanel, top: true })), + ]} + onChange={handleCodeChange} + /> +
+ + {schema && ( + + { + return { + ...step, + validate: createValidator( + step.schema, + customFieldValidators, + { apiHolder }, + ), + }; + })} + /> + + )} + +
+ + ); +}; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/index.ts b/plugins/scaffolder/src/components/TemplateEditorPage/index.ts index 506ff08f24..7ec6bddb64 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/index.ts +++ b/plugins/scaffolder/src/components/TemplateEditorPage/index.ts @@ -13,4 +13,5 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + export { TemplateEditorPage } from './TemplateEditorPage'; diff --git a/plugins/scaffolder/src/lib/filesystem/MockFileSystemAccess.ts b/plugins/scaffolder/src/lib/filesystem/MockFileSystemAccess.ts new file mode 100644 index 0000000000..d466543dbd --- /dev/null +++ b/plugins/scaffolder/src/lib/filesystem/MockFileSystemAccess.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { TemplateDirectoryAccess, TemplateFileAccess } from './types'; + +class MockFileAccess implements TemplateFileAccess { + constructor(readonly path: string, private content: string) {} + + async file(): Promise { + const blob = new Blob([this.content]); + return Object.assign(blob, { + name: this.path.split('/').pop()!, + lastModified: Date.now(), + webkitRelativePath: this.path, + }); + } + + async save(data: string | Blob | BufferSource): Promise { + this.content = await new Response(data).text(); + } +} + +class MockDirectoryAccess implements TemplateDirectoryAccess { + private readonly files = new Array(); + + constructor(inputFiles: Record) { + this.files = Object.entries(inputFiles).map( + ([path, content]) => new MockFileAccess(path, content), + ); + } + + async listFiles(): Promise { + return this.files; + } +} + +/** @internal */ +export class MockFileSystemAccess { + private constructor() {} + + static createMockDirectory(files: Record) { + return new MockDirectoryAccess(files); + } +} diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index bddd6a7b70..1291cd9951 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TaskSpec } from '@backstage/plugin-scaffolder-common'; +import { TaskSpec, TaskStep } from '@backstage/plugin-scaffolder-common'; import { JsonObject, JsonValue, Observable } from '@backstage/types'; import { JSONSchema7 } from 'json-schema'; @@ -149,6 +149,25 @@ export interface ScaffolderStreamLogsOptions { taskId: string; after?: number; } + +export interface ScaffolderDryRunOptions { + template: JsonValue; + values: JsonObject; + secrets: JsonObject; + content: { path: string; base64Content: string }[]; +} + +export interface ScaffolderDryRunResponse { + content: Array<{ + path: string; + base64Content: string; + executable: boolean; + }>; + log: Array; + steps: TaskStep[]; + output: ScaffolderTaskOutput; +} + /** * An API to interact with the scaffolder backend. * @@ -181,4 +200,6 @@ export interface ScaffolderApi { listActions(): Promise; streamLogs(options: ScaffolderStreamLogsOptions): Observable; + + dryRun?(options: ScaffolderDryRunOptions): Promise; } From 7c6ceb2d32e617ae2b93a750a6cbb7983e00150b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 24 Apr 2022 13:29:43 +0200 Subject: [PATCH 13/56] scaffolder: hook up template editor buttons + floating text area buttons and hotkey Signed-off-by: Patrik Oldsberg --- .../TemplateEditorPage/TemplateEditor.tsx | 131 ++++++++++++++---- 1 file changed, 101 insertions(+), 30 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx index 3361e3ab5a..d3f376c9ac 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx @@ -21,13 +21,20 @@ import React, { useReducer, useState, } from 'react'; +import { useKeyboardEvent } from '@react-hookz/web'; import useDebounce from 'react-use/lib/useDebounce'; import { useApiHolder } from '@backstage/core-plugin-api'; import { JsonObject, JsonValue } from '@backstage/types'; import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; import { showPanel } from '@codemirror/view'; import { StreamLanguage } from '@codemirror/language'; -import { IconButton, makeStyles, Divider } from '@material-ui/core'; +import { + IconButton, + makeStyles, + Divider, + Tooltip, + Paper, +} from '@material-ui/core'; import SaveIcon from '@material-ui/icons/Save'; import RefreshIcon from '@material-ui/icons/Refresh'; import CloseIcon from '@material-ui/icons/Close'; @@ -99,6 +106,11 @@ const useStyles = makeStyles(theme => ({ left: 0, right: 0, }, + editorFloatingButtons: { + position: 'absolute', + top: theme.spacing(1), + right: theme.spacing(3), + }, preview: { gridArea: 'preview', overflow: 'auto', @@ -108,11 +120,7 @@ const useStyles = makeStyles(theme => ({ }, })); -export const TemplateEditor = ({ - directory, - fieldExtensions = [], - onClose, -}: { +export const TemplateEditor = (props: { directory: TemplateDirectoryAccess; fieldExtensions?: FieldExtensionOptions[]; onClose?: () => void; @@ -122,12 +130,12 @@ export const TemplateEditor = ({ const [errorText, setErrorText] = useState(); return ( - +
- +
@@ -135,7 +143,7 @@ export const TemplateEditor = ({
@@ -148,23 +156,52 @@ export const TemplateEditor = ({ ); }; -function TemplateEditorBrowser() { +function TemplateEditorBrowser(props: { onClose?: () => void }) { const classes = useStyles(); const directoryEditor = useDirectoryEditor(); + const changedFiles = directoryEditor.files.filter(file => file.dirty); + + const handleClose = () => { + if (!props.onClose) { + return; + } + if (changedFiles.length > 0) { + // eslint-disable-next-line no-alert + const accepted = window.confirm( + 'Are you sure? Unsaved changes will be lost', + ); + if (!accepted) { + return; + } + } + props.onClose(); + }; return ( <>
- - - - - - + + directoryEditor.save()} + > + + + + + directoryEditor.reload()} + > + + +
- - - + + + + +
e.key === 's' && (e.ctrlKey || e.metaKey), + e => { + e.preventDefault(); + directoryEditor.save(); + }, + ); + return ( - ({ dom: errorPanel, top: true })), - ]} - value={directoryEditor.selectedFile?.content} - onChange={content => directoryEditor.selectedFile?.updateContent(content)} - /> + <> + ({ dom: errorPanel, top: true })), + ]} + value={directoryEditor.selectedFile?.content} + onChange={content => + directoryEditor.selectedFile?.updateContent(content) + } + /> + {directoryEditor.selectedFile?.dirty && ( +
+ + + directoryEditor.save()} + > + + + + + directoryEditor.reload()} + > + + + + +
+ )} + ); } From a818ce53a7f5ba5d92b52ec5fafb9594c856ecfe Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 24 Apr 2022 17:03:47 +0200 Subject: [PATCH 14/56] scaffolder: tweak TemplateEditor error display Signed-off-by: Patrik Oldsberg --- .../TemplateEditorPage/TemplateEditor.tsx | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx index d3f376c9ac..169a245af9 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx @@ -16,7 +16,6 @@ import React, { Component, ReactNode, - useEffect, useMemo, useReducer, useState, @@ -106,6 +105,11 @@ const useStyles = makeStyles(theme => ({ left: 0, right: 0, }, + editorErrorPanel: { + color: theme.palette.error.main, + lineHeight: 2, + margin: theme.spacing(0, 1), + }, editorFloatingButtons: { position: 'absolute', top: theme.spacing(1), @@ -219,14 +223,14 @@ function TemplateEditorTextArea(props: { errorText?: string }) { const directoryEditor = useDirectoryEditor(); const errorPanel = useMemo(() => { + if (!errorText) { + return undefined; + } const div = document.createElement('div'); - div.style.color = 'red'; + div.classList.add(classes.editorErrorPanel); + div.textContent = errorText; return div; - }, []); - - useEffect(() => { - errorPanel.textContent = errorText ?? ''; - }, [errorPanel, errorText]); + }, [classes, errorText]); useKeyboardEvent( e => e.key === 's' && (e.ctrlKey || e.metaKey), @@ -244,7 +248,9 @@ function TemplateEditorTextArea(props: { errorText?: string }) { height="100%" extensions={[ StreamLanguage.define(yamlSupport), - showPanel.of(() => ({ dom: errorPanel, top: true })), + ...(errorPanel + ? [showPanel.of(() => ({ dom: errorPanel, bottom: true }))] + : []), ]} value={directoryEditor.selectedFile?.content} onChange={content => From d989e0c2030de5afed76337758e5906daf01f4e1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 24 Apr 2022 17:16:55 +0200 Subject: [PATCH 15/56] scaffolder: remove mock editor data Signed-off-by: Patrik Oldsberg --- .../TemplateEditorPage/DryRunContext.tsx | 313 +----------------- .../TemplateEditorPage/TemplateEditorPage.tsx | 72 +--- 2 files changed, 3 insertions(+), 382 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx index 1b81b517d3..ea687095db 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx @@ -52,323 +52,14 @@ interface DryRunProviderProps { children: ReactNode; } -const fakeResults: DryRunResult[] = [ - { - id: 1, - content: [ - { - path: 'catalog-info.yaml', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - { - path: 'catalog-info.yaml1', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - { - path: 'catalog-info.yaml2', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - { - path: 'catalog-info.yaml3', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - { - path: 'catalog-info.yaml4', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - { - path: 'catalog-info.yaml5', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - { - path: 'catalog-info.yaml6', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - { - path: 'catalog-info.yaml7', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - { - path: 'catalog-info.yaml8', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - { - path: 'catalog-info.yaml9', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - { - path: 'catalog-info.yaml10', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - { - path: 'catalog-info.yaml11', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - { - path: 'catalog-info.yaml12', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - { - path: 'catalog-info.yaml13', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - { - path: 'catalog-info.yaml14', - executable: false, - base64Content: - 'CmFwaVZlcnNpb246IGJhY2tzdGFnZS5pby92MWFscGhhMQpraW5kOiBDb21wb25lbnQKbWV0YWRhdGE6CiAgbmFtZTogImFzZCIKc3BlYzoKICB0eXBlOiB3ZWJzaXRlCiAgbGlmZWN5Y2xlOiBleHBlcmltZW50YWwKICBvd25lcjogCg==', - }, - ], - steps: [ - { - id: 'fetch-base', - name: 'Fetch stuff', - action: 'fetch:plain', - }, - { - id: 'fetch-base2', - name: 'Fetch stuff', - action: 'fetch:plain', - }, - { - id: 'fetch-base3', - name: 'Fetch stuff', - action: 'fetch:plain', - }, - { - id: 'fetch-base4', - name: 'Fetch stuff', - action: 'fetch:plain', - }, - { - id: 'fetch-base5', - name: 'Fetch stuff', - action: 'fetch:plain', - }, - { - id: 'publish', - name: 'Publish Stuff', - action: 'publish:github', - }, - { - id: 'register', - name: 'Register Stuff', - action: 'catalog:register', - }, - ], - log: [ - { - message: 'Starting up task with 4 steps', - }, - { - stepId: 'fetch-base', - status: 'processing', - message: 'Beginning step Fetch Base', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Fetching template content from remote URL {"timestamp":"2022-04-18T11:25:46.888Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Listing files and directories in template {"timestamp":"2022-04-18T11:25:46.889Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Processing 1 template files/directories with input values {"name":"asd","timestamp":"2022-04-18T11:25:46.890Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Writing file catalog-info.yaml to template output path with mode 33188. {"timestamp":"2022-04-18T11:25:46.932Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - message: - '\u001b[32minfo\u001b[39m: Template result written to /var/folders/ll/hcvgyy216t70dkjxwkk7csn00000gn/T/dry-run-5c024be8-8a08-4a10-a502-b43c64ed9061 {"timestamp":"2022-04-18T11:25:46.936Z"}', - }, - { - stepId: 'fetch-base', - status: 'completed', - message: 'Finished step Fetch Base', - }, - { - stepId: 'fetch-base2', - status: 'completed', - message: 'Finished step Fetch Base', - }, - { - stepId: 'fetch-base3', - status: 'completed', - message: 'Finished step Fetch Base', - }, - { - stepId: 'fetch-base4', - status: 'completed', - message: 'Finished step Fetch Base', - }, - { - stepId: 'fetch-base5', - status: 'completed', - message: 'Finished step Fetch Base', - }, - { - stepId: 'publish', - status: 'processing', - message: 'Beginning step Publish', - }, - { - stepId: 'publish', - status: 'completed', - message: 'Skipping because publish:bitbucket does not support dry-run', - }, - { - stepId: 'register', - status: 'processing', - message: 'Beginning step Register', - }, - { - stepId: 'register', - status: 'completed', - message: 'Skipping because catalog:register does not support dry-run', - }, - ], - output: { - links: [ - { - title: 'Repository', - url: '', - }, - { - title: 'Open in catalog', - icon: 'catalog', - entityRef: 'entity', - }, - ], - }, - }, -]; - export function DryRunProvider(props: DryRunProviderProps) { const scaffolderApi = useApi(scaffolderApiRef); const [state, setState] = useState< Pick >({ - results: [ - { ...fakeResults[0], id: 1 }, - { ...fakeResults[0], id: 2 }, - { ...fakeResults[0], id: 3 }, - { ...fakeResults[0], id: 4 }, - { ...fakeResults[0], id: 5 }, - ], - selectedResult: fakeResults[0], + results: [], + selectedResult: undefined, }); const idRef = useRef(1); diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx index 20de52a266..f2f603337d 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx @@ -23,7 +23,6 @@ import { EditorIntro } from './EditorIntro'; import { TemplateEditor } from './TemplateEditor'; import { TemplateFormEditor } from './TemplateFormEditor'; import { FieldExtensionOptions } from '../../extensions'; -import { MockFileSystemAccess } from '../../lib/filesystem/MockFileSystemAccess'; type Selection = | { @@ -40,76 +39,7 @@ interface TemplateEditorPageProps { } export function TemplateEditorPage(props: TemplateEditorPageProps) { - const [selection, setSelection] = useState({ - type: 'local', - directory: MockFileSystemAccess.createMockDirectory({ - 'template.yaml': ` -apiVersion: scaffolder.backstage.io/v1beta3 -kind: Template -metadata: - name: bitbucket-demo -spec: - type: service - - parameters: - - title: Choose a name and location - required: - - name - - repoUrl - properties: - name: - title: Name - type: string - repoUrl: - title: Repository Location - type: string - ui:field: RepoUrlPicker - ui:options: - allowedHosts: - - github.com - - steps: - - id: fetch-base - name: Fetch Base - action: fetch:template - input: - url: ./template - values: - name: \${{ parameters.name }} - - id: publish - name: Publish - action: publish:bitbucket - input: - description: This is \${{ parameters.name }} - repoUrl: \${{ parameters.repoUrl }} - - - id: register - name: Register - action: catalog:register - input: - repoContentsUrl: \${{ steps.publish.output.repoContentsUrl }} - catalogInfoPath: '/catalog-info.yaml' - - output: - links: - - title: Repository - url: \${{ steps.publish.output.remoteUrl }} - - title: Open in catalog - icon: catalog - entityRef: \${{ steps.register.output.entityRef }} -`, - 'template/catalog-info.yaml': ` -apiVersion: backstage.io/v1alpha1 -kind: Component -metadata: - name: \${{values.name | dump}} -spec: - type: website - lifecycle: experimental - owner: \${{values.owner | dump}} -`, - }), - }); + const [selection, setSelection] = useState(); let content: JSX.Element | null = null; if (selection?.type === 'local') { From dc1d67fc5b3f9619f3de278e3ceac9429e57a968 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 24 Apr 2022 18:18:43 +0200 Subject: [PATCH 16/56] scaffolder: tweak TemplateEditor error panel Signed-off-by: Patrik Oldsberg --- .../TemplateEditorPage/TemplateEditor.tsx | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx index 169a245af9..fa5de7f7ea 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx @@ -222,14 +222,15 @@ function TemplateEditorTextArea(props: { errorText?: string }) { const classes = useStyles(); const directoryEditor = useDirectoryEditor(); - const errorPanel = useMemo(() => { + const panelExtension = useMemo(() => { if (!errorText) { - return undefined; + return showPanel.of(null); } - const div = document.createElement('div'); - div.classList.add(classes.editorErrorPanel); - div.textContent = errorText; - return div; + + const dom = document.createElement('div'); + dom.classList.add(classes.editorErrorPanel); + dom.textContent = errorText; + return showPanel.of(() => ({ dom, bottom: true })); }, [classes, errorText]); useKeyboardEvent( @@ -246,12 +247,7 @@ function TemplateEditorTextArea(props: { errorText?: string }) { className={classes.editorCodeMirror} theme="dark" height="100%" - extensions={[ - StreamLanguage.define(yamlSupport), - ...(errorPanel - ? [showPanel.of(() => ({ dom: errorPanel, bottom: true }))] - : []), - ]} + extensions={[StreamLanguage.define(yamlSupport), panelExtension]} value={directoryEditor.selectedFile?.content} onChange={content => directoryEditor.selectedFile?.updateContent(content) From a9e587c76163026be7219645399d3d25420ba809 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 24 Apr 2022 18:19:10 +0200 Subject: [PATCH 17/56] scaffolder: fix template dry run results panel not expanding on first show Signed-off-by: Patrik Oldsberg --- .../TemplateEditorPage/TemplateEditorDryRunResults.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx index 756bf8421c..3e82c62aa1 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx @@ -112,15 +112,15 @@ const useStyles = makeStyles((theme: BackstageTheme) => ({ export function TemplateEditorDryRunResults() { const classes = useStyles(); const dryRun = useDryRun(); - const [expanded, setExpanded] = useState(true); + const [expanded, setExpanded] = useState(false); const [hidden, setHidden] = useState(true); const resultsLength = dryRun.results.length; const prevResultsLength = usePrevious(resultsLength); useEffect(() => { if (prevResultsLength === 0 && resultsLength === 1) { - setExpanded(true); setHidden(false); + setExpanded(true); } else if (prevResultsLength === 1 && resultsLength === 0) { setExpanded(false); } From 84e3c67ae361b3ba7739cd496ca5ac97ffdff223 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 24 Apr 2022 18:19:41 +0200 Subject: [PATCH 18/56] scaffolder: fix width for dry run results view side pane Signed-off-by: Patrik Oldsberg --- .../TemplateEditorPage/TemplateEditorDryRunResults.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx index 3e82c62aa1..93c42b3384 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx @@ -222,7 +222,7 @@ function ResultView() { const useSplitViewStyles = makeStyles(theme => ({ root: { display: 'grid', - gridTemplateColumns: '1fr auto 3fr', + gridTemplateColumns: '280px auto 3fr', gridTemplateRows: '1fr', }, child: { From 93af1ae48eb6e25c8919c1b150c34155eecae6be Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 25 Apr 2022 23:19:06 +0200 Subject: [PATCH 19/56] scaffolder: move TemplateEditor components into subdirectory Signed-off-by: Patrik Oldsberg --- .../DirectoryEditorContext.tsx | 2 +- .../{ => TemplateEditor}/DirectoryLoader.tsx | 2 +- .../{ => TemplateEditor}/DryRunContext.tsx | 4 ++-- .../{ => TemplateEditor}/FileBrowser.test.tsx | 0 .../{ => TemplateEditor}/FileBrowser.tsx | 2 +- .../{ => TemplateEditor}/TemplateEditor.tsx | 10 +++++----- .../TemplateEditorDryRunResults.tsx | 4 ++-- .../TemplateEditorPage/TemplateEditor/index.ts | 17 +++++++++++++++++ 8 files changed, 29 insertions(+), 12 deletions(-) rename plugins/scaffolder/src/components/TemplateEditorPage/{ => TemplateEditor}/DirectoryEditorContext.tsx (99%) rename plugins/scaffolder/src/components/TemplateEditorPage/{ => TemplateEditor}/DirectoryLoader.tsx (97%) rename plugins/scaffolder/src/components/TemplateEditorPage/{ => TemplateEditor}/DryRunContext.tsx (97%) rename plugins/scaffolder/src/components/TemplateEditorPage/{ => TemplateEditor}/FileBrowser.test.tsx (100%) rename plugins/scaffolder/src/components/TemplateEditorPage/{ => TemplateEditor}/FileBrowser.tsx (99%) rename plugins/scaffolder/src/components/TemplateEditorPage/{ => TemplateEditor}/TemplateEditor.tsx (97%) rename plugins/scaffolder/src/components/TemplateEditorPage/{ => TemplateEditor}/TemplateEditorDryRunResults.tsx (98%) create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/index.ts diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DirectoryEditorContext.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DirectoryEditorContext.tsx similarity index 99% rename from plugins/scaffolder/src/components/TemplateEditorPage/DirectoryEditorContext.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DirectoryEditorContext.tsx index 0edf390bfe..74073e797b 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DirectoryEditorContext.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DirectoryEditorContext.tsx @@ -20,7 +20,7 @@ import React, { createContext, ReactNode, useContext, useEffect } from 'react'; import { TemplateDirectoryAccess, TemplateFileAccess, -} from '../../lib/filesystem'; +} from '../../../lib/filesystem'; const MAX_SIZE = 1024 * 1024; const MAX_SIZE_MESSAGE = 'This file is too large to be displayed'; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DirectoryLoader.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DirectoryLoader.tsx similarity index 97% rename from plugins/scaffolder/src/components/TemplateEditorPage/DirectoryLoader.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DirectoryLoader.tsx index 65f0c21e32..5ba68389c8 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DirectoryLoader.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DirectoryLoader.tsx @@ -20,7 +20,7 @@ import { useAsync } from '@react-hookz/web'; import { TemplateDirectoryAccess, WebFileSystemAccess, -} from '../../lib/filesystem'; +} from '../../../lib/filesystem'; interface DirectoryLoaderProps { onLoad(directory: TemplateDirectoryAccess): void; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunContext.tsx similarity index 97% rename from plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunContext.tsx index ea687095db..68cee21b77 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunContext.tsx @@ -24,8 +24,8 @@ import React, { useRef, useState, } from 'react'; -import { scaffolderApiRef } from '../../api'; -import { ScaffolderDryRunResponse } from '../../types'; +import { scaffolderApiRef } from '../../../api'; +import { ScaffolderDryRunResponse } from '../../../types'; interface DryRunOptions { templateContent: string; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/FileBrowser.test.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/FileBrowser.test.tsx similarity index 100% rename from plugins/scaffolder/src/components/TemplateEditorPage/FileBrowser.test.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/FileBrowser.test.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/FileBrowser.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/FileBrowser.tsx similarity index 99% rename from plugins/scaffolder/src/components/TemplateEditorPage/FileBrowser.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/FileBrowser.tsx index 705f5776c9..a815fc06f2 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/FileBrowser.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/FileBrowser.tsx @@ -23,7 +23,7 @@ import TreeItem from '@material-ui/lab/TreeItem'; import { TemplateDirectoryAccess, TemplateFileAccess, -} from '../../lib/filesystem'; +} from '../../../lib/filesystem'; import { useAsync, useMountEffect } from '@react-hookz/web'; import { ErrorPanel, Progress } from '@backstage/core-components'; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx similarity index 97% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx index fa5de7f7ea..b05fe632c0 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx @@ -39,11 +39,11 @@ import RefreshIcon from '@material-ui/icons/Refresh'; import CloseIcon from '@material-ui/icons/Close'; import CodeMirror from '@uiw/react-codemirror'; import yaml from 'yaml'; -import { FieldExtensionOptions } from '../../extensions'; -import { TemplateParameterSchema } from '../../types'; -import { MultistepJsonForm } from '../MultistepJsonForm'; -import { createValidator } from '../TemplatePage'; -import { TemplateDirectoryAccess } from '../../lib/filesystem'; +import { FieldExtensionOptions } from '../../../extensions'; +import { TemplateParameterSchema } from '../../../types'; +import { MultistepJsonForm } from '../../MultistepJsonForm'; +import { createValidator } from '../../TemplatePage'; +import { TemplateDirectoryAccess } from '../../../lib/filesystem'; import { FileBrowser } from './FileBrowser'; import { DirectoryEditorProvider, diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorDryRunResults.tsx similarity index 98% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorDryRunResults.tsx index 93c42b3384..a4a14f6c73 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorDryRunResults.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorDryRunResults.tsx @@ -47,8 +47,8 @@ import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; import { StreamLanguage } from '@codemirror/stream-parser'; import { LogViewer } from '@backstage/core-components'; import { usePrevious } from '@react-hookz/web'; -import { TaskStatusStepper } from '../TaskPage/TaskPage'; -import { TaskPageLinks } from '../TaskPage/TaskPageLinks'; +import { TaskStatusStepper } from '../../TaskPage/TaskPage'; +import { TaskPageLinks } from '../../TaskPage/TaskPageLinks'; import ListItemIcon from '@material-ui/core/ListItemIcon'; import { BackstageTheme } from '@backstage/theme'; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/index.ts b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/index.ts new file mode 100644 index 0000000000..f0854db3a7 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { TemplateEditor } from './TemplateEditor'; From 633afdd01bf125fb09ebe3ec26d95ae089e7254f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 28 Apr 2022 13:45:54 +0200 Subject: [PATCH 20/56] scaffolder: fix codemirror import Signed-off-by: Patrik Oldsberg --- .../TemplateEditor/TemplateEditorDryRunResults.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorDryRunResults.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorDryRunResults.tsx index a4a14f6c73..ca96f63a36 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorDryRunResults.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorDryRunResults.tsx @@ -44,7 +44,7 @@ import ExpandMoreIcon from '@material-ui/icons/ExpandLess'; import { FileBrowser } from './FileBrowser'; import CodeMirror from '@uiw/react-codemirror'; import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; -import { StreamLanguage } from '@codemirror/stream-parser'; +import { StreamLanguage } from '@codemirror/language'; import { LogViewer } from '@backstage/core-components'; import { usePrevious } from '@react-hookz/web'; import { TaskStatusStepper } from '../../TaskPage/TaskPage'; From 7e41a70dee574accd7da76955ef3776a7f4d388c Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 28 Apr 2022 14:01:50 +0200 Subject: [PATCH 21/56] scaffolder: move TemplateEditorDryRunResults to subdir Signed-off-by: Patrik Oldsberg --- .../TemplateEditorDryRunResults.tsx | 8 ++++---- .../TemplateEditor/DryRunResults/index.ts | 17 +++++++++++++++++ .../TemplateEditor/TemplateEditor.tsx | 2 +- 3 files changed, 22 insertions(+), 5 deletions(-) rename plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/{ => DryRunResults}/TemplateEditorDryRunResults.tsx (97%) create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/index.ts diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorDryRunResults.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/TemplateEditorDryRunResults.tsx similarity index 97% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorDryRunResults.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/TemplateEditorDryRunResults.tsx index ca96f63a36..84ef1fc73c 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorDryRunResults.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/TemplateEditorDryRunResults.tsx @@ -36,19 +36,19 @@ import React, { useState, } from 'react'; import classNames from 'classnames'; -import { useDryRun } from './DryRunContext'; +import { useDryRun } from '../DryRunContext'; import DeleteIcon from '@material-ui/icons/Delete'; import CheckIcon from '@material-ui/icons/Check'; import CancelIcon from '@material-ui/icons/Cancel'; import ExpandMoreIcon from '@material-ui/icons/ExpandLess'; -import { FileBrowser } from './FileBrowser'; +import { FileBrowser } from '../FileBrowser'; import CodeMirror from '@uiw/react-codemirror'; import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; import { StreamLanguage } from '@codemirror/language'; import { LogViewer } from '@backstage/core-components'; import { usePrevious } from '@react-hookz/web'; -import { TaskStatusStepper } from '../../TaskPage/TaskPage'; -import { TaskPageLinks } from '../../TaskPage/TaskPageLinks'; +import { TaskStatusStepper } from '../../../TaskPage/TaskPage'; +import { TaskPageLinks } from '../../../TaskPage/TaskPageLinks'; import ListItemIcon from '@material-ui/core/ListItemIcon'; import { BackstageTheme } from '@backstage/theme'; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/index.ts b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/index.ts new file mode 100644 index 0000000000..60ddc60026 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { TemplateEditorDryRunResults } from './TemplateEditorDryRunResults'; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx index b05fe632c0..7d4b44914c 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx @@ -50,7 +50,7 @@ import { useDirectoryEditor, } from './DirectoryEditorContext'; import { DryRunProvider, useDryRun } from './DryRunContext'; -import { TemplateEditorDryRunResults } from './TemplateEditorDryRunResults'; +import { TemplateEditorDryRunResults } from './DryRunResults'; const useStyles = makeStyles(theme => ({ // Reset and fix sizing to make sure scrolling behaves correctly From bb95aca5aae5f2b43ab448b231546fcfd9725144 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 28 Apr 2022 14:03:55 +0200 Subject: [PATCH 22/56] scaffolder: extract DryRunResultsSplitView Signed-off-by: Patrik Oldsberg --- .../DryRunResults/DryRunResultsSplitView.tsx | 55 ++++++++++++++++++ .../TemplateEditorDryRunResults.tsx | 57 +++---------------- 2 files changed, 63 insertions(+), 49 deletions(-) create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsSplitView.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsSplitView.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsSplitView.tsx new file mode 100644 index 0000000000..b554a1c0c0 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsSplitView.tsx @@ -0,0 +1,55 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { makeStyles } from '@material-ui/core/styles'; +import Divider from '@material-ui/core/Divider'; +import React, { Children, ReactNode } from 'react'; +import classNames from 'classnames'; + +const useStyles = makeStyles(theme => ({ + root: { + display: 'grid', + gridTemplateColumns: '280px auto 3fr', + gridTemplateRows: '1fr', + }, + child: { + overflowY: 'auto', + height: '100%', + minHeight: 0, + }, + firstChild: { + background: theme.palette.background.paper, + }, +})); + +export function DryRunResultsSplitView(props: { children: ReactNode }) { + const classes = useStyles(); + const childArray = Children.toArray(props.children); + + if (childArray.length !== 2) { + throw new Error('must have exactly 2 children'); + } + + return ( +
+
+ {childArray[0]} +
+ +
{childArray[1]}
+
+ ); +} diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/TemplateEditorDryRunResults.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/TemplateEditorDryRunResults.tsx index 84ef1fc73c..5a429a3cc1 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/TemplateEditorDryRunResults.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/TemplateEditorDryRunResults.tsx @@ -28,14 +28,7 @@ import Typography from '@material-ui/core/Typography'; import Tabs from '@material-ui/core/Tabs'; import Tab from '@material-ui/core/Tab'; import Box from '@material-ui/core/Box'; -import React, { - Children, - ReactNode, - useEffect, - useMemo, - useState, -} from 'react'; -import classNames from 'classnames'; +import React, { useEffect, useMemo, useState } from 'react'; import { useDryRun } from '../DryRunContext'; import DeleteIcon from '@material-ui/icons/Delete'; import CheckIcon from '@material-ui/icons/Check'; @@ -51,6 +44,7 @@ import { TaskStatusStepper } from '../../../TaskPage/TaskPage'; import { TaskPageLinks } from '../../../TaskPage/TaskPageLinks'; import ListItemIcon from '@material-ui/core/ListItemIcon'; import { BackstageTheme } from '@backstage/theme'; +import { DryRunResultsSplitView } from './DryRunResultsSplitView'; const useStyles = makeStyles((theme: BackstageTheme) => ({ accordionHeader: { @@ -219,41 +213,6 @@ function ResultView() { ); } -const useSplitViewStyles = makeStyles(theme => ({ - root: { - display: 'grid', - gridTemplateColumns: '280px auto 3fr', - gridTemplateRows: '1fr', - }, - child: { - overflowY: 'auto', - height: '100%', - minHeight: 0, - }, - childPaper: { - background: theme.palette.background.paper, - }, -})); - -function SplitView(props: { children: ReactNode }) { - const classes = useSplitViewStyles(); - const childArray = Children.toArray(props.children); - - if (childArray.length !== 2) { - throw new Error('SplitView must have exactly 2 children'); - } - - return ( -
-
- {childArray[0]} -
- -
{childArray[1]}
-
- ); -} - function FilesContent() { const classes = useStyles(); const { selectedResult } = useDryRun(); @@ -278,7 +237,7 @@ function FilesContent() { return null; } return ( - + - + ); } function LogContent() { @@ -325,14 +284,14 @@ function LogContent() { const selectedStep = steps.find(s => s.id === currentStepId) ?? steps[0]; return ( - + - + ); } @@ -345,7 +304,7 @@ function OutputContent() { } return ( - + {selectedResult.output?.links?.length && ( @@ -359,6 +318,6 @@ function OutputContent() { readOnly value={JSON.stringify(selectedResult.output, null, 2)} /> - + ); } From 483aa3a47bdbf0e49f101401fa27b200e91798ab Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 28 Apr 2022 14:05:52 +0200 Subject: [PATCH 23/56] scaffolder: extract DryRunResultsList Signed-off-by: Patrik Oldsberg --- .../DryRunResults/DryRunResultsList.tsx | 83 +++++++++++++++++++ .../TemplateEditorDryRunResults.tsx | 67 +-------------- 2 files changed, 85 insertions(+), 65 deletions(-) create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsList.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsList.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsList.tsx new file mode 100644 index 0000000000..920f767cf8 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsList.tsx @@ -0,0 +1,83 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { BackstageTheme } from '@backstage/theme'; +import IconButton from '@material-ui/core/IconButton'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; +import ListItemText from '@material-ui/core/ListItemText'; +import { makeStyles } from '@material-ui/core/styles'; +import CancelIcon from '@material-ui/icons/Cancel'; +import CheckIcon from '@material-ui/icons/Check'; +import DeleteIcon from '@material-ui/icons/Delete'; +import React from 'react'; +import { useDryRun } from '../DryRunContext'; + +const useStyles = makeStyles((theme: BackstageTheme) => ({ + root: { + overflowY: 'auto', + background: theme.palette.background.default, + }, + iconSuccess: { + minWidth: 0, + marginRight: theme.spacing(1), + color: theme.palette.status.ok, + }, + iconFailure: { + minWidth: 0, + marginRight: theme.spacing(1), + color: theme.palette.status.error, + }, +})); + +export function DryRunResultsList() { + const classes = useStyles(); + const dryRun = useDryRun(); + + return ( + + {dryRun.results.map(result => { + const failed = result.log.some(l => l.status === 'failed'); + return ( + dryRun.selectResult(result.id)} + > + + {failed ? : } + + + + dryRun.deleteResult(result.id)} + > + + + + + ); + })} + + ); +} diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/TemplateEditorDryRunResults.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/TemplateEditorDryRunResults.tsx index 5a429a3cc1..fd49286818 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/TemplateEditorDryRunResults.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/TemplateEditorDryRunResults.tsx @@ -16,11 +16,6 @@ import { makeStyles } from '@material-ui/core/styles'; import Divider from '@material-ui/core/Divider'; -import IconButton from '@material-ui/core/IconButton'; -import List from '@material-ui/core/List'; -import ListItem from '@material-ui/core/ListItem'; -import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction'; -import ListItemText from '@material-ui/core/ListItemText'; import Accordion from '@material-ui/core/Accordion'; import AccordionSummary from '@material-ui/core/AccordionSummary'; import AccordionDetails from '@material-ui/core/AccordionDetails'; @@ -30,9 +25,6 @@ import Tab from '@material-ui/core/Tab'; import Box from '@material-ui/core/Box'; import React, { useEffect, useMemo, useState } from 'react'; import { useDryRun } from '../DryRunContext'; -import DeleteIcon from '@material-ui/icons/Delete'; -import CheckIcon from '@material-ui/icons/Check'; -import CancelIcon from '@material-ui/icons/Cancel'; import ExpandMoreIcon from '@material-ui/icons/ExpandLess'; import { FileBrowser } from '../FileBrowser'; import CodeMirror from '@uiw/react-codemirror'; @@ -42,9 +34,9 @@ import { LogViewer } from '@backstage/core-components'; import { usePrevious } from '@react-hookz/web'; import { TaskStatusStepper } from '../../../TaskPage/TaskPage'; import { TaskPageLinks } from '../../../TaskPage/TaskPageLinks'; -import ListItemIcon from '@material-ui/core/ListItemIcon'; import { BackstageTheme } from '@backstage/theme'; import { DryRunResultsSplitView } from './DryRunResultsSplitView'; +import { DryRunResultsList } from './DryRunResultsList'; const useStyles = makeStyles((theme: BackstageTheme) => ({ accordionHeader: { @@ -63,20 +55,6 @@ const useStyles = makeStyles((theme: BackstageTheme) => ({ padding: 0, height: 400, }, - resultList: { - overflowY: 'auto', - background: theme.palette.background.default, - }, - resultListIconSuccess: { - minWidth: 0, - marginRight: theme.spacing(1), - color: theme.palette.status.ok, - }, - resultListIconFailure: { - minWidth: 0, - marginRight: theme.spacing(1), - color: theme.palette.status.error, - }, resultView: { display: 'flex', flexFlow: 'column nowrap', @@ -137,7 +115,7 @@ export function TemplateEditorDryRunResults() { - + @@ -146,47 +124,6 @@ export function TemplateEditorDryRunResults() { ); } -function ResultList() { - const classes = useStyles(); - const dryRun = useDryRun(); - - return ( - - {dryRun.results.map(result => { - const failed = result.log.some(l => l.status === 'failed'); - return ( - dryRun.selectResult(result.id)} - > - - {failed ? : } - - - - dryRun.deleteResult(result.id)} - > - - - - - ); - })} - - ); -} - function ResultView() { const classes = useStyles(); const [selectedTab, setSelectedTab] = useState<'files' | 'log' | 'output'>( From 5edc82d02af5cf8c9a7b186be33517f6099ca4cc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 28 Apr 2022 14:09:01 +0200 Subject: [PATCH 24/56] scaffolder: extract DryRunResultsView + refactor DryRunResults Signed-off-by: Patrik Oldsberg --- .../DryRunResults/DryRunResults.tsx | 91 +++++++++++ ...ryRunResults.tsx => DryRunResultsView.tsx} | 151 +++++------------- .../TemplateEditor/DryRunResults/index.ts | 2 +- .../TemplateEditor/TemplateEditor.tsx | 4 +- 4 files changed, 136 insertions(+), 112 deletions(-) create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResults.tsx rename plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/{TemplateEditorDryRunResults.tsx => DryRunResultsView.tsx} (67%) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResults.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResults.tsx new file mode 100644 index 0000000000..ae9374438f --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResults.tsx @@ -0,0 +1,91 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { BackstageTheme } from '@backstage/theme'; +import Accordion from '@material-ui/core/Accordion'; +import AccordionDetails from '@material-ui/core/AccordionDetails'; +import AccordionSummary from '@material-ui/core/AccordionSummary'; +import Divider from '@material-ui/core/Divider'; +import { makeStyles } from '@material-ui/core/styles'; +import Typography from '@material-ui/core/Typography'; +import ExpandMoreIcon from '@material-ui/icons/ExpandLess'; +import { usePrevious } from '@react-hookz/web'; +import React, { useEffect, useState } from 'react'; +import { useDryRun } from '../DryRunContext'; +import { DryRunResultsList } from './DryRunResultsList'; +import { DryRunResultsView } from './DryRunResultsView'; + +const useStyles = makeStyles((theme: BackstageTheme) => ({ + header: { + height: 48, + minHeight: 0, + '&.Mui-expanded': { + height: 48, + minHeight: 0, + }, + }, + content: { + display: 'grid', + background: theme.palette.background.default, + gridTemplateColumns: '180px auto 1fr', + gridTemplateRows: '1fr', + padding: 0, + height: 400, + }, +})); + +export function DryRunResults() { + const classes = useStyles(); + const dryRun = useDryRun(); + const [expanded, setExpanded] = useState(false); + const [hidden, setHidden] = useState(true); + + const resultsLength = dryRun.results.length; + const prevResultsLength = usePrevious(resultsLength); + useEffect(() => { + if (prevResultsLength === 0 && resultsLength === 1) { + setHidden(false); + setExpanded(true); + } else if (prevResultsLength === 1 && resultsLength === 0) { + setExpanded(false); + } + }, [prevResultsLength, resultsLength]); + + return ( + <> + + + ); +} diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/TemplateEditorDryRunResults.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsView.tsx similarity index 67% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/TemplateEditorDryRunResults.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsView.tsx index fd49286818..1b03d80f34 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/TemplateEditorDryRunResults.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsView.tsx @@ -14,56 +14,32 @@ * limitations under the License. */ -import { makeStyles } from '@material-ui/core/styles'; -import Divider from '@material-ui/core/Divider'; -import Accordion from '@material-ui/core/Accordion'; -import AccordionSummary from '@material-ui/core/AccordionSummary'; -import AccordionDetails from '@material-ui/core/AccordionDetails'; -import Typography from '@material-ui/core/Typography'; -import Tabs from '@material-ui/core/Tabs'; -import Tab from '@material-ui/core/Tab'; -import Box from '@material-ui/core/Box'; -import React, { useEffect, useMemo, useState } from 'react'; -import { useDryRun } from '../DryRunContext'; -import ExpandMoreIcon from '@material-ui/icons/ExpandLess'; -import { FileBrowser } from '../FileBrowser'; -import CodeMirror from '@uiw/react-codemirror'; -import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; -import { StreamLanguage } from '@codemirror/language'; import { LogViewer } from '@backstage/core-components'; -import { usePrevious } from '@react-hookz/web'; +import { StreamLanguage } from '@codemirror/language'; +import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; +import Box from '@material-ui/core/Box'; +import Divider from '@material-ui/core/Divider'; +import { makeStyles } from '@material-ui/core/styles'; +import Tab from '@material-ui/core/Tab'; +import Tabs from '@material-ui/core/Tabs'; +import CodeMirror from '@uiw/react-codemirror'; +import React, { useEffect, useMemo, useState } from 'react'; import { TaskStatusStepper } from '../../../TaskPage/TaskPage'; import { TaskPageLinks } from '../../../TaskPage/TaskPageLinks'; -import { BackstageTheme } from '@backstage/theme'; +import { useDryRun } from '../DryRunContext'; +import { FileBrowser } from '../FileBrowser'; import { DryRunResultsSplitView } from './DryRunResultsSplitView'; -import { DryRunResultsList } from './DryRunResultsList'; -const useStyles = makeStyles((theme: BackstageTheme) => ({ - accordionHeader: { - height: 48, - minHeight: 0, - '&.Mui-expanded': { - height: 48, - minHeight: 0, - }, - }, - accordionContent: { - display: 'grid', - background: theme.palette.background.default, - gridTemplateColumns: '180px auto 1fr', - gridTemplateRows: '1fr', - padding: 0, - height: 400, - }, - resultView: { +const useStyles = makeStyles({ + root: { display: 'flex', flexFlow: 'column nowrap', }, - resultViewItemWrapper: { + contentWrapper: { flex: 1, position: 'relative', }, - resultViewItem: { + content: { position: 'absolute', top: 0, left: 0, @@ -79,76 +55,7 @@ const useStyles = makeStyles((theme: BackstageTheme) => ({ height: '100%', overflowY: 'auto', }, -})); - -export function TemplateEditorDryRunResults() { - const classes = useStyles(); - const dryRun = useDryRun(); - const [expanded, setExpanded] = useState(false); - const [hidden, setHidden] = useState(true); - - const resultsLength = dryRun.results.length; - const prevResultsLength = usePrevious(resultsLength); - useEffect(() => { - if (prevResultsLength === 0 && resultsLength === 1) { - setHidden(false); - setExpanded(true); - } else if (prevResultsLength === 1 && resultsLength === 0) { - setExpanded(false); - } - }, [prevResultsLength, resultsLength]); - - return ( - <> - - - ); -} - -function ResultView() { - const classes = useStyles(); - const [selectedTab, setSelectedTab] = useState<'files' | 'log' | 'output'>( - 'files', - ); - - return ( -
- setSelectedTab(v)}> - - - - - - -
-
- {selectedTab === 'files' && } - {selectedTab === 'log' && } - {selectedTab === 'output' && } -
-
-
- ); -} +}); function FilesContent() { const classes = useStyles(); @@ -258,3 +165,29 @@ function OutputContent() { ); } + +export function DryRunResultsView() { + const classes = useStyles(); + const [selectedTab, setSelectedTab] = useState<'files' | 'log' | 'output'>( + 'files', + ); + + return ( +
+ setSelectedTab(v)}> + + + + + + +
+
+ {selectedTab === 'files' && } + {selectedTab === 'log' && } + {selectedTab === 'output' && } +
+
+
+ ); +} diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/index.ts b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/index.ts index 60ddc60026..af8ef5214f 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/index.ts +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { TemplateEditorDryRunResults } from './TemplateEditorDryRunResults'; +export { DryRunResults } from './DryRunResults'; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx index 7d4b44914c..be3ae84d39 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx @@ -50,7 +50,7 @@ import { useDirectoryEditor, } from './DirectoryEditorContext'; import { DryRunProvider, useDryRun } from './DryRunContext'; -import { TemplateEditorDryRunResults } from './DryRunResults'; +import { DryRunResults } from './DryRunResults'; const useStyles = makeStyles(theme => ({ // Reset and fix sizing to make sure scrolling behaves correctly @@ -151,7 +151,7 @@ export const TemplateEditor = (props: { />
- +
From 5d63d15dbfe1b281d139ed8dc0e31943318989e0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 28 Apr 2022 14:13:36 +0200 Subject: [PATCH 25/56] scaffolder: extract FileBrowser Signed-off-by: Patrik Oldsberg --- .../FileBrowser.test.tsx | 0 .../FileBrowser.tsx | 50 ++----------------- .../src/components/FileBrowser/index.ts | 17 +++++++ .../DryRunResults/DryRunResultsView.tsx | 2 +- .../TemplateEditor/TemplateEditor.tsx | 2 +- 5 files changed, 24 insertions(+), 47 deletions(-) rename plugins/scaffolder/src/components/{TemplateEditorPage/TemplateEditor => FileBrowser}/FileBrowser.test.tsx (100%) rename plugins/scaffolder/src/components/{TemplateEditorPage/TemplateEditor => FileBrowser}/FileBrowser.tsx (76%) create mode 100644 plugins/scaffolder/src/components/FileBrowser/index.ts diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/FileBrowser.test.tsx b/plugins/scaffolder/src/components/FileBrowser/FileBrowser.test.tsx similarity index 100% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/FileBrowser.test.tsx rename to plugins/scaffolder/src/components/FileBrowser/FileBrowser.test.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/FileBrowser.tsx b/plugins/scaffolder/src/components/FileBrowser/FileBrowser.tsx similarity index 76% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/FileBrowser.tsx rename to plugins/scaffolder/src/components/FileBrowser/FileBrowser.tsx index a815fc06f2..1273b8ad09 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/FileBrowser.tsx +++ b/plugins/scaffolder/src/components/FileBrowser/FileBrowser.tsx @@ -20,12 +20,6 @@ import TreeView from '@material-ui/lab/TreeView'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import ChevronRightIcon from '@material-ui/icons/ChevronRight'; import TreeItem from '@material-ui/lab/TreeItem'; -import { - TemplateDirectoryAccess, - TemplateFileAccess, -} from '../../../lib/filesystem'; -import { useAsync, useMountEffect } from '@react-hookz/web'; -import { ErrorPanel, Progress } from '@backstage/core-components'; const useStyles = makeStyles({ root: { @@ -114,11 +108,15 @@ function FileTreeItem({ entry }: { entry: FileEntry }) { } interface FileBrowserProps { - selected?: string; + /** A list of all filepaths to show, directories are separated with a `/` */ filePaths: string[]; + /** The currently selected file */ + selected?: string; + /** Callback for when a file is selected */ onSelect?(filePath: string): void; } +/** A simple file browser that allows you to select individual files */ export function FileBrowser(props: FileBrowserProps) { const classes = useStyles(); @@ -145,41 +143,3 @@ export function FileBrowser(props: FileBrowserProps) { ); } - -interface TemplateDirectoryAccessBrowserProps { - directory: TemplateDirectoryAccess; - onSelect?(file: TemplateFileAccess): void; -} - -function TemplateDirectoryAccessBrowser( - props: TemplateDirectoryAccessBrowserProps, -) { - const [state, { execute }] = useAsync(async () => { - const files = await props.directory.listFiles(); - return { - filePaths: files.map(file => file.path), - getFile: (path: string) => files.find(file => file.path === path), - }; - }); - - useMountEffect(execute); - - if (state.error) { - return ; - } else if (!state.result) { - return ; - } - - const handleSelect = (path: string) => { - const file = state.result?.getFile(path); - if (file) { - props.onSelect?.(file); - } - }; - - return ( - - ); -} - -FileBrowser.TemplateDirectoryAccess = TemplateDirectoryAccessBrowser; diff --git a/plugins/scaffolder/src/components/FileBrowser/index.ts b/plugins/scaffolder/src/components/FileBrowser/index.ts new file mode 100644 index 0000000000..5d18109a9c --- /dev/null +++ b/plugins/scaffolder/src/components/FileBrowser/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { FileBrowser } from './FileBrowser'; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsView.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsView.tsx index 1b03d80f34..9e0f613d91 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsView.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsView.tsx @@ -27,7 +27,7 @@ import React, { useEffect, useMemo, useState } from 'react'; import { TaskStatusStepper } from '../../../TaskPage/TaskPage'; import { TaskPageLinks } from '../../../TaskPage/TaskPageLinks'; import { useDryRun } from '../DryRunContext'; -import { FileBrowser } from '../FileBrowser'; +import { FileBrowser } from '../../../FileBrowser'; import { DryRunResultsSplitView } from './DryRunResultsSplitView'; const useStyles = makeStyles({ diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx index be3ae84d39..e1900357ad 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx @@ -44,7 +44,7 @@ import { TemplateParameterSchema } from '../../../types'; import { MultistepJsonForm } from '../../MultistepJsonForm'; import { createValidator } from '../../TemplatePage'; import { TemplateDirectoryAccess } from '../../../lib/filesystem'; -import { FileBrowser } from './FileBrowser'; +import { FileBrowser } from '../../FileBrowser'; import { DirectoryEditorProvider, useDirectoryEditor, From 48307fbf0d834da86df8dd64b4606222cca126c3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 28 Apr 2022 14:14:55 +0200 Subject: [PATCH 26/56] scaffolder: remove unused DirectoryLoader Signed-off-by: Patrik Oldsberg --- .../TemplateEditor/DirectoryLoader.tsx | 53 ------------------- 1 file changed, 53 deletions(-) delete mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DirectoryLoader.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DirectoryLoader.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DirectoryLoader.tsx deleted file mode 100644 index 5ba68389c8..0000000000 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DirectoryLoader.tsx +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed 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 Button from '@material-ui/core/Button'; -import React from 'react'; -import { useAsync } from '@react-hookz/web'; -import { - TemplateDirectoryAccess, - WebFileSystemAccess, -} from '../../../lib/filesystem'; - -interface DirectoryLoaderProps { - onLoad(directory: TemplateDirectoryAccess): void; -} - -/** @internal */ -export function DirectoryLoader(props: DirectoryLoaderProps): JSX.Element { - const supportsWebAccess = WebFileSystemAccess.isSupported(); - - const [{ status, error }, { execute }] = useAsync(async () => { - if (!supportsWebAccess) { - return; - } - const directory = await WebFileSystemAccess.get().requestDirectoryAccess(); - props.onLoad(directory); - }); - - if (error) { - return
Fail: {error.message}
; - } - - return ( - - ); -} From a788dacc7372d302150f686d57c946f6240ba9c0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 28 Apr 2022 14:55:57 +0200 Subject: [PATCH 27/56] scaffolder: extract TemplateEditorTextArea Signed-off-by: Patrik Oldsberg --- .../TemplateEditor/TemplateEditor.tsx | 110 ++----------- .../TemplateEditor/TemplateEditorTextArea.tsx | 151 ++++++++++++++++++ 2 files changed, 161 insertions(+), 100 deletions(-) create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorTextArea.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx index e1900357ad..4e1b26441b 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx @@ -13,6 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { useApiHolder } from '@backstage/core-plugin-api'; +import { JsonObject, JsonValue } from '@backstage/types'; +import { Divider, IconButton, makeStyles, Tooltip } from '@material-ui/core'; +import CloseIcon from '@material-ui/icons/Close'; +import RefreshIcon from '@material-ui/icons/Refresh'; +import SaveIcon from '@material-ui/icons/Save'; import React, { Component, ReactNode, @@ -20,37 +26,21 @@ import React, { useReducer, useState, } from 'react'; -import { useKeyboardEvent } from '@react-hookz/web'; import useDebounce from 'react-use/lib/useDebounce'; -import { useApiHolder } from '@backstage/core-plugin-api'; -import { JsonObject, JsonValue } from '@backstage/types'; -import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; -import { showPanel } from '@codemirror/view'; -import { StreamLanguage } from '@codemirror/language'; -import { - IconButton, - makeStyles, - Divider, - Tooltip, - Paper, -} from '@material-ui/core'; -import SaveIcon from '@material-ui/icons/Save'; -import RefreshIcon from '@material-ui/icons/Refresh'; -import CloseIcon from '@material-ui/icons/Close'; -import CodeMirror from '@uiw/react-codemirror'; import yaml from 'yaml'; import { FieldExtensionOptions } from '../../../extensions'; +import { TemplateDirectoryAccess } from '../../../lib/filesystem'; import { TemplateParameterSchema } from '../../../types'; +import { FileBrowser } from '../../FileBrowser'; import { MultistepJsonForm } from '../../MultistepJsonForm'; import { createValidator } from '../../TemplatePage'; -import { TemplateDirectoryAccess } from '../../../lib/filesystem'; -import { FileBrowser } from '../../FileBrowser'; import { DirectoryEditorProvider, useDirectoryEditor, } from './DirectoryEditorContext'; import { DryRunProvider, useDryRun } from './DryRunContext'; import { DryRunResults } from './DryRunResults'; +import { TemplateEditorTextArea } from './TemplateEditorTextArea'; const useStyles = makeStyles(theme => ({ // Reset and fix sizing to make sure scrolling behaves correctly @@ -94,27 +84,9 @@ const useStyles = makeStyles(theme => ({ marginBottom: theme.spacing(1), }, editor: { - position: 'relative', gridArea: 'editor', overflow: 'auto', }, - editorCodeMirror: { - position: 'absolute', - top: 0, - bottom: 0, - left: 0, - right: 0, - }, - editorErrorPanel: { - color: theme.palette.error.main, - lineHeight: 2, - margin: theme.spacing(0, 1), - }, - editorFloatingButtons: { - position: 'absolute', - top: theme.spacing(1), - right: theme.spacing(3), - }, preview: { gridArea: 'preview', overflow: 'auto', @@ -142,7 +114,7 @@ export const TemplateEditor = (props: {
- +
void }) { ); } -function TemplateEditorTextArea(props: { errorText?: string }) { - const { errorText } = props; - const classes = useStyles(); - const directoryEditor = useDirectoryEditor(); - - const panelExtension = useMemo(() => { - if (!errorText) { - return showPanel.of(null); - } - - const dom = document.createElement('div'); - dom.classList.add(classes.editorErrorPanel); - dom.textContent = errorText; - return showPanel.of(() => ({ dom, bottom: true })); - }, [classes, errorText]); - - useKeyboardEvent( - e => e.key === 's' && (e.ctrlKey || e.metaKey), - e => { - e.preventDefault(); - directoryEditor.save(); - }, - ); - - return ( - <> - - directoryEditor.selectedFile?.updateContent(content) - } - /> - {directoryEditor.selectedFile?.dirty && ( -
- - - directoryEditor.save()} - > - - - - - directoryEditor.reload()} - > - - - - -
- )} - - ); -} - interface ErrorBoundaryProps { generation: number; setErrorText(errorText: string | undefined): void; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorTextArea.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorTextArea.tsx new file mode 100644 index 0000000000..3700348ef4 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorTextArea.tsx @@ -0,0 +1,151 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { StreamLanguage } from '@codemirror/language'; +import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; +import { showPanel } from '@codemirror/view'; +import { IconButton, makeStyles, Paper, Tooltip } from '@material-ui/core'; +import RefreshIcon from '@material-ui/icons/Refresh'; +import SaveIcon from '@material-ui/icons/Save'; +import { useKeyboardEvent } from '@react-hookz/web'; +import CodeMirror from '@uiw/react-codemirror'; +import React, { useMemo } from 'react'; +import { useDirectoryEditor } from './DirectoryEditorContext'; + +const useStyles = makeStyles(theme => ({ + container: { + position: 'relative', + width: '100%', + height: '100%', + }, + codeMirror: { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + }, + errorPanel: { + color: theme.palette.error.main, + lineHeight: 2, + margin: theme.spacing(0, 1), + }, + floatingButtons: { + position: 'absolute', + top: theme.spacing(1), + right: theme.spacing(3), + }, + floatingButton: { + padding: theme.spacing(1), + }, +})); + +/** A wrapper around CodeMirror with an error panel and extra actions available */ +export function TemplateEditorTextArea(props: { + content?: string; + onUpdate?: (content: string) => void; + errorText?: string; + onSave?: () => void; + onReload?: () => void; +}) { + const { errorText } = props; + const classes = useStyles(); + + const panelExtension = useMemo(() => { + if (!errorText) { + return showPanel.of(null); + } + + const dom = document.createElement('div'); + dom.classList.add(classes.errorPanel); + dom.textContent = errorText; + return showPanel.of(() => ({ dom, bottom: true })); + }, [classes, errorText]); + + useKeyboardEvent( + e => e.key === 's' && (e.ctrlKey || e.metaKey), + e => { + e.preventDefault(); + if (props.onSave) { + props.onSave(); + } + }, + ); + + return ( +
+ + {(props.onSave || props.onReload) && ( +
+ + {props.onSave && ( + + props.onSave?.()} + > + + + + )} + {props.onReload && ( + + props.onReload?.()} + > + + + + )} + +
+ )} +
+ ); +} + +/** A version of the TemplateEditorTextArea that is connected to the DirectoryEditor context */ +export function TemplateEditorDirectoryEditorTextArea(props: { + errorText?: string; +}) { + const directoryEditor = useDirectoryEditor(); + + const actions = directoryEditor.selectedFile?.dirty + ? { + onSave: () => directoryEditor.save(), + onReload: () => directoryEditor.reload(), + } + : undefined; + + return ( + directoryEditor.selectedFile?.updateContent(content)} + {...actions} + /> + ); +} + +TemplateEditorTextArea.DirectoryEditor = TemplateEditorDirectoryEditorTextArea; From 5146bc96fc5547f4ae9b5c9524d9ca1448c772be Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 29 Apr 2022 11:16:17 +0200 Subject: [PATCH 28/56] scaffolder: extract TemplateEditorForm Signed-off-by: Patrik Oldsberg --- .../TemplateEditor/TemplateEditor.tsx | 229 +----------------- .../TemplateEditor/TemplateEditorForm.tsx | 218 +++++++++++++++++ 2 files changed, 222 insertions(+), 225 deletions(-) create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorForm.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx index 4e1b26441b..f6b936e8af 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx @@ -13,33 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useApiHolder } from '@backstage/core-plugin-api'; -import { JsonObject, JsonValue } from '@backstage/types'; import { Divider, IconButton, makeStyles, Tooltip } from '@material-ui/core'; import CloseIcon from '@material-ui/icons/Close'; import RefreshIcon from '@material-ui/icons/Refresh'; import SaveIcon from '@material-ui/icons/Save'; -import React, { - Component, - ReactNode, - useMemo, - useReducer, - useState, -} from 'react'; -import useDebounce from 'react-use/lib/useDebounce'; -import yaml from 'yaml'; +import React, { useState } from 'react'; import { FieldExtensionOptions } from '../../../extensions'; import { TemplateDirectoryAccess } from '../../../lib/filesystem'; -import { TemplateParameterSchema } from '../../../types'; import { FileBrowser } from '../../FileBrowser'; -import { MultistepJsonForm } from '../../MultistepJsonForm'; -import { createValidator } from '../../TemplatePage'; import { DirectoryEditorProvider, useDirectoryEditor, } from './DirectoryEditorContext'; -import { DryRunProvider, useDryRun } from './DryRunContext'; +import { DryRunProvider } from './DryRunContext'; import { DryRunResults } from './DryRunResults'; +import { TemplateEditorForm } from './TemplateEditorForm'; import { TemplateEditorTextArea } from './TemplateEditorTextArea'; const useStyles = makeStyles(theme => ({ @@ -117,7 +105,7 @@ export const TemplateEditor = (props: {
- @@ -188,212 +176,3 @@ function TemplateEditorBrowser(props: { onClose?: () => void }) { ); } - -interface ErrorBoundaryProps { - generation: number; - setErrorText(errorText: string | undefined): void; - children: ReactNode; -} - -interface ErrorBoundaryState { - shouldRender: boolean; -} - -class ErrorBoundary extends Component { - state = { - shouldRender: true, - }; - - componentDidUpdate(prevProps: { generation: number }) { - if (prevProps.generation !== this.props.generation) { - this.setState({ shouldRender: true }); - } - } - - componentDidCatch(error: Error) { - this.props.setErrorText(error.message); - this.setState({ shouldRender: false }); - } - - render() { - return this.state.shouldRender ? this.props.children : null; - } -} - -interface TemplateFormState { - filePath?: string; - content?: string; - steps?: TemplateParameterSchema['steps']; - formData: JsonObject; - schemaGeneration: number; -} - -type TemplateFormAction = - | { - type: 'reset'; - } - | { - type: 'updateData'; - formData: JsonObject; - } - | { - type: 'updateSchema'; - steps: TemplateParameterSchema['steps']; - filePath: string; - }; - -const initialTemplateFormState: TemplateFormState = { - steps: undefined, - filePath: undefined, - formData: {}, - // Used to reset the error boundary in edit - schemaGeneration: 0, -}; - -function templateFormReducer( - state: TemplateFormState, - action: TemplateFormAction, -): TemplateFormState { - switch (action.type) { - case 'reset': { - return initialTemplateFormState; - } - case 'updateData': { - return { - ...state, - formData: action.formData, - }; - } - case 'updateSchema': { - const { filePath, steps } = action; - - return { - steps, - filePath, - formData: state.filePath === filePath ? state.formData : {}, - schemaGeneration: state.schemaGeneration + 1, - }; - } - default: - return state; - } -} - -interface TemplateEditorFormProps { - setErrorText: (errorText?: string) => void; - fieldExtensions?: FieldExtensionOptions[]; -} - -function isJsonObject(value: JsonValue | undefined): value is JsonObject { - return typeof value === 'object' && value !== null && !Array.isArray(value); -} - -function TemplateEditorForm(props: TemplateEditorFormProps) { - const { setErrorText, fieldExtensions = [] } = props; - const dryRun = useDryRun(); - - const apiHolder = useApiHolder(); - const directoryEditor = useDirectoryEditor(); - const { selectedFile } = directoryEditor; - const [state, dispatch] = useReducer( - templateFormReducer, - initialTemplateFormState, - ); - - useDebounce( - () => { - try { - if (!selectedFile || !selectedFile.path.match(/\.ya?ml$/)) { - dispatch({ type: 'reset' }); - return; - } - const parsed: JsonValue = yaml.parse(selectedFile.content); - const isTemplate = - typeof parsed === 'object' && - parsed !== null && - 'kind' in parsed && - typeof parsed.kind === 'string' && - parsed.kind.toLocaleLowerCase('en-US') === 'template'; - - if (!isTemplate) { - dispatch({ type: 'reset' }); - return; - } - - const spec = parsed.spec; - const parameters = isJsonObject(spec) && spec.parameters; - if (!Array.isArray(parameters)) { - setErrorText('Template parameters must be an array'); - return; - } - - const fieldValidators = Object.fromEntries( - fieldExtensions.map(({ name, validation }) => [name, validation]), - ); - - setErrorText(); - - dispatch({ - type: 'updateSchema', - filePath: selectedFile.path, - steps: parameters.flatMap(param => - isJsonObject(param) - ? [ - { - title: String(param.title), - schema: param, - validate: createValidator(param, fieldValidators, { - apiHolder, - }), - }, - ] - : [], - ), - }); - } catch (e) { - setErrorText(e.message); - } - }, - 250, - [selectedFile?.path, selectedFile?.content, apiHolder], - ); - - const fields = useMemo(() => { - return Object.fromEntries( - fieldExtensions.map(({ name, component }) => [name, component]), - ); - }, [fieldExtensions]); - - if (!state.steps) { - return null; - } - - const handleDryRun = async () => { - if (!selectedFile) { - return; - } - - await dryRun.execute({ - templateContent: selectedFile.content, - values: state.formData, - files: directoryEditor.files, - }); - }; - - return ( - - dispatch({ type: 'updateData', formData: e.formData })} - onReset={() => dispatch({ type: 'updateData', formData: {} })} - finishButtonLabel="Try It" - onFinish={handleDryRun} - /> - - ); -} diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorForm.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorForm.tsx new file mode 100644 index 0000000000..a9e0182317 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorForm.tsx @@ -0,0 +1,218 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { useApiHolder } from '@backstage/core-plugin-api'; +import { JsonObject, JsonValue } from '@backstage/types'; +import React, { Component, ReactNode, useMemo, useState } from 'react'; +import useDebounce from 'react-use/lib/useDebounce'; +import yaml from 'yaml'; +import { FieldExtensionOptions } from '../../../extensions'; +import { TemplateParameterSchema } from '../../../types'; +import { MultistepJsonForm } from '../../MultistepJsonForm'; +import { createValidator } from '../../TemplatePage'; +import { useDirectoryEditor } from './DirectoryEditorContext'; +import { useDryRun } from './DryRunContext'; + +interface ErrorBoundaryProps { + invalidator: unknown; + setErrorText(errorText: string | undefined): void; + children: ReactNode; +} + +interface ErrorBoundaryState { + shouldRender: boolean; +} + +class ErrorBoundary extends Component { + state = { + shouldRender: true, + }; + + componentDidUpdate(prevProps: { invalidator: unknown }) { + if (prevProps.invalidator !== this.props.invalidator) { + this.setState({ shouldRender: true }); + } + } + + componentDidCatch(error: Error) { + this.props.setErrorText(error.message); + this.setState({ shouldRender: false }); + } + + render() { + return this.state.shouldRender ? this.props.children : null; + } +} + +interface TemplateEditorFormProps { + content?: string; + /** Setting this to true will cause the content to be parsed as if it is the template entity spec */ + contentIsSpec?: boolean; + data: JsonObject; + onUpdate: (data: JsonObject) => void; + setErrorText: (errorText?: string) => void; + + onDryRun?: (data: JsonObject) => Promise; + fieldExtensions?: FieldExtensionOptions[]; +} + +function isJsonObject(value: JsonValue | undefined): value is JsonObject { + return typeof value === 'object' && value !== null && !Array.isArray(value); +} + +/** Shows the a template form that is parsed from the provided content */ +export function TemplateEditorForm(props: TemplateEditorFormProps) { + const { + content, + contentIsSpec, + data, + onUpdate, + onDryRun, + setErrorText, + fieldExtensions = [], + } = props; + const apiHolder = useApiHolder(); + + const [steps, setSteps] = useState(); + + const fields = useMemo(() => { + return Object.fromEntries( + fieldExtensions.map(({ name, component }) => [name, component]), + ); + }, [fieldExtensions]); + + useDebounce( + () => { + try { + if (!content) { + setSteps(undefined); + return; + } + const parsed: JsonValue = yaml.parse(content); + + if (!isJsonObject(parsed)) { + setSteps(undefined); + return; + } + + let rootObj = parsed; + if (!contentIsSpec) { + const isTemplate = + String(parsed.kind).toLocaleLowerCase('en-US') === 'template'; + if (!isTemplate) { + setSteps(undefined); + return; + } + + rootObj = isJsonObject(parsed.spec) ? parsed.spec : {}; + } + + const { parameters } = rootObj; + if (!Array.isArray(parameters)) { + setErrorText('Template parameters must be an array'); + setSteps(undefined); + return; + } + + const fieldValidators = Object.fromEntries( + fieldExtensions.map(({ name, validation }) => [name, validation]), + ); + + setErrorText(); + setSteps( + parameters.flatMap(param => + isJsonObject(param) + ? [ + { + title: String(param.title), + schema: param, + validate: createValidator(param, fieldValidators, { + apiHolder, + }), + }, + ] + : [], + ), + ); + } catch (e) { + setErrorText(e.message); + } + }, + 250, + [contentIsSpec, content, apiHolder], + ); + + if (!steps) { + return null; + } + + return ( + + onUpdate(e.formData)} + onReset={() => onUpdate({})} + finishButtonLabel={onDryRun && 'Try It'} + onFinish={onDryRun && (() => onDryRun(data))} + /> + + ); +} + +/** A version of the TemplateEditorForm that is connected to the DirectoryEditor and DryRun contexts */ +export function TemplateEditorFormDirectoryEditorDryRun( + props: Pick, +) { + const { setErrorText, fieldExtensions = [] } = props; + const dryRun = useDryRun(); + + const directoryEditor = useDirectoryEditor(); + const { selectedFile } = directoryEditor; + + const [data, setData] = useState({}); + + const handleDryRun = async () => { + if (!selectedFile) { + return; + } + + await dryRun.execute({ + templateContent: selectedFile.content, + values: data, + files: directoryEditor.files, + }); + }; + + const content = + selectedFile && selectedFile.path.match(/\.ya?ml$/) + ? selectedFile.content + : undefined; + + return ( + + ); +} + +TemplateEditorForm.DirectoryEditorDryRun = + TemplateEditorFormDirectoryEditorDryRun; From 6758e83662eccec31be9ebc26f0dbdd9862ae069 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 29 Apr 2022 11:19:16 +0200 Subject: [PATCH 29/56] scaffolder: extract TemplateEditorBrowser Signed-off-by: Patrik Oldsberg --- .../TemplateEditor/TemplateEditor.tsx | 88 +---------------- .../TemplateEditor/TemplateEditorBrowser.tsx | 98 +++++++++++++++++++ 2 files changed, 103 insertions(+), 83 deletions(-) create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorBrowser.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx index f6b936e8af..bd12efa9b8 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx @@ -13,24 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Divider, IconButton, makeStyles, Tooltip } from '@material-ui/core'; -import CloseIcon from '@material-ui/icons/Close'; -import RefreshIcon from '@material-ui/icons/Refresh'; -import SaveIcon from '@material-ui/icons/Save'; +import { makeStyles } from '@material-ui/core'; import React, { useState } from 'react'; import { FieldExtensionOptions } from '../../../extensions'; import { TemplateDirectoryAccess } from '../../../lib/filesystem'; -import { FileBrowser } from '../../FileBrowser'; -import { - DirectoryEditorProvider, - useDirectoryEditor, -} from './DirectoryEditorContext'; +import { DirectoryEditorProvider } from './DirectoryEditorContext'; import { DryRunProvider } from './DryRunContext'; import { DryRunResults } from './DryRunResults'; +import { TemplateEditorBrowser } from './TemplateEditorBrowser'; import { TemplateEditorForm } from './TemplateEditorForm'; import { TemplateEditorTextArea } from './TemplateEditorTextArea'; -const useStyles = makeStyles(theme => ({ +const useStyles = makeStyles({ // Reset and fix sizing to make sure scrolling behaves correctly rootWrapper: { gridArea: 'pageContent', @@ -56,21 +50,6 @@ const useStyles = makeStyles(theme => ({ gridArea: 'browser', overflow: 'auto', }, - browserButton: { - padding: theme.spacing(1), - }, - browserButtons: { - display: 'flex', - flexFlow: 'row nowrap', - alignItems: 'center', - justifyContent: 'flex-start', - }, - browserButtonsGap: { - flex: '1 1 auto', - }, - browserButtonsDivider: { - marginBottom: theme.spacing(1), - }, editor: { gridArea: 'editor', overflow: 'auto', @@ -82,7 +61,7 @@ const useStyles = makeStyles(theme => ({ results: { gridArea: 'results', }, -})); +}); export const TemplateEditor = (props: { directory: TemplateDirectoryAccess; @@ -119,60 +98,3 @@ export const TemplateEditor = (props: { ); }; - -function TemplateEditorBrowser(props: { onClose?: () => void }) { - const classes = useStyles(); - const directoryEditor = useDirectoryEditor(); - const changedFiles = directoryEditor.files.filter(file => file.dirty); - - const handleClose = () => { - if (!props.onClose) { - return; - } - if (changedFiles.length > 0) { - // eslint-disable-next-line no-alert - const accepted = window.confirm( - 'Are you sure? Unsaved changes will be lost', - ); - if (!accepted) { - return; - } - } - props.onClose(); - }; - - return ( - <> -
- - directoryEditor.save()} - > - - - - - directoryEditor.reload()} - > - - - -
- - - - - -
- - file.path)} - /> - - ); -} diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorBrowser.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorBrowser.tsx new file mode 100644 index 0000000000..e315319a52 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorBrowser.tsx @@ -0,0 +1,98 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { Divider, IconButton, makeStyles, Tooltip } from '@material-ui/core'; +import CloseIcon from '@material-ui/icons/Close'; +import RefreshIcon from '@material-ui/icons/Refresh'; +import SaveIcon from '@material-ui/icons/Save'; +import React from 'react'; +import { FileBrowser } from '../../FileBrowser'; +import { useDirectoryEditor } from './DirectoryEditorContext'; + +const useStyles = makeStyles(theme => ({ + button: { + padding: theme.spacing(1), + }, + buttons: { + display: 'flex', + flexFlow: 'row nowrap', + alignItems: 'center', + justifyContent: 'flex-start', + }, + buttonsGap: { + flex: '1 1 auto', + }, + buttonsDivider: { + marginBottom: theme.spacing(1), + }, +})); + +/** The local file browser for the template editor */ +export function TemplateEditorBrowser(props: { onClose?: () => void }) { + const classes = useStyles(); + const directoryEditor = useDirectoryEditor(); + const changedFiles = directoryEditor.files.filter(file => file.dirty); + + const handleClose = () => { + if (!props.onClose) { + return; + } + if (changedFiles.length > 0) { + // eslint-disable-next-line no-alert + const accepted = window.confirm( + 'Are you sure? Unsaved changes will be lost', + ); + if (!accepted) { + return; + } + } + props.onClose(); + }; + + return ( + <> +
+ + directoryEditor.save()} + > + + + + + directoryEditor.reload()} + > + + + +
+ + + + + +
+ + file.path)} + /> + + ); +} From 3c87f60481abbb0e2559d3233a5387db4c3f35dc Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 29 Apr 2022 12:14:29 +0200 Subject: [PATCH 30/56] scaffolder: tweak names of TemplateEditor components Signed-off-by: Patrik Oldsberg --- .../{EditorIntro.tsx => TemplateEditorIntro.tsx} | 4 ++-- .../components/TemplateEditorPage/TemplateEditorPage.tsx | 8 ++++---- .../{TemplateFormEditor.tsx => TemplateFormPreviewer.tsx} | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) rename plugins/scaffolder/src/components/TemplateEditorPage/{EditorIntro.tsx => TemplateEditorIntro.tsx} (98%) rename plugins/scaffolder/src/components/TemplateEditorPage/{TemplateFormEditor.tsx => TemplateFormPreviewer.tsx} (99%) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/EditorIntro.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorIntro.tsx similarity index 98% rename from plugins/scaffolder/src/components/TemplateEditorPage/EditorIntro.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorIntro.tsx index 1c07a78c9e..dc411ee965 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/EditorIntro.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorIntro.tsx @@ -47,7 +47,7 @@ interface EditorIntroProps { onSelect?: (option: 'local' | 'form') => void; } -export const EditorIntro = (props: EditorIntroProps) => { +export function TemplateEditorIntro(props: EditorIntroProps) { const classes = useStyles(); const supportsLoad = WebFileSystemAccess.isSupported(); @@ -124,4 +124,4 @@ export const EditorIntro = (props: EditorIntroProps) => {
); -}; +} diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx index f2f603337d..3741a6fc3d 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx @@ -19,9 +19,9 @@ import { TemplateDirectoryAccess, WebFileSystemAccess, } from '../../lib/filesystem'; -import { EditorIntro } from './EditorIntro'; +import { TemplateEditorIntro } from './TemplateEditorIntro'; import { TemplateEditor } from './TemplateEditor'; -import { TemplateFormEditor } from './TemplateFormEditor'; +import { TemplateFormPreviewer } from './TemplateFormPreviewer'; import { FieldExtensionOptions } from '../../extensions'; type Selection = @@ -52,7 +52,7 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) { ); } else if (selection?.type === 'form') { content = ( - setSelection(undefined)} @@ -61,7 +61,7 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) { } else { content = ( - { if (option === 'local') { WebFileSystemAccess.get() diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx similarity index 99% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormEditor.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx index 43c7f10a32..bff515d13b 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -105,7 +105,7 @@ const useStyles = makeStyles(theme => ({ }, })); -export const TemplateFormEditor = ({ +export const TemplateFormPreviewer = ({ defaultPreviewTemplate = EXAMPLE_TEMPLATE_PARAMS_YAML, customFieldExtensions = [], onClose, From c98cfa0e6c1061f265e644d59d37f632f0bde4b0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 29 Apr 2022 14:35:13 +0200 Subject: [PATCH 31/56] scaffolder: refactor TemplateFormPreviewer to use common components Signed-off-by: Patrik Oldsberg --- .../TemplateFormPreviewer.tsx | 190 ++++++------------ 1 file changed, 62 insertions(+), 128 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx index bff515d13b..d59362a1cc 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -13,23 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useCallback, useState } from 'react'; -import useAsync from 'react-use/lib/useAsync'; -import useDebounce from 'react-use/lib/useDebounce'; import { Entity } from '@backstage/catalog-model'; -import { InfoCard } from '@backstage/core-components'; -import { alertApiRef, useApi, useApiHolder } from '@backstage/core-plugin-api'; +import { alertApiRef, useApi } from '@backstage/core-plugin-api'; import { catalogApiRef, humanizeEntityRef, } from '@backstage/plugin-catalog-react'; -import { JsonObject } from '@backstage/types'; -import { yaml as yamlSupport } from '@codemirror/legacy-modes/mode/yaml'; -import { showPanel } from '@codemirror/view'; -import { StreamLanguage } from '@codemirror/language'; import { FormControl, - Grid, IconButton, InputLabel, LinearProgress, @@ -38,13 +29,12 @@ import { Select, } from '@material-ui/core'; import CloseIcon from '@material-ui/icons/Close'; -import { IChangeEvent } from '@rjsf/core'; -import CodeMirror from '@uiw/react-codemirror'; +import React, { useCallback, useState } from 'react'; +import useAsync from 'react-use/lib/useAsync'; import yaml from 'yaml'; import { FieldExtensionOptions } from '../../extensions'; -import { TemplateParameterSchema } from '../../types'; -import { MultistepJsonForm } from '../MultistepJsonForm'; -import { createValidator } from '../TemplatePage'; +import { TemplateEditorForm } from './TemplateEditor/TemplateEditorForm'; +import { TemplateEditorTextArea } from './TemplateEditor/TemplateEditorTextArea'; const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI parameters: @@ -91,17 +81,28 @@ type TemplateOption = { }; const useStyles = makeStyles(theme => ({ + root: { + gridArea: 'pageContent', + display: 'grid', + gridTemplateAreas: ` + "controls controls" + "textArea preview" + `, + gridTemplateRows: 'auto 1fr', + gridTemplateColumns: '1fr 1fr', + }, controls: { + gridArea: 'controls', display: 'flex', flexFlow: 'row nowrap', alignItems: 'center', - marginBottom: theme.spacing(1), + margin: theme.spacing(1), }, - grid: { - height: '100%', + textArea: { + gridArea: 'textArea', }, - codeMirror: { - height: '95%', + preview: { + gridArea: 'preview', }, })); @@ -117,12 +118,8 @@ export const TemplateFormPreviewer = ({ const classes = useStyles(); const alertApi = useApi(alertApiRef); const catalogApi = useApi(catalogApiRef); - const apiHolder = useApiHolder(); const [selectedTemplate, setSelectedTemplate] = useState(''); - const [schema, setSchema] = useState({ - title: '', - steps: [], - }); + const [errorText, setErrorText] = useState(); const [templateOptions, setTemplateOptions] = useState([]); const [templateYaml, setTemplateYaml] = useState(defaultPreviewTemplate); const [formState, setFormState] = useState({}); @@ -161,30 +158,6 @@ export const TemplateFormPreviewer = ({ [catalogApi], ); - const errorPanel = document.createElement('div'); - errorPanel.style.color = 'red'; - - useDebounce( - () => { - try { - const parsedTemplate = yaml.parse(templateYaml); - - setSchema({ - title: 'Preview', - steps: parsedTemplate.parameters.map((param: JsonObject) => ({ - title: param.title, - schema: param, - })), - }); - setFormState({}); - } catch (e) { - errorPanel.textContent = e.message; - } - }, - 250, - [setFormState, setSchema, templateYaml], - ); - const handleSelectChange = useCallback( selected => { setSelectedTemplate(selected); @@ -193,90 +166,51 @@ export const TemplateFormPreviewer = ({ [setTemplateYaml], ); - const handleFormReset = () => setFormState({}); - const handleFormChange = useCallback( - (e: IChangeEvent) => setFormState(e.formData), - [setFormState], - ); - - const handleCodeChange = useCallback( - (code: string) => { - setTemplateYaml(code); - }, - [setTemplateYaml], - ); - - const customFieldComponents = Object.fromEntries( - customFieldExtensions.map(({ name, component }) => [name, component]), - ); - - const customFieldValidators = Object.fromEntries( - customFieldExtensions.map(({ name, validation }) => [name, validation]), - ); - return ( <> {loading && } - - -
- - - Load Existing Template - - - +
+
+ + + Load Existing Template + + + - - - -
- ({ dom: errorPanel, top: true })), - ]} - onChange={handleCodeChange} + + + +
+
+ - - - {schema && ( - - { - return { - ...step, - validate: createValidator( - step.schema, - customFieldValidators, - { apiHolder }, - ), - }; - })} - /> - - )} - - +
+
+ +
+ ); }; From f7805dfea86f7af65fab15b0e22c2affca6a3168 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 29 Apr 2022 14:38:46 +0200 Subject: [PATCH 32/56] scaffolder: add wrapper to TemplateEditorForm to handle overflow Signed-off-by: Patrik Oldsberg --- .../TemplateEditor/TemplateEditor.tsx | 47 +++++++------------ .../TemplateEditor/TemplateEditorForm.tsx | 44 ++++++++++++----- 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx index bd12efa9b8..baf8ebf3f9 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx @@ -26,17 +26,8 @@ import { TemplateEditorTextArea } from './TemplateEditorTextArea'; const useStyles = makeStyles({ // Reset and fix sizing to make sure scrolling behaves correctly - rootWrapper: { - gridArea: 'pageContent', - position: 'relative', - width: '100%', - }, root: { - position: 'absolute', - top: 0, - bottom: 0, - left: 0, - right: 0, + gridArea: 'pageContent', display: 'grid', gridTemplateAreas: ` @@ -75,25 +66,23 @@ export const TemplateEditor = (props: { return ( -
-
-
- -
-
- -
-
- -
-
- -
-
-
+
+
+ +
+
+ +
+
+ +
+
+ +
+
); diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorForm.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorForm.tsx index a9e0182317..2e675bce0a 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorForm.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorForm.tsx @@ -15,6 +15,7 @@ */ import { useApiHolder } from '@backstage/core-plugin-api'; import { JsonObject, JsonValue } from '@backstage/types'; +import { makeStyles } from '@material-ui/core/styles'; import React, { Component, ReactNode, useMemo, useState } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; import yaml from 'yaml'; @@ -25,6 +26,22 @@ import { createValidator } from '../../TemplatePage'; import { useDirectoryEditor } from './DirectoryEditorContext'; import { useDryRun } from './DryRunContext'; +const useStyles = makeStyles({ + containerWrapper: { + position: 'relative', + width: '100%', + height: '100%', + }, + container: { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + overflow: 'auto', + }, +}); + interface ErrorBoundaryProps { invalidator: unknown; setErrorText(errorText: string | undefined): void; @@ -83,6 +100,7 @@ export function TemplateEditorForm(props: TemplateEditorFormProps) { setErrorText, fieldExtensions = [], } = props; + const classes = useStyles(); const apiHolder = useApiHolder(); const [steps, setSteps] = useState(); @@ -159,17 +177,21 @@ export function TemplateEditorForm(props: TemplateEditorFormProps) { } return ( - - onUpdate(e.formData)} - onReset={() => onUpdate({})} - finishButtonLabel={onDryRun && 'Try It'} - onFinish={onDryRun && (() => onDryRun(data))} - /> - +
+
+ + onUpdate(e.formData)} + onReset={() => onUpdate({})} + finishButtonLabel={onDryRun && 'Try It'} + onFinish={onDryRun && (() => onDryRun(data))} + /> + +
+
); } From 289d3491991660c8b15a287e5bf8efffcee155fa Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 29 Apr 2022 14:40:22 +0200 Subject: [PATCH 33/56] scaffolder: always show file reload in TemplateEditorTextArea Signed-off-by: Patrik Oldsberg --- .../TemplateEditor/TemplateEditorTextArea.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorTextArea.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorTextArea.tsx index 3700348ef4..588b0cd4ba 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorTextArea.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorTextArea.tsx @@ -136,7 +136,9 @@ export function TemplateEditorDirectoryEditorTextArea(props: { onSave: () => directoryEditor.save(), onReload: () => directoryEditor.reload(), } - : undefined; + : { + onReload: () => directoryEditor.reload(), + }; return ( Date: Fri, 29 Apr 2022 14:43:58 +0200 Subject: [PATCH 34/56] scaffolder: flatten TemplateEditorPage structure Signed-off-by: Patrik Oldsberg --- .../DirectoryEditorContext.tsx | 2 +- .../{TemplateEditor => }/DryRunContext.tsx | 4 ++-- .../DryRunResults/DryRunResults.tsx | 0 .../DryRunResults/DryRunResultsList.tsx | 0 .../DryRunResults/DryRunResultsSplitView.tsx | 0 .../DryRunResults/DryRunResultsView.tsx | 6 +++--- .../{TemplateEditor => }/DryRunResults/index.ts | 0 .../{TemplateEditor => }/TemplateEditor.tsx | 4 ++-- .../TemplateEditorPage/TemplateEditor/index.ts | 17 ----------------- .../TemplateEditorBrowser.tsx | 2 +- .../{TemplateEditor => }/TemplateEditorForm.tsx | 8 ++++---- .../TemplateEditorTextArea.tsx | 0 .../TemplateFormPreviewer.tsx | 4 ++-- 13 files changed, 15 insertions(+), 32 deletions(-) rename plugins/scaffolder/src/components/TemplateEditorPage/{TemplateEditor => }/DirectoryEditorContext.tsx (99%) rename plugins/scaffolder/src/components/TemplateEditorPage/{TemplateEditor => }/DryRunContext.tsx (97%) rename plugins/scaffolder/src/components/TemplateEditorPage/{TemplateEditor => }/DryRunResults/DryRunResults.tsx (100%) rename plugins/scaffolder/src/components/TemplateEditorPage/{TemplateEditor => }/DryRunResults/DryRunResultsList.tsx (100%) rename plugins/scaffolder/src/components/TemplateEditorPage/{TemplateEditor => }/DryRunResults/DryRunResultsSplitView.tsx (100%) rename plugins/scaffolder/src/components/TemplateEditorPage/{TemplateEditor => }/DryRunResults/DryRunResultsView.tsx (96%) rename plugins/scaffolder/src/components/TemplateEditorPage/{TemplateEditor => }/DryRunResults/index.ts (100%) rename plugins/scaffolder/src/components/TemplateEditorPage/{TemplateEditor => }/TemplateEditor.tsx (95%) delete mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/index.ts rename plugins/scaffolder/src/components/TemplateEditorPage/{TemplateEditor => }/TemplateEditorBrowser.tsx (98%) rename plugins/scaffolder/src/components/TemplateEditorPage/{TemplateEditor => }/TemplateEditorForm.tsx (96%) rename plugins/scaffolder/src/components/TemplateEditorPage/{TemplateEditor => }/TemplateEditorTextArea.tsx (100%) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DirectoryEditorContext.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DirectoryEditorContext.tsx similarity index 99% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DirectoryEditorContext.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/DirectoryEditorContext.tsx index 74073e797b..0edf390bfe 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DirectoryEditorContext.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DirectoryEditorContext.tsx @@ -20,7 +20,7 @@ import React, { createContext, ReactNode, useContext, useEffect } from 'react'; import { TemplateDirectoryAccess, TemplateFileAccess, -} from '../../../lib/filesystem'; +} from '../../lib/filesystem'; const MAX_SIZE = 1024 * 1024; const MAX_SIZE_MESSAGE = 'This file is too large to be displayed'; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunContext.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx similarity index 97% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunContext.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx index 68cee21b77..ea687095db 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunContext.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx @@ -24,8 +24,8 @@ import React, { useRef, useState, } from 'react'; -import { scaffolderApiRef } from '../../../api'; -import { ScaffolderDryRunResponse } from '../../../types'; +import { scaffolderApiRef } from '../../api'; +import { ScaffolderDryRunResponse } from '../../types'; interface DryRunOptions { templateContent: string; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResults.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResults.tsx similarity index 100% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResults.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResults.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsList.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx similarity index 100% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsList.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsSplitView.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsSplitView.tsx similarity index 100% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsSplitView.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsSplitView.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsView.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx similarity index 96% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsView.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx index 9e0f613d91..f854f504ce 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/DryRunResultsView.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx @@ -24,10 +24,10 @@ import Tab from '@material-ui/core/Tab'; import Tabs from '@material-ui/core/Tabs'; import CodeMirror from '@uiw/react-codemirror'; import React, { useEffect, useMemo, useState } from 'react'; -import { TaskStatusStepper } from '../../../TaskPage/TaskPage'; -import { TaskPageLinks } from '../../../TaskPage/TaskPageLinks'; +import { TaskStatusStepper } from '../../TaskPage/TaskPage'; +import { TaskPageLinks } from '../../TaskPage/TaskPageLinks'; import { useDryRun } from '../DryRunContext'; -import { FileBrowser } from '../../../FileBrowser'; +import { FileBrowser } from '../../FileBrowser'; import { DryRunResultsSplitView } from './DryRunResultsSplitView'; const useStyles = makeStyles({ diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/index.ts b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/index.ts similarity index 100% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/DryRunResults/index.ts rename to plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/index.ts diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx similarity index 95% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx index baf8ebf3f9..cc4dd8c9a1 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditor.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor.tsx @@ -15,8 +15,8 @@ */ import { makeStyles } from '@material-ui/core'; import React, { useState } from 'react'; -import { FieldExtensionOptions } from '../../../extensions'; -import { TemplateDirectoryAccess } from '../../../lib/filesystem'; +import { FieldExtensionOptions } from '../../extensions'; +import { TemplateDirectoryAccess } from '../../lib/filesystem'; import { DirectoryEditorProvider } from './DirectoryEditorContext'; import { DryRunProvider } from './DryRunContext'; import { DryRunResults } from './DryRunResults'; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/index.ts b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/index.ts deleted file mode 100644 index f0854db3a7..0000000000 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed 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 { TemplateEditor } from './TemplateEditor'; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorBrowser.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.tsx similarity index 98% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorBrowser.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.tsx index e315319a52..9402047327 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorBrowser.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.tsx @@ -18,7 +18,7 @@ import CloseIcon from '@material-ui/icons/Close'; import RefreshIcon from '@material-ui/icons/Refresh'; import SaveIcon from '@material-ui/icons/Save'; import React from 'react'; -import { FileBrowser } from '../../FileBrowser'; +import { FileBrowser } from '../FileBrowser'; import { useDirectoryEditor } from './DirectoryEditorContext'; const useStyles = makeStyles(theme => ({ diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorForm.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx similarity index 96% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorForm.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx index 2e675bce0a..11b4da246d 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorForm.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorForm.tsx @@ -19,10 +19,10 @@ import { makeStyles } from '@material-ui/core/styles'; import React, { Component, ReactNode, useMemo, useState } from 'react'; import useDebounce from 'react-use/lib/useDebounce'; import yaml from 'yaml'; -import { FieldExtensionOptions } from '../../../extensions'; -import { TemplateParameterSchema } from '../../../types'; -import { MultistepJsonForm } from '../../MultistepJsonForm'; -import { createValidator } from '../../TemplatePage'; +import { FieldExtensionOptions } from '../../extensions'; +import { TemplateParameterSchema } from '../../types'; +import { MultistepJsonForm } from '../MultistepJsonForm'; +import { createValidator } from '../TemplatePage'; import { useDirectoryEditor } from './DirectoryEditorContext'; import { useDryRun } from './DryRunContext'; diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorTextArea.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorTextArea.tsx similarity index 100% rename from plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditor/TemplateEditorTextArea.tsx rename to plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorTextArea.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx index d59362a1cc..bfa7071d48 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateFormPreviewer.tsx @@ -33,8 +33,8 @@ import React, { useCallback, useState } from 'react'; import useAsync from 'react-use/lib/useAsync'; import yaml from 'yaml'; import { FieldExtensionOptions } from '../../extensions'; -import { TemplateEditorForm } from './TemplateEditor/TemplateEditorForm'; -import { TemplateEditorTextArea } from './TemplateEditor/TemplateEditorTextArea'; +import { TemplateEditorForm } from './TemplateEditorForm'; +import { TemplateEditorTextArea } from './TemplateEditorTextArea'; const EXAMPLE_TEMPLATE_PARAMS_YAML = `# Edit the template parameters below to see how they will render in the scaffolder form UI parameters: From 05b6044399f22304b4dacabfd7b1d8a2bb0d143f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 29 Apr 2022 15:35:20 +0200 Subject: [PATCH 35/56] scaffolder: memo DryRunContext value Signed-off-by: Patrik Oldsberg --- .../TemplateEditorPage/DryRunContext.tsx | 116 +++++++++--------- 1 file changed, 60 insertions(+), 56 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx index ea687095db..c3032d7e55 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx @@ -21,6 +21,7 @@ import React, { createContext, ReactNode, useContext, + useMemo, useRef, useState, } from 'react'; @@ -63,65 +64,68 @@ export function DryRunProvider(props: DryRunProviderProps) { }); const idRef = useRef(1); - const dryRun = { - ...state, - selectResult: (id: number) => { - setState(prevState => { - const result = prevState.results.find(r => r.id === id); - if (result === prevState.selectedResult) { - return prevState; + const dryRun = useMemo( + () => ({ + ...state, + selectResult: (id: number) => { + setState(prevState => { + const result = prevState.results.find(r => r.id === id); + if (result === prevState.selectedResult) { + return prevState; + } + return { + results: prevState.results, + selectedResult: result, + }; + }); + }, + deleteResult: (id: number) => { + setState(prevState => { + const index = prevState.results.findIndex(r => r.id === id); + if (index === -1) { + return prevState; + } + const newResults = prevState.results.slice(); + const [deleted] = newResults.splice(index, 1); + return { + results: newResults, + selectedResult: + prevState.selectedResult?.id === deleted.id + ? newResults[0] + : prevState.selectedResult, + }; + }); + }, + execute: async (options: DryRunOptions) => { + if (!scaffolderApi.dryRun) { + throw new Error('Scaffolder API does not support dry-run'); } - return { - results: prevState.results, - selectedResult: result, + + const parsed = yaml.parse(options.templateContent); + + const response = await scaffolderApi.dryRun({ + template: parsed, + values: options.values, + secrets: {}, + content: options.files.map(file => ({ + path: file.path, + base64Content: btoa(file.content), + })), + }); + + const result = { + ...response, + id: idRef.current++, }; - }); - }, - deleteResult: (id: number) => { - setState(prevState => { - const index = prevState.results.findIndex(r => r.id === id); - if (index === -1) { - return prevState; - } - const newResults = prevState.results.slice(); - const [deleted] = newResults.splice(index, 1); - return { - results: newResults, - selectedResult: - prevState.selectedResult?.id === deleted.id - ? newResults[0] - : prevState.selectedResult, - }; - }); - }, - execute: async (options: DryRunOptions) => { - if (!scaffolderApi.dryRun) { - throw new Error('Scaffolder API does not support dry-run'); - } - const parsed = yaml.parse(options.templateContent); - - const response = await scaffolderApi.dryRun({ - template: parsed, - values: options.values, - secrets: {}, - content: options.files.map(file => ({ - path: file.path, - base64Content: btoa(file.content), - })), - }); - - const result = { - ...response, - id: idRef.current++, - }; - - setState(prevState => ({ - results: [...prevState.results, result], - selectedResult: prevState.selectedResult ?? result, - })); - }, - }; + setState(prevState => ({ + results: [...prevState.results, result], + selectedResult: prevState.selectedResult ?? result, + })); + }, + }), + [scaffolderApi, state], + ); return ( From 88b81c3e6f89c2644d033142d1b3093e111f7907 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 29 Apr 2022 15:35:35 +0200 Subject: [PATCH 36/56] scaffolder: add tests for DryRunResults Signed-off-by: Patrik Oldsberg --- .../DryRunResults/DryRunResults.test.tsx | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResults.test.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResults.test.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResults.test.tsx new file mode 100644 index 0000000000..7b08544736 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResults.test.tsx @@ -0,0 +1,113 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { act, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React, { useEffect } from 'react'; +import { scaffolderApiRef } from '../../../api'; +import { DryRunProvider, useDryRun } from '../DryRunContext'; +import { DryRunResults } from './DryRunResults'; + +function DryRunRemote({ + execute, + remove, +}: { + execute?: boolean; + remove?: boolean; +}) { + const dryRun = useDryRun(); + + useEffect(() => { + if (execute) { + dryRun.execute({ + templateContent: '', + values: {}, + files: [], + }); + } + if (remove) { + if (dryRun.selectedResult) { + dryRun.deleteResult(dryRun.selectedResult.id); + } + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [execute, remove]); + + return null; +} + +const mockScaffolderApi = { + dryRun: async () => ({ content: [], log: [], output: {}, steps: [] }), +}; + +describe('DryRunResults', () => { + it('renders without exploding', async () => { + await renderInTestApp( + + + + + , + ); + expect(screen.getByText('Dry-run results')).toBeInTheDocument(); + }); + + it('expands when dry-run result is added and toggles on click, and disappears when results are gone', async () => { + const { rerender } = await renderInTestApp( + + + + + + , + ); + + expect(screen.getByText('Files')).not.toBeVisible(); + + await act(async () => { + rerender( + + + + + + , + ); + }); + + expect(screen.getByText('Files')).toBeVisible(); + + await userEvent.click(screen.getByText('Dry-run results')); + await waitFor(() => expect(screen.getByText('Files')).not.toBeVisible()); + + await userEvent.click(screen.getByText('Dry-run results')); + expect(screen.getByText('Files')).toBeVisible(); + + await act(async () => { + rerender( + + + + + + , + ); + }); + + await waitFor(() => expect(screen.getByText('Files')).not.toBeVisible()); + }); +}); From 4b839c4f02890465e05e60437f6866f29000daf9 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 29 Apr 2022 16:05:31 +0200 Subject: [PATCH 37/56] scaffolder: add DryRunResultsList tests Signed-off-by: Patrik Oldsberg --- .../DryRunResults/DryRunResultsList.test.tsx | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsList.test.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsList.test.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsList.test.tsx new file mode 100644 index 0000000000..ca35f71e95 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsList.test.tsx @@ -0,0 +1,90 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { act, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React, { useEffect } from 'react'; +import { scaffolderApiRef } from '../../../api'; +import { DryRunProvider, useDryRun } from '../DryRunContext'; +import { DryRunResultsList } from './DryRunResultsList'; + +function DryRunRemote({ execute }: { execute?: number }) { + const dryRun = useDryRun(); + + useEffect(() => { + if (execute) { + dryRun.execute({ + templateContent: '', + values: {}, + files: [], + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [execute]); + + return null; +} + +const mockScaffolderApi = { + dryRun: async () => ({ content: [], log: [], output: {}, steps: [] }), +}; + +describe('DryRunResultsList', () => { + it('renders without exploding', async () => { + const rendered = await renderInTestApp( + + + + + , + ); + expect(rendered.baseElement.querySelector('ul')).toBeEmptyDOMElement(); + }); + + it('adds new result items and deletes them', async () => { + const { rerender } = await renderInTestApp( + + + + + + , + ); + + expect(screen.queryByText('Result 1')).toBeInTheDocument(); + expect(screen.queryByText('Result 2')).not.toBeInTheDocument(); + + await act(async () => { + rerender( + + + + + + , + ); + }); + + expect(screen.queryByText('Result 1')).toBeInTheDocument(); + expect(screen.queryByText('Result 2')).toBeInTheDocument(); + + await userEvent.click(screen.getAllByLabelText('delete')[0]); + + expect(screen.queryByText('Result 1')).not.toBeInTheDocument(); + expect(screen.queryByText('Result 2')).toBeInTheDocument(); + }); +}); From e48ca034e961096a4c8482d4abb584e6f596af4e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 29 Apr 2022 16:12:15 +0200 Subject: [PATCH 38/56] scaffolder: memo methods in DryRunProvider Signed-off-by: Patrik Oldsberg --- .../TemplateEditorPage/DryRunContext.tsx | 124 ++++++++++-------- 1 file changed, 67 insertions(+), 57 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx index c3032d7e55..97112f75df 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx @@ -20,6 +20,7 @@ import { JsonObject } from '@backstage/types'; import React, { createContext, ReactNode, + useCallback, useContext, useMemo, useRef, @@ -64,67 +65,76 @@ export function DryRunProvider(props: DryRunProviderProps) { }); const idRef = useRef(1); + const selectResult = useCallback((id: number) => { + setState(prevState => { + const result = prevState.results.find(r => r.id === id); + if (result === prevState.selectedResult) { + return prevState; + } + return { + results: prevState.results, + selectedResult: result, + }; + }); + }, []); + + const deleteResult = useCallback((id: number) => { + setState(prevState => { + const index = prevState.results.findIndex(r => r.id === id); + if (index === -1) { + return prevState; + } + const newResults = prevState.results.slice(); + const [deleted] = newResults.splice(index, 1); + return { + results: newResults, + selectedResult: + prevState.selectedResult?.id === deleted.id + ? newResults[0] + : prevState.selectedResult, + }; + }); + }, []); + + const execute = useCallback( + async (options: DryRunOptions) => { + if (!scaffolderApi.dryRun) { + throw new Error('Scaffolder API does not support dry-run'); + } + + const parsed = yaml.parse(options.templateContent); + + const response = await scaffolderApi.dryRun({ + template: parsed, + values: options.values, + secrets: {}, + content: options.files.map(file => ({ + path: file.path, + base64Content: btoa(file.content), + })), + }); + + const result = { + ...response, + id: idRef.current++, + }; + + setState(prevState => ({ + results: [...prevState.results, result], + selectedResult: prevState.selectedResult ?? result, + })); + }, + [scaffolderApi], + ); + const dryRun = useMemo( () => ({ ...state, - selectResult: (id: number) => { - setState(prevState => { - const result = prevState.results.find(r => r.id === id); - if (result === prevState.selectedResult) { - return prevState; - } - return { - results: prevState.results, - selectedResult: result, - }; - }); - }, - deleteResult: (id: number) => { - setState(prevState => { - const index = prevState.results.findIndex(r => r.id === id); - if (index === -1) { - return prevState; - } - const newResults = prevState.results.slice(); - const [deleted] = newResults.splice(index, 1); - return { - results: newResults, - selectedResult: - prevState.selectedResult?.id === deleted.id - ? newResults[0] - : prevState.selectedResult, - }; - }); - }, - execute: async (options: DryRunOptions) => { - if (!scaffolderApi.dryRun) { - throw new Error('Scaffolder API does not support dry-run'); - } - - const parsed = yaml.parse(options.templateContent); - - const response = await scaffolderApi.dryRun({ - template: parsed, - values: options.values, - secrets: {}, - content: options.files.map(file => ({ - path: file.path, - base64Content: btoa(file.content), - })), - }); - - const result = { - ...response, - id: idRef.current++, - }; - - setState(prevState => ({ - results: [...prevState.results, result], - selectedResult: prevState.selectedResult ?? result, - })); - }, + selectResult, + deleteResult, + execute, }), - [scaffolderApi, state], + [state, selectResult, deleteResult, execute], ); return ( From 71228274aef7ce7c0607ed577105ecf6aa010218 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 29 Apr 2022 16:22:39 +0200 Subject: [PATCH 39/56] scaffolder: add test for DryRunResultsView Signed-off-by: Patrik Oldsberg --- .../DryRunResults/DryRunResultsView.test.tsx | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx new file mode 100644 index 0000000000..3f416dcceb --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx @@ -0,0 +1,112 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { entityRouteRef } from '@backstage/plugin-catalog-react'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React, { ReactNode, useEffect } from 'react'; +import { scaffolderApiRef } from '../../../api'; +import { DryRunProvider, useDryRun } from '../DryRunContext'; +import { DryRunResultsView } from './DryRunResultsView'; + +// The inside needs mocking to render in jsdom +jest.mock('react-virtualized-auto-sizer', () => ({ + __esModule: true, + default: (props: { + children: (size: { width: number; height: number }) => ReactNode; + }) => <>{props.children({ width: 400, height: 200 })}, +})); + +// Patch jsdom to add feature used by CodeMirror +document.createRange = () => { + const range = new Range(); + + range.getClientRects = () => ({ + item: () => null, + length: 0, + [Symbol.iterator]: jest.fn(), + }); + + return range; +}; + +function DryRunRemote() { + const dryRun = useDryRun(); + + useEffect(() => { + dryRun.execute({ + templateContent: '', + values: {}, + files: [], + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return null; +} + +describe('DryRunResultsView', () => { + it('renders a result', async () => { + await renderInTestApp( + ({ + content: [ + { + path: 'foo.txt', + base64Content: btoa('Foo Content'), + executable: false, + }, + ], + log: [{ message: 'Foo Message', stepId: 'foo' }], + output: { + links: [{ title: 'Foo Link', url: 'http://example.com' }], + }, + steps: [{ id: 'foo', action: 'foo', name: 'Foo' }], + }), + }, + ], + ]} + > + + + + + , + { + mountedRoutes: { + '/catalog/:kind/:namespace/:name': entityRouteRef, + }, + }, + ); + + expect(screen.getByText('foo.txt')).toBeInTheDocument(); + expect(screen.getByText('Foo Content')).toBeInTheDocument(); + + expect(screen.queryByText('Foo Message')).not.toBeInTheDocument(); + expect(screen.queryByText('Foo Link')).not.toBeInTheDocument(); + + await userEvent.click(screen.getByText('Log')); + expect(screen.getByText('Foo Message')).toBeInTheDocument(); + + await userEvent.click(screen.getByText('Output')); + expect(screen.getByText('Foo Link')).toBeInTheDocument(); + }); +}); From 01db4019e0c2bd935b7c49cf9c633281d9bb3582 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 29 Apr 2022 16:32:21 +0200 Subject: [PATCH 40/56] scaffolder: add test for TemplateEditorPage Signed-off-by: Patrik Oldsberg --- .../DryRunResults/DryRunResultsView.test.tsx | 13 ----- .../TemplateEditorPage.test.tsx | 58 +++++++++++++++++++ plugins/scaffolder/src/setupTests.ts | 13 +++++ 3 files changed, 71 insertions(+), 13 deletions(-) create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.test.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx index 3f416dcceb..a26d6e3803 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx @@ -31,19 +31,6 @@ jest.mock('react-virtualized-auto-sizer', () => ({ }) => <>{props.children({ width: 400, height: 200 })}, })); -// Patch jsdom to add feature used by CodeMirror -document.createRange = () => { - const range = new Range(); - - range.getClientRects = () => ({ - item: () => null, - length: 0, - [Symbol.iterator]: jest.fn(), - }); - - return range; -}; - function DryRunRemote() { const dryRun = useDryRun(); diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.test.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.test.tsx new file mode 100644 index 0000000000..bf9cd8c464 --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.test.tsx @@ -0,0 +1,58 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { catalogApiRef } from '@backstage/plugin-catalog-react'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { TemplateEditorPage } from './TemplateEditorPage'; + +describe('TemplateEditorPage', () => { + it('renders without exploding', async () => { + await renderInTestApp(); + + expect(screen.getByText('Load Template Directory')).toBeInTheDocument(); + expect(screen.getByText('Edit Template Form')).toBeInTheDocument(); + }); + + it('template directory loading should not be supported in Jest', async () => { + await renderInTestApp(); + + expect( + screen.getByRole('button', { name: /Load Template Directory/ }), + ).toBeDisabled(); + }); + + it('should be able to continue to form preview', async () => { + await renderInTestApp( + + + , + ); + + await userEvent.click(screen.getByText('Edit Template Form')); + + expect(screen.getByLabelText('Load Existing Template')).toBeInTheDocument(); + }); +}); diff --git a/plugins/scaffolder/src/setupTests.ts b/plugins/scaffolder/src/setupTests.ts index 10252413a7..5499f969dc 100644 --- a/plugins/scaffolder/src/setupTests.ts +++ b/plugins/scaffolder/src/setupTests.ts @@ -19,3 +19,16 @@ import 'cross-fetch/polyfill'; const { EventSourcePolyfill } = jest.requireMock('event-source-polyfill'); global.EventSource = EventSourcePolyfill; + +// Patch jsdom to add feature used by CodeMirror +document.createRange = () => { + const range = new Range(); + + range.getClientRects = () => ({ + item: () => null, + length: 0, + [Symbol.iterator]: jest.fn(), + }); + + return range; +}; From d59ec5c6d5843201f53c02979d61d8966e862112 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 29 Apr 2022 16:46:17 +0200 Subject: [PATCH 41/56] scaffolder: add test for TemplateEditorBrowser Signed-off-by: Patrik Oldsberg --- .../TemplateEditorBrowser.test.tsx | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx new file mode 100644 index 0000000000..cec311a5cd --- /dev/null +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.test.tsx @@ -0,0 +1,55 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed 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 { renderInTestApp } from '@backstage/test-utils'; +import { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { MockFileSystemAccess } from '../../lib/filesystem/MockFileSystemAccess'; +import { DirectoryEditorProvider } from './DirectoryEditorContext'; +import { TemplateEditorBrowser } from './TemplateEditorBrowser'; + +Blob.prototype.text = async function text() { + return new Promise(resolve => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result as string); + reader.readAsText(this); + }); +}; + +describe('TemplateEditorBrowser', () => { + it('should render files and expand dirs without exploding', async () => { + await renderInTestApp( + + + , + ); + + await expect(screen.findByText('foo.txt')).resolves.toBeInTheDocument(); + expect(screen.getByText('dir')).toBeInTheDocument(); + expect(screen.queryByText('bar.txt')).not.toBeInTheDocument(); + expect(screen.queryByText('baz.txt')).not.toBeInTheDocument(); + + await userEvent.click(screen.getByText('dir')); + expect(screen.getByText('bar.txt')).toBeInTheDocument(); + expect(screen.getByText('baz.txt')).toBeInTheDocument(); + }); +}); From 636dc857812f63bcd6c3fed959935d055fb368d8 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 29 Apr 2022 16:48:56 +0200 Subject: [PATCH 42/56] scaffolder: fix dryRun implementation in ScaffolderClient Signed-off-by: Patrik Oldsberg --- plugins/scaffolder/src/api.ts | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 0c2dee8f7f..5cefeaaa5c 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -22,8 +22,7 @@ import { } from '@backstage/core-plugin-api'; import { ResponseError } from '@backstage/errors'; import { ScmIntegrationRegistry } from '@backstage/integration'; -import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; -import { JsonObject, Observable } from '@backstage/types'; +import { Observable } from '@backstage/types'; import qs from 'qs'; import ObservableImpl from 'zen-observable'; import { @@ -37,6 +36,8 @@ import { ScaffolderGetIntegrationsListOptions, ScaffolderGetIntegrationsListResponse, ScaffolderTask, + ScaffolderDryRunOptions, + ScaffolderDryRunResponse, } from './types'; /** @@ -173,14 +174,11 @@ export class ScaffolderClient implements ScaffolderApi { return this.streamLogsEventStream(options); } - async dryRun(options: { - template: TemplateEntityV1beta3; - values: JsonObject; - secrets: JsonObject; - content: { path: string; base64Content: string }[]; - }): Promise { + async dryRun( + options: ScaffolderDryRunOptions, + ): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); - const res = await this.fetchApi.fetch(`${baseUrl}/v2/dryrun`, { + const response = await this.fetchApi.fetch(`${baseUrl}/v2/dryrun`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -193,11 +191,11 @@ export class ScaffolderClient implements ScaffolderApi { }), }); - if (!res.ok) { - throw await ResponseError.fromResponse(res); + if (!response.ok) { + throw await ResponseError.fromResponse(response); } - return res.json(); + return response.json(); } private streamLogsEventStream({ From b622410ef0397c4f01d47845c4d82d66cb007885 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 2 May 2022 11:41:19 +0200 Subject: [PATCH 43/56] scaffolder,scaffolder-backend: fix API reports Signed-off-by: Patrik Oldsberg --- plugins/scaffolder-backend/api-report.md | 4 +++ plugins/scaffolder/api-report.md | 36 ++++++++++++++++++++++++ plugins/scaffolder/src/index.ts | 2 ++ plugins/scaffolder/src/types.ts | 2 ++ 4 files changed, 44 insertions(+) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index b059a30d9b..1b2676cd00 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -40,6 +40,7 @@ export type ActionContext = { output(name: string, value: JsonValue): void; createTemporaryDirectory(): Promise; templateInfo?: TemplateInfo; + isDryRun?: boolean; }; // @public @@ -550,6 +551,8 @@ export interface TaskContext { // (undocumented) getWorkspaceName(): Promise; // (undocumented) + isDryRun?: boolean; + // (undocumented) secrets?: TaskSecrets; // (undocumented) spec: TaskSpec; @@ -665,6 +668,7 @@ export class TaskWorker { export type TemplateAction = { id: string; description?: string; + supportsDryRun?: boolean; schema?: { input?: Schema; output?: Schema; diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 229737eaf6..f7314390df 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -25,6 +25,7 @@ import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TaskSpec } from '@backstage/plugin-scaffolder-common'; +import { TaskStep } from '@backstage/plugin-scaffolder-common'; import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; // @public @@ -226,6 +227,8 @@ export type RouterProps = { // @public export interface ScaffolderApi { + // (undocumented) + dryRun?(options: ScaffolderDryRunOptions): Promise; // (undocumented) getIntegrationsList( options: ScaffolderGetIntegrationsListOptions, @@ -256,6 +259,8 @@ export class ScaffolderClient implements ScaffolderApi { useLongPollingLogs?: boolean; }); // (undocumented) + dryRun(options: ScaffolderDryRunOptions): Promise; + // (undocumented) getIntegrationsList( options: ScaffolderGetIntegrationsListOptions, ): Promise; @@ -274,6 +279,37 @@ export class ScaffolderClient implements ScaffolderApi { streamLogs(options: ScaffolderStreamLogsOptions): Observable; } +// @public (undocumented) +export interface ScaffolderDryRunOptions { + // (undocumented) + content: { + path: string; + base64Content: string; + }[]; + // (undocumented) + secrets: JsonObject; + // (undocumented) + template: JsonValue; + // (undocumented) + values: JsonObject; +} + +// @public (undocumented) +export interface ScaffolderDryRunResponse { + // (undocumented) + content: Array<{ + path: string; + base64Content: string; + executable: boolean; + }>; + // (undocumented) + log: Array; + // (undocumented) + output: ScaffolderTaskOutput; + // (undocumented) + steps: TaskStep[]; +} + // @public export const ScaffolderFieldExtensions: React_2.ComponentType; diff --git a/plugins/scaffolder/src/index.ts b/plugins/scaffolder/src/index.ts index fc132f644c..c42dc95f9e 100644 --- a/plugins/scaffolder/src/index.ts +++ b/plugins/scaffolder/src/index.ts @@ -25,6 +25,8 @@ export type { ListActionsResponse, LogEvent, ScaffolderApi, + ScaffolderDryRunOptions, + ScaffolderDryRunResponse, ScaffolderGetIntegrationsListOptions, ScaffolderGetIntegrationsListResponse, ScaffolderOutputLink, diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 1291cd9951..1044d36823 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -150,6 +150,7 @@ export interface ScaffolderStreamLogsOptions { after?: number; } +/** @public */ export interface ScaffolderDryRunOptions { template: JsonValue; values: JsonObject; @@ -157,6 +158,7 @@ export interface ScaffolderDryRunOptions { content: { path: string; base64Content: string }[]; } +/** @public */ export interface ScaffolderDryRunResponse { content: Array<{ path: string; From 36c39f9b42fd880012709fff92c6767c37cf868b Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 31 May 2022 10:27:18 +0200 Subject: [PATCH 44/56] sfaffolder: switch /v2/dryrun to /v2/dry-run Signed-off-by: Patrik Oldsberg --- plugins/scaffolder-backend/src/service/router.ts | 2 +- plugins/scaffolder/src/api.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index dcef37f8e4..7bf61c9eaf 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -334,7 +334,7 @@ export async function createRouter( clearTimeout(timeout); }); }) - .post('/v2/dryrun', async (req, res) => { + .post('/v2/dry-run', async (req, res) => { const template = req.body.template as TemplateEntityV1beta3; const values = req.body.values; const token = getBearerToken(req.headers.authorization); diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 5cefeaaa5c..763d08b60a 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -178,7 +178,7 @@ export class ScaffolderClient implements ScaffolderApi { options: ScaffolderDryRunOptions, ): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('scaffolder'); - const response = await this.fetchApi.fetch(`${baseUrl}/v2/dryrun`, { + const response = await this.fetchApi.fetch(`${baseUrl}/v2/dry-run`, { method: 'POST', headers: { 'Content-Type': 'application/json', From caafe75e890d14ad9767eb6ee0432f1bf89a5375 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 31 May 2022 10:38:29 +0200 Subject: [PATCH 45/56] scaffolder-backend: validate dry-run template input Signed-off-by: Patrik Oldsberg --- plugins/scaffolder-backend/src/service/router.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 7bf61c9eaf..da5e2c3030 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -28,6 +28,7 @@ import { ScmIntegrations } from '@backstage/integration'; import { TemplateEntityV1beta3, TaskSpec, + templateEntityV1beta3Validator, } from '@backstage/plugin-scaffolder-common'; import express from 'express'; import Router from 'express-promise-router'; @@ -336,6 +337,10 @@ export async function createRouter( }) .post('/v2/dry-run', async (req, res) => { const template = req.body.template as TemplateEntityV1beta3; + if (!(await templateEntityV1beta3Validator.check(template))) { + throw new InputError('Input template is not a template'); + } + const values = req.body.values; const token = getBearerToken(req.headers.authorization); const content = req.body.content as { From 0203775f4be5bd2c069c3e3d4726343f9da74c38 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 31 May 2022 10:55:42 +0200 Subject: [PATCH 46/56] scaffolder-backend: add dry-run request body validation Signed-off-by: Patrik Oldsberg --- plugins/scaffolder-backend/package.json | 3 +- .../scaffolder-backend/src/service/router.ts | 32 ++++++++++++------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 528d84a299..acaf18a8d4 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -72,7 +72,8 @@ "winston": "^3.2.1", "yaml": "^1.10.0", "vm2": "^3.9.6", - "zen-observable": "^0.8.15" + "zen-observable": "^0.8.15", + "zod": "^3.11.6" }, "devDependencies": { "@backstage/backend-test-utils": "^0.1.25-next.0", diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index da5e2c3030..01a6bbb235 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -22,7 +22,7 @@ import { UserEntity, } from '@backstage/catalog-model'; import { Entity } from '@backstage/catalog-model'; -import { Config } from '@backstage/config'; +import { Config, JsonObject } from '@backstage/config'; import { InputError, NotFoundError } from '@backstage/errors'; import { ScmIntegrations } from '@backstage/integration'; import { @@ -35,6 +35,7 @@ import Router from 'express-promise-router'; import { validate } from 'jsonschema'; import { Logger } from 'winston'; import { TemplateFilter } from '../lib'; +import { z } from 'zod'; import { createBuiltinActions, DatabaseTaskStore, @@ -336,20 +337,27 @@ export async function createRouter( }); }) .post('/v2/dry-run', async (req, res) => { - const template = req.body.template as TemplateEntityV1beta3; + const bodySchema = z.object({ + template: z.unknown(), + values: z.record(z.unknown()), + secrets: z.record(z.string()), + content: z.array( + z.object({ path: z.string(), base64Content: z.string() }), + ), + }); + const body = await bodySchema.parseAsync(req.body).catch(e => { + throw new InputError(`Malformed request: ${e}`); + }); + + const template = body.template as TemplateEntityV1beta3; if (!(await templateEntityV1beta3Validator.check(template))) { throw new InputError('Input template is not a template'); } - const values = req.body.values; const token = getBearerToken(req.headers.authorization); - const content = req.body.content as { - path: string; - base64Content: string; - }[]; for (const parameters of [template.spec.parameters ?? []].flat()) { - const result = validate(values, parameters); + const result = validate(body.values, parameters); if (!result.valid) { res.status(400).json({ errors: result.errors }); return; @@ -367,15 +375,15 @@ export async function createRouter( apiVersion: template.apiVersion, steps, output: template.spec.output ?? {}, - parameters: values, + parameters: body.values as JsonObject, }, - content: (content ?? []).map(file => ({ + content: (body.content ?? []).map(file => ({ path: file.path, content: Buffer.from(file.base64Content, 'base64'), })), secrets: { - ...req.body.secrets, - backstageToken: token, + ...body.secrets, + ...(token && { backstageToken: token }), }, }); From 63f2164c1dd4dbbeda378638f1b9d0cf891d6b4a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 31 May 2022 10:56:43 +0200 Subject: [PATCH 47/56] scaffolder-backend: clarifying comment about done Signed-off-by: Patrik Oldsberg --- .../scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts index de3590c3ac..d4dfb2dd17 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -109,6 +109,7 @@ export function createDryRunner(options: TemplateTesterCreateOptions) { }, }, secrets: input.secrets, + // No need to update this at the end of the run, so just hard-code it done: false, isDryRun: true, getWorkspaceName: async () => `dry-run-${dryRunId}`, From 03a467ebc9fda69117e2f681cd8a3d6aba453ea3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 31 May 2022 11:23:40 +0200 Subject: [PATCH 48/56] scaffolder-backend: refactor actions to use serializeDirectoryContents Signed-off-by: Patrik Oldsberg --- .../actions/builtin/fetch/template.ts | 2 + .../builtin/publish/githubPullRequest.ts | 73 +++++++------------ .../builtin/publish/gitlabMergeRequest.ts | 38 ++++------ 3 files changed, 43 insertions(+), 70 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 041263c90c..c6d6b86e7b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -154,6 +154,7 @@ export function createFetchTemplateAction(options: { dot: true, onlyFiles: false, markDirectories: true, + followSymbolicLinks: false, }); const nonTemplatedEntries = new Set( @@ -165,6 +166,7 @@ export function createFetchTemplateAction(options: { dot: true, onlyFiles: false, markDirectories: true, + followSymbolicLinks: false, }), ), ) diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts index 3bf90d1e30..f8f9b22d61 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/githubPullRequest.ts @@ -14,21 +14,19 @@ * limitations under the License. */ -import fs from 'fs-extra'; -import { parseRepoUrl, isExecutable } from './util'; - +import path from 'path'; +import { parseRepoUrl } from './util'; import { GithubCredentialsProvider, ScmIntegrationRegistry, } from '@backstage/integration'; -import { zipObject } from 'lodash'; import { createTemplateAction } from '../../createTemplateAction'; import { Octokit } from 'octokit'; import { InputError, CustomErrorBase } from '@backstage/errors'; import { createPullRequest } from 'octokit-plugin-create-pull-request'; -import globby from 'globby'; import { resolveSafeChildPath } from '@backstage/backend-common'; import { getOctokitOptions } from '../github/helpers'; +import { serializeDirectoryContents } from '../../../../lib/files'; export type Encoding = 'utf-8' | 'base64'; @@ -219,56 +217,39 @@ export const createPublishGithubPullRequestAction = ({ ? resolveSafeChildPath(ctx.workspacePath, sourcePath) : ctx.workspacePath; - const localFilePaths = await globby(['./**', './**/.*', '!.git'], { - cwd: fileRoot, + const directoryContents = await serializeDirectoryContents(fileRoot, { gitignore: true, - dot: true, }); - - const fileContents = await Promise.all( - localFilePaths.map(filePath => { - const absPath = resolveSafeChildPath(fileRoot, filePath); - const base64EncodedContent = fs - .readFileSync(absPath) - .toString('base64'); - const fileStat = fs.statSync(absPath); - // See the properties of tree items - // in https://docs.github.com/en/rest/reference/git#trees - const githubTreeItemMode = isExecutable(fileStat.mode) - ? '100755' - : '100644'; - // Always use base64 encoding to avoid doubling a binary file in size - // due to interpreting a binary file as utf-8 and sending github - // the utf-8 encoded content. - // - // For example, the original gradle-wrapper.jar is 57.8k in https://github.com/kennethzfeng/pull-request-test/pull/5/files. - // Its size could be doubled to 98.3K (See https://github.com/kennethzfeng/pull-request-test/pull/4/files) - const encoding: Encoding = 'base64'; - return { - encoding: encoding, - content: base64EncodedContent, - mode: githubTreeItemMode, - }; - }), + const files = Object.fromEntries( + directoryContents.map(file => [ + targetPath ? path.posix.join(targetPath, file.path) : file.path, + { + // See the properties of tree items + // in https://docs.github.com/en/rest/reference/git#trees + mode: file.executable ? '100755' : '100644', + // Always use base64 encoding to avoid doubling a binary file in size + // due to interpreting a binary file as utf-8 and sending github + // the utf-8 encoded content. + // + // For example, the original gradle-wrapper.jar is 57.8k in https://github.com/kennethzfeng/pull-request-test/pull/5/files. + // Its size could be doubled to 98.3K (See https://github.com/kennethzfeng/pull-request-test/pull/4/files) + encoding: 'base64' as const, + content: file.content.toString('base64'), + }, + ]), ); - const repoFilePaths = localFilePaths.map(repoFilePath => { - return targetPath ? `${targetPath}/${repoFilePath}` : repoFilePath; - }); - - const changes = [ - { - files: zipObject(repoFilePaths, fileContents), - commit: title, - }, - ]; - try { const response = await client.createPullRequest({ owner, repo, title, - changes, + changes: [ + { + files, + commit: title, + }, + ], body: description, head: branchName, draft, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts index 63cfeaa8b3..7259336b23 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts @@ -14,15 +14,14 @@ * limitations under the License. */ import { createTemplateAction } from '../../createTemplateAction'; -import { readFile } from 'fs-extra'; import { Gitlab } from '@gitbeaker/node'; -import globby from 'globby'; import { Types } from '@gitbeaker/core'; - +import path from 'path'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { InputError } from '@backstage/errors'; import { parseRepoUrl } from './util'; import { resolveSafeChildPath } from '@backstage/backend-common'; +import { serializeDirectoryContents } from '../../../../lib/files'; /** * Create a new action that creates a gitlab merge request. @@ -106,8 +105,6 @@ export const createPublishGitlabMergeRequestAction = (options: { const { host } = parseRepoUrl(repoUrl, integrations); const integrationConfig = integrations.gitlab.byHost(host); - const actions: Types.CommitAction[] = []; - const destinationBranch = ctx.input.branchName; if (!integrationConfig) { @@ -128,28 +125,21 @@ export const createPublishGitlabMergeRequestAction = (options: { [tokenType]: token, }); - const fileRoot = ctx.workspacePath; - const localFilePaths = await globby([`${ctx.input.targetPath}/**`], { - cwd: fileRoot, - gitignore: true, - dot: true, - }); - - const fileContents = await Promise.all( - localFilePaths.map(p => readFile(resolveSafeChildPath(fileRoot, p))), + const targetPath = resolveSafeChildPath( + ctx.workspacePath, + ctx.input.targetPath, ); - - const repoFilePaths = localFilePaths.map(repoFilePath => { - return repoFilePath; + const fileContents = await serializeDirectoryContents(targetPath, { + gitignore: true, }); - for (let i = 0; i < repoFilePaths.length; i++) { - actions.push({ - action: 'create', - filePath: repoFilePaths[i], - content: fileContents[i].toString(), - }); - } + const actions: Types.CommitAction[] = fileContents.map(file => ({ + action: 'create', + filePath: path.posix.join(ctx.input.targetPath, file.path), + encoding: 'base64', + content: file.content.toString('base64'), + execute_filemode: file.executable, + })); const projects = await api.Projects.show(ctx.input.projectid); From c7a5d8d7ae6aad56d667991d7c5bb3a95047f450 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 31 May 2022 12:18:29 +0200 Subject: [PATCH 49/56] scaffolder: rename dry-run contents -> directoryContents Signed-off-by: Patrik Oldsberg --- .../src/scaffolder/dryrun/createDryRunner.ts | 10 +++++----- plugins/scaffolder-backend/src/service/router.ts | 6 +++--- plugins/scaffolder/src/api.ts | 2 +- .../components/TemplateEditorPage/DryRunContext.tsx | 2 +- .../DryRunResults/DryRunResults.test.tsx | 7 ++++++- .../DryRunResults/DryRunResultsList.test.tsx | 7 ++++++- .../DryRunResults/DryRunResultsView.test.tsx | 2 +- .../DryRunResults/DryRunResultsView.tsx | 6 +++--- plugins/scaffolder/src/types.ts | 4 ++-- 9 files changed, 28 insertions(+), 18 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts index d4dfb2dd17..8453f793b5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -36,12 +36,12 @@ import { resolveSafeChildPath } from '@backstage/backend-common'; interface DryRunInput { spec: TaskSpec; secrets?: TaskSecrets; - content: SerializedFile[]; + directoryContents: SerializedFile[]; } interface DryRunResult { log: JsonObject[]; - content: SerializedFile[]; + directoryContents: SerializedFile[]; output: JsonObject; } @@ -88,7 +88,7 @@ export function createDryRunner(options: TemplateTesterCreateOptions) { ); try { - await deserializeDirectoryContents(contentsPath, input.content); + await deserializeDirectoryContents(contentsPath, input.directoryContents); const result = await workflowRunner.execute({ spec: { @@ -130,11 +130,11 @@ export function createDryRunner(options: TemplateTesterCreateOptions) { if (!contentPromise) { throw new Error('Content extraction step was skipped'); } - const content = await contentPromise; + const directoryContents = await contentPromise; return { log, - content, + directoryContents, output: result.output, }; } finally { diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 01a6bbb235..61035c71fe 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -341,7 +341,7 @@ export async function createRouter( template: z.unknown(), values: z.record(z.unknown()), secrets: z.record(z.string()), - content: z.array( + directoryContents: z.array( z.object({ path: z.string(), base64Content: z.string() }), ), }); @@ -377,7 +377,7 @@ export async function createRouter( output: template.spec.output ?? {}, parameters: body.values as JsonObject, }, - content: (body.content ?? []).map(file => ({ + directoryContents: (body.directoryContents ?? []).map(file => ({ path: file.path, content: Buffer.from(file.base64Content, 'base64'), })), @@ -390,7 +390,7 @@ export async function createRouter( res.status(200).json({ ...result, steps, - content: result.content.map(file => ({ + directoryContents: result.directoryContents.map(file => ({ path: file.path, executable: file.executable, base64Content: file.content.toString('base64'), diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 763d08b60a..f31bdf70b9 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -187,7 +187,7 @@ export class ScaffolderClient implements ScaffolderApi { template: options.template, values: options.values, secrets: options.secrets, - content: options.content, + directoryContents: options.directoryContents, }), }); diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx index 97112f75df..b1fe8a19ff 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunContext.tsx @@ -108,7 +108,7 @@ export function DryRunProvider(props: DryRunProviderProps) { template: parsed, values: options.values, secrets: {}, - content: options.files.map(file => ({ + directoryContents: options.files.map(file => ({ path: file.path, base64Content: btoa(file.content), })), diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResults.test.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResults.test.tsx index 7b08544736..bc3354cc46 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResults.test.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResults.test.tsx @@ -51,7 +51,12 @@ function DryRunRemote({ } const mockScaffolderApi = { - dryRun: async () => ({ content: [], log: [], output: {}, steps: [] }), + dryRun: async () => ({ + directoryContents: [], + log: [], + output: {}, + steps: [], + }), }; describe('DryRunResults', () => { diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsList.test.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsList.test.tsx index ca35f71e95..65eb59c84a 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsList.test.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsList.test.tsx @@ -40,7 +40,12 @@ function DryRunRemote({ execute }: { execute?: number }) { } const mockScaffolderApi = { - dryRun: async () => ({ content: [], log: [], output: {}, steps: [] }), + dryRun: async () => ({ + directoryContents: [], + log: [], + output: {}, + steps: [], + }), }; describe('DryRunResultsList', () => { diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx index a26d6e3803..3a6bf0aff8 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.test.tsx @@ -55,7 +55,7 @@ describe('DryRunResultsView', () => { scaffolderApiRef, { dryRun: async () => ({ - content: [ + directoryContents: [ { path: 'foo.txt', base64Content: btoa('Foo Content'), diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx index f854f504ce..275a1b9902 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx @@ -61,13 +61,13 @@ function FilesContent() { const classes = useStyles(); const { selectedResult } = useDryRun(); const [selectedPath, setSelectedPath] = useState(''); - const selectedFile = selectedResult?.content.find( + const selectedFile = selectedResult?.directoryContents.find( f => f.path === selectedPath, ); useEffect(() => { if (selectedResult) { - const [firstFile] = selectedResult.content; + const [firstFile] = selectedResult.directoryContents; if (firstFile) { setSelectedPath(firstFile.path); } else { @@ -85,7 +85,7 @@ function FilesContent() { file.path)} + filePaths={selectedResult.directoryContents.map(file => file.path)} /> Date: Tue, 31 May 2022 12:20:15 +0200 Subject: [PATCH 50/56] scaffolder: make dry-run secrets optional to match task execution Signed-off-by: Patrik Oldsberg --- plugins/scaffolder-backend/src/service/router.ts | 2 +- plugins/scaffolder/src/types.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 61035c71fe..3132ccffda 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -340,7 +340,7 @@ export async function createRouter( const bodySchema = z.object({ template: z.unknown(), values: z.record(z.unknown()), - secrets: z.record(z.string()), + secrets: z.record(z.string()).optional(), directoryContents: z.array( z.object({ path: z.string(), base64Content: z.string() }), ), diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index 4f56345cf3..c0b828314b 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -154,7 +154,7 @@ export interface ScaffolderStreamLogsOptions { export interface ScaffolderDryRunOptions { template: JsonValue; values: JsonObject; - secrets: JsonObject; + secrets?: Record; directoryContents: { path: string; base64Content: string }[]; } From c94fb6d666a1c3f71aaaf6831237e39c9a740a4d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 31 May 2022 12:20:37 +0200 Subject: [PATCH 51/56] scaffolder: fix life Signed-off-by: Patrik Oldsberg --- .../src/lib/files/serializeDirectoryContents.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts b/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts index a58274d694..e1ea676ec5 100644 --- a/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts +++ b/plugins/scaffolder-backend/src/lib/files/serializeDirectoryContents.test.ts @@ -22,7 +22,7 @@ describe('serializeDirectoryContents', () => { mockFs.restore(); }); - it('should life files in this directory', async () => { + it('should list files in this directory', async () => { await expect(serializeDirectoryContents(__dirname)).resolves.toEqual( expect.arrayContaining([ { From 73e427c4054770c46a2550144bab447526b1938d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 31 May 2022 13:03:55 +0200 Subject: [PATCH 52/56] scaffolder: wrap dry-run log body in an outer object Signed-off-by: Patrik Oldsberg --- .../src/scaffolder/dryrun/createDryRunner.ts | 10 ++++++---- .../DryRunResults/DryRunResultsList.tsx | 2 +- .../DryRunResults/DryRunResultsView.test.tsx | 2 +- .../DryRunResults/DryRunResultsView.tsx | 8 +++++--- plugins/scaffolder/src/types.ts | 2 +- 5 files changed, 14 insertions(+), 10 deletions(-) diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts index 8453f793b5..f2c3c13509 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -40,7 +40,7 @@ interface DryRunInput { } interface DryRunResult { - log: JsonObject[]; + log: Array<{ body: JsonObject }>; directoryContents: SerializedFile[]; output: JsonObject; } @@ -81,7 +81,7 @@ export function createDryRunner(options: TemplateTesterCreateOptions) { }); const dryRunId = uuid(); - const log = new Array(); + const log = new Array<{ body: JsonObject }>(); const contentsPath = resolveSafeChildPath( options.workingDirectory, `dry-run-content-${dryRunId}`, @@ -118,8 +118,10 @@ export function createDryRunner(options: TemplateTesterCreateOptions) { return; } log.push({ - ...logMetadata, - message, + body: { + ...logMetadata, + message, + }, }); }, async complete() { diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx index 920f767cf8..462c3b58fd 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsList.tsx @@ -52,7 +52,7 @@ export function DryRunResultsList() { return ( {dryRun.results.map(result => { - const failed = result.log.some(l => l.status === 'failed'); + const failed = result.log.some(l => l.body.status === 'failed'); return ( { executable: false, }, ], - log: [{ message: 'Foo Message', stepId: 'foo' }], + log: [{ body: { message: 'Foo Message', stepId: 'foo' } }], output: { links: [{ title: 'Foo Link', url: 'http://example.com' }], }, diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx index 275a1b9902..9c56039808 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/DryRunResults/DryRunResultsView.tsx @@ -110,12 +110,14 @@ function LogContent() { } return ( selectedResult.steps.map(step => { - const stepLog = selectedResult.log.filter(l => l.stepId === step.id); + const stepLog = selectedResult.log.filter( + l => l.body.stepId === step.id, + ); return { id: step.id, name: step.name, - logString: stepLog.map(l => l.message).join('\n'), - status: stepLog[stepLog.length - 1]?.status ?? 'completed', + logString: stepLog.map(l => l.body.message).join('\n'), + status: stepLog[stepLog.length - 1]?.body.status ?? 'completed', }; }) ?? [] ); diff --git a/plugins/scaffolder/src/types.ts b/plugins/scaffolder/src/types.ts index c0b828314b..efdfcaff93 100644 --- a/plugins/scaffolder/src/types.ts +++ b/plugins/scaffolder/src/types.ts @@ -165,7 +165,7 @@ export interface ScaffolderDryRunResponse { base64Content: string; executable: boolean; }>; - log: Array; + log: Array>; steps: TaskStep[]; output: ScaffolderTaskOutput; } From a2490f64fe18bb718fc461cfc85f2c45b63a9e92 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 31 May 2022 13:05:51 +0200 Subject: [PATCH 53/56] scaffolder: remove unnecessary TemplateFileSystemAccess interface Signed-off-by: Patrik Oldsberg --- .../TemplateEditorPage/TemplateEditorPage.tsx | 3 +-- .../src/lib/filesystem/WebFileSystemAccess.ts | 20 +++++-------------- .../scaffolder/src/lib/filesystem/index.ts | 6 +----- .../scaffolder/src/lib/filesystem/types.ts | 4 ---- 4 files changed, 7 insertions(+), 26 deletions(-) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx index 3741a6fc3d..fce2881190 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorPage.tsx @@ -64,8 +64,7 @@ export function TemplateEditorPage(props: TemplateEditorPageProps) { { if (option === 'local') { - WebFileSystemAccess.get() - .requestDirectoryAccess() + WebFileSystemAccess.requestDirectoryAccess() .then(directory => setSelection({ type: 'local', directory })) .catch(() => {}); } else if (option === 'form') { diff --git a/plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts b/plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts index 2de3bd296f..36e7b11de2 100644 --- a/plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts +++ b/plugins/scaffolder/src/lib/filesystem/WebFileSystemAccess.ts @@ -14,11 +14,7 @@ * limitations under the License. */ -import { - TemplateFileSystemAccess, - TemplateDirectoryAccess, - TemplateFileAccess, -} from './types'; +import { TemplateDirectoryAccess, TemplateFileAccess } from './types'; type WritableFileHandle = FileSystemFileHandle & { createWritable(): Promise<{ @@ -82,24 +78,18 @@ class WebDirectoryAccess implements TemplateDirectoryAccess { } /** @internal */ -export class WebFileSystemAccess implements TemplateFileSystemAccess { - private static instance = new WebFileSystemAccess(); - - static get(): TemplateFileSystemAccess { - return this.instance; - } - +export class WebFileSystemAccess { static isSupported(): boolean { return Boolean(showDirectoryPicker); } - private constructor() {} - - async requestDirectoryAccess(): Promise { + static async requestDirectoryAccess(): Promise { if (!showDirectoryPicker) { throw new Error('File system access is not supported'); } const handle = await showDirectoryPicker(); return new WebDirectoryAccess(handle); } + + private constructor() {} } diff --git a/plugins/scaffolder/src/lib/filesystem/index.ts b/plugins/scaffolder/src/lib/filesystem/index.ts index c57fc35756..1f485d0743 100644 --- a/plugins/scaffolder/src/lib/filesystem/index.ts +++ b/plugins/scaffolder/src/lib/filesystem/index.ts @@ -14,10 +14,6 @@ * limitations under the License. */ -export type { - TemplateFileAccess, - TemplateDirectoryAccess, - TemplateFileSystemAccess, -} from './types'; +export type { TemplateFileAccess, TemplateDirectoryAccess } from './types'; export { blobToBase64 } from './helpers'; export { WebFileSystemAccess } from './WebFileSystemAccess'; diff --git a/plugins/scaffolder/src/lib/filesystem/types.ts b/plugins/scaffolder/src/lib/filesystem/types.ts index 820ae5f99d..11de159b2a 100644 --- a/plugins/scaffolder/src/lib/filesystem/types.ts +++ b/plugins/scaffolder/src/lib/filesystem/types.ts @@ -23,7 +23,3 @@ export interface TemplateFileAccess { export interface TemplateDirectoryAccess { listFiles(): Promise>; } - -export interface TemplateFileSystemAccess { - requestDirectoryAccess(): Promise; -} From 407996a8bda52148a24d42cb1cb89afd77aed96a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 31 May 2022 13:07:28 +0200 Subject: [PATCH 54/56] scaffolder: update API report Signed-off-by: Patrik Oldsberg --- plugins/scaffolder/api-report.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index f7314390df..0884e0116c 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -282,12 +282,12 @@ export class ScaffolderClient implements ScaffolderApi { // @public (undocumented) export interface ScaffolderDryRunOptions { // (undocumented) - content: { + directoryContents: { path: string; base64Content: string; }[]; // (undocumented) - secrets: JsonObject; + secrets?: Record; // (undocumented) template: JsonValue; // (undocumented) @@ -297,13 +297,13 @@ export interface ScaffolderDryRunOptions { // @public (undocumented) export interface ScaffolderDryRunResponse { // (undocumented) - content: Array<{ + directoryContents: Array<{ path: string; base64Content: string; executable: boolean; }>; // (undocumented) - log: Array; + log: Array>; // (undocumented) output: ScaffolderTaskOutput; // (undocumented) From 139f8f08a2bf506a66e82e824f1e94104e50e691 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 31 May 2022 13:11:59 +0200 Subject: [PATCH 55/56] scaffolder: disable global save button in template editor if no files are dirty Signed-off-by: Patrik Oldsberg --- .../src/components/TemplateEditorPage/TemplateEditorBrowser.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.tsx b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.tsx index 9402047327..177ad11c2b 100644 --- a/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.tsx +++ b/plugins/scaffolder/src/components/TemplateEditorPage/TemplateEditorBrowser.tsx @@ -67,6 +67,7 @@ export function TemplateEditorBrowser(props: { onClose?: () => void }) { !file.dirty)} onClick={() => directoryEditor.save()} > From b2e0687363c0659444ae89990d274582aafd7387 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 31 May 2022 13:27:19 +0200 Subject: [PATCH 56/56] scaffolder-backend: fix merge conflict in router Signed-off-by: Patrik Oldsberg --- plugins/scaffolder-backend/src/service/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 3132ccffda..df416093f4 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -354,7 +354,7 @@ export async function createRouter( throw new InputError('Input template is not a template'); } - const token = getBearerToken(req.headers.authorization); + const { token } = parseBearerToken(req.headers.authorization); for (const parameters of [template.spec.parameters ?? []].flat()) { const result = validate(body.values, parameters);