diff --git a/.changeset/big-islands-add.md b/.changeset/big-islands-add.md new file mode 100644 index 0000000000..4a6511562b --- /dev/null +++ b/.changeset/big-islands-add.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Set the `searchTooltip` to "Filter" to follow how the `searchPlaceholder` is set making this more consistent diff --git a/.changeset/breezy-pots-worry.md b/.changeset/breezy-pots-worry.md deleted file mode 100644 index 48d5ae6c80..0000000000 --- a/.changeset/breezy-pots-worry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-org': patch ---- - -consistently show parent and child relations in group profile card diff --git a/.changeset/bright-rules-shout.md b/.changeset/bright-rules-shout.md deleted file mode 100644 index f7cdae3b4f..0000000000 --- a/.changeset/bright-rules-shout.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-model': patch ---- - -Defer `ajv` compilation of schema validators to improve module-import performance diff --git a/.changeset/brown-days-pretend.md b/.changeset/brown-days-pretend.md new file mode 100644 index 0000000000..4b6a328724 --- /dev/null +++ b/.changeset/brown-days-pretend.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-insights': patch +--- + +Fixed bug when sending data by Post in `runChecks` and `runBulkChecks` functions of the `TechInsightsClient` class, the default `Content-Type` used was `plain/text` diff --git a/.changeset/chatty-planets-flash.md b/.changeset/chatty-planets-flash.md new file mode 100644 index 0000000000..d6a5c6ad42 --- /dev/null +++ b/.changeset/chatty-planets-flash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +Add `gitlabDiscoveryEntityProviderCatalogModule` (new backend-plugin-api, alpha). diff --git a/.changeset/clean-camels-sneeze.md b/.changeset/clean-camels-sneeze.md deleted file mode 100644 index 0fccb52e0b..0000000000 --- a/.changeset/clean-camels-sneeze.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Newly created Backstage repositories now use the stable version 6 of -`react-router`, just like the main repo does. Please let us know if you find any -issues with this. - -Migrating to the stable version of `react-router` is optional for the time -being. But if you want to do the same for your existing repository, please -follow [this -guide](https://backstage.io/docs/tutorials/react-router-stable-migration). diff --git a/.changeset/clean-planets-rhyme.md b/.changeset/clean-planets-rhyme.md new file mode 100644 index 0000000000..a6878b491b --- /dev/null +++ b/.changeset/clean-planets-rhyme.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-azure': patch +--- + +`AzureDevOpsEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + +Please find how to configure the schedule at the config at +https://backstage.io/docs/integrations/azure/discovery diff --git a/.changeset/curvy-islands-marry.md b/.changeset/curvy-islands-marry.md new file mode 100644 index 0000000000..2d3bad31ac --- /dev/null +++ b/.changeset/curvy-islands-marry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-github-pull-requests-board': patch +--- + +Replace the momentjs dependency with luxon. diff --git a/.changeset/curvy-kiwis-fold.md b/.changeset/curvy-kiwis-fold.md deleted file mode 100644 index 8d94f38343..0000000000 --- a/.changeset/curvy-kiwis-fold.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Update versions of packages used in the create-app template, to match those in the main repo diff --git a/.changeset/flat-kangaroos-kiss.md b/.changeset/flat-kangaroos-kiss.md new file mode 100644 index 0000000000..a79c8621b1 --- /dev/null +++ b/.changeset/flat-kangaroos-kiss.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +`GitlabDiscoveryEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + +Please find how to configure the schedule at the config at +https://backstage.io/docs/integrations/gitlab/discovery diff --git a/.changeset/forty-jokes-lie.md b/.changeset/forty-jokes-lie.md new file mode 100644 index 0000000000..bd44898004 --- /dev/null +++ b/.changeset/forty-jokes-lie.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-openapi': patch +--- + +Added support to use the `UrlReaders` when `$ref` pointing to a URL. diff --git a/.changeset/gorgeous-onions-thank.md b/.changeset/gorgeous-onions-thank.md new file mode 100644 index 0000000000..8b41bfa5a0 --- /dev/null +++ b/.changeset/gorgeous-onions-thank.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-aws': patch +--- + +`AwsS3EntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + +Please find how to configure the schedule at the config at +https://backstage.io/docs/integrations/aws-s3/discovery diff --git a/.changeset/heavy-elephants-nail.md b/.changeset/heavy-elephants-nail.md new file mode 100644 index 0000000000..473e3eda9e --- /dev/null +++ b/.changeset/heavy-elephants-nail.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-app-api': minor +--- + +Deprecated the `homepage` config as the component that used it - `HomepageTimer` - has been removed and replaced by the `HeaderWorldClock` in the home plugin diff --git a/.changeset/itchy-paws-protect.md b/.changeset/itchy-paws-protect.md new file mode 100644 index 0000000000..a0c272e5e4 --- /dev/null +++ b/.changeset/itchy-paws-protect.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-azure': patch +--- + +Add `azureDevOpsEntityProviderCatalogModule` (new backend-plugin-api, alpha). diff --git a/.changeset/kind-emus-juggle.md b/.changeset/kind-emus-juggle.md new file mode 100644 index 0000000000..2a99deef81 --- /dev/null +++ b/.changeset/kind-emus-juggle.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-aws': patch +--- + +Add `awsS3EntityProviderCatalogModule` (new backend-plugin-api, alpha). diff --git a/.changeset/kind-penguins-report.md b/.changeset/kind-penguins-report.md deleted file mode 100644 index 4023ea5976..0000000000 --- a/.changeset/kind-penguins-report.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@backstage/plugin-auth-backend': minor ---- - -CookieConfigurer can optionally return the `SameSite` cookie attribute. -CookieConfigurer now requires an additional argument `appOrigin` - the origin URL of the app - which is used to calculate the `SameSite` attribute. -defaultCookieConfigurer returns the `SameSite` attribute which defaults to `Lax`. In cases where an auth-backend is running on a different domain than the App, `SameSite=None` is used - but only for secure contexts. This is so that cookies can be included in third-party requests. - -OAuthAdapterOptions has been modified to require additional arguments, `baseUrl`, and `cookieConfigurer`. -OAuthAdapter now resolves cookie configuration using its supplied CookieConfigurer for each request to make sure that the proper attributes always are set. diff --git a/.changeset/lazy-beds-pull.md b/.changeset/lazy-beds-pull.md deleted file mode 100644 index 0d8be46576..0000000000 --- a/.changeset/lazy-beds-pull.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Updated fallback versions of dependencies in all templates. diff --git a/.changeset/lazy-fireants-check.md b/.changeset/lazy-fireants-check.md deleted file mode 100644 index 234501b07a..0000000000 --- a/.changeset/lazy-fireants-check.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-kubernetes': patch ---- - -Add useCustomResources react hook for fetching Kubernetes Custom Resources diff --git a/.changeset/little-roses-rule.md b/.changeset/little-roses-rule.md deleted file mode 100644 index 4acec131e6..0000000000 --- a/.changeset/little-roses-rule.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Added a new `repo test` command. diff --git a/.changeset/metal-dogs-swim.md b/.changeset/metal-dogs-swim.md new file mode 100644 index 0000000000..d97c7077f2 --- /dev/null +++ b/.changeset/metal-dogs-swim.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gerrit': patch +--- + +Add `gerritEntityProviderCatalogModule` (new backend-plugin-api, alpha). diff --git a/.changeset/old-cobras-suffer.md b/.changeset/old-cobras-suffer.md deleted file mode 100644 index 2891c29133..0000000000 --- a/.changeset/old-cobras-suffer.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/test-utils': patch -'@backstage/plugin-cicd-statistics': patch ---- - -Align on the version of `@material-ui/icons` used, to `^4.9.1` like other packages in the main repo diff --git a/.changeset/perfect-moose-drum.md b/.changeset/perfect-moose-drum.md deleted file mode 100644 index 833e5eeb35..0000000000 --- a/.changeset/perfect-moose-drum.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Updated lockfile parsing to have better support for Yarn 3. diff --git a/.changeset/poor-moons-impress.md b/.changeset/poor-moons-impress.md new file mode 100644 index 0000000000..f441cba1f2 --- /dev/null +++ b/.changeset/poor-moons-impress.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-insights': patch +--- + +Set content type to 'application/json' in TechInsightsClient diff --git a/.changeset/pre.json b/.changeset/pre.json index 46123733ca..0bdcd4a8d9 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -2,178 +2,178 @@ "mode": "pre", "tag": "next", "initialVersions": { - "example-app": "0.2.75", - "@backstage/app-defaults": "1.0.6", - "example-backend": "0.2.75", - "@backstage/backend-app-api": "0.2.1", - "@backstage/backend-common": "0.15.1", - "@backstage/backend-defaults": "0.1.1", - "example-backend-next": "0.0.3", - "@backstage/backend-plugin-api": "0.1.2", - "@backstage/backend-tasks": "0.3.5", - "@backstage/backend-test-utils": "0.1.28", - "@backstage/catalog-client": "1.1.0", - "@backstage/catalog-model": "1.1.1", - "@backstage/cli": "0.19.0", + "example-app": "0.2.76", + "@backstage/app-defaults": "1.0.7", + "example-backend": "0.2.76", + "@backstage/backend-app-api": "0.2.2", + "@backstage/backend-common": "0.15.2", + "@backstage/backend-defaults": "0.1.2", + "example-backend-next": "0.0.4", + "@backstage/backend-plugin-api": "0.1.3", + "@backstage/backend-tasks": "0.3.6", + "@backstage/backend-test-utils": "0.1.29", + "@backstage/catalog-client": "1.1.1", + "@backstage/catalog-model": "1.1.2", + "@backstage/cli": "0.20.0", "@backstage/cli-common": "0.1.10", - "@backstage/codemods": "0.1.39", - "@backstage/config": "1.0.2", - "@backstage/config-loader": "1.1.4", - "@backstage/core-app-api": "1.1.0", - "@backstage/core-components": "0.11.1", - "@backstage/core-plugin-api": "1.0.6", - "@backstage/create-app": "0.4.31", - "@backstage/dev-utils": "1.0.6", + "@backstage/codemods": "0.1.40", + "@backstage/config": "1.0.3", + "@backstage/config-loader": "1.1.5", + "@backstage/core-app-api": "1.1.1", + "@backstage/core-components": "0.11.2", + "@backstage/core-plugin-api": "1.0.7", + "@backstage/create-app": "0.4.32", + "@backstage/dev-utils": "1.0.7", "e2e-test": "0.2.0", - "@backstage/errors": "1.1.1", - "@backstage/integration": "1.3.1", - "@backstage/integration-react": "1.1.4", + "@backstage/errors": "1.1.2", + "@backstage/integration": "1.3.2", + "@backstage/integration-react": "1.1.5", "@backstage/release-manifests": "0.0.6", - "@techdocs/cli": "1.2.1", - "techdocs-cli-embedded-app": "0.2.74", - "@backstage/test-utils": "1.2.0", + "@techdocs/cli": "1.2.2", + "techdocs-cli-embedded-app": "0.2.75", + "@backstage/test-utils": "1.2.1", "@backstage/theme": "0.2.16", "@backstage/types": "1.0.0", "@backstage/version-bridge": "1.0.1", - "@backstage/plugin-adr": "0.2.1", - "@backstage/plugin-adr-backend": "0.2.1", - "@backstage/plugin-adr-common": "0.2.1", - "@backstage/plugin-airbrake": "0.3.9", - "@backstage/plugin-airbrake-backend": "0.2.9", - "@backstage/plugin-allure": "0.1.25", - "@backstage/plugin-analytics-module-ga": "0.1.20", - "@backstage/plugin-apache-airflow": "0.2.2", - "@backstage/plugin-api-docs": "0.8.9", + "@backstage/plugin-adr": "0.2.2", + "@backstage/plugin-adr-backend": "0.2.2", + "@backstage/plugin-adr-common": "0.2.2", + "@backstage/plugin-airbrake": "0.3.10", + "@backstage/plugin-airbrake-backend": "0.2.10", + "@backstage/plugin-allure": "0.1.26", + "@backstage/plugin-analytics-module-ga": "0.1.21", + "@backstage/plugin-apache-airflow": "0.2.3", + "@backstage/plugin-api-docs": "0.8.10", "@backstage/plugin-api-docs-module-protoc-gen-doc": "0.1.0", - "@backstage/plugin-apollo-explorer": "0.1.2", - "@backstage/plugin-app-backend": "0.3.36", - "@backstage/plugin-auth-backend": "0.16.0", - "@backstage/plugin-auth-node": "0.2.5", - "@backstage/plugin-azure-devops": "0.2.0", - "@backstage/plugin-azure-devops-backend": "0.3.15", + "@backstage/plugin-apollo-explorer": "0.1.3", + "@backstage/plugin-app-backend": "0.3.37", + "@backstage/plugin-auth-backend": "0.17.0", + "@backstage/plugin-auth-node": "0.2.6", + "@backstage/plugin-azure-devops": "0.2.1", + "@backstage/plugin-azure-devops-backend": "0.3.16", "@backstage/plugin-azure-devops-common": "0.3.0", - "@backstage/plugin-badges": "0.2.33", - "@backstage/plugin-badges-backend": "0.1.30", - "@backstage/plugin-bazaar": "0.1.24", - "@backstage/plugin-bazaar-backend": "0.1.20", - "@backstage/plugin-bitbucket-cloud-common": "0.1.3", - "@backstage/plugin-bitrise": "0.1.36", - "@backstage/plugin-catalog": "1.5.1", - "@backstage/plugin-catalog-backend": "1.4.0", - "@backstage/plugin-catalog-backend-module-aws": "0.1.9", - "@backstage/plugin-catalog-backend-module-azure": "0.1.7", - "@backstage/plugin-catalog-backend-module-bitbucket": "0.2.3", - "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.1.3", - "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.1.1", - "@backstage/plugin-catalog-backend-module-gerrit": "0.1.4", - "@backstage/plugin-catalog-backend-module-github": "0.1.7", - "@backstage/plugin-catalog-backend-module-gitlab": "0.1.7", - "@backstage/plugin-catalog-backend-module-ldap": "0.5.3", - "@backstage/plugin-catalog-backend-module-msgraph": "0.4.2", - "@backstage/plugin-catalog-backend-module-openapi": "0.1.2", - "@backstage/plugin-catalog-common": "1.0.6", - "@internal/plugin-catalog-customized": "0.0.2", - "@backstage/plugin-catalog-graph": "0.2.21", - "@backstage/plugin-catalog-graphql": "0.3.13", - "@backstage/plugin-catalog-import": "0.8.12", - "@backstage/plugin-catalog-node": "1.1.0", - "@backstage/plugin-catalog-react": "1.1.4", - "@backstage/plugin-cicd-statistics": "0.1.11", - "@backstage/plugin-cicd-statistics-module-gitlab": "0.1.5", - "@backstage/plugin-circleci": "0.3.9", - "@backstage/plugin-cloudbuild": "0.3.9", - "@backstage/plugin-code-climate": "0.1.9", - "@backstage/plugin-code-coverage": "0.2.2", - "@backstage/plugin-code-coverage-backend": "0.2.2", - "@backstage/plugin-codescene": "0.1.4", - "@backstage/plugin-config-schema": "0.1.32", - "@backstage/plugin-cost-insights": "0.11.31", + "@backstage/plugin-badges": "0.2.34", + "@backstage/plugin-badges-backend": "0.1.31", + "@backstage/plugin-bazaar": "0.1.25", + "@backstage/plugin-bazaar-backend": "0.2.0", + "@backstage/plugin-bitbucket-cloud-common": "0.2.0", + "@backstage/plugin-bitrise": "0.1.37", + "@backstage/plugin-catalog": "1.6.0", + "@backstage/plugin-catalog-backend": "1.5.0", + "@backstage/plugin-catalog-backend-module-aws": "0.1.10", + "@backstage/plugin-catalog-backend-module-azure": "0.1.8", + "@backstage/plugin-catalog-backend-module-bitbucket": "0.2.4", + "@backstage/plugin-catalog-backend-module-bitbucket-cloud": "0.1.4", + "@backstage/plugin-catalog-backend-module-bitbucket-server": "0.1.2", + "@backstage/plugin-catalog-backend-module-gerrit": "0.1.5", + "@backstage/plugin-catalog-backend-module-github": "0.1.8", + "@backstage/plugin-catalog-backend-module-gitlab": "0.1.8", + "@backstage/plugin-catalog-backend-module-ldap": "0.5.4", + "@backstage/plugin-catalog-backend-module-msgraph": "0.4.3", + "@backstage/plugin-catalog-backend-module-openapi": "0.1.3", + "@backstage/plugin-catalog-common": "1.0.7", + "@internal/plugin-catalog-customized": "0.0.3", + "@backstage/plugin-catalog-graph": "0.2.22", + "@backstage/plugin-catalog-graphql": "0.3.14", + "@backstage/plugin-catalog-import": "0.9.0", + "@backstage/plugin-catalog-node": "1.2.0", + "@backstage/plugin-catalog-react": "1.2.0", + "@backstage/plugin-cicd-statistics": "0.1.12", + "@backstage/plugin-cicd-statistics-module-gitlab": "0.1.6", + "@backstage/plugin-circleci": "0.3.10", + "@backstage/plugin-cloudbuild": "0.3.10", + "@backstage/plugin-code-climate": "0.1.10", + "@backstage/plugin-code-coverage": "0.2.3", + "@backstage/plugin-code-coverage-backend": "0.2.3", + "@backstage/plugin-codescene": "0.1.5", + "@backstage/plugin-config-schema": "0.1.33", + "@backstage/plugin-cost-insights": "0.11.32", "@backstage/plugin-cost-insights-common": "0.1.1", - "@backstage/plugin-dynatrace": "0.2.0", - "@internal/plugin-todo-list": "1.0.5", - "@internal/plugin-todo-list-backend": "1.0.5", - "@internal/plugin-todo-list-common": "1.0.4", - "@backstage/plugin-explore": "0.3.40", - "@backstage/plugin-explore-react": "0.0.21", - "@backstage/plugin-firehydrant": "0.1.26", - "@backstage/plugin-fossa": "0.2.41", - "@backstage/plugin-gcalendar": "0.3.5", - "@backstage/plugin-gcp-projects": "0.3.28", - "@backstage/plugin-git-release-manager": "0.3.22", - "@backstage/plugin-github-actions": "0.5.9", - "@backstage/plugin-github-deployments": "0.1.40", - "@backstage/plugin-github-issues": "0.1.1", - "@backstage/plugin-github-pull-requests-board": "0.1.3", - "@backstage/plugin-gitops-profiles": "0.3.27", - "@backstage/plugin-gocd": "0.1.15", - "@backstage/plugin-graphiql": "0.2.41", - "@backstage/plugin-graphql-backend": "0.1.26", - "@backstage/plugin-home": "0.4.25", - "@backstage/plugin-ilert": "0.1.35", - "@backstage/plugin-jenkins": "0.7.8", - "@backstage/plugin-jenkins-backend": "0.1.26", - "@backstage/plugin-jenkins-common": "0.1.8", - "@backstage/plugin-kafka": "0.3.9", - "@backstage/plugin-kafka-backend": "0.2.29", - "@backstage/plugin-kubernetes": "0.7.2", - "@backstage/plugin-kubernetes-backend": "0.7.2", - "@backstage/plugin-kubernetes-common": "0.4.2", - "@backstage/plugin-lighthouse": "0.3.9", - "@backstage/plugin-newrelic": "0.3.27", - "@backstage/plugin-newrelic-dashboard": "0.2.2", - "@backstage/plugin-org": "0.5.9", - "@backstage/plugin-pagerduty": "0.5.2", - "@backstage/plugin-periskop": "0.1.7", - "@backstage/plugin-periskop-backend": "0.1.7", - "@backstage/plugin-permission-backend": "0.5.11", - "@backstage/plugin-permission-common": "0.6.4", - "@backstage/plugin-permission-node": "0.6.5", - "@backstage/plugin-permission-react": "0.4.5", - "@backstage/plugin-playlist": "0.1.0", - "@backstage/plugin-playlist-backend": "0.1.0", - "@backstage/plugin-playlist-common": "0.1.0", - "@backstage/plugin-proxy-backend": "0.2.30", - "@backstage/plugin-rollbar": "0.4.9", - "@backstage/plugin-rollbar-backend": "0.1.33", - "@backstage/plugin-scaffolder": "1.6.0", - "@backstage/plugin-scaffolder-backend": "1.6.0", - "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.11", - "@backstage/plugin-scaffolder-backend-module-rails": "0.4.4", - "@backstage/plugin-scaffolder-backend-module-yeoman": "0.2.9", - "@backstage/plugin-scaffolder-common": "1.2.0", - "@backstage/plugin-search": "1.0.2", - "@backstage/plugin-search-backend": "1.0.2", - "@backstage/plugin-search-backend-module-elasticsearch": "1.0.2", - "@backstage/plugin-search-backend-module-pg": "0.4.0", - "@backstage/plugin-search-backend-node": "1.0.2", - "@backstage/plugin-search-common": "1.0.1", - "@backstage/plugin-search-react": "1.1.0", - "@backstage/plugin-sentry": "0.4.2", - "@backstage/plugin-shortcuts": "0.3.1", - "@backstage/plugin-sonarqube": "0.4.1", - "@backstage/plugin-sonarqube-backend": "0.1.1", - "@backstage/plugin-splunk-on-call": "0.3.33", - "@backstage/plugin-stack-overflow": "0.1.5", - "@backstage/plugin-stack-overflow-backend": "0.1.5", - "@backstage/plugin-tech-insights": "0.3.0", - "@backstage/plugin-tech-insights-backend": "0.5.2", - "@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.20", - "@backstage/plugin-tech-insights-common": "0.2.6", - "@backstage/plugin-tech-insights-node": "0.3.4", - "@backstage/plugin-tech-radar": "0.5.16", - "@backstage/plugin-techdocs": "1.3.2", - "@backstage/plugin-techdocs-addons-test-utils": "1.0.4", - "@backstage/plugin-techdocs-backend": "1.3.0", - "@backstage/plugin-techdocs-module-addons-contrib": "1.0.4", - "@backstage/plugin-techdocs-node": "1.4.0", - "@backstage/plugin-techdocs-react": "1.0.4", - "@backstage/plugin-todo": "0.2.11", - "@backstage/plugin-todo-backend": "0.1.33", - "@backstage/plugin-user-settings": "0.4.8", - "@backstage/plugin-user-settings-backend": "0.1.0", - "@backstage/plugin-vault": "0.1.3", - "@backstage/plugin-vault-backend": "0.2.2", - "@backstage/plugin-xcmetrics": "0.2.29" + "@backstage/plugin-dynatrace": "1.0.0", + "@internal/plugin-todo-list": "1.0.6", + "@internal/plugin-todo-list-backend": "1.0.6", + "@internal/plugin-todo-list-common": "1.0.5", + "@backstage/plugin-explore": "0.3.41", + "@backstage/plugin-explore-react": "0.0.22", + "@backstage/plugin-firehydrant": "0.1.27", + "@backstage/plugin-fossa": "0.2.42", + "@backstage/plugin-gcalendar": "0.3.6", + "@backstage/plugin-gcp-projects": "0.3.29", + "@backstage/plugin-git-release-manager": "0.3.23", + "@backstage/plugin-github-actions": "0.5.10", + "@backstage/plugin-github-deployments": "0.1.41", + "@backstage/plugin-github-issues": "0.1.2", + "@backstage/plugin-github-pull-requests-board": "0.1.4", + "@backstage/plugin-gitops-profiles": "0.3.28", + "@backstage/plugin-gocd": "0.1.16", + "@backstage/plugin-graphiql": "0.2.42", + "@backstage/plugin-graphql-backend": "0.1.27", + "@backstage/plugin-home": "0.4.26", + "@backstage/plugin-ilert": "0.1.36", + "@backstage/plugin-jenkins": "0.7.9", + "@backstage/plugin-jenkins-backend": "0.1.27", + "@backstage/plugin-jenkins-common": "0.1.9", + "@backstage/plugin-kafka": "0.3.10", + "@backstage/plugin-kafka-backend": "0.2.30", + "@backstage/plugin-kubernetes": "0.7.3", + "@backstage/plugin-kubernetes-backend": "0.7.3", + "@backstage/plugin-kubernetes-common": "0.4.3", + "@backstage/plugin-lighthouse": "0.3.10", + "@backstage/plugin-newrelic": "0.3.28", + "@backstage/plugin-newrelic-dashboard": "0.2.3", + "@backstage/plugin-org": "0.5.10", + "@backstage/plugin-pagerduty": "0.5.3", + "@backstage/plugin-periskop": "0.1.8", + "@backstage/plugin-periskop-backend": "0.1.8", + "@backstage/plugin-permission-backend": "0.5.12", + "@backstage/plugin-permission-common": "0.7.0", + "@backstage/plugin-permission-node": "0.7.0", + "@backstage/plugin-permission-react": "0.4.6", + "@backstage/plugin-playlist": "0.1.1", + "@backstage/plugin-playlist-backend": "0.2.0", + "@backstage/plugin-playlist-common": "0.1.1", + "@backstage/plugin-proxy-backend": "0.2.31", + "@backstage/plugin-rollbar": "0.4.10", + "@backstage/plugin-rollbar-backend": "0.1.34", + "@backstage/plugin-scaffolder": "1.7.0", + "@backstage/plugin-scaffolder-backend": "1.7.0", + "@backstage/plugin-scaffolder-backend-module-cookiecutter": "0.2.12", + "@backstage/plugin-scaffolder-backend-module-rails": "0.4.5", + "@backstage/plugin-scaffolder-backend-module-yeoman": "0.2.10", + "@backstage/plugin-scaffolder-common": "1.2.1", + "@backstage/plugin-search": "1.0.3", + "@backstage/plugin-search-backend": "1.1.0", + "@backstage/plugin-search-backend-module-elasticsearch": "1.0.3", + "@backstage/plugin-search-backend-module-pg": "0.4.1", + "@backstage/plugin-search-backend-node": "1.0.3", + "@backstage/plugin-search-common": "1.1.0", + "@backstage/plugin-search-react": "1.2.0", + "@backstage/plugin-sentry": "0.4.3", + "@backstage/plugin-shortcuts": "0.3.2", + "@backstage/plugin-sonarqube": "0.4.2", + "@backstage/plugin-sonarqube-backend": "0.1.2", + "@backstage/plugin-splunk-on-call": "0.3.34", + "@backstage/plugin-stack-overflow": "0.1.6", + "@backstage/plugin-stack-overflow-backend": "0.1.6", + "@backstage/plugin-tech-insights": "0.3.1", + "@backstage/plugin-tech-insights-backend": "0.5.3", + "@backstage/plugin-tech-insights-backend-module-jsonfc": "0.1.21", + "@backstage/plugin-tech-insights-common": "0.2.7", + "@backstage/plugin-tech-insights-node": "0.3.5", + "@backstage/plugin-tech-radar": "0.5.17", + "@backstage/plugin-techdocs": "1.3.3", + "@backstage/plugin-techdocs-addons-test-utils": "1.0.5", + "@backstage/plugin-techdocs-backend": "1.4.0", + "@backstage/plugin-techdocs-module-addons-contrib": "1.0.5", + "@backstage/plugin-techdocs-node": "1.4.1", + "@backstage/plugin-techdocs-react": "1.0.5", + "@backstage/plugin-todo": "0.2.12", + "@backstage/plugin-todo-backend": "0.1.34", + "@backstage/plugin-user-settings": "0.5.0", + "@backstage/plugin-user-settings-backend": "0.1.1", + "@backstage/plugin-vault": "0.1.4", + "@backstage/plugin-vault-backend": "0.2.3", + "@backstage/plugin-xcmetrics": "0.2.30" }, "changesets": [] } diff --git a/.changeset/renovate-a02d90b.md b/.changeset/renovate-a02d90b.md deleted file mode 100644 index 7bb69d9988..0000000000 --- a/.changeset/renovate-a02d90b.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Updated dependency `@asyncapi/react-component` to `1.0.0-next.42`. diff --git a/.changeset/rich-carrots-reflect.md b/.changeset/rich-carrots-reflect.md deleted file mode 100644 index 13a67bcd56..0000000000 --- a/.changeset/rich-carrots-reflect.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Switched the Jest transform for YAML files to use a custom one available at `@backstage/cli/config/jestYamlTransform.js`. diff --git a/.changeset/rude-bulldogs-sleep.md b/.changeset/rude-bulldogs-sleep.md deleted file mode 100644 index 8a2a0590b3..0000000000 --- a/.changeset/rude-bulldogs-sleep.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch ---- - -Allow Placeholder value to be any value, not only string. diff --git a/.changeset/selfish-turkeys-exist.md b/.changeset/selfish-turkeys-exist.md deleted file mode 100644 index 3530b3a864..0000000000 --- a/.changeset/selfish-turkeys-exist.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-scaffolder': minor ---- - -EntityPickers now support flags to control when to include default namespace -in result diff --git a/.changeset/shaggy-books-smell.md b/.changeset/shaggy-books-smell.md deleted file mode 100644 index 3f8aaa389c..0000000000 --- a/.changeset/shaggy-books-smell.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-tech-insights-node': patch ---- - -Updated package role to be `node-library`. diff --git a/.changeset/soft-falcons-love.md b/.changeset/soft-falcons-love.md deleted file mode 100644 index 56ed193501..0000000000 --- a/.changeset/soft-falcons-love.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/plugin-user-settings': minor ---- - -**BREAKING**: The `apiRef` passed to `ProviderSettingsItem` now needs to -implement `ProfileInfoApi & SessionApi`, rather than just the latter. This is -unlikely to have an effect on most users though, since the builtin auth -providers generally implement both. - -Fixed settings page showing providers as logged out when the user is using more -than one provider, and displayed some additional login information. diff --git a/.changeset/spicy-parents-lick.md b/.changeset/spicy-parents-lick.md new file mode 100644 index 0000000000..692745ed12 --- /dev/null +++ b/.changeset/spicy-parents-lick.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Include query parameters when navigating to relative links in documents diff --git a/.changeset/stupid-dragons-complain.md b/.changeset/stupid-dragons-complain.md deleted file mode 100644 index 44be8342bf..0000000000 --- a/.changeset/stupid-dragons-complain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/backend-test-utils': patch ---- - -Increased test database max connection pool size to reduce the risk of resource exhaustion. diff --git a/.changeset/stupid-pens-occur.md b/.changeset/stupid-pens-occur.md new file mode 100644 index 0000000000..d8af9a447a --- /dev/null +++ b/.changeset/stupid-pens-occur.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': minor +--- + +**BREAKING**: Removed the `HomepageTimer` as it has been replaced by the `HeaderWorldClock` in the Home plugin and was deprecated over a year ago. diff --git a/.changeset/sweet-insects-camp.md b/.changeset/sweet-insects-camp.md deleted file mode 100644 index 4f91a196e8..0000000000 --- a/.changeset/sweet-insects-camp.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@backstage/cli': minor ---- - -**BREAKING**: Bumped `jest`, `jest-runtime`, and `jest-environment-jsdom` to v29. This is up from v27, so check out both the [v28](https://jestjs.io/docs/28.x/upgrading-to-jest28) and [v29](https://jestjs.io/docs/upgrading-to-jest29) (later [here](https://jestjs.io/docs/29.x/upgrading-to-jest29)) migration guides. - -Particular changes that where encountered in the main Backstage repo are: - -- The updated snapshot format. -- `jest.useFakeTimers('legacy')` is now `jest.useFakeTimers({ legacyFakeTimers: true })`. -- Error objects collected by `withLogCollector` from `@backstage/test-utils` are now objects with a `detail` property rather than a string. diff --git a/.changeset/tasty-colts-hug.md b/.changeset/tasty-colts-hug.md new file mode 100644 index 0000000000..e56d48a441 --- /dev/null +++ b/.changeset/tasty-colts-hug.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-azure-sites': minor +'@backstage/plugin-azure-sites-backend': minor +'@backstage/plugin-azure-sites-common': minor +--- + +Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. diff --git a/.changeset/tasty-scissors-tickle.md b/.changeset/tasty-scissors-tickle.md new file mode 100644 index 0000000000..745172df36 --- /dev/null +++ b/.changeset/tasty-scissors-tickle.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket-server': patch +--- + +`BitbucketServerEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + +Please find how to configure the schedule at the config at +https://backstage.io/docs/integrations/bitbucketServer/discovery diff --git a/.changeset/ten-pens-draw.md b/.changeset/ten-pens-draw.md new file mode 100644 index 0000000000..be35a6f3b0 --- /dev/null +++ b/.changeset/ten-pens-draw.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog-backend-module-gerrit': patch +--- + +`GerritEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + +Please find how to configure the schedule at the config at +https://backstage.io/docs/integrations/gerrit/discovery diff --git a/.changeset/tender-drinks-drive.md b/.changeset/tender-drinks-drive.md deleted file mode 100644 index 0ea69832af..0000000000 --- a/.changeset/tender-drinks-drive.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': minor ---- - -Add functionality to shutdown scaffolder tasks if they are stale diff --git a/.changeset/tiny-mails-bathe.md b/.changeset/tiny-mails-bathe.md deleted file mode 100644 index 868c2a8d36..0000000000 --- a/.changeset/tiny-mails-bathe.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -'@backstage/create-app': patch ---- - -Updated the root `test` scripts to use `backstage-cli repo test`. - -To apply this change to an existing app, make the following change to the root `package.json`: - -```diff -- "test": "backstage-cli test", -- "test:all": "lerna run test -- --coverage", -+ "test": "backstage-cli repo test", -+ "test:all": "backstage-cli repo test --coverage", -``` diff --git a/.changeset/two-oranges-joke.md b/.changeset/two-oranges-joke.md new file mode 100644 index 0000000000..7f116e8ab0 --- /dev/null +++ b/.changeset/two-oranges-joke.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket-server': patch +--- + +Add `bitbucketServerEntityProviderCatalogModule` (new backend-plugin-api, alpha). diff --git a/.changeset/wet-cameras-call.md b/.changeset/wet-cameras-call.md new file mode 100644 index 0000000000..3d58719269 --- /dev/null +++ b/.changeset/wet-cameras-call.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-kubernetes-backend': minor +'@backstage/plugin-kubernetes-common': patch +--- + +The Kubernetes errors when fetching pod metrics are now captured and returned to the frontend. + +- **BREAKING** The method `fetchPodMetricsByNamespace` in the interface `KubernetesFetcher` is changed to `fetchPodMetricsByNamespaces`. It now accepts a set of namespace strings and returns `Promise`. +- Add the `PodStatusFetchResponse` to the `FetchResponse` union type. +- Add `NOT_FOUND` to the `KubernetesErrorTypes` union type, the HTTP error with status code 404 will be mapped to this error. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index cb1dd5d322..2dd8f4b55b 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -35,6 +35,7 @@ yarn.lock @backstage/reviewers @backst /plugins/code-coverage @backstage/reviewers @alde @nissayeva /plugins/code-coverage-backend @backstage/reviewers @alde @nissayeva /plugins/cost-insights @backstage/reviewers @backstage/silver-lining +/plugins/cost-insights-* @backstage/reviewers @backstage/silver-lining /plugins/explore @backstage/reviewers @backstage/sda-se-reviewers /plugins/explore-react @backstage/reviewers @backstage/sda-se-reviewers /plugins/fossa @backstage/reviewers @backstage/sda-se-reviewers @@ -44,13 +45,15 @@ yarn.lock @backstage/reviewers @backst /plugins/ilert @backstage/reviewers @yacut /plugins/jenkins @backstage/reviewers @timja /plugins/jenkins-backend @backstage/reviewers @timja -/plugins/kafka @backstage/reviewers @nirga -/plugins/kafka-backend @backstage/reviewers @nirga +/plugins/kafka @backstage/reviewers @nirga @andrewthauer +/plugins/kafka-backend @backstage/reviewers @nirga @andrewthauer /plugins/kubernetes @backstage/reviewers @backstage/warpspeed /plugins/kubernetes-* @backstage/reviewers @backstage/warpspeed /plugins/newrelic-dashboard @backstage/reviewers @mufaddal7 /plugins/playlist @backstage/reviewers @kuangp /plugins/playlist-* @backstage/reviewers @kuangp +/plugins/rollbar @backstage/reviewers @andrewthauer +/plugins/rollbar-backend @backstage/reviewers @andrewthauer /plugins/scaffolder-backend-module-rails @backstage/reviewers @angeliski /plugins/scaffolder-backend-module-yeoman @backstage/reviewers @pawelmitka /plugins/search @backstage/reviewers @backstage/techdocs-core diff --git a/.github/labeler.yml b/.github/labeler.yml index bfba5eb321..587f29efa3 100644 --- a/.github/labeler.yml +++ b/.github/labeler.yml @@ -9,7 +9,7 @@ search: - plugins/search/**/* - plugins/search-*/**/* - packages/search-*/**/* -docs-like-code: +techdocs: - plugins/techdocs/**/* - plugins/techdocs-*/**/* - packages/techdocs-*/**/* diff --git a/.github/renovate.json5 b/.github/renovate.json5 index a8bc3448a5..5c4745c70c 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -15,5 +15,10 @@ groupName: 'API Extractor / Rush Stack monorepo packages', rangeStrategy: 'replace', }, + { + matchSourceUrlPrefixes: ['https://github.com/gregberge/svgr'], + groupName: 'SVGR monorepo packages', + rangeStrategy: 'replace', + }, ], } diff --git a/.github/vale/Vocab/Backstage/accept.txt b/.github/vale/Vocab/Backstage/accept.txt index ae94912f38..bbfa748bb2 100644 --- a/.github/vale/Vocab/Backstage/accept.txt +++ b/.github/vale/Vocab/Backstage/accept.txt @@ -2,6 +2,7 @@ abc accessors addon addons +ADRs Airbrake Airbrakes airbrake @@ -28,6 +29,7 @@ bool boolean builtins callout +CDNs Chai changeset changesets @@ -68,6 +70,7 @@ dataflow dayjs debounce Debounce +debuggability declaratively deduplicated deps @@ -129,6 +132,7 @@ hotspots http https Iain +iLert img incentivised Indal @@ -139,6 +143,7 @@ JaCoCo JavaScript jenkins Jira +JWTs jq js json @@ -147,6 +152,7 @@ jsx Kaewkasi Keyv Knex +KPIs kubectl kubernetes kubernetes @@ -308,6 +314,7 @@ Superfences superset supertype storable +SVGs talkdesk Talkdesk tasklist @@ -352,17 +359,21 @@ utils validator validators varchar +VPCs +VSCode Wayfair Weaveworks Webpack winston www WWW +XCMetrics XML xyz yaml Zalando Zhou +zod zoomable zsh Alef diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c6ac696e9..64067188bb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.5 + uses: backstage/actions/yarn-install@v0.5.6 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -63,7 +63,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.5 + uses: backstage/actions/yarn-install@v0.5.6 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -178,7 +178,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.5 + uses: backstage/actions/yarn-install@v0.5.6 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/cron.yml b/.github/workflows/cron.yml index 03105b753a..467c817baf 100644 --- a/.github/workflows/cron.yml +++ b/.github/workflows/cron.yml @@ -8,7 +8,7 @@ jobs: cron: runs-on: ubuntu-latest steps: - - uses: backstage/actions/cron@v0.5.5 + - uses: backstage/actions/cron@v0.5.6 with: app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} diff --git a/.github/workflows/deploy_microsite.yml b/.github/workflows/deploy_microsite.yml index fb99f41c55..af6f222377 100644 --- a/.github/workflows/deploy_microsite.yml +++ b/.github/workflows/deploy_microsite.yml @@ -55,7 +55,7 @@ jobs: run: ls microsite/build/backstage && ls microsite/build/backstage/storybook - name: Deploy both microsite and storybook to gh-pages - uses: JamesIves/github-pages-deploy-action@v4.4.0 + uses: JamesIves/github-pages-deploy-action@v4.4.1 with: branch: gh-pages folder: microsite/build/backstage diff --git a/.github/workflows/deploy_nightly.yml b/.github/workflows/deploy_nightly.yml index b82e1a30d4..476e5dd10b 100644 --- a/.github/workflows/deploy_nightly.yml +++ b/.github/workflows/deploy_nightly.yml @@ -26,7 +26,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.5 + uses: backstage/actions/yarn-install@v0.5.6 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -57,7 +57,7 @@ jobs: - name: publish nightly release run: | yarn config set -H 'npmAuthToken' "${{secrets.NPM_TOKEN}}" - yarn workspaces foreach -v --no-private npm publish --access public --tag nightly + yarn workspaces foreach -v --no-private npm publish --access public --tolerate-republish --tag nightly env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/deploy_packages.yml b/.github/workflows/deploy_packages.yml index 06ed3bc45e..f3736d93a2 100644 --- a/.github/workflows/deploy_packages.yml +++ b/.github/workflows/deploy_packages.yml @@ -67,7 +67,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.5 + uses: backstage/actions/yarn-install@v0.5.6 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -145,7 +145,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.5 + uses: backstage/actions/yarn-install@v0.5.6 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} @@ -164,9 +164,9 @@ jobs: run: | yarn config set -H 'npmAuthToken' "${{secrets.NPM_TOKEN}}" if [ -f ".changeset/pre.json" ]; then - yarn workspaces foreach -v --no-private npm publish --access public --tag next + yarn workspaces foreach -v --no-private npm publish --access public --tolerate-republish --tag next else - yarn workspaces foreach -v --no-private npm publish --access public + yarn workspaces foreach -v --no-private npm publish --access public --tolerate-republish fi env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/issue.yaml b/.github/workflows/issue.yaml index 0a8b243255..95e91dda30 100644 --- a/.github/workflows/issue.yaml +++ b/.github/workflows/issue.yaml @@ -10,4 +10,4 @@ jobs: if: github.repository == 'backstage/backstage' steps: - name: Issue sync - uses: backstage/actions/issue-sync@v0.5.5 + uses: backstage/actions/issue-sync@v0.5.6 diff --git a/.github/workflows/pr-review-comment.yaml b/.github/workflows/pr-review-comment.yaml index 4b1048da9b..e822209e03 100644 --- a/.github/workflows/pr-review-comment.yaml +++ b/.github/workflows/pr-review-comment.yaml @@ -35,7 +35,7 @@ jobs: const prNumber = artifact.name.slice('pr_number-'.length) console.log(`::set-output name=pr-number::${prNumber}`); - - uses: backstage/actions/re-review@v0.5.5 + - uses: backstage/actions/re-review@v0.5.6 with: app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} private-key: ${{ secrets.BACKSTAGE_GOALIE_PRIVATE_KEY }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 3fbac53400..e13928ccee 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -18,7 +18,7 @@ jobs: if: github.repository == 'backstage/backstage' && ( github.event.pull_request || github.event.issue.pull_request ) steps: - name: PR sync - uses: backstage/actions/pr-sync@v0.5.5 + uses: backstage/actions/pr-sync@v0.5.6 with: github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} app-id: ${{ secrets.BACKSTAGE_GOALIE_APPLICATION_ID }} diff --git a/.github/workflows/sync_code-formatting.yml b/.github/workflows/sync_code-formatting.yml index c37f8c578e..779284818f 100644 --- a/.github/workflows/sync_code-formatting.yml +++ b/.github/workflows/sync_code-formatting.yml @@ -20,7 +20,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.5 + uses: backstage/actions/yarn-install@v0.5.6 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/sync_release-manifest.yml b/.github/workflows/sync_release-manifest.yml index 8cf6991aaf..c6d00ecd28 100644 --- a/.github/workflows/sync_release-manifest.yml +++ b/.github/workflows/sync_release-manifest.yml @@ -53,6 +53,10 @@ jobs: github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} # TODO(Rugvip): Remove the create-app dispatch once we've been on the release version for a while script: | + const releaseVersion = require('./backstage/package.json').version; + if(releaseVersion.includes('next')) { + return; + } console.log('Dispatching upgrade helper sync'); await github.rest.actions.createWorkflowDispatch({ owner: 'backstage', @@ -61,6 +65,6 @@ jobs: ref: 'master', inputs: { version: require('./backstage/packages/create-app/package.json').version, - releaseVersion: require('./backstage/package.json').version + releaseVersion }, }); diff --git a/.github/workflows/sync_snyk-github-issues.yml b/.github/workflows/sync_snyk-github-issues.yml index ae65e90ac0..089474e5b8 100644 --- a/.github/workflows/sync_snyk-github-issues.yml +++ b/.github/workflows/sync_snyk-github-issues.yml @@ -13,6 +13,9 @@ jobs: matrix: node-version: [14.x] + env: + NODE_OPTIONS: --max-old-space-size=4096 + steps: - uses: actions/checkout@v3 @@ -22,7 +25,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.5 + uses: backstage/actions/yarn-install@v0.5.6 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/sync_snyk-monitor.yml b/.github/workflows/sync_snyk-monitor.yml index d6adca927e..87dec287d7 100644 --- a/.github/workflows/sync_snyk-monitor.yml +++ b/.github/workflows/sync_snyk-monitor.yml @@ -30,6 +30,7 @@ jobs: --remote-repo-url=https://github.com/backstage/backstage env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + NODE_OPTIONS: --max-old-space-size=4096 # Above we run the `monitor` command, this runs the `test` command which is # the one that generates the SARIF report that we can upload to GitHub. @@ -44,6 +45,7 @@ jobs: --sarif-file-output=snyk.sarif env: SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + NODE_OPTIONS: --max-old-space-size=4096 - name: Upload Snyk report uses: github/codeql-action/upload-sarif@v2 with: diff --git a/.github/workflows/sync_version-packages.yml b/.github/workflows/sync_version-packages.yml index 5aa12fe6b5..22421925c7 100644 --- a/.github/workflows/sync_version-packages.yml +++ b/.github/workflows/sync_version-packages.yml @@ -4,6 +4,10 @@ on: branches: - master +concurrency: + group: sync-version-packages + cancel-in-progress: true + jobs: create-release-pr: name: Create Changeset PR diff --git a/.github/workflows/verify_e2e-linux.yml b/.github/workflows/verify_e2e-linux.yml index 0eedfd9d6b..3353a84f3b 100644 --- a/.github/workflows/verify_e2e-linux.yml +++ b/.github/workflows/verify_e2e-linux.yml @@ -41,13 +41,18 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Configure Git + run: | + git config --global user.email noreply@backstage.io + git config --global user.name 'GitHub e2e user' + - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.5 + uses: backstage/actions/yarn-install@v0.5.6 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/verify_e2e-techdocs.yml b/.github/workflows/verify_e2e-techdocs.yml index e62a9040a6..e1bb2246dc 100644 --- a/.github/workflows/verify_e2e-techdocs.yml +++ b/.github/workflows/verify_e2e-techdocs.yml @@ -39,7 +39,7 @@ jobs: run: yarn build - name: Install mkdocs & techdocs-core - run: python -m pip install mkdocs-techdocs-core + run: python -m pip install mkdocs-techdocs-core==1.1.7 mkdocs==1.4.0 - name: techdocs-cli e2e test working-directory: packages/techdocs-cli diff --git a/.github/workflows/verify_e2e-windows.yml b/.github/workflows/verify_e2e-windows.yml index 3bc41a9eeb..4e8025f7d5 100644 --- a/.github/workflows/verify_e2e-windows.yml +++ b/.github/workflows/verify_e2e-windows.yml @@ -39,6 +39,11 @@ jobs: - uses: actions/checkout@v3 + - name: Configure Git + run: | + git config --global user.email noreply@backstage.io + git config --global user.name 'GitHub e2e user' + - name: use node.js ${{ matrix.node-version }} uses: actions/setup-node@v3 with: diff --git a/.github/workflows/verify_kubernetes.yml b/.github/workflows/verify_kubernetes.yml index 6b8d527609..e64ddec586 100644 --- a/.github/workflows/verify_kubernetes.yml +++ b/.github/workflows/verify_kubernetes.yml @@ -28,7 +28,7 @@ jobs: registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.5 + uses: backstage/actions/yarn-install@v0.5.6 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} diff --git a/.github/workflows/verify_storybook.yml b/.github/workflows/verify_storybook.yml index 63e857f19a..cac94ff682 100644 --- a/.github/workflows/verify_storybook.yml +++ b/.github/workflows/verify_storybook.yml @@ -35,7 +35,7 @@ jobs: node-version: ${{ matrix.node-version }} registry-url: https://registry.npmjs.org/ # Needed for auth - name: yarn install - uses: backstage/actions/yarn-install@v0.5.5 + uses: backstage/actions/yarn-install@v0.5.6 with: cache-prefix: ${{ runner.os }}-v${{ matrix.node-version }} - name: storybook yarn install diff --git a/.gitignore b/.gitignore index 256cef00c2..b7bfe7a84d 100644 --- a/.gitignore +++ b/.gitignore @@ -149,3 +149,6 @@ cypress/cypress/* # Possible leftover from build:api-reports tsconfig.tmp.json + +# vscode database functionality support files +*.session.sql diff --git a/ADOPTERS.md b/ADOPTERS.md index 4378eb14f9..bb17754c95 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -1,7 +1,7 @@ # Adopters _If you're using Backstage in your organization, please try to add your company name to this list. It really helps the project to gain momentum and credibility. It's a small contribution back to the project with a big impact._ -_You can do this by using the [Adopter form](https://form.typeform.com/to/zcOaKikB) or by editing this file after following the [CONTRIBUTING.md](./CONTRIBUTING.md)._ +_You can do this by using the [Adopter form](https://info.backstage.spotify.com/public-adopter-hand-raiser-form) or by editing this file after following the [CONTRIBUTING.md](./CONTRIBUTING.md)._ | Organization | Contact | Description of Use | | --------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | @@ -22,7 +22,7 @@ _You can do this by using the [Adopter form](https://form.typeform.com/to/zcOaKi | [Fiverr](https://www.fiverr.com) | [@nirga](https://github.com/nirga) | Unifying separate tools that developers are using today (i.e. monitoring, dead letter queues management, etc.) into a single platform. | | [Zalando SE](https://www.zalando.de) | [@leviferreira](https://github.com/leviferreira) | Building V2 of the Internal Development Portal. | | [LegalZoom](https://legalzoom.com) | [@backjo](https://github.com/backjo) | Developer portal - hub for all engineering projects and metadata. | -| [Expedia Group](https://www.expediagroup.com) | [@guillermomanzo](https://github.com/guillermomanzo), [Sheena Sharma](mailto:shesharma@expediagroup.com), [@ajbw](https://github.com/ajbw) | EG Common Developer Toolkit | +| [Expedia Group](https://www.expediagroup.com) | [@gman0922](https://github.com/gman0922), [Sheena Sharma](mailto:shesharma@expediagroup.com), [Alekhya Karuturi](mailto:akaruturi@expediagroup.com) | EG Developer Front Door | | [Paddle.com](https://paddle.com) | [Ioannis Georgoulas](https://github.com/geototti21) | Developer portal (Tech Docs, Service Catalog, Internal Tooling), we use vanilla Backstage FE and custom BE implementation in Go | | [Acast.com](https://acast.com) | [Olle Lundberg](https://github.com/lndbrg) | Developer portal with tech docs, service catalog and a bunch of other internal tooling | | [Lunar](https://lunar.app) | [Jacob Valdemar](https://github.com/JacobValdemar) | Internal developer portal for service overview and insights, API documentation, technical guides, onboarding guides and RFC's. | @@ -71,7 +71,7 @@ _You can do this by using the [Adopter form](https://form.typeform.com/to/zcOaKi | [Signal Iduna Group](https://www.signal-iduna.de/) | [Jonas Thomsen](https://github.com/JoThomsen) | Developer Portal, documentation, monitoring, service catalog for our insurance ecosystem | | [Tradeshift](https://www.tradeshift.com/) | [Soren Mathiasen](https://github.com/sorenmat) | Developer Portal: documentation, monitoring, service templates, service catalog for our micro services | | [Unity](https://unity.com) | [Ted Cordery](https://github.com/TeddyBallGame) | A centralized service catalog with documentation for our service engineers. | -| [PicPay](https://www.picpay.com) | [Luis Baroni](https://github.com/lcsbaroni), [Renata Poluceno](https://github.com/renatapoluceno), [PicPay](https://github.com/picpay) | Developer portal for building services through templates, service catalog with ownership of services, documentation and metrics providing autonomy and visibility for all. | +| [PicPay](https://www.picpay.com) | [Elton Welsch](https://github.com/eltonwelsch), [Emanuella Okada](https://github.com/ManuOkadaPicPay), [PicPay](https://github.com/picpay) | Developer portal for building services through templates, service catalog with ownership of services, documentation and metrics providing autonomy and visibility for all. | | [Epic Games](https://www.epicgames.com) | [Brian Jung](https://github.com/brian-at-epic), [Jeff Goldian](https://github.com/jeffgoldian-Epic) | Developer Portal: Service Catalog, Documentation, Software Templates and more making our internal teams' lives easier! | | [Globo](https://globo.com) | [Carlos Gusmão](https://github.com/caeugusmao), [Guilherme Vierno](https://github.com/vierno), [Denis Aoki](https://github.com/dnsaoki2), [Maycon Dionisio](https://github.com/MayconDionisio), | Reduce the friction of accessing the information engineers need about Globo's digital services through a coherent and centralized experience. | | [QBE](https://www.qbe.com/) | [Daniel Steel](https://github.com/danielsteelqbe), [Pete Jespers](https://github.com/petejespersqbe) | Developer portal allowing our global teams to explore and create applications, documentation and cloud infrastructure easily and quickly 🚀 | @@ -91,7 +91,7 @@ _You can do this by using the [Adopter form](https://form.typeform.com/to/zcOaKi | [HBO Max](https://hbomax.com) | [@mdb](https://github.com/mdb), [@nesta219](https://github.com/nesta219), [@nmische](https://github.com/nmische), [@hbomark](https://github.com/hbomark) | Developer portal hosting service catalog and API documentation, as well as cloud infrastructure details, operational visibility tools, and a custom plugin for browsing notable platform change events, such as deployments and configuration updates. | | [RCHLO](https://www.riachuelo.com.br) & [MIDWAY](https://www.midway.com.br) | [@marcosborges](https://github.com/marcosborges), [@defaultbr](https://github.com/defaultbr) | Self-Service Platform | | [HP Inc](https://www.hp.com) | [Damon Kaswell](https://github.com/dekoding) | DevEx engagement hub (dev portal: docs, standards, Q&A) and extensive assets catalog (APIs, services, code, data, etc.) for the pan-HP internal developer community. | -| [VMware](https://www.vmware.com) | [@mpriamo](https://github.com/mpriamo), [@krisapplegate](https://github.com/krisapplegate) | Part of [Tanzu Application Platform](https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/index.html) offering; internal developer portal | +| [VMware](https://www.vmware.com) | [Waldir Montoya](https://github.com/waldirmontoya25), [Kris Applegate](https://github.com/krisapplegate), [Jamie Klassen](https://github.com/jamieklassen) | Part of [Tanzu Application Platform](https://docs.vmware.com/en/VMware-Tanzu-Application-Platform/index.html) offering; internal developer portal | | [Ualá](https://www.uala.com.ar/) | [Santiago Bernal](https://github.com/sabernal) | Initial work being done to centralize documentation for all our microservices and APIs, as well as scaffolding new services and tracking code quality | | [IKEA IT AB](https://www.ingka.com) | [@bjornramberg](https://github.com/bjornramberg), [@supriyachitale](https://github.com/supriyachitale) | Supporting engineers at scale with self serve access and connecting the dots of our engineering platform and services, enabling product teams to move faster and go further, and unleashing innovation, reuse and co-creation across the organisation. | | [Invitae](https://www.invitae.com/en) | [@ryan-hanchett](https://github.com/ryan-hanchett), [@gmandler42](https://github.com/gmandler42) | Centralized Developer Experience portal, putting all of our tooling behind a single pane of glass and creating a living service catalog. | @@ -171,7 +171,7 @@ _You can do this by using the [Adopter form](https://form.typeform.com/to/zcOaKi | [AEB](https://www.aeb.com/) | [David Fankhänel](mailto:dfl@aeb.com) | Central developer platform for creating new apps via templates, getting an overview via software catalog, etc | | [SALTO Systems](https://saltosystems.com) | [Ian Cowley](mailto:i.cowley@saltosystems.com) | Currently using Backstage as an internal documentation portal. | | [Lummo](https://lummo.com) | [Anjul Sahu](mailto:anjul@lummo.com) | We are building the internal developer portal using Backstage and bringing up all integrations and service information at one place. | -| [Frontside](https://frontside.com/backstage) | [Taras Mankovski](mailto:taras@frontside.com) | An active community member and contributor, Frontside offers both custom development as well as enterprise support services to help teams get the most out of Backstage for the long-run. | +| [Frontside](https://frontside.com/backstage) | [Taras Mankovski](mailto:taras@frontside.com) | An active community member and contributor, Frontside offers both custom development as well as enterprise support services to help teams get the most out of Backstage for the long-run. | | [Stepstone](https://www.stepstone.com/en/) | [Neil Kennedy](mailto:neil.kennedy@stepstone.com) | StepStone is using Backstage to solve problems around ownership and visibility of our applications. We have thousands of repos, multiple legacy systems and a growing platform that is hard to maintain. Backstage is forming the centre of our push to embrace the chaos. | | [idwall](https://idwall.co) | [Rodrigo Catão Araujo](mailto:rodrigo@idwall.co) | Developer Portal for internal engineers to access service catalog, documentation, observability, infrastructure and internal tooling. | | [Jaguar Land Rover](https://www.jaguarlandrover.com) | [Josh Walker](mailto:jwalke18@jaguarlandrover.com) | Users can request a Gitlab user, which creates a commit with the Terraform code. | @@ -202,16 +202,16 @@ _You can do this by using the [Adopter form](https://form.typeform.com/to/zcOaKi | [Skillz](https://skillz.com/) | [Peiman Jafari](https://github.com/peimanja) | Internal developers portal for technical documentations, components ownership and relationship, software templates and integrations with internal tools | | [Telus](https://www.telus.com/en/) | [Leo Li](mailto:leo.li@telus.com), [Laurent Robichaud](mailto:laurent.robichaud@telus.com), [Seb Barre](https://github.com/sbarre) | Simplifying the developer experience through centralized team member portals. Our current focus includes the adoption of Tech Docs, Software Catalog, Software Templates, the plethora of plugins, and contributing features back to Backstage. 🤖 | | [Fidelity Investments](https://fidelity.com) | [Ankita Upadhyay](mailto:ankita.upadhyay@fmr.com) | Getting started with the adoption for Monorepo projects | -| [Verisk](https://verisk.com) | [Callen Barton](mailto:#xw_architecture@verisk.com), [Kevin Johnson](mailto:#xw_architecture@verisk.com) | Developer portal to quickly create and deploy microservices. | +| [Verisk](https://verisk.com) | [Callen Barton](mailto:#xw_architecture@verisk.com), [Kevin Johnson](mailto:#xw_architecture@verisk.com) | Developer portal to quickly create and deploy microservices. | | [iodigital](https://iodigital.com) | [Jan-Willem Mulder](mailto:jan-willem.mulder@iodigital.com) | Internal developer portal for discovery of applications, projects and teams. Using several plugins like the Software Catalog and Tech Insights for promoting best practices and supporting our SDLC toolchain | | [Fanatics](https://www.fanaticsinc.com/) | [Rory Scott](mailto:rscott@fanatics.com) | Internal Portal consolidating documentation, making it easier to manage applications, internal developer community platform, and self-service cloud infrastructure + pipelines. | | [Appfolio](https://appfolio.com) | [Andy Vaughn](mailto:andy.vaughn@appfolio.com) | Internal software catalog, tech radar, documentation portal to disambiguate software and domain ownership, foster exploration of available developer platform services and tools, improve communication, democratize documentation and knowledge sharing, and coordinate the software lifecycle; all in service of a best-in-class developer experience. | | [isaac](https://isaac.com.br/) | [Leonardo Borges](mailto:leonardo.borges@isaac.com.br), [Ordilei Souza](mailto:ordilei.souza@isaac.com.br) | We're using Backstage as our Internal Developer Portal and main microservices catalog for mapping ownership, health and metrics for each one. | -| [Paraná Banco](https://site.paranabanco.com.br/) | [Joao Antunes](mailto:joaopma@pbtech.net.br) | Internal software catalog, documentation and ownership, improve communication, democratize documentation and knowledge sharing, and coordinate the software lifecycle; all in service of a best-in-class developer experience. | -| [Stone](https://stone.com.br/) | [Levy Fialho](mailto:lfialho@stone.com.br) | We're using Backstage as our Credit Team Developer Portal and microservices catalog for mapping ownership. We are also using mkdocs for microservices documentation. | +| [Paraná Banco](https://site.paranabanco.com.br/) | [Joao Antunes](mailto:joaopma@pbtech.net.br) | Internal software catalog, documentation and ownership, improve communication, democratize documentation and knowledge sharing, and coordinate the software lifecycle; all in service of a best-in-class developer experience. | +| [Stone](https://stone.com.br/) | [Levy Fialho](mailto:lfialho@stone.com.br) | We're using Backstage as our Credit Team Developer Portal and microservices catalog for mapping ownership. We are also using mkdocs for microservices documentation. | | [REI](https://www.rei.com/) | [Jen Evans](mailto:jenevan@rei.com) | Developer portal focused on an enterprise-wide app catalog to track ownership and surface APIs. | | [next](https://next.me) | [Devan Jeronimo Nack](mailto:devan.j.nack@next.me), [Everson Crusara](mailto:everson.crusara@next.me), [Thiago Carneiro da Silva](mailto:thiagoc.silva@next.me) | We are building our Internal Developer Portal using Backstage to improve developer's experience by centralizing our services catalog and identifing microservices' ownership. Also we are going to improve Technical Documentation and speed up development using software templates to help squads in creation and deployment of new microservices. | -| [Vipps](https://vipps.no) | [Martin Ehrnst](https://github.com/ehrnst) | Vipps use backstage for our service catalog, documentation, and developer portal. Using templates we are able to simplify the developer experience when deploying new services to our platform. | -| [Ferrovial](https://ferrovial.com) | [Jose Luis Rosado](mailto:jlrosado@ferrovial.com) | Backstage is helping us to improve and acelerate dev experience helping teams to quickly find technical documentation, infrastructure templates, pipelines, software components and quickstarters that have been developed by our squads in a inner source friendly environment. - | - +| [Vipps](https://vipps.no) | [Martin Ehrnst](https://github.com/ehrnst) | Vipps use backstage for our service catalog, documentation, and developer portal. Using templates we are able to simplify the developer experience when deploying new services to our platform. | +| [Ferrovial](https://ferrovial.com) | [Jose Luis Rosado](mailto:jlrosado@ferrovial.com) | Backstage is helping us to improve and acelerate dev experience helping teams to quickly find technical documentation, infrastructure templates, pipelines, software components and quickstarters that have been developed by our squads in a inner source friendly environment. | +| [Inter&Co](https://bancointer.com.br) | [Arnaud Lanna](https://github.com/arnaudlanna), [Adriano Silva](https://github.com/adrianovss), [Bruno Grossi](https://github.com/begrossi) | We're using Backstage as our internal Developer Portal to catalog and collect repositories and microservices pieces of information like ownership, deployment time, and documentation. | +| [StatusNeo](https://statusneo.com/) | [Karan Nangru](mailto:nangru@statusneo.com), [@NishkarshRaj](https://github.com/NishkarshRaj), and [Gaurav Sarien](mailto:gaurav.sarien@statusneo.com) | Harnessing the power of central catalog inventory and self-serving software templates | diff --git a/README.md b/README.md index 5dba1fdf52..53f8275b88 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![headline](docs/assets/headline.png) +[![headline](docs/assets/headline.png)](https://backstage.io/) # [Backstage](https://backstage.io) diff --git a/app-config.yaml b/app-config.yaml index 22ca1fcaff..977b07612f 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -138,6 +138,11 @@ techdocs: dynatrace: baseUrl: https://your.dynatrace.instance.com +# Score-cards sample configuration. +scorecards: + jsonDataUrl: https://raw.githubusercontent.com/Oriflame/backstage-plugins/main/plugins/score-card/sample-data/ + wikiLinkTemplate: https://link-to-wiki/{id} + sentry: organization: my-company @@ -415,16 +420,6 @@ costInsights: kind: 'PINTS_OF_ICE_CREAM' unit: 'ice cream pint' rate: 5.5 -homepage: - clocks: - - label: UTC - timezone: UTC - - label: NYC - timezone: 'America/New_York' - - label: STO - timezone: 'Europe/Stockholm' - - label: TYO - timezone: 'Asia/Tokyo' pagerduty: eventsBaseUrl: 'https://events.pagerduty.com/v2' jenkins: diff --git a/contrib/docker/cookiecutter-with-jinja2-extensions/README.md b/contrib/docker/cookiecutter-with-jinja2-extensions/README.md index 0b754fa4ce..d51b46cb96 100644 --- a/contrib/docker/cookiecutter-with-jinja2-extensions/README.md +++ b/contrib/docker/cookiecutter-with-jinja2-extensions/README.md @@ -12,7 +12,7 @@ Using Cookiecutter extensions is a two-step process: This step depends on how the scaffolder is setup to use Cookiecutter: - Using a local Cookiecutter, or -- Using a Cookiecutter Docker image, e.g. [spotify/backstage-cookiecutter](https://github.com/backstage/backstage/blob/37e35b91/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile). +- Using a Cookiecutter Docker image, e.g. [spotify/backstage-cookiecutter](https://github.com/backstage/backstage/blob/master/plugins/scaffolder-backend/scripts/Cookiecutter.dockerfile). Say we want to install [`jinja2_custom_filters_extension`](https://pypi.org/project/jinja2-custom-filters-extension/) to use the `upper_case_first_letter` filter in a Cookiecutter template. diff --git a/contrib/docs/tutorials/authenticate-api-requests.md b/contrib/docs/tutorials/authenticate-api-requests.md index 45ae38b8b7..c8f4083a0c 100644 --- a/contrib/docs/tutorials/authenticate-api-requests.md +++ b/contrib/docs/tutorials/authenticate-api-requests.md @@ -8,30 +8,28 @@ API requests from frontend plugins include an authorization header with a Backst As techdocs HTML pages load assets without an Authorization header the code below also sets a token cookie when the user logs in (and when the token is about to expire). +Create `packages/backend/src/authMiddleware.ts`: + ```typescript -// packages/backend/src/index.ts from a create-app deployment - -import cookieParser from 'cookie-parser'; -import { Request, Response, NextFunction } from 'express'; -import { JWT } from 'jose'; -import { URL } from 'url'; +import { SingleHostDiscovery } from '@backstage/backend-common'; +import type { Config } from '@backstage/config'; import { - IdentityClient, getBearerTokenFromAuthorizationHeader, + IdentityClient, } from '@backstage/plugin-auth-node'; - -// ... +import { NextFunction, Request, Response, RequestHandler } from 'express'; +import { decodeJwt } from 'jose'; +import { URL } from 'url'; +import { PluginEnvironment } from './types'; function setTokenCookie( res: Response, options: { token: string; secure: boolean; cookieDomain: string }, ) { try { - const payload = JWT.decode(options.token) as object & { - exp: number; - }; - res.cookie(`token`, options.token, { - expires: new Date(payload?.exp ? payload?.exp * 1000 : 0), + const payload = decodeJwt(options.token); + res.cookie('token', options.token, { + expires: new Date(payload.exp ? payload.exp * 1000 : 0), secure: options.secure, sameSite: 'lax', domain: options.cookieDomain, @@ -43,9 +41,10 @@ function setTokenCookie( } } -async function main() { - // ... - +export const createAuthMiddleware = async ( + config: Config, + appEnv: PluginEnvironment, +) => { const discovery = SingleHostDiscovery.fromConfig(config); const identity = IdentityClient.create({ discovery, @@ -54,7 +53,7 @@ async function main() { const baseUrl = config.getString('backend.baseUrl'); const secure = baseUrl.startsWith('https://'); const cookieDomain = new URL(baseUrl).hostname; - const authMiddleware = async ( + const authMiddleware: RequestHandler = async ( req: Request, res: Response, next: NextFunction, @@ -62,13 +61,21 @@ async function main() { try { const token = getBearerTokenFromAuthorizationHeader(req.headers.authorization) || - req.cookies['token']; - req.user = await identity.authenticate(token); + (req.cookies.token as string | undefined); + if (!token) { + res.status(401).send('Unauthorized'); + return; + } + try { + req.user = await identity.authenticate(token); + } catch { + await appEnv.tokenManager.authenticate(token); + } if (!req.headers.authorization) { // Authorization header may be forwarded by plugin requests req.headers.authorization = `Bearer ${token}`; } - if (token !== req.cookies['token']) { + if (token && token !== req.cookies.token) { setTokenCookie(res, { token, secure, @@ -77,9 +84,24 @@ async function main() { } next(); } catch (error) { - res.status(401).send(`Unauthorized`); + res.status(401).send('Unauthorized'); } }; + return authMiddleware; +}; +``` + +```typescript +// packages/backend/src/index.ts from a create-app deployment + +import { createAuthMiddleware } from './authMiddleware'; + +// ... + +async function main() { + // ... + + const authMiddleware = await createAuthMiddleware(config, appEnv); const apiRouter = Router(); apiRouter.use(cookieParser()); @@ -99,12 +121,10 @@ async function main() { } ``` +Create `packages/app/src/cookieAuth.ts`: + ```typescript -// packages/app/src/App.tsx from a create-app deployment - -import { discoveryApiRef, useApi } from '@backstage/core-plugin-api'; - -// ... +import type { IdentityApi } from '@backstage/core-plugin-api'; // Parses supplied JWT token and returns the payload function parseJwt(token: string): { exp: number } { @@ -113,9 +133,11 @@ function parseJwt(token: string): { exp: number } { const jsonPayload = decodeURIComponent( atob(base64) .split('') - .map(function (c) { - return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2); - }) + .map( + c => + // eslint-disable-next-line prefer-template + '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2), + ) .join(''), ); @@ -132,7 +154,7 @@ function msUntilExpiry(token: string): number { // Calls the specified url regularly using an auth token to set a token cookie // to authorize regular HTTP requests when loading techdocs -async function setTokenCookie(url: string, identityApi: IdentityApi) { +export async function setTokenCookie(url: string, identityApi: IdentityApi) { const { token } = await identityApi.getCredentials(); if (!token) { return; @@ -155,6 +177,14 @@ async function setTokenCookie(url: string, identityApi: IdentityApi) { ms > 0 ? ms : 10000, ); } +``` + +```typescript +// packages/app/src/App.tsx from a create-app deployment + +import { setTokenCookie } from './cookieAuth'; + +// ... const app = createApp({ // ... diff --git a/contrib/scripts/orphan-clean-up/README.md b/contrib/scripts/orphan-clean-up/README.md index a6a25a4e7d..6fdf51bbbf 100644 --- a/contrib/scripts/orphan-clean-up/README.md +++ b/contrib/scripts/orphan-clean-up/README.md @@ -2,20 +2,47 @@ ## Overview -The Orphan Clean Up script is a basic PowerShell script to delete orphaned entities in the catalog. This script also assumes that you do not have authentication setup for your Backstage API endpoints. +The Orphan Clean Up scripts are a basic scripts to delete orphaned entities in the catalog. + +This script also assumes that you do not have authentication setup for your Backstage API endpoints. _Warning:_ There is a risk of entities being orphaned (and being deleted by this script) in case of the location having problems and returning a 404 status code. This might lead to accidental deletion of entities until the processing loop has recreated the entity. -## Requirements +## PowerShell + +A PowerShell implementation of the orphan cleanup script. + +### Requirements This script is PowerShell based so therefore needs to be ran in a PowerShell session. If you are not able to use PowerShell the script should give you a clear idea as to how to create it with another scripting language like Bash. -## Usage +### Usage Here's how to use the script: -1. Download the script +1. Download the `OrphanCleanUp.ps1` script 2. Now start a PowerShell session 3. Next navigate to the location you downloaded the script 4. Then run this command replacing `https:\\backstage.my-company.com` with the URL of your Backstage instance: `.\OrphanCleanUp.ps1 https:\\backstage.my-company.com` 5. The script will output the number of orphaned entities it finds and then the name for each one it deletes + +## Bash + +A bash implementation of the orphan cleanup script. + +### Requirements + +This script is shell based and requires the following programs to be installed on your system: + +- curl +- jq + +### Usage + +Here's how to use the script: + +1. Download the `orphan_cleanup.sh` script +2. Now start a bash session +3. Next navigate to the location you downloaded the script +4. Then run this command replacing `https://backstage.my-company.com` with the URL of your Backstage instance: `./orphan_cleanup.sh https://backstage.my-company.com` +5. The script will output the number of orphaned entities it finds and then the name for each one it deletes diff --git a/contrib/scripts/orphan-clean-up/orphan_cleanup.sh b/contrib/scripts/orphan-clean-up/orphan_cleanup.sh new file mode 100755 index 0000000000..6ffe4737fb --- /dev/null +++ b/contrib/scripts/orphan-clean-up/orphan_cleanup.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +set -euo pipefail + +# Cleanes up orphaned entities for the provided Backstage URL, defaults to the local backend +BACKSTAGE_URL=${1:-'http://localhost:7007'} +echo $BACKSTAGE_URL + +ORPHAN_API_URL="$BACKSTAGE_URL/api/catalog/entities?filter=metadata.annotations.backstage.io/orphan=true" +ORPHAN_DELETE_API_URL="$BACKSTAGE_URL/api/catalog/entities/by-uid" + +ORPHANS=$(curl -s $ORPHAN_API_URL) + +echo "" +echo "Found $(echo $ORPHANS | jq length ) orphaned entities" +echo "" + +jq -c '.[]' <<< $ORPHANS | while read ORPHAN; do + echo $ORPHAN | jq "." + echo "Deleting orphan entity: $(echo $ORPHAN | jq -r .metadata.name) of kind: $(echo $ORPHAN | jq -r .kind)" + curl -X DELETE "$ORPHAN_DELETE_API_URL/$(echo $ORPHAN | jq -r .metadata.uid)" +done diff --git a/contrib/scripts/upgrade-backstage-app/README.md b/contrib/scripts/upgrade-backstage-app/README.md new file mode 100644 index 0000000000..ea8b0a0e2f --- /dev/null +++ b/contrib/scripts/upgrade-backstage-app/README.md @@ -0,0 +1,30 @@ +# Upgrade Backstage App + +## Overview + +The script upgrades the version of Backstage created via the create-app plugin. It lets you resolve conflicts iteratively with your own merge [tool](https://www.git-scm.com/docs/git-mergetool). + +## Requirements + +You will need to have the following tools in your shell environment: + +- git +- curl +- jq + +## Usage + +Here's how to use the script: + +1. download the script +2. copy it in the root of your app +3. bootstrap a git repository (you may already have done so): + +```bash +git init +git add . +git commit -m "initial commit" +``` + +4. run `sh upgrade-backstage-app.sh [optional-backstage-version]` +5. resolve any conflicts iteratively diff --git a/contrib/scripts/upgrade-backstage-app/upgrade-backstage-app.sh b/contrib/scripts/upgrade-backstage-app/upgrade-backstage-app.sh new file mode 100755 index 0000000000..d68d9e31c4 --- /dev/null +++ b/contrib/scripts/upgrade-backstage-app/upgrade-backstage-app.sh @@ -0,0 +1,15 @@ +#!/bin/sh +CURRENT_VERSION="v$(cat backstage.json | jq -r '.version')" +TARGET_VERSION=${1:-"$(curl -s https://api.github.com/repos/backstage/backstage/releases/latest | jq -r '.tag_name')"} +CREATE_APP_CURRENT_VERSION=$(curl -s https://raw.githubusercontent.com/backstage/backstage/$CURRENT_VERSION/packages/create-app/package.json | jq -r '.version') +CREATE_APP_TARGET_VERSION=$(curl -s https://raw.githubusercontent.com/backstage/backstage/$TARGET_VERSION/packages/create-app/package.json | jq -r '.version') + +if [ "$CURRENT_VERSION" == "$TARGET_VERSION" ]; then + echo "Already up to date" +else + echo "Attempting upgrade from Backstage $CURRENT_VERSION (create-app $CREATE_APP_CURRENT_VERSION) to $TARGET_VERSION ($CREATE_APP_TARGET_VERSION)" + rm -rf .upgrade && mkdir .upgrade + curl -s https://raw.githubusercontent.com/backstage/upgrade-helper-diff/master/diffs/$CREATE_APP_CURRENT_VERSION..$CREATE_APP_TARGET_VERSION.diff > .upgrade/upgrade.diff + git apply -3 .upgrade/upgrade.diff + git mergetool +fi diff --git a/cypress/src/integration/plugins/score-card.spec.ts b/cypress/src/integration/plugins/score-card.spec.ts new file mode 100644 index 0000000000..5871a094e1 --- /dev/null +++ b/cypress/src/integration/plugins/score-card.spec.ts @@ -0,0 +1,83 @@ +/* + * Copyright 2021 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 'os'; + +describe('score-card', () => { + describe('Score board', () => { + it('displays the score board based on sample data', () => { + cy.loginAsGuest(); + + cy.visit('/score-board'); + cy.screenshot({ capture: 'viewport' }); + + cy.contains('System scores overview').should('be.visible'); + cy.checkForErrors(); + cy.get('span:contains("1-2 of 2")').should('be.visible'); // beware, there is also a hidden

element + cy.contains('audio-playback').should('be.visible'); + cy.contains('team-c').should('be.visible'); + cy.contains('non-valid-system').should('be.visible'); + cy.contains('Name').should('be.visible'); + cy.contains('Date').should('be.visible'); + cy.contains('Code').should('be.visible'); + cy.contains('Documentation').should('be.visible'); + cy.contains('Operations').should('be.visible'); + cy.contains('Quality').should('be.visible'); + cy.contains('Security').should('be.visible'); + cy.contains('Total').should('be.visible'); + cy.contains('50 %').should('be.visible'); + cy.contains('75 %').should('be.visible'); + cy.log('navigating to score card detail for audio-playback'); + cy.get('a[data-id="audio-playback"]').should('be.visible').click(); + cy.screenshot({ capture: 'viewport' }); + + cy.url().should( + 'include', + '/catalog/default/System/audio-playback/score', + ); + cy.contains('Scoring').should('be.visible'); + cy.contains('Total score: 57 %').should('be.visible'); + cy.contains('Code').should('be.visible'); + cy.contains('90 %').should('be.visible'); + cy.contains('Documentation').should('be.visible'); + cy.contains('75 %').should('be.visible'); + cy.contains('Operations').should('be.visible'); + cy.contains('50 %').should('be.visible'); + cy.contains('Quality').should('be.visible'); + cy.contains('25 %').should('be.visible'); + cy.contains('Security'); + cy.contains('10 %').should('be.visible'); + cy.checkForErrors(); + + cy.log( + 'Clicking on button [>] that is first child of the element (td) with value=Code', + ); + cy.get('[value="Code"] > button:first-child').click(); + cy.checkForErrors(); + cy.screenshot({ capture: 'viewport' }); + + cy.log('Clicking on link for Code'); + cy.contains('hints: Gitflow: 100%').should('be.visible'); + cy.get('a[data-id="2157"]') + .should('be.visible') + .should( + 'have.attr', + 'href', + 'https://TBD/XXX/_wiki/wikis/XXX.wiki/2157', + ); + }); + }); +}); diff --git a/cypress/src/support/commands.ts b/cypress/src/support/commands.ts index 02723ffc90..62e21a5bef 100644 --- a/cypress/src/support/commands.ts +++ b/cypress/src/support/commands.ts @@ -118,3 +118,8 @@ Cypress.Commands.add('waitSectionTwoPage', () => { Cypress.Commands.add('waitHomePage', () => { cy.wait(['@entityMetadata', '@syncEntity', '@techdocsMetadata', '@homeHTML']); }); + +Cypress.Commands.add('checkForErrors', () => { + // when an error occurs there is a

with an "alert" role attribute. This can change ofc => we shall add also some positive ("when error occurs") test + cy.get('div[role="alert"]').should('not.exist'); +}); diff --git a/cypress/src/types.d.ts b/cypress/src/types.d.ts index 181b608a9b..dca22112e9 100644 --- a/cypress/src/types.d.ts +++ b/cypress/src/types.d.ts @@ -77,5 +77,10 @@ declare namespace Cypress { * @example cy.isNotInViewport */ isNotInViewport(element: string): Chainable; + /** + * Check if we have not caused error by our last action + * @example cy.checkForErrors + */ + checkForErrors(): Chainable; } } diff --git a/cypress/yarn.lock b/cypress/yarn.lock index dd9856750c..d55c96a984 100644 --- a/cypress/yarn.lock +++ b/cypress/yarn.lock @@ -1415,22 +1415,22 @@ __metadata: linkType: hard "typescript@npm:^4.1.3": - version: 4.8.2 - resolution: "typescript@npm:4.8.2" + version: 4.8.4 + resolution: "typescript@npm:4.8.4" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 7f5b81d0d558c9067f952c7af52ab7f19c2e70a916817929e4a5b256c93990bf3178eccb1ac8a850bc75df35f6781b6f4cb3370ce20d8b1ded92ed462348f628 + checksum: 3e4f061658e0c8f36c820802fa809e0fd812b85687a9a2f5430bc3d0368e37d1c9605c3ce9b39df9a05af2ece67b1d844f9f6ea8ff42819f13bcb80f85629af0 languageName: node linkType: hard "typescript@patch:typescript@^4.1.3#~builtin": - version: 4.8.2 - resolution: "typescript@patch:typescript@npm%3A4.8.2#~builtin::version=4.8.2&hash=a1c5e5" + version: 4.8.4 + resolution: "typescript@patch:typescript@npm%3A4.8.4#~builtin::version=4.8.4&hash=a1c5e5" bin: tsc: bin/tsc tsserver: bin/tsserver - checksum: 5cb0f02f414f5405f4b0e7ee1fd7fa9177b6a8783c9017b6cad85f56ce4c4f93e0e6f2ce37e863cb597d44227cd009474c9fbd85bf7a50004e5557426cb58079 + checksum: 563a0ef47abae6df27a9a3ab38f75fc681f633ccf1a3502b1108e252e187787893de689220f4544aaf95a371a4eb3141e4a337deb9895de5ac3c1ca76430e5f0 languageName: node linkType: hard diff --git a/docs/assets/getting-started/portal.png b/docs/assets/getting-started/portal.png index f16ad44757..b3133f2278 100644 Binary files a/docs/assets/getting-started/portal.png and b/docs/assets/getting-started/portal.png differ diff --git a/docs/assets/getting-started/wizard.png b/docs/assets/getting-started/wizard.png index 679878e252..8b6dce6131 100644 Binary files a/docs/assets/getting-started/wizard.png and b/docs/assets/getting-started/wizard.png differ diff --git a/docs/auth/auth0/provider.md b/docs/auth/auth0/provider.md index 1bfa333191..6fb57a641b 100644 --- a/docs/auth/auth0/provider.md +++ b/docs/auth/auth0/provider.md @@ -35,6 +35,8 @@ auth: clientSecret: ${AUTH_AUTH0_CLIENT_SECRET} domain: ${AUTH_AUTH0_DOMAIN_ID} audience: ${AUTH_AUTH0_AUDIENCE} + connection: ${AUTH_AUTH0_CONNECTION} + connectionScope: ${AUTH_AUTH0_CONNECTION_SCOPE} ``` The Auth0 provider is a structure with three configuration keys: @@ -44,6 +46,12 @@ The Auth0 provider is a structure with three configuration keys: page - `domain`: The Application domain, found on the Auth0 Application page +## Optional Configuration + +- `audience`: The intended recipients of the token +- `connection`: Social identity provider name. To check the available social connections, please visit [Auth0 Social Connections](https://marketplace.auth0.com/features/social-connections). +- `connectionScope`: Additional scopes in the interactive token request. It should always be used in combination with the `connection` parameter + ## Adding the provider to the Backstage frontend To add the provider to the frontend, add the `auth0AuthApi` reference and diff --git a/docs/auth/oidc.md b/docs/auth/oidc.md new file mode 100644 index 0000000000..d915262eb7 --- /dev/null +++ b/docs/auth/oidc.md @@ -0,0 +1,259 @@ +--- +id: oidc +title: OIDC provider from scratch +description: This section shows how to use an OIDC provider from scratch, same steps apply for custom providers. +--- + +This section shows how to use an OIDC provider from scratch, same steps apply for custom +providers. Please note these steps are for using a provider, not how to implement one, +and Backstage recommends creating custom providers specific to the IDP, so we'll use a +`azureOIDC` provider throughout this example, feel free to change any of those refs +to your provider name. + +## Summary + +To add providers not enabled by default like OIDC, we need to follow some steps, we +assume you already have a sign in page to which we'll add the provider so users can +sign in through the provider. In simple steps here's how you enable the provider: + +- Create an API reference to identify the provider. +- Create the API factory that will handle the authentication. +- Add or reuse an auth provider so you can authenticate. +- Add or reuse a resolver to handle the result from the authentication. +- Configure the provider to access your 3rd party auth solution. +- Add the provider to sign in page so users can login with it. + +We'll explain each step more in detail next. + +### The API reference + +An API reference exist for the sake of **Dependency Injection**, check [Utility APIs][4] +for extended explanation. + +In this OIDC example, we'll create the API reference directly in the +`packages/app/src/apis.ts` file, it is not a requirement to put the reference in this +file. Any location will do as long as it's available to be imported to where the API +factory is, as well as easily accessible to the rest of the application so any package +and plugin can inject the API instance when necessary. + +An example of such would be when you use an auth provider from a library installed with +NPM, or any other library repository, you would import the API ref from the library. + +```ts +export const azureOIDCAuthApiRef: ApiRef< + OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi +> = createApiRef({ + id: 'auth.my-custom-provider', +}); +``` + +Please note a few things, the ID can be anything you want as long as it doesn't conflict +with other refs, backstage recommends to use a custom name that references your custom +provider, for example we are using OIDC protocol with Azure, so we could use something +like `auth.azure.oidc` as well. + +Also we're exporting this reference, as well as the `typings`, we need to +be able to import this reference anywhere in the app, and the `typings` will tell typescript +what instance we're getting from DI when injecting the API. In this case we are defining +an API for authentication, so we tell TS that this instance complies with 4 API +interfaces: + +- The OICD API that will handle authentication. +- Profile API for requesting user profile info from the auth provider in question. +- Backstage identity API to handle and associate the user profile with backstage identity. +- Session API, to handle the session the user will have while logged in. + +### The API Factory + +A factory is a function that can take some parameters or dependencies and return an +instance of something, in our case it will be a function that requests some backstage +APIs and use them to create an instance of an OIDC API provider. + +Please note that this function only runs (creates the instance) when somewhere else in +the app you request the DI to give you an instance of the OIDC provider using the API ref +defined above, and the DI will only run this function the first time, from then on any +other DI injection will just receive the same instance created the first time, basically +the instance is cached by the DI library, a singleton. + +Let's add our OIDC API factory to the APIs array in the `packages/app/src/apis.ts` file: + +```diff ++ import { OAuth2 } from '@backstage/core-app-api'; + +export const apis: AnyApiFactory[] = [ ++ createApiFactory({ ++ api: azureOIDCAuthApiRef, ++ deps: { ++ discoveryApi: discoveryApiRef, ++ oauthRequestApi: oauthRequestApiRef, ++ configApi: configApiRef, ++ }, ++ factory: ({ discoveryApi, oauthRequestApi, configApi }) => ++ OAuth2.create({ ++ discoveryApi, ++ oauthRequestApi, ++ provider: { ++ id: 'my-auth-provider', ++ title: 'My custom auth provider', ++ icon: () => null, ++ }, ++ environment: configApi.getOptionalString('auth.environment'), ++ defaultScopes: [ ++ 'openid', ++ 'profile', ++ 'email', ++ ], ++ }), ++ }), + +``` + +Please note we're importing the `OAuth2` class from `@backstage/core-app-api` effectively +delegating the authentication to it. Also we're using the `my-auth-provider` ID to tell +`OAuth2` to use the auth provider we'll define in the next section, and added the default +scopes to request ID, profile, email and user read permissions. + +## The Auth Provider + +The Auth Provider is responsible for authenticating with the 3rd party service, and give +us back the credentials, here's where you pick which protocol to use, be it Auth0, OAuth2, +OIDC, SAML or any other that your 3rd party IDP provider supports. + +For this example we'll use OIDC, we pass a factory to the `providerFactories` object with +the ID you picked to represent the Auth provider, this ID has to match with the provider's +`id` inside the API factory, the yaml config provider key under `auth.providers`, and the +callback URI provider segment (you'll have to configure your IDP to handle the callback +URI properly). + +```diff +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + return await createRouter({ + logger: env.logger, + config: env.config, + database: env.database, + discovery: env.discovery, + tokenManager: env.tokenManager, + providerFactories: { + ...defaultAuthProviderFactories, ++ 'my-auth-provider': providers.oidc.create({ ++ }), + } +``` + +### The Resolver + +Resolvers exist to map user identity from the 3rd party (in this case an azure IDP +provider) to the backstage user identity, for a detailed explanation check the +[Identity Resolver][1] page, it explains how to write a custom resolver as well as +linking the built in resolvers of backstage. + +The default OIDC provider does not support SignIn, we need to add such support by +adding a resolver for a SignIn request. + +The OIDC provider doesn't provide any build-in resolvers, so we'll need to define our own: + +```diff +import { + DEFAULT_NAMESPACE, ++ stringifyEntityRef, +} from '@backstage/catalog-model'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + return await createRouter({ + logger: env.logger, + config: env.config, + database: env.database, + discovery: env.discovery, + tokenManager: env.tokenManager, + providerFactories: { + ...defaultAuthProviderFactories, + 'my-auth-provider': providers.oidc.create({ ++ signIn: { ++ resolver(info, ctx) { ++ const userRef = stringifyEntityRef({ ++ kind: 'User', ++ name: info.result.userinfo.sub, ++ namespace: DEFAULT_NAMESPACE, ++ }); ++ return ctx.issueToken({ ++ claims: { ++ sub: userRef, // The user's own identity ++ ent: [userRef], // A list of identities that the user claims ownership through ++ }, ++ }); ++ }, ++ }, + }), + } +``` + +### The configuration + +Since we are using our custom OIDC Auth Provider, we need to add a configuration based +on the provider used, in this case based on OIDC protocol (remember the 3rd party has to +support the protocol). + +In this example we'll configure OIDC with `my-auth-provider`, to do so we need to +[Create app registration][2] in the Azure console, the only difference is that the +`http://localhost:7007/api/auth/microsoft/handler/frame` URL needs to change to +`http://localhost:7007/api/auth/my-auth-provider/handler/frame`. + +Then we need to configure the env variables for the provider, based on the provider's code +in `plugins/auth-backend/src/providers/oidc/provider.ts` we need the following variables +in the `app-config.yaml`: + +```yaml +auth: + environment: development + ### Providing an auth.session.secret will enable session support in the auth-backend + session: + secret: ${SESSION_SECRET} + providers: + my-auth-provider: + development: + metadataUrl: https://example.com/.well-known/openid-configuration + clientId: ${AUTH_MY_CLIENT_ID} + clientSecret: ${AUTH_MY_CLIENT_SECRET} +``` + +Anything enclosed in `${}` can be replaced directly in the yaml, or provided as +environment variables, the way you obtain all these except `scope` and `prompt` is to +check the App Registration you created: + +- `clientId`: Grab from the Overview page. +- `clientSecret`: Can only be seen when creating the secret, if you lose it you'll need a + new secret. +- `metadataUrl`: In Overview > Endpoints tab, grab OpenID Connect metadata document URL. +- `authorizationUrl` and `tokenUrl`: Open the `metadataUrl` in a browser, that json will + hold these 2 urls somewhere in there. +- `tokenSignedResponseAlg`: Don't define it, use the default unless you know what it does. +- `scope`: Only used if we didn't specify `defaultScopes` in the provider's factory, + basically the same thing. +- `prompt`: Recommended to use `auto` so the browser will request login to the IDP if the + user has no active session. + +Note that for the time being, any change in this yaml file requires a restart of the app, +also you need to have the `session.secret` part to use OIDC (some other providers might +need this as well) to support user sessions. + +### The Sign In provider + +The last step is to add the provider to the `SignInPage` so users can sign in with your +new provider, please follow the [Sign In Configuration][3] docs, here's where you import +and use the API reference we defined earlier. + +## Note + +These steps apply to most if not all the providers, including custom providers, the main +difference between different providers will be the contents of the API factory, the code +in the Auth Provider Factory, the resolver, and the different variables each provider +needs in the YAML config or env variables. + +[1]: https://backstage.io/docs/auth/identity-resolver +[2]: https://backstage.io/docs/auth/microsoft/provider#create-an-app-registration-on-azure +[3]: https://backstage.io/docs/auth/#sign-in-configuration +[4]: https://backstage.io/docs/api/utility-apis diff --git a/docs/auth/service-to-service-auth.md b/docs/auth/service-to-service-auth.md new file mode 100644 index 0000000000..7c501a8e9e --- /dev/null +++ b/docs/auth/service-to-service-auth.md @@ -0,0 +1,164 @@ +--- +id: service-to-service-auth +title: Service to Service Auth +# prettier-ignore +description: This section describes how to use service to service authentication, both internally within Backstage plugins and towards external services. +--- + +This article describes the steps needed to introduce _backend-to-backend auth_. +This allows plugin backends to determine whether a given request originates from +a legitimate Backstage plugin (or other external caller), by requiring a special +type of service-to-service token which is signed with a shared secret. + +When enabling this protection on your Backstage backend plugins, for example the +catalog, other callers in the ecosystem such as the search indexer and +scaffolder would need to present a valid token to the catalog to be able to +request its contents. + +## Setup + +In a newly created Backstage app, the backend is setup up to not require any +auth at all. This means that generated service-to-service tokens are empty, and +that incoming requests are not validated. If you want to enable +service-to-service auth, the first step is to switch out the following line in +your backend setup at `packages/backend/src/index.ts`: + +```diff +- const tokenManager = ServerTokenManager.noop(); ++ const tokenManager = ServerTokenManager.fromConfig(config, { logger: root }); +``` + +By switching from the no-op `ServiceTokenManager` to one created from config, +you enable service-to-service auth for any plugin that implements it. The local +development setup will generally not be impacted by this, as temporary keys are +generated under the hood. But for the production setup, this means you must now +provide a shared secret that enables your backend plugins to communicate with +each other. + +Backstage service-to-service tokens are currently always signed with a single +secret key. It needs to be shared across all backend plugins and services that +ones wishes to communicate across. The key can be any base64 encoded secret. +The following command can be used to generate such a key in a terminal: + +```bash +node -p 'require("crypto").randomBytes(24).toString("base64")' +``` + +Then place it in the backend configuration, either as a direct value or +injected as an env variable. + +```yaml +# commonly in your app-config.production.yaml +backend: + auth: + keys: + - secret: + # - secret: ${BACKEND_SECRET} - if you want to use an env variable instead +``` + +**NOTE**: For ease of development, we auto-generate a key for you if you haven't +configured a secret in dev mode. You _must set your own secret_ in order for +backend-to-backend auth to work in production; the `ServiceTokenManager` will +throw an exception in production if it has no keys to work with, which will lead +to the backend failing to start up. + +## Usage in Backend Plugins + +There are a few steps if you want to make use of the service-to-service auth in +your own backend plugin. First you need to add the `TokenManager` dependency to +the `createRouter` options. Typically as `tokenManager: TokenManager`. Along +with this you'll need to ask users to start providing this new dependency in +their backend setup code. + +Once the `TokenManager` is available, you use the `.getToken()` method to generate +a new token for any outgoing requests towards other Backstage backend plugins. +This method should be called for every request that you make; do not store the +token for later use. The `TokenManager` implementations should already cache +tokens as needed. The returned token should then be added as a `Bearer` token +for the upstream request, for example: + +```ts +const { token } = await this.tokenManager.getToken(); + +const response = await fetch(pluginBackendApiUrl, { + method: 'GET', + headers: { + ...headers, + Authorization: `Bearer ${token}`, + }, +}); +``` + +To authenticate an incoming request you use the `.authenticate(token)` method. +At the time of writing this method doesn't return anything, it will simply +throw if the token is invalid. + +```ts +await tokenManager.authenticate(token); // throws if token is invalid +``` + +## Usage in External Callers + +If you have enabled server-to-server auth, you may be interested in generating +tokens in code that is external to Backstage itself. External callers may even +be written in other languages than Node.js. This section explains how to generate +a valid token yourself. + +The token must be a JWT with a `HS256` signature, using the raw base64 decoded +value of the configured key as the secret. It must also have the following payload: + +- `sub`: "backstage-server" (only this value supported currently) +- `exp`: one hour from the time it was generated, in epoch seconds + +## Granular Access Control + +We plan to build out the service-to-service auth to be much more powerful in the +future, but before that is done there are a few tricks you can use with the +current system to harden your deployments. This section assumes that you have +already split your backend plugins into more than one backend deployment, in +order to scale or isolate them. + +The backend auth configuration has support for providing multiple keys, for +example: + +```yaml +backend: + auth: + keys: + - secret: my-secret-key-1 + - secret: my-secret-key-2 + - secret: my-secret-key-3 +``` + +The first key will be used for signing requests, while all of the keys will be +used for validation. This means that you can set up an asymmetric configuration +where some backend deployments do not have access to each other. + +For example, consider the case where we have split up the catalog, scaffolder, +and search plugin into three separate backend deployments. We can use the +following configurations to allow both the scaffolder and search plugin to speak +to the +catalog, but not the other way around, and to not allow any communication between +the scaffolder and search plugins. + +```yaml +# catalog config +backend: + auth: + keys: + - secret: my-secret-key-catalog + - secret: my-secret-key-scaffolder + - secret: my-secret-key-search + +# scaffolder config +backend: + auth: + keys: + - secret: my-secret-key-scaffolder + +# search config +backend: + auth: + keys: + - secret: my-secret-key-search +``` diff --git a/docs/conf/defining.md b/docs/conf/defining.md index 6462f4f60a..29964dbcbc 100644 --- a/docs/conf/defining.md +++ b/docs/conf/defining.md @@ -23,6 +23,17 @@ Each package is searched for a schema at a single point of entry, a top-level inlined JSON schema, or a relative path to a schema file. Supported schema file formats are `.json` or `.d.ts`. +```jsonc title="package.json" +{ + // ... + "files": [ + // ... + "config.d.ts" + ], + "configSchema": "config.d.ts" +} +``` + > When defining a schema file, be sure to include the file in your > `package.json` > `"files"` field as well! diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index d35e7dd593..04a509d316 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -61,13 +61,18 @@ FROM node:16-bullseye-slim # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, # in which case you should also move better-sqlite3 to "devDependencies" in package.json. -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ - rm -rf /var/lib/apt/lists/* && \ yarn config set python /usr/bin/python3 # From here on we use the least-privileged `node` user to run the backend. USER node + +# This should create the app dir as `node`. +# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. +# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. WORKDIR /app # This switches many Node.js dependencies to production mode. @@ -79,7 +84,8 @@ ENV NODE_ENV production COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz -RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --frozen-lockfile --production --network-timeout 300000 # Then copy the rest of the backend bundle, along with any other files we might want. COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ @@ -163,53 +169,68 @@ RUN find packages \! -name "package.json" -mindepth 2 -maxdepth 2 -exec rm -rf { # Stage 2 - Install dependencies and build packages FROM node:16-bullseye-slim AS build -WORKDIR /app -COPY --from=packages /app . - # install sqlite3 dependencies -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ yarn config set python /usr/bin/python3 -RUN yarn install --frozen-lockfile --network-timeout 600000 && rm -rf "$(yarn cache dir)" +USER node +WORKDIR /app -COPY . . +COPY --from=packages --chown=node:node /app . + +# Stop cypress from downloading it's massive binary. +ENV CYPRESS_INSTALL_BINARY=0 +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --frozen-lockfile --network-timeout 600000 + +COPY --chown=node:node . . RUN yarn tsc RUN yarn --cwd packages/backend build # If you have not yet migrated to package roles, use the following command instead: # RUN yarn --cwd packages/backend backstage-cli backend:bundle --build-dependencies +RUN mkdir packages/backend/dist/skeleton packages/backend/dist/bundle \ + && tar xzf packages/backend/dist/skeleton.tar.gz -C packages/backend/dist/skeleton \ + && tar xzf packages/backend/dist/bundle.tar.gz -C packages/backend/dist/bundle + # Stage 3 - Build the actual backend image and install production dependencies FROM node:16-bullseye-slim # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, # in which case you should also move better-sqlite3 to "devDependencies" in package.json. -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ - rm -rf /var/lib/apt/lists/* && \ yarn config set python /usr/bin/python3 # From here on we use the least-privileged `node` user to run the backend. USER node + +# This should create the app dir as `node`. +# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. +# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. WORKDIR /app -# This switches many Node.js dependencies to production mode. -ENV NODE_ENV production - # Copy the install dependencies from the build stage and context -COPY --from=build --chown=node:node /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton.tar.gz ./ -RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz +COPY --from=build --chown=node:node /app/yarn.lock /app/package.json /app/packages/backend/dist/skeleton/ ./ -RUN yarn install --frozen-lockfile --production --network-timeout 600000 && rm -rf "$(yarn cache dir)" +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --frozen-lockfile --production --network-timeout 600000 # Copy the built packages from the build stage -COPY --from=build --chown=node:node /app/packages/backend/dist/bundle.tar.gz . -RUN tar xzf bundle.tar.gz && rm bundle.tar.gz +COPY --from=build --chown=node:node /app/packages/backend/dist/bundle/ ./ # Copy any other files that we need at runtime COPY --chown=node:node app-config.yaml ./ +# This switches many Node.js dependencies to production mode. +ENV NODE_ENV production + CMD ["node", "packages/backend", "--config", "app-config.yaml"] ``` @@ -225,6 +246,7 @@ however ignore any existing build output or dependencies on the host. For our new `.dockerignore`, replace the contents of your existing one with this: ```text +dist-types node_modules packages/*/dist packages/*/node_modules diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 0adbec6a82..7dc0e326fa 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -62,7 +62,7 @@ This is an array used to determine where to retrieve cluster configuration from. Valid cluster locator methods are: - [`catalog`](#catalog) -- [`localKubectlProxy`](#localKubectlProxy) +- [`localKubectlProxy`](#localkubectlproxy) - [`config`](#config) - [`gke`](#gke) - [custom `KubernetesClustersSupplier`](#custom-kubernetesclusterssupplier) @@ -101,14 +101,14 @@ array. Users will see this value in the Software Catalog Kubernetes plugin. This determines how the Kubernetes client authenticates with the Kubernetes cluster. Valid values are: -| Value | Description | -| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `serviceAccount` | This will use a Kubernetes [service account](https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/) to access the Kubernetes API. When this is used the `serviceAccountToken` field should also be set. | -| `google` | This will use a user's Google auth token from the [Google auth plugin](https://backstage.io/docs/auth/) to access the Kubernetes API. | -| `aws` | This will use AWS credentials to access resources in EKS clusters | -| `googleServiceAccount` | This will use the Google Cloud service account credentials to access resources in clusters | -| `azure` | This will use [Azure Identity](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) to access resources in clusters | -| `oidc` | This will use [Oidc Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) to authenticate to the Kubernetes API. When this is used the `oidcTokenProvider` field should also be set. | +| Value | Description | +| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `serviceAccount` | This will use a Kubernetes [service account](https://kubernetes.io/docs/reference/access-authn-authz/service-accounts-admin/) to access the Kubernetes API. When this is used the `serviceAccountToken` field should also be set. | +| `google` | This will use a user's Google auth token from the [Google auth plugin](https://backstage.io/docs/auth/) to access the Kubernetes API. | +| `aws` | This will use AWS credentials to access resources in EKS clusters | +| `googleServiceAccount` | This will use the Google Cloud service account credentials to access resources in clusters | +| `azure` | This will use [Azure Identity](https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/overview) to access resources in clusters | +| `oidc` | This will use [Oidc Tokens](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#openid-connect-tokens) to authenticate to the Kubernetes API. When this is used the `oidcTokenProvider` field should also be set. Please note the cluster must support OIDC, at the time of writing AKS clusters do not support OIDC. | Check the [Kubernetes Authentication][4] section for additional explanation. @@ -159,7 +159,12 @@ auth: audience: ${AUTH_OKTA_AUDIENCE} ``` -The following values are supported out-of-the-box by the frontend: `google`, `microsoft`, `okta`, `onelogin`. +The following values are supported out-of-the-box by the frontend: `google`, `microsoft`, +`okta`, `onelogin`. + +Take note that `oidcTokenProvider` is just the issuer for the token, you can use any +of these with an OIDC enabled cluster, like using `microsoft` as the issuer for a EKS +cluster. ##### `clusters.\*.dashboardUrl` (optional) @@ -413,6 +418,7 @@ rules: - ingresses - statefulsets - limitranges + - daemonsets verbs: - get - list diff --git a/docs/features/search/search-engines.md b/docs/features/search/search-engines.md index 515c1268d7..35e17205ac 100644 --- a/docs/features/search/search-engines.md +++ b/docs/features/search/search-engines.md @@ -101,7 +101,7 @@ Similarly to Lunr above, ElasticSearch can be set up like this: ```typescript // app/backend/src/plugins/search.ts -const searchEngine = await ElasticSearchSearchEngine.initialize({ +const searchEngine = await ElasticSearchSearchEngine.fromConfig({ logger: env.logger, config: env.config, }); diff --git a/docs/features/software-catalog/well-known-annotations.md b/docs/features/software-catalog/well-known-annotations.md index 46f28dcb01..0a4f5ee400 100644 --- a/docs/features/software-catalog/well-known-annotations.md +++ b/docs/features/software-catalog/well-known-annotations.md @@ -110,7 +110,7 @@ metadata: backstage.io/edit-url: https://github.com/my-org/catalog/edit/master/my-service.jsonnet ``` -These annotations allow customising links from the catalog pages. The view URL +These annotations allow customizing links from the catalog pages. The view URL should point to the canonical metadata YAML that governs this entity. The edit URL should point to the source file for the metadata. In the example above, `my-org` generates its catalog data from Jsonnet files in a monorepo, so the @@ -159,11 +159,11 @@ metadata: github.com/project-slug: backstage/backstage ``` -The value of this annotation is the so-called slug that identifies a project on +The value of this annotation is the so-called slug that identifies a repository on [GitHub](https://github.com) (either the public one, or a private GitHub Enterprise installation) that is related to this entity. It is on the format -`/`, and is the same as can be seen in the URL location -bar of the browser when viewing that project. +`/`, and is the same as can be seen in the URL location +bar of the browser when viewing that repository. Specifying this annotation will enable GitHub related features in Backstage for that entity. diff --git a/docs/features/software-templates/configuration.md b/docs/features/software-templates/configuration.md index 87c17ca1a3..ff5191bed2 100644 --- a/docs/features/software-templates/configuration.md +++ b/docs/features/software-templates/configuration.md @@ -14,8 +14,7 @@ appropriate source code repository for your organization. > Note: Integrations may already be set up as part of your `app-config.yaml`. -The next step is to add -[add templates](http://backstage.io/docs/features/software-templates/adding-templates) +The next step is to [add templates](http://backstage.io/docs/features/software-templates/adding-templates) to your Backstage app. ## Publishing defaults diff --git a/docs/features/software-templates/writing-custom-actions.md b/docs/features/software-templates/writing-custom-actions.md index b35ceb907d..dfbd4b0a42 100644 --- a/docs/features/software-templates/writing-custom-actions.md +++ b/docs/features/software-templates/writing-custom-actions.md @@ -123,7 +123,7 @@ will set the available actions that the scaffolder has access to. ```ts import { createBuiltinActions } from '@backstage/plugin-scaffolder-backend'; import { ScmIntegrations } from '@backstage/integration'; -import { createNewFileAction } from './actions/custom'; +import { createNewFileAction } from './scaffolder/actions/custom'; export default async function createPlugin( env: PluginEnvironment, @@ -156,16 +156,17 @@ export default async function createPlugin( Here is a list of Open Source custom actions that you can add to your Backstage scaffolder backend: -| Name | Package | Owner | -| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | -| Yeoman | [plugin-scaffolder-backend-module-yeoman](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-yeoman) | [Backstage](https://backstage.io) | -| Cookiecutter | [plugin-scaffolder-backend-module-cookiecutter](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-cookiecutter) | [Backstage](https://backstage.io) | -| Rails | [plugin-scaffolder-backend-module-rails](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-rails) | [Backstage](https://backstage.io) | -| HTTP requests | [scaffolder-backend-module-http-request](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-http-request) | [Roadie](https://roadie.io) | -| Utility actions | [scaffolder-backend-module-utils](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-utils) | [Roadie](https://roadie.io) | -| AWS cli actions | [scaffolder-backend-module-aws](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-aws) | [Roadie](https://roadie.io) | -| Scaffolder .NET Actions | [plugin-scaffolder-dotnet-backend](https://www.npmjs.com/package/@plusultra/plugin-scaffolder-dotnet-backend) | [Alef Carlos](https://github.com/alefcarlos) | -| Scaffolder Git Actions | [plugin-scaffolder-git-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-git-actions) | [Drew Hill](https://github.com/arhill05) | -| Azure Pipeline Actions | [scaffolder-backend-module-azure-pipelines](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-pipelines) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) | +| Name | Package | Owner | +| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ | +| Yeoman | [plugin-scaffolder-backend-module-yeoman](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-yeoman) | [Backstage](https://backstage.io) | +| Cookiecutter | [plugin-scaffolder-backend-module-cookiecutter](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-cookiecutter) | [Backstage](https://backstage.io) | +| Rails | [plugin-scaffolder-backend-module-rails](https://www.npmjs.com/package/@backstage/plugin-scaffolder-backend-module-rails) | [Backstage](https://backstage.io) | +| HTTP requests | [scaffolder-backend-module-http-request](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-http-request) | [Roadie](https://roadie.io) | +| Utility actions | [scaffolder-backend-module-utils](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-utils) | [Roadie](https://roadie.io) | +| AWS cli actions | [scaffolder-backend-module-aws](https://www.npmjs.com/package/@roadiehq/scaffolder-backend-module-aws) | [Roadie](https://roadie.io) | +| Scaffolder .NET Actions | [plugin-scaffolder-dotnet-backend](https://www.npmjs.com/package/@plusultra/plugin-scaffolder-dotnet-backend) | [Alef Carlos](https://github.com/alefcarlos) | +| Scaffolder Git Actions | [plugin-scaffolder-git-actions](https://www.npmjs.com/package/@mdude2314/backstage-plugin-scaffolder-git-actions) | [Drew Hill](https://github.com/arhill05) | +| Azure Pipeline Actions | [scaffolder-backend-module-azure-pipelines](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-pipelines) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) | +| Azure Repository Actions | [scaffolder-backend-module-azure-repositories](https://www.npmjs.com/package/@parfuemerie-douglas/scaffolder-backend-module-azure-repositories) | [Parfümerie Douglas](https://github.com/Parfuemerie-Douglas) | Have fun! 🚀 diff --git a/docs/features/software-templates/writing-custom-field-extensions.md b/docs/features/software-templates/writing-custom-field-extensions.md index afef2c3372..b4c4d2d4ce 100644 --- a/docs/features/software-templates/writing-custom-field-extensions.md +++ b/docs/features/software-templates/writing-custom-field-extensions.md @@ -81,8 +81,7 @@ import { scaffolderPlugin, createScaffolderFieldExtension, } from '@backstage/plugin-scaffolder'; -import { MyCustomExtension } from './MyCustomExtension'; -import { myCustomValidation } from './validation'; +import { MyCustomExtension, myCustomValidation } from './MyCustomExtension'; export const MyCustomFieldExtension = scaffolderPlugin.provide( createScaffolderFieldExtension({ diff --git a/docs/features/software-templates/writing-custom-step-layouts.md b/docs/features/software-templates/writing-custom-step-layouts.md new file mode 100644 index 0000000000..69d61b93dc --- /dev/null +++ b/docs/features/software-templates/writing-custom-step-layouts.md @@ -0,0 +1,89 @@ +--- +id: writing-custom-step-layouts +title: Writing custom step layouts +description: How to override the default step form layout +--- + +Every form in each step rendered in the frontend uses the default form layout from [react-json-schema-form](https://react-jsonschema-form.readthedocs.io/). It is possible to override this behaviour by supplying a `ui:ObjectFieldTemplate` property for a particular step: + +```yaml +parameters: + - title: Fill in some steps + ui:ObjectFieldTemplate: TwoColumn +``` + +This is the same [field](https://react-jsonschema-form.readthedocs.io/en/latest/advanced-customization/custom-templates/#objectfieldtemplate) used by [react-json-schema-form](https://react-jsonschema-form.readthedocs.io/) but we need to add a couple of steps to ensure that the string value of `TwoColumn` above is resolved to a react component. + +## Registering a React component as a custom step layout + +The [createScaffolderLayout](https://backstage.io/docs/reference/plugin-scaffolder.createscaffolderlayout) function is used to mark a component as a custom step layout: + +```ts +import React from 'react'; +import { + createScaffolderLayout, + LayoutTemplate, + scaffolderPlugin, +} from '@backstage/plugin-scaffolder'; +import { Grid } from '@material-ui/core'; + +const TwoColumn: LayoutTemplate = ({ properties, description, title }) => { + const mid = Math.ceil(properties.length / 2); + + return ( + <> +

{title}

+

In two column layout!!

+ + {properties.slice(0, mid).map(prop => ( + + {prop.content} + + ))} + {properties.slice(mid).map(prop => ( + + {prop.content} + + ))} + + {description} + + ); +}; + +export const TwoColumnLayout = scaffolderPlugin.provide( + createScaffolderLayout({ + name: 'TwoColumn', + component: TwoColumn, + }), +); +``` + +After you have registered your component as a custom layout then you need to provide the `layouts` to the `ScaffolderPage`: + +```tsx +import { MyCustomFieldExtension } from './scaffolder/MyCustomExtension'; +import { TwoColumnLayout } from './components/scaffolder/customScaffolderLayouts'; + +const routes = ( + + ... + }> + + + + + ... + +); +``` + +## Using the custom step layout + +Any component that has been passed to the `ScaffolderPage` as children of the `ScaffolderLayouts` component can be used as a `ui:ObjectFieldTemplate` in your template file: + +```yaml +parameters: + - title: Fill in some steps + ui:ObjectFieldTemplate: TwoColumn +``` diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index a1cd50f271..826121bd02 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -258,6 +258,10 @@ use `ui:widget: password` or set some properties of `ui:backstage`: show: false # won't print any info about 'hidden' property on Review Step ``` +### Custom step layouts + +If you find that the default layout of the form used in a particular step does not meet your needs then you can supply your own [custom step layout](./writing-custom-step-layouts.md). + ### Remove sections or fields based on feature flags Based on feature flags you can hide sections or even only fields of your diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index ade73d3f68..477467bb88 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -51,14 +51,10 @@ create a subdirectory inside your current working directory. npx @backstage/create-app ``` -The wizard will ask you - -- The name of the app, which will also be the name of the directory -- The database type to use for the backend. For this guide, you'll be using the - SQLite option. +The wizard will ask you for the name of the app, which will also be the name of the directory

- Screenshot of the wizard asking for a name for the app, and a selection menu for the database. + Screenshot of the wizard asking for a name for the app.

### Run the Backstage app diff --git a/docs/integrations/aws-s3/discovery.md b/docs/integrations/aws-s3/discovery.md index 9e9e6ebede..748aeb2392 100644 --- a/docs/integrations/aws-s3/discovery.md +++ b/docs/integrations/aws-s3/discovery.md @@ -32,6 +32,11 @@ catalog: bucketName: sample-bucket prefix: prefix/ # optional region: us-east-2 # optional, uses the default region otherwise + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } ``` For simple setups, you can omit the provider ID at the config @@ -47,6 +52,11 @@ catalog: bucketName: sample-bucket prefix: prefix/ # optional region: us-east-2 # optional, uses the default region otherwise + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } ``` As this provider is not one of the default providers, you will first need to install @@ -69,10 +79,13 @@ const builder = await CatalogBuilder.create(env); builder.addEntityProvider( AwsS3EntityProvider.fromConfig(env.config, { logger: env.logger, + // optional: alternatively, use scheduler with schedule defined in app-config.yaml schedule: env.scheduler.createScheduledTaskRunner({ frequency: { minutes: 30 }, timeout: { minutes: 3 }, }), + // optional: alternatively, use schedule + scheduler: env.scheduler, }), ); ``` diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md index 6a420462c0..2156f60814 100644 --- a/docs/integrations/azure/discovery.md +++ b/docs/integrations/azure/discovery.md @@ -42,12 +42,17 @@ catalog: project: myproject repository: service-* # this will match all repos starting with service-* path: /catalog-info.yaml + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } anotherProviderId: # another identifier organization: myorg project: myproject repository: '*' # this will match all repos path: /src/*/catalog-info.yaml # this will search for files deep inside the /src folder - yetAotherProviderId: # guess, what? Another one :) + yetAnotherProviderId: # guess, what? Another one :) host: selfhostedazure.yourcompany.com organization: myorg project: myproject @@ -55,11 +60,20 @@ catalog: The parameters available are: -- `host:` Leave empty for Cloud hosted, otherwise set to your self-hosted instance host. -- `organization:` Your Organization slug (or Collection for on-premise users). Required. -- `project:` Your project slug. Required. -- `repository:` The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched. -- `path:` Where to find catalog-info.yaml files. Defaults to /catalog-info.yaml. +- **`host:`** _(optional)_ Leave empty for Cloud hosted, otherwise set to your self-hosted instance host. +- **`organization:`** Your Organization slug (or Collection for on-premise users). Required. +- **`project:`** Your project slug. Required. +- **`repository:`** _(optional)_ The repository name. Wildcards are supported as show on the examples above. If not set, all repositories will be searched. +- **`path:`** _(optional)_ Where to find catalog-info.yaml files. Defaults to /catalog-info.yaml. +- **`schedule`** _(optional)_: + - **`frequency`**: + How often you want the task to run. The system does its best to avoid overlapping invocations. + - **`timeout`**: + The maximum amount of time that a single task invocation can take. + - **`initialDelay`** _(optional)_: + The amount of time that should pass before the first invocation happens. + - **`scope`** _(optional)_: + `'global'` or `'local'`. Sets the scope of concurrency control. _Note:_ the path parameter follows the same rules as the search on Azure DevOps web interface. For more details visit the @@ -84,10 +98,13 @@ const builder = await CatalogBuilder.create(env); +builder.addEntityProvider( + AzureDevOpsEntityProvider.fromConfig(env.config, { + logger: env.logger, ++ // optional: alternatively, use scheduler with schedule defined in app-config.yaml + schedule: env.scheduler.createScheduledTaskRunner({ -+ frequency: Duration.fromObject({ minutes: 30 }), -+ timeout: Duration.fromObject({ minutes: 3 }), ++ frequency: { minutes: 30 }, ++ timeout: { minutes: 3 }, + }), ++ // optional: alternatively, use schedule ++ scheduler: env.scheduler, + }), +); ``` diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md index 6609707868..c5a17bb2c9 100644 --- a/docs/integrations/bitbucketCloud/discovery.md +++ b/docs/integrations/bitbucketCloud/discovery.md @@ -37,6 +37,7 @@ And then add the entity provider to your catalog builder: + builder.addEntityProvider( + BitbucketCloudEntityProvider.fromConfig(env.config, { + logger: env.logger, ++ // optional: alternatively, configure via app-config.yaml + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 30 }, + timeout: { minutes: 3 }, @@ -67,24 +68,38 @@ catalog: filters: # optional projectKey: '^apis-.*$' # optional; RegExp repoSlug: '^service-.*$' # optional; RegExp + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } workspace: workspace-name ``` > **Note:** It is possible but certainly not recommended to skip the provider ID level. > If you do so, `default` will be used as provider ID. -- **catalogPath** _(optional)_: +- **`catalogPath`** _(optional)_: Default: `/catalog-info.yaml`. Path where to look for `catalog-info.yaml` files. When started with `/`, it is an absolute path from the repo root. It supports values as allowed by the `path` filter/modifier [at Bitbucket Cloud's code search](https://confluence.atlassian.com/bitbucket/code-search-in-bitbucket-873876782.html#Search-Pathmodifier). -- **filters** _(optional)_: - - **projectKey** _(optional)_: +- **`filters`** _(optional)_: + - **`projectKey`** _(optional)_: Regular expression used to filter results based on the project key. - - **repoSlug** _(optional)_: + - **`repoSlug`** _(optional)_: Regular expression used to filter results based on the repo slug. -- **workspace**: +- **`schedule`** _(optional)_: + - **`frequency`**: + How often you want the task to run. The system does its best to avoid overlapping invocations. + - **`timeout`**: + The maximum amount of time that a single task invocation can take. + - **`initialDelay`** _(optional)_: + The amount of time that should pass before the first invocation happens. + - **`scope`** _(optional)_: + `'global'` or `'local'`. Sets the scope of concurrency control. +- **`workspace`**: Name of your organization account/workspace. If you want to add multiple workspaces, you need to add one provider config each. diff --git a/docs/integrations/bitbucketServer/discovery.md b/docs/integrations/bitbucketServer/discovery.md index 57e52d1dae..c4288b33ec 100644 --- a/docs/integrations/bitbucketServer/discovery.md +++ b/docs/integrations/bitbucketServer/discovery.md @@ -37,10 +37,13 @@ And then add the entity provider to your catalog builder: + builder.addEntityProvider( + BitbucketServerEntityProvider.fromConfig(env.config, { + logger: env.logger, ++ // optional: alternatively, use scheduler with schedule defined in app-config.yaml + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 30 }, + timeout: { minutes: 3 }, + }), ++ // optional: alternatively, use schedule ++ scheduler: env.scheduler, + }), + ); @@ -66,19 +69,33 @@ catalog: filters: # optional projectKey: '^apis-.*$' # optional; RegExp repoSlug: '^service-.*$' # optional; RegExp + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } ``` -- **host**: +- **`host`**: The host of the Bitbucket Server instance, **note**: the host needs to registered as an integration as well, see [location](locations.md). - **`catalogPath`** _(optional)_: Default: `/catalog-info.yaml`. Path where to look for `catalog-info.yaml` files. When started with `/`, it is an absolute path from the repo root. -- **filters** _(optional)_: +- **`filters`** _(optional)_: - **`projectKey`** _(optional)_: Regular expression used to filter results based on the project key. - - **repoSlug** _(optional)_: + - **`repoSlug`** _(optional)_: Regular expression used to filter results based on the repo slug. +- **`schedule`** _(optional)_: + - **`frequency`**: + How often you want the task to run. The system does its best to avoid overlapping invocations. + - **`timeout`**: + The maximum amount of time that a single task invocation can take. + - **`initialDelay`** _(optional)_: + The amount of time that should pass before the first invocation happens. + - **`scope`** _(optional)_: + `'global'` or `'local'`. Sets the scope of concurrency control. ## Custom location processing @@ -93,18 +110,15 @@ repository. ```typescript const provider = BitbucketServerEntityProvider.fromConfig(env.config, { logger: env.logger, - schedule: env.scheduler.createScheduledTaskRunner({ - frequency: { minutes: 30 }, - timeout: { minutes: 3 }, - }), + schedule: env.scheduler, parser: async function* customLocationParser(options: { - location: LocationSpec, - client: BitbucketServerClient, + location: LocationSpec; + client: BitbucketServerClient; }) { // Custom logic for interpreting the matching repository // See defaultBitbucketServerLocationParser for an example - } -); + }, +}); ``` ## Alternative diff --git a/docs/integrations/gerrit/discovery.md b/docs/integrations/gerrit/discovery.md index 04fa3dac2a..7ed05d2b3b 100644 --- a/docs/integrations/gerrit/discovery.md +++ b/docs/integrations/gerrit/discovery.md @@ -32,10 +32,13 @@ const builder = await CatalogBuilder.create(env); builder.addEntityProvider( GerritEntityProvider.fromConfig(env.config, { logger: env.logger, + // optional: alternatively, use scheduler with schedule defined in app-config.yaml schedule: env.scheduler.createScheduledTaskRunner({ frequency: { minutes: 30 }, timeout: { minutes: 3 }, }), + // optional: alternatively, use schedule + scheduler: env.scheduler, }), ); ``` @@ -54,6 +57,11 @@ catalog: host: gerrit-your-company.com branch: master # Optional query: 'state=ACTIVE&prefix=webapps' + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } backend: host: gerrit-your-company.com branch: master # Optional @@ -62,8 +70,8 @@ catalog: The provider configuration is composed of three parts: -- host, the host of the Gerrit integration to use. -- branch, the branch where we will look for catalog entities (defaults to "master"). -- query, this string is directly used as the argument to the "List Project" API. - Typically you will want to have some filter here to exclude projects that will +- **`host`**: the host of the Gerrit integration to use. +- **`branch`** _(optional)_: the branch where we will look for catalog entities (defaults to "master"). +- **`query`**: this string is directly used as the argument to the "List Project" API. + Typically, you will want to have some filter here to exclude projects that will never contain any catalog files. diff --git a/docs/integrations/github/discovery.md b/docs/integrations/github/discovery.md index 6966753d68..092a171223 100644 --- a/docs/integrations/github/discovery.md +++ b/docs/integrations/github/discovery.md @@ -12,7 +12,7 @@ The GitHub integration has a discovery provider for discovering catalog entities within a GitHub organization. The provider will crawl the GitHub organization and register entities matching the configured path. This can be useful as an alternative to static locations or manually adding things to the -catalog. This is the prefered method for ingesting entities into the catalog. +catalog. This is the preferred method for ingesting entities into the catalog. ## Installation @@ -30,19 +30,22 @@ And then add the entity provider to your catalog builder: ```diff // In packages/backend/src/plugins/catalog.ts -+ import { GitHubEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; ++ import { GithubEntityProvider } from '@backstage/plugin-catalog-backend-module-github'; export default async function createPlugin( env: PluginEnvironment, ): Promise { const builder = await CatalogBuilder.create(env); + builder.addEntityProvider( -+ GitHubEntityProvider.fromConfig(env.config, { ++ GithubEntityProvider.fromConfig(env.config, { + logger: env.logger, ++ // optional: alternatively, use scheduler with schedule defined in app-config.yaml + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 30 }, + timeout: { minutes: 3 }, + }), ++ // optional: alternatively, use schedule ++ scheduler: env.scheduler, + }), + ); @@ -55,7 +58,7 @@ And then add the entity provider to your catalog builder: To use the discovery provider, you'll need a GitHub integration [set up](locations.md) with either a [Personal Access Token](../../getting-started/configuration.md#setting-up-a-github-integration) or [GitHub Apps](./github-apps.md). -Then you can add a github config to the catalog providers configuration: +Then you can add a `github` config to the catalog providers configuration: ```yaml catalog: @@ -68,6 +71,11 @@ catalog: filters: branch: 'main' # string repository: '.*' # Regex + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } customProviderId: organization: 'new-org' # string catalogPath: '/custom/path/catalog-info.yaml' # string @@ -96,6 +104,10 @@ catalog: topic: include: ['backstage-include'] # optional array of strings exclude: ['experiments'] # optional array of strings + enterpriseProviderId: + host: ghe.example.net + organization: 'backstage' # string + catalogPath: '/catalog-info.yaml' # string ``` This provider supports multiple organizations via unique provider IDs. @@ -107,24 +119,35 @@ This provider supports multiple organizations via unique provider IDs. Default: `/catalog-info.yaml`. Path where to look for `catalog-info.yaml` files. You can use wildcards - `*` or `**` - to search the path and/or the filename -- **filters** _(optional)_: - - **branch** _(optional)_: +- **`filters`** _(optional)_: + - **`branch`** _(optional)_: String used to filter results based on the branch name. - - **repository** _(optional)_: + - **`repository`** _(optional)_: Regular expression used to filter results based on the repository name. - - **topic** _(optional)_: + - **`topic`** _(optional)_: Both of the filters below may be used at the same time but the exclusion filter has the highest priority. In the example above, a repository with the `backstage-include` topic would still be excluded if it were also carrying the `experiments` topic. - - **include** _(optional)_: - An array of strings used to filter in results based on their associated Github topics. + - **`include`** _(optional)_: + An array of strings used to filter in results based on their associated GitHub topics. If configured, only repositories with one (or more) topic(s) present in the inclusion filter will be ingested - - **exclude** _(optional)_: - An array of strings used to filter out results based on their associated Github topics. + - **`exclude`** _(optional)_: + An array of strings used to filter out results based on their associated GitHub topics. If configured, all repositories _except_ those with one (or more) topics(s) present in the exclusion filter will be ingested. -- **organization**: +- **`host`** _(optional)_: + The hostname of your GitHub Enterprise instance. It must match a host defined in [integrations.github](locations.md). +- **`organization`**: Name of your organization account/workspace. If you want to add multiple organizations, you need to add one provider config each. +- **`schedule`** _(optional)_: + - **`frequency`**: + How often you want the task to run. The system does its best to avoid overlapping invocations. + - **`timeout`**: + The maximum amount of time that a single task invocation can take. + - **`initialDelay`** _(optional)_: + The amount of time that should pass before the first invocation happens. + - **`scope`** _(optional)_: + `'global'` or `'local'`. Sets the scope of concurrency control. ## GitHub API Rate Limits diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index df5aadcab4..dc0b911b3d 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -25,6 +25,11 @@ catalog: group: example-group # Optional. Group and subgroup (if needed) to look for repositories. If not present the whole project will be scanned entityFilename: catalog-info.yaml # Optional. Defaults to `catalog-info.yaml` projectPattern: /[\s\S]*/ # Optional. Filters found projects based on provided patter. Defaults to `/[\s\S]*/`, what means to not filter anything + schedule: # optional; same options as in TaskScheduleDefinition + # supports cron, ISO duration, "human duration" as used in code + frequency: { minutes: 30 } + # supports ISO duration, "human duration" as used in code + timeout: { minutes: 3 } ``` As this provider is not one of the default providers, you will first need to install @@ -47,10 +52,13 @@ const builder = await CatalogBuilder.create(env); builder.addEntityProvider( ...GitlabDiscoveryEntityProvider.fromConfig(env.config, { logger: env.logger, + // optional: alternatively, use scheduler with schedule defined in app-config.yaml schedule: env.scheduler.createScheduledTaskRunner({ frequency: { minutes: 30 }, timeout: { minutes: 3 }, }), + // optional: alternatively, use schedule + scheduler: env.scheduler, }), ); ``` diff --git a/docs/overview/support.md b/docs/overview/support.md index 5122a1c9ff..662a37c6ae 100644 --- a/docs/overview/support.md +++ b/docs/overview/support.md @@ -6,6 +6,7 @@ description: Support and Community Details and Links - [Discord chatroom](https://discord.gg/MUpMjP2) - Get support or discuss the project. +- [Stack Overflow](https://stackoverflow.com/questions/tagged/backstage) - Browse or ask questions on Stack Overflow. - [Good First Issues](https://github.com/backstage/backstage/contribute) - Start here if you want to contribute. - [RFCs](https://github.com/backstage/backstage/labels/rfc) - Help shape the diff --git a/docs/permissions/custom-rules.md b/docs/permissions/custom-rules.md index 1408d732d3..9407dc407a 100644 --- a/docs/permissions/custom-rules.md +++ b/docs/permissions/custom-rules.md @@ -14,12 +14,18 @@ Plugins should export a rule factory that provides type-safety that ensures comp import type { Entity } from '@backstage/plugin-catalog-model'; import { createCatalogPermissionRule } from '@backstage/plugin-catalog-backend/alpha'; import { createConditionFactory } from '@backstage/plugin-permission-node'; +import { z } from 'zod'; export const isInSystemRule = createCatalogPermissionRule({ name: 'IS_IN_SYSTEM', description: 'Checks if an entity is part of the system provided', resourceType: 'catalog-entity', - apply: (resource: Entity, systemRef: string) => { + paramsSchema: z.object({ + systemRef: z + .string() + .describe('SystemRef to check the resource is part of'), + }), + apply: (resource: Entity, { systemRef }) => { if (!resource.relations) { return false; } @@ -28,9 +34,9 @@ export const isInSystemRule = createCatalogPermissionRule({ .filter(relation => relation.type === 'partOf') .some(relation => relation.targetRef === systemRef); }, - toQuery: (systemRef: string) => ({ + toQuery: ({ systemRef }) => ({ key: 'relations.partOf', - value: systemRef, + values: [systemRef], }), }); @@ -85,14 +91,14 @@ class TestPermissionPolicy implements PermissionPolicy { if (isResourcePermission(request.permission, 'catalog-entity')) { return createCatalogConditionalDecision( request.permission, -- catalogConditions.isEntityOwner( -- user?.identity.ownershipEntityRefs ?? [], -- ), +- catalogConditions.isEntityOwner({ +- claims: user?.identity.ownershipEntityRefs ?? [], +- }), + { + anyOf: [ -+ catalogConditions.isEntityOwner( -+ user?.identity.ownershipEntityRefs ?? [] -+ ), ++ catalogConditions.isEntityOwner({ ++ claims: user?.identity.ownershipEntityRefs ?? [] ++ }), + isInSystem('interviewing') + ] + } diff --git a/docs/permissions/frontend-integration.md b/docs/permissions/frontend-integration.md index 2a429ea120..d7302f9083 100644 --- a/docs/permissions/frontend-integration.md +++ b/docs/permissions/frontend-integration.md @@ -17,7 +17,7 @@ If your Backstage permission policy may return a `DENY` for users requesting the ... -+ import { PermissionedRoute } from '@backstage/plugin-permission-react'; ++ import { RequirePermission } from '@backstage/plugin-permission-react'; + import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; ... diff --git a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md index 5bace9ad7b..8913b6d438 100644 --- a/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md +++ b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md @@ -84,6 +84,7 @@ Create a new `plugins/todo-list-backend/src/service/rules.ts` file and append th ```typescript import { makeCreatePermissionRule } from '@backstage/plugin-permission-node'; import { TODO_LIST_RESOURCE_TYPE } from '@internal/plugin-todo-list-common'; +import { z } from 'zod'; import { Todo, TodoFilter } from './todos'; export const createTodoListPermissionRule = makeCreatePermissionRule< @@ -95,10 +96,13 @@ export const isOwner = createTodoListPermissionRule({ name: 'IS_OWNER', description: 'Should allow only if the todo belongs to the user', resourceType: TODO_LIST_RESOURCE_TYPE, - apply: (resource: Todo, userId: string) => { + paramsSchema: z.object({ + userId: z.string().describe('User ID to match on the resource') + }) + apply: (resource: Todo, { userId }) => { return resource.author === userId; }, - toQuery: (userId: string) => { + toQuery: ({ userId }) => { return { property: 'author', values: [userId], @@ -223,7 +227,9 @@ Let's go back to the permission policy's handle function and try to authorize ou + if (isPermission(request.permission, todoListUpdatePermission)) { + return createTodoListConditionalDecision( + request.permission, -+ todoListConditions.isOwner(user?.identity.userEntityRef), ++ todoListConditions.isOwner({ ++ userId: user?.identity.userEntityRef ?? '', ++ }), + ); + } + diff --git a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md index c813ba594c..8ddcffdcd9 100644 --- a/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md +++ b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md @@ -144,7 +144,9 @@ import { + ) { return createTodoListConditionalDecision( request.permission, - todoListConditions.isOwner(user?.identity.userEntityRef), + todoListConditions.isOwner({ + userId: user?.identity.userEntityRef + }), ); } ``` diff --git a/docs/permissions/writing-a-policy.md b/docs/permissions/writing-a-policy.md index c29424241c..7e741ae48f 100644 --- a/docs/permissions/writing-a-policy.md +++ b/docs/permissions/writing-a-policy.md @@ -70,9 +70,9 @@ Let's change the policy to the following: - }; + return createCatalogConditionalDecision( + request.permission, -+ catalogConditions.isEntityOwner( -+ user?.identity.ownershipEntityRefs ?? [], -+ ), ++ catalogConditions.isEntityOwner({ ++ claims: user?.identity.ownershipEntityRefs ?? [], ++ }), + ); } @@ -119,9 +119,9 @@ class TestPermissionPolicy implements PermissionPolicy { + if (isResourcePermission(request.permission, 'catalog-entity')) { return createCatalogConditionalDecision( request.permission, - catalogConditions.isEntityOwner( - user?.identity.ownershipEntityRefs ?? [], - ), + catalogConditions.isEntityOwner({ + claims: user?.identity.ownershipEntityRefs ?? [], + }), ); } diff --git a/docs/plugins/backend-plugin.md b/docs/plugins/backend-plugin.md index c6f4a50e60..3eddb9e1c2 100644 --- a/docs/plugins/backend-plugin.md +++ b/docs/plugins/backend-plugin.md @@ -13,10 +13,10 @@ A new, bare-bones backend plugin package can be created by issuing the following command in your Backstage repository root: ```sh -yarn create-plugin --backend +yarn new --select backend-plugin ``` -Please also see the `--help` flag for the `create-plugin` command for some +Please also see the `--help` flag for the `new` command for some further options that are available, notably the `--scope` and `--no-private` flags that control naming and publishing of the newly created package. Your repo root `package.json` will probably also have some default values already set up diff --git a/docs/plugins/call-existing-api.md b/docs/plugins/call-existing-api.md index 5dbf7988c9..e91506623b 100644 --- a/docs/plugins/call-existing-api.md +++ b/docs/plugins/call-existing-api.md @@ -132,7 +132,7 @@ router.use('/summary', async (req, res) => { ]).then(async ([frobs, flerps, thunk]) => { return computeAggregate(await frobs.json(), await flerps.json(), thunk); }); - res.status(200).send(agg); + res.status(200).json(agg); }); ``` diff --git a/docs/plugins/create-a-plugin.md b/docs/plugins/create-a-plugin.md index ef415d107a..29607af140 100644 --- a/docs/plugins/create-a-plugin.md +++ b/docs/plugins/create-a-plugin.md @@ -15,7 +15,7 @@ invoking the from the root of your project. ```bash -yarn create-plugin +yarn new --select plugin ``` ![](../assets/getting-started/create-plugin_output.png) diff --git a/docs/releases/v1.7.0-changelog.md b/docs/releases/v1.7.0-changelog.md new file mode 100644 index 0000000000..3653484952 --- /dev/null +++ b/docs/releases/v1.7.0-changelog.md @@ -0,0 +1,2589 @@ +# Release v1.7.0 + +## @backstage/cli@0.20.0 + +### Minor Changes + +- f368ad7279: **BREAKING**: Bumped `jest`, `jest-runtime`, and `jest-environment-jsdom` to v29. This is up from v27, so check out both the [v28](https://jestjs.io/docs/28.x/upgrading-to-jest28) and [v29](https://jestjs.io/docs/upgrading-to-jest29) (later [here](https://jestjs.io/docs/29.x/upgrading-to-jest29)) migration guides. + + Particular changes that where encountered in the main Backstage repo are: + + - The updated snapshot format. + - `jest.useFakeTimers('legacy')` is now `jest.useFakeTimers({ legacyFakeTimers: true })`. + - Error objects collected by `withLogCollector` from `@backstage/test-utils` are now objects with a `detail` property rather than a string. + +### Patch Changes + +- 78d5eb299e: Tweak the Jest Caching loader to only operate when in `watch` mode + +- 9c595302cb: Normalize on winston version ^3.2.1 + +- 24b40140c4: Treat files in `__testUtils__` and `__mocks__` directories as test files for linting + purposes. + + Updates the parts of the eslint configuration generator that specify which files + should be treated as test code to include any files under two additional + directories: + + - `__mocks__`: this is the directory used by Jest[0] for mock code. + - `__testUtils__`: a suggested location for utility code executed only when + running tests. + + [0]: https://jestjs.io/docs/manual-mocks#mocking-user-modules + +- 3e309107ca: Updated fallback versions of dependencies in all templates. + +- 292a088807: Added a new `repo test` command. + +- ba63cae41c: Updated lockfile parsing to have better support for Yarn 3. + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` + +- 2dddb32fea: Switched the Jest transform for YAML files to use a custom one available at `@backstage/cli/config/jestYamlTransform.js`. + +- a541a3a78a: Switch to upfront resolution of `swc-loader` in Webpack config. + +- cfb3598410: Removed `tsx` and `jsx` as supported extensions in backend packages. For most + repos, this will not have any effect. But if you inadvertently had added some + `tsx`/`jsx` files to your backend package, you may now start to see `code: 'MODULE_NOT_FOUND'` errors when launching the backend locally. The reason for + this is that the offending files get ignored during transpilation. Hence, the + importing file can no longer find anything to import. + + The fix is to rename any `.tsx` files in your backend packages to `.ts` instead, + or `.jsx` to `.js`. + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + - @backstage/config-loader@1.1.5 + - @backstage/errors@1.1.2 + - @backstage/release-manifests@0.0.6 + - @backstage/types@1.0.0 + +## @backstage/plugin-auth-backend@0.17.0 + +### Minor Changes + +- e2dc42e9f0: Google OAuth refresh tokens will now be revoked on logout by calling Google's API +- 5fa831ce55: CookieConfigurer can optionally return the `SameSite` cookie attribute. + CookieConfigurer now requires an additional argument `appOrigin` - the origin URL of the app - which is used to calculate the `SameSite` attribute. + defaultCookieConfigurer returns the `SameSite` attribute which defaults to `Lax`. In cases where an auth-backend is running on a different domain than the App, `SameSite=None` is used - but only for secure contexts. This is so that cookies can be included in third-party requests. + + OAuthAdapterOptions has been modified to require additional arguments, `baseUrl`, and `cookieConfigurer`. + OAuthAdapter now resolves cookie configuration using its supplied CookieConfigurer for each request to make sure that the proper attributes always are set. + +### Patch Changes + +- b5c126010c: Auth0 provider now supports optional `connection` and `connectionScope` parameters to configure social identity providers. +- 8c6ec175bf: Fix GitLab provider setup so that it supports GitLab installations with a path in the URL. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-bazaar-backend@0.2.0 + +### Minor Changes + +- 8554533546: **BREAKING** The bazaar-backend `createRouter` now requires that the `identityApi` is passed to the router. + + These changes are **required** to `packages/backend/src/plugins/bazaar.ts` + + The user entity ref is now added to the members table and is taken from the requesting user using the `identityApi`. + + ```diff + import { PluginEnvironment } from '../types'; + import { createRouter } from '@backstage/plugin-bazaar-backend'; + import { Router } from 'express'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + logger: env.logger, + config: env.config, + database: env.database, + + identity: env.identity, + }); + } + ``` + +### Patch Changes + +- f7c2855d76: Router now also has endpoint `getLatestProjects` that takes a limit of projects as prop. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-test-utils@0.1.29 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-bitbucket-cloud-common@0.2.0 + +### Minor Changes + +- ad74723fbf: Update Bitbucket Cloud models to latest OAS version. + + The latest specification contained some BREAKING CHANGES + due to removed fields. + + All of these fields are not used at other plugins, though. + Therefore, this change has no impact on other modules here. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.3.2 + +## @backstage/plugin-catalog@1.6.0 + +### Minor Changes + +- d558f41d3a: Added new column `Label` to `CatalogTable.columns`, this new column allows you make use of labels from metadata. + For example: category and visibility are type of labels associated with API entity illustrated below. + + YAML code snippet for API entity + + ```yaml + apiVersion: backstage.io/v1alpha1 + kind: API + metadata: + name: sample-api + description: API for sample + links: + - url: http://localhost:8080/swagger-ui.html + title: Swagger UI + tags: + - http + labels: + category: legacy + visibility: protected + ``` + + Consumers can customise columns to include label column and show in api-docs list + + ```typescript + const columns = [ + CatalogTable.columns.createNameColumn({ defaultKind: 'API' }), + CatalogTable.columns.createLabelColumn('category', { title: 'Category' }), + CatalogTable.columns.createLabelColumn('visibility', { + title: 'Visibility', + defaultValue: 'public', + }), + ]; + ``` + +### Patch Changes + +- 4efadb6968: Implemented the visual parts of `EntityKindPicker` so that it can be shown alongside the other filters on the left side of your catalog pages. +- 182000c663: Added `emptyContent` property to CatalogTable and DefaultCatalogPage to support customization of the Catalog Table. +- e89e1f614d: Added support for copy entity URL in entity page context menu +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend@1.5.0 + +### Minor Changes + +- b2e6cb6acf: Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. + + Moved the following types from this package to `@backstage/plugin-catalog-backend`. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- eb25f7e12d: The exported permission rules and the API of `createCatalogConditionalDecision` have changed to reflect the breaking changes made to the `PermissionRule` type. Note that all involved types are exported from `@backstage/plugin-catalog-backend/alpha` + +### Patch Changes + +- 8cb6e10105: Fixed a bug where entities provided without a location key would always replace existing entities, rather than updating them. +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- 63296ebcd4: Allow Placeholder value to be any value, not only string. +- 74022e0163: Make sure to stitch entities correctly after deletion, to ensure that their relations are updated. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-scaffolder-common@1.2.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-import@0.9.0 + +### Minor Changes + +- b2e6cb6acf: **Breaking** + Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials. + + Add the following to your `CatalogBuilder` to have the repo URL ingestion working again. + + ```ts + // catalog.ts + import { GithubLocationAnalyzer } from '@backstage/plugin-catalog-backend-module-github'; + ... + builder.addLocationAnalyzers( + new GithubLocationAnalyzer({ + discovery: env.discovery, + config: env.config, + }), + ); + ... + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/catalog-client@1.1.1 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-catalog-node@1.2.0 + +### Minor Changes + +- 404366c853: Deprecated the `LocationSpec` type. It got moved from this package to the `@backstage/plugin-catalog-common` so make sure imports are updated. + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/catalog-client@1.1.1 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-react@1.2.0 + +### Minor Changes + +- 4efadb6968: Implemented the visual parts of `EntityKindPicker` so that it can be shown alongside the other filters on the left side of your catalog pages. + +### Patch Changes + +- 7939e743f5: Added two new `EntityRefLinks` props, the first being `getTitle` that allows for customization of the title used for each link. The second one is `fetchEntities`, which triggers a fetching of all entities so that the full entity definition is available in the `getTitle` callback. +- e9e532ebd8: Fixed issue where the query kind parameter is not honored +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-permission-react@0.4.6 + +## @backstage/plugin-permission-common@0.7.0 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-permission-node@0.7.0 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- 9335ad115e: Exported types for the .metadata endpoint of the permission router +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-playlist-backend@0.2.0 + +### Minor Changes + +- eb25f7e12d: **BREAKING** The exported permission rules have changed to reflect the breaking changes made to the PermissionRule type. + + For example, the `playlistConditions.isOwner` API has changed from: + + ```ts + playlistConditions.isOwner(['user:default/me', 'group:default/owner']); + ``` + + to: + + ```ts + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/backend-test-utils@0.1.29 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-playlist-common@0.1.1 + +## @backstage/plugin-scaffolder@1.7.0 + +### Minor Changes + +- f13d5f3f06: Add support for link to TechDocs and other links defined in template entity specification metadata on TemplateCard +- 05f22193c5: EntityPickers now support flags to control when to include default namespace + in result + +### Patch Changes + +- e4f0a96424: Making the description of the GitLab repoUrl owner field more clearer by focusing it refers to the GitLab namespace. +- 92e490d6b4: Make the `/next` scaffolder work end to end with the old `TaskPage` view +- 8220f2fd83: Support custom layouts in dry run editor +- 1047baa926: Bump to `react-jsonschema-form@v5-beta` for the `NextRouter` under `@alpha` exports +- 98ae18b68f: Fixed a bug where the `allowed*` values for the `RepoUrlPicker` would be reset on render. +- 8960d83013: Add support for `allowedOrganizations` and `allowedOwners` to the `AzureRepoPicker`. +- b681275e69: Ignore .git directories in Template Editor, increase upload limit for dry-runs to 10MB. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-scaffolder-common@1.2.1 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-react@0.4.6 + +## @backstage/plugin-scaffolder-backend@1.7.0 + +### Minor Changes + +- 253453fa14: Added a new property called `additionalTemplateGlobals` which allows you to add global functions to the scaffolder nunjucks templates. +- 17ff77154c: Update the `github:publish` action to allow passing whether pull + requests must be up to date with the default branch before merging. +- 304305dd20: Add `allowAutoMerge` option for `publish:github` action +- 694bfe2d61: Add functionality to shutdown scaffolder tasks if they are stale +- a8e9848479: Added optional `sourcePath` parameter to `publish:gitlab:merge-request` action, `targetPath` is now optional and falls back to current workspace path. + +### Patch Changes + +- 489621f613: Switching off duplicated timestamp in case of logging via task logger in a custom action +- 4880d43e25: Fixed setting default branch for Bitbucket Server +- b681275e69: Ignore .git directories in Template Editor, increase upload limit for dry-runs to 10MB. +- a35a27df70: Updated the `moduleId` of the experimental module export. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/backend-tasks@0.3.6 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-scaffolder-common@1.2.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search-backend@1.1.0 + +### Minor Changes + +- 16c853a6ed: Be less restrictive with unknown keys on query endpoint +- a799972bb1: The query received by search engines now contains a property called `pageLimit`, it specifies how many results to return per page when sending a query request to the search backend. + + Example: + _Returns up to 30 results per page_ + + GET /query?pageLimit=30 + + The search backend validates the page limit and this value must not exceed 100, but it doesn't set a default value for the page limit parameter, it leaves it up to each search engine to set this, so Lunr, Postgres and Elastic Search set 25 results per page as a default value. + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search-common@1.1.0 + +### Minor Changes + +- a799972bb1: There is a new property called `pageLimit` on the `SearchQuery` interface that specifies how many results should be returned per page. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-search-react@1.2.0 + +### Minor Changes + +- 4ed1fa2480: The search query state now has an optional `pageLimit` property that determines how many results will be requested per page, it defaults to 25. + + Examples: + _Basic_ + + ```jsx + + {results => { + // Item rendering logic is omitted + }} + + ``` + + _With context_ + + ```jsx + + + {results => { + // Item rendering logic is omitted + }} + + + ``` + +- bed5a1dc6e: The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. + Possibility to provide a custom no results component if needed through the `noResultsComponent` property. + + Examples: + + _Rendering a custom no results component_ + + ```jsx + } + /> + ``` + + _Disable rendering when there are no results_ + + ```jsx + + ``` + +- 3de4bd4f19: A `` component was created for limiting the number of results shown per search page. Use this new component to give users options to select how many search results they want to display per page. The default options are 10, 25, 50, 100. + + See examples below: + + _Basic_ + + ```jsx + import React, { useState } from 'react'; + import { Grid } from '@material-ui/core'; + import { Page, Header, Content, Lifecycle } from '@backstage/core-components'; + import { + SearchBarBase, + SearchPaginationBase, + SearchResultList, + } from '@backstage/plugin-search-react'; + + const SearchPage = () => { + const [term, setTerm] = useState(''); + const [pageLimit, setPageLimit] = useState(25); + const [pageCursor, setPageCursor] = useState(); + + return ( + +
} /> + + + + + + + + + + + + + + + ); + }; + ``` + + _With context_ + + ```jsx + import React from 'react'; + import { Grid } from '@material-ui/core'; + import { Page, Header, Content, Lifecycle } from '@backstage/core-components'; + import { + SearchBar, + SearchResult, + SearchPagination, + SearchResultListLayout, + SearchContextProvider, + DefaultResultListItem, + } from '@backstage/plugin-search-react'; + + const SearchPage = () => ( + + +
} /> + + + + + + + + + + + {({ results }) => ( + ( + + )} + /> + )} + + + + + + + ); + ``` + +- 6faaa05626: The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. + Possibility to provide a custom no results component if needed through the `noResultsComponent` property. + + Examples: + + _Rendering a custom no results component_ + + ```jsx + } + title="Documentation" + noResultsComponent={} + /> + ``` + + _Disable rendering when there are no results_ + + ```jsx + } + title="Documentation" + disableRenderingWithNoResults + /> + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-techdocs-backend@1.4.0 + +### Minor Changes + +- 7ced1b4076: Add optional `catalogClient` argument to `createRoute` parameters + +### Patch Changes + +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-techdocs-node@1.4.1 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-user-settings@0.5.0 + +### Minor Changes + +- 5543e86660: **BREAKING**: The `apiRef` passed to `ProviderSettingsItem` now needs to + implement `ProfileInfoApi & SessionApi`, rather than just the latter. This is + unlikely to have an effect on most users though, since the builtin auth + providers generally implement both. + + Fixed settings page showing providers as logged out when the user is using more + than one provider, and displayed some additional login information. + +### Patch Changes + +- 06d61d1266: Handle errors that may occur when the user logs out +- 44c9a95dcf: Prevent `.set()` to execute a request to the StorageClient if the user is `guest` +- 174f02a00a: Update installation instructions +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/app-defaults@1.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6 + +## @backstage/backend-app-api@0.2.2 + +### Patch Changes + +- 0027a749cd: Added possibility to configure index plugin of the HTTP router service. +- 45857bffae: Properly export `rootLoggerFactory`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/errors@1.1.2 + +## @backstage/backend-common@0.15.2 + +### Patch Changes + +- e8d7976413: Added back support for when no branch is provided for the Bitbucket Server `UrlReader` +- c44cf412de: Fix BitBucket server integration +- c31f7cdfbc: Fixed an issue where `getClient()` for a `pluginId` would return different clients and not share them +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + - @backstage/config-loader@1.1.5 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/backend-defaults@0.1.2 + +### Patch Changes + +- 96d288a02d: Added root logger service to the set of default services. +- Updated dependencies + - @backstage/backend-app-api@0.2.2 + - @backstage/backend-plugin-api@0.1.3 + +## @backstage/backend-plugin-api@0.1.3 + +### Patch Changes + +- 28377dc89f: Allow interfaces to be used for inferred option types. +- a35a27df70: Added documentation for `createBackendModule`, with guidelines for choosing a module ID. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/config@1.0.3 + +## @backstage/backend-tasks@0.3.6 + +### Patch Changes + +- d4fea86ea3: Added new function `readTaskScheduleDefinitionFromConfig` to read `TaskScheduleDefinition` (aka. schedule) from the `Config`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/backend-test-utils@0.1.29 + +### Patch Changes + +- 72549952d1: Fixed handling of root scoped services in `startTestBackend`. +- e91e8e9c55: Increased test database max connection pool size to reduce the risk of resource exhaustion. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/cli@0.20.0 + - @backstage/backend-app-api@0.2.2 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/config@1.0.3 + +## @backstage/catalog-client@1.1.1 + +### Patch Changes + +- 4f2ac624b4: Renamed argument in `validateEntity` from `location` to `locationRef` +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/errors@1.1.2 + +## @backstage/catalog-model@1.1.2 + +### Patch Changes + +- 6f3b8d0962: Defer `ajv` compilation of schema validators to improve module-import performance +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/codemods@0.1.40 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/config@1.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/config-loader@1.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/core-app-api@1.1.1 + +### Patch Changes + +- 27e6404aba: Fixed a bug where gathered index routes would fail to bind routable extensions. This would typically show up when placing a routable extension in the entity page overview tab. +- Updated dependencies + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-components@0.11.2 + +### Patch Changes + +- bde1e8c8e2: Added `curve` prop to the `DependencyGraph` component to select the type of layout +- 882101cd9b: Deep-import LightAsync component to improve module-import speed +- 0d0f30d87e: Fixed layout for core-components docs where table was broken by splitting with code sample +- Updated dependencies + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-plugin-api@1.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/create-app@0.4.32 + +### Patch Changes + +- 58c2264325: Newly created Backstage repositories now use the stable version 6 of + `react-router`, just like the main repo does. Please let us know if you find any + issues with this. + + Migrating to the stable version of `react-router` is optional for the time + being. But if you want to do the same for your existing repository, please + follow [this + guide](https://backstage.io/docs/tutorials/react-router-stable-migration). + +- e05e0f021b: Update versions of packages used in the create-app template, to match those in the main repo + +- 01dff06be4: Leverage cache mounts in Dockerfile during `yarn install ...` and `apt-get ...` commands to speed up repeated builds. + +- 90616bcaa6: Add the new search pagination component to the search page template. + +- 7c6306fc8a: Initializes a git repository when creating an app using @packages/create-app + +- 52f25858a8: Added `*.session.sql` Visual Studio Code database functionality files to `.gitignore` in the default template. This is optional but potentially helpful if your developers use Visual Studio Code; you can add a line with that exact value to your own root `.gitignore` if you want the same. + +- 6d00e80146: Updated the root `test` scripts to use `backstage-cli repo test`. + + To apply this change to an existing app, make the following change to the root `package.json`: + + ```diff + - "test": "backstage-cli test", + - "test:all": "lerna run test -- --coverage", + + "test": "backstage-cli repo test", + + "test:all": "backstage-cli repo test --coverage", + ``` + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/dev-utils@1.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/test-utils@1.2.1 + - @backstage/app-defaults@1.0.7 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/errors@1.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/integration@1.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/integration-react@1.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @techdocs/cli@1.2.2 + +### Patch Changes + +- 0b2a30dead: fixing techdocs-cli Docker client creation + + Docker client does not need to be created when --no-docker + option is provided. + + If you had DOCKER_CERT_PATH environment variable defined + the Docker client was looking for certificates + and breaking techdocs-cli generate command even with --no-docker + option. + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-techdocs-node@1.4.1 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + +## @backstage/test-utils@1.2.1 + +### Patch Changes + +- e05e0f021b: Align on the version of `@material-ui/icons` used, to `^4.9.1` like other packages in the main repo +- Updated dependencies + - @backstage/core-app-api@1.1.1 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-react@0.4.6 + +## @backstage/plugin-adr@0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-adr-common@0.2.2 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-adr-backend@0.2.2 + +### Patch Changes + +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-adr-common@0.2.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-adr-common@0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/integration@1.3.2 + +## @backstage/plugin-airbrake@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/test-utils@1.2.1 + - @backstage/dev-utils@1.0.7 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-airbrake-backend@0.2.10 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## @backstage/plugin-allure@0.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-analytics-module-ga@0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apache-airflow@0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + +## @backstage/plugin-api-docs@0.8.10 + +### Patch Changes + +- 50c6e14aee: Updated dependency `@asyncapi/react-component` to `1.0.0-next.43`. +- 3d5bb521ee: Updated dependency `@asyncapi/react-component` to `1.0.0-next.42`. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apollo-explorer@0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-app-backend@0.3.37 + +### Patch Changes + +- 11c9e0ad33: Added alpha plugin implementation for the new backend system. Available at `@backstage/plugin-app-backend/alpha`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/config@1.0.3 + - @backstage/config-loader@1.1.5 + - @backstage/types@1.0.0 + +## @backstage/plugin-auth-node@0.2.6 + +### Patch Changes + +- f3a3fefb96: Ensure `getIdentity` throws an `AuthenticationError` instead of a `NotAllowed` error when authentication fails +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-azure-devops@0.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-azure-devops-backend@0.3.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-badges@0.2.34 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-badges-backend@0.1.31 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-bazaar@0.1.25 + +### Patch Changes + +- f7c2855d76: Added a `Overview Card` for either latest or random projects. Changed `ProjectPreview.tsx` so it take `gridSize` and `useTablePagination` as props. +- c0352bbc69: Link to the user catalog entity of a member +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/cli@0.20.0 + - @backstage/catalog-client@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + +## @backstage/plugin-bitrise@0.1.37 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog-backend-module-aws@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-azure@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.4 + +### Patch Changes + +- 23f9199a0f: Deprecate `@backstage/plugin-catalog-backend-module-bitbucket`. + + Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` + or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.4 + +### Patch Changes + +- f66e696e7b: Bitbucket Cloud provider: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- a9b91d39bb: Add `bitbucketCloudCatalogModule` (new backend-plugin-api, alpha). + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/plugin-bitbucket-cloud-common@0.2.0 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-catalog-backend-module-github@0.1.8 + +### Patch Changes + +- 8749df3d02: `GitHubEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- 7022aebf35: Added `GithubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. + +- 51046b58b0: Use schedule from config at backend module. + + Also, it removes `GithubEntityProviderCatalogModuleOptions` + in favor of config-only for the backend module setup + like at other similar modules. + +- 7edb5909e8: Add missing config schema for the `GitHubEntityProvider`. + +- be9474b103: Replaces in-code uses of `GitHub` by `Github` and deprecates old versions. + + Deprecates + + - `GitHubEntityProvider` replaced by `GithubEntityProvider` + - `GitHubLocationAnalyzer` replaced by `GithubLocationAnalyzer` + - `GitHubLocationAnalyzerOptions` replaced by `GithubLocationAnalyzerOptions` + - `GitHubOrgEntityProvider` replaced by `GithubOrgEntityProvider` + - `GitHubOrgEntityProviderOptions` replaced by `GithubOrgEntityProviderOptions` + + Renames + + - `GitHubLocationAnalyzer` to `GithubLocationAnalyzer` + - `GitHubLocationAnalyzerOptions` to `GithubLocationAnalyzerOptions` + +- a35a27df70: Updated the `moduleId` of the experimental module export. + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-common@1.0.7 + +### Patch Changes + +- 823acaa88b: Moved the following types from `@backstage/plugin-catalog-backend` to this package. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-common@1.1.0 + +## @backstage/plugin-catalog-graph@0.2.22 + +### Patch Changes + +- bde1e8c8e2: Added `curve` prop to the `DependencyGraph` component to select the type of layout +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/catalog-client@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog-graphql@0.3.14 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + +## @backstage/plugin-cicd-statistics@0.1.12 + +### Patch Changes + +- e05e0f021b: Align on the version of `@material-ui/icons` used, to `^4.9.1` like other packages in the main repo +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-plugin-api@1.0.7 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-cicd-statistics@0.1.12 + - @backstage/core-plugin-api@1.0.7 + +## @backstage/plugin-circleci@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-cloudbuild@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-climate@0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage@0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage-backend@0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-codescene@0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-config-schema@0.1.33 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-cost-insights@0.11.32 + +### Patch Changes + +- a94c2ed1b7: Fixed bug in `CostOverviewBreakdownChart` component where some datasets caused the cost overview breakdown chart to tear. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + +## @backstage/plugin-dynatrace@1.0.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-explore@0.3.41 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/plugin-explore-react@0.0.22 + +## @backstage/plugin-explore-react@0.0.22 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7 + +## @backstage/plugin-firehydrant@0.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-fossa@0.2.42 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcalendar@0.3.6 + +### Patch Changes + +- 4c2ed7ecf1: Fixed loader showing when user not signed in +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcp-projects@0.3.29 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-git-release-manager@0.3.23 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-actions@0.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-deployments@0.1.41 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-issues@0.1.2 + +### Patch Changes + +- 8c7bff2bb4: Updated the `luxon` dependency to 3.x +- 719ccbb963: Properly filter on relations instead of the spec, when finding by owner +- df226e124c: Add filtering and ordering to the graphql query +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-pull-requests-board@0.1.4 + +### Patch Changes + +- 719ccbb963: Properly filter on relations instead of the spec, when finding by owner +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gitops-profiles@0.3.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gocd@0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphiql@0.2.42 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphql-backend@0.1.27 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-graphql@0.3.14 + - @backstage/config@1.0.3 + +## @backstage/plugin-home@0.4.26 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-stack-overflow@0.1.6 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## @backstage/plugin-ilert@0.1.36 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-jenkins@0.7.9 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-jenkins-common@0.1.9 + +## @backstage/plugin-jenkins-backend@0.1.27 + +### Patch Changes + +- b19ea927af: Fixed a bug where `extraRequestHeaders` configuration was ignored. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-jenkins-common@0.1.9 + +## @backstage/plugin-jenkins-common@0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-permission-common@0.7.0 + +## @backstage/plugin-kafka@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kafka-backend@0.2.30 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-kubernetes@0.7.3 + +### Patch Changes + +- 51af8361de: Add useCustomResources react hook for fetching Kubernetes Custom Resources +- 35a6cfe257: Fix infinite call bug in `useCustomResources` hook +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-kubernetes-common@0.4.3 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kubernetes-backend@0.7.3 + +### Patch Changes + +- de676888bc: Added missing cluster locator configuration schema entries, for the catalog and local proxy types +- d4a8c683be: kubernetes service locator now take request context parameters +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-kubernetes-common@0.4.3 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-kubernetes-common@0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + +## @backstage/plugin-lighthouse@0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic@0.3.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic-dashboard@0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + +## @backstage/plugin-org@0.5.10 + +### Patch Changes + +- f2b4b55636: consistently show parent and child relations in group profile card +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-pagerduty@0.5.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop@0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop-backend@0.1.8 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## @backstage/plugin-permission-backend@0.5.12 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-permission-react@0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + +## @backstage/plugin-playlist@0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-react@1.2.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6 + - @backstage/plugin-playlist-common@0.1.1 + +## @backstage/plugin-playlist-common@0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 + +## @backstage/plugin-proxy-backend@0.2.31 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## @backstage/plugin-rollbar@0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-rollbar-backend@0.1.34 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-common@1.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search@1.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.0.3 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/config@1.0.3 + +## @backstage/plugin-search-backend-module-pg@0.4.1 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/config@1.0.3 + +## @backstage/plugin-search-backend-node@1.0.3 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-sentry@0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-shortcuts@0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-sonarqube@0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-sonarqube-backend@0.1.2 + +### Patch Changes + +- 9c595302cb: Normalize on winston version ^3.2.1 +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-splunk-on-call@0.3.34 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-stack-overflow@0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-home@0.4.26 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## @backstage/plugin-stack-overflow-backend@0.1.6 + +### Patch Changes + +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/cli@0.20.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + +## @backstage/plugin-tech-insights@0.3.1 + +### Patch Changes + +- f3d272cf57: Make sure to reload score card contents when props change +- a60a6807bd: making available the search for the last FACTS executed +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7 + +## @backstage/plugin-tech-insights-backend@0.5.3 + +### Patch Changes + +- 296aea34da: The Tech Insights plugin supports running fact retrievers across multiple instances. Update the README to remove the stale instructions. +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- f7cbfb97ed: Modify router endpoint to handle singular and collections of request parameters similarly. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-tech-insights-node@0.3.5 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-tech-insights-common@0.2.7 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-tech-insights-node@0.3.5 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-tech-insights-common@0.2.7 + +## @backstage/plugin-tech-insights-common@0.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/plugin-tech-insights-node@0.3.5 + +### Patch Changes + +- 0963b4d5fb: Updated package role to be `node-library`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7 + +## @backstage/plugin-tech-radar@0.5.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs@1.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/test-utils@1.2.1 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-techdocs@1.3.3 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs-module-addons-contrib@1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs-node@1.4.1 + +### Patch Changes + +- 0b2a30dead: fixing techdocs-cli Docker client creation + + Docker client does not need to be created when --no-docker + option is provided. + + If you had DOCKER_CERT_PATH environment variable defined + the Docker client was looking for certificates + and breaking techdocs-cli generate command even with --no-docker + option. + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-techdocs-react@1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-todo@0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-todo-backend@0.1.34 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## @backstage/plugin-user-settings-backend@0.1.1 + +### Patch Changes + +- f3463b176b: Use `Response.status` instead of `.send(number)` +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- 82ac9bcfe5: Fix wrong import statement in `README.md`. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-vault@0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-vault-backend@0.2.3 + +### Patch Changes + +- 9c595302cb: Normalize on winston version ^3.2.1 +- dae0bbe522: VaultBuilder.tsx renamed to VaultBuilder in order for module to be correctly loaded. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-test-utils@0.1.29 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @backstage/plugin-xcmetrics@0.2.30 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## example-app@0.2.76 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-user-settings@0.5.0 + - @backstage/plugin-scaffolder@1.7.0 + - @backstage/plugin-org@0.5.10 + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-graph@0.2.22 + - @backstage/core-app-api@1.1.1 + - @backstage/cli@0.20.0 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-catalog-import@0.9.0 + - @backstage/plugin-tech-insights@0.3.1 + - @backstage/plugin-kubernetes@0.7.3 + - @backstage/plugin-gcalendar@0.3.6 + - @backstage/plugin-api-docs@0.8.10 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-cost-insights@0.11.32 + - @backstage/plugin-airbrake@0.3.10 + - @backstage/plugin-azure-devops@0.2.1 + - @backstage/plugin-badges@0.2.34 + - @backstage/plugin-circleci@0.3.10 + - @backstage/plugin-cloudbuild@0.3.10 + - @backstage/plugin-code-coverage@0.2.3 + - @backstage/plugin-dynatrace@1.0.0 + - @backstage/plugin-explore@0.3.41 + - @backstage/plugin-github-actions@0.5.10 + - @backstage/plugin-gocd@0.1.16 + - @backstage/plugin-home@0.4.26 + - @backstage/plugin-jenkins@0.7.9 + - @backstage/plugin-kafka@0.3.10 + - @backstage/plugin-lighthouse@0.3.10 + - @backstage/plugin-newrelic-dashboard@0.2.3 + - @backstage/plugin-pagerduty@0.5.3 + - @backstage/plugin-playlist@0.1.1 + - @backstage/plugin-rollbar@0.4.10 + - @backstage/plugin-search@1.0.3 + - @backstage/plugin-sentry@0.4.3 + - @backstage/plugin-techdocs@1.3.3 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/plugin-todo@0.2.12 + - @internal/plugin-catalog-customized@0.0.3 + - @backstage/app-defaults@1.0.7 + - @backstage/integration-react@1.1.5 + - @backstage/plugin-apache-airflow@0.2.3 + - @backstage/plugin-gcp-projects@0.3.29 + - @backstage/plugin-graphiql@0.2.42 + - @backstage/plugin-newrelic@0.3.28 + - @backstage/plugin-shortcuts@0.3.2 + - @backstage/plugin-stack-overflow@0.1.6 + - @backstage/plugin-tech-radar@0.5.17 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6 + +## example-backend@0.2.76 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-auth-backend@0.17.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-tech-insights-backend@0.5.3 + - @backstage/plugin-search-backend@1.1.0 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-playlist-backend@0.2.0 + - @backstage/plugin-jenkins-backend@0.1.27 + - @backstage/plugin-app-backend@0.3.37 + - @backstage/plugin-badges-backend@0.1.31 + - @backstage/plugin-graphql-backend@0.1.27 + - @backstage/plugin-permission-backend@0.5.12 + - @backstage/plugin-rollbar-backend@0.1.34 + - @backstage/plugin-kubernetes-backend@0.7.3 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/plugin-search-backend-module-pg@0.4.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3 + - @backstage/plugin-techdocs-backend@1.4.0 + - @backstage/plugin-tech-insights-node@0.3.5 + - example-app@0.2.76 + - @backstage/plugin-code-coverage-backend@0.2.3 + - @backstage/plugin-kafka-backend@0.2.30 + - @backstage/plugin-todo-backend@0.1.34 + - @backstage/plugin-azure-devops-backend@0.3.16 + - @backstage/plugin-proxy-backend@0.2.31 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + +## example-backend-next@0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/backend-defaults@0.1.2 + - @backstage/plugin-app-backend@0.3.37 + +## techdocs-cli-embedded-app@0.2.75 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/cli@0.20.0 + - @backstage/test-utils@1.2.1 + - @backstage/plugin-techdocs@1.3.3 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/app-defaults@1.0.7 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## @internal/plugin-catalog-customized@0.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/plugin-catalog@1.6.0 + +## @internal/plugin-todo-list@1.0.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## @internal/plugin-todo-list-backend@1.0.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## @internal/plugin-todo-list-common@1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 diff --git a/docs/releases/v1.7.0-next.0-changelog.md b/docs/releases/v1.7.0-next.0-changelog.md new file mode 100644 index 0000000000..57b3c64f6f --- /dev/null +++ b/docs/releases/v1.7.0-next.0-changelog.md @@ -0,0 +1,2064 @@ +# Release v1.7.0-next.0 + +## @backstage/cli@0.20.0-next.0 + +### Minor Changes + +- f368ad7279: **BREAKING**: Bumped `jest`, `jest-runtime`, and `jest-environment-jsdom` to v29. This is up from v27, so check out both the [v28](https://jestjs.io/docs/28.x/upgrading-to-jest28) and [v29](https://jestjs.io/docs/upgrading-to-jest29) (later [here](https://jestjs.io/docs/29.x/upgrading-to-jest29)) migration guides. + + Particular changes that where encountered in the main Backstage repo are: + + - The updated snapshot format. + - `jest.useFakeTimers('legacy')` is now `jest.useFakeTimers({ legacyFakeTimers: true })`. + - Error objects collected by `withLogCollector` from `@backstage/test-utils` are now objects with a `detail` property rather than a string. + +### Patch Changes + +- 3e309107ca: Updated fallback versions of dependencies in all templates. + +- 292a088807: Added a new `repo test` command. + +- ba63cae41c: Updated lockfile parsing to have better support for Yarn 3. + +- 2dddb32fea: Switched the Jest transform for YAML files to use a custom one available at `@backstage/cli/config/jestYamlTransform.js`. + +- a541a3a78a: Switch to upfront resolution of `swc-loader` in Webpack config. + +- cfb3598410: Removed `tsx` and `jsx` as supported extensions in backend packages. For most + repos, this will not have any effect. But if you inadvertently had added some + `tsx`/`jsx` files to your backend package, you may now start to see `code: 'MODULE_NOT_FOUND'` errors when launching the backend locally. The reason for + this is that the offending files get ignored during transpilation. Hence, the + importing file can no longer find anything to import. + + The fix is to rename any `.tsx` files in your backend packages to `.ts` instead, + or `.jsx` to `.js`. + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.0 + - @backstage/config-loader@1.1.5-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/release-manifests@0.0.6 + - @backstage/types@1.0.0 + +## @backstage/plugin-auth-backend@0.17.0-next.0 + +### Minor Changes + +- 5fa831ce55: CookieConfigurer can optionally return the `SameSite` cookie attribute. + CookieConfigurer now requires an additional argument `appOrigin` - the origin URL of the app - which is used to calculate the `SameSite` attribute. + defaultCookieConfigurer returns the `SameSite` attribute which defaults to `Lax`. In cases where an auth-backend is running on a different domain than the App, `SameSite=None` is used - but only for secure contexts. This is so that cookies can be included in third-party requests. + + OAuthAdapterOptions has been modified to require additional arguments, `baseUrl`, and `cookieConfigurer`. + OAuthAdapter now resolves cookie configuration using its supplied CookieConfigurer for each request to make sure that the proper attributes always are set. + +### Patch Changes + +- 8c6ec175bf: Fix GitLab provider setup so that it supports GitLab installations with a path in the URL. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-bitbucket-cloud-common@0.2.0-next.0 + +### Minor Changes + +- ad74723fbf: Update Bitbucket Cloud models to latest OAS version. + + The latest specification contained some BREAKING CHANGES + due to removed fields. + + All of these fields are not used at other plugins, though. + Therefore, this change has no impact on other modules here. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.3.2-next.0 + +## @backstage/plugin-scaffolder@1.7.0-next.0 + +### Minor Changes + +- f13d5f3f06: Add support for link to TechDocs and other links defined in template entity specification metadata on TemplateCard +- 05f22193c5: EntityPickers now support flags to control when to include default namespace + in result + +### Patch Changes + +- 8960d83013: Add support for `allowedOrganizations` and `allowedOwners` to the `AzureRepoPicker`. +- b681275e69: Ignore .git directories in Template Editor, increase upload limit for dry-runs to 10MB. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-permission-react@0.4.6-next.0 + +## @backstage/plugin-scaffolder-backend@1.7.0-next.0 + +### Minor Changes + +- 253453fa14: Added a new property called `additionalTemplateGlobals` which allows you to add global functions to the scaffolder nunjucks templates. +- 304305dd20: Add `allowAutoMerge` option for `publish:github` action +- 694bfe2d61: Add functionality to shutdown scaffolder tasks if they are stale + +### Patch Changes + +- b681275e69: Ignore .git directories in Template Editor, increase upload limit for dry-runs to 10MB. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-catalog-node@1.1.1-next.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-user-settings@0.5.0-next.0 + +### Minor Changes + +- 5543e86660: **BREAKING**: The `apiRef` passed to `ProviderSettingsItem` now needs to + implement `ProfileInfoApi & SessionApi`, rather than just the latter. This is + unlikely to have an effect on most users though, since the builtin auth + providers generally implement both. + + Fixed settings page showing providers as logged out when the user is using more + than one provider, and displayed some additional login information. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/app-defaults@1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6-next.0 + +## @backstage/backend-app-api@0.2.2-next.0 + +### Patch Changes + +- 0027a749cd: Added possibility to configure index plugin of the HTTP router service. +- 45857bffae: Properly export `rootLoggerFactory`. +- Updated dependencies + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/errors@1.1.2-next.0 + +## @backstage/backend-common@0.15.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.0 + - @backstage/config-loader@1.1.5-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/backend-defaults@0.1.2-next.0 + +### Patch Changes + +- 96d288a02d: Added root logger service to the set of default services. +- Updated dependencies + - @backstage/backend-app-api@0.2.2-next.0 + - @backstage/backend-plugin-api@0.1.3-next.0 + +## @backstage/backend-plugin-api@0.1.3-next.0 + +### Patch Changes + +- 28377dc89f: Allow interfaces to be used for inferred option types. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + +## @backstage/backend-tasks@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/backend-test-utils@0.1.29-next.0 + +### Patch Changes + +- 72549952d1: Fixed handling of root scoped services in `startTestBackend`. +- e91e8e9c55: Increased test database max connection pool size to reduce the risk of resource exhaustion. +- Updated dependencies + - @backstage/backend-app-api@0.2.2-next.0 + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/cli@0.20.0-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + +## @backstage/catalog-client@1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/errors@1.1.2-next.0 + +## @backstage/catalog-model@1.1.2-next.0 + +### Patch Changes + +- 6f3b8d0962: Defer `ajv` compilation of schema validators to improve module-import performance +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/codemods@0.1.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/config@1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/config-loader@1.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/core-app-api@1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-components@0.11.2-next.0 + +### Patch Changes + +- 882101cd9b: Deep-import LightAsync component to improve module-import speed +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-plugin-api@1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/create-app@0.4.32-next.0 + +### Patch Changes + +- 58c2264325: Newly created Backstage repositories now use the stable version 6 of + `react-router`, just like the main repo does. Please let us know if you find any + issues with this. + + Migrating to the stable version of `react-router` is optional for the time + being. But if you want to do the same for your existing repository, please + follow [this + guide](https://backstage.io/docs/tutorials/react-router-stable-migration). + +- e05e0f021b: Update versions of packages used in the create-app template, to match those in the main repo + +- 52f25858a8: Added `*.session.sql` Visual Studio Code database functionality files to `.gitignore` in the default template. This is optional but potentially helpful if your developers use Visual Studio Code; you can add a line with that exact value to your own root `.gitignore` if you want the same. + +- 6d00e80146: Updated the root `test` scripts to use `backstage-cli repo test`. + + To apply this change to an existing app, make the following change to the root `package.json`: + + ```diff + - "test": "backstage-cli test", + - "test:all": "lerna run test -- --coverage", + + "test": "backstage-cli repo test", + + "test:all": "backstage-cli repo test --coverage", + ``` + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/dev-utils@1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/test-utils@1.2.1-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/app-defaults@1.0.7-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/errors@1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/integration@1.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + +## @backstage/integration-react@1.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + +## @techdocs/cli@1.2.2-next.0 + +### Patch Changes + +- 0b2a30dead: fixing techdocs-cli Docker client creation + + Docker client does not need to be created when --no-docker + option is provided. + + If you had DOCKER_CERT_PATH environment variable defined + the Docker client was looking for certificates + and breaking techdocs-cli generate command even with --no-docker + option. + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-techdocs-node@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.0 + +## @backstage/test-utils@1.2.1-next.0 + +### Patch Changes + +- e05e0f021b: Align on the version of `@material-ui/icons` used, to `^4.9.1` like other packages in the main repo +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-permission-react@0.4.6-next.0 + +## @backstage/plugin-adr@0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-adr-common@0.2.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-adr-backend@0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-adr-common@0.2.2-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-adr-common@0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-airbrake@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/test-utils@1.2.1-next.0 + - @backstage/dev-utils@1.0.7-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-airbrake-backend@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + +## @backstage/plugin-allure@0.1.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-analytics-module-ga@0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apache-airflow@0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + +## @backstage/plugin-api-docs@0.8.10-next.0 + +### Patch Changes + +- 3d5bb521ee: Updated dependency `@asyncapi/react-component` to `1.0.0-next.42`. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apollo-explorer@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-app-backend@0.3.37-next.0 + +### Patch Changes + +- 11c9e0ad33: Added alpha plugin implementation for the new backend system. Available at `@backstage/plugin-app-backend/alpha`. +- Updated dependencies + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/config-loader@1.1.5-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-auth-node@0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + +## @backstage/plugin-azure-devops@0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-azure-devops-backend@0.3.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-badges@0.2.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-badges-backend@0.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + +## @backstage/plugin-bazaar@0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/cli@0.20.0-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-catalog@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + +## @backstage/plugin-bazaar-backend@0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@0.1.29-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + +## @backstage/plugin-bitrise@0.1.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog@1.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-catalog-backend@1.4.1-next.0 + +### Patch Changes + +- 8cb6e10105: Fixed a bug where entities provided without a location key would always replace existing entities, rather than updating them. +- 63296ebcd4: Allow Placeholder value to be any value, not only string. +- 74022e0163: Make sure to stitch entities correctly after deletion, to ensure that their relations are updated. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-catalog-node@1.1.1-next.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-azure@0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/integration@1.3.2-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/plugin-catalog-node@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/plugin-catalog-node@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-common@1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-catalog-graph@0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog-graphql@0.3.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-import@0.8.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + +## @backstage/plugin-catalog-node@1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-react@1.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-permission-react@0.4.6-next.0 + +## @backstage/plugin-cicd-statistics@0.1.12-next.0 + +### Patch Changes + +- e05e0f021b: Align on the version of `@material-ui/icons` used, to `^4.9.1` like other packages in the main repo +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-cicd-statistics@0.1.12-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + +## @backstage/plugin-circleci@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-cloudbuild@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-climate@0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage@0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage-backend@0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + +## @backstage/plugin-codescene@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-config-schema@0.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-cost-insights@0.11.32-next.0 + +### Patch Changes + +- a94c2ed1b7: Fixed bug in `CostOverviewBreakdownChart` component where some datasets caused the cost overview breakdown chart to tear. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + +## @backstage/plugin-dynatrace@0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-explore@0.3.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-explore-react@0.0.22-next.0 + +## @backstage/plugin-explore-react@0.0.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.0 + +## @backstage/plugin-firehydrant@0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-fossa@0.2.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcalendar@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcp-projects@0.3.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-git-release-manager@0.3.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-actions@0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-deployments@0.1.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-issues@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-pull-requests-board@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gitops-profiles@0.3.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gocd@0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphiql@0.2.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphql-backend@0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-graphql@0.3.14-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + +## @backstage/plugin-home@0.4.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-stack-overflow@0.1.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-ilert@0.1.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-jenkins@0.7.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-jenkins-common@0.1.9-next.0 + +## @backstage/plugin-jenkins-backend@0.1.27-next.0 + +### Patch Changes + +- b19ea927af: Fixed a bug where `extraRequestHeaders` configuration was ignored. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-jenkins-common@0.1.9-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + +## @backstage/plugin-jenkins-common@0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + +## @backstage/plugin-kafka@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kafka-backend@0.2.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + +## @backstage/plugin-kubernetes@0.7.3-next.0 + +### Patch Changes + +- 51af8361de: Add useCustomResources react hook for fetching Kubernetes Custom Resources +- 35a6cfe257: Fix infinite call bug in `useCustomResources` hook +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-kubernetes-common@0.4.3-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kubernetes-backend@0.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-kubernetes-common@0.4.3-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + +## @backstage/plugin-kubernetes-common@0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + +## @backstage/plugin-lighthouse@0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic@0.3.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic-dashboard@0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + +## @backstage/plugin-org@0.5.10-next.0 + +### Patch Changes + +- f2b4b55636: consistently show parent and child relations in group profile card +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-pagerduty@0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop@0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop-backend@0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + +## @backstage/plugin-permission-backend@0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + +## @backstage/plugin-permission-common@0.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + +## @backstage/plugin-permission-node@0.6.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + +## @backstage/plugin-permission-react@0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + +## @backstage/plugin-playlist@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-permission-react@0.4.6-next.0 + - @backstage/plugin-playlist-common@0.1.1-next.0 + +## @backstage/plugin-playlist-backend@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-test-utils@0.1.29-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-playlist-common@0.1.1-next.0 + +## @backstage/plugin-playlist-common@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.6.5-next.0 + +## @backstage/plugin-proxy-backend@0.2.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + +## @backstage/plugin-rollbar@0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-rollbar-backend@0.1.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-common@1.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-search@1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-search-backend@1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-search-backend-node@1.0.3-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/plugin-search-backend-node@1.0.3-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-search-backend-module-pg@0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/plugin-search-backend-node@1.0.3-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-search-backend-node@1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-search-common@1.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + +## @backstage/plugin-search-react@1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-sentry@0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-shortcuts@0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-sonarqube@0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-sonarqube-backend@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + +## @backstage/plugin-splunk-on-call@0.3.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-stack-overflow@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-home@0.4.26-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-stack-overflow-backend@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-tech-insights@0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.0 + +## @backstage/plugin-tech-insights-backend@0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-tech-insights-node@0.3.5-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.0 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-tech-insights-node@0.3.5-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.0 + +## @backstage/plugin-tech-insights-common@0.2.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/plugin-tech-insights-node@0.3.5-next.0 + +### Patch Changes + +- 0963b4d5fb: Updated package role to be `node-library`. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.0 + +## @backstage/plugin-tech-radar@0.5.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs@1.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-techdocs-react@1.0.5-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/test-utils@1.2.1-next.0 + - @backstage/plugin-catalog@1.5.2-next.0 + - @backstage/plugin-techdocs@1.3.3-next.0 + - @backstage/plugin-techdocs-react@1.0.5-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs-backend@1.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-techdocs-node@1.4.1-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-techdocs-react@1.0.5-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs-node@1.4.1-next.0 + +### Patch Changes + +- 0b2a30dead: fixing techdocs-cli Docker client creation + + Docker client does not need to be created when --no-docker + option is provided. + + If you had DOCKER_CERT_PATH environment variable defined + the Docker client was looking for certificates + and breaking techdocs-cli generate command even with --no-docker + option. + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + +## @backstage/plugin-techdocs-react@1.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-todo@0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-todo-backend@0.1.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + +## @backstage/plugin-user-settings-backend@0.1.1-next.0 + +### Patch Changes + +- 82ac9bcfe5: Fix wrong import statement in `README.md`. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + +## @backstage/plugin-vault@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## @backstage/plugin-vault-backend@0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@0.1.29-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + +## @backstage/plugin-xcmetrics@0.2.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + +## example-app@0.2.76-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-org@0.5.10-next.0 + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/cli@0.20.0-next.0 + - @backstage/plugin-kubernetes@0.7.3-next.0 + - @backstage/plugin-scaffolder@1.7.0-next.0 + - @backstage/plugin-api-docs@0.8.10-next.0 + - @backstage/plugin-user-settings@0.5.0-next.0 + - @backstage/plugin-cost-insights@0.11.32-next.0 + - @backstage/plugin-airbrake@0.3.10-next.0 + - @backstage/plugin-azure-devops@0.2.1-next.0 + - @backstage/plugin-badges@0.2.34-next.0 + - @backstage/plugin-catalog-graph@0.2.22-next.0 + - @backstage/plugin-catalog-import@0.8.13-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-circleci@0.3.10-next.0 + - @backstage/plugin-cloudbuild@0.3.10-next.0 + - @backstage/plugin-code-coverage@0.2.3-next.0 + - @backstage/plugin-dynatrace@0.2.1-next.0 + - @backstage/plugin-explore@0.3.41-next.0 + - @backstage/plugin-github-actions@0.5.10-next.0 + - @backstage/plugin-gocd@0.1.16-next.0 + - @backstage/plugin-home@0.4.26-next.0 + - @backstage/plugin-jenkins@0.7.9-next.0 + - @backstage/plugin-kafka@0.3.10-next.0 + - @backstage/plugin-lighthouse@0.3.10-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.3-next.0 + - @backstage/plugin-pagerduty@0.5.3-next.0 + - @backstage/plugin-playlist@0.1.1-next.0 + - @backstage/plugin-rollbar@0.4.10-next.0 + - @backstage/plugin-search@1.0.3-next.0 + - @backstage/plugin-sentry@0.4.3-next.0 + - @backstage/plugin-tech-insights@0.3.1-next.0 + - @backstage/plugin-techdocs@1.3.3-next.0 + - @backstage/plugin-techdocs-react@1.0.5-next.0 + - @backstage/plugin-todo@0.2.12-next.0 + - @backstage/app-defaults@1.0.7-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/plugin-apache-airflow@0.2.3-next.0 + - @backstage/plugin-gcalendar@0.3.6-next.0 + - @backstage/plugin-gcp-projects@0.3.29-next.0 + - @backstage/plugin-graphiql@0.2.42-next.0 + - @backstage/plugin-newrelic@0.3.28-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/plugin-shortcuts@0.3.2-next.0 + - @backstage/plugin-stack-overflow@0.1.6-next.0 + - @backstage/plugin-tech-radar@0.5.17-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-permission-react@0.4.6-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + - @internal/plugin-catalog-customized@0.0.3-next.0 + +## example-backend@0.2.76-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-scaffolder-backend@1.7.0-next.0 + - @backstage/plugin-auth-backend@0.17.0-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/plugin-jenkins-backend@0.1.27-next.0 + - @backstage/plugin-app-backend@0.3.37-next.0 + - @backstage/plugin-tech-insights-node@0.3.5-next.0 + - example-app@0.2.76-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-badges-backend@0.1.31-next.0 + - @backstage/plugin-code-coverage-backend@0.2.3-next.0 + - @backstage/plugin-kafka-backend@0.2.30-next.0 + - @backstage/plugin-kubernetes-backend@0.7.3-next.0 + - @backstage/plugin-playlist-backend@0.1.1-next.0 + - @backstage/plugin-tech-insights-backend@0.5.3-next.0 + - @backstage/plugin-techdocs-backend@1.3.1-next.0 + - @backstage/plugin-todo-backend@0.1.34-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/plugin-rollbar-backend@0.1.34-next.0 + - @backstage/plugin-search-backend-module-pg@0.4.1-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/plugin-azure-devops-backend@0.3.16-next.0 + - @backstage/plugin-graphql-backend@0.1.27-next.0 + - @backstage/plugin-permission-backend@0.5.12-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-proxy-backend@0.2.31-next.0 + - @backstage/plugin-search-backend@1.0.3-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.0 + - @backstage/plugin-search-backend-node@1.0.3-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.0 + +## example-backend-next@0.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.0 + - @backstage/backend-defaults@0.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/plugin-app-backend@0.3.37-next.0 + +## techdocs-cli-embedded-app@0.2.75-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/cli@0.20.0-next.0 + - @backstage/test-utils@1.2.1-next.0 + - @backstage/plugin-catalog@1.5.2-next.0 + - @backstage/plugin-techdocs@1.3.3-next.0 + - @backstage/plugin-techdocs-react@1.0.5-next.0 + - @backstage/app-defaults@1.0.7-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @internal/plugin-catalog-customized@0.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + +## @internal/plugin-todo-list@1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + +## @internal/plugin-todo-list-backend@1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + +## @internal/plugin-todo-list-common@1.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.6.5-next.0 diff --git a/docs/releases/v1.7.0-next.1-changelog.md b/docs/releases/v1.7.0-next.1-changelog.md new file mode 100644 index 0000000000..f5480147fb --- /dev/null +++ b/docs/releases/v1.7.0-next.1-changelog.md @@ -0,0 +1,2088 @@ +# Release v1.7.0-next.1 + +## @backstage/plugin-auth-backend@0.17.0-next.1 + +### Minor Changes + +- e2dc42e9f0: Google OAuth refresh tokens will now be revoked on logout by calling Google's API + +### Patch Changes + +- b5c126010c: Auth0 provider now supports optional `connection` and `connectionScope` parameters to configure social identity providers. +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-auth-node@0.2.6-next.1 + +## @backstage/plugin-catalog@1.6.0-next.1 + +### Minor Changes + +- d558f41d3a: Added new column `Label` to `CatalogTable.columns`, this new column allows you make use of labels from metadata. + For example: category and visibility are type of labels associated with API entity illustrated below. + + YAML code snippet for API entity + + ```yaml + apiVersion: backstage.io/v1alpha1 + kind: API + metadata: + name: sample-api + description: API for sample + links: + - url: http://localhost:8080/swagger-ui.html + title: Swagger UI + tags: + - http + labels: + category: legacy + visibility: protected + ``` + + Consumers can customise columns to include label column and show in api-docs list + + ```typescript + const columns = [ + CatalogTable.columns.createNameColumn({ defaultKind: 'API' }), + CatalogTable.columns.createLabelColumn('category', { title: 'Category' }), + CatalogTable.columns.createLabelColumn('visibility', { + title: 'Visibility', + defaultValue: 'public', + }), + ]; + ``` + +### Patch Changes + +- 4efadb6968: Implemented the visual parts of `EntityKindPicker` so that it can be shown alongside the other filters on the left side of your catalog pages. +- e89e1f614d: Added support for copy entity URL in entity page context menu +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.7-next.1 + +## @backstage/plugin-catalog-react@1.2.0-next.1 + +### Minor Changes + +- 4efadb6968: Implemented the visual parts of `EntityKindPicker` so that it can be shown alongside the other filters on the left side of your catalog pages. + +### Patch Changes + +- 7939e743f5: Added two new `EntityRefLinks` props, the first being `getTitle` that allows for customization of the title used for each link. The second one is `fetchEntities`, which triggers a fetching of all entities so that the full entity definition is available in the `getTitle` callback. +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-catalog-common@1.0.7-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-react@0.4.6-next.1 + +## @backstage/plugin-search-backend@1.1.0-next.1 + +### Minor Changes + +- 16c853a6ed: Be less restrictive with unknown keys on query endpoint +- a799972bb1: The query received by search engines now contains a property called `pageLimit`, it specifies how many results to return per page when sending a query request to the search backend. + + Example: + _Returns up to 30 results per page_ + + GET /query?pageLimit=30 + + The search backend validates the page limit and this value must not exceed 100, but it doesn't set a default value for the page limit parameter, it leaves it up to each search engine to set this, so Lunr, Postgres and Elastic Search set 25 results per page as a default value. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-search-backend-node@1.0.3-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + +## @backstage/plugin-search-common@1.1.0-next.1 + +### Minor Changes + +- a799972bb1: There is a new property called `pageLimit` on the `SearchQuery` interface that specifies how many results should be returned per page. + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## @backstage/plugin-search-react@1.2.0-next.1 + +### Minor Changes + +- 4ed1fa2480: The search query state now has an optional `pageLimit` property that determines how many results will be requested per page, it defaults to 25. + + Examples: + _Basic_ + + ```jsx + + {results => { + // Item rendering logic is omitted + }} + + ``` + + _With context_ + + ```jsx + + + {results => { + // Item rendering logic is omitted + }} + + + ``` + +- bed5a1dc6e: The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. + Possibility to provide a custom no results component if needed through the `noResultsComponent` property. + + Examples: + + _Rendering a custom no results component_ + + ```jsx + } + /> + ``` + + _Disable rendering when there are no results_ + + ```jsx + + ``` + +- 6faaa05626: The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. + Possibility to provide a custom no results component if needed through the `noResultsComponent` property. + + Examples: + + _Rendering a custom no results component_ + + ```jsx + } + title="Documentation" + noResultsComponent={} + /> + ``` + + _Disable rendering when there are no results_ + + ```jsx + } + title="Documentation" + disableRenderingWithNoResults + /> + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/app-defaults@1.0.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6-next.1 + +## @backstage/backend-app-api@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + +## @backstage/backend-common@0.15.2-next.1 + +### Patch Changes + +- c31f7cdfbc: Fixed an issue where `getClient()` for a `pluginId` would return different clients and not share them +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.1 + - @backstage/config-loader@1.1.5-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + +## @backstage/backend-defaults@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/backend-app-api@0.2.2-next.1 + +## @backstage/backend-plugin-api@0.1.3-next.1 + +### Patch Changes + +- a35a27df70: Added documentation for `createBackendModule`, with guidelines for choosing a module ID. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## @backstage/backend-tasks@0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + +## @backstage/backend-test-utils@0.1.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/backend-app-api@0.2.2-next.1 + - @backstage/config@1.0.3-next.1 + +## @backstage/catalog-client@1.1.1-next.1 + +### Patch Changes + +- 4f2ac624b4: Renamed argument in `validateEntity` from `location` to `locationRef` +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + +## @backstage/catalog-model@1.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + +## @backstage/cli@0.20.0-next.1 + +### Patch Changes + +- 78d5eb299e: Tweak the Jest Caching loader to only operate when in `watch` mode +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.1 + - @backstage/config-loader@1.1.5-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/release-manifests@0.0.6 + - @backstage/types@1.0.0 + +## @backstage/codemods@0.1.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/config@1.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/config-loader@1.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + +## @backstage/core-app-api@1.1.1-next.1 + +### Patch Changes + +- 27e6404aba: Fixed a bug where gathered index routes would fail to bind routable extensions. This would typically show up when placing a routable extension in the entity page overview tab. +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-components@0.11.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-plugin-api@1.0.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.1 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/create-app@0.4.32-next.1 + +### Patch Changes + +- 7c6306fc8a: Initializes a git repository when creating an app using @packages/create-app +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/dev-utils@1.0.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/app-defaults@1.0.7-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/test-utils@1.2.1-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + +## @backstage/errors@1.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/integration@1.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## @backstage/integration-react@1.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/theme@0.2.16 + +## @techdocs/cli@1.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.1 + - @backstage/plugin-techdocs-node@1.4.1-next.1 + +## @backstage/test-utils@1.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-react@0.4.6-next.1 + +## @backstage/plugin-adr@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-adr-common@0.2.2-next.1 + +## @backstage/plugin-adr-backend@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/plugin-adr-common@0.2.2-next.1 + +## @backstage/plugin-adr-common@0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + +## @backstage/plugin-airbrake@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/dev-utils@1.0.7-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/test-utils@1.2.1-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-airbrake-backend@0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + +## @backstage/plugin-allure@0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-analytics-module-ga@0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apache-airflow@0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + +## @backstage/plugin-api-docs@0.8.10-next.1 + +### Patch Changes + +- 50c6e14aee: Updated dependency `@asyncapi/react-component` to `1.0.0-next.43`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-catalog@1.6.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apollo-explorer@0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-app-backend@0.3.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/config-loader@1.1.5-next.1 + - @backstage/types@1.0.0 + +## @backstage/plugin-auth-node@0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## @backstage/plugin-azure-devops@0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-azure-devops-backend@0.3.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-badges@0.2.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-badges-backend@0.1.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## @backstage/plugin-bazaar@0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-catalog@1.6.0-next.1 + - @backstage/cli@0.20.0-next.1 + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + +## @backstage/plugin-bazaar-backend@0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-test-utils@0.1.29-next.1 + - @backstage/config@1.0.3-next.1 + +## @backstage/plugin-bitbucket-cloud-common@0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.3.2-next.1 + +## @backstage/plugin-bitrise@0.1.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog-backend@1.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.7-next.1 + - @backstage/plugin-catalog-node@1.1.1-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + - @backstage/plugin-scaffolder-common@1.2.1-next.1 + +## @backstage/plugin-catalog-backend-module-aws@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## @backstage/plugin-catalog-backend-module-azure@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## @backstage/plugin-catalog-backend-module-github@0.1.8-next.1 + +### Patch Changes + +- a35a27df70: Updated the `moduleId` of the experimental module export. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + - @backstage/plugin-catalog-node@1.1.1-next.1 + +## @backstage/plugin-catalog-backend-module-gitlab@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## @backstage/plugin-catalog-backend-module-msgraph@0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + - @backstage/plugin-catalog-node@1.1.1-next.1 + +## @backstage/plugin-catalog-common@1.0.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## @backstage/plugin-catalog-graph@0.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog-graphql@0.3.14-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-import@0.8.13-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + +## @backstage/plugin-catalog-node@1.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + +## @backstage/plugin-cicd-statistics@0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-cicd-statistics@0.1.12-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + +## @backstage/plugin-circleci@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-cloudbuild@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-climate@0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage@0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage-backend@0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + +## @backstage/plugin-codescene@0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-config-schema@0.1.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-cost-insights@0.11.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + +## @backstage/plugin-dynatrace@1.0.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-explore@0.3.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-explore-react@0.0.22-next.1 + +## @backstage/plugin-explore-react@0.0.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.1 + +## @backstage/plugin-firehydrant@0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-fossa@0.2.42-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcalendar@0.3.6-next.1 + +### Patch Changes + +- 4c2ed7ecf1: Fixed loader showing when user not signed in +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcp-projects@0.3.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-git-release-manager@0.3.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-actions@0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-deployments@0.1.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-issues@0.1.2-next.1 + +### Patch Changes + +- 8c7bff2bb4: Updated the `luxon` dependency to 3.x +- 719ccbb963: Properly filter on relations instead of the spec, when finding by owner +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-pull-requests-board@0.1.4-next.1 + +### Patch Changes + +- 719ccbb963: Properly filter on relations instead of the spec, when finding by owner +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gitops-profiles@0.3.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gocd@0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphiql@0.2.42-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphql-backend@0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/plugin-catalog-graphql@0.3.14-next.1 + +## @backstage/plugin-home@0.4.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/plugin-stack-overflow@0.1.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-ilert@0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-jenkins@0.7.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-jenkins-common@0.1.9-next.1 + +## @backstage/plugin-jenkins-backend@0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-jenkins-common@0.1.9-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## @backstage/plugin-jenkins-common@0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## @backstage/plugin-kafka@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kafka-backend@0.2.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## @backstage/plugin-kubernetes@0.7.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-kubernetes-common@0.4.3-next.1 + +## @backstage/plugin-kubernetes-backend@0.7.3-next.1 + +### Patch Changes + +- d4a8c683be: kubernetes service locator now take request context parameters +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-kubernetes-common@0.4.3-next.1 + +## @backstage/plugin-kubernetes-common@0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.1 + +## @backstage/plugin-lighthouse@0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic@0.3.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic-dashboard@0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + +## @backstage/plugin-org@0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-pagerduty@0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop-backend@0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + +## @backstage/plugin-permission-backend@0.5.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + +## @backstage/plugin-permission-common@0.6.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## @backstage/plugin-permission-node@0.6.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## @backstage/plugin-permission-react@0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## @backstage/plugin-playlist@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.7-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-react@0.4.6-next.1 + - @backstage/plugin-playlist-common@0.1.1-next.1 + +## @backstage/plugin-playlist-backend@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-test-utils@0.1.29-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + - @backstage/plugin-playlist-common@0.1.1-next.1 + +## @backstage/plugin-playlist-common@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.6.5-next.1 + +## @backstage/plugin-proxy-backend@0.2.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + +## @backstage/plugin-rollbar@0.4.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-rollbar-backend@0.1.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + +## @backstage/plugin-scaffolder@1.7.0-next.1 + +### Patch Changes + +- e4f0a96424: Making the description of the GitLab repoUrl owner field more clearer by focusing it refers to the GitLab namespace. +- 8220f2fd83: Support custom layouts in dry run editor +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.7-next.1 + - @backstage/plugin-permission-react@0.4.6-next.1 + - @backstage/plugin-scaffolder-common@1.2.1-next.1 + +## @backstage/plugin-scaffolder-backend@1.7.0-next.1 + +### Patch Changes + +- 489621f613: Switching off duplicated timestamp in case of logging via task logger in a custom action +- a35a27df70: Updated the `moduleId` of the experimental module export. +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + - @backstage/plugin-catalog-node@1.1.1-next.1 + - @backstage/plugin-scaffolder-common@1.2.1-next.1 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-scaffolder-backend@1.7.0-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-scaffolder-backend@1.7.0-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-common@1.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/types@1.0.0 + +## @backstage/plugin-search@1.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.1 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-search-backend-node@1.0.3-next.1 + - @backstage/config@1.0.3-next.1 + +## @backstage/plugin-search-backend-module-pg@0.4.1-next.1 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-search-backend-node@1.0.3-next.1 + - @backstage/config@1.0.3-next.1 + +## @backstage/plugin-search-backend-node@1.0.3-next.1 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## @backstage/plugin-sentry@0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-shortcuts@0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-sonarqube@0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-sonarqube-backend@0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## @backstage/plugin-splunk-on-call@0.3.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-stack-overflow@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-home@0.4.26-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-stack-overflow-backend@0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/config@1.0.3-next.1 + +## @backstage/plugin-tech-insights@0.3.1-next.1 + +### Patch Changes + +- a60a6807bd: making available the search for the last FACTS executed +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.1 + +## @backstage/plugin-tech-insights-backend@0.5.3-next.1 + +### Patch Changes + +- f7cbfb97ed: Modify router endpoint to handle singular and collections of request parameters similarly. +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-tech-insights-common@0.2.7-next.1 + - @backstage/plugin-tech-insights-node@0.3.5-next.1 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-tech-insights-common@0.2.7-next.1 + - @backstage/plugin-tech-insights-node@0.3.5-next.1 + +## @backstage/plugin-tech-insights-common@0.2.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/plugin-tech-insights-node@0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.1 + +## @backstage/plugin-tech-radar@0.5.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs@1.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.1 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.1 + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/plugin-techdocs@1.3.3-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/test-utils@1.2.1-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.1 + +## @backstage/plugin-techdocs-backend@1.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/plugin-catalog-common@1.0.7-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-techdocs-node@1.4.1-next.1 + +## @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.1 + +## @backstage/plugin-techdocs-node@1.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + +## @backstage/plugin-techdocs-react@1.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-todo@0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-todo-backend@0.1.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + +## @backstage/plugin-user-settings@0.5.0-next.1 + +### Patch Changes + +- 06d61d1266: Handle errors that may occur when the user logs out +- 44c9a95dcf: Prevent `.set()` to execute a request to the StorageClient if the user is `guest` +- 174f02a00a: Update installation instructions +- Updated dependencies + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-user-settings-backend@0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-auth-node@0.2.6-next.1 + +## @backstage/plugin-vault@0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## @backstage/plugin-vault-backend@0.2.3-next.1 + +### Patch Changes + +- dae0bbe522: VaultBuilder.tsx renamed to VaultBuilder in order for module to be correctly loaded. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/backend-test-utils@0.1.29-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## @backstage/plugin-xcmetrics@0.2.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## example-app@0.2.76-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-user-settings@0.5.0-next.1 + - @backstage/plugin-scaffolder@1.7.0-next.1 + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/cli@0.20.0-next.1 + - @backstage/plugin-tech-insights@0.3.1-next.1 + - @backstage/plugin-gcalendar@0.3.6-next.1 + - @backstage/plugin-api-docs@0.8.10-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-airbrake@0.3.10-next.1 + - @backstage/plugin-azure-devops@0.2.1-next.1 + - @backstage/plugin-badges@0.2.34-next.1 + - @internal/plugin-catalog-customized@0.0.3-next.1 + - @backstage/plugin-catalog-graph@0.2.22-next.1 + - @backstage/plugin-catalog-import@0.8.13-next.1 + - @backstage/plugin-circleci@0.3.10-next.1 + - @backstage/plugin-cloudbuild@0.3.10-next.1 + - @backstage/plugin-code-coverage@0.2.3-next.1 + - @backstage/plugin-dynatrace@1.0.0-next.1 + - @backstage/plugin-explore@0.3.41-next.1 + - @backstage/plugin-github-actions@0.5.10-next.1 + - @backstage/plugin-gocd@0.1.16-next.1 + - @backstage/plugin-home@0.4.26-next.1 + - @backstage/plugin-jenkins@0.7.9-next.1 + - @backstage/plugin-kafka@0.3.10-next.1 + - @backstage/plugin-kubernetes@0.7.3-next.1 + - @backstage/plugin-lighthouse@0.3.10-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.3-next.1 + - @backstage/plugin-org@0.5.10-next.1 + - @backstage/plugin-pagerduty@0.5.3-next.1 + - @backstage/plugin-playlist@0.1.1-next.1 + - @backstage/plugin-rollbar@0.4.10-next.1 + - @backstage/plugin-search@1.0.3-next.1 + - @backstage/plugin-sentry@0.4.3-next.1 + - @backstage/plugin-techdocs@1.3.3-next.1 + - @backstage/plugin-todo@0.2.12-next.1 + - @backstage/app-defaults@1.0.7-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/plugin-apache-airflow@0.2.3-next.1 + - @backstage/plugin-cost-insights@0.11.32-next.1 + - @backstage/plugin-gcp-projects@0.3.29-next.1 + - @backstage/plugin-graphiql@0.2.42-next.1 + - @backstage/plugin-newrelic@0.3.28-next.1 + - @backstage/plugin-shortcuts@0.3.2-next.1 + - @backstage/plugin-stack-overflow@0.1.6-next.1 + - @backstage/plugin-tech-radar@0.5.17-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.7-next.1 + - @backstage/plugin-permission-react@0.4.6-next.1 + - @backstage/plugin-techdocs-react@1.0.5-next.1 + +## example-backend@0.2.76-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.17.0-next.1 + - @backstage/plugin-search-backend@1.1.0-next.1 + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-scaffolder-backend@1.7.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-search-backend-node@1.0.3-next.1 + - @backstage/plugin-search-backend-module-pg@0.4.1-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.1 + - @backstage/plugin-kubernetes-backend@0.7.3-next.1 + - @backstage/plugin-tech-insights-backend@0.5.3-next.1 + - example-app@0.2.76-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/plugin-app-backend@0.3.37-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-azure-devops-backend@0.3.16-next.1 + - @backstage/plugin-badges-backend@0.1.31-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + - @backstage/plugin-code-coverage-backend@0.2.3-next.1 + - @backstage/plugin-graphql-backend@0.1.27-next.1 + - @backstage/plugin-jenkins-backend@0.1.27-next.1 + - @backstage/plugin-kafka-backend@0.2.30-next.1 + - @backstage/plugin-permission-backend@0.5.12-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + - @backstage/plugin-playlist-backend@0.1.1-next.1 + - @backstage/plugin-proxy-backend@0.2.31-next.1 + - @backstage/plugin-rollbar-backend@0.1.34-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.1 + - @backstage/plugin-tech-insights-node@0.3.5-next.1 + - @backstage/plugin-techdocs-backend@1.3.1-next.1 + - @backstage/plugin-todo-backend@0.1.34-next.1 + +## example-backend-next@0.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.1 + - @backstage/backend-defaults@0.1.2-next.1 + - @backstage/plugin-app-backend@0.3.37-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## techdocs-cli-embedded-app@0.2.75-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.1 + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/cli@0.20.0-next.1 + - @backstage/plugin-techdocs@1.3.3-next.1 + - @backstage/app-defaults@1.0.7-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/test-utils@1.2.1-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.1 + +## @internal/plugin-catalog-customized@0.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-catalog@1.6.0-next.1 + +## @internal/plugin-todo-list@1.0.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## @internal/plugin-todo-list-backend@1.0.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + +## @internal/plugin-todo-list-common@1.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.6.5-next.1 diff --git a/docs/releases/v1.7.0-next.2-changelog.md b/docs/releases/v1.7.0-next.2-changelog.md new file mode 100644 index 0000000000..d5a9b0ddad --- /dev/null +++ b/docs/releases/v1.7.0-next.2-changelog.md @@ -0,0 +1,2127 @@ +# Release v1.7.0-next.2 + +## @backstage/plugin-bazaar-backend@0.2.0-next.2 + +### Minor Changes + +- 8554533546: **BREAKING** The bazaar-backend `createRouter` now requires that the `identityApi` is passed to the router. + + These changes are **required** to `packages/backend/src/plugins/bazaar.ts` + + The user entity ref is now added to the members table and is taken from the requesting user using the `identityApi`. + + ```diff + import { PluginEnvironment } from '../types'; + import { createRouter } from '@backstage/plugin-bazaar-backend'; + import { Router } from 'express'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + logger: env.logger, + config: env.config, + database: env.database, + + identity: env.identity, + }); + } + ``` + +### Patch Changes + +- f7c2855d76: Router now also has endpoint `getLatestProjects` that takes a limit of projects as prop. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-catalog-backend@1.5.0-next.2 + +### Minor Changes + +- b2e6cb6acf: Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. + + Moved the following types from this package to `@backstage/plugin-catalog-backend`. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- eb25f7e12d: The exported permission rules and the API of `createCatalogConditionalDecision` have changed to reflect the breaking changes made to the `PermissionRule` type. Note that all involved types are exported from `@backstage/plugin-catalog-backend/alpha` + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + +## @backstage/plugin-catalog-import@0.9.0-next.2 + +### Minor Changes + +- b2e6cb6acf: **Breaking** + Moved the code search for the existing catalog-info.yaml files to the backend from the frontend. It means it will use the configured GitHub integration's credentials. + + Add the following to your `CatalogBuilder` to have the repo URL ingestion working again. + + ```ts + // catalog.ts + import { GitHubLocationAnalyzer } from '@backstage/plugin-catalog-backend-module-github'; + ... + builder.addLocationAnalyzers( + new GitHubLocationAnalyzer({ + discovery: env.discovery, + config: env.config, + }), + ); + ... + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + +## @backstage/plugin-catalog-node@1.2.0-next.2 + +### Minor Changes + +- 404366c853: Deprecated the `LocationSpec` type. It got moved from this package to the `@backstage/plugin-catalog-common` so make sure imports are updated. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-permission-common@0.7.0-next.2 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-permission-node@0.7.0-next.2 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-playlist-backend@0.2.0-next.2 + +### Minor Changes + +- eb25f7e12d: **BREAKING** The exported permission rules have changed to reflect the breaking changes made to the PermissionRule type. + + For example, the `playlistConditions.isOwner` API has changed from: + + ```ts + playlistConditions.isOwner(['user:default/me', 'group:default/owner']); + ``` + + to: + + ```ts + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-playlist-common@0.1.1-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-scaffolder-backend@1.7.0-next.2 + +### Minor Changes + +- 17ff77154c: Update the `github:publish` action to allow passing whether pull + requests must be up to date with the default branch before merging. +- a8e9848479: Added optional `sourcePath` parameter to `publish:gitlab:merge-request` action, `targetPath` is now optional and falls back to current workspace path. + +### Patch Changes + +- 4880d43e25: Fixed setting default branch for Bitbucket Server +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + +## @backstage/plugin-techdocs-backend@1.4.0-next.2 + +### Minor Changes + +- 7ced1b4076: Add optional `catalogClient` argument to `createRoute` parameters + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-techdocs-node@1.4.1-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/app-defaults@1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/backend-app-api@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/backend-common@0.15.2-next.2 + +### Patch Changes + +- c44cf412de: Fix BitBucket server integration +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/backend-defaults@0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.2.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + +## @backstage/backend-plugin-api@0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/backend-tasks@0.3.6-next.2 + +### Patch Changes + +- d4fea86ea3: Added new function `readTaskScheduleDefinitionFromConfig` to read `TaskScheduleDefinition` (aka. schedule) from the `Config`. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/backend-test-utils@0.1.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/cli@0.20.0-next.2 + - @backstage/backend-app-api@0.2.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/catalog-client@1.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/catalog-model@1.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/cli@0.20.0-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/release-manifests@0.0.6 + - @backstage/types@1.0.0 + +## @backstage/codemods@0.1.40-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/config@1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/config-loader@1.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/core-app-api@1.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-components@0.11.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + +## @backstage/core-plugin-api@1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/create-app@0.4.32-next.2 + +### Patch Changes + +- 01dff06be4: Leverage cache mounts in Dockerfile during `yarn install ...` and `apt-get ...` commands to speed up repeated builds. +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## @backstage/dev-utils@1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + +## @backstage/errors@1.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/integration@1.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/integration-react@1.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @techdocs/cli@1.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-techdocs-node@1.4.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + +## @backstage/test-utils@1.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-adr@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-adr-common@0.2.2-next.2 + - @backstage/plugin-search-react@1.2.0-next.2 + +## @backstage/plugin-adr-backend@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/plugin-adr-common@0.2.2-next.2 + +## @backstage/plugin-adr-common@0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-airbrake@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/dev-utils@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-airbrake-backend@0.2.10-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-allure@0.1.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-analytics-module-ga@0.1.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apache-airflow@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + +## @backstage/plugin-api-docs@0.8.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-apollo-explorer@0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-app-backend@0.3.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-auth-backend@0.17.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-auth-node@0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-azure-devops@0.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-azure-devops-backend@0.3.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/plugin-azure-devops-common@0.3.0 + +## @backstage/plugin-badges@0.2.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-badges-backend@0.1.31-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-bazaar@0.1.25-next.2 + +### Patch Changes + +- f7c2855d76: Added a `Overview Card` for either latest or random projects. Changed `ProjectPreview.tsx` so it take `gridSize` and `useTablePagination` as props. +- c0352bbc69: Link to the user catalog entity of a member +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-bitrise@0.1.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog@1.6.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-search-react@1.2.0-next.2 + +## @backstage/plugin-catalog-backend-module-aws@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-azure@0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.2.4-next.2 + +### Patch Changes + +- 23f9199a0f: Deprecate `@backstage/plugin-catalog-backend-module-bitbucket`. + + Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` + or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-cloud@0.1.4-next.2 + +### Patch Changes + +- f66e696e7b: Bitbucket Cloud provider: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + + +- a9b91d39bb: Add `bitbucketCloudCatalogModule` (new backend-plugin-api, alpha). + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + +## @backstage/plugin-catalog-backend-module-bitbucket-server@0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-catalog-backend-module-github@0.1.8-next.2 + +### Patch Changes + +- 7022aebf35: Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. +- 7edb5909e8: Add missing config schema for the `GitHubEntityProvider`. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-catalog-backend-module-openapi@0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-common@1.0.7-next.2 + +### Patch Changes + +- 823acaa88b: Moved the following types from `@backstage/plugin-catalog-backend` to this package. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + +## @backstage/plugin-catalog-graph@0.2.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-catalog-graphql@0.3.14-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-catalog-react@1.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-cicd-statistics@0.1.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/plugin-cicd-statistics@0.1.12-next.2 + +## @backstage/plugin-circleci@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-cloudbuild@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-climate@0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-code-coverage-backend@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-codescene@0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-config-schema@0.1.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-cost-insights@0.11.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + +## @backstage/plugin-dynatrace@1.0.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-explore@0.3.41-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-explore-react@0.0.22-next.2 + +## @backstage/plugin-explore-react@0.0.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.2 + +## @backstage/plugin-firehydrant@0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-fossa@0.2.42-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcalendar@0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gcp-projects@0.3.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-git-release-manager@0.3.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-actions@0.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-deployments@0.1.41-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-issues@0.1.2-next.2 + +### Patch Changes + +- df226e124c: Add filtering and ordering to the graphql query +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-github-pull-requests-board@0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gitops-profiles@0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-gocd@0.1.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphiql@0.2.42-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-graphql-backend@0.1.27-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/plugin-catalog-graphql@0.3.14-next.2 + +## @backstage/plugin-home@0.4.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-stack-overflow@0.1.6-next.2 + +## @backstage/plugin-ilert@0.1.36-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-jenkins@0.7.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-jenkins-common@0.1.9-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-jenkins-backend@0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-jenkins-common@0.1.9-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-jenkins-common@0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + +## @backstage/plugin-kafka@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-kafka-backend@0.2.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-kubernetes@0.7.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-kubernetes-common@0.4.3-next.2 + +## @backstage/plugin-kubernetes-backend@0.7.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-kubernetes-common@0.4.3-next.2 + +## @backstage/plugin-kubernetes-common@0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + +## @backstage/plugin-lighthouse@0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic@0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-newrelic-dashboard@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-org@0.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-pagerduty@0.5.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop@0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-periskop-backend@0.1.8-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-permission-backend@0.5.12-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-permission-react@0.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + +## @backstage/plugin-playlist@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/plugin-playlist-common@0.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + +## @backstage/plugin-playlist-common@0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + +## @backstage/plugin-proxy-backend@0.2.31-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-rollbar@0.4.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-rollbar-backend@0.1.34-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-scaffolder@1.7.0-next.2 + +### Patch Changes + +- 92e490d6b4: Make the `/next` scaffolder work end to end with the old `TaskPage` view +- 1047baa926: Bump to `react-jsonschema-form@v5-beta` for the `NextRouter` under `@alpha` exports +- 98ae18b68f: Fixed a bug where the `allowed*` values for the `RepoUrlPicker` would be reset on render. +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-scaffolder-common@1.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search@1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-react@1.2.0-next.2 + +## @backstage/plugin-search-backend@1.1.0-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-search-backend-module-pg@0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-search-backend-node@1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-search-common@1.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-search-react@1.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-sentry@0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-shortcuts@0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-sonarqube@0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-sonarqube-backend@0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-splunk-on-call@0.3.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-stack-overflow@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-home@0.4.26-next.2 + +## @backstage/plugin-stack-overflow-backend@0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + +## @backstage/plugin-tech-insights@0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## @backstage/plugin-tech-insights-backend@0.5.3-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## @backstage/plugin-tech-insights-common@0.2.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## @backstage/plugin-tech-insights-node@0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## @backstage/plugin-tech-radar@0.5.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-techdocs@1.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## @backstage/plugin-techdocs-node@1.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-techdocs-react@1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/version-bridge@1.0.1 + +## @backstage/plugin-todo@0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-todo-backend@0.1.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## @backstage/plugin-user-settings@0.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## @backstage/plugin-user-settings-backend@0.1.1-next.2 + +### Patch Changes + +- f3463b176b: Use `Response.status` instead of `.send(number)` +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## @backstage/plugin-vault@0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## @backstage/plugin-vault-backend@0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @backstage/plugin-xcmetrics@0.2.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## example-app@0.2.76-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.7.0-next.2 + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-import@0.9.0-next.2 + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-playlist@0.1.1-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-airbrake@0.3.10-next.2 + - @backstage/plugin-apache-airflow@0.2.3-next.2 + - @backstage/plugin-api-docs@0.8.10-next.2 + - @backstage/plugin-azure-devops@0.2.1-next.2 + - @backstage/plugin-badges@0.2.34-next.2 + - @backstage/plugin-catalog-graph@0.2.22-next.2 + - @backstage/plugin-circleci@0.3.10-next.2 + - @backstage/plugin-cloudbuild@0.3.10-next.2 + - @backstage/plugin-code-coverage@0.2.3-next.2 + - @backstage/plugin-cost-insights@0.11.32-next.2 + - @backstage/plugin-dynatrace@1.0.0-next.2 + - @backstage/plugin-explore@0.3.41-next.2 + - @backstage/plugin-gcalendar@0.3.6-next.2 + - @backstage/plugin-gcp-projects@0.3.29-next.2 + - @backstage/plugin-github-actions@0.5.10-next.2 + - @backstage/plugin-gocd@0.1.16-next.2 + - @backstage/plugin-graphiql@0.2.42-next.2 + - @backstage/plugin-home@0.4.26-next.2 + - @backstage/plugin-jenkins@0.7.9-next.2 + - @backstage/plugin-kafka@0.3.10-next.2 + - @backstage/plugin-kubernetes@0.7.3-next.2 + - @backstage/plugin-lighthouse@0.3.10-next.2 + - @backstage/plugin-newrelic@0.3.28-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.3-next.2 + - @backstage/plugin-org@0.5.10-next.2 + - @backstage/plugin-pagerduty@0.5.3-next.2 + - @backstage/plugin-rollbar@0.4.10-next.2 + - @backstage/plugin-search@1.0.3-next.2 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-sentry@0.4.3-next.2 + - @backstage/plugin-shortcuts@0.3.2-next.2 + - @backstage/plugin-stack-overflow@0.1.6-next.2 + - @backstage/plugin-tech-insights@0.3.1-next.2 + - @backstage/plugin-tech-radar@0.5.17-next.2 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + - @backstage/plugin-todo@0.2.12-next.2 + - @backstage/plugin-user-settings@0.5.0-next.2 + - @internal/plugin-catalog-customized@0.0.3-next.2 + +## example-backend@0.2.76-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/plugin-playlist-backend@0.2.0-next.2 + - @backstage/plugin-badges-backend@0.1.31-next.2 + - @backstage/plugin-graphql-backend@0.1.27-next.2 + - @backstage/plugin-permission-backend@0.5.12-next.2 + - @backstage/plugin-rollbar-backend@0.1.34-next.2 + - @backstage/plugin-search-backend@1.1.0-next.2 + - @backstage/plugin-tech-insights-backend@0.5.3-next.2 + - @backstage/plugin-techdocs-backend@1.4.0-next.2 + - example-app@0.2.76-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/plugin-app-backend@0.3.37-next.2 + - @backstage/plugin-auth-backend@0.17.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-azure-devops-backend@0.3.16-next.2 + - @backstage/plugin-code-coverage-backend@0.2.3-next.2 + - @backstage/plugin-jenkins-backend@0.1.27-next.2 + - @backstage/plugin-kafka-backend@0.2.30-next.2 + - @backstage/plugin-kubernetes-backend@0.7.3-next.2 + - @backstage/plugin-proxy-backend@0.2.31-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.2 + - @backstage/plugin-search-backend-module-pg@0.4.1-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.2 + - @backstage/plugin-todo-backend@0.1.34-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + +## example-backend-next@0.0.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/plugin-app-backend@0.3.37-next.2 + - @backstage/backend-defaults@0.1.2-next.2 + +## techdocs-cli-embedded-app@0.2.75-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## @internal/plugin-catalog-customized@0.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + +## @internal/plugin-todo-list@1.0.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## @internal/plugin-todo-list-backend@1.0.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## @internal/plugin-todo-list-common@1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 diff --git a/docs/releases/v1.7.0.md b/docs/releases/v1.7.0.md new file mode 100644 index 0000000000..cb87ead4c9 --- /dev/null +++ b/docs/releases/v1.7.0.md @@ -0,0 +1,125 @@ +--- +id: v1.7.0 +title: v1.7.0 +description: Backstage Release v1.7.0 +--- + +These are the release notes for the v1.7.0 release of +[Backstage](https://backstage.io/). + +A huge thanks to the whole team of maintainers and contributors as well as the +amazing Backstage Community for the hard work in getting this release developed +and done. + +## Highlights + +### GitHub Catalog Import now Powered by the Backend + +The analysis performed during catalog imports (i.e. when supplying the URL of a +repository rather than an individual YAML file in the Create flow) is now +powered by the backend rather than frontend code. This means that the catalog +backend needs to be supplied with a location analyzer for this use case to +continue to function. + +If you want to make use of this feature, check out the installation instructions +in [the +changelog](https://github.com/backstage/backstage/blob/master/plugins/catalog-import/CHANGELOG.md#090). + +Contributed by [@kissmikijr](https://github.com/kissmikijr) in +[#13800](https://github.com/backstage/backstage/pull/13800) + +### Permission Rule Changes + +When defining permission rules, it's now necessary to provide a [Zod +Schema](https://github.com/colinhacks/zod) that specifies the parameters the +rule expects. This has been added to help better describe the parameters in the +response of the metadata endpoint and to validate the parameters before a rule +is executed. The signatures of the rule methods (`apply` and `toQuery`) have +changed slightly as well. + +You can read more about this in [the permissions +documentation](https://backstage.io/docs/permissions/overview) and [the +changelog](https://github.com/backstage/backstage/blob/master/plugins/permission-node/CHANGELOG.md#070). + +### Migration: `jest` v29 + +Both `jest`, `jest-runtime`, and `jest-environment-jsdom` as used by the +Backstage CLI were bumped to version 29. This is up from version 27, so check +out both the [v28](https://jestjs.io/docs/28.x/upgrading-to-jest28) and +[v29](https://jestjs.io/docs/upgrading-to-jest29) (later +[here](https://jestjs.io/docs/29.x/upgrading-to-jest29)) migration guides, since +your existing tests may be affected. + +Particular changes that were encountered in the main Backstage repository are: + +- The updated snapshot format. +- `jest.useFakeTimers('legacy')` is now `jest.useFakeTimers({ legacyFakeTimers: true })`. +- Error objects collected by `withLogCollector` from `@backstage/test-utils` are + now objects with a `detail` property rather than a string. + +### Migration: `react-router` v6 + +Newly created Backstage repositories now use the stable version 6 of +`react-router`, just like the main repository does. + +Migrating to the stable version of `react-router` is optional for the time +being; Backstage has support for both versions. But if you want to do the same +for your existing repository, please follow [this +guide](https://backstage.io/docs/tutorials/react-router-stable-migration). +Support for the beta version will be removed in a later release. + +### Support for `__mocks__` and `__testUtils__` directories + +The Backstage CLI now has built-in support for `__mocks__` and `__testUtils__` +directories in your code. These can be used for mocks and shared utilities in +tests. + +### New Arguments for the Router of `@backstage/plugin-bazaar-backend` + +The bazaar-backend `createRouter` function now requires that the `identityApi` +is passed to the router. + +### Deprecated plugin: `@backstage/plugin-catalog-backend-module-bitbucket` + +This has been deprecated and split into +`@backstage/plugin-catalog-backend-module-bitbucket-cloud` and +`@backstage/plugin-catalog-backend-module-bitbucket-server`, for BitBucket Cloud +and BitBucket Server respectively. Please update your dependencies accordingly, +depending on which product you use. + +The original package will be removed in a future release. + +Contributed by [@pjungermann](https://github.com/pjungermann) in +[#14070](https://github.com/backstage/backstage/pull/14070) + +## Security Fixes + +This release does not contain any security fixes. + +## Upgrade path + +We recommend that you keep your Backstage project up to date with this latest +release. For more guidance on how to upgrade, check out the documentation for +[keeping Backstage +updated](https://backstage.io/docs/getting-started/keeping-backstage-updated). + +## Links and References + +Below you can find a list of links and references to help you learn about and +start using this new release. + +- [Backstage official website](https://backstage.io/), + [documentation](https://backstage.io/docs/), and [getting started + guide](https://backstage.io/docs/getting-started/) +- [GitHub repository](https://github.com/backstage/backstage) +- Backstage's [versioning and support + policy](https://backstage.io/docs/overview/versioning-policy) +- [Community Discord](https://discord.gg/bFESRKVt) for discussions and support +- [Changelog](https://github.com/backstage/backstage/tree/master/docs/releases/v1.7.0-changelog.md) +- Backstage [Demos](https://backstage.io/demos), + [Blog](https://backstage.io/blog), + [Roadmap](https://backstage.io/docs/overview/roadmap) and + [Plugins](https://backstage.io/plugins) + +Sign up for our [newsletter](https://mailchi.mp/spotify/backstage-community) if +you want to be informed about what is happening in the world of Backstage. diff --git a/docs/tutorials/backend-to-backend-auth.md b/docs/tutorials/backend-to-backend-auth.md index 7de751be77..607356927c 100644 --- a/docs/tutorials/backend-to-backend-auth.md +++ b/docs/tutorials/backend-to-backend-auth.md @@ -4,68 +4,4 @@ title: Backend-to-Backend Authentication description: Guide for authenticating API requests between Backstage plugin backends --- -This tutorial describes the steps needed to handle _backend-to-backend -authentication_, which allows plugin backends to determine whether a given -request originates from a legitimate Backstage backend by verifying a token -signed with a shared secret. This system has limited use for now, but will be -needed to support the upcoming framework for permissions and authorization (see -[the PRFC on the topic](https://github.com/backstage/backstage/pull/7761) for -more details). - -Backends have no concept of a Backstage identity, so instead they use a token -generated using a shared key stored in config. You can generate a unique key for -your app in a terminal, and set the `BACKEND_SECRET` environment variable to the -resulting value. - -```bash -node -p 'require("crypto").randomBytes(24).toString("base64")' -``` - -**NOTE**: For ease of development, we auto-generate a key for you if you haven't -configured a secret in dev mode. You _must set your own secret_ in order for -backend-to-backend authentication to work in production. - -Requests originating from a backend plugin can be authenticated by decorating -them with a backend token. Backend tokens can be generated using a -`TokenManager`, which can be passed to plugin backends via the -`PluginEnvironment`. The `TokenManager` provided in new Backstage instances -generated by `create-app` is a stub, which returns empty tokens and accepts any -input string as valid. To enable backend-to-backend authentication, you'll need -to instantiate a new one using the secret from your config instead: - -```diff -// packages/backend/src/index.ts - -function makeCreateEnv(config: Config) { - const root = getRootLogger(); - const reader = UrlReaders.default({ logger: root, config }); - const discovery = SingleHostDiscovery.fromConfig(config); - - root.info(`Created UrlReader ${reader}`); - - const cacheManager = CacheManager.fromConfig(config); - const databaseManager = DatabaseManager.fromConfig(config); -- const tokenManager = ServerTokenManager.noop(); -+ const tokenManager = ServerTokenManager.fromConfig(config, { logger: root }); -``` - -With this `tokenManager`, you can then generate a server token for requests: - -```typescript -const { token } = await this.tokenManager.getToken(); - -const response = await fetch(pluginBackendApiUrl, { - method: 'GET', - headers: { - ...headers, - Authorization: `Bearer ${token}`, - }, -}); -``` - -You can use the same `tokenManager` to authenticate tokens supplied on incoming -requests: - -```typescript -await tokenManager.authenticate(token); // throws if token is invalid -``` +See [new docs](../auth/service-to-service-auth.md) diff --git a/docs/tutorials/react-router-stable-migration.md b/docs/tutorials/react-router-stable-migration.md index 9636345116..bb01157bdb 100644 --- a/docs/tutorials/react-router-stable-migration.md +++ b/docs/tutorials/react-router-stable-migration.md @@ -178,7 +178,7 @@ When migrating over to React Router v6 stable, you might also see browser consol ```diff - -+ } /> ++ } /> ``` ### `NavLink` diff --git a/docs/tutorials/switching-sqlite-postgres.md b/docs/tutorials/switching-sqlite-postgres.md index c0c35999c6..bd067ee388 100644 --- a/docs/tutorials/switching-sqlite-postgres.md +++ b/docs/tutorials/switching-sqlite-postgres.md @@ -72,6 +72,12 @@ backend: + port: ${POSTGRES_PORT} + user: ${POSTGRES_USER} + password: ${POSTGRES_PASSWORD} ++ # https://node-postgres.com/features/ssl ++ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable ++ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) ++ # ssl: ++ # ca: # if you have a CA file and want to verify it you can uncomment this section ++ # $file: /ca/server.crt + # Refer to Tarn docs for default values on PostgreSQL pool configuration - https://github.com/Vincit/tarn.js + knexConfig: + pool: @@ -79,12 +85,6 @@ backend: + max: 12 + acquireTimeoutMillis: 60000 + idleTimeoutMillis: 60000 -+ # https://node-postgres.com/features/ssl -+ # you can set the sslmode configuration option via the `PGSSLMODE` environment variable -+ # see https://www.postgresql.org/docs/current/libpq-ssl.html Table 33.1. SSL Mode Descriptions (e.g. require) -+ # ssl: -+ # ca: # if you have a CA file and want to verify it you can uncomment this section -+ # $file: /ca/server.crt ``` ### Using a single database diff --git a/docs/tutorials/yarn-migration.md b/docs/tutorials/yarn-migration.md index 7f898c7b98..6dee343e78 100644 --- a/docs/tutorials/yarn-migration.md +++ b/docs/tutorials/yarn-migration.md @@ -67,6 +67,13 @@ COPY .yarn ./.yarn COPY .yarnrc.yml ./ ``` +In a multi-stage `Dockerfile`, each stage that runs a `yarn` command will also need the Yarn 3 installation. For example, in the final stage you may need to add the following: + +```Dockerfile +COPY --from=build --chown=node:node /app/.yarn ./.yarn +COPY --from=build --chown=node:node /app/.yarnrc.yml ./ +``` + The `--production` flag to `yarn install` has been removed in Yarn 3, instead you need to use `yarn workspaces focus --all --production` to avoid installing development dependencies in your production deployment. A tradeoff of this is that `yarn workspaces focus` does not support the `--immutable` flag. ```Dockerfile diff --git a/microsite/data/on-demand/20220817-1.yaml b/microsite/data/on-demand/20220817-1.yaml index c0973656b4..f67c8cf4cd 100644 --- a/microsite/data/on-demand/20220817-1.yaml +++ b/microsite/data/on-demand/20220817-1.yaml @@ -1,9 +1,7 @@ --- title: Adopters Community Sessions date: August 17, 2022 -category: Upcoming +category: Meetup description: Adopters Community Session ✨. It's the monthly meetup where we all come together to listen to the latest maintainer updates, learn from each other about adopting, share exciting new demos or discuss any relevant topic like developer effectiveness, developer experience, developer portals, etc. youtubeUrl: https://youtu.be/qYnvc8ge1kg youtubeImgUrl: https://backstage.io/img/b-sessions.png -rsvpUrl: https://calendar.google.com/calendar/embed?src=c_qup9gbhn9sqpuao6trttd8mk5s@group.calendar.google.com -eventUrl: https://github.com/backstage/community/issues/54 diff --git a/microsite/data/on-demand/20220824-1.yaml b/microsite/data/on-demand/20220824-1.yaml index 411b87c74f..a2387a8aad 100644 --- a/microsite/data/on-demand/20220824-1.yaml +++ b/microsite/data/on-demand/20220824-1.yaml @@ -1,9 +1,7 @@ --- title: Contributor Community Sessions date: August 24, 2022 -category: Upcoming +category: Meetup description: Join the maintainers and contributors for the Contributor Community Sessions -youtubeUrl: https://youtu.be/qYnvc8ge1kg +youtubeUrl: https://youtu.be/8ydEFFiuHAc youtubeImgUrl: https://backstage.io/img/b-sessions.png -rsvpUrl: https://calendar.google.com/calendar/embed?src=c_qup9gbhn9sqpuao6trttd8mk5s@group.calendar.google.com -eventUrl: https://github.com/backstage/community/issues/54 diff --git a/microsite/data/on-demand/20220921-1.yaml b/microsite/data/on-demand/20220921-1.yaml new file mode 100644 index 0000000000..fe3dda08c0 --- /dev/null +++ b/microsite/data/on-demand/20220921-1.yaml @@ -0,0 +1,7 @@ +--- +title: Adopters Community Sessions +date: September 21, 2022 +category: Meetup +description: Adopters Community Session ✨. It's the monthly meetup where we all come together to listen to the latest maintainer updates, learn from each other about adopting, share exciting new demos or discuss any relevant topic like developer effectiveness, developer experience, developer portals, etc. +youtubeUrl: https://youtu.be/K44RQAVWWnY +youtubeImgUrl: https://backstage.io/img/b-sessions.png diff --git a/microsite/data/on-demand/20220928-1.yaml b/microsite/data/on-demand/20220928-1.yaml new file mode 100644 index 0000000000..31ae752e3a --- /dev/null +++ b/microsite/data/on-demand/20220928-1.yaml @@ -0,0 +1,7 @@ +--- +title: Contributor Community Sessions +date: September 28, 2022 +category: Meetup +description: Join the maintainers and contributors for the Contributor Community Sessions +youtubeUrl: https://youtu.be/jmNT5x3mKaQ +youtubeImgUrl: https://backstage.io/img/b-sessions.png diff --git a/microsite/data/plugins/azure-sites.yaml b/microsite/data/plugins/azure-sites.yaml new file mode 100644 index 0000000000..3ea9d0439b --- /dev/null +++ b/microsite/data/plugins/azure-sites.yaml @@ -0,0 +1,8 @@ +--- +title: Azure Sites +author: FRISS +authorUrl: https://friss.com +category: Infrastructure +description: Azure Sites (Apps & Functions) support for a given entity. View the current status of the site, quickly jump to site's Overview page, or Log Stream page. +documentation: https://github.com/backstage/backstage/tree/master/plugins/azure-sites +npmPackageName: '@backstage/plugin-azure-sites' diff --git a/microsite/data/plugins/gke-usage.yaml b/microsite/data/plugins/gke-usage.yaml deleted file mode 100644 index 9668b18fdf..0000000000 --- a/microsite/data/plugins/gke-usage.yaml +++ /dev/null @@ -1,15 +0,0 @@ ---- -title: GKE Usage -author: BESTSELLER -authorUrl: https://bestsellerit.com -category: Discovery -description: This plugin will show you the cost and resource usage of your application within Google Kubernetes Engine (GKE). -documentation: https://github.com/BESTSELLER/backstage-plugin-gkeusage/blob/master/README.md -iconUrl: img/gke-logo.png -npmPackageName: '@bestsellerit/backstage-plugin-gkeusage' -tags: - - gke - - cost - - google - - usage - - metering diff --git a/microsite/data/plugins/score-card.yaml b/microsite/data/plugins/score-card.yaml new file mode 100644 index 0000000000..040d94ed44 --- /dev/null +++ b/microsite/data/plugins/score-card.yaml @@ -0,0 +1,9 @@ +--- +title: System scoring +author: Oriflame +authorUrl: https://github.com/Oriflame +category: Quality +description: Visualization of maturity (and improving it) of services/systems thanks to a review process. +documentation: https://github.com/Oriflame/backstage-plugins/tree/main/plugins/score-card +iconUrl: img/score-card-plugin-logo.png +npmPackageName: '@oriflame/backstage-plugin-score-card' diff --git a/microsite/pages/en/plugins.js b/microsite/pages/en/plugins.js index de336444ab..91f4138174 100644 --- a/microsite/pages/en/plugins.js +++ b/microsite/pages/en/plugins.js @@ -16,11 +16,30 @@ const { const pluginsDirectory = require('path').join(process.cwd(), 'data/plugins'); const pluginMetadata = fs .readdirSync(pluginsDirectory) - .map(file => yaml.load(fs.readFileSync(`./data/plugins/${file}`, 'utf8'))) + .map(file => { + const fileContent = fs.readFileSync(`./data/plugins/${file}`, 'utf8'); + let metadata = yaml.load(fileContent); + + const gitIsoDate = require('child_process') + .execFileSync('git', [ + 'log', + '-1', + '--format="%ai"', + '--reverse', + `./data/plugins/${file}`, + ]) + .toString(); + + metadata.date = new Date(gitIsoDate); + + return metadata; + }) .sort((a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase())); const truncate = text => text.length > 170 ? text.substr(0, 170) + '...' : text; +const newForDays = 100; + const addPluginDocsLink = '/docs/plugins/add-to-marketplace'; const defaultIconUrl = 'img/logo-gradient-on-dark.svg'; @@ -100,8 +119,15 @@ const Plugins = () => ( authorUrl, documentation, category, + date, }) => (
+ {Math.trunc((Date.now() - date) / (1000 * 60 * 60 * 24)) < + newForDays && ( +
+ NEW +
+ )}
{title} diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 41e54ccb29..0826541071 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -1,6 +1,7 @@ { "releases": { "Release Notes": [ + "releases/v1.7.0", "releases/v1.6.0", "releases/v1.5.0", "releases/v1.4.0", @@ -97,6 +98,7 @@ "features/software-templates/builtin-actions", "features/software-templates/writing-custom-actions", "features/software-templates/writing-custom-field-extensions", + "features/software-templates/writing-custom-step-layouts", "features/software-templates/migrating-from-v1beta2-to-v1beta3" ] }, @@ -225,6 +227,7 @@ "plugins/composability", "plugins/customization", "plugins/analytics", + "plugins/feature-flags", { "type": "subcategory", "label": "Backends and APIs", @@ -277,7 +280,9 @@ }, "auth/identity-resolver", "auth/oauth", + "auth/oidc", "auth/add-auth-provider", + "auth/service-to-service-auth", "auth/troubleshooting", "auth/glossary" ], @@ -334,7 +339,6 @@ "tutorials/configuring-plugin-databases", "tutorials/switching-sqlite-postgres", "tutorials/using-backstage-proxy-within-plugin", - "tutorials/backend-to-backend-auth", "tutorials/yarn-migration" ], "Architecture Decision Records (ADRs)": [ diff --git a/microsite/static/css/plugins.css b/microsite/static/css/plugins.css index b9aebfa84c..55a0c6df40 100644 --- a/microsite/static/css/plugins.css +++ b/microsite/static/css/plugins.css @@ -4,6 +4,7 @@ padding: 16px; display: flex; flex-direction: column; + position: relative; } .PluginGrid { @@ -141,3 +142,31 @@ #add-plugin-card { border: 1px solid #69ddc7; } + +/* RIBBON */ + +.ribbon { + width: 80px; + height: 80px; + overflow: hidden; + position: absolute; +} +.ribbon span { + position: absolute; + display: block; + width: 140px; + padding: 4px 0; + background-color: rgb(54, 186, 162, 0.85); + box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1); + color: #fff; + text-align: center; +} +.ribbon-top-right { + top: 0; + right: 0; +} +.ribbon-top-right span { + left: -17px; + top: 11px; + transform: rotate(45deg); +} diff --git a/microsite/static/img/score-card-plugin-logo.png b/microsite/static/img/score-card-plugin-logo.png new file mode 100644 index 0000000000..d1bed73217 Binary files /dev/null and b/microsite/static/img/score-card-plugin-logo.png differ diff --git a/mkdocs.yml b/mkdocs.yml index c0a8e3a182..36ff1c6348 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -64,6 +64,7 @@ nav: - Writing Templates: 'features/software-templates/writing-templates.md' - Builtin Actions: 'features/software-templates/builtin-actions.md' - Writing Custom Actions: 'features/software-templates/writing-custom-actions.md' + - Writing Custom Step Layouts: 'features/software-templates/writing-custom-step-layouts.md' - Writing Templates (Legacy): 'features/software-templates/legacy.md' - Migrating from v1alpha1 to v1beta2 templates: 'features/software-templates/migrating-from-v1alpha1-to-v1beta2.md' - Backstage Search: @@ -162,7 +163,9 @@ nav: - Bitbucket: 'auth/bitbucket/provider.md' - Sign in resolvers: 'auth/identity-resolver.md' - OAuth and OpenID Connect: 'auth/oauth.md' + - OIDC provider from scratch: 'auth/oidc.md' - Contributing New Providers: 'auth/add-auth-provider.md' + - Service to Service Auth: 'auth/service-to-service-auth.md' - Troubleshooting Auth: 'auth/troubleshooting.md' - Glossary: 'auth/glossary.md' - Deployment: diff --git a/package.json b/package.json index 31b0c194d2..85c4fa6e23 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "build": "backstage-cli repo build --all", "build:api-reports": "yarn build:api-reports:only --tsc", "build:api-reports:only": "ts-node -T -P scripts/tsconfig.json scripts/api-extractor.ts", - "build:api-docs": "yarn build:api-reports --docs", + "build:api-docs": "LANG=en_EN yarn build:api-reports --docs", "tsc": "tsc", "tsc:full": "backstage-cli repo clean && tsc --skipLibCheck false --incremental false", "clean": "backstage-cli repo clean", @@ -44,8 +44,9 @@ "@types/react": "^17", "@types/react-dom": "^17" }, - "version": "1.6.0", + "version": "1.7.0", "dependencies": { + "@backstage/errors": "workspace:^", "@manypkg/get-packages": "^1.1.3", "@microsoft/api-documenter": "^7.17.11", "@microsoft/api-extractor": "^7.23.0", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index 6d9d3fc029..2dcaaa362a 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/app-defaults +## 1.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6 + +## 1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 1.0.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6-next.1 + +## 1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6-next.0 + ## 1.0.6 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 0742d2cb13..de27f276a6 100644 --- a/packages/app-defaults/package.json +++ b/packages/app-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/app-defaults", "description": "Provides the default wiring of a Backstage App", - "version": "1.0.6", + "version": "1.0.7", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index e5c37fb1d9..d0c822dd1e 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,253 @@ # example-app +## 0.2.76 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-user-settings@0.5.0 + - @backstage/plugin-scaffolder@1.7.0 + - @backstage/plugin-org@0.5.10 + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-graph@0.2.22 + - @backstage/core-app-api@1.1.1 + - @backstage/cli@0.20.0 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-catalog-import@0.9.0 + - @backstage/plugin-tech-insights@0.3.1 + - @backstage/plugin-kubernetes@0.7.3 + - @backstage/plugin-gcalendar@0.3.6 + - @backstage/plugin-api-docs@0.8.10 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-cost-insights@0.11.32 + - @backstage/plugin-airbrake@0.3.10 + - @backstage/plugin-azure-devops@0.2.1 + - @backstage/plugin-badges@0.2.34 + - @backstage/plugin-circleci@0.3.10 + - @backstage/plugin-cloudbuild@0.3.10 + - @backstage/plugin-code-coverage@0.2.3 + - @backstage/plugin-dynatrace@1.0.0 + - @backstage/plugin-explore@0.3.41 + - @backstage/plugin-github-actions@0.5.10 + - @backstage/plugin-gocd@0.1.16 + - @backstage/plugin-home@0.4.26 + - @backstage/plugin-jenkins@0.7.9 + - @backstage/plugin-kafka@0.3.10 + - @backstage/plugin-lighthouse@0.3.10 + - @backstage/plugin-newrelic-dashboard@0.2.3 + - @backstage/plugin-pagerduty@0.5.3 + - @backstage/plugin-playlist@0.1.1 + - @backstage/plugin-rollbar@0.4.10 + - @backstage/plugin-search@1.0.3 + - @backstage/plugin-sentry@0.4.3 + - @backstage/plugin-techdocs@1.3.3 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/plugin-todo@0.2.12 + - @internal/plugin-catalog-customized@0.0.3 + - @backstage/app-defaults@1.0.7 + - @backstage/integration-react@1.1.5 + - @backstage/plugin-apache-airflow@0.2.3 + - @backstage/plugin-gcp-projects@0.3.29 + - @backstage/plugin-graphiql@0.2.42 + - @backstage/plugin-newrelic@0.3.28 + - @backstage/plugin-shortcuts@0.3.2 + - @backstage/plugin-stack-overflow@0.1.6 + - @backstage/plugin-tech-radar@0.5.17 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6 + +## 0.2.76-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder@1.7.0-next.2 + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-import@0.9.0-next.2 + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-playlist@0.1.1-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-airbrake@0.3.10-next.2 + - @backstage/plugin-apache-airflow@0.2.3-next.2 + - @backstage/plugin-api-docs@0.8.10-next.2 + - @backstage/plugin-azure-devops@0.2.1-next.2 + - @backstage/plugin-badges@0.2.34-next.2 + - @backstage/plugin-catalog-graph@0.2.22-next.2 + - @backstage/plugin-circleci@0.3.10-next.2 + - @backstage/plugin-cloudbuild@0.3.10-next.2 + - @backstage/plugin-code-coverage@0.2.3-next.2 + - @backstage/plugin-cost-insights@0.11.32-next.2 + - @backstage/plugin-dynatrace@1.0.0-next.2 + - @backstage/plugin-explore@0.3.41-next.2 + - @backstage/plugin-gcalendar@0.3.6-next.2 + - @backstage/plugin-gcp-projects@0.3.29-next.2 + - @backstage/plugin-github-actions@0.5.10-next.2 + - @backstage/plugin-gocd@0.1.16-next.2 + - @backstage/plugin-graphiql@0.2.42-next.2 + - @backstage/plugin-home@0.4.26-next.2 + - @backstage/plugin-jenkins@0.7.9-next.2 + - @backstage/plugin-kafka@0.3.10-next.2 + - @backstage/plugin-kubernetes@0.7.3-next.2 + - @backstage/plugin-lighthouse@0.3.10-next.2 + - @backstage/plugin-newrelic@0.3.28-next.2 + - @backstage/plugin-newrelic-dashboard@0.2.3-next.2 + - @backstage/plugin-org@0.5.10-next.2 + - @backstage/plugin-pagerduty@0.5.3-next.2 + - @backstage/plugin-rollbar@0.4.10-next.2 + - @backstage/plugin-search@1.0.3-next.2 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-sentry@0.4.3-next.2 + - @backstage/plugin-shortcuts@0.3.2-next.2 + - @backstage/plugin-stack-overflow@0.1.6-next.2 + - @backstage/plugin-tech-insights@0.3.1-next.2 + - @backstage/plugin-tech-radar@0.5.17-next.2 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + - @backstage/plugin-todo@0.2.12-next.2 + - @backstage/plugin-user-settings@0.5.0-next.2 + - @internal/plugin-catalog-customized@0.0.3-next.2 + +## 0.2.76-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-user-settings@0.5.0-next.1 + - @backstage/plugin-scaffolder@1.7.0-next.1 + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/cli@0.20.0-next.1 + - @backstage/plugin-tech-insights@0.3.1-next.1 + - @backstage/plugin-gcalendar@0.3.6-next.1 + - @backstage/plugin-api-docs@0.8.10-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-airbrake@0.3.10-next.1 + - @backstage/plugin-azure-devops@0.2.1-next.1 + - @backstage/plugin-badges@0.2.34-next.1 + - @internal/plugin-catalog-customized@0.0.3-next.1 + - @backstage/plugin-catalog-graph@0.2.22-next.1 + - @backstage/plugin-catalog-import@0.8.13-next.1 + - @backstage/plugin-circleci@0.3.10-next.1 + - @backstage/plugin-cloudbuild@0.3.10-next.1 + - @backstage/plugin-code-coverage@0.2.3-next.1 + - @backstage/plugin-dynatrace@1.0.0-next.1 + - @backstage/plugin-explore@0.3.41-next.1 + - @backstage/plugin-github-actions@0.5.10-next.1 + - @backstage/plugin-gocd@0.1.16-next.1 + - @backstage/plugin-home@0.4.26-next.1 + - @backstage/plugin-jenkins@0.7.9-next.1 + - @backstage/plugin-kafka@0.3.10-next.1 + - @backstage/plugin-kubernetes@0.7.3-next.1 + - @backstage/plugin-lighthouse@0.3.10-next.1 + - @backstage/plugin-newrelic-dashboard@0.2.3-next.1 + - @backstage/plugin-org@0.5.10-next.1 + - @backstage/plugin-pagerduty@0.5.3-next.1 + - @backstage/plugin-playlist@0.1.1-next.1 + - @backstage/plugin-rollbar@0.4.10-next.1 + - @backstage/plugin-search@1.0.3-next.1 + - @backstage/plugin-sentry@0.4.3-next.1 + - @backstage/plugin-techdocs@1.3.3-next.1 + - @backstage/plugin-todo@0.2.12-next.1 + - @backstage/app-defaults@1.0.7-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/plugin-apache-airflow@0.2.3-next.1 + - @backstage/plugin-cost-insights@0.11.32-next.1 + - @backstage/plugin-gcp-projects@0.3.29-next.1 + - @backstage/plugin-graphiql@0.2.42-next.1 + - @backstage/plugin-newrelic@0.3.28-next.1 + - @backstage/plugin-shortcuts@0.3.2-next.1 + - @backstage/plugin-stack-overflow@0.1.6-next.1 + - @backstage/plugin-tech-radar@0.5.17-next.1 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.7-next.1 + - @backstage/plugin-permission-react@0.4.6-next.1 + - @backstage/plugin-techdocs-react@1.0.5-next.1 + +## 0.2.76-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-org@0.5.10-next.0 + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/cli@0.20.0-next.0 + - @backstage/plugin-kubernetes@0.7.3-next.0 + - @backstage/plugin-scaffolder@1.7.0-next.0 + - @backstage/plugin-api-docs@0.8.10-next.0 + - @backstage/plugin-user-settings@0.5.0-next.0 + - @backstage/plugin-cost-insights@0.11.32-next.0 + - @backstage/plugin-airbrake@0.3.10-next.0 + - @backstage/plugin-azure-devops@0.2.1-next.0 + - @backstage/plugin-badges@0.2.34-next.0 + - @backstage/plugin-catalog-graph@0.2.22-next.0 + - @backstage/plugin-catalog-import@0.8.13-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-circleci@0.3.10-next.0 + - @backstage/plugin-cloudbuild@0.3.10-next.0 + - @backstage/plugin-code-coverage@0.2.3-next.0 + - @backstage/plugin-dynatrace@0.2.1-next.0 + - @backstage/plugin-explore@0.3.41-next.0 + - @backstage/plugin-github-actions@0.5.10-next.0 + - @backstage/plugin-gocd@0.1.16-next.0 + - @backstage/plugin-home@0.4.26-next.0 + - @backstage/plugin-jenkins@0.7.9-next.0 + - @backstage/plugin-kafka@0.3.10-next.0 + - @backstage/plugin-lighthouse@0.3.10-next.0 + - @backstage/plugin-newrelic-dashboard@0.2.3-next.0 + - @backstage/plugin-pagerduty@0.5.3-next.0 + - @backstage/plugin-playlist@0.1.1-next.0 + - @backstage/plugin-rollbar@0.4.10-next.0 + - @backstage/plugin-search@1.0.3-next.0 + - @backstage/plugin-sentry@0.4.3-next.0 + - @backstage/plugin-tech-insights@0.3.1-next.0 + - @backstage/plugin-techdocs@1.3.3-next.0 + - @backstage/plugin-techdocs-react@1.0.5-next.0 + - @backstage/plugin-todo@0.2.12-next.0 + - @backstage/app-defaults@1.0.7-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/plugin-apache-airflow@0.2.3-next.0 + - @backstage/plugin-gcalendar@0.3.6-next.0 + - @backstage/plugin-gcp-projects@0.3.29-next.0 + - @backstage/plugin-graphiql@0.2.42-next.0 + - @backstage/plugin-newrelic@0.3.28-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/plugin-shortcuts@0.3.2-next.0 + - @backstage/plugin-stack-overflow@0.1.6-next.0 + - @backstage/plugin-tech-radar@0.5.17-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-permission-react@0.4.6-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + - @internal/plugin-catalog-customized@0.0.3-next.0 + ## 0.2.75 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index ad24f23c45..441bdd1138 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,6 +1,6 @@ { "name": "example-app", - "version": "0.2.75", + "version": "0.2.76", "private": true, "backstage": { "role": "frontend" @@ -19,6 +19,7 @@ "@backstage/plugin-apache-airflow": "workspace:^", "@backstage/plugin-api-docs": "workspace:^", "@backstage/plugin-azure-devops": "workspace:^", + "@backstage/plugin-azure-sites": "workspace:^", "@backstage/plugin-badges": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", "@backstage/plugin-catalog-graph": "workspace:^", @@ -67,6 +68,7 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "@octokit/rest": "^19.0.3", + "@oriflame/backstage-plugin-score-card": "^0.5.1", "@roadiehq/backstage-plugin-buildkite": "^2.0.8", "@roadiehq/backstage-plugin-github-insights": "^2.0.5", "@roadiehq/backstage-plugin-github-pull-requests": "^2.2.7", @@ -82,7 +84,6 @@ }, "devDependencies": { "@backstage/test-utils": "workspace:^", - "@rjsf/core": "^3.2.1", "@testing-library/cypress": "^8.0.2", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index a6c613ded7..2526f6740f 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -106,6 +106,7 @@ import { RequirePermission } from '@backstage/plugin-permission-react'; import { catalogEntityCreatePermission } from '@backstage/plugin-catalog-common'; import { PlaylistIndexPage } from '@backstage/plugin-playlist'; import { TwoColumnLayout } from './components/scaffolder/customScaffolderLayouts'; +import { ScoreBoardPage } from '@oriflame/backstage-plugin-score-card'; const app = createApp({ apis, @@ -136,6 +137,7 @@ const app = createApp({ }); bind(scaffolderPlugin.externalRoutes, { registerComponent: catalogImportPlugin.routes.importPage, + viewTechDoc: techdocsPlugin.routes.docRoot, }); bind(orgPlugin.externalRoutes, { catalogIndex: catalogPlugin.routes.catalogIndex, @@ -148,7 +150,7 @@ const AppRouter = app.getRouter(); const routes = ( - } /> + } /> {/* TODO(rubenl): Move this to / once its more mature and components exist */} }> {homePage} @@ -272,6 +274,7 @@ const routes = ( } /> } /> } /> + } /> ); diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 8b5f0932fb..0c142bafbd 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -51,6 +51,7 @@ import { import { MyGroupsSidebarItem } from '@backstage/plugin-org'; import GroupIcon from '@material-ui/icons/People'; import { SearchModal } from '../search/SearchModal'; +import Score from '@material-ui/icons/Score'; const useSidebarLogoStyles = makeStyles({ root: { @@ -120,6 +121,7 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( text="Cost Insights" /> + diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index c2f297a2d7..8e72bb03e4 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -144,6 +144,7 @@ import { EntityNewRelicDashboardCard, } from '@backstage/plugin-newrelic-dashboard'; import { EntityGoCdContent, isGoCdAvailable } from '@backstage/plugin-gocd'; +import { EntityScoreCardContent } from '@oriflame/backstage-plugin-score-card'; import React, { ReactNode, useMemo, useState } from 'react'; @@ -208,7 +209,7 @@ const techdocsContent = ( /** * NOTE: This page is designed to work on small screens such as mobile devices. * This is based on Material UI Grid. If breakpoints are used, each grid item must set the `xs` prop to a column size or to `true`, - * since this does not default. If no breakpoints are used, the items will equitably share the asvailable space. + * since this does not default. If no breakpoints are used, the items will equitably share the available space. * https://material-ui.com/components/grid/#basic-grid. */ @@ -704,6 +705,13 @@ const systemPage = ( + + + + + + + { return ( <>

{title}

-

In two column layout!!

{left.map(prop => ( diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index 99814130c2..d324b683d8 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -35,6 +35,7 @@ import { SearchBar, SearchFilter, SearchResult, + SearchPagination, SearchResultPager, useSearch, } from '@backstage/plugin-search-react'; @@ -129,6 +130,7 @@ const SearchPage = () => { )} + {({ results }) => ( diff --git a/packages/backend-app-api/CHANGELOG.md b/packages/backend-app-api/CHANGELOG.md index 801ec9aba6..9db48afd49 100644 --- a/packages/backend-app-api/CHANGELOG.md +++ b/packages/backend-app-api/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/backend-app-api +## 0.2.2 + +### Patch Changes + +- 0027a749cd: Added possibility to configure index plugin of the HTTP router service. +- 45857bffae: Properly export `rootLoggerFactory`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/errors@1.1.2 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- 0027a749cd: Added possibility to configure index plugin of the HTTP router service. +- 45857bffae: Properly export `rootLoggerFactory`. +- Updated dependencies + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/errors@1.1.2-next.0 + ## 0.2.1 ### Patch Changes diff --git a/packages/backend-app-api/api-report.md b/packages/backend-app-api/api-report.md index ea149a44f2..4d9fbea4a5 100644 --- a/packages/backend-app-api/api-report.md +++ b/packages/backend-app-api/api-report.md @@ -58,9 +58,14 @@ export const discoveryFactory: ( // @public (undocumented) export const httpRouterFactory: ( - options?: undefined, + options?: HttpRouterFactoryOptions | undefined, ) => ServiceFactory; +// @public (undocumented) +export type HttpRouterFactoryOptions = { + indexPlugin?: string; +}; + // @public (undocumented) export const loggerFactory: (options?: undefined) => ServiceFactory; @@ -69,6 +74,9 @@ export const permissionsFactory: ( options?: undefined, ) => ServiceFactory; +// @public (undocumented) +export const rootLoggerFactory: (options?: undefined) => ServiceFactory; + // @public (undocumented) export const schedulerFactory: ( options?: undefined, diff --git a/packages/backend-app-api/package.json b/packages/backend-app-api/package.json index 2878de5aa3..04a03677c0 100644 --- a/packages/backend-app-api/package.json +++ b/packages/backend-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-app-api", "description": "Core API used by Backstage backend apps", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-app-api/src/services/implementations/httpRouterService.ts b/packages/backend-app-api/src/services/implementations/httpRouterService.ts index be4af664c4..d9fc807b1a 100644 --- a/packages/backend-app-api/src/services/implementations/httpRouterService.ts +++ b/packages/backend-app-api/src/services/implementations/httpRouterService.ts @@ -24,6 +24,16 @@ import Router from 'express-promise-router'; import { Handler } from 'express'; import { createServiceBuilder } from '@backstage/backend-common'; +/** + * @public + */ +export type HttpRouterFactoryOptions = { + /** + * The plugin ID used for the index route. Defaults to 'app' + */ + indexPlugin?: string; +}; + /** @public */ export const httpRouterFactory = createServiceFactory({ service: httpRouterServiceRef, @@ -31,21 +41,28 @@ export const httpRouterFactory = createServiceFactory({ config: configServiceRef, plugin: pluginMetadataServiceRef, }, - async factory({ config }) { + async factory({ config }, options?: HttpRouterFactoryOptions) { + const defaultPluginId = options?.indexPlugin ?? 'app'; + + const apiRouter = Router(); const rootRouter = Router(); const service = createServiceBuilder(module) .loadConfig(config) + .addRouter('/api', apiRouter) .addRouter('', rootRouter); await service.start(); return async ({ plugin }) => { const pluginId = plugin.getId(); - const path = pluginId ? `/api/${pluginId}` : ''; return { use(handler: Handler) { - rootRouter.use(path, handler); + if (pluginId === defaultPluginId) { + rootRouter.use(handler); + } else { + apiRouter.use(`/${pluginId}`, handler); + } }, }; }; diff --git a/packages/backend-app-api/src/services/implementations/index.ts b/packages/backend-app-api/src/services/implementations/index.ts index 048c952b4e..608399bad9 100644 --- a/packages/backend-app-api/src/services/implementations/index.ts +++ b/packages/backend-app-api/src/services/implementations/index.ts @@ -19,8 +19,10 @@ export { configFactory } from './configService'; export { databaseFactory } from './databaseService'; export { discoveryFactory } from './discoveryService'; export { loggerFactory } from './loggerService'; +export { rootLoggerFactory } from './rootLoggerService'; export { permissionsFactory } from './permissionsService'; export { schedulerFactory } from './schedulerService'; export { tokenManagerFactory } from './tokenManagerService'; export { urlReaderFactory } from './urlReaderService'; export { httpRouterFactory } from './httpRouterService'; +export type { HttpRouterFactoryOptions } from './httpRouterService'; diff --git a/packages/backend-app-api/src/services/implementations/rootLoggerService.ts b/packages/backend-app-api/src/services/implementations/rootLoggerService.ts index d7da11723e..a65a8d9126 100644 --- a/packages/backend-app-api/src/services/implementations/rootLoggerService.ts +++ b/packages/backend-app-api/src/services/implementations/rootLoggerService.ts @@ -39,7 +39,7 @@ class BackstageLogger implements Logger { } /** @public */ -export const loggerFactory = createServiceFactory({ +export const rootLoggerFactory = createServiceFactory({ service: rootLoggerServiceRef, deps: {}, async factory() { diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 4e51ad3b8f..c6211b05b4 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,60 @@ # @backstage/backend-common +## 0.15.2 + +### Patch Changes + +- e8d7976413: Added back support for when no branch is provided for the Bitbucket Server `UrlReader` +- c44cf412de: Fix BitBucket server integration +- c31f7cdfbc: Fixed an issue where `getClient()` for a `pluginId` would return different clients and not share them +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + - @backstage/config-loader@1.1.5 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.15.2-next.2 + +### Patch Changes + +- c44cf412de: Fix BitBucket server integration +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## 0.15.2-next.1 + +### Patch Changes + +- c31f7cdfbc: Fixed an issue where `getClient()` for a `pluginId` would return different clients and not share them +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.1 + - @backstage/config-loader@1.1.5-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + +## 0.15.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.0 + - @backstage/config-loader@1.1.5-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + ## 0.15.1 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 35e1ccdf10..2a7e307774 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-common", "description": "Common functionality library for Backstage backends", - "version": "0.15.1", + "version": "0.15.2", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-common/src/database/DatabaseManager.test.ts b/packages/backend-common/src/database/DatabaseManager.test.ts index 9e8af44fc0..95511d5aba 100644 --- a/packages/backend-common/src/database/DatabaseManager.test.ts +++ b/packages/backend-common/src/database/DatabaseManager.test.ts @@ -113,7 +113,11 @@ describe('DatabaseManager', () => { }, }, }; - const manager = DatabaseManager.fromConfig(new ConfigReader(config)); + let manager: DatabaseManager; + + beforeEach(() => { + manager = DatabaseManager.fromConfig(new ConfigReader(config)); + }); it('connects to a plugin database using default config', async () => { const pluginId = 'pluginwithoutconfig'; @@ -340,6 +344,16 @@ describe('DatabaseManager', () => { ); }); + it('returns the same client for the same pluginId', async () => { + const [client1, client2] = await Promise.all([ + manager.forPlugin('plugin1').getClient(), + manager.forPlugin('plugin1').getClient(), + ]); + expect(mocked(createDatabaseClient)).toHaveBeenCalledTimes(1); + + expect(client1).toBe(client2); + }); + it('uses plugin connection as base if default client is different from plugin client', async () => { const pluginId = 'differentclient'; await manager.forPlugin(pluginId).getClient(); diff --git a/packages/backend-common/src/database/DatabaseManager.ts b/packages/backend-common/src/database/DatabaseManager.ts index 363bcac30b..5adb862e38 100644 --- a/packages/backend-common/src/database/DatabaseManager.ts +++ b/packages/backend-common/src/database/DatabaseManager.ts @@ -84,6 +84,7 @@ export class DatabaseManager { private readonly config: Config, private readonly prefix: string = 'backstage_plugin_', private readonly options?: DatabaseManagerOptions, + private readonly databaseCache: Map> = new Map(), ) {} /** @@ -307,45 +308,57 @@ export class DatabaseManager { * plugin */ private async getDatabase(pluginId: string): Promise { - const pluginConfig = new ConfigReader( - this.getConfigForPlugin(pluginId) as JsonObject, - ); - - const databaseName = this.getDatabaseName(pluginId); - if (databaseName && this.getEnsureExistsConfig(pluginId)) { - try { - await ensureDatabaseExists(pluginConfig, databaseName); - } catch (error) { - throw new Error( - `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`, - ); - } + if (this.databaseCache.has(pluginId)) { + return this.databaseCache.get(pluginId)!; } - let schemaOverrides; - if (this.getPluginDivisionModeConfig() === 'schema') { - schemaOverrides = this.getSchemaOverrides(pluginId); - if (this.getEnsureExistsConfig(pluginId)) { + const clientPromise = Promise.resolve().then(async () => { + const pluginConfig = new ConfigReader( + this.getConfigForPlugin(pluginId) as JsonObject, + ); + + const databaseName = this.getDatabaseName(pluginId); + if (databaseName && this.getEnsureExistsConfig(pluginId)) { try { - await ensureSchemaExists(pluginConfig, pluginId); + await ensureDatabaseExists(pluginConfig, databaseName); } catch (error) { throw new Error( - `Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`, + `Failed to connect to the database to make sure that '${databaseName}' exists, ${error}`, ); } } - } - const databaseClientOverrides = mergeDatabaseConfig( - {}, - this.getDatabaseOverrides(pluginId), - schemaOverrides, - ); + let schemaOverrides; + if (this.getPluginDivisionModeConfig() === 'schema') { + schemaOverrides = this.getSchemaOverrides(pluginId); + if (this.getEnsureExistsConfig(pluginId)) { + try { + await ensureSchemaExists(pluginConfig, pluginId); + } catch (error) { + throw new Error( + `Failed to connect to the database to make sure that schema for plugin '${pluginId}' exists, ${error}`, + ); + } + } + } - const client = createDatabaseClient(pluginConfig, databaseClientOverrides); - this.startKeepaliveLoop(pluginId, client); + const databaseClientOverrides = mergeDatabaseConfig( + {}, + this.getDatabaseOverrides(pluginId), + schemaOverrides, + ); - return client; + const client = createDatabaseClient( + pluginConfig, + databaseClientOverrides, + ); + this.startKeepaliveLoop(pluginId, client); + return client; + }); + + this.databaseCache.set(pluginId, clientPromise); + + return clientPromise; } private startKeepaliveLoop(pluginId: string, client: Knex): void { diff --git a/packages/backend-common/src/middleware/notFoundHandler.test.ts b/packages/backend-common/src/middleware/notFoundHandler.test.ts index b0dea8ef62..b49e275a69 100644 --- a/packages/backend-common/src/middleware/notFoundHandler.test.ts +++ b/packages/backend-common/src/middleware/notFoundHandler.test.ts @@ -21,7 +21,7 @@ import { notFoundHandler } from './notFoundHandler'; describe('notFoundHandler', () => { it('handles only missing routes', async () => { const app = express(); - app.use('/exists', (_, res) => res.status(200).send()); + app.use('/exists', (_, res) => res.status(200).end()); app.use(notFoundHandler()); const existsResponse = await request(app).get('/exists'); diff --git a/packages/backend-common/src/middleware/notFoundHandler.ts b/packages/backend-common/src/middleware/notFoundHandler.ts index 53a548c0ad..2d8b0ef3d0 100644 --- a/packages/backend-common/src/middleware/notFoundHandler.ts +++ b/packages/backend-common/src/middleware/notFoundHandler.ts @@ -28,6 +28,6 @@ import { NextFunction, Request, RequestHandler, Response } from 'express'; export function notFoundHandler(): RequestHandler { /* eslint-disable @typescript-eslint/no-unused-vars */ return (_request: Request, response: Response, _next: NextFunction) => { - response.status(404).send(); + response.status(404).end(); }; } diff --git a/packages/backend-common/src/middleware/requestLoggingHandler.test.ts b/packages/backend-common/src/middleware/requestLoggingHandler.test.ts index c95e59f80b..ed24f2050b 100644 --- a/packages/backend-common/src/middleware/requestLoggingHandler.test.ts +++ b/packages/backend-common/src/middleware/requestLoggingHandler.test.ts @@ -26,8 +26,8 @@ describe('requestLoggingHandler', () => { const app = express(); app.use(requestLoggingHandler(logger)); - app.use('/exists1', (_, res) => res.status(200).send()); - app.use('/exists2', (_, res) => res.status(201).send()); + app.use('/exists1', (_, res) => res.status(200).end()); + app.use('/exists2', (_, res) => res.status(201).end()); const r = request(app); await r.get('/exists1'); diff --git a/packages/backend-common/src/middleware/statusCheckHandler.ts b/packages/backend-common/src/middleware/statusCheckHandler.ts index 4655d610fe..4bd1a701c3 100644 --- a/packages/backend-common/src/middleware/statusCheckHandler.ts +++ b/packages/backend-common/src/middleware/statusCheckHandler.ts @@ -55,7 +55,7 @@ export async function statusCheckHandler( return async (_request: Request, response: Response, next: NextFunction) => { try { const status = await statusCheck(); - response.status(200).header('').send(status); + response.status(200).json(status); } catch (err) { next(err); } diff --git a/packages/backend-common/src/reading/BitbucketServerUrlReader.test.ts b/packages/backend-common/src/reading/BitbucketServerUrlReader.test.ts index f308b4d33d..9f231e2ba7 100644 --- a/packages/backend-common/src/reading/BitbucketServerUrlReader.test.ts +++ b/packages/backend-common/src/reading/BitbucketServerUrlReader.test.ts @@ -83,13 +83,44 @@ describe('BitbucketServerUrlReader', () => { ), ), rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/commits/*', + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/branches', (_, res, ctx) => res( ctx.status(200), - ctx.json({ id: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }), + ctx.json({ + size: 2, + values: [ + { + displayId: 'some-branch-that-should-be-ignored', + latestCommit: 'bogus hash', + }, + { + displayId: 'some-branch', + latestCommit: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st', + }, + ], + }), ), ), + rest.get( + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/branches/default', + (_, res, ctx) => + res( + ctx.status(200), + ctx.json({ + id: 'refs/heads/master', + displayId: 'master', + type: 'BRANCH', + latestCommit: '3bdd5457286abdf920db4b77bf2fef79a06190c2', + latestChangeset: '3bdd5457286abdf920db4b77bf2fef79a06190c2', + isDefault: true, + }), + ), + ), + rest.get( + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/default-branch', + (_, res, ctx) => res(ctx.status(404)), + ), ); }); @@ -107,6 +138,14 @@ describe('BitbucketServerUrlReader', () => { expect(indexMarkdownFile.toString()).toBe('# Test\n'); }); + + it('uses default branch when no branch is provided', async () => { + const response = await reader.readTree( + 'https://bitbucket.mycompany.net/projects/backstage/repos/mock/browse/src', + ); + + expect(response.etag).toBe('3bdd5457286a'); + }); }); describe('readTree without branch', () => { @@ -130,12 +169,22 @@ describe('BitbucketServerUrlReader', () => { ), ), rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/commits/*', + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/branches', (_, res, ctx) => res( ctx.status(200), ctx.json({ - values: [{ id: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }], + size: 2, + values: [ + { + displayId: 'some-branch-that-should-be-ignored', + latestCommit: 'bogus hash', + }, + { + displayId: 'some-branch', + latestCommit: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st', + }, + ], }), ), ), @@ -179,11 +228,23 @@ describe('BitbucketServerUrlReader', () => { ), ), rest.get( - 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/commits/*', + 'https://api.bitbucket.mycompany.net/rest/api/1.0/projects/backstage/repos/mock/branches', (_, res, ctx) => res( ctx.status(200), - ctx.json({ id: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st' }), + ctx.json({ + size: 2, + values: [ + { + displayId: 'master-of-none', + latestCommit: 'bogus hash', + }, + { + displayId: 'master', + latestCommit: '12ab34cd56ef78gh90ij12kl34mn56op78qr90st', + }, + ], + }), ), ), ); diff --git a/packages/backend-common/src/reading/BitbucketServerUrlReader.ts b/packages/backend-common/src/reading/BitbucketServerUrlReader.ts index e9a229a1ff..bcab226b0f 100644 --- a/packages/backend-common/src/reading/BitbucketServerUrlReader.ts +++ b/packages/backend-common/src/reading/BitbucketServerUrlReader.ts @@ -188,38 +188,45 @@ export class BitbucketServerUrlReader implements UrlReader { private async getLastCommitShortHash(url: string): Promise { const { name: repoName, owner: project, ref: branch } = parseGitUrl(url); - // Bitbucket Server https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp224 - const commitApiUrl = `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/commits/${branch}`; + // If a branch is provided use that otherwise fall back to the default branch + const branchParameter = branch + ? `?filterText=${encodeURIComponent(branch)}` + : '/default'; - const commitResponse = await fetch( - commitApiUrl, + // https://docs.atlassian.com/bitbucket-server/rest/7.9.0/bitbucket-rest.html#idp211 (branches docs) + const branchListUrl = `${this.integration.config.apiBaseUrl}/projects/${project}/repos/${repoName}/branches${branchParameter}`; + + const branchListResponse = await fetch( + branchListUrl, getBitbucketServerRequestOptions(this.integration.config), ); - if (!commitResponse.ok) { - const message = `Failed to retrieve commits from ${commitApiUrl}, ${commitResponse.status} ${commitResponse.statusText}`; - if (commitResponse.status === 404) { + if (!branchListResponse.ok) { + const message = `Failed to retrieve branch list from ${branchListUrl}, ${branchListResponse.status} ${branchListResponse.statusText}`; + if (branchListResponse.status === 404) { throw new NotFoundError(message); } throw new Error(message); } - const commits = await commitResponse.json(); + const branchMatches = await branchListResponse.json(); - // Handles case when a branch is provided in the URL - if (commits && commits.id) { - return commits.id.substring(0, 12); + if (branchMatches && branchMatches.size > 0) { + const exactBranchMatch = branchMatches.values.filter( + (branchDetails: { displayId: string }) => + branchDetails.displayId === branch, + )[0]; + return exactBranchMatch.latestCommit.substring(0, 12); } - // Handles case when no branch is provided in the URL - if ( - commits && - commits.values && - commits.values.length > 0 && - commits.values[0].id - ) { - return commits.values[0].id.substring(0, 12); + // Handle when no branch is provided using the default as the fallback + if (!branch && branchMatches) { + return branchMatches.latestCommit.substring(0, 12); } - throw new Error(`Failed to read response from ${commitApiUrl}`); + throw new Error( + `Failed to find Last Commit using ${ + branch ? `branch "${branch}"` : 'default branch' + } in response from ${branchListUrl}`, + ); } } diff --git a/packages/backend-defaults/CHANGELOG.md b/packages/backend-defaults/CHANGELOG.md index 2e8ef65792..a569ff349c 100644 --- a/packages/backend-defaults/CHANGELOG.md +++ b/packages/backend-defaults/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/backend-defaults +## 0.1.2 + +### Patch Changes + +- 96d288a02d: Added root logger service to the set of default services. +- Updated dependencies + - @backstage/backend-app-api@0.2.2 + - @backstage/backend-plugin-api@0.1.3 + +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-app-api@0.2.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/backend-app-api@0.2.2-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- 96d288a02d: Added root logger service to the set of default services. +- Updated dependencies + - @backstage/backend-app-api@0.2.2-next.0 + - @backstage/backend-plugin-api@0.1.3-next.0 + ## 0.1.1 ### Patch Changes diff --git a/packages/backend-defaults/package.json b/packages/backend-defaults/package.json index 23652120e2..4de7123983 100644 --- a/packages/backend-defaults/package.json +++ b/packages/backend-defaults/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-defaults", "description": "Backend defaults used by Backstage backend apps", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-defaults/src/CreateBackend.ts b/packages/backend-defaults/src/CreateBackend.ts index 5130d6733b..b95bd6f35b 100644 --- a/packages/backend-defaults/src/CreateBackend.ts +++ b/packages/backend-defaults/src/CreateBackend.ts @@ -24,6 +24,7 @@ import { httpRouterFactory, loggerFactory, permissionsFactory, + rootLoggerFactory, schedulerFactory, tokenManagerFactory, urlReaderFactory, @@ -36,6 +37,7 @@ export const defaultServiceFactories = [ databaseFactory, discoveryFactory, loggerFactory, + rootLoggerFactory, permissionsFactory, schedulerFactory, tokenManagerFactory, diff --git a/packages/backend-next/CHANGELOG.md b/packages/backend-next/CHANGELOG.md index a37fc48dac..3315f4a27f 100644 --- a/packages/backend-next/CHANGELOG.md +++ b/packages/backend-next/CHANGELOG.md @@ -1,5 +1,45 @@ # example-backend-next +## 0.0.4 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/backend-defaults@0.1.2 + - @backstage/plugin-app-backend@0.3.37 + +## 0.0.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/plugin-app-backend@0.3.37-next.2 + - @backstage/backend-defaults@0.1.2-next.2 + +## 0.0.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.1 + - @backstage/backend-defaults@0.1.2-next.1 + - @backstage/plugin-app-backend@0.3.37-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## 0.0.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.0 + - @backstage/backend-defaults@0.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/plugin-app-backend@0.3.37-next.0 + ## 0.0.3 ### Patch Changes diff --git a/packages/backend-next/package.json b/packages/backend-next/package.json index 4e989c81cf..f82f2fc4fc 100644 --- a/packages/backend-next/package.json +++ b/packages/backend-next/package.json @@ -1,6 +1,6 @@ { "name": "example-backend-next", - "version": "0.0.3", + "version": "0.0.4", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,6 +26,7 @@ }, "dependencies": { "@backstage/backend-defaults": "workspace:^", + "@backstage/plugin-app-backend": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", "@backstage/plugin-scaffolder-backend": "workspace:^" }, diff --git a/packages/backend-next/src/index.ts b/packages/backend-next/src/index.ts index c6b7da8fa7..8196b28452 100644 --- a/packages/backend-next/src/index.ts +++ b/packages/backend-next/src/index.ts @@ -17,9 +17,11 @@ import { catalogPlugin } from '@backstage/plugin-catalog-backend'; import { scaffolderCatalogModule } from '@backstage/plugin-scaffolder-backend'; import { createBackend } from '@backstage/backend-defaults'; +import { appPlugin } from '@backstage/plugin-app-backend'; const backend = createBackend(); backend.add(catalogPlugin()); backend.add(scaffolderCatalogModule()); +backend.add(appPlugin({ appPackageName: 'example-app' })); backend.start(); diff --git a/packages/backend-plugin-api/CHANGELOG.md b/packages/backend-plugin-api/CHANGELOG.md index 33e3b36268..c45f0a968e 100644 --- a/packages/backend-plugin-api/CHANGELOG.md +++ b/packages/backend-plugin-api/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/backend-plugin-api +## 0.1.3 + +### Patch Changes + +- 28377dc89f: Allow interfaces to be used for inferred option types. +- a35a27df70: Added documentation for `createBackendModule`, with guidelines for choosing a module ID. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/config@1.0.3 + +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/config@1.0.3-next.2 + +## 0.1.3-next.1 + +### Patch Changes + +- a35a27df70: Added documentation for `createBackendModule`, with guidelines for choosing a module ID. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## 0.1.3-next.0 + +### Patch Changes + +- 28377dc89f: Allow interfaces to be used for inferred option types. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + ## 0.1.2 ### Patch Changes diff --git a/packages/backend-plugin-api/api-report.md b/packages/backend-plugin-api/api-report.md index 3344576db8..70ab168053 100644 --- a/packages/backend-plugin-api/api-report.md +++ b/packages/backend-plugin-api/api-report.md @@ -71,13 +71,9 @@ export const cacheServiceRef: ServiceRef; // @public (undocumented) export const configServiceRef: ServiceRef; -// @public (undocumented) +// @public export function createBackendModule< - TOptions extends - | { - [name: string]: unknown; - } - | undefined = undefined, + TOptions extends object | undefined = undefined, >( config: BackendModuleConfig, ): undefined extends TOptions @@ -86,14 +82,11 @@ export function createBackendModule< // @public (undocumented) export function createBackendPlugin< - TOptions extends - | { - [name: string]: unknown; - } - | undefined = undefined, ->( - config: BackendPluginConfig, -): undefined extends TOptions + TOptions extends object | undefined = undefined, +>(config: { + id: string; + register(reg: BackendRegistrationPoints, options: TOptions): void; +}): undefined extends TOptions ? (options?: TOptions) => BackendFeature : (options: TOptions) => BackendFeature; @@ -110,11 +103,7 @@ export function createServiceFactory< TDeps extends { [name in string]: ServiceRef; }, - TOpts extends - | { - [name in string]: unknown; - } - | undefined = undefined, + TOpts extends object | undefined = undefined, >(config: { service: ServiceRef; deps: TDeps; diff --git a/packages/backend-plugin-api/package.json b/packages/backend-plugin-api/package.json index 18c6c842fc..59e1de9529 100644 --- a/packages/backend-plugin-api/package.json +++ b/packages/backend-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-plugin-api", "description": "Core API used by Backstage backend plugins", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-plugin-api/src/services/system/types.test.ts b/packages/backend-plugin-api/src/services/system/types.test.ts index 29512d3a58..0e02f38d3f 100644 --- a/packages/backend-plugin-api/src/services/system/types.test.ts +++ b/packages/backend-plugin-api/src/services/system/types.test.ts @@ -58,6 +58,8 @@ describe('createServiceFactory', () => { metaFactory({}); metaFactory({ x: 1 }); // @ts-expect-error + metaFactory({ x: 1, y: 2 }); + // @ts-expect-error metaFactory(null); metaFactory(undefined); metaFactory(); @@ -80,10 +82,145 @@ describe('createServiceFactory', () => { metaFactory({}); metaFactory({ x: 1 }); // @ts-expect-error + metaFactory({ x: 1, y: 2 }); + // @ts-expect-error metaFactory(null); // @ts-expect-error metaFactory(undefined); // @ts-expect-error metaFactory(); }); + + it('should create a meta factory with optional options as interface', () => { + interface TestOptions { + x: number; + } + const ref = createServiceRef({ id: 'x' }); + const metaFactory = createServiceFactory({ + service: ref, + deps: {}, + async factory(_deps, _opts?: TestOptions) { + return async () => 'x'; + }, + }); + expect(metaFactory).toEqual(expect.any(Function)); + + // @ts-expect-error + metaFactory('string'); + // @ts-expect-error + metaFactory({}); + metaFactory({ x: 1 }); + // @ts-expect-error + metaFactory({ x: 1, y: 2 }); + // @ts-expect-error + metaFactory(null); + metaFactory(undefined); + metaFactory(); + }); + + it('should create a meta factory with required options as interface', () => { + interface TestOptions { + x: number; + } + const ref = createServiceRef({ id: 'x' }); + const metaFactory = createServiceFactory({ + service: ref, + deps: {}, + async factory(_deps, _opts: TestOptions) { + return async () => 'x'; + }, + }); + expect(metaFactory).toEqual(expect.any(Function)); + + // @ts-expect-error + metaFactory('string'); + // @ts-expect-error + metaFactory({}); + metaFactory({ x: 1 }); + // @ts-expect-error + metaFactory({ x: 1, y: 2 }); + // @ts-expect-error + metaFactory(null); + // @ts-expect-error + metaFactory(undefined); + // @ts-expect-error + metaFactory(); + }); + + it('should only allow objects as options', () => { + const ref = createServiceRef({ id: 'x' }); + const metaFactory = createServiceFactory({ + service: ref, + deps: {}, + // @ts-expect-error + async factory(_deps, _opts: string) { + return async () => 'x'; + }, + }); + expect(metaFactory).toEqual(expect.any(Function)); + createServiceFactory({ + service: ref, + deps: {}, + // @ts-expect-error + async factory(_deps, _opts: number) { + return async () => 'x'; + }, + }); + createServiceFactory({ + service: ref, + deps: {}, + // @ts-expect-error + async factory(_deps, _opts: symbol) { + return async () => 'x'; + }, + }); + createServiceFactory({ + service: ref, + deps: {}, + // @ts-expect-error + async factory(_deps, _opts: bigint) { + return async () => 'x'; + }, + }); + createServiceFactory({ + service: ref, + deps: {}, + // @ts-expect-error + async factory(_deps, _opts: 'string') { + return async () => 'x'; + }, + }); + createServiceFactory({ + service: ref, + deps: {}, + // @ts-expect-error + async factory(_deps, _opts: Array) { + return async () => 'x'; + }, + }); + createServiceFactory({ + service: ref, + deps: {}, + // @ts-expect-error + async factory(_deps, _opts: Map) { + return async () => 'x'; + }, + }); + createServiceFactory({ + service: ref, + deps: {}, + // @ts-expect-error + async factory(_deps, _opts: Set) { + return async () => 'x'; + }, + }); + createServiceFactory({ + service: ref, + deps: {}, + // @ts-expect-error + async factory(_deps, _opts: null) { + return async () => 'x'; + }, + }); + }); }); diff --git a/packages/backend-plugin-api/src/services/system/types.ts b/packages/backend-plugin-api/src/services/system/types.ts index d20fd6fd8f..2459dea918 100644 --- a/packages/backend-plugin-api/src/services/system/types.ts +++ b/packages/backend-plugin-api/src/services/system/types.ts @@ -133,7 +133,7 @@ export function createServiceFactory< TScope extends 'root' | 'plugin', TImpl extends TService, TDeps extends { [name in string]: ServiceRef }, - TOpts extends { [name in string]: unknown } | undefined = undefined, + TOpts extends object | undefined = undefined, >(config: { service: ServiceRef; deps: TDeps; diff --git a/packages/backend-plugin-api/src/wiring/factories.test.ts b/packages/backend-plugin-api/src/wiring/factories.test.ts index 110d7b4f3f..cf67e411d5 100644 --- a/packages/backend-plugin-api/src/wiring/factories.test.ts +++ b/packages/backend-plugin-api/src/wiring/factories.test.ts @@ -31,7 +31,7 @@ describe('createExtensionPoint', () => { }); describe('createBackendPlugin', () => { - it('should create an BackendPlugin', () => { + it('should create a BackendPlugin', () => { const plugin = createBackendPlugin({ id: 'x', register(_reg, _options: { a: string }) {}, @@ -68,10 +68,42 @@ describe('createBackendPlugin', () => { // @ts-expect-error expect(plugin({})).toBeDefined(); }); + + it('should create a BackendPlugin with options as interface', () => { + interface TestOptions { + a: string; + } + const plugin = createBackendPlugin({ + id: 'x', + register(_reg, _options: TestOptions) {}, + }); + expect(plugin).toBeDefined(); + expect(plugin({ a: 'a' })).toBeDefined(); + expect(plugin({ a: 'a' }).id).toBe('x'); + // @ts-expect-error + expect(plugin()).toBeDefined(); + // @ts-expect-error + expect(plugin({ b: 'b' })).toBeDefined(); + }); + + it('should create plugins with optional options as interface', () => { + interface TestOptions { + a: string; + } + const plugin = createBackendPlugin({ + id: 'x', + register(_reg, _options?: TestOptions) {}, + }); + expect(plugin).toBeDefined(); + expect(plugin({ a: 'a' })).toBeDefined(); + expect(plugin()).toBeDefined(); + // @ts-expect-error + expect(plugin({ b: 'b' })).toBeDefined(); + }); }); describe('createBackendModule', () => { - it('should create an BackendModule', () => { + it('should create a BackendModule', () => { const mod = createBackendModule({ pluginId: 'x', moduleId: 'y', @@ -111,4 +143,38 @@ describe('createBackendModule', () => { // @ts-expect-error expect(mod({})).toBeDefined(); }); + + it('should create a BackendModule as interface', () => { + interface TestOptions { + a: string; + } + const mod = createBackendModule({ + pluginId: 'x', + moduleId: 'y', + register(_reg, _options: TestOptions) {}, + }); + expect(mod).toBeDefined(); + expect(mod({ a: 'a' })).toBeDefined(); + expect(mod({ a: 'a' }).id).toBe('x.y'); + // @ts-expect-error + expect(mod()).toBeDefined(); + // @ts-expect-error + expect(mod({ b: 'b' })).toBeDefined(); + }); + + it('should create modules with optional options as interface', () => { + interface TestOptions { + a: string; + } + const mod = createBackendModule({ + pluginId: 'x', + moduleId: 'y', + register(_reg, _options?: TestOptions) {}, + }); + expect(mod).toBeDefined(); + expect(mod({ a: 'a' })).toBeDefined(); + expect(mod()).toBeDefined(); + // @ts-expect-error + expect(mod({ b: 'b' })).toBeDefined(); + }); }); diff --git a/packages/backend-plugin-api/src/wiring/factories.ts b/packages/backend-plugin-api/src/wiring/factories.ts index 491a18c9db..831e40f9f8 100644 --- a/packages/backend-plugin-api/src/wiring/factories.ts +++ b/packages/backend-plugin-api/src/wiring/factories.ts @@ -44,10 +44,11 @@ export interface BackendPluginConfig { /** @public */ export function createBackendPlugin< - TOptions extends { [name: string]: unknown } | undefined = undefined, ->( - config: BackendPluginConfig, -): undefined extends TOptions + TOptions extends object | undefined = undefined, +>(config: { + id: string; + register(reg: BackendRegistrationPoints, options: TOptions): void; +}): undefined extends TOptions ? (options?: TOptions) => BackendFeature : (options: TOptions) => BackendFeature { return (options?: TOptions) => ({ @@ -68,9 +69,20 @@ export interface BackendModuleConfig { ): void; } -/** @public */ +/** + * @public + * + * Creates a new backend module for a given plugin. + * + * The `moduleId` should be equal to the module-specific prefix of the exported name, such + * that the full name is `moduleId + PluginId + "Module"`. For example, a GitHub entity + * provider module for the `catalog` plugin might have the module ID `'githubEntityProvider'`, + * and the full exported name would be `githubEntityProviderCatalogModule`. + * + * The `pluginId` should exactly match the `id` of the plugin that the module extends. + */ export function createBackendModule< - TOptions extends { [name: string]: unknown } | undefined = undefined, + TOptions extends object | undefined = undefined, >( config: BackendModuleConfig, ): undefined extends TOptions diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index 0510a54825..87d3acf503 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/backend-tasks +## 0.3.6 + +### Patch Changes + +- d4fea86ea3: Added new function `readTaskScheduleDefinitionFromConfig` to read `TaskScheduleDefinition` (aka. schedule) from the `Config`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 0.3.6-next.2 + +### Patch Changes + +- d4fea86ea3: Added new function `readTaskScheduleDefinitionFromConfig` to read `TaskScheduleDefinition` (aka. schedule) from the `Config`. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## 0.3.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + ## 0.3.5 ### Patch Changes diff --git a/packages/backend-tasks/api-report.md b/packages/backend-tasks/api-report.md index c0802fa46c..a60f6dba37 100644 --- a/packages/backend-tasks/api-report.md +++ b/packages/backend-tasks/api-report.md @@ -30,6 +30,11 @@ export interface PluginTaskScheduler { triggerTask(id: string): Promise; } +// @public +export function readTaskScheduleDefinitionFromConfig( + config: Config, +): TaskScheduleDefinition; + // @public export type TaskFunction = | ((abortSignal: AbortSignal_2) => void | Promise) @@ -60,6 +65,19 @@ export interface TaskScheduleDefinition { timeout: Duration | HumanDuration; } +// @public +export interface TaskScheduleDefinitionConfig { + frequency: + | { + cron: string; + } + | string + | HumanDuration; + initialDelay?: string | HumanDuration; + scope?: 'global' | 'local'; + timeout: string | HumanDuration; +} + // @public export class TaskScheduler { constructor(databaseManager: DatabaseManager, logger: Logger); diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index 88b8cf0345..6c10478e6c 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-tasks", "description": "Common distributed task management library for Backstage backends", - "version": "0.3.5", + "version": "0.3.6", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts index f6177b57f7..d61948a929 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.test.ts @@ -45,7 +45,7 @@ describe('PluginTaskManagerImpl', () => { ); jest.useFakeTimers(); - }, 30_000); + }, 60_000); async function init(databaseId: TestDatabaseId) { const knex = await databases.init(databaseId); diff --git a/packages/backend-tasks/src/tasks/index.ts b/packages/backend-tasks/src/tasks/index.ts index 990d6332fc..8f30e5c8dd 100644 --- a/packages/backend-tasks/src/tasks/index.ts +++ b/packages/backend-tasks/src/tasks/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +export { readTaskScheduleDefinitionFromConfig } from './readTaskScheduleDefinitionFromConfig'; export { TaskScheduler } from './TaskScheduler'; export type { PluginTaskScheduler, @@ -21,5 +22,6 @@ export type { TaskInvocationDefinition, TaskRunner, TaskScheduleDefinition, + TaskScheduleDefinitionConfig, HumanDuration, } from './types'; diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts new file mode 100644 index 0000000000..115ef89168 --- /dev/null +++ b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.test.ts @@ -0,0 +1,119 @@ +/* + * 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 { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; +import { readTaskScheduleDefinitionFromConfig } from './readTaskScheduleDefinitionFromConfig'; +import { HumanDuration } from './types'; + +describe('readTaskScheduleDefinitionFromConfig', () => { + it('all valid values', () => { + const config = new ConfigReader({ + frequency: { + cron: '0 30 * * * *', + }, + timeout: 'PT3M', + initialDelay: { + minutes: 20, + }, + scope: 'global', + }); + + const result = readTaskScheduleDefinitionFromConfig(config); + + expect((result.frequency as { cron: string }).cron).toBe('0 30 * * * *'); + expect(result.timeout).toEqual(Duration.fromISO('PT3M')); + expect((result.initialDelay as HumanDuration).minutes).toEqual(20); + expect(result.scope).toBe('global'); + }); + + it('all valid required values', () => { + const config = new ConfigReader({ + frequency: { + cron: '0 30 * * * *', + }, + timeout: 'PT3M', + }); + + const result = readTaskScheduleDefinitionFromConfig(config); + + expect((result.frequency as { cron: string }).cron).toBe('0 30 * * * *'); + expect(result.timeout).toEqual(Duration.fromISO('PT3M')); + expect(result.initialDelay).toBeUndefined(); + expect(result.scope).toBeUndefined(); + }); + + it('fail without required frequency', () => { + const config = new ConfigReader({ + timeout: 'PT3M', + }); + + expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + "Missing required config value at 'frequency'", + ); + }); + + it('fail without required timeout', () => { + const config = new ConfigReader({ + frequency: 'PT30M', + }); + + expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + "Missing required config value at 'timeout'", + ); + }); + + it('invalid frequency value', () => { + const config = new ConfigReader({ + frequency: { + invalid: 'value', + }, + timeout: 'PT3M', + }); + + expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + 'HumanDuration needs at least one of', + ); + }); + + it('frequency value with additional invalid prop', () => { + const config = new ConfigReader({ + frequency: { + minutes: 20, + invalid: 'value', + }, + timeout: 'PT3M', + }); + + expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + 'HumanDuration does not contain properties: invalid', + ); + }); + + it('invalid scope value', () => { + const config = new ConfigReader({ + frequency: { + years: 2, + }, + timeout: 'PT3M', + scope: 'invalid', + }); + + expect(() => readTaskScheduleDefinitionFromConfig(config)).toThrow( + 'Only "global" or "local" are allowed for TaskScheduleDefinition.scope, but got: invalid', + ); + }); +}); diff --git a/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts new file mode 100644 index 0000000000..3267d8ef14 --- /dev/null +++ b/packages/backend-tasks/src/tasks/readTaskScheduleDefinitionFromConfig.ts @@ -0,0 +1,102 @@ +/* + * 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 { Config } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; +import { HumanDuration, TaskScheduleDefinition } from './types'; +import { Duration } from 'luxon'; + +const propsOfHumanDuration = [ + 'years', + 'months', + 'weeks', + 'days', + 'hours', + 'minutes', + 'seconds', + 'milliseconds', +]; + +function convertToHumanDuration(config: Config, key: string): HumanDuration { + const props = config.getConfig(key).keys(); + if (!props.find(prop => propsOfHumanDuration.includes(prop))) { + throw new Error( + `HumanDuration needs at least one of: ${propsOfHumanDuration}`, + ); + } + + const invalidProps = props.filter( + prop => !propsOfHumanDuration.includes(prop), + ); + if (invalidProps.length > 0) { + throw new Error( + `HumanDuration does not contain properties: ${invalidProps}`, + ); + } + + return config.get(key) as HumanDuration; +} + +function readDuration(config: Config, key: string): Duration | HumanDuration { + return typeof config.get(key) === 'string' + ? Duration.fromISO(config.getString(key)) + : convertToHumanDuration(config, key); +} + +function readCronOrDuration( + config: Config, + key: string, +): { cron: string } | Duration | HumanDuration { + const value = config.get(key); + if (typeof value === 'object' && (value as { cron?: string }).cron) { + return value as { cron: string }; + } + + return readDuration(config, key); +} + +/** + * Reads a TaskScheduleDefinition from a Config. + * Expects the config not to be the root config, + * but the config for the definition. + * + * @param config - config for a TaskScheduleDefinition. + * @public + */ +export function readTaskScheduleDefinitionFromConfig( + config: Config, +): TaskScheduleDefinition { + const frequency = readCronOrDuration(config, 'frequency'); + const timeout = readDuration(config, 'timeout'); + + const initialDelay = config.has('initialDelay') + ? readDuration(config, 'initialDelay') + : undefined; + + const scope = config.getOptionalString('scope'); + if (scope && !['global', 'local'].includes(scope)) { + throw new Error( + `Only "global" or "local" are allowed for TaskScheduleDefinition.scope, but got: ${scope}`, + ); + } + + return { + frequency, + timeout, + initialDelay, + scope: scope as 'global' | 'local' | undefined, + }; +} diff --git a/packages/backend-tasks/src/tasks/types.ts b/packages/backend-tasks/src/tasks/types.ts index 86c0c5c462..92ec045605 100644 --- a/packages/backend-tasks/src/tasks/types.ts +++ b/packages/backend-tasks/src/tasks/types.ts @@ -58,7 +58,7 @@ export interface TaskScheduleDefinition { * * @remarks * - * This is a best effort value; under some circumstances there can be + * This is the best effort value; under some circumstances there can be * deviations. For example, if the task runtime is longer than the frequency * and the timeout has not been given or not been exceeded yet, the next * invocation of this task will be delayed until after the previous one @@ -112,7 +112,7 @@ export interface TaskScheduleDefinition { * collaborating on a task that has its `scope` field set to `'global'`, then * you may still see the task being processed by other long-lived workers, * while any given single worker is in its initial sleep delay time e.g. after - * a deployment. Therefore this parameter is not useful for "globally" pausing + * a deployment. Therefore, this parameter is not useful for "globally" pausing * work; its main intended use is for individual machines to get a chance to * reach some equilibrium at startup before triggering heavy batch workloads. */ @@ -128,7 +128,104 @@ export interface TaskScheduleDefinition { * attempt to ensure that only one worker machine runs the task at a time, * according to the given cadence. This means that as the number of worker * hosts increases, the invocation frequency of this task will not go up. - * Instead the load is spread randomly across hosts. This setting is useful + * Instead, the load is spread randomly across hosts. This setting is useful + * for tasks that access shared resources, for example catalog ingestion tasks + * where you do not want many machines to repeatedly import the same data and + * trample over each other. + * + * When the scope is set to `'local'`, there is no concurrency control across + * hosts. Each host runs the task according to the given cadence similarly to + * `setInterval`, but the runtime ensures that there are no overlapping runs. + * + * @defaultValue 'global' + */ + scope?: 'global' | 'local'; +} + +/** + * Config options for {@link TaskScheduleDefinition} + * that control the scheduling of a task. + * + * @public + */ +export interface TaskScheduleDefinitionConfig { + /** + * How often you want the task to run. The system does its best to avoid + * overlapping invocations. + * + * @remarks + * + * This is the best effort value; under some circumstances there can be + * deviations. For example, if the task runtime is longer than the frequency + * and the timeout has not been given or not been exceeded yet, the next + * invocation of this task will be delayed until after the previous one + * finishes. + * + * This is a required field. + */ + frequency: + | { + /** + * A crontab style string. + * + * @remarks + * + * Overview: + * + * ``` + * ┌────────────── second (optional) + * │ ┌──────────── minute + * │ │ ┌────────── hour + * │ │ │ ┌──────── day of month + * │ │ │ │ ┌────── month + * │ │ │ │ │ ┌──── day of week + * │ │ │ │ │ │ + * │ │ │ │ │ │ + * * * * * * * + * ``` + */ + cron: string; + } + | string + | HumanDuration; + + /** + * The maximum amount of time that a single task invocation can take, before + * it's considered timed out and gets "released" such that a new invocation + * is permitted to take place (possibly, then, on a different worker). + */ + timeout: string | HumanDuration; + + /** + * The amount of time that should pass before the first invocation happens. + * + * @remarks + * + * This can be useful in cold start scenarios to stagger or delay some heavy + * compute jobs. If no value is given for this field then the first invocation + * will happen as soon as possible according to the cadence. + * + * NOTE: This is a per-worker delay. If you have a cluster of workers all + * collaborating on a task that has its `scope` field set to `'global'`, then + * you may still see the task being processed by other long-lived workers, + * while any given single worker is in its initial sleep delay time e.g. after + * a deployment. Therefore, this parameter is not useful for "globally" pausing + * work; its main intended use is for individual machines to get a chance to + * reach some equilibrium at startup before triggering heavy batch workloads. + */ + initialDelay?: string | HumanDuration; + + /** + * Sets the scope of concurrency control / locking to apply for invocations of + * this task. + * + * @remarks + * + * When the scope is set to the default value `'global'`, the scheduler will + * attempt to ensure that only one worker machine runs the task at a time, + * according to the given cadence. This means that as the number of worker + * hosts increases, the invocation frequency of this task will not go up. + * Instead, the load is spread randomly across hosts. This setting is useful * for tasks that access shared resources, for example catalog ingestion tasks * where you do not want many machines to repeatedly import the same data and * trample over each other. diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index 27e41be568..e32a82bab8 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/backend-test-utils +## 0.1.29 + +### Patch Changes + +- 72549952d1: Fixed handling of root scoped services in `startTestBackend`. +- e91e8e9c55: Increased test database max connection pool size to reduce the risk of resource exhaustion. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/cli@0.20.0 + - @backstage/backend-app-api@0.2.2 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/config@1.0.3 + +## 0.1.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/cli@0.20.0-next.2 + - @backstage/backend-app-api@0.2.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + +## 0.1.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/backend-app-api@0.2.2-next.1 + - @backstage/config@1.0.3-next.1 + +## 0.1.29-next.0 + +### Patch Changes + +- 72549952d1: Fixed handling of root scoped services in `startTestBackend`. +- e91e8e9c55: Increased test database max connection pool size to reduce the risk of resource exhaustion. +- Updated dependencies + - @backstage/backend-app-api@0.2.2-next.0 + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/cli@0.20.0-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + ## 0.1.28 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index b1006b8464..aeedaec8e3 100644 --- a/packages/backend-test-utils/package.json +++ b/packages/backend-test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/backend-test-utils", "description": "Test helpers library for Backstage backends", - "version": "0.1.28", + "version": "0.1.29", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/packages/backend-test-utils/src/next/wiring/TestBackend.ts b/packages/backend-test-utils/src/next/wiring/TestBackend.ts index f93b34cdd8..677ba930cc 100644 --- a/packages/backend-test-utils/src/next/wiring/TestBackend.ts +++ b/packages/backend-test-utils/src/next/wiring/TestBackend.ts @@ -63,10 +63,18 @@ export async function startTestBackend< if (Array.isArray(serviceDef)) { // if type is ExtensionPoint? // do something differently? + const [ref, impl] = serviceDef; + if (ref.scope === 'plugin') { + return createServiceFactory({ + service: ref, + deps: {}, + factory: async () => async () => impl, + }); + } return createServiceFactory({ - service: serviceDef[0], + service: ref, deps: {}, - factory: async () => async () => serviceDef[1], + factory: async () => impl, }); } return serviceDef as ServiceFactory; diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index b196b1df9e..e519556f7f 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,173 @@ # example-backend +## 0.2.76 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-auth-backend@0.17.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-tech-insights-backend@0.5.3 + - @backstage/plugin-search-backend@1.1.0 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-playlist-backend@0.2.0 + - @backstage/plugin-jenkins-backend@0.1.27 + - @backstage/plugin-app-backend@0.3.37 + - @backstage/plugin-badges-backend@0.1.31 + - @backstage/plugin-graphql-backend@0.1.27 + - @backstage/plugin-permission-backend@0.5.12 + - @backstage/plugin-rollbar-backend@0.1.34 + - @backstage/plugin-kubernetes-backend@0.7.3 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/plugin-search-backend-module-pg@0.4.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3 + - @backstage/plugin-techdocs-backend@1.4.0 + - @backstage/plugin-tech-insights-node@0.3.5 + - example-app@0.2.76 + - @backstage/plugin-code-coverage-backend@0.2.3 + - @backstage/plugin-kafka-backend@0.2.30 + - @backstage/plugin-todo-backend@0.1.34 + - @backstage/plugin-azure-devops-backend@0.3.16 + - @backstage/plugin-proxy-backend@0.2.31 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + +## 0.2.76-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/plugin-playlist-backend@0.2.0-next.2 + - @backstage/plugin-badges-backend@0.1.31-next.2 + - @backstage/plugin-graphql-backend@0.1.27-next.2 + - @backstage/plugin-permission-backend@0.5.12-next.2 + - @backstage/plugin-rollbar-backend@0.1.34-next.2 + - @backstage/plugin-search-backend@1.1.0-next.2 + - @backstage/plugin-tech-insights-backend@0.5.3-next.2 + - @backstage/plugin-techdocs-backend@1.4.0-next.2 + - example-app@0.2.76-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/plugin-app-backend@0.3.37-next.2 + - @backstage/plugin-auth-backend@0.17.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-azure-devops-backend@0.3.16-next.2 + - @backstage/plugin-code-coverage-backend@0.2.3-next.2 + - @backstage/plugin-jenkins-backend@0.1.27-next.2 + - @backstage/plugin-kafka-backend@0.2.30-next.2 + - @backstage/plugin-kubernetes-backend@0.7.3-next.2 + - @backstage/plugin-proxy-backend@0.2.31-next.2 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.2 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.2 + - @backstage/plugin-search-backend-module-pg@0.4.1-next.2 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.2 + - @backstage/plugin-todo-backend@0.1.34-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + +## 0.2.76-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-auth-backend@0.17.0-next.1 + - @backstage/plugin-search-backend@1.1.0-next.1 + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-scaffolder-backend@1.7.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-search-backend-node@1.0.3-next.1 + - @backstage/plugin-search-backend-module-pg@0.4.1-next.1 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.1 + - @backstage/plugin-kubernetes-backend@0.7.3-next.1 + - @backstage/plugin-tech-insights-backend@0.5.3-next.1 + - example-app@0.2.76-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/plugin-app-backend@0.3.37-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-azure-devops-backend@0.3.16-next.1 + - @backstage/plugin-badges-backend@0.1.31-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + - @backstage/plugin-code-coverage-backend@0.2.3-next.1 + - @backstage/plugin-graphql-backend@0.1.27-next.1 + - @backstage/plugin-jenkins-backend@0.1.27-next.1 + - @backstage/plugin-kafka-backend@0.2.30-next.1 + - @backstage/plugin-permission-backend@0.5.12-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + - @backstage/plugin-playlist-backend@0.1.1-next.1 + - @backstage/plugin-proxy-backend@0.2.31-next.1 + - @backstage/plugin-rollbar-backend@0.1.34-next.1 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.1 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.1 + - @backstage/plugin-tech-insights-node@0.3.5-next.1 + - @backstage/plugin-techdocs-backend@1.3.1-next.1 + - @backstage/plugin-todo-backend@0.1.34-next.1 + +## 0.2.76-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-scaffolder-backend@1.7.0-next.0 + - @backstage/plugin-auth-backend@0.17.0-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/plugin-jenkins-backend@0.1.27-next.0 + - @backstage/plugin-app-backend@0.3.37-next.0 + - @backstage/plugin-tech-insights-node@0.3.5-next.0 + - example-app@0.2.76-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-badges-backend@0.1.31-next.0 + - @backstage/plugin-code-coverage-backend@0.2.3-next.0 + - @backstage/plugin-kafka-backend@0.2.30-next.0 + - @backstage/plugin-kubernetes-backend@0.7.3-next.0 + - @backstage/plugin-playlist-backend@0.1.1-next.0 + - @backstage/plugin-tech-insights-backend@0.5.3-next.0 + - @backstage/plugin-techdocs-backend@1.3.1-next.0 + - @backstage/plugin-todo-backend@0.1.34-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/plugin-rollbar-backend@0.1.34-next.0 + - @backstage/plugin-search-backend-module-pg@0.4.1-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/plugin-azure-devops-backend@0.3.16-next.0 + - @backstage/plugin-graphql-backend@0.1.27-next.0 + - @backstage/plugin-permission-backend@0.5.12-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-proxy-backend@0.2.31-next.0 + - @backstage/plugin-search-backend@1.0.3-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@1.0.3-next.0 + - @backstage/plugin-search-backend-node@1.0.3-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.21-next.0 + ## 0.2.75 ### Patch Changes diff --git a/packages/backend/Dockerfile b/packages/backend/Dockerfile index 21ff07deb4..60c6ffd27f 100644 --- a/packages/backend/Dockerfile +++ b/packages/backend/Dockerfile @@ -13,9 +13,10 @@ FROM node:16-bullseye-slim # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, # in which case you should also move better-sqlite3 to "devDependencies" in package.json. -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ - rm -rf /var/lib/apt/lists/* && \ yarn config set python /usr/bin/python3 # From here on we use the least-privileged `node` user to run the backend. @@ -38,7 +39,8 @@ COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz # Note that this install is not immutable, which is one of the reasons we don't recommend Yarn 3 yet -RUN yarn workspaces focus --all --production && rm -rf "$(yarn cache clean)" +RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,sharing=locked,uid=1000,gid=1000 \ + yarn workspaces focus --all --production # Then copy the rest of the backend bundle, along with any other files we might want. COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ diff --git a/packages/backend/package.json b/packages/backend/package.json index 54bf8c586e..4e296616fc 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.75", + "version": "0.2.76", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,6 +36,7 @@ "@backstage/plugin-auth-backend": "workspace:^", "@backstage/plugin-auth-node": "workspace:^", "@backstage/plugin-azure-devops-backend": "workspace:^", + "@backstage/plugin-azure-sites-backend": "workspace:^", "@backstage/plugin-badges-backend": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", "@backstage/plugin-code-coverage-backend": "workspace:^", diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 774325ff44..fbd49ff49e 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/catalog-client +## 1.1.1 + +### Patch Changes + +- 4f2ac624b4: Renamed argument in `validateEntity` from `location` to `locationRef` +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/errors@1.1.2 + +## 1.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + +## 1.1.1-next.1 + +### Patch Changes + +- 4f2ac624b4: Renamed argument in `validateEntity` from `location` to `locationRef` +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + +## 1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/errors@1.1.2-next.0 + ## 1.1.0 ### Minor Changes diff --git a/packages/catalog-client/api-report.md b/packages/catalog-client/api-report.md index 0ad77ce39c..85fc389af7 100644 --- a/packages/catalog-client/api-report.md +++ b/packages/catalog-client/api-report.md @@ -68,7 +68,7 @@ export interface CatalogApi { ): Promise; validateEntity( entity: Entity, - location: string, + locationRef: string, options?: CatalogRequestOptions, ): Promise; } @@ -130,7 +130,7 @@ export class CatalogClient implements CatalogApi { ): Promise; validateEntity( entity: Entity, - location: string, + locationRef: string, options?: CatalogRequestOptions, ): Promise; } diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index fb1de71062..9c4e79b079 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/catalog-client", "description": "An isomorphic client for the catalog backend", - "version": "1.1.0", + "version": "1.1.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/catalog-client/src/CatalogClient.test.ts b/packages/catalog-client/src/CatalogClient.test.ts index 6c9c68efa2..5a344f49a3 100644 --- a/packages/catalog-client/src/CatalogClient.test.ts +++ b/packages/catalog-client/src/CatalogClient.test.ts @@ -322,7 +322,7 @@ describe('CatalogClient', () => { name: '', }, }, - 'http://example.com', + 'url:http://example.com', ), ).toMatchObject({ valid: false, @@ -350,7 +350,7 @@ describe('CatalogClient', () => { name: 'good', }, }, - 'http://example.com', + 'url:http://example.com', ), ).toMatchObject({ valid: true, @@ -373,7 +373,7 @@ describe('CatalogClient', () => { name: 'good', }, }, - 'http://example.com', + 'url:http://example.com', ), ).rejects.toThrow(/Request failed with 500 Error/); }); diff --git a/packages/catalog-client/src/CatalogClient.ts b/packages/catalog-client/src/CatalogClient.ts index f2b4186bfb..d67392d80d 100644 --- a/packages/catalog-client/src/CatalogClient.ts +++ b/packages/catalog-client/src/CatalogClient.ts @@ -359,7 +359,7 @@ export class CatalogClient implements CatalogApi { */ async validateEntity( entity: Entity, - location: string, + locationRef: string, options?: CatalogRequestOptions, ): Promise { const response = await this.fetchApi.fetch( @@ -370,7 +370,7 @@ export class CatalogClient implements CatalogApi { ...(options?.token && { Authorization: `Bearer ${options?.token}` }), }, method: 'POST', - body: JSON.stringify({ entity, location }), + body: JSON.stringify({ entity, location: locationRef }), }, ); diff --git a/packages/catalog-client/src/types/api.ts b/packages/catalog-client/src/types/api.ts index f483c3454c..d3d02022f3 100644 --- a/packages/catalog-client/src/types/api.ts +++ b/packages/catalog-client/src/types/api.ts @@ -405,11 +405,11 @@ export interface CatalogApi { * Validate entity and its location. * * @param entity - Entity to validate - * @param location - URL location of the entity + * @param locationRef - Location ref in format `url:http://example.com/file` */ validateEntity( entity: Entity, - location: string, + locationRef: string, options?: CatalogRequestOptions, ): Promise; } diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index d3f9fe4af8..2ac459101c 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/catalog-model +## 1.1.2 + +### Patch Changes + +- 6f3b8d0962: Defer `ajv` compilation of schema validators to improve module-import performance +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 1.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## 1.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + +## 1.1.2-next.0 + +### Patch Changes + +- 6f3b8d0962: Defer `ajv` compilation of schema validators to improve module-import performance +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + ## 1.1.1 ### Patch Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index ce2abee20a..91016d6a33 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/catalog-model", "description": "Types and validators that help describe the model of a Backstage Catalog", - "version": "1.1.1", + "version": "1.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 0f36ba5e56..ad63532139 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,119 @@ # @backstage/cli +## 0.20.0 + +### Minor Changes + +- f368ad7279: **BREAKING**: Bumped `jest`, `jest-runtime`, and `jest-environment-jsdom` to v29. This is up from v27, so check out both the [v28](https://jestjs.io/docs/28.x/upgrading-to-jest28) and [v29](https://jestjs.io/docs/upgrading-to-jest29) (later [here](https://jestjs.io/docs/29.x/upgrading-to-jest29)) migration guides. + + Particular changes that where encountered in the main Backstage repo are: + + - The updated snapshot format. + - `jest.useFakeTimers('legacy')` is now `jest.useFakeTimers({ legacyFakeTimers: true })`. + - Error objects collected by `withLogCollector` from `@backstage/test-utils` are now objects with a `detail` property rather than a string. + +### Patch Changes + +- 78d5eb299e: Tweak the Jest Caching loader to only operate when in `watch` mode +- 9c595302cb: Normalize on winston version ^3.2.1 +- 24b40140c4: Treat files in `__testUtils__` and `__mocks__` directories as test files for linting + purposes. + + Updates the parts of the eslint configuration generator that specify which files + should be treated as test code to include any files under two additional + directories: + + - `__mocks__`: this is the directory used by Jest[0] for mock code. + - `__testUtils__`: a suggested location for utility code executed only when + running tests. + + [0]: https://jestjs.io/docs/manual-mocks#mocking-user-modules + +- 3e309107ca: Updated fallback versions of dependencies in all templates. +- 292a088807: Added a new `repo test` command. +- ba63cae41c: Updated lockfile parsing to have better support for Yarn 3. +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- 2dddb32fea: Switched the Jest transform for YAML files to use a custom one available at `@backstage/cli/config/jestYamlTransform.js`. +- a541a3a78a: Switch to upfront resolution of `swc-loader` in Webpack config. +- cfb3598410: Removed `tsx` and `jsx` as supported extensions in backend packages. For most + repos, this will not have any effect. But if you inadvertently had added some + `tsx`/`jsx` files to your backend package, you may now start to see `code: 'MODULE_NOT_FOUND'` errors when launching the backend locally. The reason for + this is that the offending files get ignored during transpilation. Hence, the + importing file can no longer find anything to import. + + The fix is to rename any `.tsx` files in your backend packages to `.ts` instead, + or `.jsx` to `.js`. + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + - @backstage/config-loader@1.1.5 + - @backstage/errors@1.1.2 + - @backstage/release-manifests@0.0.6 + - @backstage/types@1.0.0 + +## 0.20.0-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/release-manifests@0.0.6 + - @backstage/types@1.0.0 + +## 0.20.0-next.1 + +### Patch Changes + +- 78d5eb299e: Tweak the Jest Caching loader to only operate when in `watch` mode +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.1 + - @backstage/config-loader@1.1.5-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/release-manifests@0.0.6 + - @backstage/types@1.0.0 + +## 0.20.0-next.0 + +### Minor Changes + +- f368ad7279: **BREAKING**: Bumped `jest`, `jest-runtime`, and `jest-environment-jsdom` to v29. This is up from v27, so check out both the [v28](https://jestjs.io/docs/28.x/upgrading-to-jest28) and [v29](https://jestjs.io/docs/upgrading-to-jest29) (later [here](https://jestjs.io/docs/29.x/upgrading-to-jest29)) migration guides. + + Particular changes that where encountered in the main Backstage repo are: + + - The updated snapshot format. + - `jest.useFakeTimers('legacy')` is now `jest.useFakeTimers({ legacyFakeTimers: true })`. + - Error objects collected by `withLogCollector` from `@backstage/test-utils` are now objects with a `detail` property rather than a string. + +### Patch Changes + +- 3e309107ca: Updated fallback versions of dependencies in all templates. +- 292a088807: Added a new `repo test` command. +- ba63cae41c: Updated lockfile parsing to have better support for Yarn 3. +- 2dddb32fea: Switched the Jest transform for YAML files to use a custom one available at `@backstage/cli/config/jestYamlTransform.js`. +- a541a3a78a: Switch to upfront resolution of `swc-loader` in Webpack config. +- cfb3598410: Removed `tsx` and `jsx` as supported extensions in backend packages. For most + repos, this will not have any effect. But if you inadvertently had added some + `tsx`/`jsx` files to your backend package, you may now start to see `code: 'MODULE_NOT_FOUND'` errors when launching the backend locally. The reason for + this is that the offending files get ignored during transpilation. Hence, the + importing file can no longer find anything to import. + + The fix is to rename any `.tsx` files in your backend packages to `.ts` instead, + or `.jsx` to `.js`. + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.0 + - @backstage/config-loader@1.1.5-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/release-manifests@0.0.6 + - @backstage/types@1.0.0 + ## 0.19.0 ### Minor Changes diff --git a/packages/cli/config/eslint-factory.js b/packages/cli/config/eslint-factory.js index 6e2a510c4a..e213f58e4e 100644 --- a/packages/cli/config/eslint-factory.js +++ b/packages/cli/config/eslint-factory.js @@ -96,12 +96,16 @@ function createConfig(dir, extraConfig = {}) { `!${joinPath(dir, 'src/**')}`, joinPath(dir, 'src/**/*.test.*'), joinPath(dir, 'src/**/*.stories.*'), + joinPath(dir, 'src/**/__testUtils__/**'), + joinPath(dir, 'src/**/__mocks__/**'), joinPath(dir, 'src/setupTests.*'), ] : [ // Legacy config for packages that don't provide a dir '**/*.test.*', '**/*.stories.*', + '**/__testUtils__/**', + '**/__mocks__/**', '**/src/setupTests.*', '**/dev/**', ], @@ -137,6 +141,8 @@ function createConfig(dir, extraConfig = {}) { // Prevent imports of stories or tests '*.stories*', '*.test*', + '**/__testUtils__/**', + '**/__mocks__/**', ], }, ], @@ -159,7 +165,14 @@ function createConfig(dir, extraConfig = {}) { }, }, { - files: ['**/*.test.*', '**/*.stories.*', 'src/setupTests.*', '!src/**'], + files: [ + '**/*.test.*', + '**/*.stories.*', + '**/__testUtils__/**', + '**/__mocks__/**', + 'src/setupTests.*', + '!src/**', + ], rules: { ...testRules, 'no-restricted-syntax': [ diff --git a/packages/cli/config/jest.js b/packages/cli/config/jest.js index 8b6c37bda4..18f563a3a3 100644 --- a/packages/cli/config/jest.js +++ b/packages/cli/config/jest.js @@ -25,6 +25,11 @@ const envOptions = { enableSourceMaps: Boolean(process.env.ENABLE_SOURCE_MAPS), }; +if (envOptions.nextTests) { + // Needed so that, at import-time, it can hook into Jest's internals. + require('./jestCachingModuleLoader'); +} + const transformIgnorePattern = [ '@material-ui', 'ajv', diff --git a/packages/cli/config/jestCachingModuleLoader.js b/packages/cli/config/jestCachingModuleLoader.js index 9ef29d7485..7858a7cfb7 100644 --- a/packages/cli/config/jestCachingModuleLoader.js +++ b/packages/cli/config/jestCachingModuleLoader.js @@ -21,6 +21,7 @@ const fileTransformCache = new Map(); const scriptTransformCache = new Map(); let runtimeGeneration = 0; +let isWatchMode; module.exports = class CachingJestRuntime extends JestRuntime { // Each Jest run creates a new runtime, including when rerunning tests in @@ -28,6 +29,10 @@ module.exports = class CachingJestRuntime extends JestRuntime { __runtimeGeneration = runtimeGeneration++; transformFile(filename, options) { + if (!isWatchMode) { + return super.transformFile(filename, options); + } + const entry = fileTransformCache.get(filename); if (entry) { // Only check modification time if it's from a different runtime generation @@ -79,3 +84,11 @@ module.exports = class CachingJestRuntime extends JestRuntime { return script; } }; + +// Inject hook into createHasteMap, as it's the only way that we can +// determine (from our scope here) if we're in "watch mode" or not. +const originalCreateHasteMap = JestRuntime.createHasteMap; +JestRuntime.createHasteMap = (config, options = undefined) => { + isWatchMode = options && options.watch; + return originalCreateHasteMap(config, options); +}; diff --git a/packages/cli/package.json b/packages/cli/package.json index b07f2a0263..f460bfa4e0 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.19.0", + "version": "0.20.0", "publishConfig": { "access": "public" }, diff --git a/packages/cli/src/lib/bundler/config.ts b/packages/cli/src/lib/bundler/config.ts index 7ce9499f45..1974153f02 100644 --- a/packages/cli/src/lib/bundler/config.ts +++ b/packages/cli/src/lib/bundler/config.ts @@ -267,7 +267,7 @@ export async function createBackendConfig( paths.targetRunFile ? paths.targetRunFile : paths.targetEntry, ], resolve: { - extensions: ['.ts', '.tsx', '.js', '.jsx', '.json'], + extensions: ['.ts', '.mjs', '.js', '.json'], mainFields: ['main'], modules: [paths.rootNodeModules, ...moduleDirs], plugins: [ diff --git a/packages/cli/src/lib/bundler/transforms.ts b/packages/cli/src/lib/bundler/transforms.ts index ba8da5bad8..0a4dddcaf5 100644 --- a/packages/cli/src/lib/bundler/transforms.ts +++ b/packages/cli/src/lib/bundler/transforms.ts @@ -55,7 +55,7 @@ export const transforms = (options: TransformOptions): Transforms => { exclude: /node_modules/, use: [ { - loader: 'swc-loader', + loader: require.resolve('swc-loader'), options: { jsc: { target: 'es2019', @@ -81,7 +81,7 @@ export const transforms = (options: TransformOptions): Transforms => { exclude: /node_modules/, use: [ { - loader: 'swc-loader', + loader: require.resolve('swc-loader'), options: { jsc: { target: 'es2019', @@ -112,7 +112,7 @@ export const transforms = (options: TransformOptions): Transforms => { test: [/\.icon\.svg$/], use: [ { - loader: 'swc-loader', + loader: require.resolve('swc-loader'), options: { jsc: { target: 'es2019', diff --git a/packages/cli/templates/default-backend-plugin/package.json.hbs b/packages/cli/templates/default-backend-plugin/package.json.hbs index 0a72a5d415..df381159f0 100644 --- a/packages/cli/templates/default-backend-plugin/package.json.hbs +++ b/packages/cli/templates/default-backend-plugin/package.json.hbs @@ -33,7 +33,7 @@ "@types/express": "{{versionQuery '@types/express' '4.17.13'}}", "express": "{{versionQuery 'express' '4.18.1'}}", "express-promise-router": "{{versionQuery 'express-promise-router' '4.1.0'}}", - "winston": "{{versionQuery 'winston' '3.8.1'}}", + "winston": "{{versionQuery 'winston' '3.2.1'}}", "node-fetch": "{{versionQuery 'node-fetch' '2.6.7'}}", "yn": "{{versionQuery 'yn' '4.0.0'}}" }, diff --git a/packages/cli/templates/default-backend-plugin/src/service/router.ts b/packages/cli/templates/default-backend-plugin/src/service/router.ts index 9ceaa47627..5912a87584 100644 --- a/packages/cli/templates/default-backend-plugin/src/service/router.ts +++ b/packages/cli/templates/default-backend-plugin/src/service/router.ts @@ -33,7 +33,7 @@ export async function createRouter( router.get('/health', (_, response) => { logger.info('PONG!'); - response.send({ status: 'ok' }); + response.json({ status: 'ok' }); }); router.use(errorHandler()); return router; diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index 957910660a..161a8dbdf9 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/codemods +## 0.1.40 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## 0.1.40-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## 0.1.40-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## 0.1.40-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + ## 0.1.39 ### Patch Changes diff --git a/packages/codemods/package.json b/packages/codemods/package.json index 4be7e5a31c..cc3ad6e468 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/codemods", "description": "A collection of codemods for Backstage projects", - "version": "0.1.39", + "version": "0.1.40", "publishConfig": { "access": "public", "main": "dist/index.cjs.js" diff --git a/packages/config-loader/CHANGELOG.md b/packages/config-loader/CHANGELOG.md index f3cc482178..b5b243d788 100644 --- a/packages/config-loader/CHANGELOG.md +++ b/packages/config-loader/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/config-loader +## 1.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 1.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## 1.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + +## 1.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + ## 1.1.4 ### Patch Changes diff --git a/packages/config-loader/package.json b/packages/config-loader/package.json index 15d622cf5d..6a96e7841b 100644 --- a/packages/config-loader/package.json +++ b/packages/config-loader/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config-loader", "description": "Config loading functionality used by Backstage backend, and CLI", - "version": "1.1.4", + "version": "1.1.5", "publishConfig": { "access": "public", "main": "dist/index.cjs.js", diff --git a/packages/config/CHANGELOG.md b/packages/config/CHANGELOG.md index 7774d1aa5c..a03770f991 100644 --- a/packages/config/CHANGELOG.md +++ b/packages/config/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/config +## 1.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 1.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + ## 1.0.2 ### Patch Changes diff --git a/packages/config/package.json b/packages/config/package.json index fec1fa88ad..018aeb47cf 100644 --- a/packages/config/package.json +++ b/packages/config/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/config", "description": "Config API used by Backstage core, backend, and CLI", - "version": "1.0.2", + "version": "1.0.3", "publishConfig": { "access": "public", "main": "dist/index.cjs.js", diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 5ec66460c6..147a3737cc 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/core-app-api +## 1.1.1 + +### Patch Changes + +- 27e6404aba: Fixed a bug where gathered index routes would fail to bind routable extensions. This would typically show up when placing a routable extension in the entity page overview tab. +- Updated dependencies + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.1.1-next.1 + +### Patch Changes + +- 27e6404aba: Fixed a bug where gathered index routes would fail to bind routable extensions. This would typically show up when placing a routable extension in the entity page overview tab. +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + ## 1.1.0 ### Minor Changes diff --git a/packages/core-app-api/config.d.ts b/packages/core-app-api/config.d.ts index d88c818d11..ae876f7fe0 100644 --- a/packages/core-app-api/config.d.ts +++ b/packages/core-app-api/config.d.ts @@ -89,6 +89,11 @@ export interface Config { name?: string; }; + /** + * This config was used by the HomepageTimer but has been replaced by the HeaderWorldClock in the home plugin + * + * @deprecated in favor of the HeaderWorldClock which is found in the home plugin + */ homepage?: { clocks?: Array<{ /** @visibility frontend */ diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index d3f8f04fc2..7d4b6eba30 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "1.1.0", + "version": "1.1.1", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/core-app-api/src/routing/collectors.stable.test.tsx b/packages/core-app-api/src/routing/collectors.stable.test.tsx index f8f71a1a9a..de6f350cc9 100644 --- a/packages/core-app-api/src/routing/collectors.stable.test.tsx +++ b/packages/core-app-api/src/routing/collectors.stable.test.tsx @@ -301,7 +301,7 @@ describe('discovery', () => { HELLO }> - + @@ -322,8 +322,8 @@ describe('discovery', () => { [ref1, 'foo'], [ref2, 'foo'], [ref3, 'bar'], - [ref4, 'baz'], - [ref5, 'baz'], + [ref4, ''], + [ref5, ''], ]); expect(sortedEntries(routing.parents)).toEqual([ [ref1, undefined], @@ -337,7 +337,7 @@ describe('discovery', () => { routeObj( 'bar', [ref3], - [routeObj('baz', [ref4, ref5], [], 'gathered')], + [routeObj('', [ref4, ref5], [], 'gathered')], undefined, plugin, ), diff --git a/packages/core-app-api/src/routing/collectors.tsx b/packages/core-app-api/src/routing/collectors.tsx index 815ae534f1..0bf5e676a4 100644 --- a/packages/core-app-api/src/routing/collectors.tsx +++ b/packages/core-app-api/src/routing/collectors.tsx @@ -178,7 +178,7 @@ export const routingV2Collector = createCollector( } if (mountPoint) { - if (!ctx?.gatherPath) { + if (ctx?.gatherPath === undefined) { throw new Error( `Routable extension "${stringifyNode( node, diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index 66284b6bc6..0d58a7032a 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/core-components +## 0.11.2 + +### Patch Changes + +- bde1e8c8e2: Added `curve` prop to the `DependencyGraph` component to select the type of layout +- 882101cd9b: Deep-import LightAsync component to improve module-import speed +- 0d0f30d87e: Fixed layout for core-components docs where table was broken by splitting with code sample +- Updated dependencies + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + +## 0.11.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + +## 0.11.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + +## 0.11.2-next.0 + +### Patch Changes + +- 882101cd9b: Deep-import LightAsync component to improve module-import speed +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/version-bridge@1.0.1 + ## 0.11.1 ### Patch Changes diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 99d18b9b12..fc3ab4e5fc 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -242,6 +242,7 @@ export interface DependencyGraphProps acyclicer?: 'greedy'; // Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver align?: Alignment; + curve?: 'curveStepBefore' | 'curveMonotoneX'; defs?: SVGDefsElement | SVGDefsElement[]; // Warning: (ae-unresolved-link) The @link reference could not be resolved: This type of declaration is not supported yet by the resolver direction?: Direction; @@ -494,9 +495,6 @@ export type HeaderTabsClassKey = // @public (undocumented) export function HelpIcon(props: IconComponentProps): JSX.Element; -// @public @deprecated -export function HomepageTimer(_props: {}): JSX.Element | null; - // Warning: (ae-forgotten-export) The symbol "Props" needs to be exported by the entry point index.d.ts // // @public diff --git a/packages/core-components/package.json b/packages/core-components/package.json index 2c861c145a..4c7e9fb4db 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-components", "description": "Core components used by Backstage plugins and apps", - "version": "0.11.1", + "version": "0.11.2", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx b/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx index 0e4b12defc..fd83797a88 100644 --- a/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx +++ b/packages/core-components/src/components/CodeSnippet/CodeSnippet.tsx @@ -18,7 +18,8 @@ import React from 'react'; import { useTheme } from '@material-ui/core/styles'; import { BackstageTheme } from '@backstage/theme'; import { CopyTextButton } from '../CopyTextButton'; -import { LightAsync } from 'react-syntax-highlighter'; +import type {} from 'react-syntax-highlighter'; +import { default as LightAsync } from 'react-syntax-highlighter/dist/esm/light-async'; import dark from 'react-syntax-highlighter/dist/esm/styles/hljs/dark'; import docco from 'react-syntax-highlighter/dist/esm/styles/hljs/docco'; diff --git a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx index 9f447f80cc..5159f67d9e 100644 --- a/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx +++ b/packages/core-components/src/components/DependencyGraph/DependencyGraph.tsx @@ -162,6 +162,14 @@ export interface DependencyGraphProps * Default: `enabled` */ zoom?: 'enabled' | 'disabled' | 'enable-on-click'; + /** + * A factory for curve generators addressing both lines and areas. + * + * @remarks + * + * Default: 'curveMonotoneX' + */ + curve?: 'curveStepBefore' | 'curveMonotoneX'; } const WORKSPACE_ID = 'workspace'; @@ -194,6 +202,7 @@ export function DependencyGraph( renderLabel, defs, zoom = 'enabled', + curve = 'curveMonotoneX', ...svgProps } = props; const theme: BackstageTheme = useTheme(); @@ -424,6 +433,7 @@ export function DependencyGraph( setEdge={setEdge} render={renderLabel} edge={edge} + curve={curve} /> ); })} diff --git a/packages/core-components/src/components/DependencyGraph/Edge.test.tsx b/packages/core-components/src/components/DependencyGraph/Edge.test.tsx index db6fbe7552..21d677388e 100644 --- a/packages/core-components/src/components/DependencyGraph/Edge.test.tsx +++ b/packages/core-components/src/components/DependencyGraph/Edge.test.tsx @@ -21,6 +21,7 @@ import { RenderLabelProps } from './types'; const fromNode = 'node'; const toNode = 'other-node'; +const curve: 'curveStepBefore' | 'curveMonotoneX' = 'curveMonotoneX'; const edge = { points: [ @@ -46,6 +47,7 @@ const minProps = { setEdge, renderElement, edge, + curve, }; const label = 'label'; diff --git a/packages/core-components/src/components/DependencyGraph/Edge.tsx b/packages/core-components/src/components/DependencyGraph/Edge.tsx index cc6a044beb..aae737b9ea 100644 --- a/packages/core-components/src/components/DependencyGraph/Edge.tsx +++ b/packages/core-components/src/components/DependencyGraph/Edge.tsx @@ -25,7 +25,7 @@ import { DependencyEdge, LabelPosition, } from './types'; -import { ARROW_MARKER_ID, EDGE_TEST_ID, LABEL_TEST_ID } from './constants'; +import { EDGE_TEST_ID, LABEL_TEST_ID } from './constants'; import { DefaultLabel } from './DefaultLabel'; import dagre from 'dagre'; @@ -47,7 +47,7 @@ export type DependencyGraphEdgeClassKey = 'path' | 'label'; const useStyles = makeStyles( (theme: BackstageTheme) => ({ path: { - strokeWidth: 2, + strokeWidth: 1, stroke: theme.palette.textSubtle, fill: 'none', transition: `${theme.transitions.duration.shortest}ms`, @@ -70,23 +70,19 @@ export type EdgeComponentProps = { id: dagre.Edge, edge: DependencyEdge, ) => dagre.graphlib.Graph<{}>; + curve: 'curveStepBefore' | 'curveMonotoneX'; }; const renderDefault = (props: RenderLabelProps) => ( ); -const createPath = d3Shape - .line() - .x(d => d.x) - .y(d => d.y) - .curve(d3Shape.curveMonotoneX); - export function Edge({ render = renderDefault, setEdge, id, edge, + curve, }: EdgeComponentProps) { const { x = 0, y = 0, width, height, points } = edge; const labelProps: DependencyEdge = edge; @@ -114,6 +110,16 @@ export function Edge({ let path: string = ''; + const createPath = React.useMemo( + () => + d3Shape + .line() + .x(d => d.x) + .y(d => d.y) + .curve(d3Shape[curve]), + [curve], + ); + if (points) { const finitePoints = points.filter( (point: EdgePoint) => isFinite(point.x) && isFinite(point.y), @@ -124,12 +130,7 @@ export function Edge({ return ( <> {path && ( - + )} {labelProps.label ? ( (props: TableProps) { } data={typeof data === 'function' ? data : tableData} style={{ width: '100%' }} - localization={{ toolbar: { searchPlaceholder: 'Filter' } }} + localization={{ + toolbar: { searchPlaceholder: 'Filter', searchTooltip: 'Filter' }, + }} {...restProps} />
diff --git a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx index 661bb1da22..2ddcef52ca 100644 --- a/packages/core-components/src/components/WarningPanel/WarningPanel.tsx +++ b/packages/core-components/src/components/WarningPanel/WarningPanel.tsx @@ -134,7 +134,7 @@ const capitalize = (s: string) => { }; /** - * WarningPanel. Show a user friendly error message to a user similar to + * Show a user friendly error message to a user similar to * ErrorPanel except that the warning panel only shows the warning message to * the user. * diff --git a/packages/core-components/src/layout/HomepageTimer/HomepageTimer.test.tsx b/packages/core-components/src/layout/HomepageTimer/HomepageTimer.test.tsx deleted file mode 100644 index 9a7a8ac47d..0000000000 --- a/packages/core-components/src/layout/HomepageTimer/HomepageTimer.test.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2021 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 { - renderWithEffects, - TestApiProvider, - withLogCollector, -} from '@backstage/test-utils'; -import { HomepageTimer } from './HomepageTimer'; -import React from 'react'; -import { lightTheme } from '@backstage/theme'; -import { ThemeProvider } from '@material-ui/core/styles'; -import { ConfigReader } from '@backstage/core-app-api'; -import { ConfigApi, configApiRef } from '@backstage/core-plugin-api'; - -it('changes default timezone to GMT', async () => { - const configApi: ConfigApi = new ConfigReader({ - homepage: { - clocks: [ - { - label: 'New York', - timezone: 'America/New_Pork', - }, - ], - }, - context: 'test', - }); - - const { warn } = await withLogCollector(async () => { - const rendered = await renderWithEffects( - - - - - , - ); - - expect(rendered.getByText('GMT')).toBeInTheDocument(); - }); - expect(warn).toEqual([ - 'The timezone America/New_Pork is invalid. Defaulting to GMT', - ]); -}); diff --git a/packages/core-components/src/layout/HomepageTimer/HomepageTimer.tsx b/packages/core-components/src/layout/HomepageTimer/HomepageTimer.tsx deleted file mode 100644 index 71e764987b..0000000000 --- a/packages/core-components/src/layout/HomepageTimer/HomepageTimer.tsx +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2020 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 { HeaderLabel } from '../HeaderLabel'; -import { ConfigApi, useApi, configApiRef } from '@backstage/core-plugin-api'; - -const timeFormat: Intl.DateTimeFormatOptions = { - hour: '2-digit', - minute: '2-digit', -}; - -type TimeObj = { - time: string; - label: string; -}; - -function getTimes(configApi: ConfigApi) { - const d = new Date(); - const lang = window.navigator.language; - - const clocks: TimeObj[] = []; - - if (!configApi.has('homepage.clocks')) { - return clocks; - } - - const clockConfigs = configApi.getConfigArray('homepage.clocks'); - - for (const clock of clockConfigs) { - if (clock.has('label') && clock.has('timezone')) { - let label = clock.getString('label'); - - const options: Intl.DateTimeFormatOptions = { - timeZone: clock.getString('timezone'), - ...timeFormat, - }; - - try { - new Date().toLocaleString(lang, options); - } catch (e) { - // eslint-disable-next-line no-console - console.warn( - `The timezone ${options.timeZone} is invalid. Defaulting to GMT`, - ); - options.timeZone = 'GMT'; - label = 'GMT'; - } - - const time = d.toLocaleTimeString(lang, options); - clocks.push({ time, label }); - } - } - return clocks; -} - -/** - * Please use the HeaderWorldClock in the home plugin - * - * @public - * @deprecated in favor of the HeaderWorldClock which is found in the to home plugin - */ -export function HomepageTimer(_props: {}) { - const configApi = useApi(configApiRef); - - const defaultTimes: TimeObj[] = []; - const [clocks, setTimes] = React.useState(defaultTimes); - - React.useEffect(() => { - setTimes(getTimes(configApi)); - - const intervalId = setInterval(() => { - setTimes(getTimes(configApi)); - }, 1000); - - return () => { - clearInterval(intervalId); - }; - }, [configApi]); - - if (clocks.length !== 0) { - return ( - <> - {clocks.map(clock => ( - - ))} - - ); - } - return null; -} diff --git a/packages/core-components/src/layout/ItemCard/ItemCard.tsx b/packages/core-components/src/layout/ItemCard/ItemCard.tsx index e2efd0c136..fedcf7de8f 100644 --- a/packages/core-components/src/layout/ItemCard/ItemCard.tsx +++ b/packages/core-components/src/layout/ItemCard/ItemCard.tsx @@ -40,6 +40,7 @@ type ItemCardProps = { * This card type has been deprecated. Instead use plain MUI Card and helpers * where appropriate. * + * @example * ``` * * diff --git a/packages/core-components/src/layout/ItemCard/ItemCardGrid.tsx b/packages/core-components/src/layout/ItemCard/ItemCardGrid.tsx index b27d224418..885b43ca34 100644 --- a/packages/core-components/src/layout/ItemCard/ItemCardGrid.tsx +++ b/packages/core-components/src/layout/ItemCard/ItemCardGrid.tsx @@ -49,6 +49,7 @@ export type ItemCardGridProps = Partial> & { * A default grid to use when arranging "item cards" - cards that let users * select among several options. * + * @remarks * The immediate children are expected to be MUI Card components. * * Styles for the grid can be overridden using the `classes` prop, e.g.: diff --git a/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx b/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx index d9443ffc9a..76cf668079 100644 --- a/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx +++ b/packages/core-components/src/layout/ItemCard/ItemCardHeader.tsx @@ -64,6 +64,7 @@ export type ItemCardHeaderProps = Partial> & { * A simple card header, rendering a default look for "item cards" - cards that * are arranged in a grid for users to select among several options. * + * @remarks * This component expects to be placed within a MUI ``. * * Styles for the header can be overridden using the `classes` prop, e.g.: diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index 5a0b92f558..4bc972e272 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -510,6 +510,7 @@ const SidebarItemWithSubmenu = ({ /** * Creates a `SidebarItem` * + * @remarks * If children contain a `SidebarSubmenu` component the `SidebarItem` will have a expandable submenu */ export const SidebarItem = forwardRef((props, ref) => { @@ -663,6 +664,8 @@ export const SidebarScrollWrapper = styled('div')(({ theme }) => { /** * A button which allows you to expand the sidebar when clicked. + * + * @remarks * Use optionally to replace sidebar's expand-on-hover feature with expand-on-click. * * If you are using this you might want to set the `disableExpandOnHover` of the `Sidebar` to `true`. diff --git a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx index c85e6d11f2..bfa841690d 100644 --- a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx +++ b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx @@ -159,6 +159,7 @@ export const MobileSidebarContext = createContext({ /** * A navigation component for mobile screens, which sticks to the bottom. * + * @remarks * It alternates the normal sidebar by grouping the `SidebarItems` based on provided `SidebarGroup`s * either rendering them as a link or an overlay menu. * If no `SidebarGroup`s are provided the sidebar content is wrapped in an default overlay menu. diff --git a/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx b/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx index 1158cc14df..cb9c786ea9 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx @@ -115,6 +115,7 @@ const MobileSidebarGroup = (props: SidebarGroupProps) => { /** * Groups items of the `Sidebar` together. * + * @remarks * On bigger screens, this won't have any effect at the moment. * On small screens, it will add an action to the bottom navigation - either triggering an overlay menu or acting as a link * diff --git a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx index a1565a348f..51092d7e81 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarSubmenuItem.tsx @@ -111,6 +111,7 @@ export type SidebarSubmenuItemDropdownItem = { /** * Holds submenu item content. * + * @remarks * title: Text content of submenu item * subtitle: A subtitle displayed under the main title * to: Path to navigate to when item is clicked diff --git a/packages/core-components/src/layout/Sidebar/config.ts b/packages/core-components/src/layout/Sidebar/config.ts index bb0ff207fb..dacf4bd0a5 100644 --- a/packages/core-components/src/layout/Sidebar/config.ts +++ b/packages/core-components/src/layout/Sidebar/config.ts @@ -20,13 +20,13 @@ const drawerWidthClosed = 72; const iconPadding = 24; const userBadgePadding = 18; -/** @public **/ +/** @public */ export type SidebarOptions = { drawerWidthClosed?: number; drawerWidthOpen?: number; }; -/** @public **/ +/** @public */ export type SubmenuOptions = { drawerWidthClosed?: number; drawerWidthOpen?: number; diff --git a/packages/core-components/src/layout/SignInPage/types.ts b/packages/core-components/src/layout/SignInPage/types.ts index 0909e7242d..8a0430a27d 100644 --- a/packages/core-components/src/layout/SignInPage/types.ts +++ b/packages/core-components/src/layout/SignInPage/types.ts @@ -32,7 +32,7 @@ export type SignInProviderConfig = { apiRef: ApiRef; }; -/** @public **/ +/** @public */ export type IdentityProviders = ('guest' | 'custom' | SignInProviderConfig)[]; export type ProviderComponent = ComponentType< diff --git a/packages/core-components/src/layout/index.ts b/packages/core-components/src/layout/index.ts index 4b594f0e94..a024cfbbc4 100644 --- a/packages/core-components/src/layout/index.ts +++ b/packages/core-components/src/layout/index.ts @@ -23,7 +23,6 @@ export * from './Header'; export * from './HeaderActionMenu'; export * from './HeaderLabel'; export * from './HeaderTabs'; -export * from './HomepageTimer'; export * from './InfoCard'; export * from './ItemCard'; export * from './Page'; diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 9671062a50..8b167be430 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/core-plugin-api +## 1.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.0.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.1 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + ## 1.0.6 ### Patch Changes diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 71372e26f1..1d8e21344c 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-plugin-api", "description": "Core API used by Backstage plugins", - "version": "1.0.6", + "version": "1.0.7", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/core-plugin-api/src/routing/SubRouteRef.ts b/packages/core-plugin-api/src/routing/SubRouteRef.ts index 61d89fcbdb..14a614c2ba 100644 --- a/packages/core-plugin-api/src/routing/SubRouteRef.ts +++ b/packages/core-plugin-api/src/routing/SubRouteRef.ts @@ -72,7 +72,7 @@ export type ParamNames = export type PathParams = { [name in ParamNames]: string }; /** - * Merges a param object type with with an optional params type into a params object. + * Merges a param object type with an optional params type into a params object. * @public */ export type MergeParams< diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index c3b9164858..52ed5caee8 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,82 @@ # @backstage/create-app +## 0.4.32 + +### Patch Changes + +- 58c2264325: Newly created Backstage repositories now use the stable version 6 of + `react-router`, just like the main repo does. Please let us know if you find any + issues with this. + + Migrating to the stable version of `react-router` is optional for the time + being. But if you want to do the same for your existing repository, please + follow [this + guide](https://backstage.io/docs/tutorials/react-router-stable-migration). + +- e05e0f021b: Update versions of packages used in the create-app template, to match those in the main repo +- 01dff06be4: Leverage cache mounts in Dockerfile during `yarn install ...` and `apt-get ...` commands to speed up repeated builds. +- 90616bcaa6: Add the new search pagination component to the search page template. +- 7c6306fc8a: Initializes a git repository when creating an app using @packages/create-app +- 52f25858a8: Added `*.session.sql` Visual Studio Code database functionality files to `.gitignore` in the default template. This is optional but potentially helpful if your developers use Visual Studio Code; you can add a line with that exact value to your own root `.gitignore` if you want the same. +- 6d00e80146: Updated the root `test` scripts to use `backstage-cli repo test`. + + To apply this change to an existing app, make the following change to the root `package.json`: + + ```diff + - "test": "backstage-cli test", + - "test:all": "lerna run test -- --coverage", + + "test": "backstage-cli repo test", + + "test:all": "backstage-cli repo test --coverage", + ``` + +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## 0.4.32-next.2 + +### Patch Changes + +- 01dff06be4: Leverage cache mounts in Dockerfile during `yarn install ...` and `apt-get ...` commands to speed up repeated builds. +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## 0.4.32-next.1 + +### Patch Changes + +- 7c6306fc8a: Initializes a git repository when creating an app using @packages/create-app +- Updated dependencies + - @backstage/cli-common@0.1.10 + +## 0.4.32-next.0 + +### Patch Changes + +- 58c2264325: Newly created Backstage repositories now use the stable version 6 of + `react-router`, just like the main repo does. Please let us know if you find any + issues with this. + + Migrating to the stable version of `react-router` is optional for the time + being. But if you want to do the same for your existing repository, please + follow [this + guide](https://backstage.io/docs/tutorials/react-router-stable-migration). + +- e05e0f021b: Update versions of packages used in the create-app template, to match those in the main repo +- 52f25858a8: Added `*.session.sql` Visual Studio Code database functionality files to `.gitignore` in the default template. This is optional but potentially helpful if your developers use Visual Studio Code; you can add a line with that exact value to your own root `.gitignore` if you want the same. +- 6d00e80146: Updated the root `test` scripts to use `backstage-cli repo test`. + + To apply this change to an existing app, make the following change to the root `package.json`: + + ```diff + - "test": "backstage-cli test", + - "test:all": "lerna run test -- --coverage", + + "test": "backstage-cli repo test", + + "test:all": "backstage-cli repo test --coverage", + ``` + +- Updated dependencies + - @backstage/cli-common@0.1.10 + ## 0.4.31 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index bb65d3b4ca..a6a62fbbf0 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "A CLI that helps you create your own Backstage app", - "version": "0.4.31", + "version": "0.4.32", "publishConfig": { "access": "public" }, @@ -34,6 +34,7 @@ "dependencies": { "@backstage/cli-common": "workspace:^", "chalk": "^4.0.0", + "command-exists": "^1.2.9", "commander": "^9.1.0", "fs-extra": "10.1.0", "handlebars": "^4.7.3", @@ -43,6 +44,7 @@ }, "devDependencies": { "@backstage/cli": "workspace:^", + "@types/command-exists": "^1.2.0", "@types/fs-extra": "^9.0.1", "@types/inquirer": "^8.1.3", "@types/node": "^16.11.26", diff --git a/packages/create-app/src/createApp.test.ts b/packages/create-app/src/createApp.test.ts index 8e7b3bae7d..ec8d15ead1 100644 --- a/packages/create-app/src/createApp.test.ts +++ b/packages/create-app/src/createApp.test.ts @@ -32,6 +32,8 @@ const promptMock = jest.spyOn(inquirer, 'prompt'); const checkPathExistsMock = jest.spyOn(tasks, 'checkPathExistsTask'); const templatingMock = jest.spyOn(tasks, 'templatingTask'); const checkAppExistsMock = jest.spyOn(tasks, 'checkAppExistsTask'); +const initGitRepositoryMock = jest.spyOn(tasks, 'initGitRepository'); +const readGitConfig = jest.spyOn(tasks, 'readGitConfig'); const createTemporaryAppFolderMock = jest.spyOn( tasks, 'createTemporaryAppFolderTask', @@ -47,7 +49,7 @@ describe('command entrypoint', () => { }); }); - afterAll(() => { + afterEach(() => { mockFs.restore(); }); @@ -56,6 +58,11 @@ describe('command entrypoint', () => { name: 'MyApp', dbType: 'PostgreSQL', }); + readGitConfig.mockResolvedValue({ + name: 'git-user', + email: 'git-email', + defaultBranch: 'git-default-branch', + }); }); afterEach(() => { @@ -67,6 +74,7 @@ describe('command entrypoint', () => { await createApp(cmd); expect(checkAppExistsMock).toHaveBeenCalled(); expect(createTemporaryAppFolderMock).toHaveBeenCalled(); + expect(initGitRepositoryMock).toHaveBeenCalled(); expect(templatingMock).toHaveBeenCalled(); expect(moveAppMock).toHaveBeenCalled(); expect(buildAppMock).toHaveBeenCalled(); @@ -76,6 +84,7 @@ describe('command entrypoint', () => { const cmd = { path: 'myDirectory' } as unknown as Command; await createApp(cmd); expect(checkPathExistsMock).toHaveBeenCalled(); + expect(initGitRepositoryMock).toHaveBeenCalled(); expect(templatingMock).toHaveBeenCalled(); expect(buildAppMock).toHaveBeenCalled(); }); @@ -85,4 +94,11 @@ describe('command entrypoint', () => { await createApp(cmd); expect(buildAppMock).not.toHaveBeenCalled(); }); + + it('should not call `initGitRepository` when `gitConfig` is undefined', async () => { + const cmd = {} as unknown as Command; + readGitConfig.mockResolvedValue({}); + await createApp(cmd); + expect(initGitRepositoryMock).not.toHaveBeenCalled(); + }); }); diff --git a/packages/create-app/src/createApp.ts b/packages/create-app/src/createApp.ts index 7cf589e5f8..182e043565 100644 --- a/packages/create-app/src/createApp.ts +++ b/packages/create-app/src/createApp.ts @@ -28,8 +28,12 @@ import { createTemporaryAppFolderTask, moveAppTask, templatingTask, + initGitRepository, + readGitConfig, } from './lib/tasks'; +const DEFAULT_BRANCH = 'master'; + export default async (opts: OptionValues): Promise => { /* eslint-disable-next-line no-restricted-syntax */ const paths = findPaths(__dirname); @@ -73,6 +77,8 @@ export default async (opts: OptionValues): Promise => { Task.log('Creating the app...'); try { + const gitConfig = await readGitConfig(); + if (opts.path) { // Template directly to specified path @@ -80,7 +86,10 @@ export default async (opts: OptionValues): Promise => { await checkPathExistsTask(appDir); Task.section('Preparing files'); - await templatingTask(templateDir, opts.path, answers); + await templatingTask(templateDir, opts.path, { + ...answers, + defaultBranch: gitConfig?.defaultBranch ?? DEFAULT_BRANCH, + }); } else { // Template to temporary location, and then move files @@ -91,12 +100,20 @@ export default async (opts: OptionValues): Promise => { await createTemporaryAppFolderTask(tempDir); Task.section('Preparing files'); - await templatingTask(templateDir, tempDir, answers); + await templatingTask(templateDir, tempDir, { + ...answers, + defaultBranch: gitConfig?.defaultBranch ?? DEFAULT_BRANCH, + }); Task.section('Moving to final location'); await moveAppTask(tempDir, appDir, answers.name); } + if (gitConfig?.name && gitConfig?.email) { + Task.section('Initializing git repository'); + await initGitRepository(appDir); + } + if (!opts.skipInstall) { Task.section('Building the app'); await buildAppTask(appDir); diff --git a/packages/create-app/src/lib/tasks.test.ts b/packages/create-app/src/lib/tasks.test.ts index decc89fbd4..b918fc0180 100644 --- a/packages/create-app/src/lib/tasks.test.ts +++ b/packages/create-app/src/lib/tasks.test.ts @@ -17,7 +17,8 @@ import fs from 'fs-extra'; import mockFs from 'mock-fs'; import child_process from 'child_process'; -import path from 'path'; +import path, { resolve as resolvePath } from 'path'; +import os from 'os'; import { Task, buildAppTask, @@ -26,8 +27,12 @@ import { createTemporaryAppFolderTask, moveAppTask, templatingTask, + initGitRepository, + readGitConfig, } from './tasks'; +const commandExists = jest.fn(); + jest.spyOn(Task, 'log').mockReturnValue(undefined); jest.spyOn(Task, 'error').mockReturnValue(undefined); jest.spyOn(Task, 'section').mockReturnValue(undefined); @@ -36,6 +41,12 @@ jest .mockImplementation((_a, _b, taskFunc) => taskFunc()); jest.mock('child_process'); +jest.mock( + 'command-exists', + () => + (...args: any[]) => + commandExists(...args), +); // By mocking this the filesystem mocks won't mess with reading all of the package.jsons jest.mock('./versions', () => ({ @@ -87,6 +98,14 @@ jest.mock('./versions', () => ({ })); describe('tasks', () => { + const mockExec = child_process.exec as unknown as jest.MockedFunction< + ( + command: string, + options: any, + callback: (error: null, stdout: any, stderr: any) => void, + ) => void + >; + beforeEach(() => { mockFs({ 'projects/my-module.ts': '', @@ -100,6 +119,7 @@ describe('tasks', () => { }); afterEach(() => { + mockExec.mockRestore(); mockFs.restore(); }); @@ -174,12 +194,6 @@ describe('tasks', () => { describe('buildAppTask', () => { it('should change to `appDir` and run `yarn install` and `yarn tsc`', async () => { const mockChdir = jest.spyOn(process, 'chdir'); - const mockExec = child_process.exec as unknown as jest.MockedFunction< - ( - command: string, - callback: (error: null, stdout: string, stderr: string) => void, - ) => void - >; // requires callback implementation to support `promisify` wrapper // https://stackoverflow.com/a/60579617/10044859 @@ -273,4 +287,99 @@ describe('tasks', () => { ).toContain('sqlite3"'); }); }); + + describe('readGitConfig', () => { + const tmpDir = resolvePath(os.tmpdir(), 'git-temp-dir'); + + it('should return git config if git package is installed and git credentials are set', async () => { + mockExec.mockImplementation((_command, _options, callback) => { + callback(null, { stdout: 'main' }, 'standard error'); + }); + + commandExists.mockResolvedValue(true); + + const gitConfig = await readGitConfig(); + + expect(gitConfig).toBeTruthy(); + expect(gitConfig).toEqual({ + name: 'main', + email: 'main', + defaultBranch: 'main', + }); + expect(mockExec).toHaveBeenCalledWith( + 'git config user.name', + { cwd: tmpDir }, + expect.any(Function), + ); + expect(mockExec).toHaveBeenCalledWith( + 'git config user.email', + { cwd: tmpDir }, + expect.any(Function), + ); + expect(mockExec).toHaveBeenCalledWith( + 'git init', + { cwd: tmpDir }, + expect.any(Function), + ); + expect(mockExec).toHaveBeenCalledWith( + 'git commit --allow-empty -m "Initial commit"', + { cwd: tmpDir }, + expect.any(Function), + ); + }); + + it('should return false if git package is not installed', async () => { + commandExists.mockResolvedValue(false); + + const gitConfig = await readGitConfig(); + + expect(gitConfig).toEqual({}); + }); + + it('should return false if git package is installed but git credentials are not set', async () => { + mockExec.mockImplementation((_command, _options, callback) => { + callback(null, { stdout: null }, 'standard error'); + }); + + commandExists.mockResolvedValue(true); + + const gitConfig = await readGitConfig(); + + expect(gitConfig).toEqual({}); + expect(mockExec).toHaveBeenCalledWith( + 'git config user.name', + { cwd: tmpDir }, + expect.any(Function), + ); + expect(mockExec).toHaveBeenCalledWith( + 'git config user.email', + { cwd: tmpDir }, + expect.any(Function), + ); + }); + }); + + describe('initGitRepository', () => { + it('should initialize a git repository at the given path', async () => { + const destinationDir = 'tmp/mockApp/'; + + mockExec.mockImplementation((_command, callback) => { + callback(null, { stdout: 'main' }, 'standard error'); + }); + + await initGitRepository(destinationDir); + + expect(mockExec).toHaveBeenCalledTimes(2); + expect(mockExec).toHaveBeenNthCalledWith( + 1, + 'git init', + expect.any(Function), + ); + expect(mockExec).toHaveBeenNthCalledWith( + 2, + 'git commit --allow-empty -m "Initial commit"', + expect.any(Function), + ); + }); + }); }); diff --git a/packages/create-app/src/lib/tasks.ts b/packages/create-app/src/lib/tasks.ts index 1650432969..1e30f9d33a 100644 --- a/packages/create-app/src/lib/tasks.ts +++ b/packages/create-app/src/lib/tasks.ts @@ -28,10 +28,18 @@ import { import { exec as execCb } from 'child_process'; import { packageVersions } from './versions'; import { promisify } from 'util'; +import commandExists from 'command-exists'; +import os from 'os'; const TASK_NAME_MAX_LENGTH = 14; const exec = promisify(execCb); +export type GitConfig = { + name?: string; + email?: string; + defaultBranch?: string; +}; + export class Task { static log(name: string = '') { process.stdout.write(`${chalk.green(name)}\n`); @@ -236,3 +244,77 @@ export async function moveAppTask( }); }); } + +/** + * Read git configs by creating a temp folder and initializing a repo + * + * @throws if `exec` fails + */ +export async function readGitConfig(): Promise { + const tempDir = resolvePath(os.tmpdir(), 'git-temp-dir'); + + const runCmd = (cmd: string) => + exec(cmd, { cwd: tempDir }).catch(error => { + process.stdout.write(error.stderr); + process.stdout.write(error.stdout); + throw new Error(`Could not execute command ${chalk.cyan(cmd)}`); + }); + + const isGitAvailable = await commandExists('git').catch(() => false); + + if (!isGitAvailable) return {}; + + try { + await fs.mkdir(tempDir); + + const [gitUsername, gitEmail] = await Promise.all([ + runCmd('git config user.name'), + runCmd('git config user.email'), + ]); + + const gitCredentials = Boolean( + gitUsername.stdout?.trim() && gitEmail.stdout?.trim(), + ); + + if (!gitCredentials) return {}; + + await runCmd('git init'); + await runCmd('git commit --allow-empty -m "Initial commit"'); + + const gitDefaultBranch = await runCmd( + 'git branch --format="%(refname:short)"', + ); + + return { + name: gitUsername.stdout?.trim(), + email: gitEmail.stdout?.trim(), + defaultBranch: gitDefaultBranch.stdout?.trim(), + }; + } catch (error) { + throw new Error(`Failed to read git config, ${error}`); + } finally { + await fs.rm(tempDir, { recursive: true }); + } +} + +/** + * Initializes a git repository in the destination folder + * + * @param dir - source path to initialize git repository in + * @throws if `exec` fails + */ +export async function initGitRepository(dir: string) { + const runCmd = (cmd: string) => + exec(cmd).catch(error => { + process.stdout.write(error.stderr); + process.stdout.write(error.stdout); + throw new Error(`Could not execute command ${chalk.cyan(cmd)}`); + }); + + await Task.forItem('init', 'git repository', async () => { + process.chdir(dir); + + await runCmd('git init'); + await runCmd('git commit --allow-empty -m "Initial commit"'); + }); +} diff --git a/packages/create-app/templates/default-app/.gitignore.hbs b/packages/create-app/templates/default-app/.gitignore.hbs index fdc2a5dfb2..d452ac2932 100644 --- a/packages/create-app/templates/default-app/.gitignore.hbs +++ b/packages/create-app/templates/default-app/.gitignore.hbs @@ -46,3 +46,6 @@ site # Sensitive credentials *-credentials.yaml + +# vscode database functionality support files +*.session.sql diff --git a/packages/create-app/templates/default-app/package.json.hbs b/packages/create-app/templates/default-app/package.json.hbs index dd4d937db0..c5810d8880 100644 --- a/packages/create-app/templates/default-app/package.json.hbs +++ b/packages/create-app/templates/default-app/package.json.hbs @@ -16,7 +16,7 @@ "clean": "backstage-cli repo clean", "test": "backstage-cli repo test", "test:all": "backstage-cli repo test --coverage", - "lint": "backstage-cli repo lint --since origin/master", + "lint": "backstage-cli repo lint --since origin/{{defaultBranch}}", "lint:all": "backstage-cli repo lint", "prettier:check": "prettier --check .", "create-plugin": "backstage-cli create-plugin --scope internal", diff --git a/packages/create-app/templates/default-app/packages/app/src/App.tsx b/packages/create-app/templates/default-app/packages/app/src/App.tsx index 4d5ace5aa4..46cb786399 100644 --- a/packages/create-app/templates/default-app/packages/app/src/App.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/App.tsx @@ -58,7 +58,7 @@ const AppRouter = app.getRouter(); const routes = ( - } /> + } /> } /> { + {({ results }) => ( diff --git a/packages/create-app/templates/default-app/packages/backend/Dockerfile b/packages/create-app/templates/default-app/packages/backend/Dockerfile index 682798b826..f0be3ac1d3 100644 --- a/packages/create-app/templates/default-app/packages/backend/Dockerfile +++ b/packages/create-app/templates/default-app/packages/backend/Dockerfile @@ -13,13 +13,18 @@ FROM node:16-bullseye-slim # Install sqlite3 dependencies. You can skip this if you don't use sqlite3 in the image, # in which case you should also move better-sqlite3 to "devDependencies" in package.json. -RUN apt-get update && \ +RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ + --mount=type=cache,target=/var/lib/apt,sharing=locked \ + apt-get update && \ apt-get install -y --no-install-recommends libsqlite3-dev python3 build-essential && \ - rm -rf /var/lib/apt/lists/* && \ yarn config set python /usr/bin/python3 # From here on we use the least-privileged `node` user to run the backend. USER node + +# This should create the app dir as `node`. +# If it is instead created as `root` then the `tar` command below will fail: `can't create directory 'packages/': Permission denied`. +# If this occurs, then ensure BuildKit is enabled (`DOCKER_BUILDKIT=1`) so the app dir is correctly created as `node`. WORKDIR /app # This switches many Node.js dependencies to production mode. @@ -31,7 +36,8 @@ ENV NODE_ENV production COPY --chown=node:node yarn.lock package.json packages/backend/dist/skeleton.tar.gz ./ RUN tar xzf skeleton.tar.gz && rm skeleton.tar.gz -RUN yarn install --frozen-lockfile --production --network-timeout 300000 && rm -rf "$(yarn cache dir)" +RUN --mount=type=cache,target=/home/node/.cache/yarn,sharing=locked,uid=1000,gid=1000 \ + yarn install --frozen-lockfile --production --network-timeout 300000 # Then copy the rest of the backend bundle, along with any other files we might want. COPY --chown=node:node packages/backend/dist/bundle.tar.gz app-config*.yaml ./ diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 0f56fbb388..9513c71468 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,65 @@ # @backstage/dev-utils +## 1.0.7 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/test-utils@1.2.1 + - @backstage/app-defaults@1.0.7 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 1.0.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + +## 1.0.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/app-defaults@1.0.7-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/test-utils@1.2.1-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + +## 1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/test-utils@1.2.1-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/app-defaults@1.0.7-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 1.0.6 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 4a43a0b8b3..225f4e9a78 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "1.0.6", + "version": "1.0.7", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/packages/errors/CHANGELOG.md b/packages/errors/CHANGELOG.md index aad6d660c3..23978aeab8 100644 --- a/packages/errors/CHANGELOG.md +++ b/packages/errors/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/errors +## 1.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 1.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 1.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + ## 1.1.1 ### Patch Changes diff --git a/packages/errors/package.json b/packages/errors/package.json index eb3627f1ba..9abfeaaaaa 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/errors", "description": "Common utilities for error handling within Backstage", - "version": "1.1.1", + "version": "1.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index 29376adbab..07d8fa516f 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/integration-react +## 1.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## 1.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## 1.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/theme@0.2.16 + +## 1.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + ## 1.1.4 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index 3f23625aac..398ca58f36 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration-react", "description": "Frontend package for managing integrations towards external systems", - "version": "1.1.4", + "version": "1.1.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index 3aaeeb4d48..244516e70e 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/integration +## 1.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 1.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 1.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## 1.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + ## 1.3.1 ### Patch Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index 6c2e070467..add2b07a93 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/integration", "description": "Helpers for managing integrations towards external systems", - "version": "1.3.1", + "version": "1.3.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 982eb6c0de..ffabaf3ffb 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,81 @@ # techdocs-cli-embedded-app +## 0.2.75 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/cli@0.20.0 + - @backstage/test-utils@1.2.1 + - @backstage/plugin-techdocs@1.3.3 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/app-defaults@1.0.7 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## 0.2.75-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/app-defaults@1.0.7-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## 0.2.75-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.1 + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/cli@0.20.0-next.1 + - @backstage/plugin-techdocs@1.3.3-next.1 + - @backstage/app-defaults@1.0.7-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/test-utils@1.2.1-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.1 + +## 0.2.75-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/cli@0.20.0-next.0 + - @backstage/test-utils@1.2.1-next.0 + - @backstage/plugin-catalog@1.5.2-next.0 + - @backstage/plugin-techdocs@1.3.3-next.0 + - @backstage/plugin-techdocs-react@1.0.5-next.0 + - @backstage/app-defaults@1.0.7-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.2.74 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index 060735636b..2bcf099d80 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,6 +1,6 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.74", + "version": "0.2.75", "private": true, "backstage": { "role": "frontend" diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index 377e44625b..c5f85ab548 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,69 @@ # @techdocs/cli +## 1.2.2 + +### Patch Changes + +- 0b2a30dead: fixing techdocs-cli Docker client creation + + Docker client does not need to be created when --no-docker + option is provided. + + If you had DOCKER_CERT_PATH environment variable defined + the Docker client was looking for certificates + and breaking techdocs-cli generate command even with --no-docker + option. + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-techdocs-node@1.4.1 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3 + +## 1.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-techdocs-node@1.4.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.2 + +## 1.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.1 + - @backstage/plugin-techdocs-node@1.4.1-next.1 + +## 1.2.2-next.0 + +### Patch Changes + +- 0b2a30dead: fixing techdocs-cli Docker client creation + + Docker client does not need to be created when --no-docker + option is provided. + + If you had DOCKER_CERT_PATH environment variable defined + the Docker client was looking for certificates + and breaking techdocs-cli generate command even with --no-docker + option. + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-techdocs-node@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/cli-common@0.1.10 + - @backstage/config@1.0.3-next.0 + ## 1.2.1 ### Patch Changes diff --git a/packages/techdocs-cli/e2e-tests/techdocs-cli.test.ts b/packages/techdocs-cli/e2e-tests/techdocs-cli.test.ts index e19a8cea2f..15d420af2c 100644 --- a/packages/techdocs-cli/e2e-tests/techdocs-cli.test.ts +++ b/packages/techdocs-cli/e2e-tests/techdocs-cli.test.ts @@ -89,6 +89,23 @@ describe('end-to-end', () => { expect(proc.exit).toEqual(0); }); + it('can generate with DOCKER_* TLS variables and --no-docker option', async () => { + const env = { + DOCKER_HOST: 'tcp://localhost:2376', + DOCKER_TLS_CERTDIR: '/certs', + DOCKER_TLS_VERIFY: '1', + DOCKER_CERT_PATH: '/certs/client', + ...process.env, + }; + const proc = await executeCommand(entryPoint, ['generate', '--no-docker'], { + cwd, + timeout, + env, + }); + expect(proc.stdout).toContain('Successfully generated docs'); + expect(proc.exit).toEqual(0); + }); + it('can serve in mkdocs', async () => { const proc = await executeCommand( entryPoint, diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 2e74c28c62..1d721109bf 100644 --- a/packages/techdocs-cli/package.json +++ b/packages/techdocs-cli/package.json @@ -1,7 +1,7 @@ { "name": "@techdocs/cli", "description": "Utility CLI for managing TechDocs sites in Backstage.", - "version": "1.2.1", + "version": "1.2.2", "publishConfig": { "access": "public" }, diff --git a/packages/techdocs-cli/src/commands/generate/generate.ts b/packages/techdocs-cli/src/commands/generate/generate.ts index 1e4ed220d9..874b904d86 100644 --- a/packages/techdocs-cli/src/commands/generate/generate.ts +++ b/packages/techdocs-cli/src/commands/generate/generate.ts @@ -22,7 +22,10 @@ import { TechdocsGenerator, ParsedLocationAnnotation, } from '@backstage/plugin-techdocs-node'; -import { DockerContainerRunner } from '@backstage/backend-common'; +import { + ContainerRunner, + DockerContainerRunner, +} from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; import { convertTechDocsRefToLocationAnnotation, @@ -66,8 +69,12 @@ export default async function generate(opts: OptionValues) { }); // Docker client (conditionally) used by the generators, based on techdocs.generators config. - const dockerClient = new Docker(); - const containerRunner = new DockerContainerRunner({ dockerClient }); + let containerRunner: ContainerRunner | undefined; + + if (opts.docker) { + const dockerClient = new Docker(); + containerRunner = new DockerContainerRunner({ dockerClient }); + } let parsedLocationAnnotation = {} as ParsedLocationAnnotation; if (opts.techdocsRef) { diff --git a/packages/techdocs-cli/src/example-docs/docs/index.md b/packages/techdocs-cli/src/example-docs/docs/index.md index 074d3ccb3e..66e4c96b2f 100644 --- a/packages/techdocs-cli/src/example-docs/docs/index.md +++ b/packages/techdocs-cli/src/example-docs/docs/index.md @@ -73,20 +73,20 @@ const addThingToThing = (a, b) a + b; # MDX truly sane lists -- attributes +- `attributes` -- customer - - first_name - - test - - family_name - - email -- person - - first_name - - family_name - - birth_date -- subscription_id +- `customer` + - `first_name` + - `test` + - `family_name` + - `email` +- `person` + - `first_name` + - `family_name` + - `birth_date` +- `subscription_id` -- request +- `request` *[MOCDOC]: Mock Documentation diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 6258adff78..c891069ab5 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,59 @@ # @backstage/test-utils +## 1.2.1 + +### Patch Changes + +- e05e0f021b: Align on the version of `@material-ui/icons` used, to `^4.9.1` like other packages in the main repo +- Updated dependencies + - @backstage/core-app-api@1.1.1 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-react@0.4.6 + +## 1.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## 1.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-react@0.4.6-next.1 + +## 1.2.1-next.0 + +### Patch Changes + +- e05e0f021b: Align on the version of `@material-ui/icons` used, to `^4.9.1` like other packages in the main repo +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-permission-react@0.4.6-next.0 + ## 1.2.0 ### Minor Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 90f966ad97..cf0bf2b225 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "1.2.0", + "version": "1.2.1", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/plugins/adr-backend/CHANGELOG.md b/plugins/adr-backend/CHANGELOG.md index f32be42f18..586caf36a1 100644 --- a/plugins/adr-backend/CHANGELOG.md +++ b/plugins/adr-backend/CHANGELOG.md @@ -1,5 +1,62 @@ # @backstage/plugin-adr-backend +## 0.2.2 + +### Patch Changes + +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-adr-common@0.2.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/plugin-adr-common@0.2.2-next.2 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/plugin-adr-common@0.2.2-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-adr-common@0.2.2-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 0.2.1 ### Patch Changes diff --git a/plugins/adr-backend/package.json b/plugins/adr-backend/package.json index d8295c0e46..30b623ace0 100644 --- a/plugins/adr-backend/package.json +++ b/plugins/adr-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-adr-backend", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/adr-backend/src/search/DefaultAdrCollatorFactory.ts b/plugins/adr-backend/src/search/DefaultAdrCollatorFactory.ts index 21f0f1945f..78f6e0226e 100644 --- a/plugins/adr-backend/src/search/DefaultAdrCollatorFactory.ts +++ b/plugins/adr-backend/src/search/DefaultAdrCollatorFactory.ts @@ -113,7 +113,7 @@ export class DefaultAdrCollatorFactory implements DocumentCollatorFactory { this.catalogClient = options.catalogClient ?? new CatalogClient({ discoveryApi: options.discovery }); - this.logger = options.logger; + this.logger = options.logger.child({ documentType: this.type }); this.parser = options.parser ?? createMadrParser(); this.reader = options.reader; this.scmIntegrations = ScmIntegrations.fromConfig(options.config); diff --git a/plugins/adr-common/CHANGELOG.md b/plugins/adr-common/CHANGELOG.md index 9a68db3d74..be906e2ddc 100644 --- a/plugins/adr-common/CHANGELOG.md +++ b/plugins/adr-common/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-adr-common +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/integration@1.3.2 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 0.2.1 ### Patch Changes diff --git a/plugins/adr-common/package.json b/plugins/adr-common/package.json index 02ae1d642d..e62be75b1c 100644 --- a/plugins/adr-common/package.json +++ b/plugins/adr-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-adr-common", "description": "Common functionalities for the adr plugin", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/adr/CHANGELOG.md b/plugins/adr/CHANGELOG.md index 2fe9aacb31..40a627a340 100644 --- a/plugins/adr/CHANGELOG.md +++ b/plugins/adr/CHANGELOG.md @@ -1,5 +1,65 @@ # @backstage/plugin-adr +## 0.2.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-adr-common@0.2.2 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.2.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-adr-common@0.2.2-next.2 + - @backstage/plugin-search-react@1.2.0-next.2 + +## 0.2.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-adr-common@0.2.2-next.1 + +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-adr-common@0.2.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 0.2.1 ### Patch Changes diff --git a/plugins/adr/package.json b/plugins/adr/package.json index f64d901820..8e4835eb31 100644 --- a/plugins/adr/package.json +++ b/plugins/adr/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-adr", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/airbrake-backend/CHANGELOG.md b/plugins/airbrake-backend/CHANGELOG.md index 2e249ba14a..0df7e1669c 100644 --- a/plugins/airbrake-backend/CHANGELOG.md +++ b/plugins/airbrake-backend/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-airbrake-backend +## 0.2.10 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## 0.2.10-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## 0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + ## 0.2.9 ### Patch Changes diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json index b79cdd80b1..d35d005137 100644 --- a/plugins/airbrake-backend/package.json +++ b/plugins/airbrake-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake-backend", - "version": "0.2.9", + "version": "0.2.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/airbrake-backend/src/service/router.ts b/plugins/airbrake-backend/src/service/router.ts index b870eb5c13..5dc0b44a9b 100644 --- a/plugins/airbrake-backend/src/service/router.ts +++ b/plugins/airbrake-backend/src/service/router.ts @@ -79,7 +79,7 @@ export async function createRouter( router.get('/health', (_, response) => { logger.info('PONG!'); - response.send({ status: 'ok' }); + response.json({ status: 'ok' }); }); router.use( diff --git a/plugins/airbrake/CHANGELOG.md b/plugins/airbrake/CHANGELOG.md index 2d03fb6ef2..68bc61ff92 100644 --- a/plugins/airbrake/CHANGELOG.md +++ b/plugins/airbrake/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-airbrake +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/test-utils@1.2.1 + - @backstage/dev-utils@1.0.7 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/dev-utils@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/dev-utils@1.0.7-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/test-utils@1.2.1-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/test-utils@1.2.1-next.0 + - @backstage/dev-utils@1.0.7-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.3.9 ### Patch Changes diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index 5dc54ea863..dd65baa46d 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake", - "version": "0.3.9", + "version": "0.3.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/allure/CHANGELOG.md b/plugins/allure/CHANGELOG.md index 60dcd97b14..5b6330f11e 100644 --- a/plugins/allure/CHANGELOG.md +++ b/plugins/allure/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-allure +## 0.1.26 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.1.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.1.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.1.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.1.25 ### Patch Changes diff --git a/plugins/allure/package.json b/plugins/allure/package.json index 3bfe92c6ee..ae0f87fb1e 100644 --- a/plugins/allure/package.json +++ b/plugins/allure/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-allure", "description": "A Backstage plugin that integrates with Allure", - "version": "0.1.25", + "version": "0.1.26", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/analytics-module-ga/CHANGELOG.md b/plugins/analytics-module-ga/CHANGELOG.md index 622257c75a..1d16fce74d 100644 --- a/plugins/analytics-module-ga/CHANGELOG.md +++ b/plugins/analytics-module-ga/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-analytics-module-ga +## 0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## 0.1.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.1.20 ### Patch Changes diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index 7e4ce38c20..dc3dd253f4 100644 --- a/plugins/analytics-module-ga/package.json +++ b/plugins/analytics-module-ga/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-analytics-module-ga", - "version": "0.1.20", + "version": "0.1.21", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/apache-airflow/CHANGELOG.md b/plugins/apache-airflow/CHANGELOG.md index 66b3d3120b..aa40bfc9b2 100644 --- a/plugins/apache-airflow/CHANGELOG.md +++ b/plugins/apache-airflow/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-apache-airflow +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json index 5555fd35fc..b66067cb6e 100644 --- a/plugins/apache-airflow/package.json +++ b/plugins/apache-airflow/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-apache-airflow", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 339ad58f59..630587a60e 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-api-docs +## 0.8.10 + +### Patch Changes + +- 50c6e14aee: Updated dependency `@asyncapi/react-component` to `1.0.0-next.43`. +- 3d5bb521ee: Updated dependency `@asyncapi/react-component` to `1.0.0-next.42`. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.8.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.8.10-next.1 + +### Patch Changes + +- 50c6e14aee: Updated dependency `@asyncapi/react-component` to `1.0.0-next.43`. +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-catalog@1.6.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.8.10-next.0 + +### Patch Changes + +- 3d5bb521ee: Updated dependency `@asyncapi/react-component` to `1.0.0-next.42`. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.8.9 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 42cd8d5d74..1a5c8ae07c 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-api-docs", "description": "A Backstage plugin that helps represent API entities in the frontend", - "version": "0.8.9", + "version": "0.8.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,7 +32,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@asyncapi/react-component": "1.0.0-next.42", + "@asyncapi/react-component": "1.0.0-next.43", "@backstage/catalog-model": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", diff --git a/plugins/apollo-explorer/CHANGELOG.md b/plugins/apollo-explorer/CHANGELOG.md index f4dfdc2d35..3a56b728fe 100644 --- a/plugins/apollo-explorer/CHANGELOG.md +++ b/plugins/apollo-explorer/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-apollo-explorer +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.1.2 ### Patch Changes diff --git a/plugins/apollo-explorer/package.json b/plugins/apollo-explorer/package.json index 770af99853..3e2f958fe7 100644 --- a/plugins/apollo-explorer/package.json +++ b/plugins/apollo-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-apollo-explorer", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index a6cf9ef532..ba4a4b9ea4 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,51 @@ # @backstage/plugin-app-backend +## 0.3.37 + +### Patch Changes + +- 11c9e0ad33: Added alpha plugin implementation for the new backend system. Available at `@backstage/plugin-app-backend/alpha`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/config@1.0.3 + - @backstage/config-loader@1.1.5 + - @backstage/types@1.0.0 + +## 0.3.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/config-loader@1.1.5-next.2 + - @backstage/types@1.0.0 + +## 0.3.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/config-loader@1.1.5-next.1 + - @backstage/types@1.0.0 + +## 0.3.37-next.0 + +### Patch Changes + +- 11c9e0ad33: Added alpha plugin implementation for the new backend system. Available at `@backstage/plugin-app-backend/alpha`. +- Updated dependencies + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/config-loader@1.1.5-next.0 + - @backstage/types@1.0.0 + ## 0.3.36 ### Patch Changes diff --git a/plugins/app-backend/api-report.md b/plugins/app-backend/api-report.md index 0383129823..b52132ef51 100644 --- a/plugins/app-backend/api-report.md +++ b/plugins/app-backend/api-report.md @@ -3,11 +3,23 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import express from 'express'; import { Logger } from 'winston'; import { PluginDatabaseManager } from '@backstage/backend-common'; +// @alpha +export const appPlugin: (options: AppPluginOptions) => BackendFeature; + +// @alpha (undocumented) +export type AppPluginOptions = { + appPackageName?: string; + staticFallbackHandler?: express.Handler; + disableConfigInjection?: boolean; + disableStaticFallbackCache?: boolean; +}; + // @public (undocumented) export function createRouter(options: RouterOptions): Promise; diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index ffd40b89c0..d3b14e6afd 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,14 +1,15 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.36", + "version": "0.3.37", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { "access": "public", "main": "dist/index.cjs.js", - "types": "dist/index.d.ts" + "types": "dist/index.d.ts", + "alphaTypes": "dist/index.alpha.d.ts" }, "backstage": { "role": "backend-plugin" @@ -24,7 +25,7 @@ ], "scripts": { "start": "backstage-cli package start", - "build": "backstage-cli package build", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", @@ -33,6 +34,7 @@ }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/config": "workspace:^", "@backstage/config-loader": "workspace:^", "@backstage/types": "workspace:^", @@ -49,16 +51,20 @@ "yn": "^4.0.0" }, "devDependencies": { + "@backstage/backend-app-api": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/types": "workspace:^", "@types/supertest": "^2.0.8", + "get-port": "^6.1.2", "mock-fs": "^5.1.0", "msw": "^0.47.0", + "node-fetch": "^2.6.7", "supertest": "^6.1.3" }, "files": [ "dist", + "alpha", "migrations/**/*.{js,d.ts}", "static" ] diff --git a/plugins/app-backend/src/index.ts b/plugins/app-backend/src/index.ts index c91416eac1..167dc041fc 100644 --- a/plugins/app-backend/src/index.ts +++ b/plugins/app-backend/src/index.ts @@ -21,3 +21,5 @@ */ export * from './service/router'; +export { appPlugin } from './service/appPlugin'; +export type { AppPluginOptions } from './service/appPlugin'; diff --git a/plugins/app-backend/src/service/appPlugin.test.ts b/plugins/app-backend/src/service/appPlugin.test.ts new file mode 100644 index 0000000000..f4a2b142e8 --- /dev/null +++ b/plugins/app-backend/src/service/appPlugin.test.ts @@ -0,0 +1,84 @@ +/* + * 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 { resolve as resolvePath } from 'path'; +import fetch from 'node-fetch'; +import { configServiceRef } from '@backstage/backend-plugin-api'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { appPlugin } from './appPlugin'; +import { + databaseFactory, + httpRouterFactory, + loggerFactory, + rootLoggerFactory, +} from '@backstage/backend-app-api'; +import { ConfigReader } from '@backstage/config'; +import getPort from 'get-port'; + +describe('appPlugin', () => { + beforeAll(() => { + mockFs({ + [resolvePath(process.cwd(), 'node_modules/app')]: { + 'package.json': '{}', + dist: { + static: {}, + 'index.html': 'winning', + }, + }, + }); + }); + + afterAll(() => { + mockFs.restore(); + }); + + it('boots', async () => { + const port = await getPort(); + await startTestBackend({ + services: [ + [ + configServiceRef, + new ConfigReader({ + backend: { + listen: { port }, + database: { client: 'better-sqlite3', connection: ':memory:' }, + }, + }), + ], + loggerFactory(), + rootLoggerFactory(), + databaseFactory(), + httpRouterFactory(), + ], + features: [ + appPlugin({ + appPackageName: 'app', + disableStaticFallbackCache: true, + }), + ], + }); + + await expect( + fetch(`http://localhost:${port}/api/app/derp.html`).then(res => + res.text(), + ), + ).resolves.toBe('winning'); + await expect( + fetch(`http://localhost:${port}`).then(res => res.text()), + ).resolves.toBe('winning'); + }); +}); diff --git a/plugins/app-backend/src/service/appPlugin.ts b/plugins/app-backend/src/service/appPlugin.ts new file mode 100644 index 0000000000..f88bcb14f1 --- /dev/null +++ b/plugins/app-backend/src/service/appPlugin.ts @@ -0,0 +1,107 @@ +/* + * 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 express from 'express'; +import { + configServiceRef, + createBackendPlugin, + databaseServiceRef, + loggerServiceRef, + loggerToWinstonLogger, + httpRouterServiceRef, +} from '@backstage/backend-plugin-api'; +import { createRouter } from './router'; + +/** @alpha */ +export type AppPluginOptions = { + /** + * The name of the app package (in most Backstage repositories, this is the + * "name" field in `packages/app/package.json`) that content should be served + * from. The same app package should be added as a dependency to the backend + * package in order for it to be accessible at runtime. + * + * In a typical setup with a single app package, this will default to 'app'. + */ + appPackageName?: string; + + /** + * A request handler to handle requests for static content that are not present in the app bundle. + * + * This can be used to avoid issues with clients on older deployment versions trying to access lazy + * loaded content that is no longer present. Typically the requests would fall back to a long-term + * object store where all recently deployed versions of the app are present. + * + * Another option is to provide a `database` that will take care of storing the static assets instead. + * + * If both `database` and `staticFallbackHandler` are provided, the `database` will attempt to serve + * static assets first, and if they are not found, the `staticFallbackHandler` will be called. + */ + staticFallbackHandler?: express.Handler; + + /** + * Disables the configuration injection. This can be useful if you're running in an environment + * with a read-only filesystem, or for some other reason don't want configuration to be injected. + * + * Note that this will cause the configuration used when building the app bundle to be used, unless + * a separate configuration loading strategy is set up. + * + * This also disables configuration injection though `APP_CONFIG_` environment variables. + */ + disableConfigInjection?: boolean; + + /** + * By default the app backend plugin will cache previously deployed static assets in the database. + * If you disable this, it is recommended to set a `staticFallbackHandler` instead. + */ + disableStaticFallbackCache?: boolean; +}; + +/** + * The App plugin is responsible for serving the frontend app bundle and static assets. + * @alpha + */ +export const appPlugin = createBackendPlugin({ + id: 'app', + register(env, options: AppPluginOptions) { + env.registerInit({ + deps: { + logger: loggerServiceRef, + config: configServiceRef, + database: databaseServiceRef, + httpRouter: httpRouterServiceRef, + }, + async init({ logger, config, database, httpRouter }) { + const { + appPackageName, + staticFallbackHandler, + disableConfigInjection, + disableStaticFallbackCache, + } = options; + const winstonLogger = loggerToWinstonLogger(logger); + + const router = await createRouter({ + logger: winstonLogger, + config, + database: disableStaticFallbackCache ? undefined : database, + appPackageName: appPackageName ?? 'app', + staticFallbackHandler, + disableConfigInjection, + }); + httpRouter.use(router); + }, + }); + }, +}); diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 189a4a235f..11c6cedb8e 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,84 @@ # @backstage/plugin-auth-backend +## 0.17.0 + +### Minor Changes + +- e2dc42e9f0: Google OAuth refresh tokens will now be revoked on logout by calling Google's API +- 5fa831ce55: CookieConfigurer can optionally return the `SameSite` cookie attribute. + CookieConfigurer now requires an additional argument `appOrigin` - the origin URL of the app - which is used to calculate the `SameSite` attribute. + defaultCookieConfigurer returns the `SameSite` attribute which defaults to `Lax`. In cases where an auth-backend is running on a different domain than the App, `SameSite=None` is used - but only for secure contexts. This is so that cookies can be included in third-party requests. + + OAuthAdapterOptions has been modified to require additional arguments, `baseUrl`, and `cookieConfigurer`. + OAuthAdapter now resolves cookie configuration using its supplied CookieConfigurer for each request to make sure that the proper attributes always are set. + +### Patch Changes + +- b5c126010c: Auth0 provider now supports optional `connection` and `connectionScope` parameters to configure social identity providers. +- 8c6ec175bf: Fix GitLab provider setup so that it supports GitLab installations with a path in the URL. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 0.17.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## 0.17.0-next.1 + +### Minor Changes + +- e2dc42e9f0: Google OAuth refresh tokens will now be revoked on logout by calling Google's API + +### Patch Changes + +- b5c126010c: Auth0 provider now supports optional `connection` and `connectionScope` parameters to configure social identity providers. +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-auth-node@0.2.6-next.1 + +## 0.17.0-next.0 + +### Minor Changes + +- 5fa831ce55: CookieConfigurer can optionally return the `SameSite` cookie attribute. + CookieConfigurer now requires an additional argument `appOrigin` - the origin URL of the app - which is used to calculate the `SameSite` attribute. + defaultCookieConfigurer returns the `SameSite` attribute which defaults to `Lax`. In cases where an auth-backend is running on a different domain than the App, `SameSite=None` is used - but only for secure contexts. This is so that cookies can be included in third-party requests. + + OAuthAdapterOptions has been modified to require additional arguments, `baseUrl`, and `cookieConfigurer`. + OAuthAdapter now resolves cookie configuration using its supplied CookieConfigurer for each request to make sure that the proper attributes always are set. + +### Patch Changes + +- 8c6ec175bf: Fix GitLab provider setup so that it supports GitLab installations with a path in the URL. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + ## 0.16.0 ### Minor Changes diff --git a/plugins/auth-backend/api-report.md b/plugins/auth-backend/api-report.md index ea5b923baa..cc6962cdbe 100644 --- a/plugins/auth-backend/api-report.md +++ b/plugins/auth-backend/api-report.md @@ -314,7 +314,7 @@ export interface OAuthHandlers { response: OAuthResponse; refreshToken?: string; }>; - logout?(): Promise; + logout?(req: OAuthLogoutRequest): Promise; refresh?(req: OAuthRefreshRequest): Promise<{ response: OAuthResponse; refreshToken?: string; @@ -322,6 +322,11 @@ export interface OAuthHandlers { start(req: OAuthStartRequest): Promise; } +// @public (undocumented) +export type OAuthLogoutRequest = express.Request<{}> & { + refreshToken: string; +}; + // @public (undocumented) export type OAuthProviderInfo = { accessToken: string; diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index d3c4fc2724..23482f8eb7 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-auth-backend", "description": "A Backstage backend plugin that handles authentication", - "version": "0.16.0", + "version": "0.17.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts index 526b05d0ab..e0d4f0e051 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.test.ts @@ -17,7 +17,7 @@ import express from 'express'; import { THOUSAND_DAYS_MS, TEN_MINUTES_MS, OAuthAdapter } from './OAuthAdapter'; import { encodeState } from './helpers'; -import { OAuthHandlers, OAuthState } from './types'; +import { OAuthHandlers, OAuthLogoutRequest, OAuthState } from './types'; import { CookieConfigurer } from '../../providers/types'; const mockResponseData = { @@ -60,6 +60,7 @@ describe('OAuthAdapter', () => { refreshToken: 'token', }; } + async logout(_: OAuthLogoutRequest) {} } const providerInstance = new MyAuthProvider(); const mockCookieConfig: ReturnType = { @@ -262,13 +263,17 @@ describe('OAuthAdapter', () => { ); }); - it('removes refresh cookie when logging out', async () => { + it('removes refresh cookie and calls logout handler when logging out', async () => { + const logoutSpy = jest.spyOn(providerInstance, 'logout'); const oauthProvider = new OAuthAdapter(providerInstance, { ...oAuthProviderOptions, isOriginAllowed: () => false, }); const mockRequest = { + cookies: { + 'test-provider-refresh-token': 'token', + }, header: () => 'XMLHttpRequest', get: jest.fn(), } as unknown as express.Request; @@ -281,6 +286,7 @@ describe('OAuthAdapter', () => { await oauthProvider.logout(mockRequest, mockResponse); expect(mockRequest.get).toHaveBeenCalledTimes(1); + expect(logoutSpy).toHaveBeenCalledTimes(1); expect(mockResponse.cookie).toHaveBeenCalledTimes(1); expect(mockResponse.cookie).toHaveBeenCalledWith( expect.stringContaining('test-provider-refresh-token'), diff --git a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts index 8f08cfbbd0..85104e9608 100644 --- a/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts +++ b/plugins/auth-backend/src/lib/oauth/OAuthAdapter.ts @@ -39,6 +39,7 @@ import { OAuthStartRequest, OAuthRefreshRequest, OAuthState, + OAuthLogoutRequest, } from './types'; import { prepareBackstageIdentityResponse } from '../../providers/prepareBackstageIdentityResponse'; @@ -190,6 +191,14 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { throw new AuthenticationError('Invalid X-Requested-With header'); } + if (this.handlers.logout) { + const refreshToken = this.getRefreshTokenFromCookie(req); + const revokeRequest: OAuthLogoutRequest = Object.assign(req, { + refreshToken, + }); + await this.handlers.logout(revokeRequest); + } + // remove refresh token cookie if it is set const origin = req.get('origin'); const cookieConfig = this.getCookieConfig(origin); @@ -210,8 +219,7 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { } try { - const refreshToken = - req.cookies[`${this.options.providerId}-refresh-token`]; + const refreshToken = this.getRefreshTokenFromCookie(req); // throw error if refresh token is missing in the request if (!refreshToken) { @@ -286,6 +294,10 @@ export class OAuthAdapter implements AuthProviderRouteHandlers { }); }; + private getRefreshTokenFromCookie = (req: express.Request) => { + return req.cookies[`${this.options.providerId}-refresh-token`]; + }; + private getGrantedScopeFromCookie = (req: express.Request) => { return req.cookies[`${this.options.providerId}-granted-scope`]; }; diff --git a/plugins/auth-backend/src/lib/oauth/index.ts b/plugins/auth-backend/src/lib/oauth/index.ts index 671e8e48e9..3643898bed 100644 --- a/plugins/auth-backend/src/lib/oauth/index.ts +++ b/plugins/auth-backend/src/lib/oauth/index.ts @@ -26,5 +26,6 @@ export type { OAuthState, OAuthStartRequest, OAuthRefreshRequest, + OAuthLogoutRequest, OAuthResult, } from './types'; diff --git a/plugins/auth-backend/src/lib/oauth/types.ts b/plugins/auth-backend/src/lib/oauth/types.ts index e8b5282aad..3902c3f026 100644 --- a/plugins/auth-backend/src/lib/oauth/types.ts +++ b/plugins/auth-backend/src/lib/oauth/types.ts @@ -104,6 +104,11 @@ export type OAuthRefreshRequest = express.Request<{}> & { refreshToken: string; }; +/** @public */ +export type OAuthLogoutRequest = express.Request<{}> & { + refreshToken: string; +}; + /** * Any OAuth provider needs to implement this interface which has provider specific * handlers for different methods to perform authentication, get access tokens, @@ -136,5 +141,5 @@ export interface OAuthHandlers { /** * (Optional) Sign out of the auth provider. */ - logout?(): Promise; + logout?(req: OAuthLogoutRequest): Promise; } diff --git a/plugins/auth-backend/src/providers/auth0/provider.ts b/plugins/auth-backend/src/providers/auth0/provider.ts index 309fb0fc7b..95e83d2cce 100644 --- a/plugins/auth-backend/src/providers/auth0/provider.ts +++ b/plugins/auth-backend/src/providers/auth0/provider.ts @@ -55,6 +55,8 @@ export type Auth0AuthProviderOptions = OAuthProviderOptions & { authHandler: AuthHandler; resolverContext: AuthResolverContext; audience?: string; + connection?: string; + connectionScope?: string; }; export class Auth0AuthProvider implements OAuthHandlers { @@ -63,6 +65,8 @@ export class Auth0AuthProvider implements OAuthHandlers { private readonly authHandler: AuthHandler; private readonly resolverContext: AuthResolverContext; private readonly audience?: string; + private readonly connection?: string; + private readonly connectionScope?: string; /** * Due to passport-auth0 forcing options.state = true, @@ -86,6 +90,8 @@ export class Auth0AuthProvider implements OAuthHandlers { this.authHandler = options.authHandler; this.resolverContext = options.resolverContext; this.audience = options.audience; + this.connection = options.connection; + this.connectionScope = options.connectionScope; this._strategy = new Auth0Strategy( { clientID: options.clientId, @@ -127,6 +133,10 @@ export class Auth0AuthProvider implements OAuthHandlers { scope: req.scope, state: encodeState(req.state), ...(this.audience ? { audience: this.audience } : {}), + ...(this.connection ? { connection: this.connection } : {}), + ...(this.connectionScope + ? { connection_scope: this.connectionScope } + : {}), }); } @@ -136,6 +146,10 @@ export class Auth0AuthProvider implements OAuthHandlers { PrivateInfo >(req, this._strategy, { ...(this.audience ? { audience: this.audience } : {}), + ...(this.connection ? { connection: this.connection } : {}), + ...(this.connectionScope + ? { connection_scope: this.connectionScope } + : {}), }); return { @@ -224,6 +238,8 @@ export const auth0 = createAuthProviderIntegration({ const domain = envConfig.getString('domain'); const customCallbackUrl = envConfig.getOptionalString('callbackUrl'); const audience = envConfig.getOptionalString('audience'); + const connection = envConfig.getOptionalString('connection'); + const connectionScope = envConfig.getOptionalString('connectionScope'); const callbackUrl = customCallbackUrl || `${globalConfig.baseUrl}/${providerId}/handler/frame`; @@ -245,6 +261,8 @@ export const auth0 = createAuthProviderIntegration({ signInResolver, resolverContext, audience, + connection, + connectionScope, }); return OAuthAdapter.fromConfig(globalConfig, provider, { diff --git a/plugins/auth-backend/src/providers/gitlab/provider.ts b/plugins/auth-backend/src/providers/gitlab/provider.ts index 7f5489d391..31efafa4ee 100644 --- a/plugins/auth-backend/src/providers/gitlab/provider.ts +++ b/plugins/auth-backend/src/providers/gitlab/provider.ts @@ -91,6 +91,9 @@ export class GitlabAuthProvider implements OAuthHandlers { clientSecret: options.clientSecret, callbackURL: options.callbackUrl, baseURL: options.baseUrl, + authorizationURL: `${options.baseUrl}/oauth/authorize`, + tokenURL: `${options.baseUrl}/oauth/token`, + profileURL: `${options.baseUrl}/api/v4/user`, }, ( accessToken: any, diff --git a/plugins/auth-backend/src/providers/google/provider.ts b/plugins/auth-backend/src/providers/google/provider.ts index 7689cd1a3a..afe5c27f6b 100644 --- a/plugins/auth-backend/src/providers/google/provider.ts +++ b/plugins/auth-backend/src/providers/google/provider.ts @@ -16,6 +16,7 @@ import express from 'express'; import passport from 'passport'; +import { OAuth2Client } from 'google-auth-library'; import { Strategy as GoogleStrategy } from 'passport-google-oauth20'; import { encodeState, @@ -27,6 +28,7 @@ import { OAuthResponse, OAuthResult, OAuthStartRequest, + OAuthLogoutRequest, } from '../../lib/oauth'; import { executeFetchUserProfileStrategy, @@ -119,6 +121,11 @@ export class GoogleAuthProvider implements OAuthHandlers { }; } + async logout(req: OAuthLogoutRequest) { + const oauthClient = new OAuth2Client(); + await oauthClient.revokeToken(req.refreshToken); + } + async refresh(req: OAuthRefreshRequest) { const { accessToken, refreshToken, params } = await executeRefreshTokenStrategy( diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index 24ce679c1e..ab1229ea69 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-auth-node +## 0.2.6 + +### Patch Changes + +- f3a3fefb96: Ensure `getIdentity` throws an `AuthenticationError` instead of a `NotAllowed` error when authentication fails +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.2.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.2.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + ## 0.2.5 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index eac9d4e4d1..f657a2df01 100644 --- a/plugins/auth-node/package.json +++ b/plugins/auth-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-node", - "version": "0.2.5", + "version": "0.2.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/auth-node/src/DefaultIdentityClient.ts b/plugins/auth-node/src/DefaultIdentityClient.ts index a7946c77ff..499fc62929 100644 --- a/plugins/auth-node/src/DefaultIdentityClient.ts +++ b/plugins/auth-node/src/DefaultIdentityClient.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { AuthenticationError, NotAllowedError } from '@backstage/errors'; +import { AuthenticationError } from '@backstage/errors'; import { createRemoteJWKSet, decodeJwt, @@ -86,7 +86,7 @@ export class DefaultIdentityClient implements IdentityApi { getBearerTokenFromAuthorizationHeader(request.headers.authorization), ); } catch (e) { - throw new NotAllowedError(e.message); + throw new AuthenticationError(e.message); } } diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md index 4f40ad3c6e..c7721c604d 100644 --- a/plugins/azure-devops-backend/CHANGELOG.md +++ b/plugins/azure-devops-backend/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-azure-devops-backend +## 0.3.16 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/plugin-azure-devops-common@0.3.0 + +## 0.3.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/plugin-azure-devops-common@0.3.0 + +## 0.3.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/plugin-azure-devops-common@0.3.0 + +## 0.3.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/plugin-azure-devops-common@0.3.0 + ## 0.3.15 ### Patch Changes diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index c77860abe5..e429240ab3 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops-backend", - "version": "0.3.15", + "version": "0.3.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md index 6ca86dfefa..7c5c8983bf 100644 --- a/plugins/azure-devops/CHANGELOG.md +++ b/plugins/azure-devops/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-azure-devops +## 0.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + +## 0.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + +## 0.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-azure-devops-common@0.3.0 + ## 0.2.0 ### Minor Changes diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index 9785fdf2b3..3e1a69b3dc 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops", - "version": "0.2.0", + "version": "0.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/azure-sites-backend/.eslintrc.js b/plugins/azure-sites-backend/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/azure-sites-backend/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/azure-sites-backend/README.md b/plugins/azure-sites-backend/README.md new file mode 100644 index 0000000000..5f19267499 --- /dev/null +++ b/plugins/azure-sites-backend/README.md @@ -0,0 +1,85 @@ +# Azure Sites Backend + +Simple plugin that proxies requests to the Azure Portal API through Azure SDK JavaScript libraries. + +_Inspired by [roadie.io AWS Lamda plugin](https://roadie.io/backstage/plugins/aws-lambda/)_ + +## Setup + +The following sections will help you get the Azure Sites Backend plugin setup and running. + +### Configuration + +The Azure plugin requires the following YAML to be added to your app-config.yaml: + +```yaml +azureSites: + domain: + tenantId: + clientId: + clientSecret: + allowedSubscriptions: + - id: +``` + +Configuration Details: + +- `domain` can be found by visiting the [Directories + Subscriptions settings page](https://portal.azure.com/#settings/directory). Alternatively you can inspect the [Azure home](https://portal.azure.com/#home) URL - `https://portal.azure.com/#@/`. +- `tenantId` can be found by visiting [Azure Directory Overview page](https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade). +- (Optional) `clientId` and `clientSecret` can be the same values you used for [Azure DevOps Backend](https://github.com/backstage/backstage/tree/master/plugins/azure-devops-backend) or [Azure Integration](https://backstage.io/docs/integrations/azure/org#app-registration) as long as this App Registration has permissions to read your function apps. +- (Optional) `allowedSubscriptions` is an array of `id` that will be used to iterate over and look for the specified functions' app. `id` can be found the [Subscriptions page](https://portal.azure.com/#view/Microsoft_Azure_Billing/SubscriptionsBlade). + +### Integrating + +Here's how to get the backend plugin up and running: + +1. First we need to add the `@backstage/plugin-azure-sites-backend` package to your backend: + + ```sh + # From the Backstage root directory + yarn add --cwd packages/backend @backstage/plugin-azure-sites-backend + ``` + +2. Then we will create a new file named `packages/backend/src/plugins/azure.ts`, and add the following to it: + + ```ts + import { + createRouter, + AzureSitesApi, + } from '@backstage/plugin-azure-sites-backend'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + logger: env.logger, + azureSitesApi: AzureSitesApi.fromConfig(env.config), + }); + } + ``` + +3. Next we wire this into the overall backend router, edit `packages/backend/src/index.ts`: + + ```ts + import azure from './plugins/azure'; + + // Removed for clairty... + + async function main() { + // ... + // Add this line under the other lines that follow the useHotMemoize pattern + const azureSitesEnv = useHotMemoize(module, () => + createEnv('azure-sites'), + ); + + // ... + // Insert this line under the other lines that add their routers to apiRouter in the same way + apiRouter.use('/azure-sites', await azure(azureSitesEnv)); + } + ``` + +4. Now run `yarn start-backend` from the repo root. + +5. Finally, open `http://localhost:7007/api/azure/health` in a browser, it should return `{"status":"ok"}`. diff --git a/plugins/azure-sites-backend/api-report.md b/plugins/azure-sites-backend/api-report.md new file mode 100644 index 0000000000..751ff3f033 --- /dev/null +++ b/plugins/azure-sites-backend/api-report.md @@ -0,0 +1,61 @@ +## API Report File for "@backstage/plugin-azure-sites-backend" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { AzureSiteListRequest } from '@backstage/plugin-azure-sites-common'; +import { AzureSiteListResponse } from '@backstage/plugin-azure-sites-common'; +import { AzureSiteStartStopRequest } from '@backstage/plugin-azure-sites-common'; +import { Config } from '@backstage/config'; +import express from 'express'; +import { Logger } from 'winston'; + +// @public (undocumented) +export class AzureSitesApi { + constructor(config: AzureSitesConfig); + // (undocumented) + static fromConfig(config: Config): AzureSitesApi; + // (undocumented) + list(request: AzureSiteListRequest): Promise; + // (undocumented) + start(request: AzureSiteStartStopRequest): Promise; + // (undocumented) + stop(request: AzureSiteStartStopRequest): Promise; +} + +// @public (undocumented) +export class AzureSitesConfig { + constructor( + domain: string, + tenantId: string, + subscriptions?: string[] | undefined, + clientId?: string | undefined, + clientSecret?: string | undefined, + ); + // (undocumented) + readonly clientId?: string | undefined; + // (undocumented) + readonly clientSecret?: string | undefined; + // (undocumented) + readonly domain: string; + // (undocumented) + static fromConfig(config: Config): AzureSitesConfig; + // (undocumented) + readonly subscriptions?: string[] | undefined; + // (undocumented) + readonly tenantId: string; +} + +// @public (undocumented) +export function createRouter(options: RouterOptions): Promise; + +// @public (undocumented) +export interface RouterOptions { + // (undocumented) + azureSitesApi: AzureSitesApi; + // (undocumented) + logger: Logger; +} + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/azure-sites-backend/package.json b/plugins/azure-sites-backend/package.json new file mode 100644 index 0000000000..6930f36587 --- /dev/null +++ b/plugins/azure-sites-backend/package.json @@ -0,0 +1,56 @@ +{ + "name": "@backstage/plugin-azure-sites-backend", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "backend-plugin" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/azure-sites-backend" + }, + "keywords": [ + "backstage", + "azure" + ], + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@azure/arm-appservice": "^13.0.1", + "@azure/arm-resourcegraph": "^4.2.1", + "@azure/identity": "^2.1.0", + "@backstage/backend-common": "workspace:^", + "@backstage/config": "workspace:^", + "@backstage/plugin-azure-sites-common": "workspace:^", + "@types/express": "^4.17.6", + "express": "^4.17.1", + "express-promise-router": "^4.1.0", + "node-fetch": "^2.6.7", + "winston": "^3.2.1", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@types/supertest": "^2.0.8", + "msw": "^0.47.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/azure-sites-backend/schema.d.ts b/plugins/azure-sites-backend/schema.d.ts new file mode 100644 index 0000000000..19a12d11a1 --- /dev/null +++ b/plugins/azure-sites-backend/schema.d.ts @@ -0,0 +1,34 @@ +/* + * 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 Config { + azureSites: { + /** @visibility backend */ + domain: string; + /** @visibility backend */ + tenantId: string; + /** @visibility backend */ + subscriptions?: [ + { + id: string; + }, + ]; + /** @visibility backend */ + clientId?: string; + /** @visibility secret */ + clientSecret?: string; + }; +} diff --git a/plugins/azure-sites-backend/src/api/AzureSitesApi.ts b/plugins/azure-sites-backend/src/api/AzureSitesApi.ts new file mode 100644 index 0000000000..73e92ea6a0 --- /dev/null +++ b/plugins/azure-sites-backend/src/api/AzureSitesApi.ts @@ -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 { Config } from '@backstage/config'; +import { + DefaultAzureCredential, + ClientSecretCredential, +} from '@azure/identity'; +import { ResourceGraphClient } from '@azure/arm-resourcegraph'; +import { WebSiteManagementClient } from '@azure/arm-appservice'; +import { + AzureSite, + AzureSiteListRequest, + AzureSiteListResponse, + AzureSiteStartStopRequest, +} from '@backstage/plugin-azure-sites-common'; +import { AzureSitesConfig } from '../config'; + +/** @public */ +export class AzureSitesApi { + private readonly baseHref = (domain: string) => + `https://portal.azure.com/#@${domain}/resource`; + private readonly client: ResourceGraphClient; + + constructor(private readonly config: AzureSitesConfig) { + const creds = this.getCredentials(config); + + this.client = new ResourceGraphClient(creds); + } + + private getCredentials(config: AzureSitesConfig) { + return config.clientId && config.clientSecret + ? new ClientSecretCredential( + config.tenantId, + config.clientId, + config.clientSecret, + ) + : new DefaultAzureCredential({ tenantId: config.tenantId }); + } + + static fromConfig(config: Config): AzureSitesApi { + return new AzureSitesApi(AzureSitesConfig.fromConfig(config)); + } + + async start(request: AzureSiteStartStopRequest): Promise { + const client = new WebSiteManagementClient( + this.getCredentials(this.config), + request.subscription, + ); + await client.webApps.start(request.resourceGroup, request.name); + } + + async stop(request: AzureSiteStartStopRequest): Promise { + const client = new WebSiteManagementClient( + this.getCredentials(this.config), + request.subscription, + ); + await client.webApps.stop(request.resourceGroup, request.name); + } + + async list(request: AzureSiteListRequest): Promise { + const items: AzureSite[] = []; + const result = await this.client.resources({ + query: `resources | where type == 'microsoft.web/sites' | where name contains '${request.name}'`, + subscriptions: this.config.subscriptions, + }); + for (const v of result.data) { + items.push({ + href: `${this.baseHref(this.config.domain)}${v.id!}`, + logstreamHref: `${this.baseHref(this.config.domain)}${v.id!}/logStream`, + name: v.name!, + kind: v.kind!, + resourceGroup: v.resourceGroup!, + subscription: v.id!.match(/\w{8}\-\w{4}\-\w{4}\-\w{4}\-\w{12}/)[0], + location: v.location!, + lastModifiedDate: v.properties.lastModifiedTimeUtc!, + usageState: v.properties.usageState!, + state: v.properties.state!, + containerSize: v.properties.containerSize!, + tags: v.properties.tags!, + }); + } + return { items: items }; + } +} diff --git a/packages/core-components/src/layout/HomepageTimer/index.ts b/plugins/azure-sites-backend/src/api/index.ts similarity index 86% rename from packages/core-components/src/layout/HomepageTimer/index.ts rename to plugins/azure-sites-backend/src/api/index.ts index bc004fc757..187dc1314b 100644 --- a/packages/core-components/src/layout/HomepageTimer/index.ts +++ b/plugins/azure-sites-backend/src/api/index.ts @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. @@ -14,4 +14,4 @@ * limitations under the License. */ -export { HomepageTimer } from './HomepageTimer'; +export { AzureSitesApi } from './AzureSitesApi'; diff --git a/plugins/azure-sites-backend/src/config.ts b/plugins/azure-sites-backend/src/config.ts new file mode 100644 index 0000000000..3475175cb5 --- /dev/null +++ b/plugins/azure-sites-backend/src/config.ts @@ -0,0 +1,42 @@ +/* + * 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 { Config } from '@backstage/config'; + +/** @public */ +export class AzureSitesConfig { + constructor( + public readonly domain: string, + public readonly tenantId: string, + public readonly subscriptions?: string[], + public readonly clientId?: string, + public readonly clientSecret?: string, + ) {} + + static fromConfig(config: Config): AzureSitesConfig { + const azConfig = config.getConfig('azureSites'); + + return new AzureSitesConfig( + azConfig.getString('domain'), + azConfig.getString('tenantId'), + azConfig + .getOptionalConfigArray('subscriptions') + ?.map(as => as.getString('id')), + azConfig.getOptionalString('clientId'), + azConfig.getOptionalString('clientSecret'), + ); + } +} diff --git a/plugins/azure-sites-backend/src/index.ts b/plugins/azure-sites-backend/src/index.ts new file mode 100644 index 0000000000..97f94e4470 --- /dev/null +++ b/plugins/azure-sites-backend/src/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 * from './service/router'; +export * from './api'; +export * from './config'; diff --git a/plugins/azure-sites-backend/src/run.ts b/plugins/azure-sites-backend/src/run.ts new file mode 100644 index 0000000000..d945aa13f0 --- /dev/null +++ b/plugins/azure-sites-backend/src/run.ts @@ -0,0 +1,33 @@ +/* + * 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 { getRootLogger } from '@backstage/backend-common'; +import yn from 'yn'; +import { startStandaloneServer } from './service/standaloneServer'; + +const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7007; +const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); +const logger = getRootLogger(); + +startStandaloneServer({ port, enableCors, logger }).catch(err => { + logger.error(err); + process.exit(1); +}); + +process.on('SIGINT', () => { + logger.info('CTRL+C pressed; exiting.'); + process.exit(0); +}); diff --git a/plugins/azure-sites-backend/src/service/router.ts b/plugins/azure-sites-backend/src/service/router.ts new file mode 100644 index 0000000000..bea9a15396 --- /dev/null +++ b/plugins/azure-sites-backend/src/service/router.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 { errorHandler } from '@backstage/backend-common'; +import express from 'express'; +import Router from 'express-promise-router'; +import { Logger } from 'winston'; + +import { AzureSitesApi } from '../api'; + +/** @public */ +export interface RouterOptions { + logger: Logger; + azureSitesApi: AzureSitesApi; +} + +/** @public */ +export async function createRouter( + options: RouterOptions, +): Promise { + const { logger, azureSitesApi } = options; + + const router = Router(); + router.use(express.json()); + + router.get('/health', (_, response) => { + logger.info('PONG!'); + response.send({ status: 'ok' }); + }); + router.get('/list/:name', async (request, response) => { + const { name } = request.params; + response.json( + await azureSitesApi.list({ + name: name, + }), + ); + }); + router.post( + '/:subscription/:resourceGroup/:name/start', + async (request, response) => { + const { subscription, resourceGroup, name } = request.params; + console.log('starting...'); + response.json( + await azureSitesApi.start({ + subscription, + resourceGroup, + name, + }), + ); + }, + ); + router.post( + '/:subscription/:resourceGroup/:name/stop', + async (request, response) => { + const { subscription, resourceGroup, name } = request.params; + console.log('stopping...'); + response.json( + await azureSitesApi.stop({ + subscription, + resourceGroup, + name, + }), + ); + }, + ); + router.use(errorHandler()); + return router; +} diff --git a/plugins/azure-sites-backend/src/service/standaloneServer.ts b/plugins/azure-sites-backend/src/service/standaloneServer.ts new file mode 100644 index 0000000000..1998bee023 --- /dev/null +++ b/plugins/azure-sites-backend/src/service/standaloneServer.ts @@ -0,0 +1,56 @@ +/* + * 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 { + createServiceBuilder, + loadBackendConfig, +} from '@backstage/backend-common'; +import { Server } from 'http'; +import { Logger } from 'winston'; +import { AzureSitesApi } from '../api'; +import { createRouter } from './router'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const logger = options.logger.child({ service: 'azure-backend' }); + const config = await loadBackendConfig({ logger, argv: process.argv }); + logger.debug('Starting application server...'); + const router = await createRouter({ + logger, + azureSitesApi: AzureSitesApi.fromConfig(config), + }); + + let service = createServiceBuilder(module) + .setPort(options.port) + .addRouter('/azure-backend', router); + if (options.enableCors) { + service = service.enableCors({ origin: 'http://localhost:3000' }); + } + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); diff --git a/plugins/azure-sites-backend/src/setupTests.ts b/plugins/azure-sites-backend/src/setupTests.ts new file mode 100644 index 0000000000..813cdeaae3 --- /dev/null +++ b/plugins/azure-sites-backend/src/setupTests.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 {}; diff --git a/plugins/azure-sites-common/.eslintrc.js b/plugins/azure-sites-common/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/azure-sites-common/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/azure-sites-common/api-report.md b/plugins/azure-sites-common/api-report.md new file mode 100644 index 0000000000..64b4275085 --- /dev/null +++ b/plugins/azure-sites-common/api-report.md @@ -0,0 +1,40 @@ +## API Report File for "@backstage/plugin-azure-sites-common" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +// @public (undocumented) +export type AzureSite = { + href: string; + logstreamHref: string; + name: string; + kind: string; + location: string; + resourceGroup: string; + subscription: string; + state: string; + usageState: string; + containerSize: number; + lastModifiedDate: string; + tags: {}; +}; + +// @public (undocumented) +export type AzureSiteListRequest = { + name: string; +}; + +// @public (undocumented) +export type AzureSiteListResponse = { + items: AzureSite[]; +}; + +// @public (undocumented) +export type AzureSiteStartStopRequest = { + subscription: string; + resourceGroup: string; + name: string; +}; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/azure-sites-common/package.json b/plugins/azure-sites-common/package.json new file mode 100644 index 0000000000..eb0093bf65 --- /dev/null +++ b/plugins/azure-sites-common/package.json @@ -0,0 +1,40 @@ +{ + "name": "@backstage/plugin-azure-sites-common", + "description": "Common functionalities for the azure plugin", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "common-library" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/azure-sites-common" + }, + "keywords": [ + "backstage" + ], + "scripts": { + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "devDependencies": { + "@backstage/cli": "workspace:^" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/azure-sites-common/src/index.ts b/plugins/azure-sites-common/src/index.ts new file mode 100644 index 0000000000..db229eae34 --- /dev/null +++ b/plugins/azure-sites-common/src/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 * from './types'; diff --git a/plugins/azure-sites-common/src/types.ts b/plugins/azure-sites-common/src/types.ts new file mode 100644 index 0000000000..7c8be7ce24 --- /dev/null +++ b/plugins/azure-sites-common/src/types.ts @@ -0,0 +1,48 @@ +/* + * 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. + */ + +/** @public */ +export type AzureSite = { + href: string; + logstreamHref: string; + name: string; + kind: string; + location: string; + resourceGroup: string; + subscription: string; + state: string; + usageState: string; + containerSize: number; + lastModifiedDate: string; + tags: {}; +}; + +/** @public */ +export type AzureSiteListRequest = { + name: string; +}; + +/** @public */ +export type AzureSiteListResponse = { + items: AzureSite[]; +}; + +/** @public */ +export type AzureSiteStartStopRequest = { + subscription: string; + resourceGroup: string; + name: string; +}; diff --git a/plugins/azure-sites/.eslintrc.js b/plugins/azure-sites/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/azure-sites/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/azure-sites/README.md b/plugins/azure-sites/README.md new file mode 100644 index 0000000000..f240dab140 --- /dev/null +++ b/plugins/azure-sites/README.md @@ -0,0 +1,70 @@ +# Azure Sites Plugin + +![preview of Azure table](docs/functions-table.png) + +_Inspired by [roadie.io AWS Lamda plugin](https://roadie.io/backstage/plugins/aws-lambda/)_ + +## Features + +- Azure overview table + +## Plugin Setup + +The following sections will help you get the Azure plugin setup and running + +### Azure Sites Backend + +You need to set up the Azure Sites Backend plugin before you move forward with any of these steps if you haven't already. + +### Entity Annotation + +To be able to use the Azure Sites plugin you need to add the following annotation to any entities you want to use it with: + +```yaml +azure.com/microsoft-web-sites: +``` + +`` supports case-insensitive exact / partial value. + +Example of Partial Matching: + +Let's say you have a number of functions apps, spread out over different regions (and possibly different subscriptions), and they follow a naming convention: + +``` +func-testapp-eu +func-testapp-ca +func-testapp-us +``` + +The annotation you will use to have the three functions' app appear in the overview table would look like this: + +```yaml +azure.com/microsoft-web-sites: func-testapp +``` + +### Install the component + +1. Install the plugin in the `packages/app` directory + +```sh +yarn add @backstage/plugin-azure-sites +``` + +2. Add widget component to your Backstage instance: + +```ts +// In packages/app/src/components/catalog/EntityPage.tsx +import { EntityAzureSitesOverviewWidget, isAzureWebSiteNameAvailable } from '@backstage/plugin-azure-sites'; + +... + +const serviceEntityPage = ( + + //... + Boolean(isAzureWebSiteNameAvailable(e))} path="/azure" title="Azure"> + + + //... + +); +``` diff --git a/plugins/azure-sites/api-report.md b/plugins/azure-sites/api-report.md new file mode 100644 index 0000000000..38ca6a8fe2 --- /dev/null +++ b/plugins/azure-sites/api-report.md @@ -0,0 +1,63 @@ +## API Report File for "@backstage/plugin-azure-sites" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +/// + +import { ApiRef } from '@backstage/core-plugin-api'; +import { AzureSiteListRequest } from '@backstage/plugin-azure-sites-common'; +import { AzureSiteListResponse } from '@backstage/plugin-azure-sites-common'; +import { AzureSiteStartStopRequest } from '@backstage/plugin-azure-sites-common'; +import { BackstagePlugin } from '@backstage/core-plugin-api'; +import { DiscoveryApi } from '@backstage/core-plugin-api'; +import { Entity } from '@backstage/catalog-model'; +import { IdentityApi } from '@backstage/core-plugin-api'; +import { RouteRef } from '@backstage/core-plugin-api'; + +// @public (undocumented) +export const azureSiteApiRef: ApiRef; + +// @public (undocumented) +export type AzureSitesApi = { + list: (request: AzureSiteListRequest) => Promise; + start: (request: AzureSiteStartStopRequest) => Promise; + stop: (request: AzureSiteStartStopRequest) => Promise; +}; + +// @public (undocumented) +export class AzureSitesApiBackendClient implements AzureSitesApi { + constructor(options: { + discoveryApi: DiscoveryApi; + identityApi: IdentityApi; + }); + // (undocumented) + list(request: AzureSiteListRequest): Promise; + // (undocumented) + start(request: AzureSiteStartStopRequest): Promise; + // (undocumented) + stop(request: AzureSiteStartStopRequest): Promise; +} + +// @public (undocumented) +export const AzureSitesOverviewWidget: () => JSX.Element; + +// @public (undocumented) +export const azureSitesPlugin: BackstagePlugin< + { + entityContent: RouteRef; + }, + {}, + {} +>; + +// @public (undocumented) +export const EntityAzureSitesOverviewWidget: () => JSX.Element; + +// @public (undocumented) +export const isAzureWebSiteNameAvailable: ( + entity: Entity, +) => string | undefined; + +// (No @packageDocumentation comment for this package) +``` diff --git a/plugins/azure-sites/dev/index.tsx b/plugins/azure-sites/dev/index.tsx new file mode 100644 index 0000000000..02f8ed99cd --- /dev/null +++ b/plugins/azure-sites/dev/index.tsx @@ -0,0 +1,20 @@ +/* + * 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 { createDevApp } from '@backstage/dev-utils'; +import { azureSitesPlugin } from '../src/plugin'; + +createDevApp().registerPlugin(azureSitesPlugin).render(); diff --git a/plugins/azure-sites/docs/functions-table.png b/plugins/azure-sites/docs/functions-table.png new file mode 100644 index 0000000000..d8603e8fa7 Binary files /dev/null and b/plugins/azure-sites/docs/functions-table.png differ diff --git a/plugins/azure-sites/package.json b/plugins/azure-sites/package.json new file mode 100644 index 0000000000..2ffe0225e6 --- /dev/null +++ b/plugins/azure-sites/package.json @@ -0,0 +1,65 @@ +{ + "name": "@backstage/plugin-azure-sites", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "publishConfig": { + "access": "public", + "main": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "frontend-plugin" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/azure-sites" + }, + "keywords": [ + "backstage", + "azure" + ], + "scripts": { + "start": "backstage-cli package start", + "build": "backstage-cli package build", + "lint": "backstage-cli package lint", + "test": "backstage-cli package test", + "clean": "backstage-cli package clean", + "prepack": "backstage-cli package prepack", + "postpack": "backstage-cli package postpack" + }, + "dependencies": { + "@backstage/catalog-model": "workspace:^", + "@backstage/core-components": "workspace:^", + "@backstage/core-plugin-api": "workspace:^", + "@backstage/plugin-azure-sites-common": "workspace:^", + "@backstage/plugin-catalog-react": "workspace:^", + "@backstage/theme": "workspace:^", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.57", + "luxon": "^3.0.0", + "react-use": "^17.2.4" + }, + "peerDependencies": { + "react": "^16.13.1 || ^17.0.0" + }, + "devDependencies": { + "@backstage/cli": "workspace:^", + "@backstage/core-app-api": "workspace:^", + "@backstage/dev-utils": "workspace:^", + "@backstage/test-utils": "workspace:^", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^12.1.3", + "@testing-library/user-event": "^14.0.0", + "@types/node": "^16.11.26", + "cross-fetch": "^3.1.5", + "msw": "^0.47.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/azure-sites/src/api/AzureSitesApi.ts b/plugins/azure-sites/src/api/AzureSitesApi.ts new file mode 100644 index 0000000000..87a3b46a8d --- /dev/null +++ b/plugins/azure-sites/src/api/AzureSitesApi.ts @@ -0,0 +1,34 @@ +/* + * 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 { createApiRef } from '@backstage/core-plugin-api'; +import { + AzureSiteListRequest, + AzureSiteListResponse, + AzureSiteStartStopRequest, +} from '@backstage/plugin-azure-sites-common'; + +/** @public */ +export const azureSiteApiRef = createApiRef({ + id: 'plugin.azure-sites.service', +}); + +/** @public */ +export type AzureSitesApi = { + list: (request: AzureSiteListRequest) => Promise; + start: (request: AzureSiteStartStopRequest) => Promise; + stop: (request: AzureSiteStartStopRequest) => Promise; +}; diff --git a/plugins/azure-sites/src/api/AzureSitesApiBackendClient.ts b/plugins/azure-sites/src/api/AzureSitesApiBackendClient.ts new file mode 100644 index 0000000000..fc622e786b --- /dev/null +++ b/plugins/azure-sites/src/api/AzureSitesApiBackendClient.ts @@ -0,0 +1,78 @@ +/* + * 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 { AzureSitesApi } from './AzureSitesApi'; +import { + AzureSiteListRequest, + AzureSiteListResponse, + AzureSiteStartStopRequest, +} from '@backstage/plugin-azure-sites-common'; +import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; + +/** @public */ +export class AzureSitesApiBackendClient implements AzureSitesApi { + private readonly identityApi: IdentityApi; + private readonly discoveryApi: DiscoveryApi; + constructor(options: { + discoveryApi: DiscoveryApi; + identityApi: IdentityApi; + }) { + this.discoveryApi = options.discoveryApi; + this.identityApi = options.identityApi; + } + + async stop(request: AzureSiteStartStopRequest): Promise { + const url = `${await this.discoveryApi.getBaseUrl('azure-functions')}/${ + request.subscription + }/${request.resourceGroup}/${request.name}/stop`; + const { token: accessToken } = await this.identityApi.getCredentials(); + await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(accessToken && { Authorization: `Bearer ${accessToken}` }), + }, + }); + } + async start(request: AzureSiteStartStopRequest): Promise { + const url = `${await this.discoveryApi.getBaseUrl('azure-functions')}/${ + request.subscription + }/${request.resourceGroup}/${request.name}/start`; + const { token: accessToken } = await this.identityApi.getCredentials(); + await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(accessToken && { Authorization: `Bearer ${accessToken}` }), + }, + }); + } + + async list(request: AzureSiteListRequest): Promise { + const url = `${await this.discoveryApi.getBaseUrl('azure-sites')}/list/${ + request.name + }`; + const { token: accessToken } = await this.identityApi.getCredentials(); + const response = await fetch(url, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + ...(accessToken && { Authorization: `Bearer ${accessToken}` }), + }, + }); + return await response.json(); + } +} diff --git a/plugins/azure-sites/src/api/index.ts b/plugins/azure-sites/src/api/index.ts new file mode 100644 index 0000000000..2b6cc64221 --- /dev/null +++ b/plugins/azure-sites/src/api/index.ts @@ -0,0 +1,18 @@ +/* + * 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 * from './AzureSitesApi'; +export * from './AzureSitesApiBackendClient'; diff --git a/plugins/azure-sites/src/components/AzureSitesOverviewComponent/AzureSitesOverview.tsx b/plugins/azure-sites/src/components/AzureSitesOverviewComponent/AzureSitesOverview.tsx new file mode 100644 index 0000000000..755a42f5da --- /dev/null +++ b/plugins/azure-sites/src/components/AzureSitesOverviewComponent/AzureSitesOverview.tsx @@ -0,0 +1,76 @@ +/* + * 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 { Entity } from '@backstage/catalog-model'; +import { useSites } from '../../hooks/useSites'; +import { + AZURE_WEB_SITE_NAME_ANNOTATION, + useServiceEntityAnnotations, +} from '../../hooks/useServiceEntityAnnotations'; +import { + ErrorBoundary, + MissingAnnotationEmptyState, + ResponseErrorPanel, +} from '@backstage/core-components'; +import { useEntity } from '@backstage/plugin-catalog-react'; +import { AzureSitesOverviewTable } from '../AzureSitesOverviewTableComponent/AzureSitesOverviewTable'; + +/** @public */ +export const isAzureWebSiteNameAvailable = (entity: Entity) => + entity?.metadata.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION]; + +const AzureSitesOverview = ({ entity }: { entity: Entity }) => { + const { webSiteName } = useServiceEntityAnnotations(entity); + + const [sites] = useSites({ + name: webSiteName, + }); + + if (sites.error) { + return ( +
+ +
+ ); + } + + return ( + + ); +}; + +/** @public */ +export const AzureSitesOverviewWidget = () => { + const { entity } = useEntity(); + + if (!isAzureWebSiteNameAvailable(entity)) { + return ( + + ); + } + + return ( + + + + ); +}; diff --git a/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx new file mode 100644 index 0000000000..23476b5c3e --- /dev/null +++ b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.test.tsx @@ -0,0 +1,86 @@ +/* + * 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 { render } from '@testing-library/react'; +import { + AnyApiRef, + configApiRef, + errorApiRef, + identityApiRef, +} from '@backstage/core-plugin-api'; +import { rest } from 'msw'; +import { + setupRequestMockHandlers, + TestApiProvider, +} from '@backstage/test-utils'; +import { setupServer } from 'msw/node'; +import { DateTime } from 'luxon'; +import { siteMock } from '../../mocks/mocks'; +import { azureSiteApiRef } from '../..'; +import { AzureSitesOverviewTable } from './AzureSitesOverviewTable'; + +const errorApiMock = { post: jest.fn(), error$: jest.fn() }; +const identityApiMock = (getCredentials: any) => ({ + signOut: jest.fn(), + getProfileInfo: jest.fn(), + getBackstageIdentity: jest.fn(), + getCredentials, +}); +const azureSitesApiMock = {}; + +const config = { + getString: (_: string) => 'https://test-url', +}; + +const apis: [AnyApiRef, Partial][] = [ + [errorApiRef, errorApiMock], + [configApiRef, config], + [azureSiteApiRef, azureSitesApiMock], + [identityApiRef, identityApiMock], +]; + +describe('AzureSitesOverviewWidget', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + + beforeEach(() => { + worker.use( + rest.get('/list/func-mock', (_, res, ctx) => { + res(ctx.json(siteMock)); + }), + ); + }); + + it('should display an overview table with the data from the requests', async () => { + const rendered = render( + + + , + ); + + expect(await rendered.findByText(siteMock.name)).toBeInTheDocument(); + expect(await rendered.findByText(siteMock.location)).toBeInTheDocument(); + expect(await rendered.findByText(siteMock.state)).toBeInTheDocument(); + expect( + await rendered.findByText( + DateTime.fromISO(siteMock.lastModifiedDate).toLocaleString( + DateTime.DATETIME_MED, + ), + ), + ).toBeInTheDocument(); + }); +}); diff --git a/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.tsx b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.tsx new file mode 100644 index 0000000000..3f9895a226 --- /dev/null +++ b/plugins/azure-sites/src/components/AzureSitesOverviewTableComponent/AzureSitesOverviewTable.tsx @@ -0,0 +1,271 @@ +/* + * 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, { Dispatch, useEffect, useState } from 'react'; +import { + Box, + Card, + IconButton, + LinearProgress, + Link, + Menu, + MenuItem, + Snackbar, + Tooltip, +} from '@material-ui/core'; +import { default as MuiAlert } from '@material-ui/lab/Alert'; +import { AzureSite } from '@backstage/plugin-azure-sites-common'; +import { Table, TableColumn } from '@backstage/core-components'; +import FlashOnIcon from '@material-ui/icons/FlashOn'; +import PublicIcon from '@material-ui/icons/Public'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; +import StartIcon from '@material-ui/icons/PlayArrow'; +import StopIcon from '@material-ui/icons/Stop'; +import OpenInNewIcon from '@material-ui/icons/OpenInNew'; +import { DateTime } from 'luxon'; +import { useApi } from '@backstage/core-plugin-api'; +import { azureSiteApiRef } from '../../api'; + +type States = 'Waiting' | 'Running' | 'Paused' | 'Failed' | 'Stopped'; +type Kinds = 'app' | 'functionapp'; + +const State = ({ value }: { value: States }) => { + const colorMap = { + Waiting: '#dcbc21', + Running: 'green', + Paused: 'black', + Failed: 'red', + Stopped: 'black', + }; + return ( + + + {value} + + ); +}; + +const Kind = ({ value }: { value: Kinds }) => { + const iconMap = { + app: , + functionapp: , + }; + return ( + + {iconMap[value]} + + ); +}; + +type TableProps = { + data: AzureSite[]; + loading: boolean; +}; + +const ActionButtons = ({ + value, + onMenuItemClick, +}: { + value: AzureSite; + onMenuItemClick: Dispatch>; +}) => { + const azureApi = useApi(azureSiteApiRef); + + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + + const handleOpen = (event: React.MouseEvent) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + const start = () => { + azureApi.start({ + name: value.name, + resourceGroup: value.resourceGroup, + subscription: value.subscription, + }); + onMenuItemClick('Starting, this may take some time...'); + handleClose(); + }; + const stop = () => { + azureApi.stop({ + name: value.name, + resourceGroup: value.resourceGroup, + subscription: value.subscription, + }); + onMenuItemClick('Stopping, this may take some time...'); + handleClose(); + }; + + return ( +
+ + + + + {value.state !== 'Running' && ( + + +  Start + + )} + {value.state !== 'Stopped' && ( + + +  Stop + + )} + + +  Log Stream + + +
+ ); +}; + +/** @public */ +export const AzureSitesOverviewTable = ({ data, loading }: TableProps) => { + const [snackbarMessage, setSnackbarMessage] = useState(null); + const [isSnackbarOpen, setSnackbarOpen] = useState(false); + + const onSnackbarClose = () => { + setSnackbarMessage(null); + }; + + useEffect(() => { + setSnackbarOpen(!!snackbarMessage); + }, [snackbarMessage]); + + const columns: TableColumn[] = [ + { + width: '25px', + field: 'kind', + render: (func: AzureSite) => , + }, + { + title: 'name', + field: 'name', + highlight: true, + render: (func: AzureSite) => { + return ( + + {func.name} + + ); + }, + }, + { + title: 'location', + field: 'location', + render: (func: AzureSite) => func.location ?? 'unknown', + }, + { + title: 'status', + field: 'status', + render: (func: AzureSite) => , + }, + { + title: 'last modified', + field: 'lastModifiedDate', + render: (func: AzureSite) => + DateTime.fromISO(func.lastModifiedDate).toLocaleString( + DateTime.DATETIME_MED, + ), + }, + { + title: 'actions', + align: 'right', + sorting: false, + field: 'actions', + render: (func: AzureSite) => ( + + ), + }, + ]; + + const tableStyle = { + minWidth: '0', + width: '100%', + }; + + return ( + + + + + Azure Sites + + } + options={{ paging: true, search: false, pageSize: 10 }} + data={data} + emptyContent={} + isLoading={loading} + columns={columns} + /> + + + {snackbarMessage} + + + + ); +}; diff --git a/plugins/azure-sites/src/hooks/useServiceEntityAnnotations.ts b/plugins/azure-sites/src/hooks/useServiceEntityAnnotations.ts new file mode 100644 index 0000000000..08070dc8e4 --- /dev/null +++ b/plugins/azure-sites/src/hooks/useServiceEntityAnnotations.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. + */ + +import { Entity } from '@backstage/catalog-model'; + +export const AZURE_WEB_SITE_NAME_ANNOTATION = 'azure.com/microsoft-web-sites'; +export const useServiceEntityAnnotations = (entity: Entity) => { + const webSiteName = + entity?.metadata.annotations?.[AZURE_WEB_SITE_NAME_ANNOTATION] ?? ''; + + return { + webSiteName, + }; +}; diff --git a/plugins/azure-sites/src/hooks/useSites.ts b/plugins/azure-sites/src/hooks/useSites.ts new file mode 100644 index 0000000000..fb3215c008 --- /dev/null +++ b/plugins/azure-sites/src/hooks/useSites.ts @@ -0,0 +1,72 @@ +/* + * 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 useInterval from 'react-use/lib/useInterval'; +import useAsyncRetry from 'react-use/lib/useAsyncRetry'; +import { useApi, errorApiRef } from '@backstage/core-plugin-api'; +import { AzureSiteListResponse } from '@backstage/plugin-azure-sites-common'; +import { azureSiteApiRef } from '../api'; +import { useCallback } from 'react'; + +const POLLING_INTERVAL = 15000; + +export function useSites({ name }: { name: string }) { + const azureApi = useApi(azureSiteApiRef); + const errorApi = useApi(errorApiRef); + + const list = useCallback( + async () => { + return await azureApi.list({ + name: name, + }); + }, + [name], // eslint-disable-line react-hooks/exhaustive-deps + ); + const { + loading, + value: data, + error, + retry, + } = useAsyncRetry(async () => { + try { + const sites = await list(); + return sites; + } catch (e) { + if (e instanceof Error) { + if (e?.message === 'AbortError') { + errorApi.post( + new Error( + 'Timeout reaching backend plugin, please add azure-backend plugin', + ), + ); + } + errorApi.post(e); + } + return null; + } + }, []); + + useInterval(() => retry(), POLLING_INTERVAL); + + return [ + { + loading, + data, + error, + retry, + }, + ] as const; +} diff --git a/plugins/azure-sites/src/index.ts b/plugins/azure-sites/src/index.ts new file mode 100644 index 0000000000..83029c6af5 --- /dev/null +++ b/plugins/azure-sites/src/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 * from './plugin'; +export * from './api'; +export * from './components/AzureSitesOverviewComponent/AzureSitesOverview'; diff --git a/plugins/azure-sites/src/mocks/mocks.ts b/plugins/azure-sites/src/mocks/mocks.ts new file mode 100644 index 0000000000..2bc3b8b9fe --- /dev/null +++ b/plugins/azure-sites/src/mocks/mocks.ts @@ -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 { AzureSite } from '@backstage/plugin-azure-sites-common'; + +export const entityMock = { + metadata: { + namespace: 'default', + annotations: { + 'azure.com/microsoft-web-sites': 'func-mock', + }, + name: 'sample-azure-service', + description: 'A service for testing Backstage functionality.', + uid: 'c009b513-d053-4b3f-9429-8433a145e943', + }, + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + spec: { + type: 'service', + owner: 'dev_group@example.com', + lifecycle: 'experimental', + }, +}; + +// https://management.azure.com/subscriptions/{{subscriptionId}}/resourceGroups/{{resourceGroup}}/providers/Microsoft.Web/sites/{{functionsName}}?api-version=2022-03-01 +export const siteMock: AzureSite = { + name: 'func-mock', + kind: 'functionapp', + resourceGroup: 'mock-resourcegroup', + subscription: '00000000-0000-0000-0000-000000000000', + tags: { + isMock: true, + }, + location: 'West Europe', + state: 'Running', + href: 'https://mockurl.api.azurewebsites.windows.net:454/subscriptions/00000000-0000-0000-0000-000000000000/webspaces/rg_mock-WestEuropewebspace/sites/func-mock', + logstreamHref: + 'https://mockurl.api.azurewebsites.windows.net:454/subscriptions/00000000-0000-0000-0000-000000000000/webspaces/rg_mock-WestEuropewebspace/sites/func-mock/logStream', + usageState: 'Normal', + lastModifiedDate: '2022-09-02T11:09:58.9033333', + containerSize: 100, +}; diff --git a/plugins/azure-sites/src/plugin.test.ts b/plugins/azure-sites/src/plugin.test.ts new file mode 100644 index 0000000000..fba8e36e6f --- /dev/null +++ b/plugins/azure-sites/src/plugin.test.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. + */ + +import { azureSitesPlugin } from './plugin'; + +describe('azure', () => { + it('should export plugin', () => { + expect(azureSitesPlugin).toBeDefined(); + }); +}); diff --git a/plugins/azure-sites/src/plugin.ts b/plugins/azure-sites/src/plugin.ts new file mode 100644 index 0000000000..f54f121ea1 --- /dev/null +++ b/plugins/azure-sites/src/plugin.ts @@ -0,0 +1,61 @@ +/* + * 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 { + createApiFactory, + createComponentExtension, + createPlugin, + createRouteRef, + discoveryApiRef, + identityApiRef, +} from '@backstage/core-plugin-api'; +import { azureSiteApiRef, AzureSitesApiBackendClient } from './api'; + +const entityContentRouteRef = createRouteRef({ + id: 'Azure Sites Entity Content', +}); + +/** @public */ +export const azureSitesPlugin = createPlugin({ + id: 'azureSites', + apis: [ + createApiFactory({ + api: azureSiteApiRef, + deps: { + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + }, + factory: ({ discoveryApi, identityApi }) => + new AzureSitesApiBackendClient({ discoveryApi, identityApi }), + }), + ], + routes: { + entityContent: entityContentRouteRef, + }, +}); + +/** @public */ +export const EntityAzureSitesOverviewWidget = azureSitesPlugin.provide( + createComponentExtension({ + name: 'EntityAzureSitesOverviewWidget', + component: { + lazy: () => + import( + './components/AzureSitesOverviewComponent/AzureSitesOverview' + ).then(m => m.AzureSitesOverviewWidget), + }, + }), +); diff --git a/plugins/azure-sites/src/routes.ts b/plugins/azure-sites/src/routes.ts new file mode 100644 index 0000000000..1b10ad8dde --- /dev/null +++ b/plugins/azure-sites/src/routes.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. + */ + +import { createRouteRef } from '@backstage/core-plugin-api'; + +export const rootRouteRef = createRouteRef({ + id: 'azure-sites', +}); diff --git a/plugins/azure-sites/src/setupTests.ts b/plugins/azure-sites/src/setupTests.ts new file mode 100644 index 0000000000..2b5b615b19 --- /dev/null +++ b/plugins/azure-sites/src/setupTests.ts @@ -0,0 +1,18 @@ +/* + * 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 '@testing-library/jest-dom'; +import 'cross-fetch/polyfill'; diff --git a/plugins/badges-backend/CHANGELOG.md b/plugins/badges-backend/CHANGELOG.md index 8b842ca26a..8e71d4f13f 100644 --- a/plugins/badges-backend/CHANGELOG.md +++ b/plugins/badges-backend/CHANGELOG.md @@ -1,5 +1,51 @@ # @backstage/plugin-badges-backend +## 0.1.31 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.1.31-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.1.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## 0.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + ## 0.1.30 ### Patch Changes diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json index 0eaf8a9d0a..0ca4eccccf 100644 --- a/plugins/badges-backend/package.json +++ b/plugins/badges-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges-backend", "description": "A Backstage backend plugin that generates README badges for your entities", - "version": "0.1.30", + "version": "0.1.31", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/badges-backend/src/service/router.test.ts b/plugins/badges-backend/src/service/router.test.ts index 5fc809906f..9dc60ea1b5 100644 --- a/plugins/badges-backend/src/service/router.test.ts +++ b/plugins/badges-backend/src/service/router.test.ts @@ -114,7 +114,7 @@ describe('createRouter', () => { ); expect(response.status).toEqual(200); - expect(response.text).toEqual(JSON.stringify([badge], null, 2)); + expect(response.body).toEqual([badge]); expect(catalog.getEntityByRef).toHaveBeenCalledTimes(1); expect(catalog.getEntityByRef).toHaveBeenCalledWith( diff --git a/plugins/badges-backend/src/service/router.ts b/plugins/badges-backend/src/service/router.ts index 2006b45dc4..e5caa5cd2a 100644 --- a/plugins/badges-backend/src/service/router.ts +++ b/plugins/badges-backend/src/service/router.ts @@ -78,8 +78,7 @@ export async function createRouter( specs.push(badge); } - res.setHeader('Content-Type', 'application/json'); - res.status(200).send(JSON.stringify(specs, null, 2)); + res.status(200).json(specs); }); router.get( diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md index fff53440bc..d2f5756609 100644 --- a/plugins/badges/CHANGELOG.md +++ b/plugins/badges/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-badges +## 0.2.34 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.2.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## 0.2.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.2.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + ## 0.2.33 ### Patch Changes diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 2e8081f3f6..0d212b70b5 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-badges", "description": "A Backstage plugin that generates README badges for your entities", - "version": "0.2.33", + "version": "0.2.34", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/bazaar-backend/CHANGELOG.md b/plugins/bazaar-backend/CHANGELOG.md index 805b1d2068..ef7e924dcb 100644 --- a/plugins/bazaar-backend/CHANGELOG.md +++ b/plugins/bazaar-backend/CHANGELOG.md @@ -1,5 +1,97 @@ # @backstage/plugin-bazaar-backend +## 0.2.0 + +### Minor Changes + +- 8554533546: **BREAKING** The bazaar-backend `createRouter` now requires that the `identityApi` is passed to the router. + + These changes are **required** to `packages/backend/src/plugins/bazaar.ts` + + The user entity ref is now added to the members table and is taken from the requesting user using the `identityApi`. + + ```diff + import { PluginEnvironment } from '../types'; + import { createRouter } from '@backstage/plugin-bazaar-backend'; + import { Router } from 'express'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + logger: env.logger, + config: env.config, + database: env.database, + + identity: env.identity, + }); + } + ``` + +### Patch Changes + +- f7c2855d76: Router now also has endpoint `getLatestProjects` that takes a limit of projects as prop. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-test-utils@0.1.29 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.2.0-next.2 + +### Minor Changes + +- 8554533546: **BREAKING** The bazaar-backend `createRouter` now requires that the `identityApi` is passed to the router. + + These changes are **required** to `packages/backend/src/plugins/bazaar.ts` + + The user entity ref is now added to the members table and is taken from the requesting user using the `identityApi`. + + ```diff + import { PluginEnvironment } from '../types'; + import { createRouter } from '@backstage/plugin-bazaar-backend'; + import { Router } from 'express'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + return await createRouter({ + logger: env.logger, + config: env.config, + database: env.database, + + identity: env.identity, + }); + } + ``` + +### Patch Changes + +- f7c2855d76: Router now also has endpoint `getLatestProjects` that takes a limit of projects as prop. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-test-utils@0.1.29-next.1 + - @backstage/config@1.0.3-next.1 + +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@0.1.29-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + ## 0.1.20 ### Patch Changes diff --git a/plugins/bazaar-backend/README.md b/plugins/bazaar-backend/README.md index b7a08a2fb8..266841b26b 100644 --- a/plugins/bazaar-backend/README.md +++ b/plugins/bazaar-backend/README.md @@ -15,7 +15,7 @@ yarn add --cwd packages/backend @backstage/plugin-bazaar-backend You'll need to add the plugin to the router in your `backend` package. You can do this by creating a file called `packages/backend/src/plugins/bazaar.ts` -```tsx +```typescript import { PluginEnvironment } from '../types'; import { createRouter } from '@backstage/plugin-bazaar-backend'; import { Router } from 'express'; @@ -27,6 +27,7 @@ export default async function createPlugin( logger: env.logger, config: env.config, database: env.database, + identity: env.identity, }); } ``` diff --git a/plugins/bazaar-backend/api-report.md b/plugins/bazaar-backend/api-report.md index d62453d156..1ee889c04a 100644 --- a/plugins/bazaar-backend/api-report.md +++ b/plugins/bazaar-backend/api-report.md @@ -5,6 +5,7 @@ ```ts import { Config } from '@backstage/config'; import express from 'express'; +import { IdentityApi } from '@backstage/plugin-auth-node'; import { Logger } from 'winston'; import { PluginDatabaseManager } from '@backstage/backend-common'; @@ -18,6 +19,8 @@ export interface RouterOptions { // (undocumented) database: PluginDatabaseManager; // (undocumented) + identity: IdentityApi; + // (undocumented) logger: Logger; } diff --git a/plugins/bazaar-backend/migrations/20220817150443_add_member_entity_ref.js b/plugins/bazaar-backend/migrations/20220817150443_add_member_entity_ref.js new file mode 100644 index 0000000000..79b80ef20c --- /dev/null +++ b/plugins/bazaar-backend/migrations/20220817150443_add_member_entity_ref.js @@ -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. + */ + +exports.up = async function up(knex) { + await knex.schema.alterTable('members', table => { + table.string('user_ref').nullable(); + }); +}; + +exports.down = async function down(knex) { + return knex.schema.table('members', table => { + table.dropColumn('user_ref'); + }); +}; diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json index 5f11c694f8..6ac340184e 100644 --- a/plugins/bazaar-backend/package.json +++ b/plugins/bazaar-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar-backend", - "version": "0.1.20", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,6 +25,8 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", + "@backstage/plugin-auth-node": "workspace:^", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", diff --git a/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts b/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts index e1a722016b..371d20ff13 100644 --- a/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts +++ b/plugins/bazaar-backend/src/service/DatabaseHandler.test.ts @@ -75,6 +75,13 @@ describe('DatabaseHandler', () => { responsible: bazaarProject.responsible, }); + // Add a member to the project + await knex('members').insert({ + item_id: 1, + user_ref: 'user:default/thehulk', + user_id: 'Bruce Banner', + }); + const res = await dbHandler.getMetadataByRef('ref1'); expect(res).toHaveLength(1); @@ -85,6 +92,9 @@ describe('DatabaseHandler', () => { expect(res[0].end_date).toEqual(null); expect(res[0].size).toEqual('small'); expect(res[0].responsible).toEqual('r'); + expect( + res[0].members_count === '1' || res[0].members_count === 1, + ).toBeTruthy(); }, 60_000, ); diff --git a/plugins/bazaar-backend/src/service/DatabaseHandler.ts b/plugins/bazaar-backend/src/service/DatabaseHandler.ts index d8d975b7ed..8fbe985a10 100644 --- a/plugins/bazaar-backend/src/service/DatabaseHandler.ts +++ b/plugins/bazaar-backend/src/service/DatabaseHandler.ts @@ -67,11 +67,17 @@ export class DatabaseHandler { return await this.client.select('*').from('members').where({ item_id: id }); } - async addMember(id: number, userId: string, picture?: string) { + async addMember( + id: number, + userId: string, + userRef?: string, + picture?: string, + ) { await this.client .insert({ item_id: id, user_id: userId, + user_ref: userRef, picture: picture, }) .into('members'); @@ -169,14 +175,22 @@ export class DatabaseHandler { return await this.client('metadata').where({ id: id }).del(); } - async getProjects() { + async getProjects(limit?: number, order?: string) { const coalesce = this.client.raw( 'coalesce(count(members.item_id), 0) as members_count', ); - - return await this.client('metadata') + let get = this.client('metadata') .select([...this.columns, coalesce]) - .groupBy(this.columns) - .leftJoin('members', 'metadata.id', '=', 'members.item_id'); + .groupBy(this.columns); + if (limit) { + get = get.limit(limit); + } + if (order === 'latest') { + get = get.orderByRaw('id desc'); + } + if (order === 'random') { + get = get.orderByRaw('RANDOM()'); + } + return await get.leftJoin('members', 'metadata.id', '=', 'members.item_id'); } } diff --git a/plugins/bazaar-backend/src/service/router.ts b/plugins/bazaar-backend/src/service/router.ts index a5248c2949..f902d0e8cc 100644 --- a/plugins/bazaar-backend/src/service/router.ts +++ b/plugins/bazaar-backend/src/service/router.ts @@ -19,6 +19,7 @@ import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; import { Config } from '@backstage/config'; +import { IdentityApi } from '@backstage/plugin-auth-node'; import { DatabaseHandler } from './DatabaseHandler'; /** @public */ @@ -26,13 +27,14 @@ export interface RouterOptions { logger: Logger; database: PluginDatabaseManager; config: Config; + identity: IdentityApi; } /** @public */ export async function createRouter( options: RouterOptions, ): Promise { - const { logger, database } = options; + const { logger, database, identity } = options; const dbHandler = await DatabaseHandler.create({ database }); @@ -53,7 +55,14 @@ export async function createRouter( router.put('/projects/:id/member/:userId', async (request, response) => { const { id, userId } = request.params; - await dbHandler.addMember(parseInt(id, 10), userId, request.body?.picture); + const user = await identity.getIdentity({ request: request }); + + await dbHandler.addMember( + parseInt(id, 10), + userId, + user?.identity.userEntityRef, + request.body?.picture, + ); response.json({ status: 'ok' }); }); @@ -83,8 +92,14 @@ export async function createRouter( response.json({ status: 'ok', data: data }); }); - router.get('/projects', async (_, response) => { - const data = await dbHandler.getProjects(); + router.get('/projects', async (request, response) => { + const limit = request.query.limit?.toString(); + const order = request.query.order?.toString(); + + const data = await dbHandler.getProjects( + limit ? parseInt(limit, 10) : undefined, + order, + ); response.json({ status: 'ok', data: data }); }); diff --git a/plugins/bazaar-backend/src/service/standaloneServer.ts b/plugins/bazaar-backend/src/service/standaloneServer.ts index e8a49fbf5d..a50e350f39 100644 --- a/plugins/bazaar-backend/src/service/standaloneServer.ts +++ b/plugins/bazaar-backend/src/service/standaloneServer.ts @@ -19,6 +19,7 @@ import { loadBackendConfig, useHotMemoize, } from '@backstage/backend-common'; +import { IdentityApi } from '@backstage/plugin-auth-node'; import { Server } from 'http'; import { Logger } from 'winston'; import { createRouter } from './router'; @@ -54,6 +55,7 @@ export async function startStandaloneServer( logger, database: { getClient: async () => db }, config: config, + identity: {} as IdentityApi, }); let service = createServiceBuilder(module) diff --git a/plugins/bazaar/CHANGELOG.md b/plugins/bazaar/CHANGELOG.md index f936900cd8..82269fc0ba 100644 --- a/plugins/bazaar/CHANGELOG.md +++ b/plugins/bazaar/CHANGELOG.md @@ -1,5 +1,63 @@ # @backstage/plugin-bazaar +## 0.1.25 + +### Patch Changes + +- f7c2855d76: Added a `Overview Card` for either latest or random projects. Changed `ProjectPreview.tsx` so it take `gridSize` and `useTablePagination` as props. +- c0352bbc69: Link to the user catalog entity of a member +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/cli@0.20.0 + - @backstage/catalog-client@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + +## 0.1.25-next.2 + +### Patch Changes + +- f7c2855d76: Added a `Overview Card` for either latest or random projects. Changed `ProjectPreview.tsx` so it take `gridSize` and `useTablePagination` as props. +- c0352bbc69: Link to the user catalog entity of a member +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.1.25-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-catalog@1.6.0-next.1 + - @backstage/cli@0.20.0-next.1 + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + +## 0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/cli@0.20.0-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-catalog@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + ## 0.1.24 ### Patch Changes diff --git a/plugins/bazaar/README.md b/plugins/bazaar/README.md index eeebf6eb19..6168b0c4ba 100644 --- a/plugins/bazaar/README.md +++ b/plugins/bazaar/README.md @@ -64,6 +64,30 @@ const overviewContent = ( {/* ...other entity-cards */} ``` +Add a **Bazaar overview card** to the homepage that displays either the latest projects or random projects. In `packages/app/src/components/home/HomePage.tsx` add: + +```diff ++ import { BazaarOverviewCard } from '@backstage/plugin-bazaar'; + +export const homePage = ( + + + + + ++ ++ ++ + ++ ++ ++ + + {/* ...other homepage items */} +``` + +Specify how many projects you want through the "limit" props. In the example above 4 cards is specified. + ## How does the Bazaar work? ### Layout @@ -72,6 +96,10 @@ The latest modified Bazaar projects are displayed in the Bazaar landing page, lo ![home](media/layout.png) +The "BazaarOverviewCard" can be displayed in Backstage homepage. + +![home](media/overviewCard.png) + ### Workflow To add a project to the bazaar, simply click on the `add-project` button and fill in the form. diff --git a/plugins/bazaar/api-report.md b/plugins/bazaar/api-report.md index c6afde73f2..2e6ebe6746 100644 --- a/plugins/bazaar/api-report.md +++ b/plugins/bazaar/api-report.md @@ -8,6 +8,17 @@ import { BackstagePlugin } from '@backstage/core-plugin-api'; import { RouteRef } from '@backstage/core-plugin-api'; +// @public (undocumented) +export const BazaarOverviewCard: ( + props: BazaarOverviewCardProps, +) => JSX.Element; + +// @public (undocumented) +export type BazaarOverviewCardProps = { + order: 'latest' | 'random'; + limit: number; +}; + // @public (undocumented) export const BazaarPage: () => JSX.Element; diff --git a/plugins/bazaar/media/overviewCard.png b/plugins/bazaar/media/overviewCard.png new file mode 100644 index 0000000000..676764f6e4 Binary files /dev/null and b/plugins/bazaar/media/overviewCard.png differ diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json index 919f8a7df7..522d84aeec 100644 --- a/plugins/bazaar/package.json +++ b/plugins/bazaar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar", - "version": "0.1.24", + "version": "0.1.25", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -27,6 +27,7 @@ "@backstage/cli": "workspace:^", "@backstage/core-components": "workspace:^", "@backstage/core-plugin-api": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/plugin-catalog": "workspace:^", "@backstage/plugin-catalog-react": "workspace:^", "@date-io/luxon": "1.x", diff --git a/plugins/bazaar/src/api.ts b/plugins/bazaar/src/api.ts index d2486f74dc..33455e69fc 100644 --- a/plugins/bazaar/src/api.ts +++ b/plugins/bazaar/src/api.ts @@ -20,6 +20,7 @@ import { FetchApi, IdentityApi, } from '@backstage/core-plugin-api'; +import { ResponseError } from '@backstage/errors'; export const bazaarApiRef = createApiRef({ id: 'bazaar', @@ -40,7 +41,7 @@ export interface BazaarApi { addMember(id: number, userId: string): Promise; - getProjects(): Promise; + getProjects(limit?: number, order?: string): Promise; deleteProject(id: number): Promise; } @@ -148,12 +149,20 @@ export class BazaarClient implements BazaarApi { ); } - async getProjects(): Promise { + async getProjects(limit?: number, order?: string): Promise { const baseUrl = await this.discoveryApi.getBaseUrl('bazaar'); + const params = { + ...(limit ? { limit: limit.toString() } : {}), + ...(order ? { order } : {}), + }; + const query = new URLSearchParams(params); + const url = `projects?${query.toString()}`; - return await this.fetchApi - .fetch(`${baseUrl}/projects`) - .then(resp => resp.json()); + const data = await this.fetchApi.fetch(`${baseUrl}/${url}`); + if (!data.ok) { + throw await ResponseError.fromResponse(data); + } + return data.json(); } async deleteProject(id: number): Promise { diff --git a/plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx b/plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx new file mode 100644 index 0000000000..83358c0a05 --- /dev/null +++ b/plugins/bazaar/src/components/BazaarOverviewCard/BazaarOverviewCard.tsx @@ -0,0 +1,141 @@ +/* + * 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, { useEffect, useState } from 'react'; +import { ProjectPreview } from '../ProjectPreview/ProjectPreview'; +import useAsyncFn from 'react-use/lib/useAsyncFn'; +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; +import { useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { catalogApiRef } from '@backstage/plugin-catalog-react'; +import type { BazaarProject } from '../../types'; +import { bazaarApiRef } from '../../api'; +import { fetchCatalogItems } from '../../util/fetchMethods'; +import { parseBazaarProject } from '../../util/parseMethods'; +import { ErrorPanel, InfoCard } from '@backstage/core-components'; +import { bazaarPlugin } from '../../plugin'; + +/** @public */ +export type BazaarOverviewCardProps = { + order: 'latest' | 'random'; + limit: number; +}; + +const getUnlinkedCatalogEntities = ( + bazaarProjects: BazaarProject[], + catalogEntities: Entity[], +) => { + const bazaarProjectRefs = bazaarProjects.map( + (project: BazaarProject) => project.entityRef, + ); + + return catalogEntities.filter((entity: Entity) => { + return !bazaarProjectRefs?.includes(stringifyEntityRef(entity)); + }); +}; + +/** @public */ +export const BazaarOverviewCard = (props: BazaarOverviewCardProps) => { + const { order, limit } = props; + const bazaarApi = useApi(bazaarApiRef); + const catalogApi = useApi(catalogApiRef); + const root = useRouteRef(bazaarPlugin.routes.root); + + const bazaarLink = { + title: 'Go to Bazaar', + link: root.toString(), + }; + + const [unlinkedCatalogEntities, setUnlinkedCatalogEntities] = + useState(); + + const [catalogEntities, fetchCatalogEntities] = useAsyncFn(async () => { + return await fetchCatalogItems(catalogApi); + }); + + const [bazaarProjects, fetchBazaarProjects] = useAsyncFn(async () => { + const response = await bazaarApi.getProjects(limit, order); + return response.data.map(parseBazaarProject) as BazaarProject[]; + }); + + const catalogEntityRefs = catalogEntities.value?.map((project: Entity) => + stringifyEntityRef(project), + ); + + useEffect(() => { + const filterBrokenLinks = () => { + if (catalogEntityRefs) { + bazaarProjects.value?.forEach(async (project: BazaarProject) => { + if (project.entityRef) { + if (!catalogEntityRefs?.includes(project.entityRef)) { + await bazaarApi.updateProject({ + ...project, + entityRef: null, + }); + } + } + }); + } + }; + filterBrokenLinks(); + }, [ + bazaarApi, + bazaarProjects.value, + catalogEntityRefs, + catalogEntities.value, + ]); + + useEffect(() => { + fetchCatalogEntities(); + fetchBazaarProjects(); + }, [fetchBazaarProjects, fetchCatalogEntities]); + + useEffect(() => { + const unlinkedCEntities = getUnlinkedCatalogEntities( + bazaarProjects.value || [], + catalogEntities.value || [], + ); + + if (unlinkedCEntities) { + setUnlinkedCatalogEntities(unlinkedCEntities); + } + }, [bazaarProjects, catalogEntities]); + + if (catalogEntities.error) { + return ; + } + + if (bazaarProjects.error) { + return ; + } + + return ( + + + + ); +}; diff --git a/plugins/bazaar/src/components/BazaarOverviewCard/index.ts b/plugins/bazaar/src/components/BazaarOverviewCard/index.ts new file mode 100644 index 0000000000..06ea623e1b --- /dev/null +++ b/plugins/bazaar/src/components/BazaarOverviewCard/index.ts @@ -0,0 +1,18 @@ +/* + * 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 { BazaarOverviewCard } from './BazaarOverviewCard'; +export type { BazaarOverviewCardProps } from './BazaarOverviewCard'; diff --git a/plugins/bazaar/src/components/CardContentFields/CardContentFields.tsx b/plugins/bazaar/src/components/CardContentFields/CardContentFields.tsx index 6c6ce1bb3e..641ae97f95 100644 --- a/plugins/bazaar/src/components/CardContentFields/CardContentFields.tsx +++ b/plugins/bazaar/src/components/CardContentFields/CardContentFields.tsx @@ -23,8 +23,11 @@ import { Typography, GridSize, } from '@material-ui/core'; +import { parseEntityRef } from '@backstage/catalog-model'; import { Avatar, Link } from '@backstage/core-components'; +import { useRouteRef } from '@backstage/core-plugin-api'; import { AboutField } from '@backstage/plugin-catalog'; +import { entityRouteRef } from '@backstage/plugin-catalog-react'; import { StatusTag } from '../StatusTag'; import { Member, BazaarProject } from '../../types'; @@ -49,6 +52,7 @@ export const CardContentFields = ({ membersSize, }: Props) => { const classes = useStyles(); + const catalogEntityRoute = useRouteRef(entityRouteRef); return (
@@ -111,8 +115,14 @@ export const CardContentFields = ({ /> {member?.userId} diff --git a/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx b/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx index 3aeba305a9..dace9b8185 100644 --- a/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx +++ b/plugins/bazaar/src/components/ProjectCard/ProjectCard.tsx @@ -30,6 +30,13 @@ import { DateTime } from 'luxon'; import { HomePageBazaarInfoCard } from '../HomePageBazaarInfoCard'; import { Entity } from '@backstage/catalog-model'; +type Props = { + project: BazaarProject; + fetchBazaarProjects: () => Promise; + catalogEntities: Entity[]; + fullHeight?: boolean; +}; + const useStyles = makeStyles({ statusTag: { display: 'inline-block', @@ -47,7 +54,7 @@ const useStyles = makeStyles({ float: 'right', }, content: { - height: '13rem', + overflow: 'scroll', }, header: { whiteSpace: 'nowrap', @@ -56,16 +63,11 @@ const useStyles = makeStyles({ }, }); -type Props = { - project: BazaarProject; - fetchBazaarProjects: () => Promise; - catalogEntities: Entity[]; -}; - export const ProjectCard = ({ project, fetchBazaarProjects, catalogEntities, + fullHeight, }: Props) => { const classes = useStyles(); const [openCard, setOpenCard] = useState(false); @@ -101,7 +103,10 @@ export const ProjectCard = ({ base: DateTime.now(), })}`} /> - + {Number(membersCount) === Number(1) diff --git a/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx b/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx index 435d80408d..77c16ef306 100644 --- a/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx +++ b/plugins/bazaar/src/components/ProjectPreview/ProjectPreview.tsx @@ -25,6 +25,9 @@ type Props = { bazaarProjects: BazaarProject[]; fetchBazaarProjects: () => Promise; catalogEntities: Entity[]; + useTablePagination?: boolean; + fullHeight?: boolean; + fixedWidth?: boolean; }; const useStyles = makeStyles({ @@ -51,6 +54,9 @@ export const ProjectPreview = ({ bazaarProjects, fetchBazaarProjects, catalogEntities, + useTablePagination = true, + fullHeight = true, + fixedWidth = false, }: Props) => { const classes = useStyles(); const [page, setPage] = useState(1); @@ -80,31 +86,39 @@ export const ProjectPreview = ({ .slice((page - 1) * rows, rows * page) .map((bazaarProject: BazaarProject, i: number) => { return ( - + ); })} - = bazaarProjects.length, - }} - /> + {useTablePagination && ( + = bazaarProjects.length, + }} + /> + )} ); }; diff --git a/plugins/bazaar/src/index.ts b/plugins/bazaar/src/index.ts index 895469c031..0014c66b6d 100644 --- a/plugins/bazaar/src/index.ts +++ b/plugins/bazaar/src/index.ts @@ -15,5 +15,7 @@ */ export { bazaarPlugin, BazaarPage } from './plugin'; +export { BazaarOverviewCard } from './components/BazaarOverviewCard'; +export type { BazaarOverviewCardProps } from './components/BazaarOverviewCard'; export { EntityBazaarInfoCard } from './components/EntityBazaarInfoCard'; export { SortView } from './components/SortView'; diff --git a/plugins/bazaar/src/types.ts b/plugins/bazaar/src/types.ts index ec3996d1e2..e698f21264 100644 --- a/plugins/bazaar/src/types.ts +++ b/plugins/bazaar/src/types.ts @@ -19,6 +19,7 @@ export type Member = { userId: string; joinDate?: string; picture?: string; + userRef?: string; }; export type Status = 'ongoing' | 'proposed'; diff --git a/plugins/bazaar/src/util/parseMethods.ts b/plugins/bazaar/src/util/parseMethods.ts index 1b4ebfd308..b32c5d42d3 100644 --- a/plugins/bazaar/src/util/parseMethods.ts +++ b/plugins/bazaar/src/util/parseMethods.ts @@ -37,6 +37,7 @@ export const parseMember = (member: any): Member => { return { itemId: member.item_id, userId: member.user_id, + userRef: member.user_ref, joinDate: member.join_date, picture: member.picture, } as Member; diff --git a/plugins/bitbucket-cloud-common/CHANGELOG.md b/plugins/bitbucket-cloud-common/CHANGELOG.md index 0990b34b42..c9befa2959 100644 --- a/plugins/bitbucket-cloud-common/CHANGELOG.md +++ b/plugins/bitbucket-cloud-common/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-bitbucket-cloud-common +## 0.2.0 + +### Minor Changes + +- ad74723fbf: Update Bitbucket Cloud models to latest OAS version. + + The latest specification contained some BREAKING CHANGES + due to removed fields. + + All of these fields are not used at other plugins, though. + Therefore, this change has no impact on other modules here. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.3.2 + +## 0.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.3.2-next.2 + +## 0.2.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.3.2-next.1 + +## 0.2.0-next.0 + +### Minor Changes + +- ad74723fbf: Update Bitbucket Cloud models to latest OAS version. + + The latest specification contained some BREAKING CHANGES + due to removed fields. + + All of these fields are not used at other plugins, though. + Therefore, this change has no impact on other modules here. + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.3.2-next.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/bitbucket-cloud-common/api-report.md b/plugins/bitbucket-cloud-common/api-report.md index 367a47b9e3..d66b8b9a70 100644 --- a/plugins/bitbucket-cloud-common/api-report.md +++ b/plugins/bitbucket-cloud-common/api-report.md @@ -33,37 +33,22 @@ export type FilterAndSortOptions = { // @public (undocumented) export namespace Models { export interface Account extends ModelObject { - account_status?: string; // (undocumented) created_on?: string; // (undocumented) display_name?: string; // (undocumented) - has_2fa_enabled?: boolean; - // (undocumented) links?: AccountLinks; - nickname?: string; // (undocumented) username?: string; // (undocumented) uuid?: string; - // (undocumented) - website?: string; } - // (undocumented) export interface AccountLinks { + // (undocumented) + [key: string]: unknown; // (undocumented) avatar?: Link; - // (undocumented) - followers?: Link; - // (undocumented) - following?: Link; - // (undocumented) - html?: Link; - // (undocumented) - repositories?: Link; - // (undocumented) - self?: Link; } export interface Author extends ModelObject { raw?: string; @@ -176,7 +161,7 @@ export namespace Models { // (undocumented) state?: ParticipantStateEnum; // (undocumented) - user?: User; + user?: Account; } const // (undocumented) ParticipantRoleEnum: { @@ -339,11 +324,21 @@ export namespace Models { // (undocumented) readonly text?: string; } - export interface Team extends Account {} - export interface User extends Account { - account_id?: string; + export interface Team extends Account { // (undocumented) - is_staff?: boolean; + links?: TeamLinks; + } + export interface TeamLinks extends AccountLinks { + // (undocumented) + html?: Link; + // (undocumented) + members?: Link; + // (undocumented) + projects?: Link; + // (undocumented) + repositories?: Link; + // (undocumented) + self?: Link; } } diff --git a/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json b/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json index 01cd910171..843d009265 100644 --- a/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json +++ b/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json @@ -1,19 +1,22 @@ { "openapi": "3.0.0", "info": { - "termsOfService": "https://www.atlassian.com/legal/customer-agreement", - "version": "2.0", "title": "Bitbucket API", "description": "Code against the Bitbucket API to automate simple tasks, embed Bitbucket data into your own site, build mobile or desktop apps, or even add custom UI add-ons into Bitbucket itself using the Connect framework.", + "version": "2.0", + "termsOfService": "https://www.atlassian.com/legal/customer-agreement", "contact": { - "url": "https://support.atlassian.com/bitbucket-cloud/", "name": "Bitbucket Support", + "url": "https://support.atlassian.com/bitbucket-cloud/", "email": "support@bitbucket.org" } }, "paths": { "/addon": { "delete": { + "tags": ["Addon"], + "description": "Deletes the application for the user.\n\nThis endpoint is intended to be used by Bitbucket Connect apps\nand only supports JWT authentication -- that is how Bitbucket\nidentifies the particular installation of the app. Developers\nwith applications registered in the \"Develop Apps\" section\nof Bitbucket Marketplace need not use this endpoint as\nupdates for those applications can be sent out via the\nUI of that section.\n\n```\n$ curl -X DELETE https://api.bitbucket.org/2.0/addon \\\n -H \"Authorization: JWT \"\n```", + "summary": "Delete an app", "responses": { "204": { "description": "Request has succeeded. The application has been deleted for the user." @@ -39,8 +42,6 @@ } } }, - "tags": ["Addon"], - "summary": "Delete an app", "security": [ { "oauth2": [] @@ -51,10 +52,12 @@ { "api_key": [] } - ], - "description": "Deletes the application for the user.\n\nThis endpoint is intended to be used by Bitbucket Connect apps\nand only supports JWT authentication -- that is how Bitbucket\nidentifies the particular installation of the app. Developers\nwith applications registered in the \"Develop Apps\" section\nof Bitbucket Marketplace need not use this endpoint as\nupdates for those applications can be sent out via the\nUI of that section.\n\n```\n$ curl -X DELETE https://api.bitbucket.org/2.0/addon \\\n -H \"Authorization: JWT \"\n```" + ] }, "put": { + "tags": ["Addon"], + "description": "Updates the application installation for the user.\n\nThis endpoint is intended to be used by Bitbucket Connect apps\nand only supports JWT authentication -- that is how Bitbucket\nidentifies the particular installation of the app. Developers\nwith applications registered in the \"Develop Apps\" section\nof Bitbucket need not use this endpoint as updates for those\napplications can be sent out via the UI of that section.\n\nPassing an empty body will update the installation using the\nexisting descriptor URL.\n\n```\n$ curl -X PUT https://api.bitbucket.org/2.0/addon \\\n -H \"Authorization: JWT \" \\\n --header \"Content-Type: application/json\" \\\n --data '{}'\n```\n\nThe new `descriptor` for the installation can be also provided\nin the body directly.\n\n```\n$ curl -X PUT https://api.bitbucket.org/2.0/addon \\\n -H \"Authorization: JWT \" \\\n --header \"Content-Type: application/json\" \\\n --data '{\"descriptor\": $NEW_DESCRIPTOR}'\n```\n\nIn both these modes the URL of the descriptor cannot be changed. To\nchange the descriptor location and upgrade an installation\nthe request must be made exclusively with a `descriptor_url`.\n\n ```\n$ curl -X PUT https://api.bitbucket.org/2.0/addon \\\n -H \"Authorization: JWT \" \\\n --header \"Content-Type: application/json\" \\\n --data '{\"descriptor_url\": $NEW_URL}'\n```\n\nThe `descriptor_url` must exactly match the marketplace registration\nthat Atlassian has for the application. Contact your Atlassian\ndeveloper advocate to update this registration. Once the registration\nhas been updated you may call this resource for each installation.\n\nNote that the scopes of the application cannot be increased\nin the new descriptor nor reduced to none.", + "summary": "Update an installed app", "responses": { "204": { "description": "Request has succeeded. The installation has been updated to the new descriptor." @@ -90,8 +93,6 @@ } } }, - "tags": ["Addon"], - "summary": "Update an installed app", "security": [ { "oauth2": [] @@ -102,13 +103,15 @@ { "api_key": [] } - ], - "description": "Updates the application installation for the user.\n\nThis endpoint is intended to be used by Bitbucket Connect apps\nand only supports JWT authentication -- that is how Bitbucket\nidentifies the particular installation of the app. Developers\nwith applications registered in the \"Develop Apps\" section\nof Bitbucket need not use this endpoint as updates for those\napplications can be sent out via the UI of that section.\n\nPassing an empty body will update the installation using the\nexisting descriptor URL.\n\n```\n$ curl -X PUT https://api.bitbucket.org/2.0/addon \\\n -H \"Authorization: JWT \" \\\n --header \"Content-Type: application/json\" \\\n --data '{}'\n```\n\nThe new `descriptor` for the installation can be also provided\nin the body directly.\n\n```\n$ curl -X PUT https://api.bitbucket.org/2.0/addon \\\n -H \"Authorization: JWT \" \\\n --header \"Content-Type: application/json\" \\\n --data '{\"descriptor\": $NEW_DESCRIPTOR}'\n```\n\nIn both these modes the URL of the descriptor cannot be changed. To\nchange the descriptor location and upgrade an installation\nthe request must be made exclusively with a `descriptor_url`.\n\n ```\n$ curl -X PUT https://api.bitbucket.org/2.0/addon \\\n -H \"Authorization: JWT \" \\\n --header \"Content-Type: application/json\" \\\n --data '{\"descriptor_url\": $NEW_URL}'\n```\n\nThe `descriptor_url` must exactly match the marketplace registration\nthat Atlassian has for the application. Contact your Atlassian\ndeveloper advocate to update this registration. Once the registration\nhas been updated you may call this resource for each installation.\n\nNote that the scopes of the application cannot be increased\nin the new descriptor nor reduced to none." + ] }, "parameters": [] }, "/addon/linkers": { "get": { + "tags": ["Addon"], + "description": "Gets a list of all [linkers](/cloud/bitbucket/modules/linker/)\nfor the authenticated application.", + "summary": "List linkers for an app", "responses": { "200": { "description": "Successful." @@ -124,8 +127,6 @@ } } }, - "tags": ["Addon"], - "summary": "List linkers for an app", "security": [ { "oauth2": [] @@ -136,13 +137,15 @@ { "api_key": [] } - ], - "description": "Gets a list of all [linkers](/cloud/bitbucket/modules/linker/)\nfor the authenticated application." + ] }, "parameters": [] }, "/addon/linkers/{linker_key}": { "get": { + "tags": ["Addon"], + "description": "Gets a [linker](/cloud/bitbucket/modules/linker/) specified by `linker_key`\nfor the authenticated application.", + "summary": "Get a linker for an app", "responses": { "200": { "description": "Successful." @@ -168,8 +171,6 @@ } } }, - "tags": ["Addon"], - "summary": "Get a linker for an app", "security": [ { "oauth2": [] @@ -180,8 +181,7 @@ { "api_key": [] } - ], - "description": "Gets a [linker](/cloud/bitbucket/modules/linker/) specified by `linker_key`\nfor the authenticated application." + ] }, "parameters": [ { @@ -197,6 +197,9 @@ }, "/addon/linkers/{linker_key}/values": { "delete": { + "tags": ["Addon"], + "description": "Delete all [linker](/cloud/bitbucket/modules/linker/) values for the\nspecified linker of the authenticated application.", + "summary": "Delete all linker values", "responses": { "204": { "description": "Successfully deleted the linker values." @@ -222,8 +225,6 @@ } } }, - "tags": ["Addon"], - "summary": "Delete all linker values", "security": [ { "oauth2": [] @@ -234,10 +235,12 @@ { "api_key": [] } - ], - "description": "Delete all [linker](/cloud/bitbucket/modules/linker/) values for the\nspecified linker of the authenticated application." + ] }, "get": { + "tags": ["Addon"], + "description": "Gets a list of all [linker](/cloud/bitbucket/modules/linker/) values for the\nspecified linker of the authenticated application.\n\nA linker value lets applications supply values to modify its regular expression.\n\nThe base regular expression must use a Bitbucket-specific match group `(?K)`\nwhich will be translated to `([\\w\\-]+)`. A value must match this pattern.\n\n[Read more about linker values](/cloud/bitbucket/modules/linker/#usingthebitbucketapitosupplyvalues)", + "summary": "List linker values for a linker", "responses": { "200": { "description": "Successful." @@ -263,8 +266,6 @@ } } }, - "tags": ["Addon"], - "summary": "List linker values for a linker", "security": [ { "oauth2": [] @@ -275,10 +276,12 @@ { "api_key": [] } - ], - "description": "Gets a list of all [linker](/cloud/bitbucket/modules/linker/) values for the\nspecified linker of the authenticated application.\n\nA linker value lets applications supply values to modify its regular expression.\n\nThe base regular expression must use a Bitbucket-specific match group `(?K)`\nwhich will be translated to `([\\w\\-]+)`. A value must match this pattern.\n\n[Read more about linker values](/cloud/bitbucket/modules/linker/#usingthebitbucketapitosupplyvalues)" + ] }, "post": { + "tags": ["Addon"], + "description": "Creates a [linker](/cloud/bitbucket/modules/linker/) value for the specified\nlinker of authenticated application.\n\nA linker value lets applications supply values to modify its regular expression.\n\nThe base regular expression must use a Bitbucket-specific match group `(?K)`\nwhich will be translated to `([\\w\\-]+)`. A value must match this pattern.\n\n[Read more about linker values](/cloud/bitbucket/modules/linker/#usingthebitbucketapitosupplyvalues)", + "summary": "Create a linker value", "responses": { "201": { "description": "Successfully created the linker value." @@ -314,8 +317,6 @@ } } }, - "tags": ["Addon"], - "summary": "Create a linker value", "security": [ { "oauth2": [] @@ -326,10 +327,12 @@ { "api_key": [] } - ], - "description": "Creates a [linker](/cloud/bitbucket/modules/linker/) value for the specified\nlinker of authenticated application.\n\nA linker value lets applications supply values to modify its regular expression.\n\nThe base regular expression must use a Bitbucket-specific match group `(?K)`\nwhich will be translated to `([\\w\\-]+)`. A value must match this pattern.\n\n[Read more about linker values](/cloud/bitbucket/modules/linker/#usingthebitbucketapitosupplyvalues)" + ] }, "put": { + "tags": ["Addon"], + "description": "Bulk update [linker](/cloud/bitbucket/modules/linker/) values for the specified\nlinker of the authenticated application.\n\nA linker value lets applications supply values to modify its regular expression.\n\nThe base regular expression must use a Bitbucket-specific match group `(?K)`\nwhich will be translated to `([\\w\\-]+)`. A value must match this pattern.\n\n[Read more about linker values](/cloud/bitbucket/modules/linker/#usingthebitbucketapitosupplyvalues)", + "summary": "Update a linker value", "responses": { "204": { "description": "Successfully updated the linker values." @@ -365,8 +368,6 @@ } } }, - "tags": ["Addon"], - "summary": "Update a linker value", "security": [ { "oauth2": [] @@ -377,8 +378,7 @@ { "api_key": [] } - ], - "description": "Bulk update [linker](/cloud/bitbucket/modules/linker/) values for the specified\nlinker of the authenticated application.\n\nA linker value lets applications supply values to modify its regular expression.\n\nThe base regular expression must use a Bitbucket-specific match group `(?K)`\nwhich will be translated to `([\\w\\-]+)`. A value must match this pattern.\n\n[Read more about linker values](/cloud/bitbucket/modules/linker/#usingthebitbucketapitosupplyvalues)" + ] }, "parameters": [ { @@ -394,6 +394,9 @@ }, "/addon/linkers/{linker_key}/values/{value_id}": { "delete": { + "tags": ["Addon"], + "description": "Delete a single [linker](/cloud/bitbucket/modules/linker/) value\nof the authenticated application.", + "summary": "Delete a linker value", "responses": { "204": { "description": "Successfully deleted the linker value." @@ -419,8 +422,6 @@ } } }, - "tags": ["Addon"], - "summary": "Delete a linker value", "security": [ { "oauth2": [] @@ -431,10 +432,12 @@ { "api_key": [] } - ], - "description": "Delete a single [linker](/cloud/bitbucket/modules/linker/) value\nof the authenticated application." + ] }, "get": { + "tags": ["Addon"], + "description": "Get a single [linker](/cloud/bitbucket/modules/linker/) value\nof the authenticated application.", + "summary": "Get a linker value", "responses": { "200": { "description": "Successful." @@ -460,8 +463,6 @@ } } }, - "tags": ["Addon"], - "summary": "Get a linker value", "security": [ { "oauth2": [] @@ -472,8 +473,7 @@ { "api_key": [] } - ], - "description": "Get a single [linker](/cloud/bitbucket/modules/linker/) value\nof the authenticated application." + ] }, "parameters": [ { @@ -498,6 +498,9 @@ }, "/hook_events": { "get": { + "tags": ["Webhooks"], + "description": "Returns the webhook resource or subject types on which webhooks can\nbe registered.\n\nEach resource/subject type contains an `events` link that returns the\npaginated list of specific events each individual subject type can\nemit.\n\nThis endpoint is publicly accessible and does not require\nauthentication or scopes.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/hook_events\n\n{\n \"repository\": {\n \"links\": {\n \"events\": {\n \"href\": \"https://api.bitbucket.org/2.0/hook_events/repository\"\n }\n }\n },\n \"workspace\": {\n \"links\": {\n \"events\": {\n \"href\": \"https://api.bitbucket.org/2.0/hook_events/workspace\"\n }\n }\n }\n}\n```", + "summary": "Get a webhook resource", "responses": { "200": { "description": "A mapping of resource/subject types pointing to their individual event types.", @@ -510,8 +513,6 @@ } } }, - "tags": ["Webhooks"], - "summary": "Get a webhook resource", "security": [ { "oauth2": [] @@ -522,13 +523,15 @@ { "api_key": [] } - ], - "description": "Returns the webhook resource or subject types on which webhooks can\nbe registered.\n\nEach resource/subject type contains an `events` link that returns the\npaginated list of specific events each individual subject type can\nemit.\n\nThis endpoint is publicly accessible and does not require\nauthentication or scopes.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/hook_events\n\n{\n \"repository\": {\n \"links\": {\n \"events\": {\n \"href\": \"https://api.bitbucket.org/2.0/hook_events/repository\"\n }\n }\n },\n \"team\": {\n \"links\": {\n \"events\": {\n \"href\": \"https://api.bitbucket.org/2.0/hook_events/team\"\n }\n }\n },\n \"user\": {\n \"links\": {\n \"events\": {\n \"href\": \"https://api.bitbucket.org/2.0/hook_events/user\"\n }\n }\n }\n}\n```" + ] }, "parameters": [] }, "/hook_events/{subject_type}": { "get": { + "tags": ["Webhooks"], + "description": "Returns a paginated list of all valid webhook events for the\nspecified entity.\n**The team and user webhooks are deprecated, and you should use workspace instead.\nFor more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**\n\nThis is public data that does not require any scopes or authentication.\n\nExample:\n\nNOTE: The following example is a truncated response object for the `workspace` `subject_type`.\nWe return the same structure for the other `subject_type` objects.\n\n```\n$ curl https://api.bitbucket.org/2.0/hook_events/workspace\n{\n \"page\": 1,\n \"pagelen\": 30,\n \"size\": 21,\n \"values\": [\n {\n \"category\": \"Repository\",\n \"description\": \"Whenever a repository push occurs\",\n \"event\": \"repo:push\",\n \"label\": \"Push\"\n },\n {\n \"category\": \"Repository\",\n \"description\": \"Whenever a repository fork occurs\",\n \"event\": \"repo:fork\",\n \"label\": \"Fork\"\n },\n {\n \"category\": \"Repository\",\n \"description\": \"Whenever a repository import occurs\",\n \"event\": \"repo:imported\",\n \"label\": \"Import\"\n },\n ...\n {\n \"category\":\"Pull Request\",\n \"label\":\"Approved\",\n \"description\":\"When someone has approved a pull request\",\n \"event\":\"pullrequest:approved\"\n },\n ]\n}\n```", + "summary": "List subscribable webhook types", "responses": { "200": { "description": "A paginated list of webhook types available to subscribe on.", @@ -551,8 +554,6 @@ } } }, - "tags": ["Webhooks"], - "summary": "List subscribable webhook types", "security": [ { "oauth2": [] @@ -563,8 +564,7 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of all valid webhook events for the\nspecified entity.\n**The team and user webhooks are deprecated, and you should use workspace instead.\nFor more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**\n\nThis is public data that does not require any scopes or authentication.\n\nExample:\n\nNOTE: The following example is a truncated response object for the `workspace` `subject_type`.\nWe return the same structure for the other `subject_type` objects.\n\n```\n$ curl https://api.bitbucket.org/2.0/hook_events/workspace\n{\n \"page\": 1,\n \"pagelen\": 30,\n \"size\": 21,\n \"values\": [\n {\n \"category\": \"Repository\",\n \"description\": \"Whenever a repository push occurs\",\n \"event\": \"repo:push\",\n \"label\": \"Push\"\n },\n {\n \"category\": \"Repository\",\n \"description\": \"Whenever a repository fork occurs\",\n \"event\": \"repo:fork\",\n \"label\": \"Fork\"\n },\n {\n \"category\": \"Repository\",\n \"description\": \"Whenever a repository import occurs\",\n \"event\": \"repo:imported\",\n \"label\": \"Import\"\n },\n ...\n {\n \"category\":\"Pull Request\",\n \"label\":\"Approved\",\n \"description\":\"When someone has approved a pull request\",\n \"event\":\"pullrequest:approved\"\n },\n ]\n}\n```" + ] }, "parameters": [ { @@ -574,13 +574,16 @@ "required": true, "schema": { "type": "string", - "enum": ["workspace", "user", "repository", "team"] + "enum": ["repository", "workspace"] } } ] }, "/pullrequests/{selected_user}": { "get": { + "tags": ["Pullrequests"], + "description": "Returns all pull requests authored by the specified user.\n\nBy default only open pull requests are returned. This can be controlled\nusing the `state` query parameter. To retrieve pull requests that are\nin one of multiple states, repeat the `state` parameter for each\nindividual state.\n\nThis endpoint also supports filtering and sorting of the results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more details.", + "summary": "List pull requests for a user", "responses": { "200": { "description": "All pull requests authored by the specified user.", @@ -610,12 +613,10 @@ "description": "Only return pull requests that are in this state. This parameter can be repeated.", "schema": { "type": "string", - "enum": ["MERGED", "SUPERSEDED", "OPEN", "DECLINED"] + "enum": ["OPEN", "MERGED", "DECLINED", "SUPERSEDED"] } } ], - "tags": ["Pullrequests"], - "summary": "List pull requests for a user", "security": [ { "oauth2": ["pullrequest"] @@ -626,8 +627,7 @@ { "api_key": [] } - ], - "description": "Returns all pull requests authored by the specified user.\n\nBy default only open pull requests are returned. This can be controlled\nusing the `state` query parameter. To retrieve pull requests that are\nin one of multiple states, repeat the `state` parameter for each\nindividual state.\n\nThis endpoint also supports filtering and sorting of the results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more details." + ] }, "parameters": [ { @@ -643,6 +643,9 @@ }, "/repositories": { "get": { + "tags": ["Repositories"], + "description": "Returns a paginated list of all public repositories.\n\nThis endpoint also supports filtering and sorting of the results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more details.", + "summary": "List public repositories", "responses": { "200": { "description": "All public repositories.", @@ -694,8 +697,6 @@ } } ], - "tags": ["Repositories"], - "summary": "List public repositories", "security": [ { "oauth2": ["repository"] @@ -706,13 +707,105 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of all public repositories.\n\nThis endpoint also supports filtering and sorting of the results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more details." + ] }, "parameters": [] }, + "/repositories/{workspace_slug}/{repo_slug}/override-settings": { + "get": { + "tags": ["Repositories"], + "description": "", + "summary": "Retrieve the inheritance state for repository settings", + "responses": { + "200": { + "description": "The repository setting inheritance state", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository_inheritance_state" + } + } + } + }, + "404": { + "description": "If no repository exists at this location", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "put": { + "tags": ["Repositories"], + "description": "", + "summary": "Set the inheritance state for repository settings\n ", + "responses": { + "204": { + "description": "The repository setting inheritance state was set and no content returned" + }, + "404": { + "description": "If no repository exists at this location", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "parameters": [ + { + "name": "repo_slug", + "in": "path", + "description": "This can either be the repository slug or the UUID of the repository,\nsurrounded by curly-braces, for example: `{repository UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "workspace_slug", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, "/repositories/{workspace}": { "get": { + "tags": ["Repositories"], + "description": "Returns a paginated list of all repositories owned by the specified\nworkspace.\n\nThe result can be narrowed down based on the authenticated user's role.\n\nE.g. with `?role=contributor`, only those repositories that the\nauthenticated user has write access to are returned (this includes any\nrepo the user is an admin on, as that implies write access).\n\nThis endpoint also supports filtering and sorting of the results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more details.", + "summary": "List repositories in a workspace", "responses": { "200": { "description": "The repositories owned by the specified account.", @@ -775,8 +868,6 @@ } } ], - "tags": ["Repositories"], - "summary": "List repositories in a workspace", "security": [ { "oauth2": ["repository"] @@ -787,8 +878,7 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of all repositories owned by the specified\nworkspace.\n\nThe result can be narrowed down based on the authenticated user's role.\n\nE.g. with `?role=contributor`, only those repositories that the\nauthenticated user has write access to are returned (this includes any\nrepo the user is an admin on, as that implies write access).\n\nThis endpoint also supports filtering and sorting of the results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more details." + ] }, "parameters": [ { @@ -804,6 +894,9 @@ }, "/repositories/{workspace}/{repo_slug}": { "delete": { + "tags": ["Repositories"], + "description": "Deletes the repository. This is an irreversible operation.\n\nThis does not affect its forks.", + "summary": "Delete a repository", "responses": { "204": { "description": "Indicates successful deletion." @@ -840,8 +933,6 @@ } } ], - "tags": ["Repositories"], - "summary": "Delete a repository", "security": [ { "oauth2": ["repository:delete"] @@ -852,10 +943,12 @@ { "api_key": [] } - ], - "description": "Deletes the repository. This is an irreversible operation.\n\nThis does not affect its forks." + ] }, "get": { + "tags": ["Repositories"], + "description": "Returns the object describing this repository.", + "summary": "Get a repository", "responses": { "200": { "description": "The repository object.", @@ -888,8 +981,6 @@ } } }, - "tags": ["Repositories"], - "summary": "Get a repository", "security": [ { "oauth2": ["repository"] @@ -900,10 +991,12 @@ { "api_key": [] } - ], - "description": "Returns the object describing this repository." + ] }, "post": { + "tags": ["Repositories"], + "description": "Creates a new repository.\n\nNote: In order to set the project for the newly created repository,\npass in either the project key or the project UUID as part of the\nrequest body as shown in the examples below:\n\n```\n$ curl -X POST -H \"Content-Type: application/json\" -d '{\n \"scm\": \"git\",\n \"project\": {\n \"key\": \"MARS\"\n }\n}' https://api.bitbucket.org/2.0/repositories/teamsinspace/hablanding\n```\n\nor\n\n```\n$ curl -X POST -H \"Content-Type: application/json\" -d '{\n \"scm\": \"git\",\n \"project\": {\n \"key\": \"{ba516952-992a-4c2d-acbd-17d502922f96}\"\n }\n}' https://api.bitbucket.org/2.0/repositories/teamsinspace/hablanding\n```\n\nThe project must be assigned for all repositories. If the project is not provided,\nthe repository is automatically assigned to the oldest project in the workspace.\n\nNote: In the examples above, the workspace ID `teamsinspace`,\nand/or the repository name `hablanding` can be replaced by UUIDs.", + "summary": "Create a repository", "responses": { "200": { "description": "The newly created repository.", @@ -946,8 +1039,6 @@ }, "description": "The repository that is to be created. Note that most object elements are optional. Elements \"owner\" and \"full_name\" are ignored as the URL implies them." }, - "tags": ["Repositories"], - "summary": "Create a repository", "security": [ { "oauth2": ["repository:admin"] @@ -958,10 +1049,12 @@ { "api_key": [] } - ], - "description": "Creates a new repository.\n\nNote: In order to set the project for the newly created repository,\npass in either the project key or the project UUID as part of the\nrequest body as shown in the examples below:\n\n```\n$ curl -X POST -H \"Content-Type: application/json\" -d '{\n \"scm\": \"git\",\n \"project\": {\n \"key\": \"MARS\"\n }\n}' https://api.bitbucket.org/2.0/repositories/teamsinspace/hablanding\n```\n\nor\n\n```\n$ curl -X POST -H \"Content-Type: application/json\" -d '{\n \"scm\": \"git\",\n \"project\": {\n \"key\": \"{ba516952-992a-4c2d-acbd-17d502922f96}\"\n }\n}' https://api.bitbucket.org/2.0/repositories/teamsinspace/hablanding\n```\n\nThe project must be assigned for all repositories. If the project is not provided,\nthe repository is automatically assigned to the oldest project in the workspace.\n\nNote: In the examples above, the workspace ID `teamsinspace`,\nand/or the repository name `hablanding` can be replaced by UUIDs." + ] }, "put": { + "tags": ["Repositories"], + "description": "Since this endpoint can be used to both update and to create a\nrepository, the request body depends on the intent.\n\n#### Creation\n\nSee the POST documentation for the repository endpoint for an example\nof the request body.\n\n#### Update\n\nNote: Changing the `name` of the repository will cause the location to\nbe changed. This is because the URL of the repo is derived from the\nname (a process called slugification). In such a scenario, it is\npossible for the request to fail if the newly created slug conflicts\nwith an existing repository's slug. But if there is no conflict,\nthe new location will be returned in the `Location` header of the\nresponse.", + "summary": "Update a repository", "responses": { "200": { "description": "The existing repository has been updated", @@ -1030,8 +1123,6 @@ }, "description": "The repository that is to be updated.\n\nNote that the elements \"owner\" and \"full_name\" are ignored since the\nURL implies them.\n" }, - "tags": ["Repositories"], - "summary": "Update a repository", "security": [ { "oauth2": ["repository:admin"] @@ -1042,8 +1133,7 @@ { "api_key": [] } - ], - "description": "Since this endpoint can be used to both update and to create a\nrepository, the request body depends on the intent.\n\n#### Creation\n\nSee the POST documentation for the repository endpoint for an example\nof the request body.\n\n#### Update\n\nNote: Changing the `name` of the repository will cause the location to\nbe changed. This is because the URL of the repo is derived from the\nname (a process called slugification). In such a scenario, it is\npossible for the request to fail if the newly created slug conflicts\nwith an existing repository's slug. But if there is no conflict,\nthe new location will be returned in the `Location` header of the\nresponse." + ] }, "parameters": [ { @@ -1068,6 +1158,9 @@ }, "/repositories/{workspace}/{repo_slug}/branch-restrictions": { "get": { + "tags": ["Branch restrictions"], + "description": "Returns a paginated list of all branch restrictions on the\nrepository.", + "summary": "List branch restrictions", "responses": { "200": { "description": "A paginated list of branch restrictions", @@ -1130,8 +1223,6 @@ } } ], - "tags": ["Branch restrictions"], - "summary": "List branch restrictions", "security": [ { "oauth2": ["repository:admin"] @@ -1142,10 +1233,12 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of all branch restrictions on the\nrepository." + ] }, "post": { + "tags": ["Branch restrictions"], + "description": "Creates a new branch restriction rule for a repository.\n\n`kind` describes what will be restricted. Allowed values include:\n`push`, `force`, `delete` and `restrict_merges`.\n\nDifferent kinds of branch restrictions have different requirements:\n\n* `push` and `restrict_merges` require `users` and `groups` to be\n specified. Empty lists are allowed, in which case permission is\n denied for everybody.\n\nThe restriction applies to all branches that match. There are\ntwo ways to match a branch. It is configured in `branch_match_kind`:\n\n1. `glob`: Matches a branch against the `pattern`. A `'*'` in\n `pattern` will expand to match zero or more characters, and every\n other character matches itself. For example, `'foo*'` will match\n `'foo'` and `'foobar'`, but not `'barfoo'`. `'*'` will match all\n branches.\n2. `branching_model`: Matches a branch against the repository's\n branching model. The `branch_type` controls the type of branch\n to match. Allowed values include: `production`, `development`,\n `bugfix`, `release`, `feature` and `hotfix`.\n\nThe combination of `kind` and match must be unique. This means that\ntwo `glob` restrictions in a repository cannot have the same `kind` and\n`pattern`. Additionally, two `branching_model` restrictions in a\nrepository cannot have the same `kind` and `branch_type`.\n\n`users` and `groups` are lists of users and groups that are except from\nthe restriction. They can only be configured in `push` and\n`restrict_merges` restrictions. The `push` restriction stops a user\npushing to matching branches unless that user is in `users` or is a\nmember of a group in `groups`. The `restrict_merges` stops a user\nmerging pull requests to matching branches unless that user is in\n`users` or is a member of a group in `groups`. Adding new users or\ngroups to an existing restriction should be done via `PUT`.\n\nNote that branch restrictions with overlapping matchers is allowed,\nbut the resulting behavior may be surprising.", + "summary": "Create a branch restriction rule", "responses": { "201": { "description": "A paginated list of branch restrictions", @@ -1199,8 +1292,6 @@ "description": "The new rule", "required": true }, - "tags": ["Branch restrictions"], - "summary": "Create a branch restriction rule", "security": [ { "oauth2": ["repository:admin"] @@ -1211,8 +1302,7 @@ { "api_key": [] } - ], - "description": "Creates a new branch restriction rule for a repository.\n\n`kind` describes what will be restricted. Allowed values include:\n`push`, `force`, `delete` and `restrict_merges`.\n\nDifferent kinds of branch restrictions have different requirements:\n\n* `push` and `restrict_merges` require `users` and `groups` to be\n specified. Empty lists are allowed, in which case permission is\n denied for everybody.\n\nThe restriction applies to all branches that match. There are\ntwo ways to match a branch. It is configured in `branch_match_kind`:\n\n1. `glob`: Matches a branch against the `pattern`. A `'*'` in\n `pattern` will expand to match zero or more characters, and every\n other character matches itself. For example, `'foo*'` will match\n `'foo'` and `'foobar'`, but not `'barfoo'`. `'*'` will match all\n branches.\n2. `branching_model`: Matches a branch against the repository's\n branching model. The `branch_type` controls the type of branch\n to match. Allowed values include: `production`, `development`,\n `bugfix`, `release`, `feature` and `hotfix`.\n\nThe combination of `kind` and match must be unique. This means that\ntwo `glob` restrictions in a repository cannot have the same `kind` and\n`pattern`. Additionally, two `branching_model` restrictions in a\nrepository cannot have the same `kind` and `branch_type`.\n\n`users` and `groups` are lists of users and groups that are except from\nthe restriction. They can only be configured in `push` and\n`restrict_merges` restrictions. The `push` restriction stops a user\npushing to matching branches unless that user is in `users` or is a\nmember of a group in `groups`. The `restrict_merges` stops a user\nmerging pull requests to matching branches unless that user is in\n`users` or is a member of a group in `groups`. Adding new users or\ngroups to an existing restriction should be done via `PUT`.\n\nNote that branch restrictions with overlapping matchers is allowed,\nbut the resulting behavior may be surprising." + ] }, "parameters": [ { @@ -1237,6 +1327,9 @@ }, "/repositories/{workspace}/{repo_slug}/branch-restrictions/{id}": { "delete": { + "tags": ["Branch restrictions"], + "description": "Deletes an existing branch restriction rule.", + "summary": "Delete a branch restriction rule", "responses": { "204": { "description": "" @@ -1272,8 +1365,6 @@ } } }, - "tags": ["Branch restrictions"], - "summary": "Delete a branch restriction rule", "security": [ { "oauth2": ["repository:admin"] @@ -1284,10 +1375,12 @@ { "api_key": [] } - ], - "description": "Deletes an existing branch restriction rule." + ] }, "get": { + "tags": ["Branch restrictions"], + "description": "Returns a specific branch restriction rule.", + "summary": "Get a branch restriction rule", "responses": { "200": { "description": "The branch restriction rule", @@ -1330,8 +1423,6 @@ } } }, - "tags": ["Branch restrictions"], - "summary": "Get a branch restriction rule", "security": [ { "oauth2": ["repository:admin"] @@ -1342,10 +1433,12 @@ { "api_key": [] } - ], - "description": "Returns a specific branch restriction rule." + ] }, "put": { + "tags": ["Branch restrictions"], + "description": "Updates an existing branch restriction rule.\n\nFields not present in the request body are ignored.\n\nSee [`POST`](/cloud/bitbucket/rest/api-group-branch-restrictions/#api-repositories-workspace-repo-slug-branch-restrictions-post) for details.", + "summary": "Update a branch restriction rule", "responses": { "200": { "description": "The updated branch restriction rule", @@ -1399,8 +1492,6 @@ "description": "The new version of the existing rule", "required": true }, - "tags": ["Branch restrictions"], - "summary": "Update a branch restriction rule", "security": [ { "oauth2": ["repository:admin"] @@ -1411,8 +1502,7 @@ { "api_key": [] } - ], - "description": "Updates an existing branch restriction rule.\n\nFields not present in the request body are ignored.\n\nSee [`POST`](/cloud/bitbucket/rest/api-group-branch-restrictions/#api-repositories-workspace-repo-slug-branch-restrictions-post) for details." + ] }, "parameters": [ { @@ -1446,6 +1536,9 @@ }, "/repositories/{workspace}/{repo_slug}/branching-model": { "get": { + "tags": ["Branching model"], + "description": "Return the branching model as applied to the repository. This view is\nread-only. The branching model settings can be changed using the\n[settings](#api-repositories-workspace-repo-slug-branching-model-settings-get) API.\n\nThe returned object:\n\n1. Always has a `development` property. `development.branch` contains\n the actual repository branch object that is considered to be the\n `development` branch. `development.branch` will not be present\n if it does not exist.\n2. Might have a `production` property. `production` will not\n be present when `production` is disabled.\n `production.branch` contains the actual branch object that is\n considered to be the `production` branch. `production.branch` will\n not be present if it does not exist.\n3. Always has a `branch_types` array which contains all enabled branch\n types.\n\nExample body:\n\n```\n{\n \"development\": {\n \"name\": \"master\",\n \"branch\": {\n \"type\": \"branch\",\n \"name\": \"master\",\n \"target\": {\n \"hash\": \"16dffcb0de1b22e249db6799532074cf32efe80f\"\n }\n },\n \"use_mainbranch\": true\n },\n \"production\": {\n \"name\": \"production\",\n \"branch\": {\n \"type\": \"branch\",\n \"name\": \"production\",\n \"target\": {\n \"hash\": \"16dffcb0de1b22e249db6799532074cf32efe80f\"\n }\n },\n \"use_mainbranch\": false\n },\n \"branch_types\": [\n {\n \"kind\": \"release\",\n \"prefix\": \"release/\"\n },\n {\n \"kind\": \"hotfix\",\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"feature\",\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"bugfix\",\n \"prefix\": \"bugfix/\"\n }\n ],\n \"type\": \"branching_model\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/.../branching-model\"\n }\n }\n}\n```", + "summary": "Get the branching model for a repository", "responses": { "200": { "description": "The branching model object", @@ -1488,8 +1581,6 @@ } } }, - "tags": ["Branching model"], - "summary": "Get the branching model for a repository", "security": [ { "oauth2": ["repository"] @@ -1500,8 +1591,7 @@ { "api_key": [] } - ], - "description": "Return the branching model as applied to the repository. This view is\nread-only. The branching model settings can be changed using the\n[settings](branching-model/settings#get) API.\n\nThe returned object:\n\n1. Always has a `development` property. `development.branch` contains\n the actual repository branch object that is considered to be the\n `development` branch. `development.branch` will not be present\n if it does not exist.\n2. Might have a `production` property. `production` will not\n be present when `production` is disabled.\n `production.branch` contains the actual branch object that is\n considered to be the `production` branch. `production.branch` will\n not be present if it does not exist.\n3. Always has a `branch_types` array which contains all enabled branch\n types.\n\nExample body:\n\n```\n{\n \"development\": {\n \"name\": \"master\",\n \"branch\": {\n \"type\": \"branch\",\n \"name\": \"master\",\n \"target\": {\n \"hash\": \"16dffcb0de1b22e249db6799532074cf32efe80f\"\n }\n },\n \"use_mainbranch\": true\n },\n \"production\": {\n \"name\": \"production\",\n \"branch\": {\n \"type\": \"branch\",\n \"name\": \"production\",\n \"target\": {\n \"hash\": \"16dffcb0de1b22e249db6799532074cf32efe80f\"\n }\n },\n \"use_mainbranch\": false\n },\n \"branch_types\": [\n {\n \"kind\": \"release\",\n \"prefix\": \"release/\"\n },\n {\n \"kind\": \"hotfix\",\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"feature\",\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"bugfix\",\n \"prefix\": \"bugfix/\"\n }\n ],\n \"type\": \"branching_model\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/.../branching-model\"\n }\n }\n}\n```" + ] }, "parameters": [ { @@ -1526,6 +1616,9 @@ }, "/repositories/{workspace}/{repo_slug}/branching-model/settings": { "get": { + "tags": ["Branching model"], + "description": "Return the branching model configuration for a repository. The returned\nobject:\n\n1. Always has a `development` property for the development branch.\n2. Always a `production` property for the production branch. The\n production branch can be disabled.\n3. The `branch_types` contains all the branch types.\n\nThis is the raw configuration for the branching model. A client\nwishing to see the branching model with its actual current branches may\nfind the [active model API](/cloud/bitbucket/rest/api-group-branching-model/#api-repositories-workspace-repo-slug-branching-model-get) more useful.\n\nExample body:\n\n```\n{\n \"development\": {\n \"is_valid\": true,\n \"name\": null,\n \"use_mainbranch\": true\n },\n \"production\": {\n \"is_valid\": true,\n \"name\": \"production\",\n \"use_mainbranch\": false,\n \"enabled\": false\n },\n \"branch_types\": [\n {\n \"kind\": \"release\",\n \"enabled\": true,\n \"prefix\": \"release/\"\n },\n {\n \"kind\": \"hotfix\",\n \"enabled\": true,\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"feature\",\n \"enabled\": true,\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"bugfix\",\n \"enabled\": false,\n \"prefix\": \"bugfix/\"\n }\n ],\n \"type\": \"branching_model_settings\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/.../branching-model/settings\"\n }\n }\n}\n```", + "summary": "Get the branching model config for a repository", "responses": { "200": { "description": "The branching model configuration", @@ -1568,8 +1661,6 @@ } } }, - "tags": ["Branching model"], - "summary": "Get the branching model config for a repository", "security": [ { "oauth2": ["repository:admin"] @@ -1580,10 +1671,12 @@ { "api_key": [] } - ], - "description": "Return the branching model configuration for a repository. The returned\nobject:\n\n1. Always has a `development` property for the development branch.\n2. Always a `production` property for the production branch. The\n production branch can be disabled.\n3. The `branch_types` contains all the branch types.\n\nThis is the raw configuration for the branching model. A client\nwishing to see the branching model with its actual current branches may\nfind the [active model API](/cloud/bitbucket/rest/api-group-branching-model/#api-repositories-workspace-repo-slug-branching-model-get) more useful.\n\nExample body:\n\n```\n{\n \"development\": {\n \"is_valid\": true,\n \"name\": null,\n \"use_mainbranch\": true\n },\n \"production\": {\n \"is_valid\": true,\n \"name\": \"production\",\n \"use_mainbranch\": false,\n \"enabled\": false\n },\n \"branch_types\": [\n {\n \"kind\": \"release\",\n \"enabled\": true,\n \"prefix\": \"release/\"\n },\n {\n \"kind\": \"hotfix\",\n \"enabled\": true,\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"feature\",\n \"enabled\": true,\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"bugfix\",\n \"enabled\": false,\n \"prefix\": \"bugfix/\"\n }\n ],\n \"type\": \"branching_model_settings\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/.../branching-model/settings\"\n }\n }\n}\n```" + ] }, "put": { + "tags": ["Branching model"], + "description": "Update the branching model configuration for a repository.\n\nThe `development` branch can be configured to a specific branch or to\ntrack the main branch. When set to a specific branch it must\ncurrently exist. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. A request without a\n`development` property will leave the development branch unchanged.\n\nIt is possible for the `development` branch to be invalid. This\nhappens when it points at a specific branch that has been\ndeleted. This is indicated in the `is_valid` field for the branch. It is\nnot possible to update the settings for `development` if that\nwould leave the branch in an invalid state. Such a request will be\nrejected.\n\nThe `production` branch can be a specific branch, the main\nbranch or disabled. When set to a specific branch it must currently\nexist. The `enabled` property can be used to enable (`true`) or\ndisable (`false`) it. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. A request without a\n`production` property will leave the production branch unchanged.\n\nIt is possible for the `production` branch to be invalid. This\nhappens when it points at a specific branch that has been\ndeleted. This is indicated in the `is_valid` field for the branch. A\nrequest that would leave `production` enabled and invalid will be\nrejected. It is possible to update `production` and make it invalid if\nit would also be left disabled.\n\nThe `branch_types` property contains the branch types to be updated.\nOnly the branch types passed will be updated. All updates will be\nrejected if it would leave the branching model in an invalid state.\nFor branch types this means that:\n\n1. The prefixes for all enabled branch types are valid. For example,\n it is not possible to use '*' inside a Git prefix.\n2. A prefix of an enabled branch type must not be a prefix of another\n enabled branch type. This is to ensure that a branch can be easily\n classified by its prefix unambiguously.\n\nIt is possible to store an invalid prefix if that branch type would be\nleft disabled. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. Each branch type must\nhave a `kind` property to identify it.\n\nExample Body:\n\n```\n {\n \"development\": {\n \"use_mainbranch\": true\n },\n \"production\": {\n \"enabled\": true,\n \"use_mainbranch\": false,\n \"name\": \"production\"\n },\n \"branch_types\": [\n {\n \"kind\": \"bugfix\",\n \"enabled\": true,\n \"prefix\": \"bugfix/\"\n },\n {\n \"kind\": \"feature\",\n \"enabled\": true,\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"hotfix\",\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"release\",\n \"enabled\": false,\n }\n ]\n }\n```\n\nThere is currently a side effect when using this API endpoint. If the\nrepository is inheriting branching model settings from its project,\nupdating the branching model for this repository will disable the\nproject setting inheritance.\n\n\nWe have deprecated this side effect and will remove it on 1 August 2022.", + "summary": "Update the branching model config for a repository", "responses": { "200": { "description": "The updated branching model configuration", @@ -1636,8 +1729,6 @@ } } }, - "tags": ["Branching model"], - "summary": "Update the branching model config for a repository", "security": [ { "oauth2": ["repository:admin"] @@ -1648,8 +1739,7 @@ { "api_key": [] } - ], - "description": "Update the branching model configuration for a repository.\n\nThe `development` branch can be configured to a specific branch or to\ntrack the main branch. When set to a specific branch it must\ncurrently exist. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. A request without a\n`development` property will leave the development branch unchanged.\n\nIt is possible for the `development` branch to be invalid. This\nhappens when it points at a specific branch that has been\ndeleted. This is indicated in the `is_valid` field for the branch. It is\nnot possible to update the settings for `development` if that\nwould leave the branch in an invalid state. Such a request will be\nrejected.\n\nThe `production` branch can be a specific branch, the main\nbranch or disabled. When set to a specific branch it must currently\nexist. The `enabled` property can be used to enable (`true`) or\ndisable (`false`) it. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. A request without a\n`production` property will leave the production branch unchanged.\n\nIt is possible for the `production` branch to be invalid. This\nhappens when it points at a specific branch that has been\ndeleted. This is indicated in the `is_valid` field for the branch. A\nrequest that would leave `production` enabled and invalid will be\nrejected. It is possible to update `production` and make it invalid if\nit would also be left disabled.\n\nThe `branch_types` property contains the branch types to be updated.\nOnly the branch types passed will be updated. All updates will be\nrejected if it would leave the branching model in an invalid state.\nFor branch types this means that:\n\n1. The prefixes for all enabled branch types are valid. For example,\n it is not possible to use '*' inside a Git prefix.\n2. A prefix of an enabled branch type must not be a prefix of another\n enabled branch type. This is to ensure that a branch can be easily\n classified by its prefix unambiguously.\n\nIt is possible to store an invalid prefix if that branch type would be\nleft disabled. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. Each branch type must\nhave a `kind` property to identify it.\n\nExample Body:\n\n```\n {\n \"development\": {\n \"use_mainbranch\": true\n },\n \"production\": {\n \"enabled\": true,\n \"use_mainbranch\": false,\n \"name\": \"production\"\n },\n \"branch_types\": [\n {\n \"kind\": \"bugfix\",\n \"enabled\": true,\n \"prefix\": \"bugfix/\"\n },\n {\n \"kind\": \"feature\",\n \"enabled\": true,\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"hotfix\",\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"release\",\n \"enabled\": false,\n }\n ]\n }\n```\n\nThere is currently a side effect when using this API endpoint. If the\nrepository is inheriting branching model settings from its project,\nupdating the branching model for this repository will disable the\nproject setting inheritance.\n\n\nWe have deprecated this side effect and will remove it on 1 August 2022." + ] }, "parameters": [ { @@ -1674,6 +1764,9 @@ }, "/repositories/{workspace}/{repo_slug}/commit/{commit}": { "get": { + "tags": ["Commits"], + "description": "Returns the specified commit.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a1\n{\n \"rendered\": {\n \"message\": {\n \"raw\": \"Add a GEORDI_OUTPUT_DIR setting\",\n \"markup\": \"markdown\",\n \"html\": \"

Add a GEORDI_OUTPUT_DIR setting

\",\n \"type\": \"rendered\"\n }\n },\n \"hash\": \"f7591a13eda445d9a9167f98eb870319f4b6c2d8\",\n \"repository\": {\n \"name\": \"geordi\",\n \"type\": \"repository\",\n \"full_name\": \"bitbucket/geordi\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/bitbucket/geordi\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B85d08b4e-571d-44e9-a507-fa476535aa98%7D?ts=1730260\"\n }\n },\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8\"\n },\n \"comments\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8/comments\"\n },\n \"patch\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/patch/f7591a13eda445d9a9167f98eb870319f4b6c2d8\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/bitbucket/geordi/commits/f7591a13eda445d9a9167f98eb870319f4b6c2d8\"\n },\n \"diff\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/diff/f7591a13eda445d9a9167f98eb870319f4b6c2d8\"\n },\n \"approve\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8/approve\"\n },\n \"statuses\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8/statuses\"\n }\n },\n \"author\": {\n \"raw\": \"Brodie Rao \",\n \"type\": \"author\",\n \"user\": {\n \"display_name\": \"Brodie Rao\",\n \"uuid\": \"{9484702e-c663-4afd-aefb-c93a8cd31c28}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/%7B9484702e-c663-4afd-aefb-c93a8cd31c28%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B9484702e-c663-4afd-aefb-c93a8cd31c28%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/557058:3aae1e05-702a-41e5-81c8-f36f29afb6ca/613070db-28b0-421f-8dba-ae8a87e2a5c7/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"brodie\",\n \"account_id\": \"557058:3aae1e05-702a-41e5-81c8-f36f29afb6ca\"\n }\n },\n \"summary\": {\n \"raw\": \"Add a GEORDI_OUTPUT_DIR setting\",\n \"markup\": \"markdown\",\n \"html\": \"

Add a GEORDI_OUTPUT_DIR setting

\",\n \"type\": \"rendered\"\n },\n \"participants\": [],\n \"parents\": [\n {\n \"type\": \"commit\",\n \"hash\": \"f06941fec4ef6bcb0c2456927a0cf258fa4f899b\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f06941fec4ef6bcb0c2456927a0cf258fa4f899b\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/bitbucket/geordi/commits/f06941fec4ef6bcb0c2456927a0cf258fa4f899b\"\n }\n }\n }\n ],\n \"date\": \"2012-07-16T19:37:54+00:00\",\n \"message\": \"Add a GEORDI_OUTPUT_DIR setting\",\n \"type\": \"commit\"\n}\n```", + "summary": "Get a commit", "responses": { "200": { "description": "The commit object", @@ -1696,8 +1789,6 @@ } } }, - "tags": ["Commits"], - "summary": "Get a commit", "security": [ { "oauth2": ["repository"] @@ -1708,8 +1799,7 @@ { "api_key": [] } - ], - "description": "Returns the specified commit.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a1\n{\n \"rendered\": {\n \"message\": {\n \"raw\": \"Add a GEORDI_OUTPUT_DIR setting\",\n \"markup\": \"markdown\",\n \"html\": \"

Add a GEORDI_OUTPUT_DIR setting

\",\n \"type\": \"rendered\"\n }\n },\n \"hash\": \"f7591a13eda445d9a9167f98eb870319f4b6c2d8\",\n \"repository\": {\n \"name\": \"geordi\",\n \"type\": \"repository\",\n \"full_name\": \"bitbucket/geordi\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/bitbucket/geordi\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B85d08b4e-571d-44e9-a507-fa476535aa98%7D?ts=1730260\"\n }\n },\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8\"\n },\n \"comments\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8/comments\"\n },\n \"patch\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/patch/f7591a13eda445d9a9167f98eb870319f4b6c2d8\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/bitbucket/geordi/commits/f7591a13eda445d9a9167f98eb870319f4b6c2d8\"\n },\n \"diff\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/diff/f7591a13eda445d9a9167f98eb870319f4b6c2d8\"\n },\n \"approve\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8/approve\"\n },\n \"statuses\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f7591a13eda445d9a9167f98eb870319f4b6c2d8/statuses\"\n }\n },\n \"author\": {\n \"raw\": \"Brodie Rao \",\n \"type\": \"author\",\n \"user\": {\n \"display_name\": \"Brodie Rao\",\n \"uuid\": \"{9484702e-c663-4afd-aefb-c93a8cd31c28}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/%7B9484702e-c663-4afd-aefb-c93a8cd31c28%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B9484702e-c663-4afd-aefb-c93a8cd31c28%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/557058:3aae1e05-702a-41e5-81c8-f36f29afb6ca/613070db-28b0-421f-8dba-ae8a87e2a5c7/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"brodie\",\n \"account_id\": \"557058:3aae1e05-702a-41e5-81c8-f36f29afb6ca\"\n }\n },\n \"summary\": {\n \"raw\": \"Add a GEORDI_OUTPUT_DIR setting\",\n \"markup\": \"markdown\",\n \"html\": \"

Add a GEORDI_OUTPUT_DIR setting

\",\n \"type\": \"rendered\"\n },\n \"participants\": [],\n \"parents\": [\n {\n \"type\": \"commit\",\n \"hash\": \"f06941fec4ef6bcb0c2456927a0cf258fa4f899b\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/commit/f06941fec4ef6bcb0c2456927a0cf258fa4f899b\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/bitbucket/geordi/commits/f06941fec4ef6bcb0c2456927a0cf258fa4f899b\"\n }\n }\n }\n ],\n \"date\": \"2012-07-16T19:37:54+00:00\",\n \"message\": \"Add a GEORDI_OUTPUT_DIR setting\",\n \"type\": \"commit\"\n}\n```" + ] }, "parameters": [ { @@ -1743,6 +1833,9 @@ }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/approve": { "delete": { + "tags": ["Commits"], + "description": "Redact the authenticated user's approval of the specified commit.\n\nThis operation is only available to users that have explicit access to\nthe repository. In contrast, just the fact that a repository is\npublicly accessible to users does not give them the ability to approve\ncommits.", + "summary": "Unapprove a commit", "responses": { "204": { "description": "An empty response indicating the authenticated user's approval has been withdrawn." @@ -1758,8 +1851,6 @@ } } }, - "tags": ["Commits"], - "summary": "Unapprove a commit", "security": [ { "oauth2": ["repository:write"] @@ -1770,10 +1861,12 @@ { "api_key": [] } - ], - "description": "Redact the authenticated user's approval of the specified commit.\n\nThis operation is only available to users that have explicit access to\nthe repository. In contrast, just the fact that a repository is\npublicly accessible to users does not give them the ability to approve\ncommits." + ] }, "post": { + "tags": ["Commits"], + "description": "Approve the specified commit as the authenticated user.\n\nThis operation is only available to users that have explicit access to\nthe repository. In contrast, just the fact that a repository is\npublicly accessible to users does not give them the ability to approve\ncommits.", + "summary": "Approve a commit", "responses": { "200": { "description": "The `participant` object recording that the authenticated user approved the commit.", @@ -1796,8 +1889,6 @@ } } }, - "tags": ["Commits"], - "summary": "Approve a commit", "security": [ { "oauth2": ["repository:write"] @@ -1808,8 +1899,7 @@ { "api_key": [] } - ], - "description": "Approve the specified commit as the authenticated user.\n\nThis operation is only available to users that have explicit access to\nthe repository. In contrast, just the fact that a repository is\npublicly accessible to users does not give them the ability to approve\ncommits." + ] }, "parameters": [ { @@ -1843,6 +1933,9 @@ }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/comments": { "get": { + "tags": ["Commits"], + "description": "Returns the commit's comments.\n\nThis includes both global and inline comments.\n\nThe default sorting is oldest to newest and can be overridden with\nthe `sort` query parameter.", + "summary": "List a commit's comments", "responses": { "200": { "description": "A paginated list of commit comments.", @@ -1875,8 +1968,6 @@ } } ], - "tags": ["Commits"], - "summary": "List a commit's comments", "security": [ { "oauth2": ["repository"] @@ -1887,10 +1978,12 @@ { "api_key": [] } - ], - "description": "Returns the commit's comments.\n\nThis includes both global and inline comments.\n\nThe default sorting is oldest to newest and can be overridden with\nthe `sort` query parameter." + ] }, "post": { + "tags": ["Commits"], + "description": "Creates new comment on the specified commit.\n\nTo post a reply to an existing comment, include the `parent.id` field:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/prlinks/commit/db9ba1e031d07a02603eae0e559a7adc010257fc/comments/ \\\n -X POST -u evzijst \\\n -H 'Content-Type: application/json' \\\n -d '{\"content\": {\"raw\": \"One more thing!\"},\n \"parent\": {\"id\": 5728901}}'\n```", + "summary": "Create comment for a commit", "responses": { "201": { "description": "The newly created comment.", @@ -1921,8 +2014,6 @@ "description": "The specified comment.", "required": true }, - "tags": ["Commits"], - "summary": "Create comment for a commit", "security": [ { "oauth2": ["repository"] @@ -1933,8 +2024,7 @@ { "api_key": [] } - ], - "description": "Creates new comment on the specified commit.\n\nTo post a reply to an existing comment, include the `parent.id` field:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/prlinks/commit/db9ba1e031d07a02603eae0e559a7adc010257fc/comments/ \\\n -X POST -u evzijst \\\n -H 'Content-Type: application/json' \\\n -d '{\"content\": {\"raw\": \"One more thing!\"},\n \"parent\": {\"id\": 5728901}}'\n```" + ] }, "parameters": [ { @@ -1968,6 +2058,9 @@ }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/comments/{comment_id}": { "get": { + "tags": ["Commits"], + "description": "Returns the specified commit comment.", + "summary": "Get a commit comment", "responses": { "200": { "description": "The commit comment.", @@ -1980,8 +2073,6 @@ } } }, - "tags": ["Commits"], - "summary": "Get a commit comment", "security": [ { "oauth2": ["repository"] @@ -1992,8 +2083,7 @@ { "api_key": [] } - ], - "description": "Returns the specified commit comment." + ] }, "parameters": [ { @@ -2041,48 +2131,51 @@ "description": "An empty response." } }, + "operationId": "updateCommitHostedPropertyValue", + "summary": "Update a commit application property", + "description": "Update an [application property](/cloud/bitbucket/application-properties/) value stored against a commit.", "parameters": [ { "required": true, - "description": "The repository container; either the workspace slug or the UUID in curly braces.", "in": "path", "name": "workspace", + "description": "The repository container; either the workspace slug or the UUID in curly braces.", "schema": { "type": "string" } }, { "required": true, - "description": "The repository.", "in": "path", "name": "repo_slug", + "description": "The repository.", "schema": { "type": "string" } }, { "required": true, - "description": "The commit.", "in": "path", "name": "commit", + "description": "The commit.", "schema": { "type": "string" } }, { "required": true, - "description": "The key of the Connect app.", "in": "path", "name": "app_key", + "description": "The key of the Connect app.", "schema": { "type": "string" } }, { "required": true, - "description": "The name of the property.", "in": "path", "name": "property_name", + "description": "The name of the property.", "schema": { "type": "string" } @@ -2091,10 +2184,7 @@ "requestBody": { "$ref": "#/components/requestBodies/application_property" }, - "tags": ["properties"], - "description": "Update an [application property](/cloud/bitbucket/application-properties/) value stored against a commit.", - "summary": "Update a commit application property", - "operationId": "updateCommitHostedPropertyValue" + "tags": ["properties"] }, "delete": { "responses": { @@ -2102,57 +2192,57 @@ "description": "An empty response." } }, + "operationId": "deleteCommitHostedPropertyValue", + "summary": "Delete a commit application property", + "description": "Delete an [application property](/cloud/bitbucket/application-properties/) value stored against a commit.", "parameters": [ { "required": true, - "description": "The repository container; either the workspace slug or the UUID in curly braces.", "in": "path", "name": "workspace", + "description": "The repository container; either the workspace slug or the UUID in curly braces.", "schema": { "type": "string" } }, { "required": true, - "description": "The repository.", "in": "path", "name": "repo_slug", + "description": "The repository.", "schema": { "type": "string" } }, { "required": true, - "description": "The commit.", "in": "path", "name": "commit", + "description": "The commit.", "schema": { "type": "string" } }, { "required": true, - "description": "The key of the Connect app.", "in": "path", "name": "app_key", + "description": "The key of the Connect app.", "schema": { "type": "string" } }, { "required": true, - "description": "The name of the property.", "in": "path", "name": "property_name", + "description": "The name of the property.", "schema": { "type": "string" } } ], - "tags": ["properties"], - "description": "Delete an [application property](/cloud/bitbucket/application-properties/) value stored against a commit.", - "summary": "Delete a commit application property", - "operationId": "deleteCommitHostedPropertyValue" + "tags": ["properties"] }, "get": { "responses": { @@ -2167,61 +2257,116 @@ } } }, + "operationId": "getCommitHostedPropertyValue", + "summary": "Get a commit application property", + "description": "Retrieve an [application property](/cloud/bitbucket/application-properties/) value stored against a commit.", "parameters": [ { "required": true, - "description": "The repository container; either the workspace slug or the UUID in curly braces.", "in": "path", "name": "workspace", + "description": "The repository container; either the workspace slug or the UUID in curly braces.", "schema": { "type": "string" } }, { "required": true, - "description": "The repository.", "in": "path", "name": "repo_slug", + "description": "The repository.", "schema": { "type": "string" } }, { "required": true, - "description": "The commit.", "in": "path", "name": "commit", + "description": "The commit.", "schema": { "type": "string" } }, { "required": true, - "description": "The key of the Connect app.", "in": "path", "name": "app_key", + "description": "The key of the Connect app.", "schema": { "type": "string" } }, { "required": true, - "description": "The name of the property.", "in": "path", "name": "property_name", + "description": "The name of the property.", "schema": { "type": "string" } } ], - "tags": ["properties"], - "description": "Retrieve an [application property](/cloud/bitbucket/application-properties/) value stored against a commit.", - "summary": "Get a commit application property", - "operationId": "getCommitHostedPropertyValue" + "tags": ["properties"] } }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/pullrequests": { "get": { + "tags": ["Pullrequests"], + "summary": "List pull requests that contain a commit", + "description": "Returns a paginated list of all pull requests as part of which this commit was reviewed. Pull Request Commit Links app must be installed first before using this API; installation automatically occurs when 'Go to pull request' is clicked from the web interface for a commit's details.", + "operationId": "getPullrequestsForCommit", + "parameters": [ + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "in": "path", + "description": "The repository; either the UUID in curly braces, or the slug", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "commit", + "in": "path", + "description": "The SHA1 of the commit", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "page", + "in": "query", + "description": "Which page to retrieve", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "pagelen", + "in": "query", + "description": "How many pull requests to retrieve per page", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 30 + } + } + ], "responses": { "200": { "description": "The paginated list of pull requests.", @@ -2253,66 +2398,44 @@ } } } - }, - "parameters": [ - { - "required": true, - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces", - "in": "path", - "name": "workspace", - "schema": { - "type": "string" - } - }, - { - "required": true, - "description": "The repository; either the UUID in curly braces, or the slug", - "in": "path", - "name": "repo_slug", - "schema": { - "type": "string" - } - }, - { - "required": true, - "description": "The SHA1 of the commit", - "in": "path", - "name": "commit", - "schema": { - "type": "string" - } - }, - { - "description": "Which page to retrieve", - "required": false, - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "format": "int32", - "default": 1 - } - }, - { - "description": "How many pull requests to retrieve per page", - "required": false, - "in": "query", - "name": "pagelen", - "schema": { - "type": "integer", - "format": "int32", - "default": 30 - } - } - ], - "tags": ["Pullrequests"], - "summary": "List pull requests that contain a commit", - "operationId": "getPullrequestsForCommit", - "description": "Returns a paginated list of all pull requests as part of which this commit was reviewed. Pull Request Commit Links app must be installed first before using this API; installation automatically occurs when 'Go to pull request' is clicked from the web interface for a commit's details." + } } }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/reports": { "get": { + "tags": ["Reports", "Commits"], + "description": "Returns a paginated list of Reports linked to this commit.", + "summary": "List reports", + "operationId": "getReportsForCommit", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "commit", + "description": "The commit for which to retrieve reports.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "OK", @@ -2324,44 +2447,64 @@ } } } - }, + } + } + }, + "/repositories/{workspace}/{repo_slug}/commit/{commit}/reports/{reportId}": { + "put": { + "tags": ["Reports", "Commits"], + "description": "Creates or updates a report for the specified commit.\nTo upload a report, make sure to generate an ID that is unique across all reports for that commit. If you want to use an existing id from your own system, we recommend prefixing it with your system's name to avoid collisions, for example, mySystem-001.\n\n### Sample cURL request:\n```\ncurl --request PUT 'https://api.bitbucket.org/2.0/repositories///commit//reports/mysystem-001' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"title\": \"Security scan report\",\n \"details\": \"This pull request introduces 10 new dependency vulnerabilities.\",\n \"report_type\": \"SECURITY\",\n \"reporter\": \"mySystem\",\n \"link\": \"http://www.mysystem.com/reports/001\",\n \"result\": \"FAILED\",\n \"data\": [\n {\n \"title\": \"Duration (seconds)\",\n \"type\": \"DURATION\",\n \"value\": 14\n },\n {\n \"title\": \"Safe to merge?\",\n \"type\": \"BOOLEAN\",\n \"value\": false\n }\n ]\n}'\n```\n\n### Possible field values:\nreport_type: SECURITY, COVERAGE, TEST, BUG\nresult: PASSED, FAILED, PENDING\ndata.type: BOOLEAN, DATE, DURATION, LINK, NUMBER, PERCENTAGE, TEXT\n\n#### Data field formats\n| Type Field | Value Field Type | Value Field Display |\n|:--------------|:------------------|:--------------------|\n| None/ Omitted | Number, String or Boolean (not an array or object) | Plain text |\n| BOOLEAN\t| Boolean | The value will be read as a JSON boolean and displayed as 'Yes' or 'No'. |\n| DATE | Number | The value will be read as a JSON number in the form of a Unix timestamp (milliseconds) and will be displayed as a relative date if the date is less than one week ago, otherwise it will be displayed as an absolute date. |\n| DURATION | Number | The value will be read as a JSON number in milliseconds and will be displayed in a human readable duration format. |\n| LINK | Object: `{\"text\": \"Link text here\", \"href\": \"https://link.to.annotation/in/external/tool\"}` | The value will be read as a JSON object containing the fields \"text\" and \"href\" and will be displayed as a clickable link on the report. |\n| NUMBER | Number | The value will be read as a JSON number and large numbers will be displayed in a human readable format (e.g. 14.3k). |\n| PERCENTAGE | Number (between 0 and 100) | The value will be read as a JSON number between 0 and 100 and will be displayed with a percentage sign. |\n| TEXT | String | The value will be read as a JSON string and will be displayed as-is |\n\nPlease refer to the [Code Insights documentation](https://confluence.atlassian.com/bitbucket/code-insights-994316785.html) for more information.\n", + "operationId": "createOrUpdateReport", + "summary": "Create or update a report", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { - "description": "The commit for which to retrieve reports.", - "required": true, "name": "commit", + "description": "The commit the report belongs to.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "reportId", + "description": "Either the uuid or external-id of the report.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "tags": ["Reports", "Commits"], - "summary": "List reports", - "operationId": "getReportsForCommit", - "description": "Returns a paginated list of Reports linked to this commit." - } - }, - "/repositories/{workspace}/{repo_slug}/commit/{commit}/reports/{reportId}": { - "put": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/report" + } + } + }, + "description": "The report to create or update", + "required": true + }, "responses": { "200": { "description": "OK", @@ -2383,62 +2526,51 @@ } } } - }, + } + }, + "get": { + "tags": ["Reports", "Commits"], + "description": "Returns a single Report matching the provided ID.", + "summary": "Get a report", + "operationId": "getReport", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "commit", "description": "The commit the report belongs to.", "required": true, - "name": "commit", "in": "path", "schema": { "type": "string" } }, { + "name": "reportId", "description": "Either the uuid or external-id of the report.", "required": true, - "name": "reportId", "in": "path", "schema": { "type": "string" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/report" - } - } - }, - "description": "The report to create or update", - "required": true - }, - "tags": ["Reports", "Commits"], - "summary": "Create or update a report", - "operationId": "createOrUpdateReport", - "description": "Creates or updates a report for the specified commit.\nTo upload a report, make sure to generate an ID that is unique across all reports for that commit. If you want to use an existing id from your own system, we recommend prefixing it with your system's name to avoid collisions, for example, mySystem-001.\n\n### Sample cURL request:\n```\ncurl --request PUT 'https://api.bitbucket.org/2.0/repositories///commit//reports/mysystem-001' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"title\": \"Security scan report\",\n \"details\": \"This pull request introduces 10 new dependency vulnerabilities.\",\n \"report_type\": \"SECURITY\",\n \"reporter\": \"mySystem\",\n \"link\": \"http://www.mysystem.com/reports/001\",\n \"result\": \"FAILED\",\n \"data\": [\n {\n \"title\": \"Duration (seconds)\",\n \"type\": \"DURATION\",\n \"value\": 14\n },\n {\n \"title\": \"Safe to merge?\",\n \"type\": \"BOOLEAN\",\n \"value\": false\n }\n ]\n}'\n```\n\n### Possible field values:\nreport_type: SECURITY, COVERAGE, TEST, BUG\nresult: PASSED, FAILED, PENDING\ndata.type: BOOLEAN, DATE, DURATION, LINK, NUMBER, PERCENTAGE, TEXT\n\n#### Data field formats\n| Type Field | Value Field Type | Value Field Display |\n|:--------------|:------------------|:--------------------|\n| None/ Omitted | Number, String or Boolean (not an array or object) | Plain text |\n| BOOLEAN\t| Boolean | The value will be read as a JSON boolean and displayed as 'Yes' or 'No'. |\n| DATE | Number | The value will be read as a JSON number in the form of a Unix timestamp (milliseconds) and will be displayed as a relative date if the date is less than one week ago, otherwise it will be displayed as an absolute date. |\n| DURATION | Number | The value will be read as a JSON number in milliseconds and will be displayed in a human readable duration format. |\n| LINK | Object: `{\"text\": \"Link text here\", \"href\": \"https://link.to.annotation/in/external/tool\"}` | The value will be read as a JSON object containing the fields \"text\" and \"href\" and will be displayed as a clickable link on the report. |\n| NUMBER | Number | The value will be read as a JSON number and large numbers will be displayed in a human readable format (e.g. 14.3k). |\n| PERCENTAGE | Number (between 0 and 100) | The value will be read as a JSON number between 0 and 100 and will be displayed with a percentage sign. |\n| TEXT | String | The value will be read as a JSON string and will be displayed as-is |\n\nPlease refer to the [Code Insights documentation](https://confluence.atlassian.com/bitbucket/code-insights-994316785.html) for more information.\n" - }, - "get": { "responses": { "200": { "description": "OK", @@ -2460,177 +2592,102 @@ } } } - }, + } + }, + "delete": { + "tags": ["Reports", "Commits"], + "description": "Deletes a single Report matching the provided ID.", + "summary": "Delete a report", + "operationId": "deleteReport", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "commit", "description": "The commit the report belongs to.", "required": true, - "name": "commit", "in": "path", "schema": { "type": "string" } }, { + "name": "reportId", "description": "Either the uuid or external-id of the report.", "required": true, - "name": "reportId", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Reports", "Commits"], - "summary": "Get a report", - "operationId": "getReport", - "description": "Returns a single Report matching the provided ID." - }, - "delete": { "responses": { "204": { "description": "No content" } - }, - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The commit the report belongs to.", - "required": true, - "name": "commit", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "Either the uuid or external-id of the report.", - "required": true, - "name": "reportId", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Reports", "Commits"], - "summary": "Delete a report", - "operationId": "deleteReport", - "description": "Deletes a single Report matching the provided ID." + } } }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/reports/{reportId}/annotations": { - "post": { - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "items": { - "$ref": "#/components/schemas/report_annotation" - }, - "type": "array" - } - } - } - } - }, + "get": { + "tags": ["Reports", "Commits"], + "description": "Returns a paginated list of Annotations for a specified report.", + "summary": "List annotations", + "operationId": "getAnnotationsForReport", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "commit", "description": "The commit for which to retrieve reports.", "required": true, - "name": "commit", "in": "path", "schema": { "type": "string" } }, { + "name": "reportId", "description": "Uuid or external-if of the report for which to get annotations for.", "required": true, - "name": "reportId", "in": "path", "schema": { "type": "string" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "minItems": 1, - "items": { - "$ref": "#/components/schemas/report_annotation" - }, - "type": "array", - "maxItems": 100 - } - } - }, - "description": "The annotations to create or update", - "required": true - }, - "tags": ["Reports", "Commits"], - "summary": "Bulk create or update annotations", - "operationId": "bulkCreateOrUpdateAnnotations", - "description": "Bulk upload of annotations.\nAnnotations are individual findings that have been identified as part of a report, for example, a line of code that represents a vulnerability. These annotations can be attached to a specific file and even a specific line in that file, however, that is optional. Annotations are not mandatory and a report can contain up to 1000 annotations.\n\nAdd the annotations you want to upload as objects in a JSON array and make sure each annotation has the external_id field set to a unique value. If you want to use an existing id from your own system, we recommend prefixing it with your system's name to avoid collisions, for example, mySystem-annotation001. The external id can later be used to identify the report as an alternative to the generated [UUID](https://developer.atlassian.com/bitbucket/api/2/reference/meta/uri-uuid#uuid). You can upload up to 100 annotations per POST request.\n\n### Sample cURL request:\n```\ncurl --location 'https://api.bitbucket.org/2.0/repositories///commit//reports/mysystem-001/annotations' \\\n--header 'Content-Type: application/json' \\\n--data-raw '[\n {\n \"external_id\": \"mysystem-annotation001\",\n \"title\": \"Security scan report\",\n \"annotation_type\": \"VULNERABILITY\",\n \"summary\": \"This line represents a security threat.\",\n \"severity\": \"HIGH\",\n \"path\": \"my-service/src/main/java/com/myCompany/mysystem/logic/Main.java\",\n \"line\": 42\n },\n {\n \"external_id\": \"mySystem-annotation002\",\n \"title\": \"Bug report\",\n \"annotation_type\": \"BUG\",\n \"result\": \"FAILED\",\n \"summary\": \"This line might introduce a bug.\",\n \"severity\": \"MEDIUM\",\n \"path\": \"my-service/src/main/java/com/myCompany/mysystem/logic/Helper.java\",\n \"line\": 13\n }\n]'\n```\n\n### Possible field values:\nannotation_type: VULNERABILITY, CODE_SMELL, BUG\nresult: PASSED, FAILED, IGNORED, SKIPPED\nseverity: HIGH, MEDIUM, LOW, CRITICAL\n\nPlease refer to the [Code Insights documentation](https://confluence.atlassian.com/bitbucket/code-insights-994316785.html) for more information.\n" - }, - "get": { "responses": { "200": { "description": "OK", @@ -2642,116 +2699,45 @@ } } } - }, + } + }, + "post": { + "tags": ["Reports", "Commits"], + "description": "Bulk upload of annotations.\nAnnotations are individual findings that have been identified as part of a report, for example, a line of code that represents a vulnerability. These annotations can be attached to a specific file and even a specific line in that file, however, that is optional. Annotations are not mandatory and a report can contain up to 1000 annotations.\n\nAdd the annotations you want to upload as objects in a JSON array and make sure each annotation has the external_id field set to a unique value. If you want to use an existing id from your own system, we recommend prefixing it with your system's name to avoid collisions, for example, mySystem-annotation001. The external id can later be used to identify the report as an alternative to the generated [UUID](https://developer.atlassian.com/bitbucket/api/2/reference/meta/uri-uuid#uuid). You can upload up to 100 annotations per POST request.\n\n### Sample cURL request:\n```\ncurl --location 'https://api.bitbucket.org/2.0/repositories///commit//reports/mysystem-001/annotations' \\\n--header 'Content-Type: application/json' \\\n--data-raw '[\n {\n \"external_id\": \"mysystem-annotation001\",\n \"title\": \"Security scan report\",\n \"annotation_type\": \"VULNERABILITY\",\n \"summary\": \"This line represents a security threat.\",\n \"severity\": \"HIGH\",\n \"path\": \"my-service/src/main/java/com/myCompany/mysystem/logic/Main.java\",\n \"line\": 42\n },\n {\n \"external_id\": \"mySystem-annotation002\",\n \"title\": \"Bug report\",\n \"annotation_type\": \"BUG\",\n \"result\": \"FAILED\",\n \"summary\": \"This line might introduce a bug.\",\n \"severity\": \"MEDIUM\",\n \"path\": \"my-service/src/main/java/com/myCompany/mysystem/logic/Helper.java\",\n \"line\": 13\n }\n]'\n```\n\n### Possible field values:\nannotation_type: VULNERABILITY, CODE_SMELL, BUG\nresult: PASSED, FAILED, IGNORED, SKIPPED\nseverity: HIGH, MEDIUM, LOW, CRITICAL\n\nPlease refer to the [Code Insights documentation](https://confluence.atlassian.com/bitbucket/code-insights-994316785.html) for more information.\n", + "operationId": "bulkCreateOrUpdateAnnotations", + "summary": "Bulk create or update annotations", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "commit", "description": "The commit for which to retrieve reports.", "required": true, - "name": "commit", "in": "path", "schema": { "type": "string" } }, { + "name": "reportId", "description": "Uuid or external-if of the report for which to get annotations for.", "required": true, - "name": "reportId", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Reports", "Commits"], - "summary": "List annotations", - "operationId": "getAnnotationsForReport", - "description": "Returns a paginated list of Annotations for a specified report." - } - }, - "/repositories/{workspace}/{repo_slug}/commit/{commit}/reports/{reportId}/annotations/{annotationId}": { - "put": { - "responses": { - "200": { - "description": "OK", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/report_annotation" - } - } - } - }, - "400": { - "description": "The provided Annotation object is malformed or incomplete.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The commit the report belongs to.", - "required": true, - "name": "commit", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "Either the uuid or external-id of the report.", - "required": true, - "name": "reportId", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "Either the uuid or external-id of the annotation.", - "required": true, - "name": "annotationId", "in": "path", "schema": { "type": "string" @@ -2762,19 +2748,88 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/report_annotation" + "type": "array", + "items": { + "$ref": "#/components/schemas/report_annotation" + }, + "minItems": 1, + "maxItems": 100 } } }, - "description": "The annotation to create or update", + "description": "The annotations to create or update", "required": true }, - "tags": ["Reports", "Commits"], - "summary": "Create or update an annotation", - "operationId": "createOrUpdateAnnotation", - "description": "Creates or updates an individual annotation for the specified report.\nAnnotations are individual findings that have been identified as part of a report, for example, a line of code that represents a vulnerability. These annotations can be attached to a specific file and even a specific line in that file, however, that is optional. Annotations are not mandatory and a report can contain up to 1000 annotations.\n\nJust as reports, annotation needs to be uploaded with a unique ID that can later be used to identify the report as an alternative to the generated [UUID](https://developer.atlassian.com/bitbucket/api/2/reference/meta/uri-uuid#uuid). If you want to use an existing id from your own system, we recommend prefixing it with your system's name to avoid collisions, for example, mySystem-annotation001.\n\n### Sample cURL request:\n```\ncurl --request PUT 'https://api.bitbucket.org/2.0/repositories///commit//reports/mySystem-001/annotations/mysystem-annotation001' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"title\": \"Security scan report\",\n \"annotation_type\": \"VULNERABILITY\",\n \"summary\": \"This line represents a security thread.\",\n \"severity\": \"HIGH\",\n \"path\": \"my-service/src/main/java/com/myCompany/mysystem/logic/Main.java\",\n \"line\": 42\n}'\n```\n\n### Possible field values:\nannotation_type: VULNERABILITY, CODE_SMELL, BUG\nresult: PASSED, FAILED, IGNORED, SKIPPED\nseverity: HIGH, MEDIUM, LOW, CRITICAL\n\nPlease refer to the [Code Insights documentation](https://confluence.atlassian.com/bitbucket/code-insights-994316785.html) for more information.\n" - }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/report_annotation" + } + } + } + } + } + } + } + }, + "/repositories/{workspace}/{repo_slug}/commit/{commit}/reports/{reportId}/annotations/{annotationId}": { "get": { + "tags": ["Reports", "Commits"], + "description": "Returns a single Annotation matching the provided ID.", + "summary": "Get an annotation", + "operationId": "getAnnotation", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "commit", + "description": "The commit the report belongs to.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "reportId", + "description": "Either the uuid or external-id of the report.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "annotationId", + "description": "Either the uuid or external-id of the annotation.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "OK", @@ -2796,120 +2851,158 @@ } } } - }, + } + }, + "put": { + "tags": ["Reports", "Commits"], + "description": "Creates or updates an individual annotation for the specified report.\nAnnotations are individual findings that have been identified as part of a report, for example, a line of code that represents a vulnerability. These annotations can be attached to a specific file and even a specific line in that file, however, that is optional. Annotations are not mandatory and a report can contain up to 1000 annotations.\n\nJust as reports, annotation needs to be uploaded with a unique ID that can later be used to identify the report as an alternative to the generated [UUID](https://developer.atlassian.com/bitbucket/api/2/reference/meta/uri-uuid#uuid). If you want to use an existing id from your own system, we recommend prefixing it with your system's name to avoid collisions, for example, mySystem-annotation001.\n\n### Sample cURL request:\n```\ncurl --request PUT 'https://api.bitbucket.org/2.0/repositories///commit//reports/mySystem-001/annotations/mysystem-annotation001' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\n \"title\": \"Security scan report\",\n \"annotation_type\": \"VULNERABILITY\",\n \"summary\": \"This line represents a security thread.\",\n \"severity\": \"HIGH\",\n \"path\": \"my-service/src/main/java/com/myCompany/mysystem/logic/Main.java\",\n \"line\": 42\n}'\n```\n\n### Possible field values:\nannotation_type: VULNERABILITY, CODE_SMELL, BUG\nresult: PASSED, FAILED, IGNORED, SKIPPED\nseverity: HIGH, MEDIUM, LOW, CRITICAL\n\nPlease refer to the [Code Insights documentation](https://confluence.atlassian.com/bitbucket/code-insights-994316785.html) for more information.\n", + "operationId": "createOrUpdateAnnotation", + "summary": "Create or update an annotation", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "commit", "description": "The commit the report belongs to.", "required": true, - "name": "commit", "in": "path", "schema": { "type": "string" } }, { + "name": "reportId", "description": "Either the uuid or external-id of the report.", "required": true, - "name": "reportId", "in": "path", "schema": { "type": "string" } }, { + "name": "annotationId", "description": "Either the uuid or external-id of the annotation.", "required": true, - "name": "annotationId", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Reports", "Commits"], - "summary": "Get an annotation", - "operationId": "getAnnotation", - "description": "Returns a single Annotation matching the provided ID." + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/report_annotation" + } + } + }, + "description": "The annotation to create or update", + "required": true + }, + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/report_annotation" + } + } + } + }, + "400": { + "description": "The provided Annotation object is malformed or incomplete.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + } }, "delete": { + "tags": ["Reports", "Commits"], + "description": "Deletes a single Annotation matching the provided ID.", + "summary": "Delete an annotation", + "operationId": "deleteAnnotation", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "commit", + "description": "The commit the annotation belongs to.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "reportId", + "description": "Either the uuid or external-id of the annotation.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "annotationId", + "description": "Either the uuid or external-id of the annotation.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "204": { "description": "No content" } - }, - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The commit the annotation belongs to.", - "required": true, - "name": "commit", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "Either the uuid or external-id of the annotation.", - "required": true, - "name": "reportId", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "Either the uuid or external-id of the annotation.", - "required": true, - "name": "annotationId", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Reports", "Commits"], - "summary": "Delete an annotation", - "operationId": "deleteAnnotation", - "description": "Deletes a single Annotation matching the provided ID." + } } }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/statuses": { "get": { + "tags": ["Commit statuses"], + "description": "Returns all statuses (e.g. build results) for a specific commit.", + "summary": "List commit statuses for a commit", "responses": { "200": { "description": "A paginated list of all commit statuses for this commit.", @@ -2955,8 +3048,6 @@ } } ], - "tags": ["Commit statuses"], - "summary": "List commit statuses for a commit", "security": [ { "oauth2": ["repository"] @@ -2967,8 +3058,7 @@ { "api_key": [] } - ], - "description": "Returns all statuses (e.g. build results) for a specific commit." + ] }, "parameters": [ { @@ -3002,6 +3092,9 @@ }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/statuses/build": { "post": { + "tags": ["Commit statuses"], + "description": "Creates a new build status against the specified commit.\n\nIf the specified key already exists, the existing status object will\nbe overwritten.\n\nExample:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/my-workspace/my-repo/commit/e10dae226959c2194f2b07b077c07762d93821cf/statuses/build/ -X POST -u jdoe -H 'Content-Type: application/json' -d '{\n \"key\": \"MY-BUILD\",\n \"state\": \"SUCCESSFUL\",\n \"description\": \"42 tests passed\",\n \"url\": \"https://www.example.org/my-build-result\"\n }'\n```\n\nWhen creating a new commit status, you can use a URI template for the URL.\nTemplates are URLs that contain variable names that Bitbucket will\nevaluate at runtime whenever the URL is displayed anywhere similar to\nparameter substitution in\n[Bitbucket Connect](https://developer.atlassian.com/bitbucket/concepts/context-parameters.html).\nFor example, one could use `https://foo.com/builds/{repository.full_name}`\nwhich Bitbucket will turn into `https://foo.com/builds/foo/bar` at render time.\nThe context variables available are `repository` and `commit`.", + "summary": "Create a build status for a commit", "responses": { "201": { "description": "The newly created build status object.", @@ -3037,8 +3130,6 @@ }, "description": "The new commit status object." }, - "tags": ["Commit statuses"], - "summary": "Create a build status for a commit", "security": [ { "oauth2": ["repository"] @@ -3049,8 +3140,7 @@ { "api_key": [] } - ], - "description": "Creates a new build status against the specified commit.\n\nIf the specified key already exists, the existing status object will\nbe overwritten.\n\nExample:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/my-workspace/my-repo/commit/e10dae226959c2194f2b07b077c07762d93821cf/statuses/build/ -X POST -u jdoe -H 'Content-Type: application/json' -d '{\n \"key\": \"MY-BUILD\",\n \"state\": \"SUCCESSFUL\",\n \"description\": \"42 tests passed\",\n \"url\": \"https://www.example.org/my-build-result\"\n }'\n```\n\nWhen creating a new commit status, you can use a URI template for the URL.\nTemplates are URLs that contain variable names that Bitbucket will\nevaluate at runtime whenever the URL is displayed anywhere similar to\nparameter substitution in\n[Bitbucket Connect](https://developer.atlassian.com/bitbucket/concepts/context-parameters.html).\nFor example, one could use `https://foo.com/builds/{repository.full_name}`\nwhich Bitbucket will turn into `https://foo.com/builds/foo/bar` at render time.\nThe context variables available are `repository` and `commit`." + ] }, "parameters": [ { @@ -3084,6 +3174,9 @@ }, "/repositories/{workspace}/{repo_slug}/commit/{commit}/statuses/build/{key}": { "get": { + "tags": ["Commit statuses"], + "description": "Returns the specified build status for a commit.", + "summary": "Get a build status for a commit", "responses": { "200": { "description": "The build status object with the specified key.", @@ -3109,8 +3202,6 @@ } } }, - "tags": ["Commit statuses"], - "summary": "Get a build status for a commit", "security": [ { "oauth2": ["repository"] @@ -3121,10 +3212,12 @@ { "api_key": [] } - ], - "description": "Returns the specified build status for a commit." + ] }, "put": { + "tags": ["Commit statuses"], + "description": "Used to update the current status of a build status object on the\nspecific commit.\n\nThis operation can also be used to change other properties of the\nbuild status:\n\n* `state`\n* `name`\n* `description`\n* `url`\n* `refname`\n\nThe `key` cannot be changed.", + "summary": "Update a build status for a commit", "responses": { "200": { "description": "The updated build status object.", @@ -3160,8 +3253,6 @@ }, "description": "The updated build status object" }, - "tags": ["Commit statuses"], - "summary": "Update a build status for a commit", "security": [ { "oauth2": ["repository"] @@ -3172,8 +3263,7 @@ { "api_key": [] } - ], - "description": "Used to update the current status of a build status object on the\nspecific commit.\n\nThis operation can also be used to change other properties of the\nbuild status:\n\n* `state`\n* `name`\n* `description`\n* `url`\n* `refname`\n\nThe `key` cannot be changed." + ] }, "parameters": [ { @@ -3216,6 +3306,9 @@ }, "/repositories/{workspace}/{repo_slug}/commits": { "get": { + "tags": ["Commits"], + "description": "These are the repository's commits. They are paginated and returned\nin reverse chronological order, similar to the output of `git log`.\nLike these tools, the DAG can be filtered.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/\n\nReturns all commits in the repo in topological order (newest commit\nfirst). All branches and tags are included (similar to\n`git log --all`).\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/?exclude=master\n\nReturns all commits in the repo that are not on master\n(similar to `git log --all ^master`).\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/?include=foo&include=bar&exclude=fu&exclude=fubar\n\nReturns all commits that are on refs `foo` or `bar`, but not on `fu` or\n`fubar` (similar to `git log foo bar ^fu ^fubar`).\n\nAn optional `path` parameter can be specified that will limit the\nresults to commits that affect that path. `path` can either be a file\nor a directory. If a directory is specified, commits are returned that\nhave modified any file in the directory tree rooted by `path`. It is\nimportant to note that if the `path` parameter is specified, the commits\nreturned by this endpoint may no longer be a DAG, parent commits that\ndo not modify the path will be omitted from the response.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/?path=README.md&include=foo&include=bar&exclude=master\n\nReturns all commits that are on refs `foo` or `bar`, but not on `master`\nthat changed the file README.md.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/?path=src/&include=foo&include=bar&exclude=master\n\nReturns all commits that are on refs `foo` or `bar`, but not on `master`\nthat changed to a file in any file in the directory src or its children.\n\nBecause the response could include a very large number of commits, it\nis paginated. Follow the 'next' link in the response to navigate to the\nnext page of commits. As with other paginated resources, do not\nconstruct your own links.\n\nWhen the include and exclude parameters are more than can fit in a\nquery string, clients can use a `x-www-form-urlencoded` POST instead.", + "summary": "List commits", "responses": { "200": { "description": "A paginated list of commits", @@ -3238,8 +3331,6 @@ } } }, - "tags": ["Commits"], - "summary": "List commits", "security": [ { "oauth2": ["repository"] @@ -3250,10 +3341,12 @@ { "api_key": [] } - ], - "description": "These are the repository's commits. They are paginated and returned\nin reverse chronological order, similar to the output of `git log`.\nLike these tools, the DAG can be filtered.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/\n\nReturns all commits in the repo in topological order (newest commit\nfirst). All branches and tags are included (similar to\n`git log --all`).\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/?exclude=master\n\nReturns all commits in the repo that are not on master\n(similar to `git log --all ^master`).\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/?include=foo&include=bar&exclude=fu&exclude=fubar\n\nReturns all commits that are on refs `foo` or `bar`, but not on `fu` or\n`fubar` (similar to `git log foo bar ^fu ^fubar`).\n\nAn optional `path` parameter can be specified that will limit the\nresults to commits that affect that path. `path` can either be a file\nor a directory. If a directory is specified, commits are returned that\nhave modified any file in the directory tree rooted by `path`. It is\nimportant to note that if the `path` parameter is specified, the commits\nreturned by this endpoint may no longer be a DAG, parent commits that\ndo not modify the path will be omitted from the response.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/?path=README.md&include=foo&include=bar&exclude=master\n\nReturns all commits that are on refs `foo` or `bar`, but not on `master`\nthat changed the file README.md.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/?path=src/&include=foo&include=bar&exclude=master\n\nReturns all commits that are on refs `foo` or `bar`, but not on `master`\nthat changed to a file in any file in the directory src or its children.\n\nBecause the response could include a very large number of commits, it\nis paginated. Follow the 'next' link in the response to navigate to the\nnext page of commits. As with other paginated resources, do not\nconstruct your own links.\n\nWhen the include and exclude parameters are more than can fit in a\nquery string, clients can use a `x-www-form-urlencoded` POST instead." + ] }, "post": { + "tags": ["Commits"], + "description": "Identical to `GET /repositories/{workspace}/{repo_slug}/commits`,\nexcept that POST allows clients to place the include and exclude\nparameters in the request body to avoid URL length issues.\n\n**Note that this resource does NOT support new commit creation.**", + "summary": "List commits with include/exclude", "responses": { "200": { "description": "A paginated list of commits", @@ -3276,8 +3369,6 @@ } } }, - "tags": ["Commits"], - "summary": "List commits with include/exclude", "security": [ { "oauth2": ["repository"] @@ -3288,8 +3379,7 @@ { "api_key": [] } - ], - "description": "Identical to `GET /repositories/{workspace}/{repo_slug}/commits`,\nexcept that POST allows clients to place the include and exclude\nparameters in the request body to avoid URL length issues.\n\n**Note that this resource does NOT support new commit creation.**" + ] }, "parameters": [ { @@ -3314,6 +3404,9 @@ }, "/repositories/{workspace}/{repo_slug}/commits/{revision}": { "get": { + "tags": ["Commits"], + "description": "These are the repository's commits. They are paginated and returned\nin reverse chronological order, similar to the output of `git log`.\nLike these tools, the DAG can be filtered.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/master\n\nReturns all commits on rev `master` (similar to `git log master`).\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/dev?include=foo&exclude=master\n\nReturns all commits on ref `dev` or `foo`, except those that are reachable on\n`master` (similar to `git log dev foo ^master`).\n\nAn optional `path` parameter can be specified that will limit the\nresults to commits that affect that path. `path` can either be a file\nor a directory. If a directory is specified, commits are returned that\nhave modified any file in the directory tree rooted by `path`. It is\nimportant to note that if the `path` parameter is specified, the commits\nreturned by this endpoint may no longer be a DAG, parent commits that\ndo not modify the path will be omitted from the response.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/dev?path=README.md&include=foo&include=bar&exclude=master\n\nReturns all commits that are on refs `dev` or `foo` or `bar`, but not on `master`\nthat changed the file README.md.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/dev?path=src/&include=foo&exclude=master\n\nReturns all commits that are on refs `dev` or `foo`, but not on `master`\nthat changed to a file in any file in the directory src or its children.\n\nBecause the response could include a very large number of commits, it\nis paginated. Follow the 'next' link in the response to navigate to the\nnext page of commits. As with other paginated resources, do not\nconstruct your own links.\n\nWhen the include and exclude parameters are more than can fit in a\nquery string, clients can use a `x-www-form-urlencoded` POST instead.", + "summary": "List commits for revision", "responses": { "200": { "description": "A paginated list of commits", @@ -3336,8 +3429,6 @@ } } }, - "tags": ["Commits"], - "summary": "List commits for revision", "security": [ { "oauth2": ["repository"] @@ -3348,10 +3439,12 @@ { "api_key": [] } - ], - "description": "These are the repository's commits. They are paginated and returned\nin reverse chronological order, similar to the output of `git log`.\nLike these tools, the DAG can be filtered.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/master\n\nReturns all commits on rev `master` (similar to `git log master`).\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/dev?include=foo&exclude=master\n\nReturns all commits on ref `dev` or `foo`, except those that are reachable on\n`master` (similar to `git log dev foo ^master`).\n\nAn optional `path` parameter can be specified that will limit the\nresults to commits that affect that path. `path` can either be a file\nor a directory. If a directory is specified, commits are returned that\nhave modified any file in the directory tree rooted by `path`. It is\nimportant to note that if the `path` parameter is specified, the commits\nreturned by this endpoint may no longer be a DAG, parent commits that\ndo not modify the path will be omitted from the response.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/dev?path=README.md&include=foo&include=bar&exclude=master\n\nReturns all commits that are on refs `dev` or `foo` or `bar`, but not on `master`\nthat changed the file README.md.\n\n#### GET /repositories/{workspace}/{repo_slug}/commits/dev?path=src/&include=foo&exclude=master\n\nReturns all commits that are on refs `dev` or `foo`, but not on `master`\nthat changed to a file in any file in the directory src or its children.\n\nBecause the response could include a very large number of commits, it\nis paginated. Follow the 'next' link in the response to navigate to the\nnext page of commits. As with other paginated resources, do not\nconstruct your own links.\n\nWhen the include and exclude parameters are more than can fit in a\nquery string, clients can use a `x-www-form-urlencoded` POST instead." + ] }, "post": { + "tags": ["Commits"], + "description": "Identical to `GET /repositories/{workspace}/{repo_slug}/commits/{revision}`,\nexcept that POST allows clients to place the include and exclude\nparameters in the request body to avoid URL length issues.\n\n**Note that this resource does NOT support new commit creation.**", + "summary": "List commits for revision using include/exclude", "responses": { "200": { "description": "A paginated list of commits", @@ -3374,8 +3467,6 @@ } } }, - "tags": ["Commits"], - "summary": "List commits for revision using include/exclude", "security": [ { "oauth2": ["repository"] @@ -3386,8 +3477,7 @@ { "api_key": [] } - ], - "description": "Identical to `GET /repositories/{workspace}/{repo_slug}/commits/{revision}`,\nexcept that POST allows clients to place the include and exclude\nparameters in the request body to avoid URL length issues.\n\n**Note that this resource does NOT support new commit creation.**" + ] }, "parameters": [ { @@ -3421,6 +3511,9 @@ }, "/repositories/{workspace}/{repo_slug}/components": { "get": { + "tags": ["Issue tracker"], + "description": "Returns the components that have been defined in the issue tracker.\n\nThis resource is only available on repositories that have the issue\ntracker enabled.", + "summary": "List components", "responses": { "200": { "description": "The components that have been defined in the issue tracker.", @@ -3443,8 +3536,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "List components", "security": [ { "oauth2": ["issue"] @@ -3455,8 +3546,7 @@ { "api_key": [] } - ], - "description": "Returns the components that have been defined in the issue tracker.\n\nThis resource is only available on repositories that have the issue\ntracker enabled." + ] }, "parameters": [ { @@ -3481,6 +3571,9 @@ }, "/repositories/{workspace}/{repo_slug}/components/{component_id}": { "get": { + "tags": ["Issue tracker"], + "description": "Returns the specified issue tracker component object.", + "summary": "Get a component for issues", "responses": { "200": { "description": "The specified component object.", @@ -3503,8 +3596,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Get a component for issues", "security": [ { "oauth2": ["issue"] @@ -3515,8 +3606,7 @@ { "api_key": [] } - ], - "description": "Returns the specified issue tracker component object." + ] }, "parameters": [ { @@ -3550,9 +3640,19 @@ }, "/repositories/{workspace}/{repo_slug}/default-reviewers": { "get": { + "tags": ["Pullrequests"], + "description": "Returns the repository's default reviewers.\n\nThese are the users that are automatically added as reviewers on every\nnew pull request that is created.", + "summary": "List default reviewers", "responses": { "200": { - "description": "The paginated list of default reviewers" + "description": "The paginated list of default reviewers", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_accounts" + } + } + } }, "403": { "description": "If the authenticated user does not have access to view the default reviewers", @@ -3565,8 +3665,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "List default reviewers", "security": [ { "oauth2": ["pullrequest"] @@ -3577,8 +3675,7 @@ { "api_key": [] } - ], - "description": "Returns the repository's default reviewers.\n\nThese are the users that are automatically added as reviewers on every\nnew pull request that is created." + ] }, "parameters": [ { @@ -3603,6 +3700,9 @@ }, "/repositories/{workspace}/{repo_slug}/default-reviewers/{target_username}": { "delete": { + "tags": ["Pullrequests"], + "description": "Removes a default reviewer from the repository.", + "summary": "Remove a user from the default reviewers", "responses": { "204": { "description": "The specified user successfully removed from the default reviewers" @@ -3628,8 +3728,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "Remove a user from the default reviewers", "security": [ { "oauth2": ["repository:admin"] @@ -3640,13 +3738,22 @@ { "api_key": [] } - ], - "description": "Removes a default reviewer from the repository." + ] }, "get": { + "tags": ["Pullrequests"], + "description": "Returns the specified reviewer.\n\nThis can be used to test whether a user is among the repository's\ndefault reviewers list. A 404 indicates that that specified user is not\na default reviewer.", + "summary": "Get a default reviewer", "responses": { "200": { - "description": "The specified user is a default reviewer" + "description": "The specified user is a default reviewer", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/account" + } + } + } }, "403": { "description": "If the authenticated user does not have access to check if the specified user is a default reviewer", @@ -3669,8 +3776,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "Get a default reviewer", "security": [ { "oauth2": ["pullrequest"] @@ -3681,13 +3786,22 @@ { "api_key": [] } - ], - "description": "Returns the specified reviewer.\n\nThis can be used to test whether a user is among the repository's\ndefault reviewers list. A 404 indicates that that specified user is not\na default reviewer." + ] }, "put": { + "tags": ["Pullrequests"], + "description": "Adds the specified user to the repository's list of default\nreviewers.\n\nThis method is idempotent. Adding a user a second time has no effect.", + "summary": "Add a user to the default reviewers", "responses": { "200": { - "description": "The specified user was successfully added to the default reviewers" + "description": "The specified user was successfully added to the default reviewers", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/account" + } + } + } }, "400": { "description": "If the authenticated user tried to add a team, bot user, or user without access to the repository to the default reviewers", @@ -3720,8 +3834,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "Add a user to the default reviewers", "security": [ { "oauth2": ["repository:admin"] @@ -3732,8 +3844,7 @@ { "api_key": [] } - ], - "description": "Adds the specified user to the repository's list of default\nreviewers.\n\nThis method is idempotent. Adding a user a second time has no effect." + ] }, "parameters": [ { @@ -3767,6 +3878,9 @@ }, "/repositories/{workspace}/{repo_slug}/deploy-keys": { "get": { + "tags": ["Deployments"], + "description": "Returns all deploy-keys belonging to a repository.\n\nExample:\n```\n$ curl -H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys\n\nOutput:\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"id\": 123,\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5\",\n \"label\": \"mykey\",\n \"type\": \"deploy_key\",\n \"created_on\": \"2018-08-15T23:50:59.993890+00:00\",\n \"repository\": {\n \"full_name\": \"mleu/test\",\n \"name\": \"test\",\n \"type\": \"repository\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"links\":{\n \"self\":{\n \"href\": \"https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123\"\n }\n }\n \"last_used\": null,\n \"comment\": \"mleu@C02W454JHTD8\"\n }\n ],\n \"page\": 1,\n \"size\": 1\n}\n```", + "summary": "List repository deploy keys", "responses": { "200": { "description": "Deploy keys matching the repository", @@ -3792,8 +3906,6 @@ } } }, - "tags": ["Deployments"], - "summary": "List deploy keys", "security": [ { "oauth2": ["repository", "repository:admin"] @@ -3804,10 +3916,12 @@ { "api_key": [] } - ], - "description": "Returns all deploy-keys belonging to a repository.\n\nExample:\n```\n$ curl -H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys\n\nOutput:\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"id\": 123,\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5\",\n \"label\": \"mykey\",\n \"type\": \"deploy_key\",\n \"created_on\": \"2018-08-15T23:50:59.993890+00:00\",\n \"repository\": {\n \"full_name\": \"mleu/test\",\n \"name\": \"test\",\n \"type\": \"repository\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"links\":{\n \"self\":{\n \"href\": \"https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123\"\n }\n }\n \"last_used\": null,\n \"comment\": \"mleu@C02W454JHTD8\"\n }\n ],\n \"page\": 1,\n \"size\": 1\n}\n```" + ] }, "post": { + "tags": ["Deployments"], + "description": "Create a new deploy key in a repository. Note: If authenticating a deploy key\nwith an OAuth consumer, any changes to the OAuth consumer will subsequently\ninvalidate the deploy key.\n\n\nExample:\n```\n$ curl -XPOST \\\n-H \"Authorization \" \\\n-H \"Content-type: application/json\" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys -d \\\n'{\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5 mleu@C02W454JHTD8\",\n \"label\": \"mydeploykey\"\n}'\n\nOutput:\n{\n \"id\": 123,\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5\",\n \"label\": \"mydeploykey\",\n \"type\": \"deploy_key\",\n \"created_on\": \"2018-08-15T23:50:59.993890+00:00\",\n \"repository\": {\n \"full_name\": \"mleu/test\",\n \"name\": \"test\",\n \"type\": \"repository\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"links\":{\n \"self\":{\n \"href\": \"https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123\"\n }\n }\n \"last_used\": null,\n \"comment\": \"mleu@C02W454JHTD8\"\n}\n```", + "summary": "Add a repository deploy key", "responses": { "200": { "description": "The deploy key that was created", @@ -3836,8 +3950,6 @@ } } }, - "tags": ["Deployments"], - "summary": "Add a deploy key", "security": [ { "oauth2": ["repository", "repository:admin"] @@ -3848,8 +3960,7 @@ { "api_key": [] } - ], - "description": "Create a new deploy key in a repository. Note: If authenticating a deploy key\nwith an OAuth consumer, any changes to the OAuth consumer will subsequently\ninvalidate the deploy key.\n\n\nExample:\n```\n$ curl -XPOST \\\n-H \"Authorization \" \\\n-H \"Content-type: application/json\" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys -d \\\n'{\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5 mleu@C02W454JHTD8\",\n \"label\": \"mydeploykey\"\n}'\n\nOutput:\n{\n \"id\": 123,\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5\",\n \"label\": \"mydeploykey\",\n \"type\": \"deploy_key\",\n \"created_on\": \"2018-08-15T23:50:59.993890+00:00\",\n \"repository\": {\n \"full_name\": \"mleu/test\",\n \"name\": \"test\",\n \"type\": \"repository\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"links\":{\n \"self\":{\n \"href\": \"https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/123\"\n }\n }\n \"last_used\": null,\n \"comment\": \"mleu@C02W454JHTD8\"\n}\n```" + ] }, "parameters": [ { @@ -3874,6 +3985,9 @@ }, "/repositories/{workspace}/{repo_slug}/deploy-keys/{key_id}": { "delete": { + "tags": ["Deployments"], + "description": "This deletes a deploy key from a repository.\n\nExample:\n```\n$ curl -XDELETE \\\n-H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/1234\n```", + "summary": "Delete a repository deploy key", "responses": { "204": { "description": "The key has been deleted" @@ -3892,8 +4006,6 @@ } } }, - "tags": ["Deployments"], - "summary": "Delete a deploy key", "security": [ { "oauth2": ["repository", "repository:admin"] @@ -3904,10 +4016,12 @@ { "api_key": [] } - ], - "description": "This deletes a deploy key from a repository.\n\nExample:\n```\n$ curl -XDELETE \\\n-H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/1234\n```" + ] }, "get": { + "tags": ["Deployments"], + "description": "Returns the deploy key belonging to a specific key.\n\nExample:\n```\n$ curl -H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-key/1234\n\nOutput:\n{\n \"comment\": \"mleu@C02W454JHTD8\",\n \"last_used\": null,\n \"links\": {\n \"self\": {\n \"href\": https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-key/1234\"\n }\n },\n \"repository\": {\n \"full_name\": \"mleu/test\",\n \"name\": \"test\",\n \"type\": \"repository\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"label\": \"mykey\",\n \"created_on\": \"2018-08-15T23:50:59.993890+00:00\",\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5\",\n \"id\": 1234,\n \"type\": \"deploy_key\"\n}\n```", + "summary": "Get a repository deploy key", "responses": { "200": { "description": "Deploy key matching the key ID", @@ -3933,8 +4047,6 @@ } } }, - "tags": ["Deployments"], - "summary": "Get a deploy key", "security": [ { "oauth2": ["repository", "repository:admin"] @@ -3945,10 +4057,12 @@ { "api_key": [] } - ], - "description": "Returns the deploy key belonging to a specific key.\n\nExample:\n```\n$ curl -H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-key/1234\n\nOutput:\n{\n \"comment\": \"mleu@C02W454JHTD8\",\n \"last_used\": null,\n \"links\": {\n \"self\": {\n \"href\": https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-key/1234\"\n }\n },\n \"repository\": {\n \"full_name\": \"mleu/test\",\n \"name\": \"test\",\n \"type\": \"repository\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"label\": \"mykey\",\n \"created_on\": \"2018-08-15T23:50:59.993890+00:00\",\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5\",\n \"id\": 1234,\n \"type\": \"deploy_key\"\n}\n```" + ] }, "put": { + "tags": ["Deployments"], + "description": "Create a new deploy key in a repository.\n\nThe same key needs to be passed in but the comment and label can change.\n\nExample:\n```\n$ curl -XPUT \\\n-H \"Authorization \" \\\n-H \"Content-type: application/json\" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/1234 -d \\\n'{\n \"label\": \"newlabel\",\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5 newcomment\",\n}'\n\nOutput:\n{\n \"comment\": \"newcomment\",\n \"last_used\": null,\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/1234\"\n }\n },\n \"repository\": {\n \"full_name\": \"mleu/test\",\n \"name\": \"test\",\n \"type\": \"repository\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"label\": \"newlabel\",\n \"created_on\": \"2018-08-15T23:50:59.993890+00:00\",\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5\",\n \"id\": 1234,\n \"type\": \"deploy_key\"\n}\n```", + "summary": "Update a repository deploy key", "responses": { "200": { "description": "The newly updated deploy key.", @@ -3984,8 +4098,6 @@ } } }, - "tags": ["Deployments"], - "summary": "Update a deploy key", "security": [ { "oauth2": ["repository", "repository:admin"] @@ -3996,8 +4108,7 @@ { "api_key": [] } - ], - "description": "Create a new deploy key in a repository.\n\nThe same key needs to be passed in but the comment and label can change.\n\nExample:\n```\n$ curl -XPUT \\\n-H \"Authorization \" \\\n-H \"Content-type: application/json\" \\\nhttps://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/1234 -d \\\n'{\n \"label\": \"newlabel\",\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5 newcomment\",\n}'\n\nOutput:\n{\n \"comment\": \"newcomment\",\n \"last_used\": null,\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/mleu/test/deploy-keys/1234\"\n }\n },\n \"repository\": {\n \"full_name\": \"mleu/test\",\n \"name\": \"test\",\n \"type\": \"repository\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"label\": \"newlabel\",\n \"created_on\": \"2018-08-15T23:50:59.993890+00:00\",\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5\",\n \"id\": 1234,\n \"type\": \"deploy_key\"\n}\n```" + ] }, "parameters": [ { @@ -4031,6 +4142,30 @@ }, "/repositories/{workspace}/{repo_slug}/deployments/": { "get": { + "tags": ["Deployments"], + "description": "Find deployments", + "summary": "List deployments", + "operationId": "getDeploymentsForRepository", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "The matching deployments.", @@ -4042,35 +4177,44 @@ } } } - }, - "description": "Find deployments", + } + } + }, + "/repositories/{workspace}/{repo_slug}/deployments/{deployment_uuid}": { + "get": { + "tags": ["Deployments"], + "description": "Retrieve a deployment", + "summary": "Get a deployment", + "operationId": "getDeploymentForRepository", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "deployment_uuid", + "description": "The deployment UUID.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "tags": ["Deployments"], - "summary": "List deployments", - "operationId": "getDeploymentsForRepository" - } - }, - "/repositories/{workspace}/{repo_slug}/deployments/{deployment_uuid}": { - "get": { "responses": { "200": { "description": "The deployment.", @@ -4092,46 +4236,105 @@ } } } - }, - "description": "Retrieve a deployment", + } + } + }, + "/repositories/{workspace}/{repo_slug}/deployments_config/environments/{environment_uuid}/variables": { + "get": { + "tags": ["Pipelines"], + "description": "Find deployment environment level variables.", + "summary": "List variables for an environment", + "operationId": "getDeploymentVariables", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { - "description": "The deployment UUID.", + "name": "environment_uuid", + "description": "The environment.", "required": true, - "name": "deployment_uuid", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Deployments"], - "summary": "Get a deployment", - "operationId": "getDeploymentForRepository" - } - }, - "/repositories/{workspace}/{repo_slug}/deployments_config/environments/{environment_uuid}/variables": { + "responses": { + "200": { + "description": "The retrieved deployment variables.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_deployment_variable" + } + } + } + } + } + }, "post": { + "tags": ["Pipelines"], + "description": "Create a deployment environment level variable.", + "summary": "Create a variable for an environment", + "operationId": "createDeploymentVariable", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "environment_uuid", + "description": "The environment.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/deployment_variable" + } + } + }, + "description": "The variable to create", + "required": true + }, "responses": { "201": { + "description": "The variable was created.", "headers": { "Location": { "description": "The URL of the newly created variable.", @@ -4140,7 +4343,6 @@ } } }, - "description": "The variable was created.", "content": { "application/json": { "schema": { @@ -4169,31 +4371,47 @@ } } } - }, - "description": "Create a deployment environment level variable.", + } + } + }, + "/repositories/{workspace}/{repo_slug}/deployments_config/environments/{environment_uuid}/variables/{variable_uuid}": { + "put": { + "tags": ["Pipelines"], + "description": "Update a deployment environment level variable.", + "summary": "Update a variable for an environment", + "operationId": "updateDeploymentVariable", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "environment_uuid", "description": "The environment.", "required": true, - "name": "environment_uuid", + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "variable_uuid", + "description": "The UUID of the variable to update.", + "required": true, "in": "path", "schema": { "type": "string" @@ -4208,63 +4426,9 @@ } } }, - "description": "The variable to create", + "description": "The updated deployment variable.", "required": true }, - "tags": ["Pipelines"], - "summary": "Create a variable for an environment", - "operationId": "createDeploymentVariable" - }, - "get": { - "responses": { - "200": { - "description": "The retrieved deployment variables.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_deployment_variable" - } - } - } - } - }, - "description": "Find deployment environment level variables.", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The environment.", - "required": true, - "name": "environment_uuid", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Pipelines"], - "summary": "List variables for an environment", - "operationId": "getDeploymentVariables" - } - }, - "/repositories/{workspace}/{repo_slug}/deployments_config/environments/{environment_uuid}/variables/{variable_uuid}": { - "put": { "responses": { "200": { "description": "The deployment variable was updated.", @@ -4286,62 +4450,51 @@ } } } - }, - "description": "Update a deployment environment level variable.", + } + }, + "delete": { + "tags": ["Pipelines"], + "description": "Delete a deployment environment level variable.", + "summary": "Delete a variable for an environment", + "operationId": "deleteDeploymentVariable", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "environment_uuid", "description": "The environment.", "required": true, - "name": "environment_uuid", "in": "path", "schema": { "type": "string" } }, { - "description": "The UUID of the variable to update.", - "required": true, "name": "variable_uuid", + "description": "The UUID of the variable to delete.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/deployment_variable" - } - } - }, - "description": "The updated deployment variable.", - "required": true - }, - "tags": ["Pipelines"], - "summary": "Update a variable for an environment", - "operationId": "updateDeploymentVariable" - }, - "delete": { "responses": { "204": { "description": "The variable was deleted." @@ -4356,53 +4509,14 @@ } } } - }, - "description": "Delete a deployment environment level variable.", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The environment.", - "required": true, - "name": "environment_uuid", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The UUID of the variable to delete.", - "required": true, - "name": "variable_uuid", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Pipelines"], - "summary": "Delete a variable for an environment", - "operationId": "deleteDeploymentVariable" + } } }, "/repositories/{workspace}/{repo_slug}/diff/{spec}": { "get": { + "tags": ["Commits"], + "description": "Produces a raw git-style diff.\n\n#### Single commit spec\n\nIf the `spec` argument to this API is a single commit, the diff is\nproduced against the first parent of the specified commit.\n\n#### Two commit spec\n\nTwo commits separated by `..` may be provided as the `spec`, e.g.,\n`3a8b42..9ff173`. When two commits are provided and the `topic` query\nparameter is true or absent, this API produces a 2-way three dot diff.\nThis is the diff between source commit and the merge base of the source\ncommit and the destination commit. When the `topic` query param is false,\na simple git-style diff is produced.\n\nThe two commits are interpreted as follows:\n\n* First commit: the commit containing the changes we wish to preview\n* Second commit: the commit representing the state to which we want to\n compare the first commit\n* **Note**: This is the opposite of the order used in `git diff`.\n\n#### Comparison to patches\n\nWhile similar to patches, diffs:\n\n* Don't have a commit header (username, commit message, etc)\n* Support the optional `path=foo/bar.py` query param to filter\n the diff to just that one file diff\n\n#### Response\n\nThe raw diff is returned as-is, in whatever encoding the files in the\nrepository use. It is not decoded into unicode. As such, the\ncontent-type is `text/plain`.", + "summary": "Compare two commits", "responses": { "200": { "description": "The raw diff" @@ -4483,8 +4597,6 @@ } } ], - "tags": ["Commits"], - "summary": "Compare two commits", "security": [ { "oauth2": ["repository"] @@ -4495,8 +4607,7 @@ { "api_key": [] } - ], - "description": "Produces a raw git-style diff.\n\n#### Single commit spec\n\nIf the `spec` argument to this API is a single commit, the diff is\nproduced against the first parent of the specified commit.\n\n#### Two commit spec\n\nTwo commits separated by `..` may be provided as the `spec`, e.g.,\n`3a8b42..9ff173`. When two commits are provided and the `topic` query\nparameter is true or absent, this API produces a 2-way three dot diff.\nThis is the diff between source commit and the merge base of the source\ncommit and the destination commit. When the `topic` query param is false,\na simple git-style diff is produced.\n\nThe two commits are interpreted as follows:\n\n* First commit: the commit containing the changes we wish to preview\n* Second commit: the commit representing the state to which we want to\n compare the first commit\n* **Note**: This is the opposite of the order used in `git diff`.\n\n#### Comparison to patches\n\nWhile similar to patches, diffs:\n\n* Don't have a commit header (username, commit message, etc)\n* Support the optional `path=foo/bar.py` query param to filter\n the diff to just that one file diff\n\n#### Response\n\nThe raw diff is returned as-is, in whatever encoding the files in the\nrepository use. It is not decoded into unicode. As such, the\ncontent-type is `text/plain`." + ] }, "parameters": [ { @@ -4530,6 +4641,9 @@ }, "/repositories/{workspace}/{repo_slug}/diffstat/{spec}": { "get": { + "tags": ["Commits"], + "description": "Produces a response in JSON format with a record for every path\nmodified, including information on the type of the change and the\nnumber of lines added and removed.\n\n#### Single commit spec\n\nIf the `spec` argument to this API is a single commit, the diff is\nproduced against the first parent of the specified commit.\n\n#### Two commit spec\n\nTwo commits separated by `..` may be provided as the `spec`, e.g.,\n`3a8b42..9ff173`. When two commits are provided and the `topic` query\nparameter is true or absent, this API produces a 2-way three dot diff.\nThis is the diff between source commit and the merge base of the source\ncommit and the destination commit. When the `topic` query param is false,\na simple git-style diff is produced.\n\nThe two commits are interpreted as follows:\n\n* First commit: the commit containing the changes we wish to preview\n* Second commit: the commit representing the state to which we want to\n compare the first commit\n* **Note**: This is the opposite of the order used in `git diff`.\n\n#### Sample output\n```\ncurl https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/diffstat/d222fa2..e174964\n{\n \"pagelen\": 500,\n \"values\": [\n {\n \"type\": \"diffstat\",\n \"status\": \"modified\",\n \"lines_removed\": 1,\n \"lines_added\": 2,\n \"old\": {\n \"path\": \"setup.py\",\n \"escaped_path\": \"setup.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/src/e1749643d655d7c7014001a6c0f58abaf42ad850/setup.py\"\n }\n }\n },\n \"new\": {\n \"path\": \"setup.py\",\n \"escaped_path\": \"setup.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/src/d222fa235229c55dad20b190b0b571adf737d5a6/setup.py\"\n }\n }\n }\n }\n ],\n \"page\": 1,\n \"size\": 1\n}\n```", + "summary": "Compare two commit diff stats", "responses": { "200": { "description": "The diff stats", @@ -4552,8 +4666,6 @@ } } }, - "tags": ["Commits"], - "summary": "Compare two commit diff stats", "security": [ { "oauth2": ["repository"] @@ -4564,8 +4676,7 @@ { "api_key": [] } - ], - "description": "Produces a response in JSON format with a record for every path\nmodified, including information on the type of the change and the\nnumber of lines added and removed.\n\n#### Single commit spec\n\nIf the `spec` argument to this API is a single commit, the diff is\nproduced against the first parent of the specified commit.\n\n#### Two commit spec\n\nTwo commits separated by `..` may be provided as the `spec`, e.g.,\n`3a8b42..9ff173`. When two commits are provided and the `topic` query\nparameter is true or absent, this API produces a 2-way three dot diff.\nThis is the diff between source commit and the merge base of the source\ncommit and the destination commit. When the `topic` query param is false,\na simple git-style diff is produced.\n\nThe two commits are interpreted as follows:\n\n* First commit: the commit containing the changes we wish to preview\n* Second commit: the commit representing the state to which we want to\n compare the first commit\n* **Note**: This is the opposite of the order used in `git diff`.\n\n#### Sample output\n```\ncurl https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/diffstat/d222fa2..e174964\n{\n \"pagelen\": 500,\n \"values\": [\n {\n \"type\": \"diffstat\",\n \"status\": \"modified\",\n \"lines_removed\": 1,\n \"lines_added\": 2,\n \"old\": {\n \"path\": \"setup.py\",\n \"escaped_path\": \"setup.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/src/e1749643d655d7c7014001a6c0f58abaf42ad850/setup.py\"\n }\n }\n },\n \"new\": {\n \"path\": \"setup.py\",\n \"escaped_path\": \"setup.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bitbucket/geordi/src/d222fa235229c55dad20b190b0b571adf737d5a6/setup.py\"\n }\n }\n }\n }\n ],\n \"page\": 1,\n \"size\": 1\n}\n```" + ] }, "parameters": [ { @@ -4644,6 +4755,9 @@ }, "/repositories/{workspace}/{repo_slug}/downloads": { "get": { + "tags": ["Downloads"], + "description": "Returns a list of download links associated with the repository.", + "summary": "List download artifacts", "responses": { "200": { "description": "Returns a paginated list of the downloads associated with the repository." @@ -4659,8 +4773,6 @@ } } }, - "tags": ["Downloads"], - "summary": "List download artifacts", "security": [ { "oauth2": ["repository"] @@ -4671,10 +4783,12 @@ { "api_key": [] } - ], - "description": "Returns a list of download links associated with the repository." + ] }, "post": { + "tags": ["Downloads"], + "description": "Upload new download artifacts.\n\nTo upload files, perform a `multipart/form-data` POST containing one\nor more `files` fields:\n\n $ echo Hello World > hello.txt\n $ curl -s -u evzijst -X POST https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/downloads -F files=@hello.txt\n\nWhen a file is uploaded with the same name as an existing artifact,\nthen the existing file will be replaced.", + "summary": "Upload a download artifact", "responses": { "201": { "description": "The artifact was uploaded sucessfully." @@ -4710,8 +4824,6 @@ } } }, - "tags": ["Downloads"], - "summary": "Upload a download artifact", "security": [ { "oauth2": ["repository:write"] @@ -4722,8 +4834,7 @@ { "api_key": [] } - ], - "description": "Upload new download artifacts.\n\nTo upload files, perform a `multipart/form-data` POST containing one\nor more `files` fields:\n\n $ echo Hello World > hello.txt\n $ curl -s -u evzijst -X POST https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/downloads -F files=@hello.txt\n\nWhen a file is uploaded with the same name as an existing artifact,\nthen the existing file will be replaced." + ] }, "parameters": [ { @@ -4748,6 +4859,9 @@ }, "/repositories/{workspace}/{repo_slug}/downloads/{filename}": { "delete": { + "tags": ["Downloads"], + "description": "Deletes the specified download artifact from the repository.", + "summary": "Delete a download artifact", "responses": { "204": { "description": "The specified download artifact was deleted." @@ -4773,8 +4887,6 @@ } } }, - "tags": ["Downloads"], - "summary": "Delete a download artifact", "security": [ { "oauth2": ["repository:write"] @@ -4785,10 +4897,12 @@ { "api_key": [] } - ], - "description": "Deletes the specified download artifact from the repository." + ] }, "get": { + "tags": ["Downloads"], + "description": "Return a redirect to the contents of a download artifact.\n\nThis endpoint returns the actual file contents and not the artifact's\nmetadata.\n\n $ curl -s -L https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/downloads/hello.txt\n Hello World", + "summary": "Get a download artifact link", "responses": { "302": { "description": "Redirects to the url of the specified download artifact." @@ -4814,8 +4928,6 @@ } } }, - "tags": ["Downloads"], - "summary": "Get a download artifact link", "security": [ { "oauth2": ["repository"] @@ -4826,8 +4938,7 @@ { "api_key": [] } - ], - "description": "Return a redirect to the contents of a download artifact.\n\nThis endpoint returns the actual file contents and not the artifact's\nmetadata.\n\n $ curl -s -L https://api.bitbucket.org/2.0/repositories/evzijst/git-tests/downloads/hello.txt\n Hello World" + ] }, "parameters": [ { @@ -4859,10 +4970,164 @@ } ] }, + "/repositories/{workspace}/{repo_slug}/effective-branching-model": { + "get": { + "tags": ["Branching model"], + "description": "", + "summary": "Get the effective, or currently applied, branching model for a repository", + "responses": { + "200": { + "description": "The effective branching model object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/effective_repo_branching_model" + } + } + } + }, + "401": { + "description": "If the request was not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have read access to the repository", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the repository does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "parameters": [ + { + "name": "repo_slug", + "in": "path", + "description": "This can either be the repository slug or the UUID of the repository,\nsurrounded by curly-braces, for example: `{repository UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, "/repositories/{workspace}/{repo_slug}/environments/": { + "get": { + "tags": ["Deployments"], + "description": "Find environments", + "summary": "List environments", + "operationId": "getEnvironmentsForRepository", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The matching environments.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_environments" + } + } + } + } + } + }, "post": { + "tags": ["Deployments"], + "description": "Create an environment.", + "summary": "Create an environment", + "operationId": "createEnvironment", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/deployment_environment" + } + } + }, + "description": "The environment to create.", + "required": true + }, "responses": { "201": { + "description": "The environment was created.", "headers": { "Location": { "description": "The URL of the newly created environment.", @@ -4871,7 +5136,6 @@ } } }, - "description": "The environment was created.", "content": { "application/json": { "schema": { @@ -4900,84 +5164,44 @@ } } } - }, - "description": "Create an environment.", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/deployment_environment" - } - } - }, - "description": "The environment to create.", - "required": true - }, - "tags": ["Deployments"], - "summary": "Create an environment", - "operationId": "createEnvironment" - }, - "get": { - "responses": { - "200": { - "description": "The matching environments.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_environments" - } - } - } - } - }, - "description": "Find environments", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Deployments"], - "summary": "List environments", - "operationId": "getEnvironmentsForRepository" + } } }, "/repositories/{workspace}/{repo_slug}/environments/{environment_uuid}": { "get": { + "tags": ["Deployments"], + "summary": "Get an environment", + "description": "Retrieve an environment", + "operationId": "getEnvironmentForRepository", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "environment_uuid", + "description": "The environment UUID.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "The environment.", @@ -4999,42 +5223,42 @@ } } } - }, - "description": "Retrieve an environment", + } + }, + "delete": { + "tags": ["Deployments"], + "description": "Delete an environment", + "summary": "Delete an environment", + "operationId": "deleteEnvironmentForRepository", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "environment_uuid", "description": "The environment UUID.", "required": true, - "name": "environment_uuid", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Deployments"], - "summary": "Get an environment", - "operationId": "getEnvironmentForRepository" - }, - "delete": { "responses": { "204": { "description": "The environment was deleted." @@ -5049,44 +5273,44 @@ } } } - }, - "description": "Delete an environment", + } + } + }, + "/repositories/{workspace}/{repo_slug}/environments/{environment_uuid}/changes/": { + "post": { + "tags": ["Deployments"], + "description": "Update an environment", + "summary": "Update an environment", + "operationId": "updateEnvironmentForRepository", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "environment_uuid", "description": "The environment UUID.", "required": true, - "name": "environment_uuid", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Deployments"], - "summary": "Delete an environment", - "operationId": "deleteEnvironmentForRepository" - } - }, - "/repositories/{workspace}/{repo_slug}/environments/{environment_uuid}/changes/": { - "post": { "responses": { "202": { "description": "The environment update request was accepted." @@ -5101,44 +5325,14 @@ } } } - }, - "description": "Update an environment", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The environment UUID.", - "required": true, - "name": "environment_uuid", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Deployments"], - "summary": "Update an environment", - "operationId": "updateEnvironmentForRepository" + } } }, "/repositories/{workspace}/{repo_slug}/filehistory/{commit}/{path}": { "get": { + "tags": ["Source", "Repositories"], + "description": "Returns a paginated list of commits that modified the specified file.\n\nCommits are returned in reverse chronological order. This is roughly\nequivalent to the following commands:\n\n $ git log --follow --date-order \n\nBy default, Bitbucket will follow renames and the path name in the\nreturned entries reflects that. This can be turned off using the\n`?renames=false` query parameter.\n\nResults are returned in descending chronological order by default, and\nlike most endpoints you can\n[filter and sort](/cloud/bitbucket/rest/intro/#filtering) the response to\nonly provide exactly the data you want.\n\nFor example, if you wanted to find commits made before 2011-05-18\nagainst a file named `README.rst`, but you only wanted the path and\ndate, your query would look like this:\n\n```\n$ curl 'https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/filehistory/master/README.rst'\\\n '?fields=values.next,values.path,values.commit.date&q=commit.date<=2011-05-18'\n{\n \"values\": [\n {\n \"commit\": {\n \"date\": \"2011-05-17T07:32:09+00:00\"\n },\n \"path\": \"README.rst\"\n },\n {\n \"commit\": {\n \"date\": \"2011-05-16T06:33:28+00:00\"\n },\n \"path\": \"README.txt\"\n },\n {\n \"commit\": {\n \"date\": \"2011-05-16T06:15:39+00:00\"\n },\n \"path\": \"README.txt\"\n }\n ]\n}\n```\n\nIn the response you can see that the file was renamed to `README.rst`\nby the commit made on 2011-05-16, and was previously named `README.txt`.", + "summary": "List commits that modified a file", "responses": { "200": { "description": "A paginated list of commits that modified the specified file", @@ -5190,8 +5384,6 @@ } } ], - "tags": ["Source", "Repositories"], - "summary": "List commits that modified a file", "security": [ { "oauth2": ["repository"] @@ -5202,8 +5394,7 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of commits that modified the specified file.\n\nCommits are returned in reverse chronological order. This is roughly\nequivalent to the following commands:\n\n $ git log --follow --date-order \n\nBy default, Bitbucket will follow renames and the path name in the\nreturned entries reflects that. This can be turned off using the\n`?renames=false` query parameter.\n\nResults are returned in descending chronological order by default, and\nlike most endpoints you can\n[filter and sort](/cloud/bitbucket/rest/intro/#filtering) the response to\nonly provide exactly the data you want.\n\nFor example, if you wanted to find commits made before 2011-05-18\nagainst a file named `README.rst`, but you only wanted the path and\ndate, your query would look like this:\n\n```\n$ curl 'https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/filehistory/master/README.rst'\\\n '?fields=values.next,values.path,values.commit.date&q=commit.date<=2011-05-18'\n{\n \"values\": [\n {\n \"commit\": {\n \"date\": \"2011-05-17T07:32:09+00:00\"\n },\n \"path\": \"README.rst\"\n },\n {\n \"commit\": {\n \"date\": \"2011-05-16T06:33:28+00:00\"\n },\n \"path\": \"README.txt\"\n },\n {\n \"commit\": {\n \"date\": \"2011-05-16T06:15:39+00:00\"\n },\n \"path\": \"README.txt\"\n }\n ]\n}\n```\n\nIn the response you can see that the file was renamed to `README.rst`\nby the commit made on 2011-05-16, and was previously named `README.txt`." + ] }, "parameters": [ { @@ -5246,6 +5437,9 @@ }, "/repositories/{workspace}/{repo_slug}/forks": { "get": { + "tags": ["Repositories"], + "description": "Returns a paginated list of all the forks of the specified\nrepository.", + "summary": "List repository forks", "responses": { "200": { "description": "All forks.", @@ -5288,8 +5482,6 @@ } } ], - "tags": ["Repositories"], - "summary": "List repository forks", "security": [ { "oauth2": ["repository"] @@ -5300,10 +5492,12 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of all the forks of the specified\nrepository." + ] }, "post": { + "tags": ["Repositories"], + "description": "Creates a new fork of the specified repository.\n\n#### Forking a repository\n\nTo create a fork, specify the workspace explicitly as part of the\nrequest body:\n\n```\n$ curl -X POST -u jdoe https://api.bitbucket.org/2.0/repositories/atlassian/bbql/forks \\\n -H 'Content-Type: application/json' -d '{\n \"name\": \"bbql_fork\",\n \"workspace\": {\n \"slug\": \"atlassian\"\n }\n}'\n```\n\nTo fork a repository into the same workspace, also specify a new `name`.\n\nWhen you specify a value for `name`, it will also affect the `slug`.\nThe `slug` is reflected in the repository URL of the new fork. It is\nderived from `name` by substituting non-ASCII characters, removes\nwhitespace, and changes characters to lower case. For example,\n`My repo` would turn into `my_repo`.\n\nYou need contributor access to create new forks within a workspace.\n\n\n#### Change the properties of a new fork\n\nBy default the fork inherits most of its properties from the parent.\nHowever, since the optional POST body document follows the normal\n`repository` JSON schema and you can override the new fork's\nproperties.\n\nProperties that can be overridden include:\n\n* description\n* fork_policy\n* language\n* mainbranch\n* is_private (note that a private repo's fork_policy might prohibit\n the creation of public forks, in which `is_private=False` would fail)\n* has_issues (to initialize or disable the new repo's issue tracker --\n note that the actual contents of the parent repository's issue\n tracker are not copied during forking)\n* has_wiki (to initialize or disable the new repo's wiki --\n note that the actual contents of the parent repository's wiki are not\n copied during forking)\n* project (when forking into a private project, the fork's `is_private`\n must be `true`)\n\nProperties that cannot be modified include:\n\n* scm\n* parent\n* full_name", + "summary": "Fork a repository", "responses": { "201": { "description": "The newly created fork.", @@ -5334,8 +5528,6 @@ }, "description": "A repository object. This can be left blank." }, - "tags": ["Repositories"], - "summary": "Fork a repository", "security": [ { "oauth2": ["repository:write"] @@ -5346,8 +5538,7 @@ { "api_key": [] } - ], - "description": "Creates a new fork of the specified repository.\n\n#### Forking a repository\n\nTo create a fork, specify the workspace explicitly as part of the\nrequest body:\n\n```\n$ curl -X POST -u jdoe https://api.bitbucket.org/2.0/repositories/atlassian/bbql/forks \\\n -H 'Content-Type: application/json' -d '{\n \"name\": \"bbql_fork\",\n \"workspace\": {\n \"slug\": \"atlassian\"\n }\n}'\n```\n\nTo fork a repository into the same workspace, also specify a new `name`.\n\nWhen you specify a value for `name`, it will also affect the `slug`.\nThe `slug` is reflected in the repository URL of the new fork. It is\nderived from `name` by substituting non-ASCII characters, removes\nwhitespace, and changes characters to lower case. For example,\n`My repo` would turn into `my_repo`.\n\nYou need contributor access to create new forks within a workspace.\n\n\n#### Change the properties of a new fork\n\nBy default the fork inherits most of its properties from the parent.\nHowever, since the optional POST body document follows the normal\n`repository` JSON schema and you can override the new fork's\nproperties.\n\nProperties that can be overridden include:\n\n* description\n* fork_policy\n* language\n* mainbranch\n* is_private (note that a private repo's fork_policy might prohibit\n the creation of public forks, in which `is_private=False` would fail)\n* has_issues (to initialize or disable the new repo's issue tracker --\n note that the actual contents of the parent repository's issue\n tracker are not copied during forking)\n* has_wiki (to initialize or disable the new repo's wiki --\n note that the actual contents of the parent repository's wiki are not\n copied during forking)\n* project (when forking into a private project, the fork's `is_private`\n must be `true`)\n\nProperties that cannot be modified include:\n\n* scm\n* parent\n* full_name" + ] }, "parameters": [ { @@ -5372,6 +5563,9 @@ }, "/repositories/{workspace}/{repo_slug}/hooks": { "get": { + "tags": ["Repositories", "Webhooks"], + "description": "Returns a paginated list of webhooks installed on this repository.", + "summary": "List webhooks for a repository", "responses": { "200": { "description": "The paginated list of installed webhooks.", @@ -5404,8 +5598,6 @@ } } }, - "tags": ["Repositories", "Webhooks"], - "summary": "List webhooks for a repository", "security": [ { "oauth2": ["webhook"] @@ -5416,10 +5608,12 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of webhooks installed on this repository." + ] }, "post": { + "tags": ["Repositories", "Webhooks"], + "description": "Creates a new webhook on the specified repository.\n\nExample:\n\n```\n$ curl -X POST -u credentials -H 'Content-Type: application/json'\n https://api.bitbucket.org/2.0/repositories/my-workspace/my-repo-slug/hooks\n -d '\n {\n \"description\": \"Webhook Description\",\n \"url\": \"https://example.com/\",\n \"active\": true,\n \"events\": [\n \"repo:push\",\n \"issue:created\",\n \"issue:updated\"\n ]\n }'\n```\n\nNote that this call requires the webhook scope, as well as any scope\nthat applies to the events that the webhook subscribes to. In the\nexample above that means: `webhook`, `repository` and `issue`.\n\nAlso note that the `url` must properly resolve and cannot be an\ninternal, non-routed address.", + "summary": "Create a webhook for a repository", "responses": { "201": { "description": "If the webhook was registered successfully.", @@ -5460,8 +5654,6 @@ } } }, - "tags": ["Repositories", "Webhooks"], - "summary": "Create a webhook for a repository", "security": [ { "oauth2": ["webhook"] @@ -5472,8 +5664,7 @@ { "api_key": [] } - ], - "description": "Creates a new webhook on the specified repository.\n\nExample:\n\n```\n$ curl -X POST -u credentials -H 'Content-Type: application/json'\n https://api.bitbucket.org/2.0/repositories/my-workspace/my-repo-slug/hooks\n -d '\n {\n \"description\": \"Webhook Description\",\n \"url\": \"https://example.com/\",\n \"active\": true,\n \"events\": [\n \"repo:push\",\n \"issue:created\",\n \"issue:updated\"\n ]\n }'\n```\n\nNote that this call requires the webhook scope, as well as any scope\nthat applies to the events that the webhook subscribes to. In the\nexample above that means: `webhook`, `repository` and `issue`.\n\nAlso note that the `url` must properly resolve and cannot be an\ninternal, non-routed address." + ] }, "parameters": [ { @@ -5498,6 +5689,9 @@ }, "/repositories/{workspace}/{repo_slug}/hooks/{uid}": { "delete": { + "tags": ["Repositories", "Webhooks"], + "description": "Deletes the specified webhook subscription from the given\nrepository.", + "summary": "Delete a webhook for a repository", "responses": { "204": { "description": "When the webhook was deleted successfully" @@ -5523,8 +5717,6 @@ } } }, - "tags": ["Repositories", "Webhooks"], - "summary": "Delete a webhook for a repository", "security": [ { "oauth2": ["webhook"] @@ -5535,10 +5727,12 @@ { "api_key": [] } - ], - "description": "Deletes the specified webhook subscription from the given\nrepository." + ] }, "get": { + "tags": ["Repositories", "Webhooks"], + "description": "Returns the webhook with the specified id installed on the specified\nrepository.", + "summary": "Get a webhook for a repository", "responses": { "200": { "description": "The webhook subscription object.", @@ -5561,8 +5755,6 @@ } } }, - "tags": ["Repositories", "Webhooks"], - "summary": "Get a webhook for a repository", "security": [ { "oauth2": ["webhook"] @@ -5573,10 +5765,12 @@ { "api_key": [] } - ], - "description": "Returns the webhook with the specified id installed on the specified\nrepository." + ] }, "put": { + "tags": ["Repositories", "Webhooks"], + "description": "Updates the specified webhook subscription.\n\nThe following properties can be mutated:\n\n* `description`\n* `url`\n* `active`\n* `events`", + "summary": "Update a webhook for a repository", "responses": { "200": { "description": "The webhook subscription object.", @@ -5609,8 +5803,6 @@ } } }, - "tags": ["Repositories", "Webhooks"], - "summary": "Update a webhook for a repository", "security": [ { "oauth2": ["webhook"] @@ -5621,8 +5813,7 @@ { "api_key": [] } - ], - "description": "Updates the specified webhook subscription.\n\nThe following properties can be mutated:\n\n* `description`\n* `url`\n* `active`\n* `events`" + ] }, "parameters": [ { @@ -5656,6 +5847,9 @@ }, "/repositories/{workspace}/{repo_slug}/issues": { "get": { + "tags": ["Issue tracker"], + "description": "Returns the issues in the issue tracker.", + "summary": "List issues", "responses": { "200": { "description": "A paginated list of the issues matching any filter criteria that were provided.", @@ -5678,8 +5872,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "List issues", "security": [ { "oauth2": ["issue"] @@ -5690,10 +5882,12 @@ { "api_key": [] } - ], - "description": "Returns the issues in the issue tracker." + ] }, "post": { + "tags": ["Issue tracker"], + "description": "Creates a new issue.\n\nThis call requires authentication. Private repositories or private\nissue trackers require the caller to authenticate with an account that\nhas appropriate authorization.\n\nThe authenticated user is used for the issue's `reporter` field.", + "summary": "Create an issue", "responses": { "201": { "description": "The newly created issue.", @@ -5755,8 +5949,6 @@ "description": "The new issue. The only required element is `title`. All other elements can be omitted from the body.", "required": true }, - "tags": ["Issue tracker"], - "summary": "Create an issue", "security": [ { "oauth2": ["issue:write"] @@ -5767,8 +5959,7 @@ { "api_key": [] } - ], - "description": "Creates a new issue.\n\nThis call requires authentication. Private repositories or private\nissue trackers require the caller to authenticate with an account that\nhas appropriate authorization.\n\nThe authenticated user is used for the issue's `reporter` field." + ] }, "parameters": [ { @@ -5793,6 +5984,9 @@ }, "/repositories/{workspace}/{repo_slug}/issues/export": { "post": { + "tags": ["Issue tracker"], + "description": "A POST request to this endpoint initiates a new background celery task that archives the repo's issues.\n\nFor example, you can run:\n\ncurl -u -X POST http://api.bitbucket.org/2.0/repositories///\nissues/export\n\nWhen the job has been accepted, it will return a 202 (Accepted) along with a unique url to this job in the\n'Location' response header. This url is the endpoint for where the user can obtain their zip files.\"", + "summary": "Export issues", "responses": { "202": { "description": "The export job has been accepted" @@ -5838,8 +6032,6 @@ }, "description": "The options to apply to the export. Available options include `project_key` and `project_name` which, if specified, are used as the project key and name in the exported Jira json format. Option `send_email` specifies whether an email should be sent upon export result. Option `include_attachments` specifies whether attachments are included in the export." }, - "tags": ["Issue tracker"], - "summary": "Export issues", "security": [ { "oauth2": ["issue", "repository:admin"] @@ -5850,8 +6042,7 @@ { "api_key": [] } - ], - "description": "A POST request to this endpoint initiates a new background celery task that archives the repo's issues.\n\nFor example, you can run:\n\ncurl -u -X POST http://api.bitbucket.org/2.0/repositories///\nissues/export\n\nWhen the job has been accepted, it will return a 202 (Accepted) along with a unique url to this job in the\n'Location' response header. This url is the endpoint for where the user can obtain their zip files.\"" + ] }, "parameters": [ { @@ -5876,6 +6067,9 @@ }, "/repositories/{workspace}/{repo_slug}/issues/export/{repo_name}-issues-{task_id}.zip": { "get": { + "tags": ["Issue tracker"], + "description": "This endpoint is used to poll for the progress of an issue export\njob and return the zip file after the job is complete.\nAs long as the job is running, this will return a 200 response\nwith in the response body a description of the current status.\n\nAfter the job has been scheduled, but before it starts executing, this\nendpoint's response is:\n\n{\n \"type\": \"issue_job_status\",\n \"status\": \"ACCEPTED\",\n \"phase\": \"Initializing\",\n \"total\": 0,\n \"count\": 0,\n \"pct\": 0\n}\n\n\nThen once it starts running, it becomes:\n\n{\n \"type\": \"issue_job_status\",\n \"status\": \"STARTED\",\n \"phase\": \"Attachments\",\n \"total\": 15,\n \"count\": 11,\n \"pct\": 73\n}\n\nOnce the job has successfully completed, it returns a stream of the zip file.", + "summary": "Check issue export status", "responses": { "202": { "description": "Export job accepted", @@ -5918,8 +6112,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Check issue export status", "security": [ { "oauth2": ["issue", "repository:admin"] @@ -5930,8 +6122,7 @@ { "api_key": [] } - ], - "description": "This endpoint is used to poll for the progress of an issue export\njob and return the zip file after the job is complete.\nAs long as the job is running, this will return a 200 response\nwith in the response body a description of the current status.\n\nAfter the job has been scheduled, but before it starts executing, this\nendpoint's response is:\n\n{\n \"type\": \"issue_job_status\",\n \"status\": \"ACCEPTED\",\n \"phase\": \"Initializing\",\n \"total\": 0,\n \"count\": 0,\n \"pct\": 0\n}\n\n\nThen once it starts running, it becomes:\n\n{\n \"type\": \"issue_job_status\",\n \"status\": \"STARTED\",\n \"phase\": \"Attachments\",\n \"total\": 15,\n \"count\": 11,\n \"pct\": 73\n}\n\nOnce the job has successfully completed, it returns a stream of the zip file." + ] }, "parameters": [ { @@ -5974,6 +6165,9 @@ }, "/repositories/{workspace}/{repo_slug}/issues/import": { "get": { + "tags": ["Issue tracker"], + "description": "When using GET, this endpoint reports the status of the current import task. Request example:\n\n```\n$ curl -u -X GET https://api.bitbucket.org/2.0/repositories///issues/import\n```\n\nAfter the job has been scheduled, but before it starts executing, this endpoint's response is:\n\n```\n< HTTP/1.1 202 Accepted\n{\n \"type\": \"issue_job_status\",\n \"status\": \"PENDING\",\n \"phase\": \"Attachments\",\n \"total\": 15,\n \"count\": 0,\n \"percent\": 0\n}\n```\n\nOnce it starts running, it is a 202 response with status STARTED and progress filled.\n\nAfter it is finished, it becomes a 200 response with status SUCCESS or FAILURE.", + "summary": "Check issue import status", "responses": { "200": { "description": "Import job complete with either FAILURE or SUCCESS status", @@ -6026,8 +6220,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Check issue import status", "security": [ { "oauth2": ["issue:write", "repository:admin"] @@ -6038,10 +6230,12 @@ { "api_key": [] } - ], - "description": "When using GET, this endpoint reports the status of the current import task. Request example:\n\n```\n$ curl -u -X GET https://api.bitbucket.org/2.0/repositories///issues/import\n```\n\nAfter the job has been scheduled, but before it starts executing, this endpoint's response is:\n\n```\n< HTTP/1.1 202 Accepted\n{\n \"type\": \"issue_job_status\",\n \"status\": \"PENDING\",\n \"phase\": \"Attachments\",\n \"total\": 15,\n \"count\": 0,\n \"percent\": 0\n}\n```\n\nOnce it starts running, it is a 202 response with status STARTED and progress filled.\n\nAfter it is finished, it becomes a 200 response with status SUCCESS or FAILURE." + ] }, "post": { + "tags": ["Issue tracker"], + "description": "A POST request to this endpoint will import the zip file given by the archive parameter into the repository. All\nexisting issues will be deleted and replaced by the contents of the imported zip file.\n\nImports are done through a multipart/form-data POST. There is one valid and required form field, with the name\n\"archive,\" which needs to be a file field:\n\n```\n$ curl -u -X POST -F archive=@/path/to/file.zip https://api.bitbucket.org/2.0/repositories///issues/import\n```\n\nWhen the import job is accepted, here is example output:\n\n```\n< HTTP/1.1 202 Accepted\n\n{\n \"type\": \"issue_job_status\",\n \"status\": \"ACCEPTED\",\n \"phase\": \"Attachments\",\n \"total\": 15,\n \"count\": 0,\n \"percent\": 0\n}\n```", + "summary": "Import issues", "responses": { "202": { "description": "Import job accepted", @@ -6094,8 +6288,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Import issues", "security": [ { "oauth2": ["issue:write", "repository:admin"] @@ -6106,8 +6298,7 @@ { "api_key": [] } - ], - "description": "A POST request to this endpoint will import the zip file given by the archive parameter into the repository. All\nexisting issues will be deleted and replaced by the contents of the imported zip file.\n\nImports are done through a multipart/form-data POST. There is one valid and required form field, with the name\n\"archive,\" which needs to be a file field:\n\n```\n$ curl -u -X POST -F archive=@/path/to/file.zip https://api.bitbucket.org/2.0/repositories///issues/import\n```\n\nWhen the import job is accepted, here is example output:\n\n```\n< HTTP/1.1 202 Accepted\n\n{\n \"type\": \"issue_job_status\",\n \"status\": \"ACCEPTED\",\n \"phase\": \"Attachments\",\n \"total\": 15,\n \"count\": 0,\n \"percent\": 0\n}\n```" + ] }, "parameters": [ { @@ -6132,6 +6323,9 @@ }, "/repositories/{workspace}/{repo_slug}/issues/{issue_id}": { "delete": { + "tags": ["Issue tracker"], + "description": "Deletes the specified issue. This requires write access to the\nrepository.", + "summary": "Delete an issue", "responses": { "200": { "description": "The issue object.", @@ -6164,8 +6358,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Delete an issue", "security": [ { "oauth2": ["issue:write"] @@ -6176,10 +6368,12 @@ { "api_key": [] } - ], - "description": "Deletes the specified issue. This requires write access to the\nrepository." + ] }, "get": { + "tags": ["Issue tracker"], + "description": "Returns the specified issue.", + "summary": "Get an issue", "responses": { "200": { "description": "The issue object.", @@ -6222,8 +6416,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Get an issue", "security": [ { "oauth2": ["issue"] @@ -6234,10 +6426,12 @@ { "api_key": [] } - ], - "description": "Returns the specified issue." + ] }, "put": { + "tags": ["Issue tracker"], + "description": "Modifies the issue.\n\n```\n$ curl https://api.bitbucket.org/2.0/repostories/evzijst/dogslow/issues/123 \\\n -u evzijst -s -X PUT -H 'Content-Type: application/json' \\\n -d '{\n \"title\": \"Updated title\",\n \"assignee\": {\n \"account_id\": \"5d5355e8c6b9320d9ea5b28d\"\n },\n \"priority\": \"minor\",\n \"version\": {\n \"name\": \"1.0\"\n },\n \"component\": null\n}'\n```\n\nThis example changes the `title`, `assignee`, `priority` and the\n`version`. It also removes the value of the `component` from the issue\nby setting the field to `null`. Any field not present keeps its existing\nvalue.\n\nEach time an issue is edited in the UI or through the API, an immutable\nchange record is created under the `/issues/123/changes` endpoint. It\nalso has a comment associated with the change.", + "summary": "Update an issue", "responses": { "200": { "description": "The updated issue object.", @@ -6270,8 +6464,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Update an issue", "security": [ { "oauth2": ["issue:write"] @@ -6282,8 +6474,7 @@ { "api_key": [] } - ], - "description": "Modifies the issue.\n\n```\n$ curl https://api.bitbucket.org/2.0/repostories/evzijst/dogslow/issues/123 \\\n -u evzijst -s -X PUT -H 'Content-Type: application/json' \\\n -d '{\n \"title\": \"Updated title\",\n \"assignee\": {\n \"username\": \"evzijst\"\n },\n \"priority\": \"minor\",\n \"version\": {\n \"name\": \"1.0\"\n },\n \"component\": null\n}'\n```\n\nThis example changes the `title`, `assignee`, `priority` and the\n`version`. It also removes the value of the `component` from the issue\nby setting the field to `null`. Any field not present keeps its existing\nvalue.\n\nEach time an issue is edited in the UI or through the API, an immutable\nchange record is created under the `/issues/123/changes` endpoint. It\nalso has a comment associated with the change." + ] }, "parameters": [ { @@ -6317,6 +6508,9 @@ }, "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/attachments": { "get": { + "tags": ["Issue tracker"], + "description": "Returns all attachments for this issue.\n\nThis returns the files' meta data. This does not return the files'\nactual contents.\n\nThe files are always ordered by their upload date.", + "summary": "List attachments for an issue", "responses": { "200": { "description": "A paginated list of all attachments for this issue.", @@ -6342,8 +6536,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "List attachments for an issue", "security": [ { "oauth2": ["issue"] @@ -6354,10 +6546,12 @@ { "api_key": [] } - ], - "description": "Returns all attachments for this issue.\n\nThis returns the files' meta data. This does not return the files'\nactual contents.\n\nThe files are always ordered by their upload date." + ] }, "post": { + "tags": ["Issue tracker"], + "description": "Upload new issue attachments.\n\nTo upload files, perform a `multipart/form-data` POST containing one\nor more file fields.\n\nWhen a file is uploaded with the same name as an existing attachment,\nthen the existing file will be replaced.", + "summary": "Upload an attachment to an issue", "responses": { "201": { "description": "An empty response document.", @@ -6387,8 +6581,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Upload an attachment to an issue", "security": [ { "oauth2": ["issue:write"] @@ -6399,8 +6591,7 @@ { "api_key": [] } - ], - "description": "Upload new issue attachments.\n\nTo upload files, perform a `multipart/form-data` POST containing one\nor more file fields.\n\nWhen a file is uploaded with the same name as an existing attachment,\nthen the existing file will be replaced." + ] }, "parameters": [ { @@ -6434,6 +6625,9 @@ }, "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/attachments/{path}": { "delete": { + "tags": ["Issue tracker"], + "description": "Deletes an attachment.", + "summary": "Delete an attachment for an issue", "responses": { "204": { "description": "Indicates that the deletion was successful" @@ -6452,8 +6646,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Delete an attachment for an issue", "security": [ { "oauth2": ["issue:write"] @@ -6464,10 +6656,12 @@ { "api_key": [] } - ], - "description": "Deletes an attachment." + ] }, "get": { + "tags": ["Issue tracker"], + "description": "Returns the contents of the specified file attachment.\n\nNote that this endpoint does not return a JSON response, but instead\nreturns a redirect pointing to the actual file that in turn will return\nthe raw contents.\n\nThe redirect URL contains a one-time token that has a limited lifetime.\nAs a result, the link should not be persisted, stored, or shared.", + "summary": "Get attachment for an issue", "responses": { "302": { "description": "A redirect to the file's contents", @@ -6493,8 +6687,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Get attachment for an issue", "security": [ { "oauth2": ["issue"] @@ -6505,8 +6697,7 @@ { "api_key": [] } - ], - "description": "Returns the contents of the specified file attachment.\n\nNote that this endpoint does not return a JSON response, but instead\nreturns a redirect pointing to the actual file that in turn will return\nthe raw contents.\n\nThe redirect URL contains a one-time token that has a limited lifetime.\nAs a result, the link should not be persisted, stored, or shared." + ] }, "parameters": [ { @@ -6549,6 +6740,9 @@ }, "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/changes": { "get": { + "tags": ["Issue tracker"], + "description": "Returns the list of all changes that have been made to the specified\nissue. Changes are returned in chronological order with the oldest\nchange first.\n\nEach time an issue is edited in the UI or through the API, an immutable\nchange record is created under the `/issues/123/changes` endpoint. It\nalso has a comment associated with the change.\n\nNote that this operation is changing significantly, due to privacy changes.\nSee the [announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-changes-gdpr/#changes-to-the-issue-changes-api)\nfor details.\n\n```\n$ curl -s https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/issues/1/changes - | jq .\n\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"changes\": {\n \"priority\": {\n \"new\": \"trivial\",\n \"old\": \"major\"\n },\n \"assignee\": {\n \"new\": \"\",\n \"old\": \"evzijst\"\n },\n \"assignee_account_id\": {\n \"new\": \"\",\n \"old\": \"557058:c0b72ad0-1cb5-4018-9cdc-0cde8492c443\"\n },\n \"kind\": {\n \"new\": \"enhancement\",\n \"old\": \"bug\"\n }\n },\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/issues/1/changes/2\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst/dogslow/issues/1#comment-2\"\n }\n },\n \"issue\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/issues/1\"\n }\n },\n \"type\": \"issue\",\n \"id\": 1,\n \"repository\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/evzijst/dogslow\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst/dogslow\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/evzijst/dogslow/avatar/32/\"\n }\n },\n \"type\": \"repository\",\n \"name\": \"dogslow\",\n \"full_name\": \"evzijst/dogslow\",\n \"uuid\": \"{988b17c6-1a47-4e70-84ee-854d5f012bf6}\"\n },\n \"title\": \"Updated title\"\n },\n \"created_on\": \"2018-03-03T00:35:28.353630+00:00\",\n \"user\": {\n \"username\": \"evzijst\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"evzijst\",\n \"type\": \"user\",\n \"uuid\": \"{aaa7972b-38af-4fb1-802d-6e3854c95778}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/evzijst\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst/\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/evzijst/avatar/32/\"\n }\n }\n },\n \"message\": {\n \"raw\": \"Removed assignee, changed kind and priority.\",\n \"markup\": \"markdown\",\n \"html\": \"

Removed assignee, changed kind and priority.

\",\n \"type\": \"rendered\"\n },\n \"type\": \"issue_change\",\n \"id\": 2\n }\n ],\n \"page\": 1\n}\n```\n\nChanges support [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) that\ncan be used to search for specific changes. For instance, to see\nwhen an issue transitioned to \"resolved\":\n\n```\n$ curl -s https://api.bitbucket.org/2.0/repositories/site/master/issues/1/changes \\\n -G --data-urlencode='q=changes.state.new = \"resolved\"'\n```\n\nThis resource is only available on repositories that have the issue\ntracker enabled.\n\nN.B.\n\nThe `changes.assignee` and `changes.assignee_account_id` fields are not\na `user` object. Instead, they contain the raw `username` and\n`account_id` of the user. This is to protect the integrity of the audit\nlog even after a user account gets deleted.\n\nThe `changes.assignee` field is deprecated will disappear in the\nfuture. Use `changes.assignee_account_id` instead.", + "summary": "List changes on an issue", "responses": { "200": { "description": "Returns all the issue changes that were made on the specified issue.", @@ -6591,8 +6785,6 @@ } } ], - "tags": ["Issue tracker"], - "summary": "List changes on an issue", "security": [ { "oauth2": ["issue"] @@ -6603,10 +6795,12 @@ { "api_key": [] } - ], - "description": "Returns the list of all changes that have been made to the specified\nissue. Changes are returned in chronological order with the oldest\nchange first.\n\nEach time an issue is edited in the UI or through the API, an immutable\nchange record is created under the `/issues/123/changes` endpoint. It\nalso has a comment associated with the change.\n\nNote that this operation is changing significantly, due to privacy changes.\nSee the [announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-changes-gdpr/#changes-to-the-issue-changes-api)\nfor details.\n\n```\n$ curl -s https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/issues/1/changes - | jq .\n\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"changes\": {\n \"priority\": {\n \"new\": \"trivial\",\n \"old\": \"major\"\n },\n \"assignee\": {\n \"new\": \"\",\n \"old\": \"evzijst\"\n },\n \"assignee_account_id\": {\n \"new\": \"\",\n \"old\": \"557058:c0b72ad0-1cb5-4018-9cdc-0cde8492c443\"\n },\n \"kind\": {\n \"new\": \"enhancement\",\n \"old\": \"bug\"\n }\n },\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/issues/1/changes/2\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst/dogslow/issues/1#comment-2\"\n }\n },\n \"issue\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/evzijst/dogslow/issues/1\"\n }\n },\n \"type\": \"issue\",\n \"id\": 1,\n \"repository\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/evzijst/dogslow\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst/dogslow\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/evzijst/dogslow/avatar/32/\"\n }\n },\n \"type\": \"repository\",\n \"name\": \"dogslow\",\n \"full_name\": \"evzijst/dogslow\",\n \"uuid\": \"{988b17c6-1a47-4e70-84ee-854d5f012bf6}\"\n },\n \"title\": \"Updated title\"\n },\n \"created_on\": \"2018-03-03T00:35:28.353630+00:00\",\n \"user\": {\n \"username\": \"evzijst\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"evzijst\",\n \"type\": \"user\",\n \"uuid\": \"{aaa7972b-38af-4fb1-802d-6e3854c95778}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/evzijst\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst/\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/evzijst/avatar/32/\"\n }\n }\n },\n \"message\": {\n \"raw\": \"Removed assignee, changed kind and priority.\",\n \"markup\": \"markdown\",\n \"html\": \"

Removed assignee, changed kind and priority.

\",\n \"type\": \"rendered\"\n },\n \"type\": \"issue_change\",\n \"id\": 2\n }\n ],\n \"page\": 1\n}\n```\n\nChanges support [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) that\ncan be used to search for specific changes. For instance, to see\nwhen an issue transitioned to \"resolved\":\n\n```\n$ curl -s https://api.bitbucket.org/2.0/repositories/site/master/issues/1/changes \\\n -G --data-urlencode='q=changes.state.new = \"resolved\"'\n```\n\nThis resource is only available on repositories that have the issue\ntracker enabled.\n\nN.B.\n\nThe `changes.assignee` and `changes.assignee_account_id` fields are not\na `user` object. Instead, they contain the raw `username` and\n`account_id` of the user. This is to protect the integrity of the audit\nlog even after a user account gets deleted.\n\nThe `changes.assignee` field is deprecated will disappear in the\nfuture. Use `changes.assignee_account_id` instead." + ] }, "post": { + "tags": ["Issue tracker"], + "description": "Makes a change to the specified issue.\n\nFor example, to change an issue's state and assignee, create a new\nchange object that modifies these fields:\n\n```\ncurl https://api.bitbucket.org/2.0/site/master/issues/1234/changes \\\n -s -u evzijst -X POST -H \"Content-Type: application/json\" \\\n -d '{\n \"changes\": {\n \"assignee_account_id\": {\n \"new\": \"557058:c0b72ad0-1cb5-4018-9cdc-0cde8492c443\"\n },\n \"state\": {\n \"new\": 'resolved\"\n }\n }\n \"message\": {\n \"raw\": \"This is now resolved.\"\n }\n }'\n```\n\nThe above example also includes a custom comment to go alongside the\nchange. This comment will also be visible on the issue page in the UI.\n\nThe fields of the `changes` object are strings, not objects. This\nallows for immutable change log records, even after user accounts,\nmilestones, or other objects recorded in a change entry, get renamed or\ndeleted.\n\nThe `assignee_account_id` field stores the account id. When POSTing a\nnew change and changing the assignee, the client should therefore use\nthe user's account_id in the `changes.assignee_account_id.new` field.\n\nThis call requires authentication. Private repositories or private\nissue trackers require the caller to authenticate with an account that\nhas appropriate authorization.", + "summary": "Modify the state of an issue", "responses": { "201": { "description": "The newly created issue change.", @@ -6668,8 +6862,6 @@ "description": "The new issue state change. The only required elements are `changes.[].new`. All other elements can be omitted from the body.", "required": true }, - "tags": ["Issue tracker"], - "summary": "Modify the state of an issue", "security": [ { "oauth2": ["issue:write"] @@ -6680,8 +6872,7 @@ { "api_key": [] } - ], - "description": "Makes a change to the specified issue.\n\nFor example, to change an issue's state and assignee, create a new\nchange object that modifies these fields:\n\n```\ncurl https://api.bitbucket.org/2.0/site/master/issues/1234/changes \\\n -s -u evzijst -X POST -H \"Content-Type: application/json\" \\\n -d '{\n \"changes\": {\n \"assignee_account_id\": {\n \"new\": \"557058:c0b72ad0-1cb5-4018-9cdc-0cde8492c443\"\n },\n \"state\": {\n \"new\": 'resolved\"\n }\n }\n \"message\": {\n \"raw\": \"This is now resolved.\"\n }\n }'\n```\n\nThe above example also includes a custom comment to go alongside the\nchange. This comment will also be visible on the issue page in the UI.\n\nThe fields of the `changes` object are strings, not objects. This\nallows for immutable change log records, even after user accounts,\nmilestones, or other objects recorded in a change entry, get renamed or\ndeleted.\n\nThe `assignee_account_id` field stores the account id. When POSTing a\nnew change and changing the assignee, the client should therefore use\nthe user's account_id in the `changes.assignee_account_id.new` field.\n\nThis call requires authentication. Private repositories or private\nissue trackers require the caller to authenticate with an account that\nhas appropriate authorization." + ] }, "parameters": [ { @@ -6715,6 +6906,9 @@ }, "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/changes/{change_id}": { "get": { + "tags": ["Issue tracker"], + "description": "Returns the specified issue change object.\n\nThis resource is only available on repositories that have the issue\ntracker enabled.", + "summary": "Get issue change object", "responses": { "200": { "description": "The specified issue change object.", @@ -6737,8 +6931,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Get issue change object", "security": [ { "oauth2": ["issue"] @@ -6749,8 +6941,7 @@ { "api_key": [] } - ], - "description": "Returns the specified issue change object.\n\nThis resource is only available on repositories that have the issue\ntracker enabled." + ] }, "parameters": [ { @@ -6793,6 +6984,9 @@ }, "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/comments": { "get": { + "tags": ["Issue tracker"], + "description": "Returns a paginated list of all comments that were made on the\nspecified issue.\n\nThe default sorting is oldest to newest and can be overridden with\nthe `sort` query parameter.\n\nThis endpoint also supports filtering and sorting of the results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more details.", + "summary": "List comments on an issue", "responses": { "200": { "description": "A paginated list of issue comments.", @@ -6816,8 +7010,6 @@ } } ], - "tags": ["Issue tracker"], - "summary": "List comments on an issue", "security": [ { "oauth2": ["issue"] @@ -6828,10 +7020,12 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of all comments that were made on the\nspecified issue.\n\nThe default sorting is oldest to newest and can be overridden with\nthe `sort` query parameter.\n\nThis endpoint also supports filtering and sorting of the results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more details." + ] }, "post": { + "tags": ["Issue tracker"], + "description": "Creates a new issue comment.\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/prlinks/issues/42/comments/ \\\n -X POST -u evzijst \\\n -H 'Content-Type: application/json' \\\n -d '{\"content\": {\"raw\": \"Lorem ipsum.\"}}'\n```", + "summary": "Create a comment on an issue", "responses": { "201": { "description": "The newly created comment.", @@ -6866,8 +7060,6 @@ "description": "The new issue comment object.", "required": true }, - "tags": ["Issue tracker"], - "summary": "Create a comment on an issue", "security": [ { "oauth2": ["issue:write"] @@ -6878,8 +7070,7 @@ { "api_key": [] } - ], - "description": "Creates a new issue comment.\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/prlinks/issues/42/comments/ \\\n -X POST -u evzijst \\\n -H 'Content-Type: application/json' \\\n -d '{\"content\": {\"raw\": \"Lorem ipsum.\"}}'\n```" + ] }, "parameters": [ { @@ -6913,16 +7104,14 @@ }, "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/comments/{comment_id}": { "delete": { + "tags": ["Issue tracker"], + "description": "Deletes the specified comment.", + "summary": "Delete a comment on an issue", "responses": { "204": { "description": "Indicates successful deletion." } }, - "requestBody": { - "$ref": "#/components/requestBodies/issue_comment" - }, - "tags": ["Issue tracker"], - "summary": "Delete a comment on an issue", "security": [ { "oauth2": ["issue:write"] @@ -6933,10 +7122,12 @@ { "api_key": [] } - ], - "description": "Deletes the specified comment." + ] }, "get": { + "tags": ["Issue tracker"], + "description": "Returns the specified issue comment object.", + "summary": "Get a comment on an issue", "responses": { "200": { "description": "The issue comment.", @@ -6949,8 +7140,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Get a comment on an issue", "security": [ { "oauth2": ["issue"] @@ -6961,10 +7150,12 @@ { "api_key": [] } - ], - "description": "Returns the specified issue comment object." + ] }, "put": { + "tags": ["Issue tracker"], + "description": "Updates the content of the specified issue comment. Note that only\nthe `content.raw` field can be modified.\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/prlinks/issues/42/comments/5728901 \\\n -X PUT -u evzijst \\\n -H 'Content-Type: application/json' \\\n -d '{\"content\": {\"raw\": \"Lorem ipsum.\"}'\n```", + "summary": "Update a comment on an issue", "responses": { "200": { "description": "The updated issue comment.", @@ -6988,10 +7179,16 @@ } }, "requestBody": { - "$ref": "#/components/requestBodies/issue_comment" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue_comment" + } + } + }, + "description": "The updated comment.", + "required": true }, - "tags": ["Issue tracker"], - "summary": "Update a comment on an issue", "security": [ { "oauth2": ["issue:write"] @@ -7002,8 +7199,7 @@ { "api_key": [] } - ], - "description": "Updates the content of the specified issue comment. Note that only\nthe `content.raw` field can be modified.\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/prlinks/issues/42/comments/5728901 \\\n -X PUT -u evzijst \\\n -H 'Content-Type: application/json' \\\n -d '{\"content\": {\"raw\": \"Lorem ipsum.\"}'\n```" + ] }, "parameters": [ { @@ -7046,6 +7242,9 @@ }, "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/vote": { "delete": { + "tags": ["Issue tracker"], + "description": "Retract your vote.", + "summary": "Remove vote for an issue", "responses": { "default": { "description": "Unexpected error.", @@ -7058,8 +7257,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Remove vote for an issue", "security": [ { "oauth2": ["account:write", "issue:write"] @@ -7070,10 +7267,12 @@ { "api_key": [] } - ], - "description": "Retract your vote." + ] }, "get": { + "tags": ["Issue tracker"], + "description": "Check whether the authenticated user has voted for this issue.\nA 204 status code indicates that the user has voted, while a 404\nimplies they haven't.", + "summary": "Check if current user voted for an issue", "responses": { "204": { "description": "If the authenticated user has not voted for this issue.", @@ -7106,8 +7305,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Check if current user voted for an issue", "security": [ { "oauth2": ["account", "issue"] @@ -7118,10 +7315,12 @@ { "api_key": [] } - ], - "description": "Check whether the authenticated user has voted for this issue.\nA 204 status code indicates that the user has voted, while a 404\nimplies they haven't." + ] }, "put": { + "tags": ["Issue tracker"], + "description": "Vote for this issue.\n\nTo cast your vote, do an empty PUT. The 204 status code indicates that\nthe operation was successful.", + "summary": "Vote for an issue", "responses": { "204": { "description": "Indicating the authenticated user has cast their vote successfully.", @@ -7154,8 +7353,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Vote for an issue", "security": [ { "oauth2": ["account:write", "issue"] @@ -7166,8 +7363,7 @@ { "api_key": [] } - ], - "description": "Vote for this issue.\n\nTo cast your vote, do an empty PUT. The 204 status code indicates that\nthe operation was successful." + ] }, "parameters": [ { @@ -7201,6 +7397,9 @@ }, "/repositories/{workspace}/{repo_slug}/issues/{issue_id}/watch": { "delete": { + "tags": ["Issue tracker"], + "description": "Stop watching this issue.", + "summary": "Stop watching an issue", "responses": { "204": { "description": "Indicates that the authenticated user successfully stopped watching this issue.", @@ -7233,8 +7432,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Stop watching an issue", "security": [ { "oauth2": ["account:write", "issue:write"] @@ -7245,10 +7442,12 @@ { "api_key": [] } - ], - "description": "Stop watching this issue." + ] }, "get": { + "tags": ["Issue tracker"], + "description": "Indicated whether or not the authenticated user is watching this\nissue.", + "summary": "Check if current user is watching a issue", "responses": { "204": { "description": "If the authenticated user is watching this issue.", @@ -7281,8 +7480,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Check if current user is watching a issue", "security": [ { "oauth2": ["account", "issue"] @@ -7293,10 +7490,12 @@ { "api_key": [] } - ], - "description": "Indicated whether or not the authenticated user is watching this\nissue." + ] }, "put": { + "tags": ["Issue tracker"], + "description": "Start watching this issue.\n\nTo start watching this issue, do an empty PUT. The 204 status code\nindicates that the operation was successful.", + "summary": "Watch an issue", "responses": { "204": { "description": "Indicates that the authenticated user successfully started watching this issue.", @@ -7329,8 +7528,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Watch an issue", "security": [ { "oauth2": ["account:write", "issue"] @@ -7341,8 +7538,7 @@ { "api_key": [] } - ], - "description": "Start watching this issue.\n\nTo start watching this issue, do an empty PUT. The 204 status code\nindicates that the operation was successful." + ] }, "parameters": [ { @@ -7376,6 +7572,9 @@ }, "/repositories/{workspace}/{repo_slug}/merge-base/{revspec}": { "get": { + "tags": ["Commits"], + "description": "Returns the best common ancestor between two commits, specified in a revspec\nof 2 commits (e.g. 3a8b42..9ff173).\n\nIf more than one best common ancestor exists, only one will be returned. It is\nunspecified which will be returned.", + "summary": "Get the common ancestor between two commits", "responses": { "200": { "description": "The merge base of the provided spec.", @@ -7418,8 +7617,6 @@ } } }, - "tags": ["Commits"], - "summary": "Get the common ancestor between two commits", "security": [ { "oauth2": ["repository"] @@ -7430,8 +7627,7 @@ { "api_key": [] } - ], - "description": "Returns the best common ancestor between two commits, specified in a revspec\nof 2 commits (e.g. 3a8b42..9ff173).\n\nIf more than one best common ancestor exists, only one will be returned. It is\nunspecified which will be returned." + ] }, "parameters": [ { @@ -7465,6 +7661,9 @@ }, "/repositories/{workspace}/{repo_slug}/milestones": { "get": { + "tags": ["Issue tracker"], + "description": "Returns the milestones that have been defined in the issue tracker.\n\nThis resource is only available on repositories that have the issue\ntracker enabled.", + "summary": "List milestones", "responses": { "200": { "description": "The milestones that have been defined in the issue tracker.", @@ -7487,8 +7686,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "List milestones", "security": [ { "oauth2": ["issue"] @@ -7499,8 +7696,7 @@ { "api_key": [] } - ], - "description": "Returns the milestones that have been defined in the issue tracker.\n\nThis resource is only available on repositories that have the issue\ntracker enabled." + ] }, "parameters": [ { @@ -7525,6 +7721,9 @@ }, "/repositories/{workspace}/{repo_slug}/milestones/{milestone_id}": { "get": { + "tags": ["Issue tracker"], + "description": "Returns the specified issue tracker milestone object.", + "summary": "Get a milestone", "responses": { "200": { "description": "The specified milestone object.", @@ -7547,8 +7746,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Get a milestone", "security": [ { "oauth2": ["issue"] @@ -7559,8 +7756,7 @@ { "api_key": [] } - ], - "description": "Returns the specified issue tracker milestone object." + ] }, "parameters": [ { @@ -7594,6 +7790,9 @@ }, "/repositories/{workspace}/{repo_slug}/patch/{spec}": { "get": { + "tags": ["Commits"], + "description": "Produces a raw patch for a single commit (diffed against its first\nparent), or a patch-series for a revspec of 2 commits (e.g.\n`3a8b42..9ff173` where the first commit represents the source and the\nsecond commit the destination).\n\nIn case of the latter (diffing a revspec), a patch series is returned\nfor the commits on the source branch (`3a8b42` and its ancestors in\nour example).\n\nWhile similar to diffs, patches:\n\n* Have a commit header (username, commit message, etc)\n* Do not support the `path=foo/bar.py` query parameter\n\nThe raw patch is returned as-is, in whatever encoding the files in the\nrepository use. It is not decoded into unicode. As such, the\ncontent-type is `text/plain`.", + "summary": "Get a patch for two commits", "responses": { "200": { "description": "The raw patches" @@ -7609,8 +7808,6 @@ } } }, - "tags": ["Commits"], - "summary": "Get a patch for two commits", "security": [ { "oauth2": ["repository"] @@ -7621,8 +7818,7 @@ { "api_key": [] } - ], - "description": "Produces a raw patch for a single commit (diffed against its first\nparent), or a patch-series for a revspec of 2 commits (e.g.\n`3a8b42..9ff173` where the first commit represents the source and the\nsecond commit the destination).\n\nIn case of the latter (diffing a revspec), a patch series is returned\nfor the commits on the source branch (`3a8b42` and its ancestors in\nour example).\n\nWhile similar to diffs, patches:\n\n* Have a commit header (username, commit message, etc)\n* Do not support the `path=foo/bar.py` query parameter\n\nThe raw patch is returned as-is, in whatever encoding the files in the\nrepository use. It is not decoded into unicode. As such, the\ncontent-type is `text/plain`." + ] }, "parameters": [ { @@ -7656,6 +7852,9 @@ }, "/repositories/{workspace}/{repo_slug}/permissions-config/groups": { "get": { + "tags": ["Repositories"], + "description": "Returns a paginated list of explicit group permissions for the given repository.\nThis endpoint does not support BBQL features.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups\n\nHTTP/1.1 200\nLocation: https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_group_permission\",\n \"group\": {\n \"type\": \"group\",\n \"name\": \"Administrators\",\n \"slug\": \"administrators\"\n },\n \"permission\": \"admin\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/\n geordi/permissions-config/groups/administrators\"\n }\n }\n },\n {\n \"type\": \"repository_group_permission\",\n \"group\": {\n \"type\": \"group\",\n \"name\": \"Developers\",\n \"slug\": \"developers\"\n },\n \"permission\": \"read\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/\n geordi/permissions-config/groups/developers\"\n }\n }\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```", + "summary": "List explicit group permissions for a repository", "responses": { "200": { "description": "Paginated of explicit group permissions on the repository.", @@ -7698,8 +7897,6 @@ } } }, - "tags": ["Repositories"], - "summary": "List explicit group permissions for a repository", "security": [ { "oauth2": ["repository:admin"] @@ -7710,8 +7907,7 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of explicit group permissions for the given repository.\nThis endpoint does not support BBQL features.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups\n\nHTTP/1.1 200\nLocation: https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_group_permission\",\n \"group\": {\n \"type\": \"group\",\n \"name\": \"Administrators\",\n \"slug\": \"administrators\"\n },\n \"permission\": \"admin\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/\n geordi/permissions-config/groups/administrators\"\n }\n }\n },\n {\n \"type\": \"repository_group_permission\",\n \"group\": {\n \"type\": \"group\",\n \"name\": \"Developers\",\n \"slug\": \"developers\"\n },\n \"permission\": \"read\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/\n geordi/permissions-config/groups/developers\"\n }\n }\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```" + ] }, "parameters": [ { @@ -7736,6 +7932,9 @@ }, "/repositories/{workspace}/{repo_slug}/permissions-config/groups/{group_slug}": { "delete": { + "tags": ["Repositories"], + "description": "Deletes the repository group permission between the requested repository and group, if one exists.\n\nOnly users with admin permission for the repository may access this resource.\n\nExample:\n\n$ curl -X DELETE https://api.bitbucket.org/2.0/repositories/atlassian_tutorial\n/geordi/permissions-config/groups/developers\n\n\nHTTP/1.1 204", + "summary": "Delete an explicit group permission for a repository", "responses": { "204": { "description": "Group permission deleted" @@ -7771,8 +7970,6 @@ } } }, - "tags": ["Repositories"], - "summary": "Delete an explicit group permission for a repository", "security": [ { "oauth2": ["repository:admin"] @@ -7783,10 +7980,12 @@ { "api_key": [] } - ], - "description": "Deletes the repository group permission between the requested repository and group, if one exists.\n\nOnly users with admin permission for the repository may access this resource.\n\nExample:\n\n$ curl -X DELETE https://api.bitbucket.org/2.0/repositories/atlassian_tutorial\n/geordi/permissions-config/groups/developers\n\n\nHTTP/1.1 204" + ] }, "get": { + "tags": ["Repositories"], + "description": "Returns the group permission for a given group slug and repository\n\nOnly users with admin permission for the repository may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n* `none`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\n\nHTTP/1.1 200\nLocation:\nhttps://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\n\n{\n \"type\": \"repository_group_permission\",\n \"group\": {\n \"type\": \"group\",\n \"name\": \"Developers\",\n \"slug\": \"developers\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"read\",\n \"links\": {\n \"self\": {\n \"href\":\n \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\"\n }\n }\n}\n```", + "summary": "Get an explicit group permission for a repository", "responses": { "200": { "description": "Group permission for group slug and repository", @@ -7829,8 +8028,6 @@ } } }, - "tags": ["Repositories"], - "summary": "Get an explicit group permission for a repository", "security": [ { "oauth2": ["repository:admin"] @@ -7841,10 +8038,12 @@ { "api_key": [] } - ], - "description": "Returns the group permission for a given group slug and repository\n\nOnly users with admin permission for the repository may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n* `none`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\n\nHTTP/1.1 200\nLocation:\nhttps://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\n\n{\n \"type\": \"repository_group_permission\",\n \"group\": {\n \"type\": \"group\",\n \"name\": \"Developers\",\n \"slug\": \"developers\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"read\",\n \"links\": {\n \"self\": {\n \"href\":\n \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\"\n }\n }\n}\n```" + ] }, "put": { + "tags": ["Repositories"], + "description": "Updates the group permission if it exists.\n\nOnly users with admin permission for the repository may access this resource.\n\nThe only authentication method supported for this endpoint is via app passwords.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n```\n$ curl -X PUT -H \"Content-Type: application/json\"\nhttps://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\n-d\n'{\n \"permission\": \"write\"\n}'\n\nHTTP/1.1 200\nLocation:\nhttps://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\n\n{\n \"type\": \"repository_group_permission\",\n \"group\": {\n \"type\": \"group\",\n \"name\": \"Developers\",\n \"slug\": \"developers\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"write\",\n \"links\": {\n \"self\": {\n \"href\":\n \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\"\n }\n }\n}\n```", + "summary": "Update an explicit group permission for a repository", "responses": { "200": { "description": "Group permission updated", @@ -7897,8 +8096,6 @@ } } }, - "tags": ["Repositories"], - "summary": "Update an explicit group permission for a repository", "security": [ { "oauth2": ["repository:admin"] @@ -7909,8 +8106,7 @@ { "api_key": [] } - ], - "description": "Updates the group permission if it exists.\n\nOnly users with admin permission for the repository may access this resource.\n\nThe only authentication method supported for this endpoint is via app passwords.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n```\n$ curl -X PUT -H \"Content-Type: application/json\"\nhttps://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\n-d\n'{\n \"permission\": \"write\"\n}'\n\nHTTP/1.1 200\nLocation:\nhttps://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\n\n{\n \"type\": \"repository_group_permission\",\n \"group\": {\n \"type\": \"group\",\n \"name\": \"Developers\",\n \"slug\": \"developers\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"write\",\n \"links\": {\n \"self\": {\n \"href\":\n \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/groups/developers\"\n }\n }\n}\n```" + ] }, "parameters": [ { @@ -7944,6 +8140,9 @@ }, "/repositories/{workspace}/{repo_slug}/permissions-config/users": { "get": { + "tags": ["Repositories"], + "description": "Returns a paginated list of explicit user permissions for the given repository.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/users\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_user_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Colin Cameron\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"account_id\": \"557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n },\n \"permission\": \"admin\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n }\n }\n },\n {\n \"type\": \"repository_user_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\",\n \"account_id\": \"557058:ba8948b2-49da-43a9-9e8b-e7249b8e324c\"\n },\n \"permission\": \"write\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0//repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324c\"\n }\n }\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```", + "summary": "List explicit user permissions for a repository", "responses": { "200": { "description": "Paginated of explicit user permissions on the repository.", @@ -7986,8 +8185,6 @@ } } }, - "tags": ["Repositories"], - "summary": "List explicit user permissions for a repository", "security": [ { "oauth2": ["repository:admin"] @@ -7998,8 +8195,7 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of explicit user permissions for the given repository.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/permissions-config/users\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_user_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Colin Cameron\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"account_id\": \"557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n },\n \"permission\": \"admin\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n }\n }\n },\n {\n \"type\": \"repository_user_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\",\n \"account_id\": \"557058:ba8948b2-49da-43a9-9e8b-e7249b8e324c\"\n },\n \"permission\": \"write\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0//repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324c\"\n }\n }\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```" + ] }, "parameters": [ { @@ -8024,6 +8220,9 @@ }, "/repositories/{workspace}/{repo_slug}/permissions-config/users/{selected_user_id}": { "delete": { + "tags": ["Repositories"], + "description": "Deletes the repository user permission between the requested repository and user, if one exists.\n\nOnly users with admin permission for the repository may access this resource.\n\nThe only authentication method for this endpoint is via app passwords.\n\n```\n$ curl -X DELETE https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\npermissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\n\n\nHTTP/1.1 204\n```", + "summary": "Delete an explicit user permission for a repository", "responses": { "204": { "description": "The repository user permission was deleted and no content returned." @@ -8059,8 +8258,6 @@ } } }, - "tags": ["Repositories"], - "summary": "Delete an explicit user permission for a repository", "security": [ { "oauth2": ["repository:admin"] @@ -8071,10 +8268,12 @@ { "api_key": [] } - ], - "description": "Deletes the repository user permission between the requested repository and user, if one exists.\n\nOnly users with admin permission for the repository may access this resource.\n\nThe only authentication method for this endpoint is via app passwords.\n\n```\n$ curl -X DELETE https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\npermissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\n\n\nHTTP/1.1 204\n```" + ] }, "get": { + "tags": ["Repositories"], + "description": "Returns the explicit user permission for a given user and repository.\n\nOnly users with admin permission for the repository may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n* `none`\n\nExample:\n\n```\n$ curl 'https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a'\n\nHTTP/1.1 200\nLocation: 'https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a'\n\n{\n \"type\": \"repository_user_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Colin Cameron\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"account_id\": \"557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"admin\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n }\n }\n}\n```", + "summary": "Get an explicit user permission for a repository", "responses": { "200": { "description": "Explicit user permission for user and repository", @@ -8117,8 +8316,6 @@ } } }, - "tags": ["Repositories"], - "summary": "Get an explicit user permission for a repository", "security": [ { "oauth2": ["repository:admin"] @@ -8129,10 +8326,12 @@ { "api_key": [] } - ], - "description": "Returns the explicit user permission for a given user and repository.\n\nOnly users with admin permission for the repository may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n* `none`\n\nExample:\n\n```\n$ curl 'https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a'\n\nHTTP/1.1 200\nLocation: 'https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a'\n\n{\n \"type\": \"repository_user_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Colin Cameron\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"account_id\": \"557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"admin\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n }\n }\n}\n```" + ] }, "put": { + "tags": ["Repositories"], + "description": "Updates the explicit user permission for a given user and repository. The selected user must be a member of\nthe workspace, and cannot be the workspace owner.\nOnly users with admin permission for the repository may access this resource.\n\nThe only authentication method for this endpoint is via app passwords.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n\n```\n$ curl -X PUT -H \"Content-Type: application/json\" 'https://api.bitbucket.org/2.0/repositories/\natlassian_tutorial/geordi/permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a'\n-d '{\n \"permission\": \"write\"\n}'\n\nHTTP/1.1 200\nLocation: 'https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\npermissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a'\n\n\n{\n \"type\": \"repository_user_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Colin Cameron\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"account_id\": \"557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"write\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n }\n }\n}\n```", + "summary": "Update an explicit user permission for a repository", "responses": { "200": { "description": "Explicit user permission updated", @@ -8185,8 +8384,6 @@ } } }, - "tags": ["Repositories"], - "summary": "Update an explicit user permission for a repository", "security": [ { "oauth2": ["repository:admin"] @@ -8197,8 +8394,7 @@ { "api_key": [] } - ], - "description": "Updates the explicit user permission for a given user and repository. The selected user must be a member of\nthe workspace, and cannot be the workspace owner.\nOnly users with admin permission for the repository may access this resource.\n\nThe only authentication method for this endpoint is via app passwords.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n\n```\n$ curl -X PUT -H \"Content-Type: application/json\" 'https://api.bitbucket.org/2.0/repositories/\natlassian_tutorial/geordi/permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a'\n-d '{\n \"permission\": \"write\"\n}'\n\nHTTP/1.1 200\nLocation: 'https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\npermissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a'\n\n\n{\n \"type\": \"repository_user_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Colin Cameron\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"account_id\": \"557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"write\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian_tutorial/geordi/\n permissions-config/users/557058:ba8948b2-49da-43a9-9e8b-e7249b8e324a\"\n }\n }\n}\n```" + ] }, "parameters": [ { @@ -8232,6 +8428,30 @@ }, "/repositories/{workspace}/{repo_slug}/pipelines-config/caches/": { "get": { + "tags": ["Pipelines"], + "summary": "List caches", + "description": "Retrieve the repository pipelines caches.", + "operationId": "getRepositoryPipelineCaches", + "parameters": [ + { + "name": "workspace", + "description": "The account.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "The list of caches for the given repository.", @@ -8253,35 +8473,44 @@ } } } - }, - "description": "Retrieve the repository pipelines caches.", + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines-config/caches/{cache_uuid}": { + "delete": { + "tags": ["Pipelines"], + "summary": "Delete a cache", + "description": "Delete a repository cache.", + "operationId": "deleteRepositoryPipelineCache", "parameters": [ { + "name": "workspace", "description": "The account.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "cache_uuid", + "description": "The UUID of the cache to delete.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "List caches", - "operationId": "getRepositoryPipelineCaches" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines-config/caches/{cache_uuid}": { - "delete": { "responses": { "204": { "description": "The cache was deleted." @@ -8296,44 +8525,44 @@ } } } - }, - "description": "Delete a repository cache.", + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines-config/caches/{cache_uuid}/content-uri": { + "get": { + "tags": ["Pipelines"], + "summary": "Get cache content URI", + "description": "Retrieve the URI of the content of the specified cache.", + "operationId": "getRepositoryPipelineCacheContentURI", "parameters": [ { + "name": "workspace", "description": "The account.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { - "description": "The UUID of the cache to delete.", - "required": true, "name": "cache_uuid", + "description": "The UUID of the cache.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Delete a cache", - "operationId": "deleteRepositoryPipelineCache" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines-config/caches/{cache_uuid}/content-uri": { - "get": { "responses": { "200": { "description": "The cache content uri.", @@ -8355,46 +8584,87 @@ } } } - }, - "description": "Retrieve the URI of the content of the specified cache.", + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines/": { + "get": { + "tags": ["Pipelines"], + "summary": "List pipelines", + "description": "Find pipelines", + "operationId": "getPipelinesForRepository", "parameters": [ { - "description": "The account.", - "required": true, "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The UUID of the cache.", - "required": true, - "name": "cache_uuid", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get cache content URI", - "operationId": "getRepositoryPipelineCacheContentURI" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines/": { + "responses": { + "200": { + "description": "The matching pipelines.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pipelines" + } + } + } + } + } + }, "post": { + "tags": ["Pipelines"], + "summary": "Run a pipeline", + "description": "Endpoint to create and initiate a pipeline.\nThere are a couple of different options to initiate a pipeline, where the payload of the request will determine which type of pipeline will be instantiated.\n# Trigger a Pipeline for a branch\nOne way to trigger pipelines is by specifying the branch for which you want to trigger a pipeline.\nThe specified branch will be used to determine which pipeline definition from the `bitbucket-pipelines.yml` file will be applied to initiate the pipeline. The pipeline will then do a clone of the repository and checkout the latest revision of the specified branch.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"ref_type\": \"branch\",\n \"type\": \"pipeline_ref_target\",\n \"ref_name\": \"master\"\n }\n }'\n```\n# Trigger a Pipeline for a commit on a branch or tag\nYou can initiate a pipeline for a specific commit and in the context of a specified reference (e.g. a branch, tag or bookmark).\nThe specified reference will be used to determine which pipeline definition from the bitbucket-pipelines.yml file will be applied to initiate the pipeline. The pipeline will clone the repository and then do a checkout the specified reference.\n\nThe following reference types are supported:\n\n* `branch`\n* `named_branch`\n* `bookmark`\n * `tag`\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"ce5b7431602f7cbba007062eeb55225c6e18e956\"\n },\n \"ref_type\": \"branch\",\n \"type\": \"pipeline_ref_target\",\n \"ref_name\": \"master\"\n }\n }'\n```\n# Trigger a specific pipeline definition for a commit\nYou can trigger a specific pipeline that is defined in your `bitbucket-pipelines.yml` file for a specific commit.\nIn addition to the commit revision, you specify the type and pattern of the selector that identifies the pipeline definition. The resulting pipeline will then clone the repository and checkout the specified revision.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"commit\": {\n \"hash\":\"a3c4e02c9a3755eccdc3764e6ea13facdf30f923\",\n \"type\":\"commit\"\n },\n \"selector\": {\n \"type\":\"custom\",\n \"pattern\":\"Deploy to production\"\n },\n \"type\":\"pipeline_commit_target\"\n }\n }'\n```\n# Trigger a specific pipeline definition for a commit on a branch or tag\nYou can trigger a specific pipeline that is defined in your `bitbucket-pipelines.yml` file for a specific commit in the context of a specified reference.\nIn addition to the commit revision, you specify the type and pattern of the selector that identifies the pipeline definition, as well as the reference information. The resulting pipeline will then clone the repository a checkout the specified reference.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"commit\": {\n \"hash\":\"a3c4e02c9a3755eccdc3764e6ea13facdf30f923\",\n \"type\":\"commit\"\n },\n \"selector\": {\n \"type\": \"custom\",\n \"pattern\": \"Deploy to production\"\n },\n \"type\": \"pipeline_ref_target\",\n \"ref_name\": \"master\",\n \"ref_type\": \"branch\"\n }\n }'\n```\n\n\n# Trigger a custom pipeline with variables\nIn addition to triggering a custom pipeline that is defined in your `bitbucket-pipelines.yml` file as shown in the examples above, you can specify variables that will be available for your build. In the request, provide a list of variables, specifying the following for each variable: key, value, and whether it should be secured or not (this field is optional and defaults to not secured).\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/{workspace}/{repo_slug}/pipelines/ \\\n -d '\n {\n \"target\": {\n \"type\": \"pipeline_ref_target\",\n \"ref_type\": \"branch\",\n \"ref_name\": \"master\",\n \"selector\": {\n \"type\": \"custom\",\n \"pattern\": \"Deploy to production\"\n }\n },\n \"variables\": [\n {\n \"key\": \"var1key\",\n \"value\": \"var1value\",\n \"secured\": true\n },\n {\n \"key\": \"var2key\",\n \"value\": \"var2value\"\n }\n ]\n }'\n```\n\n# Trigger a pull request pipeline\n\nYou can also initiate a pipeline for a specific pull request.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/{workspace}/{repo_slug}/pipelines/ \\\n -d '\n {\n\t\"target\": {\n \"type\": \"pipeline_pullrequest_target\",\n\t \"source\": \"pull-request-branch\",\n \"destination\": \"master\",\n \"destination_commit\": {\n \t \"hash\" : \"9f848b7\"\n },\n \"commit\": {\n \t\"hash\" : \"1a372fc\"\n },\n \"pullrequest\" : {\n \t\"id\" : \"3\"\n },\n\t \"selector\": {\n \"type\": \"pull-requests\",\n \"pattern\": \"**\"\n }\n }\n }'\n```\n", + "operationId": "createPipelineForRepository", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline" + } + } + }, + "description": "The pipeline to initiate.", + "required": true + }, "responses": { "201": { + "description": "The initiated pipeline.", "headers": { "Location": { "description": "The URL of the newly created pipeline.", @@ -8403,7 +8673,6 @@ } } }, - "description": "The initiated pipeline.", "content": { "application/json": { "schema": { @@ -8432,84 +8701,44 @@ } } } - }, - "description": "Endpoint to create and initiate a pipeline.\nThere are a couple of different options to initiate a pipeline, where the payload of the request will determine which type of pipeline will be instantiated.\n# Trigger a Pipeline for a branch\nOne way to trigger pipelines is by specifying the branch for which you want to trigger a pipeline.\nThe specified branch will be used to determine which pipeline definition from the `bitbucket-pipelines.yml` file will be applied to initiate the pipeline. The pipeline will then do a clone of the repository and checkout the latest revision of the specified branch.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"ref_type\": \"branch\",\n \"type\": \"pipeline_ref_target\",\n \"ref_name\": \"master\"\n }\n }'\n```\n# Trigger a Pipeline for a commit on a branch or tag\nYou can initiate a pipeline for a specific commit and in the context of a specified reference (e.g. a branch, tag or bookmark).\nThe specified reference will be used to determine which pipeline definition from the bitbucket-pipelines.yml file will be applied to initiate the pipeline. The pipeline will clone the repository and then do a checkout the specified reference.\n\nThe following reference types are supported:\n\n* `branch`\n* `named_branch`\n* `bookmark`\n * `tag`\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"ce5b7431602f7cbba007062eeb55225c6e18e956\"\n },\n \"ref_type\": \"branch\",\n \"type\": \"pipeline_ref_target\",\n \"ref_name\": \"master\"\n }\n }'\n```\n# Trigger a specific pipeline definition for a commit\nYou can trigger a specific pipeline that is defined in your `bitbucket-pipelines.yml` file for a specific commit.\nIn addition to the commit revision, you specify the type and pattern of the selector that identifies the pipeline definition. The resulting pipeline will then clone the repository and checkout the specified revision.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"commit\": {\n \"hash\":\"a3c4e02c9a3755eccdc3764e6ea13facdf30f923\",\n \"type\":\"commit\"\n },\n \"selector\": {\n \"type\":\"custom\",\n \"pattern\":\"Deploy to production\"\n },\n \"type\":\"pipeline_commit_target\"\n }\n }'\n```\n# Trigger a specific pipeline definition for a commit on a branch or tag\nYou can trigger a specific pipeline that is defined in your `bitbucket-pipelines.yml` file for a specific commit in the context of a specified reference.\nIn addition to the commit revision, you specify the type and pattern of the selector that identifies the pipeline definition, as well as the reference information. The resulting pipeline will then clone the repository a checkout the specified reference.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/jeroendr/meat-demo2/pipelines/ \\\n -d '\n {\n \"target\": {\n \"commit\": {\n \"hash\":\"a3c4e02c9a3755eccdc3764e6ea13facdf30f923\",\n \"type\":\"commit\"\n },\n \"selector\": {\n \"type\": \"custom\",\n \"pattern\": \"Deploy to production\"\n },\n \"type\": \"pipeline_ref_target\",\n \"ref_name\": \"master\",\n \"ref_type\": \"branch\"\n }\n }'\n```\n\n\n# Trigger a custom pipeline with variables\nIn addition to triggering a custom pipeline that is defined in your `bitbucket-pipelines.yml` file as shown in the examples above, you can specify variables that will be available for your build. In the request, provide a list of variables, specifying the following for each variable: key, value, and whether it should be secured or not (this field is optional and defaults to not secured).\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/{workspace}/{repo_slug}/pipelines/ \\\n -d '\n {\n \"target\": {\n \"type\": \"pipeline_ref_target\",\n \"ref_type\": \"branch\",\n \"ref_name\": \"master\",\n \"selector\": {\n \"type\": \"custom\",\n \"pattern\": \"Deploy to production\"\n }\n },\n \"variables\": [\n {\n \"key\": \"var1key\",\n \"value\": \"var1value\",\n \"secured\": true\n },\n {\n \"key\": \"var2key\",\n \"value\": \"var2value\"\n }\n ]\n }'\n```\n\n# Trigger a pull request pipeline\n\nYou can also initiate a pipeline for a specific pull request.\n\n### Example\n\n```\n$ curl -X POST -is -u username:password \\\n -H 'Content-Type: application/json' \\\n https://api.bitbucket.org/2.0/repositories/{workspace}/{repo_slug}/pipelines/ \\\n -d '\n {\n\t\"target\": {\n \"type\": \"pipeline_pullrequest_target\",\n\t \"source\": \"pull-request-branch\",\n \"destination\": \"master\",\n \"destination_commit\": {\n \t \"hash\" : \"9f848b7\"\n },\n \"commit\": {\n \t\"hash\" : \"1a372fc\"\n },\n \"pullrequest\" : {\n \t\"id\" : \"3\"\n },\n\t \"selector\": {\n \"type\": \"pull-requests\",\n \"pattern\": \"**\"\n }\n }\n }'\n```\n", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline" - } - } - }, - "description": "The pipeline to initiate.", - "required": true - }, - "tags": ["Pipelines"], - "summary": "Run a pipeline", - "operationId": "createPipelineForRepository" - }, - "get": { - "responses": { - "200": { - "description": "The matching pipelines.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_pipelines" - } - } - } - } - }, - "description": "Find pipelines", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Pipelines"], - "summary": "List pipelines", - "operationId": "getPipelinesForRepository" + } } }, "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}": { "get": { + "tags": ["Pipelines"], + "summary": "Get a pipeline", + "description": "Retrieve a specified pipeline", + "operationId": "getPipelineForRepository", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "pipeline_uuid", + "description": "The pipeline UUID.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "The pipeline.", @@ -8531,44 +8760,44 @@ } } } - }, - "description": "Retrieve a specified pipeline", + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/": { + "get": { + "tags": ["Pipelines"], + "summary": "List steps for a pipeline", + "description": "Find steps for the given pipeline.", + "operationId": "getPipelineStepsForRepository", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { - "description": "The pipeline UUID.", - "required": true, "name": "pipeline_uuid", + "description": "The UUID of the pipeline.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get a pipeline", - "operationId": "getPipelineForRepository" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/": { - "get": { "responses": { "200": { "description": "The steps.", @@ -8580,44 +8809,53 @@ } } } - }, - "description": "Find steps for the given pipeline.", + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}": { + "get": { + "tags": ["Pipelines"], + "summary": "Get a step of a pipeline", + "description": "Retrieve a given step of a pipeline.", + "operationId": "getPipelineStepForRepository", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "pipeline_uuid", "description": "The UUID of the pipeline.", "required": true, - "name": "pipeline_uuid", + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "step_uuid", + "description": "The UUID of the step.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "List steps for a pipeline", - "operationId": "getPipelineStepsForRepository" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}": { - "get": { "responses": { "200": { "description": "The step.", @@ -8639,53 +8877,53 @@ } } } - }, - "description": "Retrieve a given step of a pipeline.", + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/log": { + "get": { + "tags": ["Pipelines"], + "summary": "Get log file for a step", + "description": "Retrieve the log file for a given step of a pipeline.\n\nThis endpoint supports (and encourages!) the use of [HTTP Range requests](https://tools.ietf.org/html/rfc7233) to deal with potentially very large log files.", + "operationId": "getPipelineStepLogForRepository", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "pipeline_uuid", "description": "The UUID of the pipeline.", "required": true, - "name": "pipeline_uuid", "in": "path", "schema": { "type": "string" } }, { + "name": "step_uuid", "description": "The UUID of the step.", "required": true, - "name": "step_uuid", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get a step of a pipeline", - "operationId": "getPipelineStepForRepository" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/log": { - "get": { "responses": { "200": { "description": "The raw log file for this pipeline step." @@ -8720,53 +8958,62 @@ } } } - }, + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/logs/{log_uuid}": { + "get": { + "tags": ["Pipelines"], + "summary": "Get the logs for the build container or a service container for a given step of a pipeline.", + "description": "Retrieve the log file for a build container or service container.\n\nThis endpoint supports (and encourages!) the use of [HTTP Range requests](https://tools.ietf.org/html/rfc7233) to deal with potentially very large log files.", + "operationId": "getPipelineContainerLog", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "pipeline_uuid", "description": "The UUID of the pipeline.", "required": true, - "name": "pipeline_uuid", "in": "path", "schema": { "type": "string" } }, { + "name": "step_uuid", "description": "The UUID of the step.", "required": true, - "name": "step_uuid", + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "log_uuid", + "description": "For the main build container specify the step UUID; for a service container specify the service container UUID", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get log file for a step", - "operationId": "getPipelineStepLogForRepository", - "description": "Retrieve the log file for a given step of a pipeline.\n\nThis endpoint supports (and encourages!) the use of [HTTP Range requests](https://tools.ietf.org/html/rfc7233) to deal with potentially very large log files." - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/logs/{log_uuid}": { - "get": { "responses": { "200": { "description": "The raw log file for the build container or service container." @@ -8781,62 +9028,52 @@ } } } - }, + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/test_reports": { + "get": { + "tags": ["Pipelines"], + "summary": "Get a summary of test reports for a given step of a pipeline.", + "operationId": "getPipelineTestReports", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "pipeline_uuid", "description": "The UUID of the pipeline.", "required": true, - "name": "pipeline_uuid", "in": "path", "schema": { "type": "string" } }, { + "name": "step_uuid", "description": "The UUID of the step.", "required": true, - "name": "step_uuid", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "For the main build container specify the step UUID; for a service container specify the service container UUID", - "required": true, - "name": "log_uuid", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get the logs for the build container or a service container for a given step of a pipeline.", - "operationId": "getPipelineContainerLog", - "description": "Retrieve the log file for a build container or service container.\n\nThis endpoint supports (and encourages!) the use of [HTTP Range requests](https://tools.ietf.org/html/rfc7233) to deal with potentially very large log files." - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/test_reports": { - "get": { "responses": { "200": { "description": "A summary of test reports for this pipeline step." @@ -8851,52 +9088,52 @@ } } } - }, + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/test_reports/test_cases": { + "get": { + "tags": ["Pipelines"], + "summary": "Get test cases for a given step of a pipeline.", + "operationId": "getPipelineTestReportTestCases", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "pipeline_uuid", "description": "The UUID of the pipeline.", "required": true, - "name": "pipeline_uuid", "in": "path", "schema": { "type": "string" } }, { + "name": "step_uuid", "description": "The UUID of the step.", "required": true, - "name": "step_uuid", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get a summary of test reports for a given step of a pipeline.", - "operationId": "getPipelineTestReports" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/test_reports/test_cases": { - "get": { "responses": { "200": { "description": "Test cases for this pipeline step." @@ -8911,52 +9148,61 @@ } } } - }, + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/test_reports/test_cases/{test_case_uuid}/test_case_reasons": { + "get": { + "tags": ["Pipelines"], + "summary": "Get test case reasons (output) for a given test case in a step of a pipeline.", + "operationId": "getPipelineTestReportTestCaseReasons", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "pipeline_uuid", "description": "The UUID of the pipeline.", "required": true, - "name": "pipeline_uuid", "in": "path", "schema": { "type": "string" } }, { + "name": "step_uuid", "description": "The UUID of the step.", "required": true, - "name": "step_uuid", + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "test_case_uuid", + "description": "The UUID of the test case.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get test cases for a given step of a pipeline.", - "operationId": "getPipelineTestReportTestCases" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/steps/{step_uuid}/test_reports/test_cases/{test_case_uuid}/test_case_reasons": { - "get": { "responses": { "200": { "description": "Test case reasons (output)." @@ -8971,61 +9217,44 @@ } } } - }, + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/stopPipeline": { + "post": { + "tags": ["Pipelines"], + "summary": "Stop a pipeline", + "description": "Signal the stop of a pipeline and all of its steps that not have completed yet.", + "operationId": "stopPipeline", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "pipeline_uuid", "description": "The UUID of the pipeline.", "required": true, - "name": "pipeline_uuid", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The UUID of the step.", - "required": true, - "name": "step_uuid", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The UUID of the test case.", - "required": true, - "name": "test_case_uuid", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get test case reasons (output) for a given test case in a step of a pipeline.", - "operationId": "getPipelineTestReportTestCaseReasons" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines/{pipeline_uuid}/stopPipeline": { - "post": { "responses": { "204": { "description": "The pipeline has been signaled to stop." @@ -9050,47 +9279,38 @@ } } } - }, - "description": "Signal the stop of a pipeline and all of its steps that not have completed yet.", + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines_config": { + "get": { + "tags": ["Pipelines"], + "summary": "Get configuration", + "description": "Retrieve the repository pipelines configuration.", + "operationId": "getRepositoryPipelineConfig", "parameters": [ { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, "name": "workspace", + "description": "The account.", + "required": true, "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The UUID of the pipeline.", - "required": true, - "name": "pipeline_uuid", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Stop a pipeline", - "operationId": "stopPipeline" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines_config": { - "put": { "responses": { "200": { - "description": "The repository pipelines configuration was updated.", + "description": "The repository pipelines configuration.", "content": { "application/json": { "schema": { @@ -9099,22 +9319,27 @@ } } } - }, + } + }, + "put": { + "tags": ["Pipelines"], + "summary": "Update configuration", "description": "Update the pipelines configuration for a repository.", + "operationId": "updateRepositoryPipelineConfig", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" @@ -9132,14 +9357,9 @@ "description": "The updated repository pipelines configuration.", "required": true }, - "tags": ["Pipelines"], - "summary": "Update configuration", - "operationId": "updateRepositoryPipelineConfig" - }, - "get": { "responses": { "200": { - "description": "The repository pipelines configuration.", + "description": "The repository pipelines configuration was updated.", "content": { "application/json": { "schema": { @@ -9148,35 +9368,46 @@ } } } - }, - "description": "Retrieve the repository pipelines configuration.", + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines_config/build_number": { + "put": { + "tags": ["Pipelines"], + "summary": "Update the next build number", + "description": "Update the next build number that should be assigned to a pipeline. The next build number that will be configured has to be strictly higher than the current latest build number for this repository.", + "operationId": "updateRepositoryBuildNumber", "parameters": [ { - "description": "The account.", - "required": true, "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get configuration", - "operationId": "getRepositoryPipelineConfig" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines_config/build_number": { - "put": { + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_build_number" + } + } + }, + "description": "The build number to update.", + "required": true + }, "responses": { "200": { "description": "The build number has been configured.", @@ -9208,22 +9439,29 @@ } } } - }, - "description": "Update the next build number that should be assigned to a pipeline. The next build number that will be configured has to be strictly higher than the current latest build number for this repository.", + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines_config/schedules/": { + "post": { + "tags": ["Pipelines"], + "summary": "Create a schedule", + "description": "Create a schedule for the given repository.", + "operationId": "createRepositoryPipelineSchedule", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" @@ -9234,20 +9472,13 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/pipeline_build_number" + "$ref": "#/components/schemas/pipeline_schedule" } } }, - "description": "The build number to update.", + "description": "The schedule to create.", "required": true }, - "tags": ["Pipelines"], - "summary": "Update the next build number", - "operationId": "updateRepositoryBuildNumber" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines_config/schedules/": { - "post": { "responses": { "201": { "description": "The created schedule.", @@ -9289,44 +9520,33 @@ } } } - }, - "description": "Create a schedule for the given repository.", + } + }, + "get": { + "tags": ["Pipelines"], + "summary": "List schedules", + "description": "Retrieve the configured schedules for the given repository.", + "operationId": "getRepositoryPipelineSchedules", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_schedule" - } - } - }, - "description": "The schedule to create.", - "required": true - }, - "tags": ["Pipelines"], - "summary": "Create a schedule", - "operationId": "createRepositoryPipelineSchedule" - }, - "get": { "responses": { "200": { "description": "The list of schedules.", @@ -9348,103 +9568,44 @@ } } } - }, - "description": "Retrieve the configured schedules for the given repository.", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Pipelines"], - "summary": "List schedules", - "operationId": "getRepositoryPipelineSchedules" + } } }, "/repositories/{workspace}/{repo_slug}/pipelines_config/schedules/{schedule_uuid}": { - "put": { - "responses": { - "200": { - "description": "The schedule is updated.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_schedule" - } - } - } - }, - "404": { - "description": "The account, repository or schedule was not found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "description": "Update a schedule.", + "get": { + "tags": ["Pipelines"], + "summary": "Get a schedule", + "description": "Retrieve a schedule by its UUID.", + "operationId": "getRepositoryPipelineSchedule", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "schedule_uuid", "description": "The uuid of the schedule.", "required": true, - "name": "schedule_uuid", "in": "path", "schema": { "type": "string" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_schedule" - } - } - }, - "description": "The schedule to update.", - "required": true - }, - "tags": ["Pipelines"], - "summary": "Update a schedule", - "operationId": "updateRepositoryPipelineSchedule" - }, - "get": { "responses": { "200": { "description": "The requested schedule.", @@ -9466,42 +9627,110 @@ } } } - }, - "description": "Retrieve a schedule by its UUID.", + } + }, + "put": { + "tags": ["Pipelines"], + "summary": "Update a schedule", + "description": "Update a schedule.", + "operationId": "updateRepositoryPipelineSchedule", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "schedule_uuid", "description": "The uuid of the schedule.", "required": true, - "name": "schedule_uuid", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get a schedule", - "operationId": "getRepositoryPipelineSchedule" + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_schedule" + } + } + }, + "description": "The schedule to update.", + "required": true + }, + "responses": { + "200": { + "description": "The schedule is updated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_schedule" + } + } + } + }, + "404": { + "description": "The account, repository or schedule was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + } }, "delete": { + "tags": ["Pipelines"], + "summary": "Delete a schedule", + "description": "Delete a schedule.", + "operationId": "deleteRepositoryPipelineSchedule", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "schedule_uuid", + "description": "The uuid of the schedule.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "204": { "description": "The schedule was deleted." @@ -9516,44 +9745,44 @@ } } } - }, - "description": "Delete a schedule.", + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines_config/schedules/{schedule_uuid}/executions/": { + "get": { + "tags": ["Pipelines"], + "summary": "List executions of a schedule", + "description": "Retrieve the executions of a given schedule.", + "operationId": "getRepositoryPipelineScheduleExecutions", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { + "name": "schedule_uuid", "description": "The uuid of the schedule.", "required": true, - "name": "schedule_uuid", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Delete a schedule", - "operationId": "deleteRepositoryPipelineSchedule" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines_config/schedules/{schedule_uuid}/executions/": { - "get": { "responses": { "200": { "description": "The list of executions of a schedule.", @@ -9575,103 +9804,35 @@ } } } - }, - "description": "Retrieve the executions of a given schedule.", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The uuid of the schedule.", - "required": true, - "name": "schedule_uuid", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Pipelines"], - "summary": "List executions of a schedule", - "operationId": "getRepositoryPipelineScheduleExecutions" + } } }, "/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/key_pair": { - "put": { - "responses": { - "200": { - "description": "The SSH key pair was created or updated.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_ssh_key_pair" - } - } - } - }, - "404": { - "description": "The account, repository or SSH key pair was not found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "description": "Create or update the repository SSH key pair. The private key will be set as a default SSH identity in your build container.", + "get": { + "tags": ["Pipelines"], + "summary": "Get SSH key pair", + "description": "Retrieve the repository SSH key pair excluding the SSH private key. The private key is a write only field and will never be exposed in the logs or the REST API.", + "operationId": "getRepositoryPipelineSshKeyPair", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_ssh_key_pair" - } - } - }, - "description": "The created or updated SSH key pair.", - "required": true - }, - "tags": ["Pipelines"], - "summary": "Update SSH key pair", - "operationId": "updateRepositoryPipelineKeyPair" - }, - "get": { "responses": { "200": { "description": "The SSH key pair.", @@ -9693,33 +9854,92 @@ } } } - }, - "description": "Retrieve the repository SSH key pair excluding the SSH private key. The private key is a write only field and will never be exposed in the logs or the REST API.", + } + }, + "put": { + "tags": ["Pipelines"], + "summary": "Update SSH key pair", + "description": "Create or update the repository SSH key pair. The private key will be set as a default SSH identity in your build container.", + "operationId": "updateRepositoryPipelineKeyPair", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get SSH key pair", - "operationId": "getRepositoryPipelineSshKeyPair" + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_ssh_key_pair" + } + } + }, + "description": "The created or updated SSH key pair.", + "required": true + }, + "responses": { + "200": { + "description": "The SSH key pair was created or updated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_ssh_key_pair" + } + } + } + }, + "404": { + "description": "The account, repository or SSH key pair was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + } }, "delete": { + "tags": ["Pipelines"], + "summary": "Delete SSH key pair", + "description": "Delete the repository SSH key pair.", + "operationId": "deleteRepositoryPipelineKeyPair", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "204": { "description": "The SSH key pair was deleted." @@ -9734,37 +9954,87 @@ } } } - }, - "description": "Delete the repository SSH key pair.", + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/known_hosts/": { + "get": { + "tags": ["Pipelines"], + "summary": "List known hosts", + "description": "Find repository level known hosts.", + "operationId": "getRepositoryPipelineKnownHosts", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Delete SSH key pair", - "operationId": "deleteRepositoryPipelineKeyPair" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/known_hosts/": { + "responses": { + "200": { + "description": "The retrieved known hosts.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pipeline_known_hosts" + } + } + } + } + } + }, "post": { + "tags": ["Pipelines"], + "summary": "Create a known host", + "description": "Create a repository level known host.", + "operationId": "createRepositoryPipelineKnownHost", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_known_host" + } + } + }, + "description": "The known host to create.", + "required": true + }, "responses": { "201": { + "description": "The known host was created.", "headers": { "Location": { "description": "The URL of the newly created pipeline known host.", @@ -9773,7 +10043,6 @@ } } }, - "description": "The known host was created.", "content": { "application/json": { "schema": { @@ -9802,152 +10071,44 @@ } } } - }, - "description": "Create a repository level known host.", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_known_host" - } - } - }, - "description": "The known host to create.", - "required": true - }, - "tags": ["Pipelines"], - "summary": "Create a known host", - "operationId": "createRepositoryPipelineKnownHost" - }, - "get": { - "responses": { - "200": { - "description": "The retrieved known hosts.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_pipeline_known_hosts" - } - } - } - } - }, - "description": "Find repository level known hosts.", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Pipelines"], - "summary": "List known hosts", - "operationId": "getRepositoryPipelineKnownHosts" + } } }, "/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/known_hosts/{known_host_uuid}": { - "put": { - "responses": { - "200": { - "description": "The known host was updated.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_known_host" - } - } - } - }, - "404": { - "description": "The account, repository or known host with the given UUID was not found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "description": "Update a repository level known host.", + "get": { + "tags": ["Pipelines"], + "summary": "Get a known host", + "description": "Retrieve a repository level known host.", + "operationId": "getRepositoryPipelineKnownHost", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { - "description": "The UUID of the known host to update.", - "required": true, "name": "known_host_uuid", + "description": "The UUID of the known host to retrieve.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_known_host" - } - } - }, - "description": "The updated known host.", - "required": true - }, - "tags": ["Pipelines"], - "summary": "Update a known host", - "operationId": "updateRepositoryPipelineKnownHost" - }, - "get": { "responses": { "200": { "description": "The known host.", @@ -9969,42 +10130,110 @@ } } } - }, - "description": "Retrieve a repository level known host.", + } + }, + "put": { + "tags": ["Pipelines"], + "summary": "Update a known host", + "description": "Update a repository level known host.", + "operationId": "updateRepositoryPipelineKnownHost", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { - "description": "The UUID of the known host to retrieve.", - "required": true, "name": "known_host_uuid", + "description": "The UUID of the known host to update.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get a known host", - "operationId": "getRepositoryPipelineKnownHost" + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_known_host" + } + } + }, + "description": "The updated known host.", + "required": true + }, + "responses": { + "200": { + "description": "The known host was updated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_known_host" + } + } + } + }, + "404": { + "description": "The account, repository or known host with the given UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + } }, "delete": { + "tags": ["Pipelines"], + "summary": "Delete a known host", + "description": "Delete a repository level known host.", + "operationId": "deleteRepositoryPipelineKnownHost", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "known_host_uuid", + "description": "The UUID of the known host to delete.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "204": { "description": "The known host was deleted." @@ -10019,46 +10248,87 @@ } } } - }, - "description": "Delete a repository level known host.", + } + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines_config/variables/": { + "get": { + "tags": ["Pipelines"], + "summary": "List variables for a repository", + "description": "Find repository level variables.", + "operationId": "getRepositoryPipelineVariables", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The UUID of the known host to delete.", - "required": true, - "name": "known_host_uuid", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Delete a known host", - "operationId": "deleteRepositoryPipelineKnownHost" - } - }, - "/repositories/{workspace}/{repo_slug}/pipelines_config/variables/": { + "responses": { + "200": { + "description": "The retrieved variables.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pipeline_variables" + } + } + } + } + } + }, "post": { + "tags": ["Pipelines"], + "summary": "Create a variable for a repository", + "description": "Create a repository level variable.", + "operationId": "createRepositoryPipelineVariable", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + }, + "description": "The variable to create.", + "required": true + }, "responses": { "201": { + "description": "The variable was created.", "headers": { "Location": { "description": "The URL of the newly created pipeline variable.", @@ -10067,7 +10337,6 @@ } } }, - "description": "The variable was created.", "content": { "application/json": { "schema": { @@ -10096,152 +10365,44 @@ } } } - }, - "description": "Create a repository level variable.", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_variable" - } - } - }, - "description": "The variable to create.", - "required": true - }, - "tags": ["Pipelines"], - "summary": "Create a variable for a repository", - "operationId": "createRepositoryPipelineVariable" - }, - "get": { - "responses": { - "200": { - "description": "The retrieved variables.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_pipeline_variables" - } - } - } - } - }, - "description": "Find repository level variables.", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Pipelines"], - "summary": "List variables for a repository", - "operationId": "getRepositoryPipelineVariables" + } } }, "/repositories/{workspace}/{repo_slug}/pipelines_config/variables/{variable_uuid}": { - "put": { - "responses": { - "200": { - "description": "The variable was updated.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_variable" - } - } - } - }, - "404": { - "description": "The account, repository or variable with the given UUID was not found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "description": "Update a repository level variable.", + "get": { + "tags": ["Pipelines"], + "summary": "Get a variable for a repository", + "description": "Retrieve a repository level variable.", + "operationId": "getRepositoryPipelineVariable", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { - "description": "The UUID of the variable to update.", - "required": true, "name": "variable_uuid", + "description": "The UUID of the variable to retrieve.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "requestBody": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_variable" - } - } - }, - "description": "The updated variable", - "required": true - }, - "tags": ["Pipelines"], - "summary": "Update a variable for a repository", - "operationId": "updateRepositoryPipelineVariable" - }, - "get": { "responses": { "200": { "description": "The variable.", @@ -10263,42 +10424,110 @@ } } } - }, - "description": "Retrieve a repository level variable.", + } + }, + "put": { + "tags": ["Pipelines"], + "summary": "Update a variable for a repository", + "description": "Update a repository level variable.", + "operationId": "updateRepositoryPipelineVariable", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { + "name": "repo_slug", "description": "The repository.", "required": true, - "name": "repo_slug", "in": "path", "schema": { "type": "string" } }, { - "description": "The UUID of the variable to retrieve.", - "required": true, "name": "variable_uuid", + "description": "The UUID of the variable to update.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get a variable for a repository", - "operationId": "getRepositoryPipelineVariable" + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + }, + "description": "The updated variable", + "required": true + }, + "responses": { + "200": { + "description": "The variable was updated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + } + }, + "404": { + "description": "The account, repository or variable with the given UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + } }, "delete": { + "tags": ["Pipelines"], + "summary": "Delete a variable for a repository", + "description": "Delete a repository level variable.", + "operationId": "deleteRepositoryPipelineVariable", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "repo_slug", + "description": "The repository.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "variable_uuid", + "description": "The UUID of the variable to delete.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "204": { "description": "The variable was deleted." @@ -10313,40 +10542,7 @@ } } } - }, - "description": "Delete a repository level variable.", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The repository.", - "required": true, - "name": "repo_slug", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The UUID of the variable to delete.", - "required": true, - "name": "variable_uuid", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Pipelines"], - "summary": "Delete a variable for a repository", - "operationId": "deleteRepositoryPipelineVariable" + } } }, "/repositories/{workspace}/{repo_slug}/properties/{app_key}/{property_name}": { @@ -10356,39 +10552,42 @@ "description": "An empty response." } }, + "operationId": "updateRepositoryHostedPropertyValue", + "summary": "Update a repository application property", + "description": "Update an [application property](/cloud/bitbucket/application-properties/) value stored against a repository.", "parameters": [ { "required": true, - "description": "The repository container; either the workspace slug or the UUID in curly braces.", "in": "path", "name": "workspace", + "description": "The repository container; either the workspace slug or the UUID in curly braces.", "schema": { "type": "string" } }, { "required": true, - "description": "The repository.", "in": "path", "name": "repo_slug", + "description": "The repository.", "schema": { "type": "string" } }, { "required": true, - "description": "The key of the Connect app.", "in": "path", "name": "app_key", + "description": "The key of the Connect app.", "schema": { "type": "string" } }, { "required": true, - "description": "The name of the property.", "in": "path", "name": "property_name", + "description": "The name of the property.", "schema": { "type": "string" } @@ -10397,10 +10596,7 @@ "requestBody": { "$ref": "#/components/requestBodies/application_property" }, - "tags": ["properties"], - "description": "Update an [application property](/cloud/bitbucket/application-properties/) value stored against a repository.", - "summary": "Update a repository application property", - "operationId": "updateRepositoryHostedPropertyValue" + "tags": ["properties"] }, "delete": { "responses": { @@ -10408,48 +10604,48 @@ "description": "An empty response." } }, + "operationId": "deleteRepositoryHostedPropertyValue", + "summary": "Delete a repository application property", + "description": "Delete an [application property](/cloud/bitbucket/application-properties/) value stored against a repository.", "parameters": [ { "required": true, - "description": "The repository container; either the workspace slug or the UUID in curly braces.", "in": "path", "name": "workspace", + "description": "The repository container; either the workspace slug or the UUID in curly braces.", "schema": { "type": "string" } }, { "required": true, - "description": "The repository.", "in": "path", "name": "repo_slug", + "description": "The repository.", "schema": { "type": "string" } }, { "required": true, - "description": "The key of the Connect app.", "in": "path", "name": "app_key", + "description": "The key of the Connect app.", "schema": { "type": "string" } }, { "required": true, - "description": "The name of the property.", "in": "path", "name": "property_name", + "description": "The name of the property.", "schema": { "type": "string" } } ], - "tags": ["properties"], - "description": "Delete an [application property](/cloud/bitbucket/application-properties/) value stored against a repository.", - "summary": "Delete a repository application property", - "operationId": "deleteRepositoryHostedPropertyValue" + "tags": ["properties"] }, "get": { "responses": { @@ -10464,52 +10660,55 @@ } } }, + "operationId": "getRepositoryHostedPropertyValue", + "summary": "Get a repository application property", + "description": "Retrieve an [application property](/cloud/bitbucket/application-properties/) value stored against a repository.", "parameters": [ { "required": true, - "description": "The repository container; either the workspace slug or the UUID in curly braces.", "in": "path", "name": "workspace", + "description": "The repository container; either the workspace slug or the UUID in curly braces.", "schema": { "type": "string" } }, { "required": true, - "description": "The repository.", "in": "path", "name": "repo_slug", + "description": "The repository.", "schema": { "type": "string" } }, { "required": true, - "description": "The key of the Connect app.", "in": "path", "name": "app_key", + "description": "The key of the Connect app.", "schema": { "type": "string" } }, { "required": true, - "description": "The name of the property.", "in": "path", "name": "property_name", + "description": "The name of the property.", "schema": { "type": "string" } } ], - "tags": ["properties"], - "description": "Retrieve an [application property](/cloud/bitbucket/application-properties/) value stored against a repository.", - "summary": "Get a repository application property", - "operationId": "getRepositoryHostedPropertyValue" + "tags": ["properties"] } }, "/repositories/{workspace}/{repo_slug}/pullrequests": { "get": { + "tags": ["Pullrequests"], + "description": "Returns all pull requests on the specified repository.\n\nBy default only open pull requests are returned. This can be controlled\nusing the `state` query parameter. To retrieve pull requests that are\nin one of multiple states, repeat the `state` parameter for each\nindividual state.\n\nThis endpoint also supports filtering and sorting of the results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more details.", + "summary": "List pull requests", "responses": { "200": { "description": "All pull requests on the specified repository.", @@ -10542,12 +10741,10 @@ "description": "Only return pull requests that are in this state. This parameter can be repeated.", "schema": { "type": "string", - "enum": ["MERGED", "SUPERSEDED", "OPEN", "DECLINED"] + "enum": ["OPEN", "MERGED", "DECLINED", "SUPERSEDED"] } } ], - "tags": ["Pullrequests"], - "summary": "List pull requests", "security": [ { "oauth2": ["pullrequest"] @@ -10558,10 +10755,12 @@ { "api_key": [] } - ], - "description": "Returns all pull requests on the specified repository.\n\nBy default only open pull requests are returned. This can be controlled\nusing the `state` query parameter. To retrieve pull requests that are\nin one of multiple states, repeat the `state` parameter for each\nindividual state.\n\nThis endpoint also supports filtering and sorting of the results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more details." + ] }, "post": { + "tags": ["Pullrequests"], + "description": "Creates a new pull request where the destination repository is\nthis repository and the author is the authenticated user.\n\nThe minimum required fields to create a pull request are `title` and\n`source`, specified by a branch name.\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/my-workspace/my-repository/pullrequests \\\n -u my-username:my-password \\\n --request POST \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"title\": \"My Title\",\n \"source\": {\n \"branch\": {\n \"name\": \"staging\"\n }\n }\n }'\n```\n\nIf the pull request's `destination` is not specified, it will default\nto the `repository.mainbranch`. To open a pull request to a\ndifferent branch, say from a feature branch to a staging branch,\nspecify a `destination` (same format as the `source`):\n\n```\n{\n \"title\": \"My Title\",\n \"source\": {\n \"branch\": {\n \"name\": \"my-feature-branch\"\n }\n },\n \"destination\": {\n \"branch\": {\n \"name\": \"staging\"\n }\n }\n}\n```\n\nReviewers can be specified by adding an array of user objects as the\n`reviewers` property.\n\n```\n{\n \"title\": \"My Title\",\n \"source\": {\n \"branch\": {\n \"name\": \"my-feature-branch\"\n }\n },\n \"reviewers\": [\n {\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n }\n ]\n}\n```\n\nOther fields:\n\n* `description` - a string\n* `close_source_branch` - boolean that specifies if the source branch should be closed upon merging", + "summary": "Create a pull request", "responses": { "201": { "description": "The newly created pull request.", @@ -10612,8 +10811,6 @@ }, "description": "The new pull request.\n\nThe request URL you POST to becomes the destination repository URL. For this reason, you must specify an explicit source repository in the request object if you want to pull from a different repository (fork).\n\nSince not all elements are required or even mutable, you only need to include the elements you want to initialize, such as the source branch and the title." }, - "tags": ["Pullrequests"], - "summary": "Create a pull request", "security": [ { "oauth2": ["pullrequest:write"] @@ -10624,8 +10821,7 @@ { "api_key": [] } - ], - "description": "Creates a new pull request where the destination repository is\nthis repository and the author is the authenticated user.\n\nThe minimum required fields to create a pull request are `title` and\n`source`, specified by a branch name.\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/my-workspace/my-repository/pullrequests \\\n -u my-username:my-password \\\n --request POST \\\n --header 'Content-Type: application/json' \\\n --data '{\n \"title\": \"My Title\",\n \"source\": {\n \"branch\": {\n \"name\": \"staging\"\n }\n }\n }'\n```\n\nIf the pull request's `destination` is not specified, it will default\nto the `repository.mainbranch`. To open a pull request to a\ndifferent branch, say from a feature branch to a staging branch,\nspecify a `destination` (same format as the `source`):\n\n```\n{\n \"title\": \"My Title\",\n \"source\": {\n \"branch\": {\n \"name\": \"my-feature-branch\"\n }\n },\n \"destination\": {\n \"branch\": {\n \"name\": \"staging\"\n }\n }\n}\n```\n\nReviewers can be specified by adding an array of user objects as the\n`reviewers` property.\n\n```\n{\n \"title\": \"My Title\",\n \"source\": {\n \"branch\": {\n \"name\": \"my-feature-branch\"\n }\n },\n \"reviewers\": [\n {\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n }\n ]\n}\n```\n\nOther fields:\n\n* `description` - a string\n* `close_source_branch` - boolean that specifies if the source branch should be closed upon merging" + ] }, "parameters": [ { @@ -10650,6 +10846,9 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/activity": { "get": { + "tags": ["Pullrequests"], + "description": "Returns a paginated list of the pull request's activity log.\n\nThis handler serves both a v20 and internal endpoint. The v20 endpoint\nreturns reviewer comments, updates, approvals and request changes. The internal\nendpoint includes those plus tasks and attachments.\n\nComments created on a file or a line of code have an inline property.\n\nComment example:\n```\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"comment\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695/comments/118571088\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695/_/diff#comment-118571088\"\n }\n },\n \"deleted\": false,\n \"pullrequest\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n },\n \"content\": {\n \"raw\": \"inline with to a dn from lines\",\n \"markup\": \"markdown\",\n \"html\": \"

inline with to a dn from lines

\",\n \"type\": \"rendered\"\n },\n \"created_on\": \"2019-09-27T00:33:46.039178+00:00\",\n \"user\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n },\n \"created_on\": \"2019-09-27T00:33:46.039178+00:00\",\n \"user\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n },\n \"updated_on\": \"2019-09-27T00:33:46.055384+00:00\",\n \"inline\": {\n \"context_lines\": \"\",\n \"to\": null,\n \"path\": \"\",\n \"outdated\": false,\n \"from\": 211\n },\n \"type\": \"pullrequest_comment\",\n \"id\": 118571088\n },\n \"pull_request\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n }\n }\n ]\n}\n```\n\nUpdates include a state property of OPEN, MERGED, or DECLINED.\n\nUpdate example:\n```\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"update\": {\n \"description\": \"\",\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\",\n \"destination\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"6a2c16e4a152\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/commit/6a2c16e4a152\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/commits/6a2c16e4a152\"\n }\n }\n },\n \"branch\": {\n \"name\": \"master\"\n },\n \"repository\": {\n \"name\": \"Atlaskit-MK-2\",\n \"type\": \"repository\",\n \"full_name\": \"atlassian/atlaskit-mk-2\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B%7D?ts=js\"\n }\n },\n \"uuid\": \"{}\"\n }\n },\n \"reason\": \"\",\n \"source\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"728c8bad1813\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/commit/728c8bad1813\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/commits/728c8bad1813\"\n }\n }\n },\n \"branch\": {\n \"name\": \"username/NONE-add-onClick-prop-for-accessibility\"\n },\n \"repository\": {\n \"name\": \"Atlaskit-MK-2\",\n \"type\": \"repository\",\n \"full_name\": \"atlassian/atlaskit-mk-2\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B%7D?ts=js\"\n }\n },\n \"uuid\": \"{}\"\n }\n },\n \"state\": \"OPEN\",\n \"author\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n },\n \"date\": \"2019-05-10T06:48:25.305565+00:00\"\n },\n \"pull_request\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n }\n }\n ]\n}\n```\n\nApproval example:\n```\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"approval\": {\n \"date\": \"2019-09-27T00:37:19.849534+00:00\",\n \"pullrequest\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n },\n \"user\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n }\n },\n \"pull_request\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n }\n }\n ]\n}\n```", + "summary": "List a pull request activity log", "responses": { "200": { "description": "The pull request activity log" @@ -10668,8 +10867,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "List a pull request activity log", "security": [ { "oauth2": ["pullrequest"] @@ -10680,8 +10877,7 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of the pull request's activity log.\n\nThis handler serves both a v20 and internal endpoint. The v20 endpoint\nreturns reviewer comments, updates, approvals and request changes. The internal\nendpoint includes those plus tasks and attachments.\n\nComments created on a file or a line of code have an inline property.\n\nComment example:\n```\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"comment\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695/comments/118571088\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695/_/diff#comment-118571088\"\n }\n },\n \"deleted\": false,\n \"pullrequest\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n },\n \"content\": {\n \"raw\": \"inline with to a dn from lines\",\n \"markup\": \"markdown\",\n \"html\": \"

inline with to a dn from lines

\",\n \"type\": \"rendered\"\n },\n \"created_on\": \"2019-09-27T00:33:46.039178+00:00\",\n \"user\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n },\n \"created_on\": \"2019-09-27T00:33:46.039178+00:00\",\n \"user\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n },\n \"updated_on\": \"2019-09-27T00:33:46.055384+00:00\",\n \"inline\": {\n \"context_lines\": \"\",\n \"to\": null,\n \"path\": \"\",\n \"outdated\": false,\n \"from\": 211\n },\n \"type\": \"pullrequest_comment\",\n \"id\": 118571088\n },\n \"pull_request\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n }\n }\n ]\n}\n```\n\nUpdates include a state property of OPEN, MERGED, or DECLINED.\n\nUpdate example:\n```\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"update\": {\n \"description\": \"\",\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\",\n \"destination\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"6a2c16e4a152\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/commit/6a2c16e4a152\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/commits/6a2c16e4a152\"\n }\n }\n },\n \"branch\": {\n \"name\": \"master\"\n },\n \"repository\": {\n \"name\": \"Atlaskit-MK-2\",\n \"type\": \"repository\",\n \"full_name\": \"atlassian/atlaskit-mk-2\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B%7D?ts=js\"\n }\n },\n \"uuid\": \"{}\"\n }\n },\n \"reason\": \"\",\n \"source\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"728c8bad1813\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/commit/728c8bad1813\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/commits/728c8bad1813\"\n }\n }\n },\n \"branch\": {\n \"name\": \"username/NONE-add-onClick-prop-for-accessibility\"\n },\n \"repository\": {\n \"name\": \"Atlaskit-MK-2\",\n \"type\": \"repository\",\n \"full_name\": \"atlassian/atlaskit-mk-2\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B%7D?ts=js\"\n }\n },\n \"uuid\": \"{}\"\n }\n },\n \"state\": \"OPEN\",\n \"author\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n },\n \"date\": \"2019-05-10T06:48:25.305565+00:00\"\n },\n \"pull_request\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n }\n }\n ]\n}\n```\n\nApproval example:\n```\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"approval\": {\n \"date\": \"2019-09-27T00:37:19.849534+00:00\",\n \"pullrequest\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n },\n \"user\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n }\n },\n \"pull_request\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n }\n }\n ]\n}\n```" + ] }, "parameters": [ { @@ -10706,6 +10902,9 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}": { "get": { + "tags": ["Pullrequests"], + "description": "Returns the specified pull request.", + "summary": "Get a pull request", "responses": { "200": { "description": "The pull request object", @@ -10731,8 +10930,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "Get a pull request", "security": [ { "oauth2": ["pullrequest"] @@ -10743,10 +10940,12 @@ { "api_key": [] } - ], - "description": "Returns the specified pull request." + ] }, "put": { + "tags": ["Pullrequests"], + "description": "Mutates the specified pull request.\n\nThis can be used to change the pull request's branches or description.\n\nOnly open pull requests can be mutated.", + "summary": "Update a pull request", "responses": { "200": { "description": "The updated pull request", @@ -10799,8 +10998,6 @@ }, "description": "The pull request that is to be updated." }, - "tags": ["Pullrequests"], - "summary": "Update a pull request", "security": [ { "oauth2": ["pullrequest:write"] @@ -10811,8 +11008,7 @@ { "api_key": [] } - ], - "description": "Mutates the specified pull request.\n\nThis can be used to change the pull request's branches or description.\n\nOnly open pull requests can be mutated." + ] }, "parameters": [ { @@ -10846,6 +11042,9 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/activity": { "get": { + "tags": ["Pullrequests"], + "description": "Returns a paginated list of the pull request's activity log.\n\nThis handler serves both a v20 and internal endpoint. The v20 endpoint\nreturns reviewer comments, updates, approvals and request changes. The internal\nendpoint includes those plus tasks and attachments.\n\nComments created on a file or a line of code have an inline property.\n\nComment example:\n```\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"comment\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695/comments/118571088\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695/_/diff#comment-118571088\"\n }\n },\n \"deleted\": false,\n \"pullrequest\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n },\n \"content\": {\n \"raw\": \"inline with to a dn from lines\",\n \"markup\": \"markdown\",\n \"html\": \"

inline with to a dn from lines

\",\n \"type\": \"rendered\"\n },\n \"created_on\": \"2019-09-27T00:33:46.039178+00:00\",\n \"user\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n },\n \"created_on\": \"2019-09-27T00:33:46.039178+00:00\",\n \"user\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n },\n \"updated_on\": \"2019-09-27T00:33:46.055384+00:00\",\n \"inline\": {\n \"context_lines\": \"\",\n \"to\": null,\n \"path\": \"\",\n \"outdated\": false,\n \"from\": 211\n },\n \"type\": \"pullrequest_comment\",\n \"id\": 118571088\n },\n \"pull_request\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n }\n }\n ]\n}\n```\n\nUpdates include a state property of OPEN, MERGED, or DECLINED.\n\nUpdate example:\n```\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"update\": {\n \"description\": \"\",\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\",\n \"destination\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"6a2c16e4a152\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/commit/6a2c16e4a152\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/commits/6a2c16e4a152\"\n }\n }\n },\n \"branch\": {\n \"name\": \"master\"\n },\n \"repository\": {\n \"name\": \"Atlaskit-MK-2\",\n \"type\": \"repository\",\n \"full_name\": \"atlassian/atlaskit-mk-2\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B%7D?ts=js\"\n }\n },\n \"uuid\": \"{}\"\n }\n },\n \"reason\": \"\",\n \"source\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"728c8bad1813\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/commit/728c8bad1813\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/commits/728c8bad1813\"\n }\n }\n },\n \"branch\": {\n \"name\": \"username/NONE-add-onClick-prop-for-accessibility\"\n },\n \"repository\": {\n \"name\": \"Atlaskit-MK-2\",\n \"type\": \"repository\",\n \"full_name\": \"atlassian/atlaskit-mk-2\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B%7D?ts=js\"\n }\n },\n \"uuid\": \"{}\"\n }\n },\n \"state\": \"OPEN\",\n \"author\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n },\n \"date\": \"2019-05-10T06:48:25.305565+00:00\"\n },\n \"pull_request\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n }\n }\n ]\n}\n```\n\nApproval example:\n```\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"approval\": {\n \"date\": \"2019-09-27T00:37:19.849534+00:00\",\n \"pullrequest\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n },\n \"user\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n }\n },\n \"pull_request\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n }\n }\n ]\n}\n```", + "summary": "List a pull request activity log", "responses": { "200": { "description": "The pull request activity log" @@ -10864,8 +11063,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "List a pull request activity log", "security": [ { "oauth2": ["pullrequest"] @@ -10876,8 +11073,7 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of the pull request's activity log.\n\nThis handler serves both a v20 and internal endpoint. The v20 endpoint\nreturns reviewer comments, updates, approvals and request changes. The internal\nendpoint includes those plus tasks and attachments.\n\nComments created on a file or a line of code have an inline property.\n\nComment example:\n```\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"comment\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695/comments/118571088\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695/_/diff#comment-118571088\"\n }\n },\n \"deleted\": false,\n \"pullrequest\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n },\n \"content\": {\n \"raw\": \"inline with to a dn from lines\",\n \"markup\": \"markdown\",\n \"html\": \"

inline with to a dn from lines

\",\n \"type\": \"rendered\"\n },\n \"created_on\": \"2019-09-27T00:33:46.039178+00:00\",\n \"user\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n },\n \"created_on\": \"2019-09-27T00:33:46.039178+00:00\",\n \"user\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n },\n \"updated_on\": \"2019-09-27T00:33:46.055384+00:00\",\n \"inline\": {\n \"context_lines\": \"\",\n \"to\": null,\n \"path\": \"\",\n \"outdated\": false,\n \"from\": 211\n },\n \"type\": \"pullrequest_comment\",\n \"id\": 118571088\n },\n \"pull_request\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n }\n }\n ]\n}\n```\n\nUpdates include a state property of OPEN, MERGED, or DECLINED.\n\nUpdate example:\n```\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"update\": {\n \"description\": \"\",\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\",\n \"destination\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"6a2c16e4a152\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/commit/6a2c16e4a152\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/commits/6a2c16e4a152\"\n }\n }\n },\n \"branch\": {\n \"name\": \"master\"\n },\n \"repository\": {\n \"name\": \"Atlaskit-MK-2\",\n \"type\": \"repository\",\n \"full_name\": \"atlassian/atlaskit-mk-2\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B%7D?ts=js\"\n }\n },\n \"uuid\": \"{}\"\n }\n },\n \"reason\": \"\",\n \"source\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"728c8bad1813\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/commit/728c8bad1813\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/commits/728c8bad1813\"\n }\n }\n },\n \"branch\": {\n \"name\": \"username/NONE-add-onClick-prop-for-accessibility\"\n },\n \"repository\": {\n \"name\": \"Atlaskit-MK-2\",\n \"type\": \"repository\",\n \"full_name\": \"atlassian/atlaskit-mk-2\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B%7D?ts=js\"\n }\n },\n \"uuid\": \"{}\"\n }\n },\n \"state\": \"OPEN\",\n \"author\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n },\n \"date\": \"2019-05-10T06:48:25.305565+00:00\"\n },\n \"pull_request\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n }\n }\n ]\n}\n```\n\nApproval example:\n```\n{\n \"pagelen\": 20,\n \"values\": [\n {\n \"approval\": {\n \"date\": \"2019-09-27T00:37:19.849534+00:00\",\n \"pullrequest\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n },\n \"user\": {\n \"display_name\": \"Name Lastname\",\n \"uuid\": \"{}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/users/%7B%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/:/128\"\n }\n },\n \"type\": \"user\",\n \"nickname\": \"Name\",\n \"account_id\": \"\"\n }\n },\n \"pull_request\": {\n \"type\": \"pullrequest\",\n \"id\": 5695,\n \"links\": {\n \"self\": {\n \"href\": \"https://bitbucket.org/!api/2.0/repositories/atlassian/atlaskit-mk-2/pullrequests/5695\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/atlaskit-mk-2/pull-requests/5695\"\n }\n },\n \"title\": \"username/NONE: small change from onFocus to onClick to handle tabbing through the page and not expand the editor unless a click event triggers it\"\n }\n }\n ]\n}\n```" + ] }, "parameters": [ { @@ -10911,6 +11107,9 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/approve": { "delete": { + "tags": ["Pullrequests"], + "description": "Redact the authenticated user's approval of the specified pull\nrequest.", + "summary": "Unapprove a pull request", "responses": { "204": { "description": "An empty response indicating the authenticated user's approval has been withdrawn." @@ -10936,8 +11135,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "Unapprove a pull request", "security": [ { "oauth2": ["pullrequest:write"] @@ -10948,10 +11145,12 @@ { "api_key": [] } - ], - "description": "Redact the authenticated user's approval of the specified pull\nrequest." + ] }, "post": { + "tags": ["Pullrequests"], + "description": "Approve the specified pull request as the authenticated user.", + "summary": "Approve a pull request", "responses": { "200": { "description": "The `participant` object recording that the authenticated user approved the pull request.", @@ -10984,8 +11183,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "Approve a pull request", "security": [ { "oauth2": ["pullrequest:write"] @@ -10996,8 +11193,7 @@ { "api_key": [] } - ], - "description": "Approve the specified pull request as the authenticated user." + ] }, "parameters": [ { @@ -11031,6 +11227,9 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/comments": { "get": { + "tags": ["Pullrequests"], + "description": "Returns a paginated list of the pull request's comments.\n\nThis includes both global, inline comments and replies.\n\nThe default sorting is oldest to newest and can be overridden with\nthe `sort` query parameter.\n\nThis endpoint also supports filtering and sorting of the results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more\ndetails.", + "summary": "List comments on a pull request", "responses": { "200": { "description": "A paginated list of comments made on the given pull request, in chronological order.", @@ -11063,8 +11262,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "List comments on a pull request", "security": [ { "oauth2": ["pullrequest"] @@ -11075,10 +11272,12 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of the pull request's comments.\n\nThis includes both global, inline comments and replies.\n\nThe default sorting is oldest to newest and can be overridden with\nthe `sort` query parameter.\n\nThis endpoint also supports filtering and sorting of the results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more\ndetails." + ] }, "post": { + "tags": ["Pullrequests"], + "description": "Creates a new pull request comment.\n\nReturns the newly created pull request comment.", + "summary": "Create a comment on a pull request", "responses": { "201": { "description": "The newly created comment.", @@ -11130,8 +11329,6 @@ "description": "The comment object.", "required": true }, - "tags": ["Pullrequests"], - "summary": "Create a comment on a pull request", "security": [ { "oauth2": ["pullrequest"] @@ -11142,8 +11339,7 @@ { "api_key": [] } - ], - "description": "Creates a new pull request comment.\n\nReturns the newly created pull request comment." + ] }, "parameters": [ { @@ -11177,6 +11373,9 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/comments/{comment_id}": { "delete": { + "tags": ["Pullrequests"], + "description": "Deletes a specific pull request comment.", + "summary": "Delete a comment on a pull request", "responses": { "204": { "description": "Successful deletion." @@ -11202,8 +11401,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "Delete a comment on a pull request", "security": [ { "oauth2": ["pullrequest"] @@ -11214,10 +11411,12 @@ { "api_key": [] } - ], - "description": "Deletes a specific pull request comment." + ] }, "get": { + "tags": ["Pullrequests"], + "description": "Returns a specific pull request comment.", + "summary": "Get a comment on a pull request", "responses": { "200": { "description": "The comment.", @@ -11250,8 +11449,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "Get a comment on a pull request", "security": [ { "oauth2": ["pullrequest"] @@ -11262,10 +11459,12 @@ { "api_key": [] } - ], - "description": "Returns a specific pull request comment." + ] }, "put": { + "tags": ["Pullrequests"], + "description": "Updates a specific pull request comment.", + "summary": "Update a comment on a pull request", "responses": { "200": { "description": "The updated comment.", @@ -11309,8 +11508,6 @@ "description": "The contents of the updated comment.", "required": true }, - "tags": ["Pullrequests"], - "summary": "Update a comment on a pull request", "security": [ { "oauth2": ["pullrequest"] @@ -11321,8 +11518,7 @@ { "api_key": [] } - ], - "description": "Updates a specific pull request comment." + ] }, "parameters": [ { @@ -11365,6 +11561,9 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/commits": { "get": { + "tags": ["Pullrequests"], + "description": "Returns a paginated list of the pull request's commits.\n\nThese are the commits that are being merged into the destination\nbranch when the pull requests gets accepted.", + "summary": "List commits on a pull request", "responses": { "200": { "description": "A paginated list of commits made on the given pull request, in chronological order. This list will be empty if the source branch no longer exists." @@ -11390,8 +11589,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "List commits on a pull request", "security": [ { "oauth2": ["pullrequest"] @@ -11402,8 +11599,7 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of the pull request's commits.\n\nThese are the commits that are being merged into the destination\nbranch when the pull requests gets accepted." + ] }, "parameters": [ { @@ -11437,6 +11633,9 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/decline": { "post": { + "tags": ["Pullrequests"], + "description": "Declines the pull request.", + "summary": "Decline a pull request", "responses": { "200": { "description": "The pull request was successfully declined.", @@ -11459,8 +11658,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "Decline a pull request", "security": [ { "oauth2": ["pullrequest:write"] @@ -11471,8 +11668,7 @@ { "api_key": [] } - ], - "description": "Declines the pull request." + ] }, "parameters": [ { @@ -11506,13 +11702,14 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/diff": { "get": { + "tags": ["Pullrequests"], + "description": "Redirects to the [repository diff](/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-diff-spec-get)\nwith the revspec that corresponds to the pull request.", + "summary": "List changes in a pull request", "responses": { "302": { "description": "Redirects to the [repository diff](/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-diff-spec-get) with the\nrevspec that corresponds to the pull request.\n" } }, - "tags": ["Pullrequests"], - "summary": "List changes in a pull request", "security": [ { "oauth2": ["pullrequest"] @@ -11523,8 +11720,7 @@ { "api_key": [] } - ], - "description": "Redirects to the [repository diff](/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-diff-spec-get)\nwith the revspec that corresponds to the pull request." + ] }, "parameters": [ { @@ -11558,13 +11754,14 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/diffstat": { "get": { + "tags": ["Pullrequests"], + "description": "Redirects to the [repository diffstat](/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-diffstat-spec-get)\nwith the revspec that corresponds to the pull request.", + "summary": "Get the diff stat for a pull request", "responses": { "302": { "description": "Redirects to the [repository diffstat](/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-diffstat-spec-get) with\nthe revspec that corresponds to pull request.\n" } }, - "tags": ["Pullrequests"], - "summary": "Get the diff stat for a pull request", "security": [ { "oauth2": ["pullrequest"] @@ -11575,8 +11772,7 @@ { "api_key": [] } - ], - "description": "Redirects to the [repository diffstat](/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-diffstat-spec-get)\nwith the revspec that corresponds to the pull request." + ] }, "parameters": [ { @@ -11610,6 +11806,9 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/merge": { "post": { + "tags": ["Pullrequests"], + "description": "Merges the pull request.", + "summary": "Merge a pull request", "responses": { "200": { "description": "The pull request object.", @@ -11655,8 +11854,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "Merge a pull request", "security": [ { "oauth2": ["pullrequest:write"] @@ -11667,8 +11864,7 @@ { "api_key": [] } - ], - "description": "Merges the pull request." + ] }, "parameters": [ { @@ -11702,6 +11898,9 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/merge/task-status/{task_id}": { "get": { + "tags": ["Pullrequests"], + "description": "When merging a pull request takes too long, the client receives a\ntask ID along with a 202 status code. The task ID can be used in a call\nto this endpoint to check the status of a merge task.\n\n```\ncurl -X GET https://api.bitbucket.org/2.0/repositories/atlassian/bitbucket/pullrequests/2286/merge/task-status/\n```\n\nIf the merge task is not yet finished, a PENDING status will be returned.\n\n```\nHTTP/2 200\n{\n \"task_status\": \"PENDING\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bitbucket/pullrequests/2286/merge/task-status/\"\n }\n }\n}\n```\n\nIf the merge was successful, a SUCCESS status will be returned.\n\n```\nHTTP/2 200\n{\n \"task_status\": \"SUCCESS\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bitbucket/pullrequests/2286/merge/task-status/\"\n }\n },\n \"merge_result\": \n}\n```\n\nIf the merge task failed, an error will be returned.\n\n```\n{\n \"type\": \"error\",\n \"error\": {\n \"message\": \"\"\n }\n}\n```", + "summary": "Get the merge task status for a pull request", "responses": { "200": { "description": "Returns a task status if the merge is either pending or successful, and if it is successful, a pull request" @@ -11713,8 +11912,6 @@ "description": "The user making the request does not have permission to the repo and is different from the user who queued the task" } }, - "tags": ["Pullrequests"], - "summary": "Get the merge task status for a pull request", "security": [ { "oauth2": ["pullrequest"] @@ -11725,8 +11922,7 @@ { "api_key": [] } - ], - "description": "When merging a pull request takes too long, the client receives a\ntask ID along with a 202 status code. The task ID can be used in a call\nto this endpoint to check the status of a merge task.\n\n```\ncurl -X GET https://api.bitbucket.org/2.0/repositories/atlassian/bitbucket/pullrequests/2286/merge/task-status/\n```\n\nIf the merge task is not yet finished, a PENDING status will be returned.\n\n```\nHTTP/2 200\n{\n \"task_status\": \"PENDING\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bitbucket/pullrequests/2286/merge/task-status/\"\n }\n }\n}\n```\n\nIf the merge was successful, a SUCCESS status will be returned.\n\n```\nHTTP/2 200\n{\n \"task_status\": \"SUCCESS\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bitbucket/pullrequests/2286/merge/task-status/\"\n }\n },\n \"merge_result\": \n}\n```\n\nIf the merge task failed, an error will be returned.\n\n```\n{\n \"type\": \"error\",\n \"error\": {\n \"message\": \"\"\n }\n}\n```" + ] }, "parameters": [ { @@ -11769,13 +11965,14 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/patch": { "get": { + "tags": ["Pullrequests"], + "description": "Redirects to the [repository patch](/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-patch-spec-get)\nwith the revspec that corresponds to pull request.", + "summary": "Get the patch for a pull request", "responses": { "302": { "description": "Redirects to the [repository patch](/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-patch-spec-get) with\nthe revspec that corresponds to pull request.\n" } }, - "tags": ["Pullrequests"], - "summary": "Get the patch for a pull request", "security": [ { "oauth2": ["pullrequest"] @@ -11786,8 +11983,7 @@ { "api_key": [] } - ], - "description": "Redirects to the [repository patch](/cloud/bitbucket/rest/api-group-commits/#api-repositories-workspace-repo-slug-patch-spec-get)\nwith the revspec that corresponds to pull request." + ] }, "parameters": [ { @@ -11821,6 +12017,9 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/request-changes": { "delete": { + "tags": ["Pullrequests"], + "description": "", + "summary": "Remove change request for a pull request", "responses": { "204": { "description": "An empty response indicating the authenticated user's request for change has been withdrawn." @@ -11846,8 +12045,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "Remove change request for a pull request", "security": [ { "oauth2": ["pullrequest:write"] @@ -11858,10 +12055,12 @@ { "api_key": [] } - ], - "description": "" + ] }, "post": { + "tags": ["Pullrequests"], + "description": "", + "summary": "Request changes for a pull request", "responses": { "200": { "description": "The `participant` object recording that the authenticated user requested changes on the pull request.", @@ -11894,8 +12093,6 @@ } } }, - "tags": ["Pullrequests"], - "summary": "Request changes for a pull request", "security": [ { "oauth2": ["pullrequest:write"] @@ -11906,8 +12103,7 @@ { "api_key": [] } - ], - "description": "" + ] }, "parameters": [ { @@ -11941,6 +12137,9 @@ }, "/repositories/{workspace}/{repo_slug}/pullrequests/{pull_request_id}/statuses": { "get": { + "tags": ["Pullrequests", "Commit statuses"], + "description": "Returns all statuses (e.g. build results) for the given pull\nrequest.", + "summary": "List commit statuses for a pull request", "responses": { "200": { "description": "A paginated list of all commit statuses for this pull request.", @@ -11986,8 +12185,6 @@ } } ], - "tags": ["Pullrequests", "Commit statuses"], - "summary": "List commit statuses for a pull request", "security": [ { "oauth2": ["pullrequest"] @@ -11998,8 +12195,7 @@ { "api_key": [] } - ], - "description": "Returns all statuses (e.g. build results) for the given pull\nrequest." + ] }, "parameters": [ { @@ -12038,48 +12234,51 @@ "description": "An empty response." } }, + "operationId": "updatePullRequestHostedPropertyValue", + "summary": "Update a pull request application property", + "description": "Update an [application property](/cloud/bitbucket/application-properties/) value stored against a pull request.", "parameters": [ { "required": true, - "description": "The repository container; either the workspace slug or the UUID in curly braces.", "in": "path", "name": "workspace", + "description": "The repository container; either the workspace slug or the UUID in curly braces.", "schema": { "type": "string" } }, { "required": true, - "description": "The repository.", "in": "path", "name": "repo_slug", + "description": "The repository.", "schema": { "type": "string" } }, { "required": true, - "description": "The pull request ID.", "in": "path", "name": "pullrequest_id", + "description": "The pull request ID.", "schema": { "type": "string" } }, { "required": true, - "description": "The key of the Connect app.", "in": "path", "name": "app_key", + "description": "The key of the Connect app.", "schema": { "type": "string" } }, { "required": true, - "description": "The name of the property.", "in": "path", "name": "property_name", + "description": "The name of the property.", "schema": { "type": "string" } @@ -12088,10 +12287,7 @@ "requestBody": { "$ref": "#/components/requestBodies/application_property" }, - "tags": ["properties"], - "description": "Update an [application property](/cloud/bitbucket/application-properties/) value stored against a pull request.", - "summary": "Update a pull request application property", - "operationId": "updatePullRequestHostedPropertyValue" + "tags": ["properties"] }, "delete": { "responses": { @@ -12099,57 +12295,57 @@ "description": "An empty response." } }, + "operationId": "deletePullRequestHostedPropertyValue", + "summary": "Delete a pull request application property", + "description": "Delete an [application property](/cloud/bitbucket/application-properties/) value stored against a pull request.", "parameters": [ { "required": true, - "description": "The repository container; either the workspace slug or the UUID in curly braces.", "in": "path", "name": "workspace", + "description": "The repository container; either the workspace slug or the UUID in curly braces.", "schema": { "type": "string" } }, { "required": true, - "description": "The repository.", "in": "path", "name": "repo_slug", + "description": "The repository.", "schema": { "type": "string" } }, { "required": true, - "description": "The pull request ID.", "in": "path", "name": "pullrequest_id", + "description": "The pull request ID.", "schema": { "type": "string" } }, { "required": true, - "description": "The key of the Connect app.", "in": "path", "name": "app_key", + "description": "The key of the Connect app.", "schema": { "type": "string" } }, { "required": true, - "description": "The name of the property.", "in": "path", "name": "property_name", + "description": "The name of the property.", "schema": { "type": "string" } } ], - "tags": ["properties"], - "description": "Delete an [application property](/cloud/bitbucket/application-properties/) value stored against a pull request.", - "summary": "Delete a pull request application property", - "operationId": "deletePullRequestHostedPropertyValue" + "tags": ["properties"] }, "get": { "responses": { @@ -12164,61 +12360,64 @@ } } }, + "operationId": "getPullRequestHostedPropertyValue", + "summary": "Get a pull request application property", + "description": "Retrieve an [application property](/cloud/bitbucket/application-properties/) value stored against a pull request.", "parameters": [ { "required": true, - "description": "The repository container; either the workspace slug or the UUID in curly braces.", "in": "path", "name": "workspace", + "description": "The repository container; either the workspace slug or the UUID in curly braces.", "schema": { "type": "string" } }, { "required": true, - "description": "The repository.", "in": "path", "name": "repo_slug", + "description": "The repository.", "schema": { "type": "string" } }, { "required": true, - "description": "The pull request ID.", "in": "path", "name": "pullrequest_id", + "description": "The pull request ID.", "schema": { "type": "string" } }, { "required": true, - "description": "The key of the Connect app.", "in": "path", "name": "app_key", + "description": "The key of the Connect app.", "schema": { "type": "string" } }, { "required": true, - "description": "The name of the property.", "in": "path", "name": "property_name", + "description": "The name of the property.", "schema": { "type": "string" } } ], - "tags": ["properties"], - "description": "Retrieve an [application property](/cloud/bitbucket/application-properties/) value stored against a pull request.", - "summary": "Get a pull request application property", - "operationId": "getPullRequestHostedPropertyValue" + "tags": ["properties"] } }, "/repositories/{workspace}/{repo_slug}/refs": { "get": { + "tags": ["Refs"], + "description": "Returns the branches and tags in the repository.\n\nBy default, results will be in the order the underlying source control system returns them and identical to\nthe ordering one sees when running \"$ git show-ref\". Note that this follows simple\nlexical ordering of the ref names.\n\nThis can be undesirable as it does apply any natural sorting semantics, meaning for instance that refs are\nsorted [\"branch1\", \"branch10\", \"branch2\", \"v10\", \"v11\", \"v9\"] instead of [\"branch1\", \"branch2\",\n\"branch10\", \"v9\", \"v10\", \"v11\"].\n\nSorting can be changed using the ?sort= query parameter. When using ?sort=name to explicitly sort on ref name,\nBitbucket will apply natural sorting and interpret numerical values as numbers instead of strings.", + "summary": "List branches and tags", "responses": { "200": { "description": "A paginated list of refs matching any filter criteria that were provided.", @@ -12269,8 +12468,6 @@ } } ], - "tags": ["Refs"], - "summary": "List branches and tags", "security": [ { "oauth2": ["repository"] @@ -12281,8 +12478,7 @@ { "api_key": [] } - ], - "description": "Returns the branches and tags in the repository.\n\nBy default, results will be in the order the underlying source control system returns them and identical to\nthe ordering one sees when running \"$ git show-ref\". Note that this follows simple\nlexical ordering of the ref names.\n\nThis can be undesirable as it does apply any natural sorting semantics, meaning for instance that refs are\nsorted [\"branch1\", \"branch10\", \"branch2\", \"v10\", \"v11\", \"v9\"] instead of [\"branch1\", \"branch2\",\n\"branch10\", \"v9\", \"v10\", \"v11\"].\n\nSorting can be changed using the ?sort= query parameter. When using ?sort=name to explicitly sort on ref name,\nBitbucket will apply natural sorting and interpret numerical values as numbers instead of strings." + ] }, "parameters": [ { @@ -12307,6 +12503,9 @@ }, "/repositories/{workspace}/{repo_slug}/refs/branches": { "get": { + "tags": ["Refs"], + "description": "Returns a list of all open branches within the specified repository.\n Results will be in the order the source control manager returns them.\n\n ```\n $ curl -s https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches?pagelen=1 | jq .\n {\n \"pagelen\": 1,\n \"size\": 187,\n \"values\": [\n {\n \"name\": \"issue-9.3/AUI-5343-assistive-class\",\n \"links\": {\n \"commits\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commits/issue-9.3/AUI-5343-assistive-class\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches/issue-9.3/AUI-5343-assistive-class\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/branch/issue-9.3/AUI-5343-assistive-class\"\n }\n },\n \"default_merge_strategy\": \"squash\",\n \"merge_strategies\": [\n \"merge_commit\",\n \"squash\",\n \"fast_forward\"\n ],\n \"type\": \"branch\",\n \"target\": {\n \"hash\": \"e5d1cde9069fcb9f0af90403a4de2150c125a148\",\n \"repository\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B585074de-7b60-4fd1-81ed-e0bc7fafbda5%7D?ts=86317\"\n }\n },\n \"type\": \"repository\",\n \"name\": \"aui\",\n \"full_name\": \"atlassian/aui\",\n \"uuid\": \"{585074de-7b60-4fd1-81ed-e0bc7fafbda5}\"\n },\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148\"\n },\n \"comments\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148/comments\"\n },\n \"patch\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/patch/e5d1cde9069fcb9f0af90403a4de2150c125a148\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/commits/e5d1cde9069fcb9f0af90403a4de2150c125a148\"\n },\n \"diff\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/diff/e5d1cde9069fcb9f0af90403a4de2150c125a148\"\n },\n \"approve\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148/approve\"\n },\n \"statuses\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148/statuses\"\n }\n },\n \"author\": {\n \"raw\": \"Marcin Konopka \",\n \"type\": \"author\",\n \"user\": {\n \"display_name\": \"Marcin Konopka\",\n \"uuid\": \"{47cc24f4-2a05-4420-88fe-0417535a110a}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/%7B47cc24f4-2a05-4420-88fe-0417535a110a%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B47cc24f4-2a05-4420-88fe-0417535a110a%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/initials/MK-1.png\"\n }\n },\n \"nickname\": \"Marcin Konopka\",\n \"type\": \"user\",\n \"account_id\": \"60113d2b47a9540069f4de03\"\n }\n },\n \"parents\": [\n {\n \"hash\": \"87f7fc92b00464ae47b13ef65c91884e4ac9be51\",\n \"type\": \"commit\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/87f7fc92b00464ae47b13ef65c91884e4ac9be51\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/commits/87f7fc92b00464ae47b13ef65c91884e4ac9be51\"\n }\n }\n }\n ],\n \"date\": \"2021-04-13T13:44:49+00:00\",\n \"message\": \"wip\n\",\n \"type\": \"commit\"\n }\n }\n ],\n \"page\": 1,\n \"next\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches?pagelen=1&page=2\"\n }\n ```\n\n Branches support [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering)\n that can be used to search for specific branches. For instance, to find\n all branches that have \"stab\" in their name:\n\n ```\n curl -s https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches -G --data-urlencode 'q=name ~ \"stab\"'\n ```\n\n By default, results will be in the order the underlying source control system returns them and identical to\n the ordering one sees when running \"$ git branch --list\". Note that this follows simple\n lexical ordering of the ref names.\n\n This can be undesirable as it does apply any natural sorting semantics, meaning for instance that tags are\n sorted [\"v10\", \"v11\", \"v9\"] instead of [\"v9\", \"v10\", \"v11\"].\n\n Sorting can be changed using the ?q= query parameter. When using ?q=name to explicitly sort on ref name,\n Bitbucket will apply natural sorting and interpret numerical values as numbers instead of strings.", + "summary": "List open branches", "responses": { "200": { "description": "A paginated list of branches matching any filter criteria that were provided.", @@ -12357,8 +12556,6 @@ } } ], - "tags": ["Refs"], - "summary": "List open branches", "security": [ { "oauth2": ["repository"] @@ -12369,10 +12566,12 @@ { "api_key": [] } - ], - "description": "Returns a list of all open branches within the specified repository.\n Results will be in the order the source control manager returns them.\n\n ```\n $ curl -s https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches?pagelen=1 | jq .\n {\n \"pagelen\": 1,\n \"size\": 187,\n \"values\": [\n {\n \"name\": \"issue-9.3/AUI-5343-assistive-class\",\n \"links\": {\n \"commits\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commits/issue-9.3/AUI-5343-assistive-class\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches/issue-9.3/AUI-5343-assistive-class\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/branch/issue-9.3/AUI-5343-assistive-class\"\n }\n },\n \"default_merge_strategy\": \"squash\",\n \"merge_strategies\": [\n \"merge_commit\",\n \"squash\",\n \"fast_forward\"\n ],\n \"type\": \"branch\",\n \"target\": {\n \"hash\": \"e5d1cde9069fcb9f0af90403a4de2150c125a148\",\n \"repository\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B585074de-7b60-4fd1-81ed-e0bc7fafbda5%7D?ts=86317\"\n }\n },\n \"type\": \"repository\",\n \"name\": \"aui\",\n \"full_name\": \"atlassian/aui\",\n \"uuid\": \"{585074de-7b60-4fd1-81ed-e0bc7fafbda5}\"\n },\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148\"\n },\n \"comments\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148/comments\"\n },\n \"patch\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/patch/e5d1cde9069fcb9f0af90403a4de2150c125a148\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/commits/e5d1cde9069fcb9f0af90403a4de2150c125a148\"\n },\n \"diff\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/diff/e5d1cde9069fcb9f0af90403a4de2150c125a148\"\n },\n \"approve\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148/approve\"\n },\n \"statuses\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e5d1cde9069fcb9f0af90403a4de2150c125a148/statuses\"\n }\n },\n \"author\": {\n \"raw\": \"Marcin Konopka \",\n \"type\": \"author\",\n \"user\": {\n \"display_name\": \"Marcin Konopka\",\n \"uuid\": \"{47cc24f4-2a05-4420-88fe-0417535a110a}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/%7B47cc24f4-2a05-4420-88fe-0417535a110a%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B47cc24f4-2a05-4420-88fe-0417535a110a%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://avatar-management--avatars.us-west-2.prod.public.atl-paas.net/initials/MK-1.png\"\n }\n },\n \"nickname\": \"Marcin Konopka\",\n \"type\": \"user\",\n \"account_id\": \"60113d2b47a9540069f4de03\"\n }\n },\n \"parents\": [\n {\n \"hash\": \"87f7fc92b00464ae47b13ef65c91884e4ac9be51\",\n \"type\": \"commit\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/87f7fc92b00464ae47b13ef65c91884e4ac9be51\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/commits/87f7fc92b00464ae47b13ef65c91884e4ac9be51\"\n }\n }\n }\n ],\n \"date\": \"2021-04-13T13:44:49+00:00\",\n \"message\": \"wip\n\",\n \"type\": \"commit\"\n }\n }\n ],\n \"page\": 1,\n \"next\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches?pagelen=1&page=2\"\n }\n ```\n\n Branches support [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering)\n that can be used to search for specific branches. For instance, to find\n all branches that have \"stab\" in their name:\n\n ```\n curl -s https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches -G --data-urlencode 'q=name ~ \"stab\"'\n ```\n\n By default, results will be in the order the underlying source control system returns them and identical to\n the ordering one sees when running \"$ git branch --list\". Note that this follows simple\n lexical ordering of the ref names.\n\n This can be undesirable as it does apply any natural sorting semantics, meaning for instance that tags are\n sorted [\"v10\", \"v11\", \"v9\"] instead of [\"v9\", \"v10\", \"v11\"].\n\n Sorting can be changed using the ?q= query parameter. When using ?q=name to explicitly sort on ref name,\n Bitbucket will apply natural sorting and interpret numerical values as numbers instead of strings." + ] }, "post": { + "tags": ["Refs"], + "description": "Creates a new branch in the specified repository.\n\nThe payload of the POST should consist of a JSON document that\ncontains the name of the tag and the target hash.\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/seanfarley/hg/refs/branches \\\n-s -u seanfarley -X POST -H \"Content-Type: application/json\" \\\n-d '{\n \"name\" : \"smf/create-feature\",\n \"target\" : {\n \"hash\" : \"default\",\n }\n}'\n```\n\nThis call requires authentication. Private repositories require the\ncaller to authenticate with an account that has appropriate\nauthorization.\n\nThe branch name should not include any prefixes (e.g.\nrefs/heads). This endpoint does support using short hash prefixes for\nthe commit hash, but it may return a 400 response if the provided\nprefix is ambiguous. Using a full commit hash is the preferred\napproach.", + "summary": "Create a branch", "responses": { "201": { "description": "The newly created branch object.", @@ -12405,8 +12604,6 @@ } } }, - "tags": ["Refs"], - "summary": "Create a branch", "security": [ { "oauth2": ["repository:write"] @@ -12417,8 +12614,7 @@ { "api_key": [] } - ], - "description": "Creates a new branch in the specified repository.\n\nThe payload of the POST should consist of a JSON document that\ncontains the name of the tag and the target hash.\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/seanfarley/hg/refs/branches \\\n-s -u seanfarley -X POST -H \"Content-Type: application/json\" \\\n-d '{\n \"name\" : \"smf/create-feature\",\n \"target\" : {\n \"hash\" : \"default\",\n }\n}'\n```\n\nThis call requires authentication. Private repositories require the\ncaller to authenticate with an account that has appropriate\nauthorization.\n\nThe branch name should not include any prefixes (e.g.\nrefs/heads). This endpoint does support using short hash prefixes for\nthe commit hash, but it may return a 400 response if the provided\nprefix is ambiguous. Using a full commit hash is the preferred\napproach." + ] }, "parameters": [ { @@ -12443,6 +12639,9 @@ }, "/repositories/{workspace}/{repo_slug}/refs/branches/{name}": { "delete": { + "tags": ["Refs"], + "description": "Delete a branch in the specified repository.\n\nThe main branch is not allowed to be deleted and will return a 400\nresponse.\n\nThe branch name should not include any prefixes (e.g.\nrefs/heads).", + "summary": "Delete a branch", "responses": { "204": { "description": "Indicates that the specified branch was successfully deleted." @@ -12468,8 +12667,6 @@ } } }, - "tags": ["Refs"], - "summary": "Delete a branch", "security": [ { "oauth2": ["repository:write"] @@ -12480,10 +12677,12 @@ { "api_key": [] } - ], - "description": "Delete a branch in the specified repository.\n\nThe main branch is not allowed to be deleted and will return a 400\nresponse.\n\nThe branch name should not include any prefixes (e.g.\nrefs/heads)." + ] }, "get": { + "tags": ["Refs"], + "description": "Returns a branch object within the specified repository.\n\n ```\n $ curl -s https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches/master | jq .\n {\n \"name\": \"master\",\n \"links\": {\n \"commits\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commits/master\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches/master\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/branch/master\"\n }\n },\n \"default_merge_strategy\": \"squash\",\n \"merge_strategies\": [\n \"merge_commit\",\n \"squash\",\n \"fast_forward\"\n ],\n \"type\": \"branch\",\n \"target\": {\n \"hash\": \"e7d158ff7ed5538c28f94cd97a9ad569680fc94e\",\n \"repository\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B585074de-7b60-4fd1-81ed-e0bc7fafbda5%7D?ts=86317\"\n }\n },\n \"type\": \"repository\",\n \"name\": \"aui\",\n \"full_name\": \"atlassian/aui\",\n \"uuid\": \"{585074de-7b60-4fd1-81ed-e0bc7fafbda5}\"\n },\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e\"\n },\n \"comments\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e/comments\"\n },\n \"patch\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/patch/e7d158ff7ed5538c28f94cd97a9ad569680fc94e\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/commits/e7d158ff7ed5538c28f94cd97a9ad569680fc94e\"\n },\n \"diff\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/diff/e7d158ff7ed5538c28f94cd97a9ad569680fc94e\"\n },\n \"approve\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e/approve\"\n },\n \"statuses\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e/statuses\"\n }\n },\n \"author\": {\n \"raw\": \"psre-renovate-bot \",\n \"type\": \"author\",\n \"user\": {\n \"display_name\": \"psre-renovate-bot\",\n \"uuid\": \"{250a442a-3ab3-4fcb-87c3-3c8f3df65ec7}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/%7B250a442a-3ab3-4fcb-87c3-3c8f3df65ec7%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B250a442a-3ab3-4fcb-87c3-3c8f3df65ec7%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://secure.gravatar.com/avatar/6972ee037c9f36360170a86f544071a2?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FP-3.png\"\n }\n },\n \"nickname\": \"Renovate Bot\",\n \"type\": \"user\",\n \"account_id\": \"5d5355e8c6b9320d9ea5b28d\"\n }\n },\n \"parents\": [\n {\n \"hash\": \"eab868a309e75733de80969a7bed1ec6d4651e06\",\n \"type\": \"commit\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/eab868a309e75733de80969a7bed1ec6d4651e06\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/commits/eab868a309e75733de80969a7bed1ec6d4651e06\"\n }\n }\n }\n ],\n \"date\": \"2021-04-12T06:44:38+00:00\",\n \"message\": \"Merged in issue/NONE-renovate-master-babel-monorepo (pull request #2883)\n\nchore(deps): update babel monorepo to v7.13.15 (master)\n\nApproved-by: Chris \"Daz\" Darroch\n\",\n \"type\": \"commit\"\n }\n }\n ```\n\n This call requires authentication. Private repositories require the\n caller to authenticate with an account that has appropriate\n authorization.\n\n For Git, the branch name should not include any prefixes (e.g.\n refs/heads).", + "summary": "Get a branch", "responses": { "200": { "description": "The branch object.", @@ -12516,8 +12715,6 @@ } } }, - "tags": ["Refs"], - "summary": "Get a branch", "security": [ { "oauth2": ["repository"] @@ -12528,8 +12725,7 @@ { "api_key": [] } - ], - "description": "Returns a branch object within the specified repository.\n\n ```\n $ curl -s https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches/master | jq .\n {\n \"name\": \"master\",\n \"links\": {\n \"commits\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commits/master\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/refs/branches/master\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/branch/master\"\n }\n },\n \"default_merge_strategy\": \"squash\",\n \"merge_strategies\": [\n \"merge_commit\",\n \"squash\",\n \"fast_forward\"\n ],\n \"type\": \"branch\",\n \"target\": {\n \"hash\": \"e7d158ff7ed5538c28f94cd97a9ad569680fc94e\",\n \"repository\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B585074de-7b60-4fd1-81ed-e0bc7fafbda5%7D?ts=86317\"\n }\n },\n \"type\": \"repository\",\n \"name\": \"aui\",\n \"full_name\": \"atlassian/aui\",\n \"uuid\": \"{585074de-7b60-4fd1-81ed-e0bc7fafbda5}\"\n },\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e\"\n },\n \"comments\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e/comments\"\n },\n \"patch\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/patch/e7d158ff7ed5538c28f94cd97a9ad569680fc94e\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/commits/e7d158ff7ed5538c28f94cd97a9ad569680fc94e\"\n },\n \"diff\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/diff/e7d158ff7ed5538c28f94cd97a9ad569680fc94e\"\n },\n \"approve\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e/approve\"\n },\n \"statuses\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/e7d158ff7ed5538c28f94cd97a9ad569680fc94e/statuses\"\n }\n },\n \"author\": {\n \"raw\": \"psre-renovate-bot \",\n \"type\": \"author\",\n \"user\": {\n \"display_name\": \"psre-renovate-bot\",\n \"uuid\": \"{250a442a-3ab3-4fcb-87c3-3c8f3df65ec7}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/%7B250a442a-3ab3-4fcb-87c3-3c8f3df65ec7%7D\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/%7B250a442a-3ab3-4fcb-87c3-3c8f3df65ec7%7D/\"\n },\n \"avatar\": {\n \"href\": \"https://secure.gravatar.com/avatar/6972ee037c9f36360170a86f544071a2?d=https%3A%2F%2Favatar-management--avatars.us-west-2.prod.public.atl-paas.net%2Finitials%2FP-3.png\"\n }\n },\n \"nickname\": \"Renovate Bot\",\n \"type\": \"user\",\n \"account_id\": \"5d5355e8c6b9320d9ea5b28d\"\n }\n },\n \"parents\": [\n {\n \"hash\": \"eab868a309e75733de80969a7bed1ec6d4651e06\",\n \"type\": \"commit\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/aui/commit/eab868a309e75733de80969a7bed1ec6d4651e06\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/aui/commits/eab868a309e75733de80969a7bed1ec6d4651e06\"\n }\n }\n }\n ],\n \"date\": \"2021-04-12T06:44:38+00:00\",\n \"message\": \"Merged in issue/NONE-renovate-master-babel-monorepo (pull request #2883)\n\nchore(deps): update babel monorepo to v7.13.15 (master)\n\nApproved-by: Chris \"Daz\" Darroch\n\",\n \"type\": \"commit\"\n }\n }\n ```\n\n This call requires authentication. Private repositories require the\n caller to authenticate with an account that has appropriate\n authorization.\n\n For Git, the branch name should not include any prefixes (e.g.\n refs/heads)." + ] }, "parameters": [ { @@ -12563,6 +12759,9 @@ }, "/repositories/{workspace}/{repo_slug}/refs/tags": { "get": { + "tags": ["Refs"], + "description": "Returns the tags in the repository.\n\nBy default, results will be in the order the underlying source control system returns them and identical to\nthe ordering one sees when running \"$ git tag --list\". Note that this follows simple\nlexical ordering of the ref names.\n\nThis can be undesirable as it does apply any natural sorting semantics, meaning for instance that tags are\nsorted [\"v10\", \"v11\", \"v9\"] instead of [\"v9\", \"v10\", \"v11\"].\n\nSorting can be changed using the ?sort= query parameter. When using ?sort=name to explicitly sort on ref name,\nBitbucket will apply natural sorting and interpret numerical values as numbers instead of strings.", + "summary": "List tags", "responses": { "200": { "description": "A paginated list of tags matching any filter criteria that were provided.", @@ -12613,8 +12812,6 @@ } } ], - "tags": ["Refs"], - "summary": "List tags", "security": [ { "oauth2": ["repository"] @@ -12625,10 +12822,12 @@ { "api_key": [] } - ], - "description": "Returns the tags in the repository.\n\nBy default, results will be in the order the underlying source control system returns them and identical to\nthe ordering one sees when running \"$ git tag --list\". Note that this follows simple\nlexical ordering of the ref names.\n\nThis can be undesirable as it does apply any natural sorting semantics, meaning for instance that tags are\nsorted [\"v10\", \"v11\", \"v9\"] instead of [\"v9\", \"v10\", \"v11\"].\n\nSorting can be changed using the ?sort= query parameter. When using ?sort=name to explicitly sort on ref name,\nBitbucket will apply natural sorting and interpret numerical values as numbers instead of strings." + ] }, "post": { + "tags": ["Refs"], + "description": "Creates a new tag in the specified repository.\n\nThe payload of the POST should consist of a JSON document that\ncontains the name of the tag and the target hash.\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/jdoe/myrepo/refs/tags \\\n-s -u jdoe -X POST -H \"Content-Type: application/json\" \\\n-d '{\n \"name\" : \"new-tag-name\",\n \"target\" : {\n \"hash\" : \"a1b2c3d4e5f6\",\n }\n}'\n```\n\nThis endpoint does support using short hash prefixes for the commit\nhash, but it may return a 400 response if the provided prefix is\nambiguous. Using a full commit hash is the preferred approach.", + "summary": "Create a tag", "responses": { "201": { "description": "The newly created tag.", @@ -12661,8 +12860,6 @@ }, "required": true }, - "tags": ["Refs"], - "summary": "Create a tag", "security": [ { "oauth2": ["repository:write"] @@ -12673,8 +12870,7 @@ { "api_key": [] } - ], - "description": "Creates a new tag in the specified repository.\n\nThe payload of the POST should consist of a JSON document that\ncontains the name of the tag and the target hash.\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/jdoe/myrepo/refs/tags \\\n-s -u jdoe -X POST -H \"Content-Type: application/json\" \\\n-d '{\n \"name\" : \"new-tag-name\",\n \"target\" : {\n \"hash\" : \"a1b2c3d4e5f6\",\n }\n}'\n```\n\nThis endpoint does support using short hash prefixes for the commit\nhash, but it may return a 400 response if the provided prefix is\nambiguous. Using a full commit hash is the preferred approach." + ] }, "parameters": [ { @@ -12699,6 +12895,9 @@ }, "/repositories/{workspace}/{repo_slug}/refs/tags/{name}": { "delete": { + "tags": ["Refs"], + "description": "Delete a tag in the specified repository.\n\nThe tag name should not include any prefixes (e.g. refs/tags).", + "summary": "Delete a tag", "responses": { "204": { "description": "Indicates the specified tag was successfully deleted." @@ -12724,8 +12923,6 @@ } } }, - "tags": ["Refs"], - "summary": "Delete a tag", "security": [ { "oauth2": ["repository:write"] @@ -12736,10 +12933,12 @@ { "api_key": [] } - ], - "description": "Delete a tag in the specified repository.\n\nThe tag name should not include any prefixes (e.g. refs/tags)." + ] }, "get": { + "tags": ["Refs"], + "description": "Returns the specified tag.\n\n```\n$ curl -s https://api.bitbucket.org/2.0/repositories/seanfarley/hg/refs/tags/3.8 -G | jq .\n{\n \"name\": \"3.8\",\n \"links\": {\n \"commits\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/commits/3.8\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/refs/tags/3.8\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/seanfarley/hg/commits/tag/3.8\"\n }\n },\n \"tagger\": {\n \"raw\": \"Matt Mackall \",\n \"type\": \"author\",\n \"user\": {\n \"username\": \"mpmselenic\",\n \"nickname\": \"mpmselenic\",\n \"display_name\": \"Matt Mackall\",\n \"type\": \"user\",\n \"uuid\": \"{a4934530-db4c-419c-a478-9ab4964c2ee7}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/mpmselenic\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/mpmselenic/\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/mpmselenic/avatar/32/\"\n }\n }\n }\n },\n \"date\": \"2016-05-01T18:52:25+00:00\",\n \"message\": \"Added tag 3.8 for changeset f85de28eae32\",\n \"type\": \"tag\",\n \"target\": {\n \"hash\": \"f85de28eae32e7d3064b1a1321309071bbaaa069\",\n \"repository\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/seanfarley/hg\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/seanfarley/hg/avatar/32/\"\n }\n },\n \"type\": \"repository\",\n \"name\": \"hg\",\n \"full_name\": \"seanfarley/hg\",\n \"uuid\": \"{c75687fb-e99d-4579-9087-190dbd406d30}\"\n },\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/commit/f85de28eae32e7d3064b1a1321309071bbaaa069\"\n },\n \"comments\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/commit/f85de28eae32e7d3064b1a1321309071bbaaa069/comments\"\n },\n \"patch\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/patch/f85de28eae32e7d3064b1a1321309071bbaaa069\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/seanfarley/hg/commits/f85de28eae32e7d3064b1a1321309071bbaaa069\"\n },\n \"diff\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/diff/f85de28eae32e7d3064b1a1321309071bbaaa069\"\n },\n \"approve\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/commit/f85de28eae32e7d3064b1a1321309071bbaaa069/approve\"\n },\n \"statuses\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/commit/f85de28eae32e7d3064b1a1321309071bbaaa069/statuses\"\n }\n },\n \"author\": {\n \"raw\": \"Sean Farley \",\n \"type\": \"author\",\n \"user\": {\n \"username\": \"seanfarley\",\n \"nickname\": \"seanfarley\",\n \"display_name\": \"Sean Farley\",\n \"type\": \"user\",\n \"uuid\": \"{a295f8a8-5876-4d43-89b5-3ad8c6c3c51d}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/seanfarley\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/seanfarley/\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/seanfarley/avatar/32/\"\n }\n }\n }\n },\n \"parents\": [\n {\n \"hash\": \"9a98d0e5b07fc60887f9d3d34d9ac7d536f470d2\",\n \"type\": \"commit\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/commit/9a98d0e5b07fc60887f9d3d34d9ac7d536f470d2\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/seanfarley/hg/commits/9a98d0e5b07fc60887f9d3d34d9ac7d536f470d2\"\n }\n }\n }\n ],\n \"date\": \"2016-05-01T04:21:17+00:00\",\n \"message\": \"debian: alphabetize build deps\",\n \"type\": \"commit\"\n }\n}\n```", + "summary": "Get a tag", "responses": { "200": { "description": "The tag object.", @@ -12772,8 +12971,6 @@ } } }, - "tags": ["Refs"], - "summary": "Get a tag", "security": [ { "oauth2": ["repository"] @@ -12784,8 +12981,7 @@ { "api_key": [] } - ], - "description": "Returns the specified tag.\n\n```\n$ curl -s https://api.bitbucket.org/2.0/repositories/seanfarley/hg/refs/tags/3.8 -G | jq .\n{\n \"name\": \"3.8\",\n \"links\": {\n \"commits\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/commits/3.8\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/refs/tags/3.8\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/seanfarley/hg/commits/tag/3.8\"\n }\n },\n \"tagger\": {\n \"raw\": \"Matt Mackall \",\n \"type\": \"author\",\n \"user\": {\n \"username\": \"mpmselenic\",\n \"nickname\": \"mpmselenic\",\n \"display_name\": \"Matt Mackall\",\n \"type\": \"user\",\n \"uuid\": \"{a4934530-db4c-419c-a478-9ab4964c2ee7}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/mpmselenic\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/mpmselenic/\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/mpmselenic/avatar/32/\"\n }\n }\n }\n },\n \"date\": \"2016-05-01T18:52:25+00:00\",\n \"message\": \"Added tag 3.8 for changeset f85de28eae32\",\n \"type\": \"tag\",\n \"target\": {\n \"hash\": \"f85de28eae32e7d3064b1a1321309071bbaaa069\",\n \"repository\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/seanfarley/hg\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/seanfarley/hg/avatar/32/\"\n }\n },\n \"type\": \"repository\",\n \"name\": \"hg\",\n \"full_name\": \"seanfarley/hg\",\n \"uuid\": \"{c75687fb-e99d-4579-9087-190dbd406d30}\"\n },\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/commit/f85de28eae32e7d3064b1a1321309071bbaaa069\"\n },\n \"comments\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/commit/f85de28eae32e7d3064b1a1321309071bbaaa069/comments\"\n },\n \"patch\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/patch/f85de28eae32e7d3064b1a1321309071bbaaa069\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/seanfarley/hg/commits/f85de28eae32e7d3064b1a1321309071bbaaa069\"\n },\n \"diff\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/diff/f85de28eae32e7d3064b1a1321309071bbaaa069\"\n },\n \"approve\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/commit/f85de28eae32e7d3064b1a1321309071bbaaa069/approve\"\n },\n \"statuses\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/commit/f85de28eae32e7d3064b1a1321309071bbaaa069/statuses\"\n }\n },\n \"author\": {\n \"raw\": \"Sean Farley \",\n \"type\": \"author\",\n \"user\": {\n \"username\": \"seanfarley\",\n \"nickname\": \"seanfarley\",\n \"display_name\": \"Sean Farley\",\n \"type\": \"user\",\n \"uuid\": \"{a295f8a8-5876-4d43-89b5-3ad8c6c3c51d}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/seanfarley\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/seanfarley/\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/seanfarley/avatar/32/\"\n }\n }\n }\n },\n \"parents\": [\n {\n \"hash\": \"9a98d0e5b07fc60887f9d3d34d9ac7d536f470d2\",\n \"type\": \"commit\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/seanfarley/hg/commit/9a98d0e5b07fc60887f9d3d34d9ac7d536f470d2\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/seanfarley/hg/commits/9a98d0e5b07fc60887f9d3d34d9ac7d536f470d2\"\n }\n }\n }\n ],\n \"date\": \"2016-05-01T04:21:17+00:00\",\n \"message\": \"debian: alphabetize build deps\",\n \"type\": \"commit\"\n }\n}\n```" + ] }, "parameters": [ { @@ -12819,6 +13015,9 @@ }, "/repositories/{workspace}/{repo_slug}/src": { "get": { + "tags": ["Source", "Repositories"], + "description": "This endpoint redirects the client to the directory listing of the\nroot directory on the main branch.\n\nThis is equivalent to directly hitting\n[/2.0/repositories/{username}/{repo_slug}/src/{commit}/{path}](src/%7Bcommit%7D/%7Bpath%7D)\nwithout having to know the name or SHA1 of the repo's main branch.\n\nTo create new commits, [POST to this endpoint](#post)", + "summary": "Get the root directory of the main branch", "responses": { "200": { "description": "If the path matches a file, then the raw contents of the file are\nreturned (unless the `format=meta` query parameter was provided,\nin which case a json document containing the file's meta data is\nreturned). If the path matches a directory, then a paginated\nlist of file and directory entries is returned (if the\n`format=meta` query parameter was provided, then the json document\ncontaining the directory's meta data is returned).\n", @@ -12853,8 +13052,6 @@ } } ], - "tags": ["Source", "Repositories"], - "summary": "Get the root directory of the main branch", "security": [ { "oauth2": ["repository"] @@ -12865,10 +13062,12 @@ { "api_key": [] } - ], - "description": "This endpoint redirects the client to the directory listing of the\nroot directory on the main branch.\n\nThis is equivalent to directly hitting\n[/2.0/repositories/{username}/{repo_slug}/src/{commit}/{path}](src/%7Bcommit%7D/%7Bpath%7D)\nwithout having to know the name or SHA1 of the repo's main branch.\n\nTo create new commits, [POST to this endpoint](#post)" + ] }, "post": { + "tags": ["Source", "Repositories"], + "description": "This endpoint is used to create new commits in the repository by\nuploading files.\n\nTo add a new file to a repository:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/username/slug/src \\\n -F /repo/path/to/image.png=@image.png\n```\n\nThis will create a new commit on top of the main branch, inheriting the\ncontents of the main branch, but adding (or overwriting) the\n`image.png` file to the repository in the `/repo/path/to` directory.\n\nTo create a commit that deletes files, use the `files` parameter:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/username/slug/src \\\n -F files=/file/to/delete/1.txt \\\n -F files=/file/to/delete/2.txt\n```\n\nYou can add/modify/delete multiple files in a request. Rename/move a\nfile by deleting the old path and adding the content at the new path.\n\nThis endpoint accepts `multipart/form-data` (as in the examples above),\nas well as `application/x-www-form-urlencoded`.\n\n#### multipart/form-data\n\nA `multipart/form-data` post contains a series of \"form fields\" that\nidentify both the individual files that are being uploaded, as well as\nadditional, optional meta data.\n\nFiles are uploaded in file form fields (those that have a\n`Content-Disposition` parameter) whose field names point to the remote\npath in the repository where the file should be stored. Path field\nnames are always interpreted to be absolute from the root of the\nrepository, regardless whether the client uses a leading slash (as the\nabove `curl` example did).\n\nFile contents are treated as bytes and are not decoded as text.\n\nThe commit message, as well as other non-file meta data for the\nrequest, is sent along as normal form field elements. Meta data fields\nshare the same namespace as the file objects. For `multipart/form-data`\nbodies that should not lead to any ambiguity, as the\n`Content-Disposition` header will contain the `filename` parameter to\ndistinguish between a file named \"message\" and the commit message field.\n\n#### application/x-www-form-urlencoded\n\nIt is also possible to upload new files using a simple\n`application/x-www-form-urlencoded` POST. This can be convenient when\nuploading pure text files:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src \\\n --data-urlencode \"/path/to/me.txt=Lorem ipsum.\" \\\n --data-urlencode \"message=Initial commit\" \\\n --data-urlencode \"author=Erik van Zijst \"\n```\n\nThere could be a field name clash if a client were to upload a file\nnamed \"message\", as this filename clashes with the meta data property\nfor the commit message. To avoid this and to upload files whose names\nclash with the meta data properties, use a leading slash for the files,\ne.g. `curl --data-urlencode \"/message=file contents\"`.\n\nWhen an explicit slash is omitted for a file whose path matches that of\na meta data parameter, then it is interpreted as meta data, not as a\nfile.\n\n#### Executables and links\n\nWhile this API aims to facilitate the most common use cases, it is\npossible to perform some more advanced operations like creating a new\nsymlink in the repository, or creating an executable file.\n\nFiles can be supplied with a `x-attributes` value in the\n`Content-Disposition` header. For example, to upload an executable\nfile, as well as create a symlink from `README.txt` to `README`:\n\n```\n--===============1438169132528273974==\nContent-Type: text/plain; charset=\"us-ascii\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-ID: \"bin/shutdown.sh\"\nContent-Disposition: attachment; filename=\"shutdown.sh\"; x-attributes:\"executable\"\n\n#!/bin/sh\nhalt\n\n--===============1438169132528273974==\nContent-Type: text/plain; charset=\"us-ascii\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-ID: \"/README.txt\"\nContent-Disposition: attachment; filename=\"README.txt\"; x-attributes:\"link\"\n\nREADME\n--===============1438169132528273974==--\n```\n\nLinks are files that contain the target path and have\n`x-attributes:\"link\"` set.\n\nWhen overwriting links with files, or vice versa, the newly uploaded\nfile determines both the new contents, as well as the attributes. That\nmeans uploading a file without specifying `x-attributes=\"link\"` will\ncreate a regular file, even if the parent commit hosted a symlink at\nthe same path.\n\nThe same applies to executables. When modifying an existing executable\nfile, the form-data file element must include\n`x-attributes=\"executable\"` in order to preserve the executable status\nof the file.\n\nNote that this API does not support the creation or manipulation of\nsubrepos / submodules.", + "summary": "Create a commit by uploading a file", "responses": { "201": { "description": "\n" @@ -12941,8 +13140,6 @@ } } ], - "tags": ["Source", "Repositories"], - "summary": "Create a commit by uploading a file", "security": [ { "oauth2": ["repository:write"] @@ -12953,8 +13150,7 @@ { "api_key": [] } - ], - "description": "This endpoint is used to create new commits in the repository by\nuploading files.\n\nTo add a new file to a repository:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/username/slug/src \\\n -F /repo/path/to/image.png=@image.png\n```\n\nThis will create a new commit on top of the main branch, inheriting the\ncontents of the main branch, but adding (or overwriting) the\n`image.png` file to the repository in the `/repo/path/to` directory.\n\nTo create a commit that deletes files, use the `files` parameter:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/username/slug/src \\\n -F files=/file/to/delete/1.txt \\\n -F files=/file/to/delete/2.txt\n```\n\nYou can add/modify/delete multiple files in a request. Rename/move a\nfile by deleting the old path and adding the content at the new path.\n\nThis endpoint accepts `multipart/form-data` (as in the examples above),\nas well as `application/x-www-form-urlencoded`.\n\n#### multipart/form-data\n\nA `multipart/form-data` post contains a series of \"form fields\" that\nidentify both the individual files that are being uploaded, as well as\nadditional, optional meta data.\n\nFiles are uploaded in file form fields (those that have a\n`Content-Disposition` parameter) whose field names point to the remote\npath in the repository where the file should be stored. Path field\nnames are always interpreted to be absolute from the root of the\nrepository, regardless whether the client uses a leading slash (as the\nabove `curl` example did).\n\nFile contents are treated as bytes and are not decoded as text.\n\nThe commit message, as well as other non-file meta data for the\nrequest, is sent along as normal form field elements. Meta data fields\nshare the same namespace as the file objects. For `multipart/form-data`\nbodies that should not lead to any ambiguity, as the\n`Content-Disposition` header will contain the `filename` parameter to\ndistinguish between a file named \"message\" and the commit message field.\n\n#### application/x-www-form-urlencoded\n\nIt is also possible to upload new files using a simple\n`application/x-www-form-urlencoded` POST. This can be convenient when\nuploading pure text files:\n\n```\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src \\\n --data-urlencode \"/path/to/me.txt=Lorem ipsum.\" \\\n --data-urlencode \"message=Initial commit\" \\\n --data-urlencode \"author=Erik van Zijst \"\n```\n\nThere could be a field name clash if a client were to upload a file\nnamed \"message\", as this filename clashes with the meta data property\nfor the commit message. To avoid this and to upload files whose names\nclash with the meta data properties, use a leading slash for the files,\ne.g. `curl --data-urlencode \"/message=file contents\"`.\n\nWhen an explicit slash is omitted for a file whose path matches that of\na meta data parameter, then it is interpreted as meta data, not as a\nfile.\n\n#### Executables and links\n\nWhile this API aims to facilitate the most common use cases, it is\npossible to perform some more advanced operations like creating a new\nsymlink in the repository, or creating an executable file.\n\nFiles can be supplied with a `x-attributes` value in the\n`Content-Disposition` header. For example, to upload an executable\nfile, as well as create a symlink from `README.txt` to `README`:\n\n```\n--===============1438169132528273974==\nContent-Type: text/plain; charset=\"us-ascii\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-ID: \"bin/shutdown.sh\"\nContent-Disposition: attachment; filename=\"shutdown.sh\"; x-attributes:\"executable\"\n\n#!/bin/sh\nhalt\n\n--===============1438169132528273974==\nContent-Type: text/plain; charset=\"us-ascii\"\nMIME-Version: 1.0\nContent-Transfer-Encoding: 7bit\nContent-ID: \"/README.txt\"\nContent-Disposition: attachment; filename=\"README.txt\"; x-attributes:\"link\"\n\nREADME\n--===============1438169132528273974==--\n```\n\nLinks are files that contain the target path and have\n`x-attributes:\"link\"` set.\n\nWhen overwriting links with files, or vice versa, the newly uploaded\nfile determines both the new contents, as well as the attributes. That\nmeans uploading a file without specifying `x-attributes=\"link\"` will\ncreate a regular file, even if the parent commit hosted a symlink at\nthe same path.\n\nThe same applies to executables. When modifying an existing executable\nfile, the form-data file element must include\n`x-attributes=\"executable\"` in order to preserve the executable status\nof the file.\n\nNote that this API does not support the creation or manipulation of\nsubrepos / submodules." + ] }, "parameters": [ { @@ -12979,6 +13175,9 @@ }, "/repositories/{workspace}/{repo_slug}/src/{commit}/{path}": { "get": { + "tags": ["Source", "Repositories"], + "description": "This endpoints is used to retrieve the contents of a single file,\nor the contents of a directory at a specified revision.\n\n#### Raw file contents\n\nWhen `path` points to a file, this endpoint returns the raw contents.\nThe response's Content-Type is derived from the filename\nextension (not from the contents). The file contents are not processed\nand no character encoding/recoding is performed and as a result no\ncharacter encoding is included as part of the Content-Type.\n\nThe `Content-Disposition` header will be \"attachment\" to prevent\nbrowsers from running executable files.\n\nIf the file is managed by LFS, then a 301 redirect pointing to\nAtlassian's media services platform is returned.\n\nThe response includes an ETag that is based on the contents of the file\nand its attributes. This means that an empty `__init__.py` always\nreturns the same ETag, regardless on the directory it lives in, or the\ncommit it is on.\n\n#### File meta data\n\nWhen the request for a file path includes the query parameter\n`?format=meta`, instead of returning the file's raw contents, Bitbucket\ninstead returns the JSON object describing the file's properties:\n\n```javascript\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef/tests/__init__.py?format=meta\n{\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/__init__.py\"\n },\n \"meta\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/__init__.py?format=meta\"\n }\n },\n \"path\": \"tests/__init__.py\",\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"eefd5ef5d3df01aed629f650959d6706d54cd335\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/commit/eefd5ef5d3df01aed629f650959d6706d54cd335\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/bbql/commits/eefd5ef5d3df01aed629f650959d6706d54cd335\"\n }\n }\n },\n \"attributes\": [],\n \"type\": \"commit_file\",\n \"size\": 0\n}\n```\n\nFile objects contain an `attributes` element that contains a list of\npossible modifiers. Currently defined values are:\n\n* `link` -- indicates that the entry is a symbolic link. The contents\n of the file represent the path the link points to.\n* `executable` -- indicates that the file has the executable bit set.\n* `subrepository` -- indicates that the entry points to a submodule or\n subrepo. The contents of the file is the SHA1 of the repository\n pointed to.\n* `binary` -- indicates whether Bitbucket thinks the file is binary.\n\nThis endpoint can provide an alternative to how a HEAD request can be\nused to check for the existence of a file, or a file's size without\nincurring the overhead of receiving its full contents.\n\n\n#### Directory listings\n\nWhen `path` points to a directory instead of a file, the response is a\npaginated list of directory and file objects in the same order as the\nunderlying SCM system would return them.\n\nFor example:\n\n```javascript\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef/tests\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"path\": \"tests/test_project\",\n \"type\": \"commit_directory\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/test_project/\"\n },\n \"meta\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/test_project/?format=meta\"\n }\n },\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"eefd5ef5d3df01aed629f650959d6706d54cd335\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/commit/eefd5ef5d3df01aed629f650959d6706d54cd335\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/bbql/commits/eefd5ef5d3df01aed629f650959d6706d54cd335\"\n }\n }\n }\n },\n {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/__init__.py\"\n },\n \"meta\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/__init__.py?format=meta\"\n }\n },\n \"path\": \"tests/__init__.py\",\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"eefd5ef5d3df01aed629f650959d6706d54cd335\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/commit/eefd5ef5d3df01aed629f650959d6706d54cd335\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/bbql/commits/eefd5ef5d3df01aed629f650959d6706d54cd335\"\n }\n }\n },\n \"attributes\": [],\n \"type\": \"commit_file\",\n \"size\": 0\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```\n\nWhen listing the contents of the repo's root directory, the use of a\ntrailing slash at the end of the URL is required.\n\nThe response by default is not recursive, meaning that only the direct contents of\na path are returned. The response does not recurse down into\nsubdirectories. In order to \"walk\" the entire directory tree, the\nclient can either parse each response and follow the `self` links of each\n`commit_directory` object, or can specify a `max_depth` to recurse to.\n\nThe max_depth parameter will do a breadth-first search to return the contents of the subdirectories\nup to the depth specified. Breadth-first search was chosen as it leads to the least amount of\nfile system operations for git. If the `max_depth` parameter is specified to be too\nlarge, the call will time out and return a 555.\n\nEach returned object is either a `commit_file`, or a `commit_directory`,\nboth of which contain a `path` element. This path is the absolute path\nfrom the root of the repository. Each object also contains a `commit`\nobject which embeds the commit the file is on. Note that this is merely\nthe commit that was used in the URL. It is *not* the commit that last\nmodified the file.\n\nDirectory objects have 2 representations. Their `self` link returns the\npaginated contents of the directory. The `meta` link on the other hand\nreturns the actual `directory` object itself, e.g.:\n\n```javascript\n{\n \"path\": \"tests/test_project\",\n \"type\": \"commit_directory\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/test_project/\"\n },\n \"meta\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/test_project/?format=meta\"\n }\n },\n \"commit\": { ... }\n}\n```\n\n#### Querying, filtering and sorting\n\nLike most API endpoints, this API supports the Bitbucket\nquerying/filtering syntax and so you could filter a directory listing\nto only include entries that match certain criteria. For instance, to\nlist all binary files over 1kb use the expression:\n\n`size > 1024 and attributes = \"binary\"`\n\nwhich after urlencoding yields the query string:\n\n`?q=size%3E1024+and+attributes%3D%22binary%22`\n\nTo change the ordering of the response, use the `?sort` parameter:\n\n`.../src/eefd5ef/?sort=-size`\n\nSee [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more\ndetails.", + "summary": "Get file or directory contents", "responses": { "200": { "description": "If the path matches a file, then the raw contents of the file are\nreturned. If the `format=meta` query parameter is provided,\na json document containing the file's meta data is\nreturned. If the `format=rendered` query parameter is provided,\nthe contents of the file in HTML-formated rendered markup is returned.\nIf the path matches a directory, then a paginated\nlist of file and directory entries is returned (if the\n`format=meta` query parameter was provided, then the json document\ncontaining the directory's meta data is returned.)\n", @@ -13050,8 +13249,6 @@ } } ], - "tags": ["Source", "Repositories"], - "summary": "Get file or directory contents", "security": [ { "oauth2": ["repository"] @@ -13062,8 +13259,7 @@ { "api_key": [] } - ], - "description": "This endpoints is used to retrieve the contents of a single file,\nor the contents of a directory at a specified revision.\n\n#### Raw file contents\n\nWhen `path` points to a file, this endpoint returns the raw contents.\nThe response's Content-Type is derived from the filename\nextension (not from the contents). The file contents are not processed\nand no character encoding/recoding is performed and as a result no\ncharacter encoding is included as part of the Content-Type.\n\nThe `Content-Disposition` header will be \"attachment\" to prevent\nbrowsers from running executable files.\n\nIf the file is managed by LFS, then a 301 redirect pointing to\nAtlassian's media services platform is returned.\n\nThe response includes an ETag that is based on the contents of the file\nand its attributes. This means that an empty `__init__.py` always\nreturns the same ETag, regardless on the directory it lives in, or the\ncommit it is on.\n\n#### File meta data\n\nWhen the request for a file path includes the query parameter\n`?format=meta`, instead of returning the file's raw contents, Bitbucket\ninstead returns the JSON object describing the file's properties:\n\n```javascript\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef/tests/__init__.py?format=meta\n{\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/__init__.py\"\n },\n \"meta\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/__init__.py?format=meta\"\n }\n },\n \"path\": \"tests/__init__.py\",\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"eefd5ef5d3df01aed629f650959d6706d54cd335\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/commit/eefd5ef5d3df01aed629f650959d6706d54cd335\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/bbql/commits/eefd5ef5d3df01aed629f650959d6706d54cd335\"\n }\n }\n },\n \"attributes\": [],\n \"type\": \"commit_file\",\n \"size\": 0\n}\n```\n\nFile objects contain an `attributes` element that contains a list of\npossible modifiers. Currently defined values are:\n\n* `link` -- indicates that the entry is a symbolic link. The contents\n of the file represent the path the link points to.\n* `executable` -- indicates that the file has the executable bit set.\n* `subrepository` -- indicates that the entry points to a submodule or\n subrepo. The contents of the file is the SHA1 of the repository\n pointed to.\n* `binary` -- indicates whether Bitbucket thinks the file is binary.\n\nThis endpoint can provide an alternative to how a HEAD request can be\nused to check for the existence of a file, or a file's size without\nincurring the overhead of receiving its full contents.\n\n\n#### Directory listings\n\nWhen `path` points to a directory instead of a file, the response is a\npaginated list of directory and file objects in the same order as the\nunderlying SCM system would return them.\n\nFor example:\n\n```javascript\n$ curl https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef/tests\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"path\": \"tests/test_project\",\n \"type\": \"commit_directory\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/test_project/\"\n },\n \"meta\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/test_project/?format=meta\"\n }\n },\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"eefd5ef5d3df01aed629f650959d6706d54cd335\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/commit/eefd5ef5d3df01aed629f650959d6706d54cd335\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/bbql/commits/eefd5ef5d3df01aed629f650959d6706d54cd335\"\n }\n }\n }\n },\n {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/__init__.py\"\n },\n \"meta\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/__init__.py?format=meta\"\n }\n },\n \"path\": \"tests/__init__.py\",\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"eefd5ef5d3df01aed629f650959d6706d54cd335\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/commit/eefd5ef5d3df01aed629f650959d6706d54cd335\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/atlassian/bbql/commits/eefd5ef5d3df01aed629f650959d6706d54cd335\"\n }\n }\n },\n \"attributes\": [],\n \"type\": \"commit_file\",\n \"size\": 0\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```\n\nWhen listing the contents of the repo's root directory, the use of a\ntrailing slash at the end of the URL is required.\n\nThe response by default is not recursive, meaning that only the direct contents of\na path are returned. The response does not recurse down into\nsubdirectories. In order to \"walk\" the entire directory tree, the\nclient can either parse each response and follow the `self` links of each\n`commit_directory` object, or can specify a `max_depth` to recurse to.\n\nThe max_depth parameter will do a breadth-first search to return the contents of the subdirectories\nup to the depth specified. Breadth-first search was chosen as it leads to the least amount of\nfile system operations for git. If the `max_depth` parameter is specified to be too\nlarge, the call will time out and return a 555.\n\nEach returned object is either a `commit_file`, or a `commit_directory`,\nboth of which contain a `path` element. This path is the absolute path\nfrom the root of the repository. Each object also contains a `commit`\nobject which embeds the commit the file is on. Note that this is merely\nthe commit that was used in the URL. It is *not* the commit that last\nmodified the file.\n\nDirectory objects have 2 representations. Their `self` link returns the\npaginated contents of the directory. The `meta` link on the other hand\nreturns the actual `directory` object itself, e.g.:\n\n```javascript\n{\n \"path\": \"tests/test_project\",\n \"type\": \"commit_directory\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/test_project/\"\n },\n \"meta\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/atlassian/bbql/src/eefd5ef5d3df01aed629f650959d6706d54cd335/tests/test_project/?format=meta\"\n }\n },\n \"commit\": { ... }\n}\n```\n\n#### Querying, filtering and sorting\n\nLike most API endpoints, this API supports the Bitbucket\nquerying/filtering syntax and so you could filter a directory listing\nto only include entries that match certain criteria. For instance, to\nlist all binary files over 1kb use the expression:\n\n`size > 1024 and attributes = \"binary\"`\n\nwhich after urlencoding yields the query string:\n\n`?q=size%3E1024+and+attributes%3D%22binary%22`\n\nTo change the ordering of the response, use the `?sort` parameter:\n\n`.../src/eefd5ef/?sort=-size`\n\nSee [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for more\ndetails." + ] }, "parameters": [ { @@ -13106,6 +13302,9 @@ }, "/repositories/{workspace}/{repo_slug}/versions": { "get": { + "tags": ["Issue tracker"], + "description": "Returns the versions that have been defined in the issue tracker.\n\nThis resource is only available on repositories that have the issue\ntracker enabled.", + "summary": "List defined versions for issues", "responses": { "200": { "description": "The versions that have been defined in the issue tracker.", @@ -13128,8 +13327,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "List defined versions for issues", "security": [ { "oauth2": ["issue"] @@ -13140,8 +13337,7 @@ { "api_key": [] } - ], - "description": "Returns the versions that have been defined in the issue tracker.\n\nThis resource is only available on repositories that have the issue\ntracker enabled." + ] }, "parameters": [ { @@ -13166,6 +13362,9 @@ }, "/repositories/{workspace}/{repo_slug}/versions/{version_id}": { "get": { + "tags": ["Issue tracker"], + "description": "Returns the specified issue tracker version object.", + "summary": "Get a defined version for issues", "responses": { "200": { "description": "The specified version object.", @@ -13188,8 +13387,6 @@ } } }, - "tags": ["Issue tracker"], - "summary": "Get a defined version for issues", "security": [ { "oauth2": ["issue"] @@ -13200,8 +13397,7 @@ { "api_key": [] } - ], - "description": "Returns the specified issue tracker version object." + ] }, "parameters": [ { @@ -13235,13 +13431,21 @@ }, "/repositories/{workspace}/{repo_slug}/watchers": { "get": { + "tags": ["Repositories"], + "description": "Returns a paginated list of all the watchers on the specified\nrepository.", + "summary": "List repositories watchers", "responses": { "200": { - "description": "A paginated list of all the watchers on the specified repository." + "description": "A paginated list of all the watchers on the specified repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_accounts" + } + } + } } }, - "tags": ["Repositories"], - "summary": "List repositories watchers", "security": [ { "oauth2": ["repository"] @@ -13252,8 +13456,7 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of all the watchers on the specified\nrepository." + ] }, "parameters": [ { @@ -13278,6 +13481,9 @@ }, "/snippets": { "get": { + "tags": ["Snippets"], + "description": "Returns all snippets. Like pull requests, repositories and workspaces, the\nfull set of snippets is defined by what the current user has access to.\n\nThis includes all snippets owned by any of the workspaces the user is a member of,\nor snippets by other users that the current user is either watching or has collaborated\non (for instance by commenting on it).\n\nTo limit the set of returned snippets, apply the\n`?role=[owner|contributor|member]` query parameter where the roles are\ndefined as follows:\n\n* `owner`: all snippets owned by the current user\n* `contributor`: all snippets owned by, or watched by the current user\n* `member`: created in a workspaces or watched by the current user\n\nWhen no role is specified, all public snippets are returned, as well as all\nprivately owned snippets watched or commented on.\n\nThe returned response is a normal paginated JSON list. This endpoint\nonly supports `application/json` responses and no\n`multipart/form-data` or `multipart/related`. As a result, it is not\npossible to include the file contents.", + "summary": "List snippets", "responses": { "200": { "description": "A paginated list of snippets.", @@ -13312,8 +13518,6 @@ } } ], - "tags": ["Snippets"], - "summary": "List snippets", "security": [ { "oauth2": ["snippet"] @@ -13324,10 +13528,12 @@ { "api_key": [] } - ], - "description": "Returns all snippets. Like pull requests, repositories and workspaces, the\nfull set of snippets is defined by what the current user has access to.\n\nThis includes all snippets owned by any of the workspaces the user is a member of,\nor snippets by other users that the current user is either watching or has collaborated\non (for instance by commenting on it).\n\nTo limit the set of returned snippets, apply the\n`?role=[owner|contributor|member]` query parameter where the roles are\ndefined as follows:\n\n* `owner`: all snippets owned by the current user\n* `contributor`: all snippets owned by, or watched by the current user\n* `member`: created in a workspaces or watched by the current user\n\nWhen no role is specified, all public snippets are returned, as well as all\nprivately owned snippets watched or commented on.\n\nThe returned response is a normal paginated JSON list. This endpoint\nonly supports `application/json` responses and no\n`multipart/form-data` or `multipart/related`. As a result, it is not\npossible to include the file contents." + ] }, "post": { + "tags": ["Snippets"], + "description": "Creates a new snippet under the authenticated user's account.\n\nSnippets can contain multiple files. Both text and binary files are\nsupported.\n\nThe simplest way to create a new snippet from a local file:\n\n $ curl -u username:password -X POST https://api.bitbucket.org/2.0/snippets -F file=@image.png\n\nCreating snippets through curl has a few limitations and so let's look\nat a more complicated scenario.\n\nSnippets are created with a multipart POST. Both `multipart/form-data`\nand `multipart/related` are supported. Both allow the creation of\nsnippets with both meta data (title, etc), as well as multiple text\nand binary files.\n\nThe main difference is that `multipart/related` can use rich encoding\nfor the meta data (currently JSON).\n\n\nmultipart/related (RFC-2387)\n----------------------------\n\nThis is the most advanced and efficient way to create a paste.\n\n POST /2.0/snippets/evzijst HTTP/1.1\n Content-Length: 1188\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"title\": \"My snippet\",\n \"is_private\": true,\n \"scm\": \"git\",\n \"files\": {\n \"foo.txt\": {},\n \"image.png\": {}\n }\n }\n\n --===============1438169132528273974==\n Content-Type: text/plain; charset=\"us-ascii\"\n MIME-Version: 1.0\n Content-Transfer-Encoding: 7bit\n Content-ID: \"foo.txt\"\n Content-Disposition: attachment; filename=\"foo.txt\"\n\n foo\n\n --===============1438169132528273974==\n Content-Type: image/png\n MIME-Version: 1.0\n Content-Transfer-Encoding: base64\n Content-ID: \"image.png\"\n Content-Disposition: attachment; filename=\"image.png\"\n\n iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAYAAAD+MdrbAAABD0lEQVR4Ae3VMUoDQRTG8ccUaW2m\n TKONFxArJYJamCvkCnZTaa+VnQdJSBFl2SMsLFrEWNjZBZs0JgiL/+KrhhVmJRbCLPx4O+/DT2TB\n cbblJxf+UWFVVRNsEGAtgvJxnLm2H+A5RQ93uIl+3632PZyl/skjfOn9Gvdwmlcw5aPUwimG+NT5\n EnNN036IaZePUuIcK533NVfal7/5yjWeot2z9ta1cAczHEf7I+3J0ws9Cgx0fsOFpmlfwKcWPuBQ\n 73Oc4FHzBaZ8llq4q1mr5B2mOUCt815qYR8eB1hG2VJ7j35q4RofaH7IG+Xrf/PfJhfmwtfFYoIN\n AqxFUD6OMxcvkO+UfKfkOyXfKdsv/AYCHMLVkHAFWgAAAABJRU5ErkJggg==\n --===============1438169132528273974==--\n\nThe request contains multiple parts and is structured as follows.\n\nThe first part is the JSON document that describes the snippet's\nproperties or meta data. It either has to be the first part, or the\nrequest's `Content-Type` header must contain the `start` parameter to\npoint to it.\n\nThe remaining parts are the files of which there can be zero or more.\nEach file part should contain the `Content-ID` MIME header through\nwhich the JSON meta data's `files` element addresses it. The value\nshould be the name of the file.\n\n`Content-Disposition` is an optional MIME header. The header's\noptional `filename` parameter can be used to specify the file name\nthat Bitbucket should use when writing the file to disk. When present,\n`filename` takes precedence over the value of `Content-ID`.\n\nWhen the JSON body omits the `files` element, the remaining parts are\nnot ignored. Instead, each file is added to the new snippet as if its\nname was explicitly linked (the use of the `files` elements is\nmandatory for some operations like deleting or renaming files).\n\n\nmultipart/form-data\n-------------------\n\nThe use of JSON for the snippet's meta data is optional. Meta data can\nalso be supplied as regular form fields in a more conventional\n`multipart/form-data` request:\n\n $ curl -X POST -u credentials https://api.bitbucket.org/2.0/snippets -F title=\"My snippet\" -F file=@foo.txt -F file=@image.png\n\n POST /2.0/snippets HTTP/1.1\n Content-Length: 951\n Content-Type: multipart/form-data; boundary=----------------------------63a4b224c59f\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"file\"; filename=\"foo.txt\"\n Content-Type: text/plain\n\n foo\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"file\"; filename=\"image.png\"\n Content-Type: application/octet-stream\n\n ?PNG\n\n IHDR?1??I.....\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"title\"\n\n My snippet\n ------------------------------63a4b224c59f--\n\nHere the meta data properties are included as flat, top-level form\nfields. The file attachments use the `file` field name. To attach\nmultiple files, simply repeat the field.\n\nThe advantage of `multipart/form-data` over `multipart/related` is\nthat it can be easier to build clients.\n\nEssentially all properties are optional, `title` and `files` included.\n\n\nSharing and Visibility\n----------------------\n\nSnippets can be either public (visible to anyone on Bitbucket, as well\nas anonymous users), or private (visible only to members of the workspace).\nThis is controlled through the snippet's `is_private` element:\n\n* **is_private=false** -- everyone, including anonymous users can view\n the snippet\n* **is_private=true** -- only workspace members can view the snippet\n\nTo create the snippet under a workspace, just append the workspace ID\nto the URL. See [`/2.0/snippets/{workspace}`](/cloud/bitbucket/rest/api-group-snippets/#api-snippets-workspace-post).", + "summary": "Create a snippet", "responses": { "201": { "description": "The newly created snippet object.", @@ -13361,8 +13567,6 @@ "requestBody": { "$ref": "#/components/requestBodies/snippet" }, - "tags": ["Snippets"], - "summary": "Create a snippet", "security": [ { "oauth2": ["snippet:write"] @@ -13373,13 +13577,15 @@ { "api_key": [] } - ], - "description": "Creates a new snippet under the authenticated user's account.\n\nSnippets can contain multiple files. Both text and binary files are\nsupported.\n\nThe simplest way to create a new snippet from a local file:\n\n $ curl -u username:password -X POST https://api.bitbucket.org/2.0/snippets -F file=@image.png\n\nCreating snippets through curl has a few limitations and so let's look\nat a more complicated scenario.\n\nSnippets are created with a multipart POST. Both `multipart/form-data`\nand `multipart/related` are supported. Both allow the creation of\nsnippets with both meta data (title, etc), as well as multiple text\nand binary files.\n\nThe main difference is that `multipart/related` can use rich encoding\nfor the meta data (currently JSON).\n\n\nmultipart/related (RFC-2387)\n----------------------------\n\nThis is the most advanced and efficient way to create a paste.\n\n POST /2.0/snippets/evzijst HTTP/1.1\n Content-Length: 1188\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"title\": \"My snippet\",\n \"is_private\": true,\n \"scm\": \"git\",\n \"files\": {\n \"foo.txt\": {},\n \"image.png\": {}\n }\n }\n\n --===============1438169132528273974==\n Content-Type: text/plain; charset=\"us-ascii\"\n MIME-Version: 1.0\n Content-Transfer-Encoding: 7bit\n Content-ID: \"foo.txt\"\n Content-Disposition: attachment; filename=\"foo.txt\"\n\n foo\n\n --===============1438169132528273974==\n Content-Type: image/png\n MIME-Version: 1.0\n Content-Transfer-Encoding: base64\n Content-ID: \"image.png\"\n Content-Disposition: attachment; filename=\"image.png\"\n\n iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAYAAAD+MdrbAAABD0lEQVR4Ae3VMUoDQRTG8ccUaW2m\n TKONFxArJYJamCvkCnZTaa+VnQdJSBFl2SMsLFrEWNjZBZs0JgiL/+KrhhVmJRbCLPx4O+/DT2TB\n cbblJxf+UWFVVRNsEGAtgvJxnLm2H+A5RQ93uIl+3632PZyl/skjfOn9Gvdwmlcw5aPUwimG+NT5\n EnNN036IaZePUuIcK533NVfal7/5yjWeot2z9ta1cAczHEf7I+3J0ws9Cgx0fsOFpmlfwKcWPuBQ\n 73Oc4FHzBaZ8llq4q1mr5B2mOUCt815qYR8eB1hG2VJ7j35q4RofaH7IG+Xrf/PfJhfmwtfFYoIN\n AqxFUD6OMxcvkO+UfKfkOyXfKdsv/AYCHMLVkHAFWgAAAABJRU5ErkJggg==\n --===============1438169132528273974==--\n\nThe request contains multiple parts and is structured as follows.\n\nThe first part is the JSON document that describes the snippet's\nproperties or meta data. It either has to be the first part, or the\nrequest's `Content-Type` header must contain the `start` parameter to\npoint to it.\n\nThe remaining parts are the files of which there can be zero or more.\nEach file part should contain the `Content-ID` MIME header through\nwhich the JSON meta data's `files` element addresses it. The value\nshould be the name of the file.\n\n`Content-Disposition` is an optional MIME header. The header's\noptional `filename` parameter can be used to specify the file name\nthat Bitbucket should use when writing the file to disk. When present,\n`filename` takes precedence over the value of `Content-ID`.\n\nWhen the JSON body omits the `files` element, the remaining parts are\nnot ignored. Instead, each file is added to the new snippet as if its\nname was explicitly linked (the use of the `files` elements is\nmandatory for some operations like deleting or renaming files).\n\n\nmultipart/form-data\n-------------------\n\nThe use of JSON for the snippet's meta data is optional. Meta data can\nalso be supplied as regular form fields in a more conventional\n`multipart/form-data` request:\n\n $ curl -X POST -u credentials https://api.bitbucket.org/2.0/snippets -F title=\"My snippet\" -F file=@foo.txt -F file=@image.png\n\n POST /2.0/snippets HTTP/1.1\n Content-Length: 951\n Content-Type: multipart/form-data; boundary=----------------------------63a4b224c59f\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"file\"; filename=\"foo.txt\"\n Content-Type: text/plain\n\n foo\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"file\"; filename=\"image.png\"\n Content-Type: application/octet-stream\n\n ?PNG\n\n IHDR?1??I.....\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"title\"\n\n My snippet\n ------------------------------63a4b224c59f--\n\nHere the meta data properties are included as flat, top-level form\nfields. The file attachments use the `file` field name. To attach\nmultiple files, simply repeat the field.\n\nThe advantage of `multipart/form-data` over `multipart/related` is\nthat it can be easier to build clients.\n\nEssentially all properties are optional, `title` and `files` included.\n\n\nSharing and Visibility\n----------------------\n\nSnippets can be either public (visible to anyone on Bitbucket, as well\nas anonymous users), or private (visible only to members of the workspace).\nThis is controlled through the snippet's `is_private` element:\n\n* **is_private=false** -- everyone, including anonymous users can view\n the snippet\n* **is_private=true** -- only workspace members can view the snippet\n\nTo create the snippet under a workspace, just append the workspace ID\nto the URL. See [`/2.0/snippets/{workspace}`](/cloud/bitbucket/rest/api-group-snippets/#api-snippets-workspace-post)." + ] }, "parameters": [] }, "/snippets/{workspace}": { "get": { + "tags": ["Snippets"], + "description": "Identical to [`/snippets`](/cloud/bitbucket/rest/api-group-snippets/#api-snippets-get), except that the result is further filtered\nby the snippet owner and only those that are owned by `{workspace}` are\nreturned.", + "summary": "List snippets in a workspace", "responses": { "200": { "description": "A paginated list of snippets.", @@ -13414,8 +13620,6 @@ } } ], - "tags": ["Snippets"], - "summary": "List snippets in a workspace", "security": [ { "oauth2": ["snippet"] @@ -13426,10 +13630,12 @@ { "api_key": [] } - ], - "description": "Identical to [`/snippets`](/cloud/bitbucket/rest/api-group-snippets/#api-snippets-get), except that the result is further filtered\nby the snippet owner and only those that are owned by `{workspace}` are\nreturned." + ] }, "post": { + "tags": ["Snippets"], + "description": "Identical to [`/snippets`](/cloud/bitbucket/rest/api-group-snippets/#api-snippets-post), except that the new snippet will be\ncreated under the workspace specified in the path parameter\n`{workspace}`.", + "summary": "Create a snippet for a workspace", "responses": { "201": { "description": "The newly created snippet object.", @@ -13473,8 +13679,6 @@ "requestBody": { "$ref": "#/components/requestBodies/snippet" }, - "tags": ["Snippets"], - "summary": "Create a snippet for a workspace", "security": [ { "oauth2": ["snippet:write"] @@ -13485,8 +13689,7 @@ { "api_key": [] } - ], - "description": "Identical to [`/snippets`](/cloud/bitbucket/rest/api-group-snippets/#api-snippets-post), except that the new snippet will be\ncreated under the workspace specified in the path parameter\n`{workspace}`." + ] }, "parameters": [ { @@ -13502,6 +13705,9 @@ }, "/snippets/{workspace}/{encoded_id}": { "delete": { + "tags": ["Snippets"], + "description": "Deletes a snippet and returns an empty response.", + "summary": "Delete a snippet", "responses": { "204": { "description": "If the snippet was deleted successfully." @@ -13537,8 +13743,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Delete a snippet", "security": [ { "oauth2": ["snippet:write"] @@ -13549,10 +13753,12 @@ { "api_key": [] } - ], - "description": "Deletes a snippet and returns an empty response." + ] }, "get": { + "tags": ["Snippets"], + "description": "Retrieves a single snippet.\n\nSnippets support multiple content types:\n\n* application/json\n* multipart/related\n* multipart/form-data\n\n\napplication/json\n----------------\n\nThe default content type of the response is `application/json`.\nSince JSON is always `utf-8`, it cannot reliably contain file contents\nfor files that are not text. Therefore, JSON snippet documents only\ncontain the filename and links to the file contents.\n\nThis means that in order to retrieve all parts of a snippet, N+1\nrequests need to be made (where N is the number of files in the\nsnippet).\n\n\nmultipart/related\n-----------------\n\nTo retrieve an entire snippet in a single response, use the\n`Accept: multipart/related` HTTP request header.\n\n $ curl -H \"Accept: multipart/related\" https://api.bitbucket.org/2.0/snippets/evzijst/1\n\nResponse:\n\n HTTP/1.1 200 OK\n Content-Length: 2214\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/snippets/evzijst/kypj\"\n },\n \"comments\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/comments\"\n },\n \"watchers\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/watchers\"\n },\n \"commits\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/commits\"\n }\n },\n \"id\": kypj,\n \"title\": \"My snippet\",\n \"created_on\": \"2014-12-29T22:22:04.790331+00:00\",\n \"updated_on\": \"2014-12-29T22:22:04.790331+00:00\",\n \"is_private\": false,\n \"files\": {\n \"foo.txt\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/files/367ab19/foo.txt\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/snippets/evzijst/kypj#file-foo.txt\"\n }\n }\n },\n \"image.png\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/files/367ab19/image.png\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/snippets/evzijst/kypj#file-image.png\"\n }\n }\n }\n ],\n \"owner\": {\n \"username\": \"evzijst\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/evzijst\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket-staging-assetroot.s3.amazonaws.com/c/photos/2013/Jul/31/erik-avatar-725122544-0_avatar.png\"\n }\n }\n },\n \"creator\": {\n \"username\": \"evzijst\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/evzijst\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket-staging-assetroot.s3.amazonaws.com/c/photos/2013/Jul/31/erik-avatar-725122544-0_avatar.png\"\n }\n }\n }\n }\n\n --===============1438169132528273974==\n Content-Type: text/plain; charset=\"us-ascii\"\n MIME-Version: 1.0\n Content-Transfer-Encoding: 7bit\n Content-ID: \"foo.txt\"\n Content-Disposition: attachment; filename=\"foo.txt\"\n\n foo\n\n --===============1438169132528273974==\n Content-Type: image/png\n MIME-Version: 1.0\n Content-Transfer-Encoding: base64\n Content-ID: \"image.png\"\n Content-Disposition: attachment; filename=\"image.png\"\n\n iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAYAAAD+MdrbAAABD0lEQVR4Ae3VMUoDQRTG8ccUaW2m\n TKONFxArJYJamCvkCnZTaa+VnQdJSBFl2SMsLFrEWNjZBZs0JgiL/+KrhhVmJRbCLPx4O+/DT2TB\n cbblJxf+UWFVVRNsEGAtgvJxnLm2H+A5RQ93uIl+3632PZyl/skjfOn9Gvdwmlcw5aPUwimG+NT5\n EnNN036IaZePUuIcK533NVfal7/5yjWeot2z9ta1cAczHEf7I+3J0ws9Cgx0fsOFpmlfwKcWPuBQ\n 73Oc4FHzBaZ8llq4q1mr5B2mOUCt815qYR8eB1hG2VJ7j35q4RofaH7IG+Xrf/PfJhfmwtfFYoIN\n AqxFUD6OMxcvkO+UfKfkOyXfKdsv/AYCHMLVkHAFWgAAAABJRU5ErkJggg==\n --===============1438169132528273974==--\n\nmultipart/form-data\n-------------------\n\nAs with creating new snippets, `multipart/form-data` can be used as an\nalternative to `multipart/related`. However, the inherently flat\nstructure of form-data means that only basic, root-level properties\ncan be returned, while nested elements like `links` are omitted:\n\n $ curl -H \"Accept: multipart/form-data\" https://api.bitbucket.org/2.0/snippets/evzijst/kypj\n\nResponse:\n\n HTTP/1.1 200 OK\n Content-Length: 951\n Content-Type: multipart/form-data; boundary=----------------------------63a4b224c59f\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"title\"\n Content-Type: text/plain; charset=\"utf-8\"\n\n My snippet\n ------------------------------63a4b224c59f--\n Content-Disposition: attachment; name=\"file\"; filename=\"foo.txt\"\n Content-Type: text/plain\n\n foo\n\n ------------------------------63a4b224c59f\n Content-Disposition: attachment; name=\"file\"; filename=\"image.png\"\n Content-Transfer-Encoding: base64\n Content-Type: application/octet-stream\n\n iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAYAAAD+MdrbAAABD0lEQVR4Ae3VMUoDQRTG8ccUaW2m\n TKONFxArJYJamCvkCnZTaa+VnQdJSBFl2SMsLFrEWNjZBZs0JgiL/+KrhhVmJRbCLPx4O+/DT2TB\n cbblJxf+UWFVVRNsEGAtgvJxnLm2H+A5RQ93uIl+3632PZyl/skjfOn9Gvdwmlcw5aPUwimG+NT5\n EnNN036IaZePUuIcK533NVfal7/5yjWeot2z9ta1cAczHEf7I+3J0ws9Cgx0fsOFpmlfwKcWPuBQ\n 73Oc4FHzBaZ8llq4q1mr5B2mOUCt815qYR8eB1hG2VJ7j35q4RofaH7IG+Xrf/PfJhfmwtfFYoIN\n AqxFUD6OMxcvkO+UfKfkOyXfKdsv/AYCHMLVkHAFWgAAAABJRU5ErkJggg==\n ------------------------------5957323a6b76--", + "summary": "Get a snippet", "responses": { "200": { "description": "The snippet object.", @@ -13655,8 +13861,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Get a snippet", "security": [ { "oauth2": ["snippet"] @@ -13667,10 +13871,12 @@ { "api_key": [] } - ], - "description": "Retrieves a single snippet.\n\nSnippets support multiple content types:\n\n* application/json\n* multipart/related\n* multipart/form-data\n\n\napplication/json\n----------------\n\nThe default content type of the response is `application/json`.\nSince JSON is always `utf-8`, it cannot reliably contain file contents\nfor files that are not text. Therefore, JSON snippet documents only\ncontain the filename and links to the file contents.\n\nThis means that in order to retrieve all parts of a snippet, N+1\nrequests need to be made (where N is the number of files in the\nsnippet).\n\n\nmultipart/related\n-----------------\n\nTo retrieve an entire snippet in a single response, use the\n`Accept: multipart/related` HTTP request header.\n\n $ curl -H \"Accept: multipart/related\" https://api.bitbucket.org/2.0/snippets/evzijst/1\n\nResponse:\n\n HTTP/1.1 200 OK\n Content-Length: 2214\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/snippets/evzijst/kypj\"\n },\n \"comments\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/comments\"\n },\n \"watchers\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/watchers\"\n },\n \"commits\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/commits\"\n }\n },\n \"id\": kypj,\n \"title\": \"My snippet\",\n \"created_on\": \"2014-12-29T22:22:04.790331+00:00\",\n \"updated_on\": \"2014-12-29T22:22:04.790331+00:00\",\n \"is_private\": false,\n \"files\": {\n \"foo.txt\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/files/367ab19/foo.txt\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/snippets/evzijst/kypj#file-foo.txt\"\n }\n }\n },\n \"image.png\": {\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/evzijst/kypj/files/367ab19/image.png\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/snippets/evzijst/kypj#file-image.png\"\n }\n }\n }\n ],\n \"owner\": {\n \"username\": \"evzijst\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/evzijst\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket-staging-assetroot.s3.amazonaws.com/c/photos/2013/Jul/31/erik-avatar-725122544-0_avatar.png\"\n }\n }\n },\n \"creator\": {\n \"username\": \"evzijst\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/evzijst\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/evzijst\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket-staging-assetroot.s3.amazonaws.com/c/photos/2013/Jul/31/erik-avatar-725122544-0_avatar.png\"\n }\n }\n }\n }\n\n --===============1438169132528273974==\n Content-Type: text/plain; charset=\"us-ascii\"\n MIME-Version: 1.0\n Content-Transfer-Encoding: 7bit\n Content-ID: \"foo.txt\"\n Content-Disposition: attachment; filename=\"foo.txt\"\n\n foo\n\n --===============1438169132528273974==\n Content-Type: image/png\n MIME-Version: 1.0\n Content-Transfer-Encoding: base64\n Content-ID: \"image.png\"\n Content-Disposition: attachment; filename=\"image.png\"\n\n iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAYAAAD+MdrbAAABD0lEQVR4Ae3VMUoDQRTG8ccUaW2m\n TKONFxArJYJamCvkCnZTaa+VnQdJSBFl2SMsLFrEWNjZBZs0JgiL/+KrhhVmJRbCLPx4O+/DT2TB\n cbblJxf+UWFVVRNsEGAtgvJxnLm2H+A5RQ93uIl+3632PZyl/skjfOn9Gvdwmlcw5aPUwimG+NT5\n EnNN036IaZePUuIcK533NVfal7/5yjWeot2z9ta1cAczHEf7I+3J0ws9Cgx0fsOFpmlfwKcWPuBQ\n 73Oc4FHzBaZ8llq4q1mr5B2mOUCt815qYR8eB1hG2VJ7j35q4RofaH7IG+Xrf/PfJhfmwtfFYoIN\n AqxFUD6OMxcvkO+UfKfkOyXfKdsv/AYCHMLVkHAFWgAAAABJRU5ErkJggg==\n --===============1438169132528273974==--\n\nmultipart/form-data\n-------------------\n\nAs with creating new snippets, `multipart/form-data` can be used as an\nalternative to `multipart/related`. However, the inherently flat\nstructure of form-data means that only basic, root-level properties\ncan be returned, while nested elements like `links` are omitted:\n\n $ curl -H \"Accept: multipart/form-data\" https://api.bitbucket.org/2.0/snippets/evzijst/kypj\n\nResponse:\n\n HTTP/1.1 200 OK\n Content-Length: 951\n Content-Type: multipart/form-data; boundary=----------------------------63a4b224c59f\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"title\"\n Content-Type: text/plain; charset=\"utf-8\"\n\n My snippet\n ------------------------------63a4b224c59f--\n Content-Disposition: attachment; name=\"file\"; filename=\"foo.txt\"\n Content-Type: text/plain\n\n foo\n\n ------------------------------63a4b224c59f\n Content-Disposition: attachment; name=\"file\"; filename=\"image.png\"\n Content-Transfer-Encoding: base64\n Content-Type: application/octet-stream\n\n iVBORw0KGgoAAAANSUhEUgAAABQAAAAoCAYAAAD+MdrbAAABD0lEQVR4Ae3VMUoDQRTG8ccUaW2m\n TKONFxArJYJamCvkCnZTaa+VnQdJSBFl2SMsLFrEWNjZBZs0JgiL/+KrhhVmJRbCLPx4O+/DT2TB\n cbblJxf+UWFVVRNsEGAtgvJxnLm2H+A5RQ93uIl+3632PZyl/skjfOn9Gvdwmlcw5aPUwimG+NT5\n EnNN036IaZePUuIcK533NVfal7/5yjWeot2z9ta1cAczHEf7I+3J0ws9Cgx0fsOFpmlfwKcWPuBQ\n 73Oc4FHzBaZ8llq4q1mr5B2mOUCt815qYR8eB1hG2VJ7j35q4RofaH7IG+Xrf/PfJhfmwtfFYoIN\n AqxFUD6OMxcvkO+UfKfkOyXfKdsv/AYCHMLVkHAFWgAAAABJRU5ErkJggg==\n ------------------------------5957323a6b76--" + ] }, "put": { + "tags": ["Snippets"], + "description": "Used to update a snippet. Use this to add and delete files and to\nchange a snippet's title.\n\nTo update a snippet, one can either PUT a full snapshot, or only the\nparts that need to be changed.\n\nThe contract for PUT on this API is that properties missing from the\nrequest remain untouched so that snippets can be efficiently\nmanipulated with differential payloads.\n\nTo delete a property (e.g. the title, or a file), include its name in\nthe request, but omit its value (use `null`).\n\nAs in Git, explicit renaming of files is not supported. Instead, to\nrename a file, delete it and add it again under another name. This can\nbe done atomically in a single request. Rename detection is left to\nthe SCM.\n\nPUT supports three different content types for both request and\nresponse bodies:\n\n* `application/json`\n* `multipart/related`\n* `multipart/form-data`\n\nThe content type used for the request body can be different than that\nused for the response. Content types are specified using standard HTTP\nheaders.\n\nUse the `Content-Type` and `Accept` headers to select the desired\nrequest and response format.\n\n\napplication/json\n----------------\n\nAs with creation and retrieval, the content type determines what\nproperties can be manipulated. `application/json` does not support\nfile contents and is therefore limited to a snippet's meta data.\n\nTo update the title, without changing any of its files:\n\n $ curl -X POST -H \"Content-Type: application/json\" https://api.bitbucket.org/2.0/snippets/evzijst/kypj -d '{\"title\": \"Updated title\"}'\n\n\nTo delete the title:\n\n $ curl -X POST -H \"Content-Type: application/json\" https://api.bitbucket.org/2.0/snippets/evzijst/kypj -d '{\"title\": null}'\n\nNot all parts of a snippet can be manipulated. The owner and creator\nfor instance are immutable.\n\n\nmultipart/related\n-----------------\n\n`multipart/related` can be used to manipulate all of a snippet's\nproperties. The body is identical to a POST. properties omitted from\nthe request are left unchanged. Since the `start` part contains JSON,\nthe mechanism for manipulating the snippet's meta data is identical\nto `application/json` requests.\n\nTo update one of a snippet's file contents, while also changing its\ntitle:\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 288\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"title\": \"My updated snippet\",\n \"files\": {\n \"foo.txt\": {}\n }\n }\n\n --===============1438169132528273974==\n Content-Type: text/plain; charset=\"us-ascii\"\n MIME-Version: 1.0\n Content-Transfer-Encoding: 7bit\n Content-ID: \"foo.txt\"\n Content-Disposition: attachment; filename=\"foo.txt\"\n\n Updated file contents.\n\n --===============1438169132528273974==--\n\nHere only the parts that are changed are included in the body. The\nother files remain untouched.\n\nNote the use of the `files` list in the JSON part. This list contains\nthe files that are being manipulated. This list should have\ncorresponding multiparts in the request that contain the new contents\nof these files.\n\nIf a filename in the `files` list does not have a corresponding part,\nit will be deleted from the snippet, as shown below:\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 188\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"files\": {\n \"image.png\": {}\n }\n }\n\n --===============1438169132528273974==--\n\nTo simulate a rename, delete a file and add the same file under\nanother name:\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 212\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"files\": {\n \"foo.txt\": {},\n \"bar.txt\": {}\n }\n }\n\n --===============1438169132528273974==\n Content-Type: text/plain; charset=\"us-ascii\"\n MIME-Version: 1.0\n Content-Transfer-Encoding: 7bit\n Content-ID: \"bar.txt\"\n Content-Disposition: attachment; filename=\"bar.txt\"\n\n foo\n\n --===============1438169132528273974==--\n\n\nmultipart/form-data\n-----------------\n\nAgain, one can also use `multipart/form-data` to manipulate file\ncontents and meta data atomically.\n\n $ curl -X PUT http://localhost:12345/2.0/snippets/evzijst/kypj -F title=\"My updated snippet\" -F file=@foo.txt\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 351\n Content-Type: multipart/form-data; boundary=----------------------------63a4b224c59f\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"file\"; filename=\"foo.txt\"\n Content-Type: text/plain\n\n foo\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"title\"\n\n My updated snippet\n ------------------------------63a4b224c59f\n\nTo delete a file, omit its contents while including its name in the\n`files` field:\n\n $ curl -X PUT https://api.bitbucket.org/2.0/snippets/evzijst/kypj -F files=image.png\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 149\n Content-Type: multipart/form-data; boundary=----------------------------ef8871065a86\n\n ------------------------------ef8871065a86\n Content-Disposition: form-data; name=\"files\"\n\n image.png\n ------------------------------ef8871065a86--\n\nThe explicit use of the `files` element in `multipart/related` and\n`multipart/form-data` is only required when deleting files.\nThe default mode of operation is for file parts to be processed,\nregardless of whether or not they are listed in `files`, as a\nconvenience to the client.", + "summary": "Update a snippet", "responses": { "200": { "description": "The updated snippet object.", @@ -13753,8 +13959,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Update a snippet", "security": [ { "oauth2": ["snippet:write"] @@ -13765,8 +13969,7 @@ { "api_key": [] } - ], - "description": "Used to update a snippet. Use this to add and delete files and to\nchange a snippet's title.\n\nTo update a snippet, one can either PUT a full snapshot, or only the\nparts that need to be changed.\n\nThe contract for PUT on this API is that properties missing from the\nrequest remain untouched so that snippets can be efficiently\nmanipulated with differential payloads.\n\nTo delete a property (e.g. the title, or a file), include its name in\nthe request, but omit its value (use `null`).\n\nAs in Git, explicit renaming of files is not supported. Instead, to\nrename a file, delete it and add it again under another name. This can\nbe done atomically in a single request. Rename detection is left to\nthe SCM.\n\nPUT supports three different content types for both request and\nresponse bodies:\n\n* `application/json`\n* `multipart/related`\n* `multipart/form-data`\n\nThe content type used for the request body can be different than that\nused for the response. Content types are specified using standard HTTP\nheaders.\n\nUse the `Content-Type` and `Accept` headers to select the desired\nrequest and response format.\n\n\napplication/json\n----------------\n\nAs with creation and retrieval, the content type determines what\nproperties can be manipulated. `application/json` does not support\nfile contents and is therefore limited to a snippet's meta data.\n\nTo update the title, without changing any of its files:\n\n $ curl -X POST -H \"Content-Type: application/json\" https://api.bitbucket.org/2.0/snippets/evzijst/kypj -d '{\"title\": \"Updated title\"}'\n\n\nTo delete the title:\n\n $ curl -X POST -H \"Content-Type: application/json\" https://api.bitbucket.org/2.0/snippets/evzijst/kypj -d '{\"title\": null}'\n\nNot all parts of a snippet can be manipulated. The owner and creator\nfor instance are immutable.\n\n\nmultipart/related\n-----------------\n\n`multipart/related` can be used to manipulate all of a snippet's\nproperties. The body is identical to a POST. properties omitted from\nthe request are left unchanged. Since the `start` part contains JSON,\nthe mechanism for manipulating the snippet's meta data is identical\nto `application/json` requests.\n\nTo update one of a snippet's file contents, while also changing its\ntitle:\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 288\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"title\": \"My updated snippet\",\n \"files\": {\n \"foo.txt\": {}\n }\n }\n\n --===============1438169132528273974==\n Content-Type: text/plain; charset=\"us-ascii\"\n MIME-Version: 1.0\n Content-Transfer-Encoding: 7bit\n Content-ID: \"foo.txt\"\n Content-Disposition: attachment; filename=\"foo.txt\"\n\n Updated file contents.\n\n --===============1438169132528273974==--\n\nHere only the parts that are changed are included in the body. The\nother files remain untouched.\n\nNote the use of the `files` list in the JSON part. This list contains\nthe files that are being manipulated. This list should have\ncorresponding multiparts in the request that contain the new contents\nof these files.\n\nIf a filename in the `files` list does not have a corresponding part,\nit will be deleted from the snippet, as shown below:\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 188\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"files\": {\n \"image.png\": {}\n }\n }\n\n --===============1438169132528273974==--\n\nTo simulate a rename, delete a file and add the same file under\nanother name:\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 212\n Content-Type: multipart/related; start=\"snippet\"; boundary=\"===============1438169132528273974==\"\n MIME-Version: 1.0\n\n --===============1438169132528273974==\n Content-Type: application/json; charset=\"utf-8\"\n MIME-Version: 1.0\n Content-ID: snippet\n\n {\n \"files\": {\n \"foo.txt\": {},\n \"bar.txt\": {}\n }\n }\n\n --===============1438169132528273974==\n Content-Type: text/plain; charset=\"us-ascii\"\n MIME-Version: 1.0\n Content-Transfer-Encoding: 7bit\n Content-ID: \"bar.txt\"\n Content-Disposition: attachment; filename=\"bar.txt\"\n\n foo\n\n --===============1438169132528273974==--\n\n\nmultipart/form-data\n-----------------\n\nAgain, one can also use `multipart/form-data` to manipulate file\ncontents and meta data atomically.\n\n $ curl -X PUT http://localhost:12345/2.0/snippets/evzijst/kypj -F title=\"My updated snippet\" -F file=@foo.txt\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 351\n Content-Type: multipart/form-data; boundary=----------------------------63a4b224c59f\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"file\"; filename=\"foo.txt\"\n Content-Type: text/plain\n\n foo\n\n ------------------------------63a4b224c59f\n Content-Disposition: form-data; name=\"title\"\n\n My updated snippet\n ------------------------------63a4b224c59f\n\nTo delete a file, omit its contents while including its name in the\n`files` field:\n\n $ curl -X PUT https://api.bitbucket.org/2.0/snippets/evzijst/kypj -F files=image.png\n\n PUT /2.0/snippets/evzijst/kypj HTTP/1.1\n Content-Length: 149\n Content-Type: multipart/form-data; boundary=----------------------------ef8871065a86\n\n ------------------------------ef8871065a86\n Content-Disposition: form-data; name=\"files\"\n\n image.png\n ------------------------------ef8871065a86--\n\nThe explicit use of the `files` element in `multipart/related` and\n`multipart/form-data` is only required when deleting files.\nThe default mode of operation is for file parts to be processed,\nregardless of whether or not they are listed in `files`, as a\nconvenience to the client." + ] }, "parameters": [ { @@ -13791,6 +13994,9 @@ }, "/snippets/{workspace}/{encoded_id}/comments": { "get": { + "tags": ["Snippets"], + "description": "Used to retrieve a paginated list of all comments for a specific\nsnippet.\n\nThis resource works identical to commit and pull request comments.\n\nThe default sorting is oldest to newest and can be overridden with\nthe `sort` query parameter.", + "summary": "List comments on a snippet", "responses": { "200": { "description": "A paginated list of snippet comments, ordered by creation date.", @@ -13823,8 +14029,6 @@ } } }, - "tags": ["Snippets"], - "summary": "List comments on a snippet", "security": [ { "oauth2": ["snippet"] @@ -13835,10 +14039,12 @@ { "api_key": [] } - ], - "description": "Used to retrieve a paginated list of all comments for a specific\nsnippet.\n\nThis resource works identical to commit and pull request comments.\n\nThe default sorting is oldest to newest and can be overridden with\nthe `sort` query parameter." + ] }, "post": { + "tags": ["Snippets"], + "description": "Creates a new comment.\n\nThe only required field in the body is `content.raw`.\n\nTo create a threaded reply to an existing comment, include `parent.id`.", + "summary": "Create a comment on a snippet", "responses": { "201": { "description": "The newly created comment.", @@ -13853,7 +14059,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/snippet" + "$ref": "#/components/schemas/snippet_comment" } } } @@ -13883,15 +14089,13 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/snippet" + "$ref": "#/components/schemas/snippet_comment" } } }, "description": "The contents of the new comment.", "required": true }, - "tags": ["Snippets"], - "summary": "Create a comment on a snippet", "security": [ { "oauth2": ["snippet"] @@ -13902,8 +14106,7 @@ { "api_key": [] } - ], - "description": "Creates a new comment.\n\nThe only required field in the body is `content.raw`.\n\nTo create a threaded reply to an existing comment, include `parent.id`." + ] }, "parameters": [ { @@ -13928,6 +14131,9 @@ }, "/snippets/{workspace}/{encoded_id}/comments/{comment_id}": { "delete": { + "tags": ["Snippets"], + "description": "Deletes a snippet comment.\n\nComments can only be removed by the comment author, snippet creator, or workspace admin.", + "summary": "Delete a comment on a snippet", "responses": { "204": { "description": "Indicates the comment was deleted successfully." @@ -13953,8 +14159,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Delete a comment on a snippet", "security": [ { "oauth2": ["snippet"] @@ -13965,10 +14169,12 @@ { "api_key": [] } - ], - "description": "Deletes a snippet comment.\n\nComments can only be removed by the comment author, snippet creator, or workspace admin." + ] }, "get": { + "tags": ["Snippets"], + "description": "Returns the specific snippet comment.", + "summary": "Get a comment on a snippet", "responses": { "200": { "description": "The specified comment.", @@ -14001,8 +14207,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Get a comment on a snippet", "security": [ { "oauth2": ["snippet"] @@ -14013,13 +14217,22 @@ { "api_key": [] } - ], - "description": "Returns the specific snippet comment." + ] }, "put": { + "tags": ["Snippets"], + "description": "Updates a comment.\n\nThe only required field in the body is `content.raw`.\n\nComments can only be updated by their author.", + "summary": "Update a comment on a snippet", "responses": { "200": { - "description": "The updated comment object." + "description": "The updated comment object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/snippet_comment" + } + } + } }, "403": { "description": "If the authenticated user does not have access to the snippet.", @@ -14042,8 +14255,17 @@ } } }, - "tags": ["Snippets"], - "summary": "Update a comment on a snippet", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/snippet_comment" + } + } + }, + "description": "The contents to update the comment to.", + "required": true + }, "security": [ { "oauth2": ["snippet"] @@ -14054,8 +14276,7 @@ { "api_key": [] } - ], - "description": "Updates a comment.\n\nComments can only be updated by their author." + ] }, "parameters": [ { @@ -14089,6 +14310,9 @@ }, "/snippets/{workspace}/{encoded_id}/commits": { "get": { + "tags": ["Snippets"], + "description": "Returns the changes (commits) made on this snippet.", + "summary": "List snippet changes", "responses": { "200": { "description": "The paginated list of snippet commits.", @@ -14121,8 +14345,6 @@ } } }, - "tags": ["Snippets"], - "summary": "List snippet changes", "security": [ { "oauth2": ["snippet"] @@ -14133,8 +14355,7 @@ { "api_key": [] } - ], - "description": "Returns the changes (commits) made on this snippet." + ] }, "parameters": [ { @@ -14159,6 +14380,9 @@ }, "/snippets/{workspace}/{encoded_id}/commits/{revision}": { "get": { + "tags": ["Snippets"], + "description": "Returns the changes made on this snippet in this commit.", + "summary": "Get a previous snippet change", "responses": { "200": { "description": "The specified snippet commit.", @@ -14191,8 +14415,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Get a previous snippet change", "security": [ { "oauth2": ["snippet"] @@ -14203,8 +14425,7 @@ { "api_key": [] } - ], - "description": "Returns the changes made on this snippet in this commit." + ] }, "parameters": [ { @@ -14238,6 +14459,9 @@ }, "/snippets/{workspace}/{encoded_id}/files/{path}": { "get": { + "tags": ["Snippets"], + "description": "Convenience resource for getting to a snippet's raw files without the\nneed for first having to retrieve the snippet itself and having to pull\nout the versioned file links.", + "summary": "Get a snippet's raw file at HEAD", "responses": { "302": { "description": "A redirect to the most recent revision of the specified file.", @@ -14271,8 +14495,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Get a snippet's raw file at HEAD", "security": [ { "oauth2": ["snippet"] @@ -14283,8 +14505,7 @@ { "api_key": [] } - ], - "description": "Convenience resource for getting to a snippet's raw files without the\nneed for first having to retrieve the snippet itself and having to pull\nout the versioned file links." + ] }, "parameters": [ { @@ -14318,16 +14539,12 @@ }, "/snippets/{workspace}/{encoded_id}/watch": { "delete": { + "tags": ["Snippets"], + "description": "Used to stop watching a specific snippet. Returns 204 (No Content)\nto indicate success.", + "summary": "Stop watching a snippet", "responses": { "204": { - "description": "Indicates the user stopped watching the snippet successfully.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_users" - } - } - } + "description": "Indicates the user stopped watching the snippet successfully." }, "401": { "description": "If the request was not authenticated.", @@ -14350,8 +14567,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Stop watching a snippet", "security": [ { "oauth2": ["snippet:write"] @@ -14362,20 +14577,15 @@ { "api_key": [] } - ], - "description": "Used to stop watching a specific snippet. Returns 204 (No Content)\nto indicate success." + ] }, "get": { + "tags": ["Snippets"], + "description": "Used to check if the current user is watching a specific snippet.\n\nReturns 204 (No Content) if the user is watching the snippet and 404 if\nnot.\n\nHitting this endpoint anonymously always returns a 404.", + "summary": "Check if the current user is watching a snippet", "responses": { "204": { - "description": "If the authenticated user is watching the snippet.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_users" - } - } - } + "description": "If the authenticated user is watching the snippet." }, "404": { "description": "If the snippet does not exist, or if the authenticated user is not watching the snippet.", @@ -14388,8 +14598,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Check if the current user is watching a snippet", "security": [ { "oauth2": ["snippet"] @@ -14400,20 +14608,15 @@ { "api_key": [] } - ], - "description": "Used to check if the current user is watching a specific snippet.\n\nReturns 204 (No Content) if the user is watching the snippet and 404 if\nnot.\n\nHitting this endpoint anonymously always returns a 404." + ] }, "put": { + "tags": ["Snippets"], + "description": "Used to start watching a specific snippet. Returns 204 (No Content).", + "summary": "Watch a snippet", "responses": { "204": { - "description": "Indicates the authenticated user is now watching the snippet.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_users" - } - } - } + "description": "Indicates the authenticated user is now watching the snippet." }, "401": { "description": "If the request was not authenticated.", @@ -14436,8 +14639,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Watch a snippet", "security": [ { "oauth2": ["snippet:write"] @@ -14448,8 +14649,7 @@ { "api_key": [] } - ], - "description": "Used to start watching a specific snippet. Returns 204 (No Content)." + ] }, "parameters": [ { @@ -14474,13 +14674,16 @@ }, "/snippets/{workspace}/{encoded_id}/watchers": { "get": { + "tags": ["Snippets"], + "description": "Returns a paginated list of all users watching a specific snippet.", + "summary": "List users watching a snippet", "responses": { "200": { "description": "The paginated list of users watching this snippet", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/paginated_users" + "$ref": "#/components/schemas/paginated_accounts" } } } @@ -14496,8 +14699,6 @@ } } }, - "tags": ["Snippets"], - "summary": "List users watching a snippet", "security": [ { "oauth2": ["snippet"] @@ -14509,7 +14710,6 @@ "api_key": [] } ], - "description": "Returns a paginated list of all users watching a specific snippet.", "deprecated": true }, "parameters": [ @@ -14535,6 +14735,9 @@ }, "/snippets/{workspace}/{encoded_id}/{node_id}": { "delete": { + "tags": ["Snippets"], + "description": "Deletes the snippet.\n\nNote that this only works for versioned URLs that point to the latest\ncommit of the snippet. Pointing to an older commit results in a 405\nstatus code.\n\nTo delete a snippet, regardless of whether or not concurrent changes\nare being made to it, use `DELETE /snippets/{encoded_id}` instead.", + "summary": "Delete a previous revision of a snippet", "responses": { "204": { "description": "If the snippet was deleted successfully." @@ -14580,8 +14783,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Delete a previous revision of a snippet", "security": [ { "oauth2": ["snippet:write"] @@ -14592,10 +14793,12 @@ { "api_key": [] } - ], - "description": "Deletes the snippet.\n\nNote that this only works for versioned URLs that point to the latest\ncommit of the snippet. Pointing to an older commit results in a 405\nstatus code.\n\nTo delete a snippet, regardless of whether or not concurrent changes\nare being made to it, use `DELETE /snippets/{encoded_id}` instead." + ] }, "get": { + "tags": ["Snippets"], + "description": "Identical to `GET /snippets/encoded_id`, except that this endpoint\ncan be used to retrieve the contents of the snippet as it was at an\nolder revision, while `/snippets/encoded_id` always returns the\nsnippet's current revision.\n\nNote that only the snippet's file contents are versioned, not its\nmeta data properties like the title.\n\nOther than that, the two endpoints are identical in behavior.", + "summary": "Get a previous revision of a snippet", "responses": { "200": { "description": "The snippet object.", @@ -14678,8 +14881,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Get a previous revision of a snippet", "security": [ { "oauth2": ["snippet"] @@ -14690,10 +14891,12 @@ { "api_key": [] } - ], - "description": "Identical to `GET /snippets/encoded_id`, except that this endpoint\ncan be used to retrieve the contents of the snippet as it was at an\nolder revision, while `/snippets/encoded_id` always returns the\nsnippet's current revision.\n\nNote that only the snippet's file contents are versioned, not its\nmeta data properties like the title.\n\nOther than that, the two endpoints are identical in behavior." + ] }, "put": { + "tags": ["Snippets"], + "description": "Identical to `UPDATE /snippets/encoded_id`, except that this endpoint\ntakes an explicit commit revision. Only the snippet's \"HEAD\"/\"tip\"\n(most recent) version can be updated and requests on all other,\nolder revisions fail by returning a 405 status.\n\nUsage of this endpoint over the unrestricted `/snippets/encoded_id`\ncould be desired if the caller wants to be sure no concurrent\nmodifications have taken place between the moment of the UPDATE\nrequest and the original GET.\n\nThis can be considered a so-called \"Compare And Swap\", or CAS\noperation.\n\nOther than that, the two endpoints are identical in behavior.", + "summary": "Update a previous revision of a snippet", "responses": { "200": { "description": "The updated snippet object.", @@ -14796,8 +14999,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Update a previous revision of a snippet", "security": [ { "oauth2": ["snippet:write"] @@ -14808,8 +15009,7 @@ { "api_key": [] } - ], - "description": "Identical to `UPDATE /snippets/encoded_id`, except that this endpoint\ntakes an explicit commit revision. Only the snippet's \"HEAD\"/\"tip\"\n(most recent) version can be updated and requests on all other,\nolder revisions fail by returning a 405 status.\n\nUsage of this endpoint over the unrestricted `/snippets/encoded_id`\ncould be desired if the caller wants to be sure no concurrent\nmodifications have taken place between the moment of the UPDATE\nrequest and the original GET.\n\nThis can be considered a so-called \"Compare And Swap\", or CAS\noperation.\n\nOther than that, the two endpoints are identical in behavior." + ] }, "parameters": [ { @@ -14843,6 +15043,9 @@ }, "/snippets/{workspace}/{encoded_id}/{node_id}/files/{path}": { "get": { + "tags": ["Snippets"], + "description": "Retrieves the raw contents of a specific file in the snippet. The\n`Content-Disposition` header will be \"attachment\" to avoid issues with\nmalevolent executable files.\n\nThe file's mime type is derived from its filename and returned in the\n`Content-Type` header.\n\nNote that for text files, no character encoding is included as part of\nthe content type.", + "summary": "Get a snippet's raw file", "responses": { "200": { "description": "Returns the contents of the specified file.", @@ -14882,8 +15085,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Get a snippet's raw file", "security": [ { "oauth2": ["snippet"] @@ -14894,8 +15095,7 @@ { "api_key": [] } - ], - "description": "Retrieves the raw contents of a specific file in the snippet. The\n`Content-Disposition` header will be \"attachment\" to avoid issues with\nmalevolent executable files.\n\nThe file's mime type is derived from its filename and returned in the\n`Content-Type` header.\n\nNote that for text files, no character encoding is included as part of\nthe content type." + ] }, "parameters": [ { @@ -14938,6 +15138,9 @@ }, "/snippets/{workspace}/{encoded_id}/{revision}/diff": { "get": { + "tags": ["Snippets"], + "description": "Returns the diff of the specified commit against its first parent.\n\nNote that this resource is different in functionality from the `patch`\nresource.\n\nThe differences between a diff and a patch are:\n\n* patches have a commit header with the username, message, etc\n* diffs support the optional `path=foo/bar.py` query param to filter the\n diff to just that one file diff (not supported for patches)\n* for a merge, the diff will show the diff between the merge commit and\n its first parent (identical to how PRs work), while patch returns a\n response containing separate patches for each commit on the second\n parent's ancestry, up to the oldest common ancestor (identical to\n its reachability).\n\nNote that the character encoding of the contents of the diff is\nunspecified as Git does not track this, making it hard for\nBitbucket to reliably determine this.", + "summary": "Get snippet changes between versions", "responses": { "200": { "description": "The raw diff contents." @@ -14973,8 +15176,6 @@ } } ], - "tags": ["Snippets"], - "summary": "Get snippet changes between versions", "security": [ { "oauth2": ["snippet"] @@ -14985,8 +15186,7 @@ { "api_key": [] } - ], - "description": "Returns the diff of the specified commit against its first parent.\n\nNote that this resource is different in functionality from the `patch`\nresource.\n\nThe differences between a diff and a patch are:\n\n* patches have a commit header with the username, message, etc\n* diffs support the optional `path=foo/bar.py` query param to filter the\n diff to just that one file diff (not supported for patches)\n* for a merge, the diff will show the diff between the merge commit and\n its first parent (identical to how PRs work), while patch returns a\n response containing separate patches for each commit on the second\n parent's ancestry, up to the oldest common ancestor (identical to\n its reachability).\n\nNote that the character encoding of the contents of the diff is\nunspecified as Git does not track this, making it hard for\nBitbucket to reliably determine this." + ] }, "parameters": [ { @@ -15020,6 +15220,9 @@ }, "/snippets/{workspace}/{encoded_id}/{revision}/patch": { "get": { + "tags": ["Snippets"], + "description": "Returns the patch of the specified commit against its first\nparent.\n\nNote that this resource is different in functionality from the `diff`\nresource.\n\nThe differences between a diff and a patch are:\n\n* patches have a commit header with the username, message, etc\n* diffs support the optional `path=foo/bar.py` query param to filter the\n diff to just that one file diff (not supported for patches)\n* for a merge, the diff will show the diff between the merge commit and\n its first parent (identical to how PRs work), while patch returns a\n response containing separate patches for each commit on the second\n parent's ancestry, up to the oldest common ancestor (identical to\n its reachability).\n\nNote that the character encoding of the contents of the patch is\nunspecified as Git does not track this, making it hard for\nBitbucket to reliably determine this.", + "summary": "Get snippet patch between versions", "responses": { "200": { "description": "The raw patch contents." @@ -15045,8 +15248,6 @@ } } }, - "tags": ["Snippets"], - "summary": "Get snippet patch between versions", "security": [ { "oauth2": ["snippet"] @@ -15057,8 +15258,7 @@ { "api_key": [] } - ], - "description": "Returns the patch of the specified commit against its first\nparent.\n\nNote that this resource is different in functionality from the `diff`\nresource.\n\nThe differences between a diff and a patch are:\n\n* patches have a commit header with the username, message, etc\n* diffs support the optional `path=foo/bar.py` query param to filter the\n diff to just that one file diff (not supported for patches)\n* for a merge, the diff will show the diff between the merge commit and\n its first parent (identical to how PRs work), while patch returns a\n response containing separate patches for each commit on the second\n parent's ancestry, up to the oldest common ancestor (identical to\n its reachability).\n\nNote that the character encoding of the contents of the patch is\nunspecified as Git does not track this, making it hard for\nBitbucket to reliably determine this." + ] }, "parameters": [ { @@ -15090,497 +15290,60 @@ } ] }, - "/teams": { - "get": { - "responses": { - "200": { - "description": "A paginated list of teams.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_teams" - } - } - } - }, - "401": { - "description": "When the request wasn't authenticated.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "parameters": [ - { - "name": "role", - "in": "query", - "description": "\nFilters the teams based on the authenticated user's role on each team.\n\n* **member**: returns a list of all the teams which the caller is a member of\n at least one team group or repository owned by the team\n* **contributor**: returns a list of teams which the caller has write access\n to at least one repository owned by the team\n* **admin**: returns a list teams which the caller has team administrator access\n", - "required": false, - "schema": { - "type": "string", - "enum": ["admin", "contributor", "member"] - } - } - ], - "tags": ["Teams"], - "summary": "List teams a user is part of", - "security": [ - { - "oauth2": ["team"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "Returns all the teams that the authenticated user is associated\nwith.\n\n**This endpoint has been removed.\nYou should use the [workspaces](/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-get) endpoint instead.\nFor more information, see [this post](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**", - "deprecated": true - }, - "parameters": [] - }, - "/teams/{username}": { - "get": { - "responses": { - "200": { - "description": "The team object", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/team" - } - } - } - }, - "404": { - "description": "If no team exists for the specified name or UUID, or if the specified account is a personal account, not a team account.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "tags": ["Teams"], - "summary": "Get a team", - "security": [ - { - "oauth2": [] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "Gets the public information associated with a team.\n\nIf the team's profile is private, `location`, `website` and\n`created_on` elements are omitted.\n\n**This endpoint has been removed.\nYou should use the [workspace](/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-get) endpoint instead.\nFor more information, see [this post](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**", - "deprecated": true - }, - "parameters": [ - { - "name": "username", - "in": "path", - "description": "This can either be the username or the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`. An account\nis either a team or user.\n", - "required": true, - "schema": { - "type": "string" - } - } - ] - }, - "/teams/{username}/followers": { - "get": { - "responses": { - "200": { - "description": "A paginated list of user objects.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_users" - } - } - } - }, - "404": { - "description": "If no team exists for the specified name, or if the specified account is a personal account, not a team account.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "tags": ["Teams"], - "summary": "List team followers", - "security": [ - { - "oauth2": [] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "Returns the list of accounts that are following this team.\n\n**This endpoint has been removed. There is no replacement endpoint.\nFor more information, see [this post](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**", - "deprecated": true - }, - "parameters": [ - { - "name": "username", - "in": "path", - "description": "This can either be the username or the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`. An account\nis either a team or user.\n", - "required": true, - "schema": { - "type": "string" - } - } - ] - }, - "/teams/{username}/following": { - "get": { - "responses": { - "200": { - "description": "A paginated list of user objects.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_users" - } - } - } - }, - "404": { - "description": "If no team exists for the specified name, or if the specified account is a personal account, not a team account.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "tags": ["Teams"], - "summary": "List accounts a team is following", - "security": [ - { - "oauth2": ["account"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "Returns the list of accounts this team is following.\n\n**This endpoint has been removed. There is no replacement endpoint.\nFor more information, see [this post](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**", - "deprecated": true - }, - "parameters": [ - { - "name": "username", - "in": "path", - "description": "This can either be the username or the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`. An account\nis either a team or user.\n", - "required": true, - "schema": { - "type": "string" - } - } - ] - }, - "/teams/{username}/members": { - "get": { - "responses": { - "200": { - "description": "All members", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/user" - } - } - } - }, - "404": { - "description": "When the team does not exist, or multiple teams with the same name exist that differ only in casing and the URL did not match the exact casing of a particular one.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "tags": ["Teams"], - "summary": "List team members", - "security": [ - { - "oauth2": ["account"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "Returns all members of the specified team. Any member of any of the\nteam's groups is considered a member of the team. This includes users\nin groups that may not actually have access to any of the team's\nrepositories.\n\n**This operation has been removed due to privacy changes.\nSee the [announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-changes-gdpr/)\nfor details.\nYou should use the [workspaces](/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-workspace-members-get) endpoint as a replacement.**", - "deprecated": true - }, - "parameters": [ - { - "name": "username", - "in": "path", - "description": "This can either be the username or the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`. An account\nis either a team or user.\n", - "required": true, - "schema": { - "type": "string" - } - } - ] - }, - "/teams/{username}/permissions": { - "get": { - "responses": { - "200": { - "description": "Repositories owned by a team.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_team_permissions" - } - } - } - }, - "403": { - "description": "The requesting user isn't an admin of the team.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "parameters": [ - { - "name": "q", - "in": "query", - "description": "\nQuery string to narrow down the response as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "sort", - "in": "query", - "description": "\nName of a response property sort the result by as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#sorting-query-results).\n", - "required": false, - "schema": { - "type": "string" - } - } - ], - "tags": ["Teams"], - "summary": "List team permissions for a user ", - "security": [ - { - "oauth2": ["team"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "Returns an object for each team permission a user on the team has.\n\n**This endpoint has been removed.\nYou should use the [workspace permissions](/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-workspace-members-member-get) endpoint instead.\nFor more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**\n\nPermissions returned are effective permissions — if a user is a member of\nmultiple groups with distinct roles, only the highest level is returned.\n\nPermissions can be:\n\n* `admin`\n* `collaborator`\n\nOnly users with admin permission for the team may access this resource.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/teams/atlassian_tutorial/permissions\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"permission\": \"admin\",\n \"type\": \"team_permission\",\n \"user\": {\n \"type\": \"user\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\"\n },\n \"team\": {\n \"display_name\": \"Atlassian Bitbucket\",\n \"uuid\": \"{4cc6108a-a241-4db0-96a5-64347ac04f87}\"\n }\n },\n {\n \"permission\": \"collaborator\",\n \"type\": \"team_permission\",\n \"user\": {\n \"type\": \"user\",\n \"nickname\": \"seanaty\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n },\n \"team\": {\n \"display_name\": \"Atlassian Bitbucket\",\n \"uuid\": \"{4cc6108a-a241-4db0-96a5-64347ac04f87}\"\n }\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering) by\nteam, user, or permission by adding the following query string\nparameters:\n\n* `q=user.uuid=\"{d301aafa-d676-4ee0-88be-962be7417567}\"` or `q=permission=\"admin\"`\n* `sort=team.display_name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", - "deprecated": true - }, - "parameters": [ - { - "name": "username", - "in": "path", - "description": "This can either be the username or the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`. An account\nis either a team or user.\n", - "required": true, - "schema": { - "type": "string" - } - } - ] - }, - "/teams/{username}/permissions/repositories": { - "get": { - "responses": { - "200": { - "description": "List of team's repository permissions.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_repository_permissions" - } - } - } - }, - "403": { - "description": "The requesting user isn't an admin of the team.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "parameters": [ - { - "name": "q", - "in": "query", - "description": "\nQuery string to narrow down the response as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "sort", - "in": "query", - "description": "\nName of a response property sort the result by as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#sorting-query-results).\n", - "required": false, - "schema": { - "type": "string" - } - } - ], - "tags": ["Teams"], - "summary": "List repository permissions for a team", - "security": [ - { - "oauth2": ["repository", "team"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "Returns an object for each repository permission for all of a\nteam’s repositories.\n\n**This endpoint has been removed.\nYou should use the [workspace repository permissions](/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-workspace-permissions-repositories-get) endpoint instead.\nFor more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**\n\nIf the username URL parameter refers to a user account instead of\na team account, an object containing the repository permissions\nof all the username's repositories will be returned.\n\nPermissions returned are effective permissions — the highest level of\npermission the user has. This does not include public repositories that\nusers are not granted any specific permission in, and does not\ndistinguish between explicit and implicit privileges.\n\nOnly users with admin permission for the team may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/teams/atlassian_tutorial/permissions/repositories\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"bitbucket/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"admin\"\n },\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"bitbucket/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"write\"\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering)\nby repository, user, or permission by adding the following query string\nparameters:\n\n* `q=repository.name=\"geordi\"` or `q=permission>\"read\"`\n* `sort=user.display_name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", - "deprecated": true - }, - "parameters": [ - { - "name": "username", - "in": "path", - "description": "This can either be the username or the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`. An account\nis either a team or user.\n", - "required": true, - "schema": { - "type": "string" - } - } - ] - }, - "/teams/{username}/permissions/repositories/{repo_slug}": { - "get": { - "responses": { - "200": { - "description": "List of repository's repository permissions.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_repository_permissions" - } - } - } - }, - "403": { - "description": "The requesting user isn't an admin of the repository.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "parameters": [ - { - "name": "q", - "in": "query", - "description": "\nQuery string to narrow down the response as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "sort", - "in": "query", - "description": "\nName of a response property sort the result by as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#sorting-query-results).\n", - "required": false, - "schema": { - "type": "string" - } - } - ], - "tags": ["Teams"], - "summary": "List repository permissions for a team", - "security": [ - { - "oauth2": ["repository"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "Returns an object for each repository permission of a given repository.\n\n**This endpoint has been removed.\nYou should use the [workspace repository permissions](/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-workspace-permissions-repositories-repo-slug-get) endpoint instead.\nFor more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**\n\nIf the username URL parameter refers to a user account instead of\na team account, an object containing the repository permissions\nof the username's repository will be returned.\n\nPermissions returned are effective permissions — the highest level of\npermission the user has. This does not include public repositories that\nusers are not granted any specific permission in, and does not\ndistinguish between explicit and implicit privileges.\n\nOnly users with admin permission for the repository may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/teams/atlassian_tutorial/permissions/repositories/geordi\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"bitbucket/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"admin\"\n },\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"bitbucket/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"write\"\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering)\nby user, or permission by adding the following query string parameters:\n\n* `q=permission>\"read\"`\n* `sort=user.display_name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", - "deprecated": true - }, - "parameters": [ - { - "name": "repo_slug", - "in": "path", - "description": "This can either be the repository slug or the UUID of the repository,\nsurrounded by curly-braces, for example: `{repository UUID}`.\n", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "username", - "in": "path", - "description": "This can either be the username or the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`. An account\nis either a team or user.\n", - "required": true, - "schema": { - "type": "string" - } - } - ] - }, "/teams/{username}/pipelines_config/variables/": { + "get": { + "tags": ["Pipelines"], + "summary": "List variables for an account", + "deprecated": true, + "description": "Find account level variables.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).", + "operationId": "getPipelineVariablesForTeam", + "parameters": [ + { + "name": "username", + "description": "The account.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The found account level variables.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pipeline_variables" + } + } + } + } + } + }, "post": { + "tags": ["Pipelines"], + "deprecated": true, + "summary": "Create a variable for a user", + "description": "Create an account level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).", + "operationId": "createPipelineVariableForTeam", + "parameters": [ + { + "name": "username", + "description": "The account.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/pipeline_variable2" + }, "responses": { "201": { + "description": "The created variable.", "headers": { "Location": { "description": "The URL of the newly created pipeline variable.", @@ -15589,7 +15352,6 @@ } } }, - "description": "The created variable.", "content": { "application/json": { "schema": { @@ -15618,112 +15380,36 @@ } } } - }, - "parameters": [ - { - "description": "The account.", - "required": true, - "name": "username", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "$ref": "#/components/requestBodies/pipeline_variable2" - }, - "tags": ["Pipelines"], - "deprecated": true, - "summary": "Create a variable for a user", - "operationId": "createPipelineVariableForTeam", - "description": "Create an account level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/)." - }, - "get": { - "responses": { - "200": { - "description": "The found account level variables.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_pipeline_variables" - } - } - } - } - }, - "parameters": [ - { - "description": "The account.", - "required": true, - "name": "username", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Pipelines"], - "deprecated": true, - "summary": "List variables for an account", - "operationId": "getPipelineVariablesForTeam", - "description": "Find account level variables.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/)." + } } }, "/teams/{username}/pipelines_config/variables/{variable_uuid}": { - "put": { - "responses": { - "200": { - "description": "The variable was updated.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_variable" - } - } - } - }, - "404": { - "description": "The account or the variable was not found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, + "get": { + "tags": ["Pipelines"], + "deprecated": true, + "summary": "Get a variable for a team", + "description": "Retrieve a team level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).", + "operationId": "getPipelineVariableForTeam", "parameters": [ { + "name": "username", "description": "The account.", "required": true, - "name": "username", "in": "path", "schema": { "type": "string" } }, { - "description": "The UUID of the variable.", - "required": true, "name": "variable_uuid", + "description": "The UUID of the variable to retrieve.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "requestBody": { - "$ref": "#/components/requestBodies/pipeline_variable" - }, - "tags": ["Pipelines"], - "deprecated": true, - "summary": "Update a variable for a team", - "operationId": "updatePipelineVariableForTeam", - "description": "Update a team level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/)." - }, - "get": { "responses": { "200": { "description": "The variable.", @@ -15745,34 +15431,86 @@ } } } - }, + } + }, + "put": { + "tags": ["Pipelines"], + "deprecated": true, + "summary": "Update a variable for a team", + "description": "Update a team level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).", + "operationId": "updatePipelineVariableForTeam", "parameters": [ { + "name": "username", "description": "The account.", "required": true, - "name": "username", "in": "path", "schema": { "type": "string" } }, { - "description": "The UUID of the variable to retrieve.", - "required": true, "name": "variable_uuid", + "description": "The UUID of the variable.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "deprecated": true, - "summary": "Get a variable for a team", - "operationId": "getPipelineVariableForTeam", - "description": "Retrieve a team level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/)." + "requestBody": { + "$ref": "#/components/requestBodies/pipeline_variable" + }, + "responses": { + "200": { + "description": "The variable was updated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + } + }, + "404": { + "description": "The account or the variable was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + } }, "delete": { + "tags": ["Pipelines"], + "deprecated": true, + "summary": "Delete a variable for a team", + "description": "Delete a team level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).", + "operationId": "deletePipelineVariableForTeam", + "parameters": [ + { + "name": "username", + "description": "The account.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "variable_uuid", + "description": "The UUID of the variable to delete.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "204": { "description": "The variable was deleted" @@ -15787,349 +15525,57 @@ } } } - }, - "parameters": [ - { - "description": "The account.", - "required": true, - "name": "username", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The UUID of the variable to delete.", - "required": true, - "name": "variable_uuid", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Pipelines"], - "deprecated": true, - "summary": "Delete a variable for a team", - "operationId": "deletePipelineVariableForTeam", - "description": "Delete a team level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/)." + } } }, - "/teams/{username}/projects/": { - "get": { - "responses": { - "200": { - "description": "A paginated list of projects that belong to the specified team.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_projects" - } - } - } - }, - "403": { - "description": "The requesting user isn't authorized to read the list of projects for the specified team.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - }, - "404": { - "description": "A team doesn't exist at this location.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "tags": ["Projects"], - "summary": "List a projects for a team", - "security": [ - { - "oauth2": ["project"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "**This endpoint has been removed.\nYou should use the [workspace projects](/cloud/bitbucket/rest/api-group-projects/#api-workspaces-workspace-projects-project-key-get) endpoint instead.\nFor more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**", - "deprecated": true - }, - "post": { - "responses": { - "201": { - "description": "A new project has been created.", - "headers": { - "Location": { - "description": "The location of the newly created project", - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/project" - } - } - } - }, - "403": { - "description": "The requesting user isn't authorized to create the project.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - }, - "404": { - "description": "A team doesn't exist at this location.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "requestBody": { - "$ref": "#/components/requestBodies/project" - }, - "tags": ["Projects"], - "summary": "Create a project for a team", - "security": [ - { - "oauth2": ["project:write"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "Creates a new project.\n\n**This endpoint has been removed.\nYou should use the [workspace projects](/cloud/bitbucket/rest/api-group-projects/#api-workspaces-workspace-projects-post) endpoint instead.\nFor more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**\n\nNote that the avatar has to be embedded as either a data-url\nor a URL to an external image as shown in the examples below:\n\n```\n$ body=$(cat << EOF\n{\n \"name\": \"Mars Project\",\n \"key\": \"MARS\",\n \"description\": \"Software for colonizing mars.\",\n \"links\": {\n \"avatar\": {\n \"href\": \"data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/...\"\n }\n },\n \"is_private\": false\n}\nEOF\n)\n$ curl -H \"Content-Type: application/json\" \\\n -X POST \\\n -d \"$body\" \\\n https://api.bitbucket.org/2.0/teams/teams-in-space/projects/ | jq .\n{\n // Serialized project document\n}\n```\n\nor even:\n\n```\n$ body=$(cat << EOF\n{\n \"name\": \"Mars Project\",\n \"key\": \"MARS\",\n \"description\": \"Software for colonizing mars.\",\n \"links\": {\n \"avatar\": {\n \"href\": \"http://i.imgur.com/72tRx4w.gif\"\n }\n },\n \"is_private\": false\n}\nEOF\n)\n$ curl -H \"Content-Type: application/json\" \\\n -X POST \\\n -d \"$body\" \\\n https://api.bitbucket.org/2.0/teams/teams-in-space/projects/ | jq .\n{\n // Serialized project document\n}\n```", - "deprecated": true - }, - "parameters": [ - { - "name": "username", - "in": "path", - "description": "This can either be the username or the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`. An account\nis either a team or user.\n", - "required": true, - "schema": { - "type": "string" - } - } - ] - }, - "/teams/{username}/projects/{project_key}": { - "delete": { - "responses": { - "204": { - "description": "Successful deletion." - }, - "403": { - "description": "The requesting user isn't authorized to delete the project or the project isn't empty.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - }, - "404": { - "description": "A project isn't hosted at this location.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "tags": ["Projects"], - "summary": "Delete a project", - "security": [ - { - "oauth2": ["project:write"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "**This endpoint has been removed.\nYou should use the [workspace project](/cloud/bitbucket/rest/api-group-projects/#api-workspaces-workspace-projects-project-key-delete) endpoint instead.\nFor more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**", - "deprecated": true - }, - "get": { - "responses": { - "200": { - "description": "The project object.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/project" - } - } - } - }, - "403": { - "description": "The requesting user isn't authorized to access the project.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - }, - "404": { - "description": "A project isn't hosted at this location.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "tags": ["Projects"], - "summary": "Get a project", - "security": [ - { - "oauth2": ["project"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "**This endpoint has been removed.\nYou should use the [workspace project](/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-workspace-projects-get) endpoint instead.\nFor more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**", - "deprecated": true - }, - "put": { - "responses": { - "200": { - "description": "The existing project is has been updated.", - "headers": { - "Location": { - "description": "The location of the project. This header is only provided\nwhen the project key is updated.", - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/project" - } - } - } - }, - "201": { - "description": "A new project has been created.", - "headers": { - "Location": { - "description": "The location of the newly created project", - "schema": { - "type": "string" - } - } - }, - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/project" - } - } - } - }, - "403": { - "description": "The requesting user isn't authorized to update or create the project.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - }, - "404": { - "description": "A team doesn't exist at the location. Note that the project's absence from this location doesn't raise a 404, since a PUT at a non-existent location can be used to create a new project.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "requestBody": { - "$ref": "#/components/requestBodies/project" - }, - "tags": ["Projects"], - "summary": "Update a project", - "security": [ - { - "oauth2": ["project:write"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "Since this endpoint can be used to both update and to create a\nproject, the request body depends on the intent.\n\n**This endpoint has been removed.\nYou should use the [workspace project](/cloud/bitbucket/rest/api-group-projects/#api-workspaces-workspace-projects-project-key-put) endpoint instead.\nFor more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**\n\n#### Creation\n\nSee the POST documentation for the project collection for an\nexample of the request body.\n\nNote: The `key` should not be specified in the body of request\n(since it is already present in the URL). The `name` is required,\neverything else is optional.\n\n#### Update\n\nSee the POST documentation for the project collection for an\nexample of the request body.\n\nNote: The key is not required in the body (since it is already in\nthe URL). The key may be specified in the body, if the intent is\nto change the key itself. In such a scenario, the location of the\nproject is changed and is returned in the `Location` header of the\nresponse.", - "deprecated": true - }, - "parameters": [ - { - "name": "project_key", - "in": "path", - "description": "The project in question. This can either be the actual `key` assigned\nto the project or the `UUID` (surrounded by curly-braces (`{}`)).\n", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "username", - "in": "path", - "description": "This can either be the username or the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`. An account\nis either a team or user.\n", - "required": true, - "schema": { - "type": "string" - } - } - ] - }, "/teams/{username}/search/code": { "get": { + "tags": ["Search"], + "summary": "Search for code in a team's repositories", + "description": "Search for code in the repositories of the specified team.\n\nSearching across all repositories:\n\n```\ncurl 'https://api.bitbucket.org/2.0/teams/team_name/search/code?search_query=foo'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 2,\n \"content_matches\": [\n {\n \"lines\": [\n {\n \"line\": 2,\n \"segments\": []\n },\n {\n \"line\": 3,\n \"segments\": [\n {\n \"text\": \"def \"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \"():\"\n }\n ]\n },\n {\n \"line\": 4,\n \"segments\": [\n {\n \"text\": \" print(\\\"snek\\\")\"\n }\n ]\n },\n {\n \"line\": 5,\n \"segments\": []\n }\n ]\n }\n ],\n \"path_matches\": [\n {\n \"text\": \"src/\"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \".py\"\n }\n ],\n \"file\": {\n \"path\": \"src/foo.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n }\n }\n }\n ]\n}\n```\n\nNote that searches can match in the file's text (`content_matches`),\nthe path (`path_matches`), or both as in the example above.\n\nYou can use the same syntax for the search query as in the UI, e.g.\nto only search within a specific repository:\n\n```\ncurl 'https://api.bitbucket.org/2.0/teams/team_name/search/code?search_query=foo+repo:demo'\n# results from the \"demo\" repository\n```\n\nSimilar to other APIs, you can request more fields using a\n`fields` query parameter. E.g. to get some more information about\nthe repository of matched files (the `%2B` is a URL-encoded `+`):\n\n```\ncurl 'https://api.bitbucket.org/2.0/teams/team_name/search/code'\\\n '?search_query=foo&fields=%2Bvalues.file.commit.repository'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 1,\n \"content_matches\": [...],\n \"path_matches\": [...],\n \"file\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/commit/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo/commits/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n }\n },\n \"repository\": {\n \"name\": \"demo\",\n \"type\": \"repository\",\n \"full_name\": \"my-workspace/demo\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B850e1749-781a-4115-9316-df39d0600e7a%7D?ts=default\"\n }\n },\n \"uuid\": \"{850e1749-781a-4115-9316-df39d0600e7a}\"\n }\n },\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n },\n \"path\": \"src/foo.py\"\n }\n }\n ]\n}\n```\n\nTry `fields=%2Bvalues.*.*.*.*` to get an idea what's possible.\n", + "operationId": "searchTeam", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The account to search in; either the username or the UUID in curly braces", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "search_query", + "in": "query", + "description": "The search query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "page", + "in": "query", + "description": "Which page of the search results to retrieve", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "pagelen", + "in": "query", + "description": "How many search results to retrieve per page", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + } + ], "responses": { "200": { "description": "Successful search", @@ -16171,106 +15617,21 @@ } } } - }, - "parameters": [ - { - "required": true, - "description": "The account to search in; either the username or the UUID in curly braces", - "in": "path", - "name": "username", - "schema": { - "type": "string" - } - }, - { - "required": true, - "description": "The search query", - "in": "query", - "name": "search_query", - "schema": { - "type": "string" - } - }, - { - "description": "Which page of the search results to retrieve", - "required": false, - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "format": "int32", - "default": 1 - } - }, - { - "description": "How many search results to retrieve per page", - "required": false, - "in": "query", - "name": "pagelen", - "schema": { - "type": "integer", - "format": "int32", - "default": 10 - } - } - ], - "tags": ["Search"], - "summary": "Search for code in a team's repositories", - "operationId": "searchTeam", - "description": "Search for code in the repositories of the specified team.\n\nSearching across all repositories:\n\n```\ncurl 'https://api.bitbucket.org/2.0/teams/team_name/search/code?search_query=foo'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 2,\n \"content_matches\": [\n {\n \"lines\": [\n {\n \"line\": 2,\n \"segments\": []\n },\n {\n \"line\": 3,\n \"segments\": [\n {\n \"text\": \"def \"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \"():\"\n }\n ]\n },\n {\n \"line\": 4,\n \"segments\": [\n {\n \"text\": \" print(\\\"snek\\\")\"\n }\n ]\n },\n {\n \"line\": 5,\n \"segments\": []\n }\n ]\n }\n ],\n \"path_matches\": [\n {\n \"text\": \"src/\"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \".py\"\n }\n ],\n \"file\": {\n \"path\": \"src/foo.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n }\n }\n }\n ]\n}\n```\n\nNote that searches can match in the file's text (`content_matches`),\nthe path (`path_matches`), or both as in the example above.\n\nYou can use the same syntax for the search query as in the UI, e.g.\nto only search within a specific repository:\n\n```\ncurl 'https://api.bitbucket.org/2.0/teams/team_name/search/code?search_query=foo+repo:demo'\n# results from the \"demo\" repository\n```\n\nSimilar to other APIs, you can request more fields using a\n`fields` query parameter. E.g. to get some more information about\nthe repository of matched files (the `%2B` is a URL-encoded `+`):\n\n```\ncurl 'https://api.bitbucket.org/2.0/teams/team_name/search/code'\\\n '?search_query=foo&fields=%2Bvalues.file.commit.repository'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 1,\n \"content_matches\": [...],\n \"path_matches\": [...],\n \"file\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/commit/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo/commits/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n }\n },\n \"repository\": {\n \"name\": \"demo\",\n \"type\": \"repository\",\n \"full_name\": \"my-workspace/demo\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B850e1749-781a-4115-9316-df39d0600e7a%7D?ts=default\"\n }\n },\n \"uuid\": \"{850e1749-781a-4115-9316-df39d0600e7a}\"\n }\n },\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n },\n \"path\": \"src/foo.py\"\n }\n }\n ]\n}\n```\n\nTry `fields=%2Bvalues.*.*.*.*` to get an idea what's possible.\n" - } - }, - "/teams/{workspace}/repositories": { - "get": { - "responses": { - "default": { - "description": "Unexpected error.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "tags": ["Users", "Teams"], - "summary": "List workspace repositories", - "security": [ - { - "oauth2": ["repository"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "All repositories in the given workspace. This includes any private\nrepositories the calling user has access to.\n\n**This endpoint has been removed.\nYou should use the [repository list](/cloud/bitbucket/rest/api-group-repositories/#api-repositories-workspace-get) endpoint instead.\nFor more information, see the [deprecation announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**", - "deprecated": true - }, - "parameters": [ - { - "name": "workspace", - "in": "path", - "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", - "required": true, - "schema": { - "type": "string" - } } - ] + } }, "/user": { "get": { + "tags": ["Users"], + "description": "Returns the currently logged in user.", + "summary": "Get current user", "responses": { "200": { "description": "The current user.", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/user" + "$ref": "#/components/schemas/account" } } } @@ -16286,8 +15647,6 @@ } } }, - "tags": ["Users"], - "summary": "Get current user", "security": [ { "oauth2": ["account"] @@ -16298,13 +15657,15 @@ { "api_key": [] } - ], - "description": "Returns the currently logged in user." + ] }, "parameters": [] }, "/user/emails": { "get": { + "tags": ["Users"], + "description": "Returns all the authenticated user's email addresses. Both\nconfirmed and unconfirmed.", + "summary": "List email addresses for current user", "responses": { "default": { "description": "Unexpected error.", @@ -16317,8 +15678,6 @@ } } }, - "tags": ["Users"], - "summary": "List email addresses for current user", "security": [ { "oauth2": ["email"] @@ -16329,13 +15688,15 @@ { "api_key": [] } - ], - "description": "Returns all the authenticated user's email addresses. Both\nconfirmed and unconfirmed." + ] }, "parameters": [] }, "/user/emails/{email}": { "get": { + "tags": ["Users"], + "description": "Returns details about a specific one of the authenticated user's\nemail addresses.\n\nDetails describe whether the address has been confirmed by the user and\nwhether it is the user's primary address or not.", + "summary": "Get an email address for current user", "responses": { "default": { "description": "Unexpected error.", @@ -16348,8 +15709,6 @@ } } }, - "tags": ["Users"], - "summary": "Get an email address for current user", "security": [ { "oauth2": ["email"] @@ -16360,8 +15719,7 @@ { "api_key": [] } - ], - "description": "Returns details about a specific one of the authenticated user's\nemail addresses.\n\nDetails describe whether the address has been confirmed by the user and\nwhether it is the user's primary address or not." + ] }, "parameters": [ { @@ -16377,6 +15735,9 @@ }, "/user/permissions/repositories": { "get": { + "tags": ["Repositories"], + "description": "Returns an object for each repository the caller has explicit access\nto and their effective permission — the highest level of permission the\ncaller has. This does not return public repositories that the user was\nnot granted any specific permission in, and does not distinguish between\nexplicit and implicit privileges.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/user/permissions/repositories\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"bitbucket/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"admin\"\n }\n ],\n \"page\": 1,\n \"size\": 1\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering) by\nrepository or permission by adding the following query string\nparameters:\n\n* `q=repository.name=\"geordi\"` or `q=permission>\"read\"`\n* `sort=repository.name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", + "summary": "List repository permissions for a user", "responses": { "200": { "description": "Repository permissions for the repositories a caller has explicit access to.", @@ -16409,8 +15770,6 @@ } } ], - "tags": ["Repositories"], - "summary": "List repository permissions for a user", "security": [ { "oauth2": ["account", "repository"] @@ -16421,65 +15780,15 @@ { "api_key": [] } - ], - "description": "Returns an object for each repository the caller has explicit access\nto and their effective permission — the highest level of permission the\ncaller has. This does not return public repositories that the user was\nnot granted any specific permission in, and does not distinguish between\nexplicit and implicit privileges.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/user/permissions/repositories\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"bitbucket/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"admin\"\n }\n ],\n \"page\": 1,\n \"size\": 1\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering) by\nrepository or permission by adding the following query string\nparameters:\n\n* `q=repository.name=\"geordi\"` or `q=permission>\"read\"`\n* `sort=repository.name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`." - }, - "parameters": [] - }, - "/user/permissions/teams": { - "get": { - "responses": { - "200": { - "description": "Team permissions for the teams a caller is a member of.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_team_permissions" - } - } - } - } - }, - "parameters": [ - { - "name": "q", - "in": "query", - "description": "\nQuery string to narrow down the response as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).", - "required": false, - "schema": { - "type": "string" - } - }, - { - "name": "sort", - "in": "query", - "description": "\nName of a response property sort the result by as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#sorting-query-results).\n", - "required": false, - "schema": { - "type": "string" - } - } - ], - "tags": ["Teams"], - "summary": "List team permissions for the user", - "security": [ - { - "oauth2": ["account"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "Returns an object for each team the caller is a member of, and their\neffective role — the highest level of privilege the caller has. If a\nuser is a member of multiple groups with distinct roles, only the\nhighest level is returned.\n\n**This endpoint has been removed.\nYou should use the [workspace permissions](/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-get) endpoint instead.\nFor more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**\n\nPermissions can be:\n\n* `admin`\n* `collaborator`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/user/permissions/teams\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"permission\": \"admin\",\n \"type\": \"team_permission\",\n \"user\": {\n \"type\": \"user\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\"\n },\n \"team\": {\n \"display_name\": \"Atlassian Bitbucket\",\n \"uuid\": \"{4cc6108a-a241-4db0-96a5-64347ac04f87}\"\n }\n }\n ],\n \"page\": 1,\n \"size\": 1\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering) by\nteam or permission by adding the following query string parameters:\n\n* `q=team.uuid=\"{4cc6108a-a241-4db0-96a5-64347ac04f87}\"` or `q=permission=\"admin\"`\n* `sort=team.display_name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", - "deprecated": true + ] }, "parameters": [] }, "/user/permissions/workspaces": { "get": { + "tags": ["Workspaces"], + "description": "Returns an object for each workspace the caller is a member of, and\ntheir effective role - the highest level of privilege the caller has.\nIf a user is a member of multiple groups with distinct roles, only the\nhighest level is returned.\n\nPermissions can be:\n\n* `owner`\n* `collaborator`\n* `member`\n\n**The `collaborator` role is being removed from the Bitbucket Cloud API. For more information,\nsee the [deprecation announcement](/cloud/bitbucket/deprecation-notice-collaborator-role/).**\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/user/permissions/workspaces\n\n{\n \"pagelen\": 10,\n \"page\": 1,\n \"size\": 1,\n \"values\": [\n {\n \"type\": \"workspace_membership\",\n \"permission\": \"owner\",\n \"last_accessed\": \"2019-03-07T12:35:02.900024+00:00\",\n \"added_on\": \"2018-10-11T17:42:02.961424+00:00\",\n \"user\": {\n \"type\": \"user\",\n \"uuid\": \"{470c176d-3574-44ea-bb41-89e8638bcca4}\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"Erik van Zijst\",\n },\n \"workspace\": {\n \"type\": \"workspace\",\n \"uuid\": \"{a15fb181-db1f-48f7-b41f-e1eff06929d6}\",\n \"slug\": \"bbworkspace1\",\n \"name\": \"Atlassian Bitbucket\",\n }\n }\n ]\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering) by\nworkspace or permission by adding the following query string parameters:\n\n* `q=workspace.slug=\"bbworkspace1\"` or `q=permission=\"owner\"`\n* `sort=workspace.slug`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", + "summary": "List workspaces for the current user", "responses": { "200": { "description": "All of the workspace memberships for the authenticated user.", @@ -16522,8 +15831,6 @@ } } ], - "tags": ["Workspaces"], - "summary": "List workspaces for the current user", "security": [ { "oauth2": ["account"] @@ -16534,20 +15841,22 @@ { "api_key": [] } - ], - "description": "Returns an object for each workspace the caller is a member of, and\ntheir effective role - the highest level of privilege the caller has.\nIf a user is a member of multiple groups with distinct roles, only the\nhighest level is returned.\n\nPermissions can be:\n\n* `owner`\n* `collaborator`\n* `member`\n\n**The `collaborator` role is being removed from the Bitbucket Cloud API. For more information,\nsee the [deprecation announcement](/cloud/bitbucket/deprecation-notice-collaborator-role/).**\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/user/permissions/workspaces\n\n{\n \"pagelen\": 10,\n \"page\": 1,\n \"size\": 1,\n \"values\": [\n {\n \"type\": \"workspace_membership\",\n \"permission\": \"owner\",\n \"last_accessed\": \"2019-03-07T12:35:02.900024+00:00\",\n \"added_on\": \"2018-10-11T17:42:02.961424+00:00\",\n \"user\": {\n \"type\": \"user\",\n \"uuid\": \"{470c176d-3574-44ea-bb41-89e8638bcca4}\",\n \"nickname\": \"evzijst\",\n \"display_name\": \"Erik van Zijst\",\n },\n \"workspace\": {\n \"type\": \"workspace\",\n \"uuid\": \"{a15fb181-db1f-48f7-b41f-e1eff06929d6}\",\n \"slug\": \"bbworkspace1\",\n \"name\": \"Atlassian Bitbucket\",\n }\n }\n ]\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering) by\nworkspace or permission by adding the following query string parameters:\n\n* `q=workspace.slug=\"bbworkspace1\"` or `q=permission=\"owner\"`\n* `sort=workspace.slug`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`." + ] }, "parameters": [] }, "/users/{selected_user}": { "get": { + "tags": ["Users"], + "description": "Gets the public information associated with a user account.\n\nIf the user's profile is private, `location`, `website` and\n`created_on` elements are omitted.\n\nNote that the user object returned by this operation is changing significantly, due to privacy changes.\nSee the [announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-changes-gdpr/#changes-to-bitbucket-user-objects) for details.", + "summary": "Get a user", "responses": { "200": { "description": "The user object", "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/user" + "$ref": "#/components/schemas/account" } } } @@ -16563,8 +15872,6 @@ } } }, - "tags": ["Users"], - "summary": "Get a user", "security": [ { "oauth2": [] @@ -16575,8 +15882,7 @@ { "api_key": [] } - ], - "description": "Gets the public information associated with a user account.\n\nIf the user's profile is private, `location`, `website` and\n`created_on` elements are omitted.\n\nNote that the user object returned by this operation is changing significantly, due to privacy changes.\nSee the [announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-changes-gdpr/#changes-to-bitbucket-user-objects) for details." + ] }, "parameters": [ { @@ -16591,9 +15897,59 @@ ] }, "/users/{selected_user}/pipelines_config/variables/": { + "get": { + "tags": ["Pipelines"], + "deprecated": true, + "summary": "List variables for a user", + "description": "Find user level variables.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).", + "operationId": "getPipelineVariablesForUser", + "parameters": [ + { + "name": "selected_user", + "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The found user level variables.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pipeline_variables" + } + } + } + } + } + }, "post": { + "tags": ["Pipelines"], + "deprecated": true, + "summary": "Create a variable for a user", + "description": "Create a user level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).", + "operationId": "createPipelineVariableForUser", + "parameters": [ + { + "name": "selected_user", + "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/pipeline_variable2" + }, "responses": { "201": { + "description": "The created variable.", "headers": { "Location": { "description": "The URL of the newly created pipeline variable.", @@ -16602,7 +15958,6 @@ } } }, - "description": "The created variable.", "content": { "application/json": { "schema": { @@ -16631,112 +15986,36 @@ } } } - }, - "parameters": [ - { - "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", - "required": true, - "name": "selected_user", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "$ref": "#/components/requestBodies/pipeline_variable2" - }, - "tags": ["Pipelines"], - "deprecated": true, - "summary": "Create a variable for a user", - "operationId": "createPipelineVariableForUser", - "description": "Create a user level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/)." - }, - "get": { - "responses": { - "200": { - "description": "The found user level variables.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_pipeline_variables" - } - } - } - } - }, - "parameters": [ - { - "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", - "required": true, - "name": "selected_user", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Pipelines"], - "deprecated": true, - "summary": "List variables for a user", - "operationId": "getPipelineVariablesForUser", - "description": "Find user level variables.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/)." + } } }, "/users/{selected_user}/pipelines_config/variables/{variable_uuid}": { - "put": { - "responses": { - "200": { - "description": "The variable was updated.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_variable" - } - } - } - }, - "404": { - "description": "The account or the variable was not found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, + "get": { + "tags": ["Pipelines"], + "deprecated": true, + "summary": "Get a variable for a user", + "description": "Retrieve a user level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).", + "operationId": "getPipelineVariableForUser", "parameters": [ { + "name": "selected_user", "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", "required": true, - "name": "selected_user", "in": "path", "schema": { "type": "string" } }, { - "description": "The UUID of the variable.", - "required": true, "name": "variable_uuid", + "description": "The UUID of the variable to retrieve.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "requestBody": { - "$ref": "#/components/requestBodies/pipeline_variable" - }, - "tags": ["Pipelines"], - "deprecated": true, - "summary": "Update a variable for a user", - "operationId": "updatePipelineVariableForUser", - "description": "Update a user level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/)." - }, - "get": { "responses": { "200": { "description": "The variable.", @@ -16758,34 +16037,86 @@ } } } - }, + } + }, + "put": { + "tags": ["Pipelines"], + "deprecated": true, + "summary": "Update a variable for a user", + "description": "Update a user level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).", + "operationId": "updatePipelineVariableForUser", "parameters": [ { + "name": "selected_user", "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", "required": true, - "name": "selected_user", "in": "path", "schema": { "type": "string" } }, { - "description": "The UUID of the variable to retrieve.", - "required": true, "name": "variable_uuid", + "description": "The UUID of the variable.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "deprecated": true, - "summary": "Get a variable for a user", - "operationId": "getPipelineVariableForUser", - "description": "Retrieve a user level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/)." + "requestBody": { + "$ref": "#/components/requestBodies/pipeline_variable" + }, + "responses": { + "200": { + "description": "The variable was updated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + } + }, + "404": { + "description": "The account or the variable was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + } }, "delete": { + "tags": ["Pipelines"], + "deprecated": true, + "summary": "Delete a variable for a user", + "description": "Delete an account level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).", + "operationId": "deletePipelineVariableForUser", + "parameters": [ + { + "name": "selected_user", + "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "variable_uuid", + "description": "The UUID of the variable to delete.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "204": { "description": "The variable was deleted" @@ -16800,32 +16131,7 @@ } } } - }, - "parameters": [ - { - "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", - "required": true, - "name": "selected_user", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The UUID of the variable to delete.", - "required": true, - "name": "variable_uuid", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Pipelines"], - "deprecated": true, - "summary": "Delete a variable for a user", - "operationId": "deletePipelineVariableForUser", - "description": "Delete an account level variable.\nThis endpoint has been deprecated, and you should use the new workspaces endpoint. For more information, see [the announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/)." + } } }, "/users/{selected_user}/properties/{app_key}/{property_name}": { @@ -16835,30 +16141,33 @@ "description": "An empty response." } }, + "operationId": "updateUserHostedPropertyValue", + "summary": "Update a user application property", + "description": "Update an [application property](/cloud/bitbucket/application-properties/) value stored against a user.", "parameters": [ { "required": true, - "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", "in": "path", "name": "selected_user", + "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", "schema": { "type": "string" } }, { "required": true, - "description": "The key of the Connect app.", "in": "path", "name": "app_key", + "description": "The key of the Connect app.", "schema": { "type": "string" } }, { "required": true, - "description": "The name of the property.", "in": "path", "name": "property_name", + "description": "The name of the property.", "schema": { "type": "string" } @@ -16867,10 +16176,7 @@ "requestBody": { "$ref": "#/components/requestBodies/application_property" }, - "tags": ["properties"], - "description": "Update an [application property](/cloud/bitbucket/application-properties/) value stored against a user.", - "summary": "Update a user application property", - "operationId": "updateUserHostedPropertyValue" + "tags": ["properties"] }, "delete": { "responses": { @@ -16878,39 +16184,39 @@ "description": "An empty response." } }, + "operationId": "deleteUserHostedPropertyValue", + "summary": "Delete a user application property", + "description": "Delete an [application property](/cloud/bitbucket/application-properties/) value stored against a user.", "parameters": [ { "required": true, - "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", "in": "path", "name": "selected_user", + "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", "schema": { "type": "string" } }, { "required": true, - "description": "The key of the Connect app.", "in": "path", "name": "app_key", + "description": "The key of the Connect app.", "schema": { "type": "string" } }, { "required": true, - "description": "The name of the property.", "in": "path", "name": "property_name", + "description": "The name of the property.", "schema": { "type": "string" } } ], - "tags": ["properties"], - "description": "Delete an [application property](/cloud/bitbucket/application-properties/) value stored against a user.", - "summary": "Delete a user application property", - "operationId": "deleteUserHostedPropertyValue" + "tags": ["properties"] }, "get": { "responses": { @@ -16925,43 +16231,89 @@ } } }, + "operationId": "retrieveUserHostedPropertyValue", + "summary": "Get a user application property", + "description": "Retrieve an [application property](/cloud/bitbucket/application-properties/) value stored against a user.", "parameters": [ { "required": true, - "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", "in": "path", "name": "selected_user", + "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", "schema": { "type": "string" } }, { "required": true, - "description": "The key of the Connect app.", "in": "path", "name": "app_key", + "description": "The key of the Connect app.", "schema": { "type": "string" } }, { "required": true, - "description": "The name of the property.", "in": "path", "name": "property_name", + "description": "The name of the property.", "schema": { "type": "string" } } ], - "tags": ["properties"], - "description": "Retrieve an [application property](/cloud/bitbucket/application-properties/) value stored against a user.", - "summary": "Get a user application property", - "operationId": "retrieveUserHostedPropertyValue" + "tags": ["properties"] } }, "/users/{selected_user}/search/code": { "get": { + "tags": ["Search"], + "summary": "Search for code in a user's repositories", + "description": "Search for code in the repositories of the specified user.\n\nSearching across all repositories:\n\n```\ncurl 'https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/search/code?search_query=foo'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 2,\n \"content_matches\": [\n {\n \"lines\": [\n {\n \"line\": 2,\n \"segments\": []\n },\n {\n \"line\": 3,\n \"segments\": [\n {\n \"text\": \"def \"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \"():\"\n }\n ]\n },\n {\n \"line\": 4,\n \"segments\": [\n {\n \"text\": \" print(\\\"snek\\\")\"\n }\n ]\n },\n {\n \"line\": 5,\n \"segments\": []\n }\n ]\n }\n ],\n \"path_matches\": [\n {\n \"text\": \"src/\"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \".py\"\n }\n ],\n \"file\": {\n \"path\": \"src/foo.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n }\n }\n }\n ]\n}\n```\n\nNote that searches can match in the file's text (`content_matches`),\nthe path (`path_matches`), or both as in the example above.\n\nYou can use the same syntax for the search query as in the UI, e.g.\nto only search within a specific repository:\n\n```\ncurl 'https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/search/code?search_query=foo+repo:demo'\n# results from the \"demo\" repository\n```\n\nSimilar to other APIs, you can request more fields using a\n`fields` query parameter. E.g. to get some more information about\nthe repository of matched files (the `%2B` is a URL-encoded `+`):\n\n```\ncurl 'https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/search/code'\\\n '?search_query=foo&fields=%2Bvalues.file.commit.repository'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 1,\n \"content_matches\": [...],\n \"path_matches\": [...],\n \"file\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/commit/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo/commits/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n }\n },\n \"repository\": {\n \"name\": \"demo\",\n \"type\": \"repository\",\n \"full_name\": \"my-workspace/demo\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B850e1749-781a-4115-9316-df39d0600e7a%7D?ts=default\"\n }\n },\n \"uuid\": \"{850e1749-781a-4115-9316-df39d0600e7a}\"\n }\n },\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n },\n \"path\": \"src/foo.py\"\n }\n }\n ]\n}\n```\n\nTry `fields=%2Bvalues.*.*.*.*` to get an idea what's possible.\n", + "operationId": "searchAccount", + "parameters": [ + { + "name": "selected_user", + "in": "path", + "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "search_query", + "in": "query", + "description": "The search query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "page", + "in": "query", + "description": "Which page of the search results to retrieve", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "pagelen", + "in": "query", + "description": "How many search results to retrieve per page", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + } + ], "responses": { "200": { "description": "Successful search", @@ -17003,57 +16355,14 @@ } } } - }, - "parameters": [ - { - "required": true, - "description": "Either the UUID of the account surrounded by curly-braces, for example `{account UUID}`, OR an Atlassian Account ID.", - "in": "path", - "name": "selected_user", - "schema": { - "type": "string" - } - }, - { - "required": true, - "description": "The search query", - "in": "query", - "name": "search_query", - "schema": { - "type": "string" - } - }, - { - "description": "Which page of the search results to retrieve", - "required": false, - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "format": "int32", - "default": 1 - } - }, - { - "description": "How many search results to retrieve per page", - "required": false, - "in": "query", - "name": "pagelen", - "schema": { - "type": "integer", - "format": "int32", - "default": 10 - } - } - ], - "tags": ["Search"], - "summary": "Search for code in a user's repositories", - "operationId": "searchAccount", - "description": "Search for code in the repositories of the specified user.\n\nSearching across all repositories:\n\n```\ncurl 'https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/search/code?search_query=foo'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 2,\n \"content_matches\": [\n {\n \"lines\": [\n {\n \"line\": 2,\n \"segments\": []\n },\n {\n \"line\": 3,\n \"segments\": [\n {\n \"text\": \"def \"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \"():\"\n }\n ]\n },\n {\n \"line\": 4,\n \"segments\": [\n {\n \"text\": \" print(\\\"snek\\\")\"\n }\n ]\n },\n {\n \"line\": 5,\n \"segments\": []\n }\n ]\n }\n ],\n \"path_matches\": [\n {\n \"text\": \"src/\"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \".py\"\n }\n ],\n \"file\": {\n \"path\": \"src/foo.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n }\n }\n }\n ]\n}\n```\n\nNote that searches can match in the file's text (`content_matches`),\nthe path (`path_matches`), or both as in the example above.\n\nYou can use the same syntax for the search query as in the UI, e.g.\nto only search within a specific repository:\n\n```\ncurl 'https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/search/code?search_query=foo+repo:demo'\n# results from the \"demo\" repository\n```\n\nSimilar to other APIs, you can request more fields using a\n`fields` query parameter. E.g. to get some more information about\nthe repository of matched files (the `%2B` is a URL-encoded `+`):\n\n```\ncurl 'https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/search/code'\\\n '?search_query=foo&fields=%2Bvalues.file.commit.repository'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 1,\n \"content_matches\": [...],\n \"path_matches\": [...],\n \"file\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/commit/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo/commits/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n }\n },\n \"repository\": {\n \"name\": \"demo\",\n \"type\": \"repository\",\n \"full_name\": \"my-workspace/demo\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B850e1749-781a-4115-9316-df39d0600e7a%7D?ts=default\"\n }\n },\n \"uuid\": \"{850e1749-781a-4115-9316-df39d0600e7a}\"\n }\n },\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n },\n \"path\": \"src/foo.py\"\n }\n }\n ]\n}\n```\n\nTry `fields=%2Bvalues.*.*.*.*` to get an idea what's possible.\n" + } } }, "/users/{selected_user}/ssh-keys": { "get": { + "tags": ["Ssh"], + "description": "Returns a paginated list of the user's SSH public keys.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys\n{\n \"page\": 1,\n \"pagelen\": 10,\n \"size\": 1,\n \"values\": [\n {\n \"comment\": \"user@myhost\",\n \"created_on\": \"2018-03-14T13:17:05.196003+00:00\",\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY\",\n \"label\": \"\",\n \"last_used\": \"2018-03-20T13:18:05.196003+00:00\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a\"\n }\n },\n \"owner\": {\n \"display_name\": \"Mark Adams\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/markadams-atl/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/markadams-atl/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}\"\n }\n },\n \"type\": \"user\",\n \"username\": \"markadams-atl\",\n \"nickname\": \"markadams-atl\",\n \"uuid\": \"{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}\"\n },\n \"type\": \"ssh_key\",\n \"uuid\": \"{b15b6026-9c02-4626-b4ad-b905f99f763a}\"\n }\n ]\n}\n```", + "summary": "List SSH keys", "responses": { "200": { "description": "A list of the SSH keys associated with the account.", @@ -17079,8 +16388,6 @@ } } }, - "tags": ["Ssh"], - "summary": "List SSH keys", "security": [ { "oauth2": ["account"] @@ -17091,10 +16398,12 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of the user's SSH public keys.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys\n{\n \"page\": 1,\n \"pagelen\": 10,\n \"size\": 1,\n \"values\": [\n {\n \"comment\": \"user@myhost\",\n \"created_on\": \"2018-03-14T13:17:05.196003+00:00\",\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY\",\n \"label\": \"\",\n \"last_used\": \"2018-03-20T13:18:05.196003+00:00\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a\"\n }\n },\n \"owner\": {\n \"display_name\": \"Mark Adams\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/markadams-atl/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/markadams-atl/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}\"\n }\n },\n \"type\": \"user\",\n \"username\": \"markadams-atl\",\n \"nickname\": \"markadams-atl\",\n \"uuid\": \"{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}\"\n },\n \"type\": \"ssh_key\",\n \"uuid\": \"{b15b6026-9c02-4626-b4ad-b905f99f763a}\"\n }\n ]\n}\n```" + ] }, "post": { + "tags": ["Ssh"], + "description": "Adds a new SSH public key to the specified user account and returns the resulting key.\n\nExample:\n```\n$ curl -X POST -H \"Content-Type: application/json\" -d '{\"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY user@myhost\"}' https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys\n\n{\n \"comment\": \"user@myhost\",\n \"created_on\": \"2018-03-14T13:17:05.196003+00:00\",\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY\",\n \"label\": \"\",\n \"last_used\": \"2018-03-20T13:18:05.196003+00:00\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a\"\n }\n },\n \"owner\": {\n \"display_name\": \"Mark Adams\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/markadams-atl/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/markadams-atl/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}\"\n }\n },\n \"type\": \"user\",\n \"username\": \"markadams-atl\",\n \"nickname\": \"markadams-atl\",\n \"uuid\": \"{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}\"\n },\n \"type\": \"ssh_key\",\n \"uuid\": \"{b15b6026-9c02-4626-b4ad-b905f99f763a}\"\n}\n```", + "summary": "Add a new SSH key", "responses": { "201": { "description": "The newly created SSH key.", @@ -17140,8 +16449,6 @@ }, "description": "The new SSH key object. Note that the username property has been deprecated due to [privacy changes](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-changes-gdpr/#removal-of-usernames-from-user-referencing-apis)." }, - "tags": ["Ssh"], - "summary": "Add a new SSH key", "security": [ { "oauth2": ["account:write"] @@ -17152,8 +16459,7 @@ { "api_key": [] } - ], - "description": "Adds a new SSH public key to the specified user account and returns the resulting key.\n\nExample:\n```\n$ curl -X POST -H \"Content-Type: application/json\" -d '{\"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY user@myhost\"}' https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys\n\n{\n \"comment\": \"user@myhost\",\n \"created_on\": \"2018-03-14T13:17:05.196003+00:00\",\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY\",\n \"label\": \"\",\n \"last_used\": \"2018-03-20T13:18:05.196003+00:00\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a\"\n }\n },\n \"owner\": {\n \"display_name\": \"Mark Adams\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/markadams-atl/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/markadams-atl/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}\"\n }\n },\n \"type\": \"user\",\n \"username\": \"markadams-atl\",\n \"nickname\": \"markadams-atl\",\n \"uuid\": \"{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}\"\n },\n \"type\": \"ssh_key\",\n \"uuid\": \"{b15b6026-9c02-4626-b4ad-b905f99f763a}\"\n}\n```" + ] }, "parameters": [ { @@ -17169,6 +16475,9 @@ }, "/users/{selected_user}/ssh-keys/{key_id}": { "delete": { + "tags": ["Ssh"], + "description": "Deletes a specific SSH public key from a user's account\n\nExample:\n```\n$ curl -X DELETE https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/{b15b6026-9c02-4626-b4ad-b905f99f763a}\n```", + "summary": "Delete a SSH key", "responses": { "204": { "description": "The key has been deleted" @@ -17197,8 +16506,6 @@ } } }, - "tags": ["Ssh"], - "summary": "Delete a SSH key", "security": [ { "oauth2": ["account:write"] @@ -17209,10 +16516,12 @@ { "api_key": [] } - ], - "description": "Deletes a specific SSH public key from a user's account\n\nExample:\n```\n$ curl -X DELETE https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/{b15b6026-9c02-4626-b4ad-b905f99f763a}\n```" + ] }, "get": { + "tags": ["Ssh"], + "description": "Returns a specific SSH public key belonging to a user.\n\nExample:\n```\n$ curl https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/{fbe4bbab-f6f7-4dde-956b-5c58323c54b3}\n\n{\n \"comment\": \"user@myhost\",\n \"created_on\": \"2018-03-14T13:17:05.196003+00:00\",\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY\",\n \"label\": \"\",\n \"last_used\": \"2018-03-20T13:18:05.196003+00:00\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a\"\n }\n },\n \"owner\": {\n \"display_name\": \"Mark Adams\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/markadams-atl/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/markadams-atl/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}\"\n }\n },\n \"type\": \"user\",\n \"username\": \"markadams-atl\",\n \"nickname\": \"markadams-atl\",\n \"uuid\": \"{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}\"\n },\n \"type\": \"ssh_key\",\n \"uuid\": \"{b15b6026-9c02-4626-b4ad-b905f99f763a}\"\n}\n```", + "summary": "Get a SSH key", "responses": { "200": { "description": "The specific SSH key matching the user and UUID", @@ -17238,8 +16547,6 @@ } } }, - "tags": ["Ssh"], - "summary": "Get a SSH key", "security": [ { "oauth2": ["account"] @@ -17250,10 +16557,12 @@ { "api_key": [] } - ], - "description": "Returns a specific SSH public key belonging to a user.\n\nExample:\n```\n$ curl https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/{fbe4bbab-f6f7-4dde-956b-5c58323c54b3}\n\n{\n \"comment\": \"user@myhost\",\n \"created_on\": \"2018-03-14T13:17:05.196003+00:00\",\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY\",\n \"label\": \"\",\n \"last_used\": \"2018-03-20T13:18:05.196003+00:00\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a\"\n }\n },\n \"owner\": {\n \"display_name\": \"Mark Adams\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/markadams-atl/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/markadams-atl/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}\"\n }\n },\n \"type\": \"user\",\n \"username\": \"markadams-atl\",\n \"nickname\": \"markadams-atl\",\n \"uuid\": \"{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}\"\n },\n \"type\": \"ssh_key\",\n \"uuid\": \"{b15b6026-9c02-4626-b4ad-b905f99f763a}\"\n}\n```" + ] }, "put": { + "tags": ["Ssh"], + "description": "Updates a specific SSH public key on a user's account\n\nNote: Only the 'comment' field can be updated using this API. To modify the key or comment values, you must delete and add the key again.\n\nExample:\n```\n$ curl -X PUT -H \"Content-Type: application/json\" -d '{\"label\": \"Work key\"}' https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/{b15b6026-9c02-4626-b4ad-b905f99f763a}\n\n{\n \"comment\": \"\",\n \"created_on\": \"2018-03-14T13:17:05.196003+00:00\",\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY\",\n \"label\": \"Work key\",\n \"last_used\": \"2018-03-20T13:18:05.196003+00:00\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a\"\n }\n },\n \"owner\": {\n \"display_name\": \"Mark Adams\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/markadams-atl/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/markadams-atl/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}\"\n }\n },\n \"type\": \"user\",\n \"username\": \"markadams-atl\",\n \"nickname\": \"markadams-atl\",\n \"uuid\": \"{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}\"\n },\n \"type\": \"ssh_key\",\n \"uuid\": \"{b15b6026-9c02-4626-b4ad-b905f99f763a}\"\n}\n```", + "summary": "Update a SSH key", "responses": { "200": { "description": "The newly updated SSH key.", @@ -17299,8 +16608,6 @@ }, "description": "The updated SSH key object" }, - "tags": ["Ssh"], - "summary": "Update a SSH key", "security": [ { "oauth2": ["account:write"] @@ -17311,8 +16618,7 @@ { "api_key": [] } - ], - "description": "Updates a specific SSH public key on a user's account\n\nNote: Only the 'comment' field can be updated using this API. To modify the key or comment values, you must delete and add the key again.\n\nExample:\n```\n$ curl -X PUT -H \"Content-Type: application/json\" -d '{\"label\": \"Work key\"}' https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/{b15b6026-9c02-4626-b4ad-b905f99f763a}\n\n{\n \"comment\": \"\",\n \"created_on\": \"2018-03-14T13:17:05.196003+00:00\",\n \"key\": \"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKqP3Cr632C2dNhhgKVcon4ldUSAeKiku2yP9O9/bDtY\",\n \"label\": \"Work key\",\n \"last_used\": \"2018-03-20T13:18:05.196003+00:00\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}/ssh-keys/b15b6026-9c02-4626-b4ad-b905f99f763a\"\n }\n },\n \"owner\": {\n \"display_name\": \"Mark Adams\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/markadams-atl/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/markadams-atl/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/{ed08f5e1-605b-4f4a-aee4-6c97628a673e}\"\n }\n },\n \"type\": \"user\",\n \"username\": \"markadams-atl\",\n \"nickname\": \"markadams-atl\",\n \"uuid\": \"{d7dd0e2d-3994-4a50-a9ee-d260b6cefdab}\"\n },\n \"type\": \"ssh_key\",\n \"uuid\": \"{b15b6026-9c02-4626-b4ad-b905f99f763a}\"\n}\n```" + ] }, "parameters": [ { @@ -17335,102 +16641,11 @@ } ] }, - "/users/{username}/members": { - "get": { - "responses": { - "200": { - "description": "All members", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/user" - } - } - } - }, - "404": { - "description": "When the team does not exist, or multiple teams with the same name exist that differ only in casing and the URL did not match the exact casing of a particular one.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "tags": ["Users"], - "summary": "List team users", - "security": [ - { - "oauth2": ["account"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "**This endpoint has been removed.\nYou should use the [workspaces](/cloud/bitbucket/rest/api-group-workspaces/#api-workspaces-workspace-members-get) endpoint instead.\nFor more information, see [this post](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**", - "deprecated": true - }, - "parameters": [ - { - "name": "username", - "in": "path", - "description": "This can either be the username or the UUID of the account,\nsurrounded by curly-braces, for example: `{account UUID}`. An account\nis either a team or user.\n", - "required": true, - "schema": { - "type": "string" - } - } - ] - }, - "/users/{workspace}/repositories": { - "get": { - "responses": { - "default": { - "description": "Unexpected error.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "tags": ["Users", "Teams"], - "summary": "List workspace repositories", - "security": [ - { - "oauth2": ["repository"] - }, - { - "basic": [] - }, - { - "api_key": [] - } - ], - "description": "All repositories in the given workspace. This includes any private\nrepositories the calling user has access to.\n\n**This endpoint has been removed.\nYou should use the [repository list](/cloud/bitbucket/rest/api-group-repositories/#api-repositories-workspace-get) endpoint instead.\nFor more information, see the [deprecation announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-teams-deprecation/).**", - "deprecated": true - }, - "parameters": [ - { - "name": "workspace", - "in": "path", - "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", - "required": true, - "schema": { - "type": "string" - } - } - ] - }, "/workspaces": { "get": { + "tags": ["Workspaces"], + "description": "Returns a list of workspaces accessible by the authenticated user.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/workspaces\n\n{\n \"pagelen\": 10,\n \"page\": 1,\n \"size\": 1,\n \"values\": [\n {\n \"uuid\": \"{a15fb181-db1f-48f7-b41f-e1eff06929d6}\",\n \"links\": {\n \"owners\": {\n \"href\": \"https://api.bitbucket.org/2.0/workspaces/bbworkspace1/members?q=permission%3D%22owner%22\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/workspaces/bbworkspace1\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bbworkspace1\"\n },\n \"snippets\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/bbworkspace1\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/bbworkspace1/\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/workspaces/bbworkspace1/avatar/?ts=1543465801\"\n },\n \"members\": {\n \"href\": \"https://api.bitbucket.org/2.0/workspaces/bbworkspace1/members\"\n },\n \"projects\": {\n \"href\": \"https://api.bitbucket.org/2.0/workspaces/bbworkspace1/projects\"\n }\n },\n \"created_on\": \"2018-11-14T19:15:05.058566+00:00\",\n \"type\": \"workspace\",\n \"slug\": \"bbworkspace1\",\n \"is_private\": true,\n \"name\": \"Atlassian Bitbucket\"\n }\n ]\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering) by\nworkspace or permission by adding the following query string parameters:\n\n* `q=slug=\"bbworkspace1\"` or `q=is_private=true`\n* `sort=created_on`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.\n\n**The `collaborator` role is being removed from the Bitbucket Cloud API. For more information,\nsee the [deprecation announcement](/cloud/bitbucket/deprecation-notice-collaborator-role/).**", + "summary": "List workspaces for user", "responses": { "200": { "description": "The list of workspaces accessible by the authenticated user.", @@ -17483,8 +16698,6 @@ } } ], - "tags": ["Workspaces"], - "summary": "List workspaces for user", "security": [ { "oauth2": ["account"] @@ -17495,13 +16708,15 @@ { "api_key": [] } - ], - "description": "Returns a list of workspaces accessible by the authenticated user.\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/workspaces\n\n{\n \"pagelen\": 10,\n \"page\": 1,\n \"size\": 1,\n \"values\": [\n {\n \"uuid\": \"{a15fb181-db1f-48f7-b41f-e1eff06929d6}\",\n \"links\": {\n \"owners\": {\n \"href\": \"https://api.bitbucket.org/2.0/workspaces/bbworkspace1/members?q=permission%3D%22owner%22\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/workspaces/bbworkspace1\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/bbworkspace1\"\n },\n \"snippets\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/bbworkspace1\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/bbworkspace1/\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket.org/workspaces/bbworkspace1/avatar/?ts=1543465801\"\n },\n \"members\": {\n \"href\": \"https://api.bitbucket.org/2.0/workspaces/bbworkspace1/members\"\n },\n \"projects\": {\n \"href\": \"https://api.bitbucket.org/2.0/workspaces/bbworkspace1/projects\"\n }\n },\n \"created_on\": \"2018-11-14T19:15:05.058566+00:00\",\n \"type\": \"workspace\",\n \"slug\": \"bbworkspace1\",\n \"is_private\": true,\n \"name\": \"Atlassian Bitbucket\"\n }\n ]\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering) by\nworkspace or permission by adding the following query string parameters:\n\n* `q=slug=\"bbworkspace1\"` or `q=is_private=true`\n* `sort=created_on`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.\n\n**The `collaborator` role is being removed from the Bitbucket Cloud API. For more information,\nsee the [deprecation announcement](/cloud/bitbucket/deprecation-notice-collaborator-role/).**" + ] }, "parameters": [] }, "/workspaces/{workspace}": { "get": { + "tags": ["Workspaces"], + "description": "Returns the requested workspace.", + "summary": "Get a workspace", "responses": { "200": { "description": "The workspace.", @@ -17524,8 +16739,6 @@ } } }, - "tags": ["Workspaces"], - "summary": "Get a workspace", "security": [ { "oauth2": [] @@ -17536,8 +16749,7 @@ { "api_key": [] } - ], - "description": "Returns the requested workspace." + ] }, "parameters": [ { @@ -17553,6 +16765,9 @@ }, "/workspaces/{workspace}/hooks": { "get": { + "tags": ["Workspaces", "Webhooks"], + "description": "Returns a paginated list of webhooks installed on this workspace.", + "summary": "List webhooks for a workspace", "responses": { "200": { "description": "The paginated list of installed webhooks.", @@ -17585,8 +16800,6 @@ } } }, - "tags": ["Workspaces", "Webhooks"], - "summary": "List webhooks for a workspace", "security": [ { "oauth2": ["webhook"] @@ -17597,10 +16810,12 @@ { "api_key": [] } - ], - "description": "Returns a paginated list of webhooks installed on this workspace." + ] }, "post": { + "tags": ["Workspaces", "Webhooks"], + "description": "Creates a new webhook on the specified workspace.\n\nWorkspace webhooks are fired for events from all repositories contained\nby that workspace.\n\nExample:\n\n```\n$ curl -X POST -u credentials -H 'Content-Type: application/json'\n https://api.bitbucket.org/2.0/workspaces/my-workspace/hooks\n -d '\n {\n \"description\": \"Webhook Description\",\n \"url\": \"https://example.com/\",\n \"active\": true,\n \"events\": [\n \"repo:push\",\n \"issue:created\",\n \"issue:updated\"\n ]\n }'\n```\n\nThis call requires the webhook scope, as well as any scope\nthat applies to the events that the webhook subscribes to. In the\nexample above that means: `webhook`, `repository` and `issue`.\n\nThe `url` must properly resolve and cannot be an internal, non-routed address.\n\nOnly workspace owners can install webhooks on workspaces.", + "summary": "Create a webhook for a workspace", "responses": { "201": { "description": "If the webhook was registered successfully.", @@ -17641,8 +16856,6 @@ } } }, - "tags": ["Workspaces", "Webhooks"], - "summary": "Create a webhook for a workspace", "security": [ { "oauth2": ["webhook"] @@ -17653,8 +16866,7 @@ { "api_key": [] } - ], - "description": "Creates a new webhook on the specified workspace.\n\nWorkspace webhooks are fired for events from all repositories contained\nby that workspace.\n\nExample:\n\n```\n$ curl -X POST -u credentials -H 'Content-Type: application/json'\n https://api.bitbucket.org/2.0/workspaces/my-workspace/hooks\n -d '\n {\n \"description\": \"Webhook Description\",\n \"url\": \"https://example.com/\",\n \"active\": true,\n \"events\": [\n \"repo:push\",\n \"issue:created\",\n \"issue:updated\"\n ]\n }'\n```\n\nThis call requires the webhook scope, as well as any scope\nthat applies to the events that the webhook subscribes to. In the\nexample above that means: `webhook`, `repository` and `issue`.\n\nThe `url` must properly resolve and cannot be an internal, non-routed address.\n\nOnly workspace owners can install webhooks on workspaces." + ] }, "parameters": [ { @@ -17670,6 +16882,9 @@ }, "/workspaces/{workspace}/hooks/{uid}": { "delete": { + "tags": ["Workspaces", "Webhooks"], + "description": "Deletes the specified webhook subscription from the given workspace.", + "summary": "Delete a webhook for a workspace", "responses": { "204": { "description": "When the webhook was deleted successfully" @@ -17695,8 +16910,6 @@ } } }, - "tags": ["Workspaces", "Webhooks"], - "summary": "Delete a webhook for a workspace", "security": [ { "oauth2": ["webhook"] @@ -17707,10 +16920,12 @@ { "api_key": [] } - ], - "description": "Deletes the specified webhook subscription from the given workspace." + ] }, "get": { + "tags": ["Workspaces", "Webhooks"], + "description": "Returns the webhook with the specified id installed on the given\nworkspace.", + "summary": "Get a webhook for a workspace", "responses": { "200": { "description": "The webhook subscription object.", @@ -17733,8 +16948,6 @@ } } }, - "tags": ["Workspaces", "Webhooks"], - "summary": "Get a webhook for a workspace", "security": [ { "oauth2": ["webhook"] @@ -17745,10 +16958,12 @@ { "api_key": [] } - ], - "description": "Returns the webhook with the specified id installed on the given\nworkspace." + ] }, "put": { + "tags": ["Workspaces", "Webhooks"], + "description": "Updates the specified webhook subscription.\n\nThe following properties can be mutated:\n\n* `description`\n* `url`\n* `active`\n* `events`", + "summary": "Update a webhook for a workspace", "responses": { "200": { "description": "The webhook subscription object.", @@ -17781,8 +16996,6 @@ } } }, - "tags": ["Workspaces", "Webhooks"], - "summary": "Update a webhook for a workspace", "security": [ { "oauth2": ["webhook"] @@ -17793,8 +17006,7 @@ { "api_key": [] } - ], - "description": "Updates the specified webhook subscription.\n\nThe following properties can be mutated:\n\n* `description`\n* `url`\n* `active`\n* `events`" + ] }, "parameters": [ { @@ -17819,6 +17031,9 @@ }, "/workspaces/{workspace}/members": { "get": { + "tags": ["Workspaces"], + "description": "Returns all members of the requested workspace.", + "summary": "List users in a workspace", "responses": { "200": { "description": "The list of users that are part of a workspace.", @@ -17841,8 +17056,6 @@ } } }, - "tags": ["Workspaces"], - "summary": "List users in a workspace", "security": [ { "oauth2": ["account"] @@ -17853,8 +17066,7 @@ { "api_key": [] } - ], - "description": "Returns all members of the requested workspace." + ] }, "parameters": [ { @@ -17870,6 +17082,9 @@ }, "/workspaces/{workspace}/members/{member}": { "get": { + "tags": ["Workspaces"], + "description": "Returns the workspace membership, which includes\na `User` object for the member and a `Workspace` object\nfor the requested workspace.", + "summary": "Get user membership for a workspace", "responses": { "200": { "description": "The user that is part of a workspace.", @@ -17902,8 +17117,6 @@ } } }, - "tags": ["Workspaces"], - "summary": "Get user membership for a workspace", "security": [ { "oauth2": ["account"] @@ -17914,8 +17127,7 @@ { "api_key": [] } - ], - "description": "Returns the workspace membership, which includes\na `User` object for the member and a `Workspace` object\nfor the requested workspace." + ] }, "parameters": [ { @@ -17940,6 +17152,9 @@ }, "/workspaces/{workspace}/permissions": { "get": { + "tags": ["Workspaces"], + "description": "Returns the list of members in a workspace\nand their permission levels.\nPermission can be:\n* `owner`\n* `collaborator`\n* `member`\n\n**The `collaborator` role is being removed from the Bitbucket Cloud API. For more information,\nsee the [deprecation announcement](/cloud/bitbucket/deprecation-notice-collaborator-role/).**\n\nExample:\n\n```\n$ curl -X https://api.bitbucket.org/2.0/workspaces/bbworkspace1/permissions\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"permission\": \"owner\",\n \"type\": \"workspace_membership\",\n \"user\": {\n \"type\": \"user\",\n \"uuid\": \"{470c176d-3574-44ea-bb41-89e8638bcca4}\",\n \"display_name\": \"Erik van Zijst\",\n },\n \"workspace\": {\n \"type\": \"workspace\",\n \"uuid\": \"{a15fb181-db1f-48f7-b41f-e1eff06929d6}\",\n \"slug\": \"bbworkspace1\",\n \"name\": \"Atlassian Bitbucket\",\n }\n },\n {\n \"permission\": \"member\",\n \"type\": \"workspace_membership\",\n \"user\": {\n \"type\": \"user\",\n \"nickname\": \"seanaty\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n },\n \"workspace\": {\n \"type\": \"workspace\",\n \"uuid\": \"{a15fb181-db1f-48f7-b41f-e1eff06929d6}\",\n \"slug\": \"bbworkspace1\",\n \"name\": \"Atlassian Bitbucket\",\n }\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```\n\nResults may be further [filtered](/cloud/bitbucket/rest/intro/#filtering) by\npermission by adding the following query string parameters:\n\n* `q=permission=\"owner\"`", + "summary": "List user permissions in a workspace", "responses": { "200": { "description": "The list of users that are part of a workspace, along with their permission.", @@ -17973,8 +17188,6 @@ } } ], - "tags": ["Workspaces"], - "summary": "List user permissions in a workspace", "security": [ { "oauth2": ["account"] @@ -17985,8 +17198,7 @@ { "api_key": [] } - ], - "description": "Returns the list of members in a workspace\nand their permission levels.\nPermission can be:\n* `owner`\n* `collaborator`\n* `member`\n\n**The `collaborator` role is being removed from the Bitbucket Cloud API. For more information,\nsee the [deprecation announcement](/cloud/bitbucket/deprecation-notice-collaborator-role/).**\n\nExample:\n\n```\n$ curl -X https://api.bitbucket.org/2.0/workspaces/bbworkspace1/permissions\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"permission\": \"owner\",\n \"type\": \"workspace_membership\",\n \"user\": {\n \"type\": \"user\",\n \"uuid\": \"{470c176d-3574-44ea-bb41-89e8638bcca4}\",\n \"display_name\": \"Erik van Zijst\",\n },\n \"workspace\": {\n \"type\": \"workspace\",\n \"uuid\": \"{a15fb181-db1f-48f7-b41f-e1eff06929d6}\",\n \"slug\": \"bbworkspace1\",\n \"name\": \"Atlassian Bitbucket\",\n }\n },\n {\n \"permission\": \"member\",\n \"type\": \"workspace_membership\",\n \"user\": {\n \"type\": \"user\",\n \"nickname\": \"seanaty\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n },\n \"workspace\": {\n \"type\": \"workspace\",\n \"uuid\": \"{a15fb181-db1f-48f7-b41f-e1eff06929d6}\",\n \"slug\": \"bbworkspace1\",\n \"name\": \"Atlassian Bitbucket\",\n }\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```\n\nResults may be further [filtered](/cloud/bitbucket/rest/intro/#filtering) by\npermission by adding the following query string parameters:\n\n* `q=permission=\"owner\"`" + ] }, "parameters": [ { @@ -18002,6 +17214,9 @@ }, "/workspaces/{workspace}/permissions/repositories": { "get": { + "tags": ["Workspaces"], + "description": "Returns an object for each repository permission for all of a\nworkspace's repositories.\n\nPermissions returned are effective permissions: the highest level of\npermission the user has. This does not distinguish between direct and\nindirect (group) privileges.\n\nOnly users with admin permission for the team may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/permissions/repositories\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"admin\"\n },\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"write\"\n },\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Jeff Zeng\",\n \"uuid\": \"{47f92a9a-c3a3-4d0b-bc4e-782a969c5c72}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"whee\",\n \"full_name\": \"atlassian_tutorial/whee\",\n \"uuid\": \"{30ba25e9-51ff-4555-8dd0-fc7ee2fa0895}\"\n },\n \"permission\": \"admin\"\n }\n ],\n \"page\": 1,\n \"size\": 3\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering)\nby repository, user, or permission by adding the following query string\nparameters:\n\n* `q=repository.name=\"geordi\"` or `q=permission>\"read\"`\n* `sort=user.display_name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", + "summary": "List all repository permissions for a workspace", "responses": { "200": { "description": "List of workspace's repository permissions.", @@ -18044,8 +17259,6 @@ } } ], - "tags": ["Workspaces"], - "summary": "List all repository permissions for a workspace", "security": [ { "oauth2": ["account"] @@ -18056,8 +17269,7 @@ { "api_key": [] } - ], - "description": "Returns an object for each repository permission for all of a\nworkspace's repositories.\n\nPermissions returned are effective permissions: the highest level of\npermission the user has. This does not distinguish between direct and\nindirect (group) privileges.\n\nOnly users with admin permission for the team may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/permissions/repositories\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"admin\"\n },\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"write\"\n },\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Jeff Zeng\",\n \"uuid\": \"{47f92a9a-c3a3-4d0b-bc4e-782a969c5c72}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"whee\",\n \"full_name\": \"atlassian_tutorial/whee\",\n \"uuid\": \"{30ba25e9-51ff-4555-8dd0-fc7ee2fa0895}\"\n },\n \"permission\": \"admin\"\n }\n ],\n \"page\": 1,\n \"size\": 3\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering)\nby repository, user, or permission by adding the following query string\nparameters:\n\n* `q=repository.name=\"geordi\"` or `q=permission>\"read\"`\n* `sort=user.display_name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`." + ] }, "parameters": [ { @@ -18073,6 +17285,9 @@ }, "/workspaces/{workspace}/permissions/repositories/{repo_slug}": { "get": { + "tags": ["Workspaces"], + "description": "Returns an object for the repository permission of each user in the\nrequested repository.\n\nPermissions returned are effective permissions: the highest level of\npermission the user has. This does not distinguish between direct and\nindirect (group) privileges.\n\nOnly users with admin permission for the repository may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/permissions/repositories/geordi\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"admin\"\n },\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"write\"\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering)\nby user, or permission by adding the following query string parameters:\n\n* `q=permission>\"read\"`\n* `sort=user.display_name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`.", + "summary": "List a repository permissions for a workspace", "responses": { "200": { "description": "The repository permission for all users in this repository.", @@ -18115,8 +17330,6 @@ } } ], - "tags": ["Workspaces"], - "summary": "List a repository permissions for a workspace", "security": [ { "oauth2": ["repository"] @@ -18127,8 +17340,7 @@ { "api_key": [] } - ], - "description": "Returns an object for the repository permission of each user in the\nrequested repository.\n\nPermissions returned are effective permissions: the highest level of\npermission the user has. This does not distinguish between direct and\nindirect (group) privileges.\n\nOnly users with admin permission for the repository may access this resource.\n\nPermissions can be:\n\n* `admin`\n* `write`\n* `read`\n\nExample:\n\n```\n$ curl https://api.bitbucket.org/2.0/workspaces/atlassian_tutorial/permissions/repositories/geordi\n\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{d301aafa-d676-4ee0-88be-962be7417567}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"admin\"\n },\n {\n \"type\": \"repository_permission\",\n \"user\": {\n \"type\": \"user\",\n \"display_name\": \"Sean Conaty\",\n \"uuid\": \"{504c3b62-8120-4f0c-a7bc-87800b9d6f70}\"\n },\n \"repository\": {\n \"type\": \"repository\",\n \"name\": \"geordi\",\n \"full_name\": \"atlassian_tutorial/geordi\",\n \"uuid\": \"{85d08b4e-571d-44e9-a507-fa476535aa98}\"\n },\n \"permission\": \"write\"\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```\n\nResults may be further [filtered or sorted](/cloud/bitbucket/rest/intro/#filtering)\nby user, or permission by adding the following query string parameters:\n\n* `q=permission>\"read\"`\n* `sort=user.display_name`\n\nNote that the query parameter values need to be URL escaped so that `=`\nwould become `%3D`." + ] }, "parameters": [ { @@ -18153,6 +17365,21 @@ }, "/workspaces/{workspace}/pipelines-config/identity/oidc/.well-known/openid-configuration": { "get": { + "tags": ["Pipelines"], + "summary": "Get OpenID configuration for OIDC in Pipelines", + "description": "This is part of OpenID Connect for Pipelines, see https://support.atlassian.com/bitbucket-cloud/docs/integrate-pipelines-with-resource-servers-using-oidc/", + "operationId": "getOIDCConfiguration", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "200": { "description": "The OpenID configuration" @@ -18167,26 +17394,26 @@ } } } - }, + } + } + }, + "/workspaces/{workspace}/pipelines-config/identity/oidc/keys.json": { + "get": { + "tags": ["Pipelines"], + "summary": "Get keys for OIDC in Pipelines", + "description": "This is part of OpenID Connect for Pipelines, see https://support.atlassian.com/bitbucket-cloud/docs/integrate-pipelines-with-resource-servers-using-oidc/", + "operationId": "getOIDCKeys", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get OpenID configuration for OIDC in Pipelines", - "operationId": "getOIDCConfiguration", - "description": "This is part of OpenID Connect for Pipelines, see https://support.atlassian.com/bitbucket-cloud/docs/integrate-pipelines-with-resource-servers-using-oidc/" - } - }, - "/workspaces/{workspace}/pipelines-config/identity/oidc/keys.json": { - "get": { "responses": { "200": { "description": "The keys in JSON web key format" @@ -18201,28 +17428,61 @@ } } } - }, + } + } + }, + "/workspaces/{workspace}/pipelines-config/variables": { + "get": { + "tags": ["Pipelines"], + "summary": "List variables for a workspace", + "description": "Find workspace level variables.", + "operationId": "getPipelineVariablesForWorkspace", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get keys for OIDC in Pipelines", - "operationId": "getOIDCKeys", - "description": "This is part of OpenID Connect for Pipelines, see https://support.atlassian.com/bitbucket-cloud/docs/integrate-pipelines-with-resource-servers-using-oidc/" - } - }, - "/workspaces/{workspace}/pipelines-config/variables": { + "responses": { + "200": { + "description": "The found workspace level variables.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pipeline_variables" + } + } + } + } + } + }, "post": { + "tags": ["Pipelines"], + "summary": "Create a variable for a workspace", + "description": "Create a workspace level variable.", + "operationId": "createPipelineVariableForWorkspace", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "$ref": "#/components/requestBodies/pipeline_variable2" + }, "responses": { "201": { + "description": "The created variable.", "headers": { "Location": { "description": "The URL of the newly created pipeline variable.", @@ -18231,7 +17491,6 @@ } } }, - "description": "The created variable.", "content": { "application/json": { "schema": { @@ -18260,109 +17519,35 @@ } } } - }, - "description": "Create a workspace level variable.", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "requestBody": { - "$ref": "#/components/requestBodies/pipeline_variable2" - }, - "tags": ["Pipelines"], - "summary": "Create a variable for a workspace", - "operationId": "createPipelineVariableForWorkspace" - }, - "get": { - "responses": { - "200": { - "description": "The found workspace level variables.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/paginated_pipeline_variables" - } - } - } - } - }, - "description": "Find workspace level variables.", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Pipelines"], - "summary": "List variables for a workspace", - "operationId": "getPipelineVariablesForWorkspace" + } } }, "/workspaces/{workspace}/pipelines-config/variables/{variable_uuid}": { - "put": { - "responses": { - "200": { - "description": "The variable was updated.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/pipeline_variable" - } - } - } - }, - "404": { - "description": "The workspace or the variable was not found.", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/error" - } - } - } - } - }, - "description": "Update a workspace level variable.", + "get": { + "tags": ["Pipelines"], + "summary": "Get variable for a workspace", + "description": "Retrieve a workspace level variable.", + "operationId": "getPipelineVariableForWorkspace", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { - "description": "The UUID of the variable.", - "required": true, "name": "variable_uuid", + "description": "The UUID of the variable to retrieve.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "requestBody": { - "$ref": "#/components/requestBodies/pipeline_variable" - }, - "tags": ["Pipelines"], - "summary": "Update variable for a workspace", - "operationId": "updatePipelineVariableForWorkspace" - }, - "get": { "responses": { "200": { "description": "The variable.", @@ -18384,33 +17569,84 @@ } } } - }, - "description": "Retrieve a workspace level variable.", + } + }, + "put": { + "tags": ["Pipelines"], + "summary": "Update variable for a workspace", + "description": "Update a workspace level variable.", + "operationId": "updatePipelineVariableForWorkspace", "parameters": [ { + "name": "workspace", "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", "required": true, - "name": "workspace", "in": "path", "schema": { "type": "string" } }, { - "description": "The UUID of the variable to retrieve.", - "required": true, "name": "variable_uuid", + "description": "The UUID of the variable.", + "required": true, "in": "path", "schema": { "type": "string" } } ], - "tags": ["Pipelines"], - "summary": "Get variable for a workspace", - "operationId": "getPipelineVariableForWorkspace" + "requestBody": { + "$ref": "#/components/requestBodies/pipeline_variable" + }, + "responses": { + "200": { + "description": "The variable was updated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + } + }, + "404": { + "description": "The workspace or the variable was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + } }, "delete": { + "tags": ["Pipelines"], + "summary": "Delete a variable for a workspace", + "description": "Delete a workspace level variable.", + "operationId": "deletePipelineVariableForWorkspace", + "parameters": [ + { + "name": "workspace", + "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + }, + { + "name": "variable_uuid", + "description": "The UUID of the variable to delete.", + "required": true, + "in": "path", + "schema": { + "type": "string" + } + } + ], "responses": { "204": { "description": "The variable was deleted" @@ -18425,35 +17661,14 @@ } } } - }, - "description": "Delete a workspace level variable.", - "parameters": [ - { - "description": "This can either be the workspace ID (slug) or the workspace UUID surrounded by curly-braces, for example `{workspace UUID}`.", - "required": true, - "name": "workspace", - "in": "path", - "schema": { - "type": "string" - } - }, - { - "description": "The UUID of the variable to delete.", - "required": true, - "name": "variable_uuid", - "in": "path", - "schema": { - "type": "string" - } - } - ], - "tags": ["Pipelines"], - "summary": "Delete a variable for a workspace", - "operationId": "deletePipelineVariableForWorkspace" + } } }, "/workspaces/{workspace}/projects": { "get": { + "tags": ["Workspaces"], + "description": "Returns the list of projects in this workspace.", + "summary": "List projects in a workspace", "responses": { "200": { "description": "The list of projects in this workspace.", @@ -18476,8 +17691,6 @@ } } }, - "tags": ["Workspaces"], - "summary": "List projects in a workspace", "security": [ { "oauth2": ["project"] @@ -18488,10 +17701,12 @@ { "api_key": [] } - ], - "description": "Returns the list of projects in this workspace." + ] }, "post": { + "tags": ["Projects"], + "description": "Creates a new project.\n\nNote that the avatar has to be embedded as either a data-url\nor a URL to an external image as shown in the examples below:\n\n```\n$ body=$(cat << EOF\n{\n \"name\": \"Mars Project\",\n \"key\": \"MARS\",\n \"description\": \"Software for colonizing mars.\",\n \"links\": {\n \"avatar\": {\n \"href\": \"data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/...\"\n }\n },\n \"is_private\": false\n}\nEOF\n)\n$ curl -H \"Content-Type: application/json\" \\\n -X POST \\\n -d \"$body\" \\\n https://api.bitbucket.org/2.0/teams/teams-in-space/projects/ | jq .\n{\n // Serialized project document\n}\n```\n\nor even:\n\n```\n$ body=$(cat << EOF\n{\n \"name\": \"Mars Project\",\n \"key\": \"MARS\",\n \"description\": \"Software for colonizing mars.\",\n \"links\": {\n \"avatar\": {\n \"href\": \"http://i.imgur.com/72tRx4w.gif\"\n }\n },\n \"is_private\": false\n}\nEOF\n)\n$ curl -H \"Content-Type: application/json\" \\\n -X POST \\\n -d \"$body\" \\\n https://api.bitbucket.org/2.0/teams/teams-in-space/projects/ | jq .\n{\n // Serialized project document\n}\n```", + "summary": "Create a project in a workspace", "responses": { "201": { "description": "A new project has been created.", @@ -18535,11 +17750,9 @@ "requestBody": { "$ref": "#/components/requestBodies/project" }, - "tags": ["Projects"], - "summary": "Create a project in a workspace", "security": [ { - "oauth2": ["project:write"] + "oauth2": ["project:admin"] }, { "basic": [] @@ -18547,8 +17760,7 @@ { "api_key": [] } - ], - "description": "Creates a new project.\n\nNote that the avatar has to be embedded as either a data-url\nor a URL to an external image as shown in the examples below:\n\n```\n$ body=$(cat << EOF\n{\n \"name\": \"Mars Project\",\n \"key\": \"MARS\",\n \"description\": \"Software for colonizing mars.\",\n \"links\": {\n \"avatar\": {\n \"href\": \"data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/...\"\n }\n },\n \"is_private\": false\n}\nEOF\n)\n$ curl -H \"Content-Type: application/json\" \\\n -X POST \\\n -d \"$body\" \\\n https://api.bitbucket.org/2.0/teams/teams-in-space/projects/ | jq .\n{\n // Serialized project document\n}\n```\n\nor even:\n\n```\n$ body=$(cat << EOF\n{\n \"name\": \"Mars Project\",\n \"key\": \"MARS\",\n \"description\": \"Software for colonizing mars.\",\n \"links\": {\n \"avatar\": {\n \"href\": \"http://i.imgur.com/72tRx4w.gif\"\n }\n },\n \"is_private\": false\n}\nEOF\n)\n$ curl -H \"Content-Type: application/json\" \\\n -X POST \\\n -d \"$body\" \\\n https://api.bitbucket.org/2.0/teams/teams-in-space/projects/ | jq .\n{\n // Serialized project document\n}\n```" + ] }, "parameters": [ { @@ -18564,6 +17776,9 @@ }, "/workspaces/{workspace}/projects/{project_key}": { "delete": { + "tags": ["Projects"], + "description": "Deletes this project. This is an irreversible operation.\n\nYou cannot delete a project that still contains repositories.\nTo delete the project, [delete](/cloud/bitbucket/rest/api-group-repositories/#api-repositories-workspace-repo-slug-delete)\nor transfer the repositories first.\n\nExample:\n```\n$ curl -X DELETE https://api.bitbucket.org/2.0/bbworkspace1/PROJ\n```", + "summary": "Delete a project for a workspace", "responses": { "204": { "description": "Successful deletion." @@ -18589,11 +17804,9 @@ } } }, - "tags": ["Projects"], - "summary": "Delete a project for a workspace", "security": [ { - "oauth2": ["project:write"] + "oauth2": ["project:admin"] }, { "basic": [] @@ -18601,10 +17814,12 @@ { "api_key": [] } - ], - "description": "Deletes this project. This is an irreversible operation.\n\nYou cannot delete a project that still contains repositories.\nTo delete the project, [delete](/cloud/bitbucket/rest/api-group-repositories/#api-repositories-workspace-repo-slug-delete)\nor transfer the repositories first.\n\nExample:\n```\n$ curl -X DELETE https://api.bitbucket.org/2.0/bbworkspace1/PROJ\n```" + ] }, "get": { + "tags": ["Projects", "Workspaces"], + "description": "Returns the requested project.", + "summary": "Get a project for a workspace", "responses": { "200": { "description": "The project that is part of a workspace.", @@ -18647,8 +17862,6 @@ } } }, - "tags": ["Projects", "Workspaces"], - "summary": "Get a project for a workspace", "security": [ { "oauth2": ["project"] @@ -18659,10 +17872,12 @@ { "api_key": [] } - ], - "description": "Returns the requested project." + ] }, "put": { + "tags": ["Projects"], + "description": "Since this endpoint can be used to both update and to create a\nproject, the request body depends on the intent.\n\n#### Creation\n\nSee the POST documentation for the project collection for an\nexample of the request body.\n\nNote: The `key` should not be specified in the body of request\n(since it is already present in the URL). The `name` is required,\neverything else is optional.\n\n#### Update\n\nSee the POST documentation for the project collection for an\nexample of the request body.\n\nNote: The key is not required in the body (since it is already in\nthe URL). The key may be specified in the body, if the intent is\nto change the key itself. In such a scenario, the location of the\nproject is changed and is returned in the `Location` header of the\nresponse.", + "summary": "Update a project for a workspace", "responses": { "200": { "description": "The existing project is has been updated.", @@ -18724,11 +17939,9 @@ "requestBody": { "$ref": "#/components/requestBodies/project" }, - "tags": ["Projects"], - "summary": "Update a project for a workspace", "security": [ { - "oauth2": ["project:write"] + "oauth2": ["project:admin"] }, { "basic": [] @@ -18736,8 +17949,7 @@ { "api_key": [] } - ], - "description": "Since this endpoint can be used to both update and to create a\nproject, the request body depends on the intent.\n\n#### Creation\n\nSee the POST documentation for the project collection for an\nexample of the request body.\n\nNote: The `key` should not be specified in the body of request\n(since it is already present in the URL). The `name` is required,\neverything else is optional.\n\n#### Update\n\nSee the POST documentation for the project collection for an\nexample of the request body.\n\nNote: The key is not required in the body (since it is already in\nthe URL). The key may be specified in the body, if the intent is\nto change the key itself. In such a scenario, the location of the\nproject is changed and is returned in the `Location` header of the\nresponse." + ] }, "parameters": [ { @@ -18760,8 +17972,793 @@ } ] }, + "/workspaces/{workspace}/projects/{project_key}/branching-model": { + "get": { + "tags": ["Branching model"], + "description": "Return the branching model set at the project level. This view is\nread-only. The branching model settings can be changed using the\n[settings](#api-workspaces-workspace-projects-project-key-branching-model-settings-get)\nAPI.\n\nThe returned object:\n\n1. Always has a `development` property. `development.name` is\n the user-specified branch that can be inherited by an individual repository's\n branching model.\n2. Might have a `production` property. `production` will not\n be present when `production` is disabled.\n `production.name` is the user-specified branch that can be\n inherited by an individual repository's branching model.\n3. Always has a `branch_types` array which contains all enabled branch\n types.\n\nExample body:\n\n```\n{\n \"development\": {\n \"name\": \"master\",\n \"use_mainbranch\": true\n },\n \"production\": {\n \"name\": \"production\",\n \"use_mainbranch\": false\n },\n \"branch_types\": [\n {\n \"kind\": \"release\",\n \"prefix\": \"release/\"\n },\n {\n \"kind\": \"hotfix\",\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"feature\",\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"bugfix\",\n \"prefix\": \"bugfix/\"\n }\n ],\n \"type\": \"project_branching_model\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/.../branching-model\"\n }\n }\n}\n```", + "summary": "Get the branching model for a project", + "responses": { + "200": { + "description": "The branching model object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/project_branching_model" + } + } + } + }, + "401": { + "description": "If the request was not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have read access to the project", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the project does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "parameters": [ + { + "name": "project_key", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/workspaces/{workspace}/projects/{project_key}/branching-model/settings": { + "get": { + "tags": ["Branching model"], + "description": "Return the branching model configuration for a project. The returned\nobject:\n\n1. Always has a `development` property for the development branch.\n2. Always a `production` property for the production branch. The\n production branch can be disabled.\n3. The `branch_types` contains all the branch types.\n\n\nThis is the raw configuration for the branching model. A client\nwishing to see the branching model with its actual current branches may find the\n[active model API](#api-workspaces-workspace-projects-project-key-branching-model-get)\nmore useful.\n\nExample body:\n\n```\n{\n \"development\": {\n \"name\": null,\n \"use_mainbranch\": true\n },\n \"production\": {\n \"name\": \"production\",\n \"use_mainbranch\": false,\n \"enabled\": false\n },\n \"branch_types\": [\n {\n \"kind\": \"release\",\n \"enabled\": true,\n \"prefix\": \"release/\"\n },\n {\n \"kind\": \"hotfix\",\n \"enabled\": true,\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"feature\",\n \"enabled\": true,\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"bugfix\",\n \"enabled\": false,\n \"prefix\": \"bugfix/\"\n }\n ],\n \"type\": \"branching_model_settings\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/.../branching-model/settings\"\n }\n }\n}\n```", + "summary": "Get the branching model config for a project", + "responses": { + "200": { + "description": "The branching model configuration", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/branching_model_settings" + } + } + } + }, + "401": { + "description": "If the request was not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have admin access to the project", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the project does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "put": { + "tags": ["Branching model"], + "description": "Update the branching model configuration for a project.\n\nThe `development` branch can be configured to a specific branch or to\ntrack the main branch. Any branch name can be supplied, but will only\nsuccessfully be applied to a repository via inheritance if that branch\nexists for that repository. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. A request without a\n`development` property will leave the development branch unchanged.\n\nThe `production` branch can be a specific branch, the main\nbranch or disabled. Any branch name can be supplied, but will only\nsuccessfully be applied to a repository via inheritance if that branch\nexists for that repository. The `enabled` property can be used to enable (`true`)\nor disable (`false`) it. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. A request without a\n`production` property will leave the production branch unchanged.\n\nThe `branch_types` property contains the branch types to be updated.\nOnly the branch types passed will be updated. All updates will be\nrejected if it would leave the branching model in an invalid state.\nFor branch types this means that:\n\n1. The prefixes for all enabled branch types are valid. For example,\n it is not possible to use '*' inside a Git prefix.\n2. A prefix of an enabled branch type must not be a prefix of another\n enabled branch type. This is to ensure that a branch can be easily\n classified by its prefix unambiguously.\n\nIt is possible to store an invalid prefix if that branch type would be\nleft disabled. Only the passed properties will be updated. The\nproperties not passed will be left unchanged. Each branch type must\nhave a `kind` property to identify it.\n\nExample Body:\n\n```\n {\n \"development\": {\n \"use_mainbranch\": true\n },\n \"production\": {\n \"enabled\": true,\n \"use_mainbranch\": false,\n \"name\": \"production\"\n },\n \"branch_types\": [\n {\n \"kind\": \"bugfix\",\n \"enabled\": true,\n \"prefix\": \"bugfix/\"\n },\n {\n \"kind\": \"feature\",\n \"enabled\": true,\n \"prefix\": \"feature/\"\n },\n {\n \"kind\": \"hotfix\",\n \"prefix\": \"hotfix/\"\n },\n {\n \"kind\": \"release\",\n \"enabled\": false,\n }\n ]\n }\n```", + "summary": "Update the branching model config for a project", + "responses": { + "200": { + "description": "The updated branching model configuration", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/branching_model_settings" + } + } + } + }, + "400": { + "description": "If the request contains an invalid branching model configuration", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "If the request was not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have admin access to the project", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the project does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "parameters": [ + { + "name": "project_key", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/workspaces/{workspace}/projects/{project_key}/default-reviewers": { + "get": { + "tags": ["Projects"], + "description": "Return a list of all default reviewers for a project. This is a list of users that will be added as default\nreviewers to pull requests for any repository within the project.\n\nExample:\n```\n$ curl https://bitbucket.org/!api/2.0/.../projects/.../default-reviewers | jq .\n{\n \"pagelen\": 10,\n \"values\": [\n {\n \"user\": {\n \"display_name\": \"Davis Lee\",\n \"uuid\": \"{f0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6}\"\n },\n \"reviewer_type\": \"project\",\n \"type\": \"default_reviewer\"\n },\n {\n \"user\": {\n \"display_name\": \"Jorge Rodriguez\",\n \"uuid\": \"{1aa43376-260d-4a0b-9660-f62672b9655d}\"\n },\n \"reviewer_type\": \"project\",\n \"type\": \"default_reviewer\"\n }\n ],\n \"page\": 1,\n \"size\": 2\n}\n```", + "summary": "List the default reviewers in a project", + "responses": { + "200": { + "description": "The list of project default reviewers", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_default_reviewer_and_type" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have admin access to the project", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the workspace or project does not exist at this location", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "parameters": [ + { + "name": "project_key", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/workspaces/{workspace}/projects/{project_key}/default-reviewers/{selected_user}": { + "delete": { + "tags": ["Projects"], + "description": "Removes a default reviewer from the project.\n\nExample:\n```\n$ curl https://bitbucket.org/!api/2.0/.../default-reviewers/%7Bf0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6%7D\n\nHTTP/1.1 204\n```", + "summary": "Remove the specific user from the project's default reviewers", + "responses": { + "204": { + "description": "The specified user was removed from the list of project default reviewers" + }, + "400": { + "description": "If the specified user is not a default reviewer for the project", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have admin access to the project", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the specified user, project, or workspace does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "get": { + "tags": ["Projects"], + "description": "Returns the specified default reviewer.\n\nExample:\n```\n$ curl https://bitbucket.org/!api/2.0/.../default-reviewers/%7Bf0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6%7D\n{\n \"display_name\": \"Davis Lee\",\n \"type\": \"user\",\n \"uuid\": \"{f0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6}\"\n}\n```", + "summary": "Get a default reviewer", + "responses": { + "200": { + "description": "The specified user that is a default reviewer", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + } + }, + "400": { + "description": "If the specified user is not a default reviewer for the project", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have admin access to the project", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the specified user, project, or workspace does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "put": { + "tags": ["Projects"], + "description": "Adds the specified user to the project's list of default reviewers. The method is\nidempotent. Accepts an optional body containing the `uuid` of the user to be added.\n\nExample:\n```\n$ curl -XPUT https://bitbucket.org/!api/2.0/.../default-reviewers/%7Bf0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6%7D\n-d { 'uuid': '{f0e0e8e9-66c1-4b85-a784-44a9eb9ef1a6}' }\n\nHTTP/1.1 204\n```", + "summary": "Add the specific user as a default reviewer for the project", + "responses": { + "204": { + "description": "The specified user was added as a project default reviewer", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + } + }, + "400": { + "description": "If the specified user cannot be added as a default reviewer for the project", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have admin access to the project", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the specified user, project, or workspace does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "parameters": [ + { + "name": "project_key", + "in": "path", + "description": "The project in question. This can either be the actual `key` assigned\nto the project or the `UUID` (surrounded by curly-braces (`{}`)).\n", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "selected_user", + "in": "path", + "description": "This can either be the username or the UUID of the default reviewer,\nsurrounded by curly-braces, for example: `{account UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/workspaces/{workspace}/projects/{project_key}/deploy-keys": { + "get": { + "tags": ["Deployments"], + "description": "Returns all deploy keys belonging to a project.\n\nExample:\n```\n$ curl -H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT/deploy-keys\n\nOutput:\n{\n \"pagelen\":10,\n \"values\":[\n {\n \"comment\":\"thakseth@C02W454JHTD8\",\n \"last_used\":null,\n \"links\":{\n \"self\":{\n \"href\":\"https://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT/deploy-keys/1234\"\n }\n },\n \"label\":\"test\",\n \"project\":{\n \"links\":{\n \"self\":{\n \"href\":\"https://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT\"\n }\n },\n \"type\":\"project\",\n \"name\":\"cooperative standard\",\n \"key\":\"TEST_PROJECT\",\n \"uuid\":\"{3b3e510b-7f2b-414d-a2b7-76c4e405c1c0}\"\n },\n \"created_on\":\"2021-07-28T21:20:19.491721+00:00\",\n \"key\":\"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDX5yfMOEw6HG9jKTYTisbmDTJ4MCUTSVGr5e4OWvY3UuI2A6F8SdzQqa2f5BABA/4g5Sk5awJrYHlNu3EzV1V2I44tR3A4fnZAG71ZKyDPi1wvdO7UYmFgxV/Vd18H9QZFFjICGDM7W0PT2mI0kON/jN3qNWi+GiB/xgaeQKSqynysdysDp8lnnI/8Sh3ikURP9UP83ShRCpAXszOUNaa+UUlcYQYBDLIGowsg51c4PCkC3DNhAMxppkNRKoSOWwyl+oRVXHSDylkiJSBHW3HH4Q6WHieD54kGrjbhWBKdnnxKX7QAAZBDseY+t01N36m6/ljvXSUEcBWtHxBYye0r\",\n \"type\":\"project_deploy_key\",\n \"id\":1234\n }\n ],\n \"page\":1,\n \"size\":1\n}\n```", + "summary": "List project deploy keys", + "responses": { + "200": { + "description": "Deploy keys matching the project", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_project_deploy_keys" + } + } + } + }, + "403": { + "description": "If the specified workspace or project is not accessible to the current user", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the specified workspace or project does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project", "project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "post": { + "tags": ["Deployments"], + "description": "Create a new deploy key in a project.\n\nExample:\n```\n$ curl -XPOST \\\n-H \"Authorization \" \\\n-H \"Content-type: application/json\" \\\nhttps://api.bitbucket.org/!api/2.0/workspaces/jzeng/projects/JZ/deploy-keys/ -d \\\n'{\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5 mleu@C02W454JHTD8\",\n \"label\": \"mydeploykey\"\n}'\n\nOutput:\n{\n \"comment\": \"mleu@C02W454JHTD8\",\n \"last_used\": null,\n \"links\": {\n \"self\": {\n \"href\": \"https://jzeng.devbucket.org/!api/2.0/workspaces/testadfsa/projects/ASDF/deploy-keys/5/\"\n }\n },\n \"label\": \"myprojectkey\",\n \"project\": {\n ...\n },\n \"created_on\": \"2021-08-10T05:28:00.570859+00:00\",\n \"key\": \"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDAK/b1cHHDr/TEV1JGQl+WjCwStKG6Bhrv0rFpEsYlyTBm1fzN0VOJJYn4ZOPCPJwqse6fGbXntEs+BbXiptR+++HycVgl65TMR0b5ul5AgwrVdZdT7qjCOCgaSV74/9xlHDK8oqgGnfA7ZoBBU+qpVyaloSjBdJfLtPY/xqj4yHnXKYzrtn/uFc4Kp9Tb7PUg9Io3qohSTGJGVHnsVblq/rToJG7L5xIo0OxK0SJSQ5vuId93ZuFZrCNMXj8JDHZeSEtjJzpRCBEXHxpOPhAcbm4MzULgkFHhAVgp4JbkrT99/wpvZ7r9AdkTg7HGqL3rlaDrEcWfL7Lu6TnhBdq5\",\n \"type\": \"project_deploy_key\",\n \"id\": 5\n}\n```", + "summary": "Create a project deploy key", + "responses": { + "200": { + "description": "The project deploy key that was created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/project_deploy_key" + } + } + } + }, + "400": { + "description": "Invalid deploy key inputs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the specified workspace or project is not accessible to the current user", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the specified workspace or project does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project", "project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "parameters": [ + { + "name": "project_key", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/workspaces/{workspace}/projects/{project_key}/deploy-keys/{key_id}": { + "delete": { + "tags": ["Deployments"], + "description": "This deletes a deploy key from a project.\n\nExample:\n```\n$ curl -XDELETE \\\n-H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/workspaces/jzeng/projects/JZ/deploy-keys/1234\n```", + "summary": "Delete a deploy key from a project", + "responses": { + "204": { + "description": "The project deploy key has been deleted" + }, + "403": { + "description": "If the current user does not have permission to delete a key for the specified project", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the specified workspace, project, or project deploy key does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project", "project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "get": { + "tags": ["Deployments"], + "description": "Returns the deploy key belonging to a specific key ID.\n\nExample:\n```\n$ curl -H \"Authorization \" \\\nhttps://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT/deploy-keys/1234\n\nOutput:\n{\n \"pagelen\":10,\n \"values\":[\n {\n \"comment\":\"thakseth@C02W454JHTD8\",\n \"last_used\":null,\n \"links\":{\n \"self\":{\n \"href\":\"https://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT/deploy-keys/1234\"\n }\n },\n \"label\":\"test\",\n \"project\":{\n \"links\":{\n \"self\":{\n \"href\":\"https://api.bitbucket.org/2.0/workspaces/standard/projects/TEST_PROJECT\"\n }\n },\n \"type\":\"project\",\n \"name\":\"cooperative standard\",\n \"key\":\"TEST_PROJECT\",\n \"uuid\":\"{3b3e510b-7f2b-414d-a2b7-76c4e405c1c0}\"\n },\n \"created_on\":\"2021-07-28T21:20:19.491721+00:00\",\n \"key\":\"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDX5yfMOEw6HG9jKTYTisbmDTJ4MCUTSVGr5e4OWvY3UuI2A6F8SdzQqa2f5BABA/4g5Sk5awJrYHlNu3EzV1V2I44tR3A4fnZAG71ZKyDPi1wvdO7UYmFgxV/Vd18H9QZFFjICGDM7W0PT2mI0kON/jN3qNWi+GiB/xgaeQKSqynysdysDp8lnnI/8Sh3ikURP9UP83ShRCpAXszOUNaa+UUlcYQYBDLIGowsg51c4PCkC3DNhAMxppkNRKoSOWwyl+oRVXHSDylkiJSBHW3HH4Q6WHieD54kGrjbhWBKdnnxKX7QAAZBDseY+t01N36m6/ljvXSUEcBWtHxBYye0r\",\n \"type\":\"project_deploy_key\",\n \"id\":1234\n }\n ],\n}\n```", + "summary": "Get a project deploy key", + "responses": { + "200": { + "description": "Project deploy key matching the key ID", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/project_deploy_key" + } + } + } + }, + "403": { + "description": "If the specified workspace or project is not accessible to the current user", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the specified workspace or project does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "security": [ + { + "oauth2": ["project", "project:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ] + }, + "parameters": [ + { + "name": "key_id", + "in": "path", + "description": "The key ID matching the project deploy key.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "project_key", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "workspace", + "in": "path", + "description": "This can either be the workspace ID (slug) or the workspace UUID\nsurrounded by curly-braces, for example: `{workspace UUID}`.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, "/workspaces/{workspace}/search/code": { "get": { + "tags": ["Search"], + "summary": "Search for code in a workspace", + "description": "Search for code in the repositories of the specified workspace.\n\nSearching across all repositories:\n\n```\ncurl 'https://api.bitbucket.org/2.0/workspaces/workspace_slug_or_uuid/search/code?search_query=foo'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 2,\n \"content_matches\": [\n {\n \"lines\": [\n {\n \"line\": 2,\n \"segments\": []\n },\n {\n \"line\": 3,\n \"segments\": [\n {\n \"text\": \"def \"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \"():\"\n }\n ]\n },\n {\n \"line\": 4,\n \"segments\": [\n {\n \"text\": \" print(\\\"snek\\\")\"\n }\n ]\n },\n {\n \"line\": 5,\n \"segments\": []\n }\n ]\n }\n ],\n \"path_matches\": [\n {\n \"text\": \"src/\"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \".py\"\n }\n ],\n \"file\": {\n \"path\": \"src/foo.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n }\n }\n }\n ]\n}\n```\n\nNote that searches can match in the file's text (`content_matches`),\nthe path (`path_matches`), or both as in the example above.\n\nYou can use the same syntax for the search query as in the UI, e.g.\nto only search within a specific repository:\n\n```\ncurl 'https://api.bitbucket.org/2.0/workspaces/my-workspace/search/code?search_query=foo+repo:demo'\n# results from the \"demo\" repository\n```\n\nSimilar to other APIs, you can request more fields using a\n`fields` query parameter. E.g. to get some more information about\nthe repository of matched files (the `%2B` is a URL-encoded `+`):\n\n```\ncurl 'https://api.bitbucket.org/2.0/workspaces/my-workspace/search/code'\\\n '?search_query=foo&fields=%2Bvalues.file.commit.repository'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 1,\n \"content_matches\": [...],\n \"path_matches\": [...],\n \"file\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/commit/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo/commits/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n }\n },\n \"repository\": {\n \"name\": \"demo\",\n \"type\": \"repository\",\n \"full_name\": \"my-workspace/demo\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B850e1749-781a-4115-9316-df39d0600e7a%7D?ts=default\"\n }\n },\n \"uuid\": \"{850e1749-781a-4115-9316-df39d0600e7a}\"\n }\n },\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n },\n \"path\": \"src/foo.py\"\n }\n }\n ]\n}\n```\n\nTry `fields=%2Bvalues.*.*.*.*` to get an idea what's possible.\n", + "operationId": "searchWorkspace", + "parameters": [ + { + "name": "workspace", + "in": "path", + "description": "The workspace to search in; either the slug or the UUID in curly braces", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "search_query", + "in": "query", + "description": "The search query", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "page", + "in": "query", + "description": "Which page of the search results to retrieve", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 1 + } + }, + { + "name": "pagelen", + "in": "query", + "description": "How many search results to retrieve per page", + "required": false, + "schema": { + "type": "integer", + "format": "int32", + "default": 10 + } + } + ], "responses": { "200": { "description": "Successful search", @@ -18803,53 +18800,7 @@ } } } - }, - "parameters": [ - { - "required": true, - "description": "The workspace to search in; either the slug or the UUID in curly braces", - "in": "path", - "name": "workspace", - "schema": { - "type": "string" - } - }, - { - "required": true, - "description": "The search query", - "in": "query", - "name": "search_query", - "schema": { - "type": "string" - } - }, - { - "description": "Which page of the search results to retrieve", - "required": false, - "in": "query", - "name": "page", - "schema": { - "type": "integer", - "format": "int32", - "default": 1 - } - }, - { - "description": "How many search results to retrieve per page", - "required": false, - "in": "query", - "name": "pagelen", - "schema": { - "type": "integer", - "format": "int32", - "default": 10 - } - } - ], - "tags": ["Search"], - "summary": "Search for code in a workspace", - "operationId": "searchWorkspace", - "description": "Search for code in the repositories of the specified workspace.\n\nSearching across all repositories:\n\n```\ncurl 'https://api.bitbucket.org/2.0/workspaces/workspace_slug_or_uuid/search/code?search_query=foo'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 2,\n \"content_matches\": [\n {\n \"lines\": [\n {\n \"line\": 2,\n \"segments\": []\n },\n {\n \"line\": 3,\n \"segments\": [\n {\n \"text\": \"def \"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \"():\"\n }\n ]\n },\n {\n \"line\": 4,\n \"segments\": [\n {\n \"text\": \" print(\\\"snek\\\")\"\n }\n ]\n },\n {\n \"line\": 5,\n \"segments\": []\n }\n ]\n }\n ],\n \"path_matches\": [\n {\n \"text\": \"src/\"\n },\n {\n \"text\": \"foo\",\n \"match\": true\n },\n {\n \"text\": \".py\"\n }\n ],\n \"file\": {\n \"path\": \"src/foo.py\",\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n }\n }\n }\n ]\n}\n```\n\nNote that searches can match in the file's text (`content_matches`),\nthe path (`path_matches`), or both as in the example above.\n\nYou can use the same syntax for the search query as in the UI, e.g.\nto only search within a specific repository:\n\n```\ncurl 'https://api.bitbucket.org/2.0/workspaces/my-workspace/search/code?search_query=foo+repo:demo'\n# results from the \"demo\" repository\n```\n\nSimilar to other APIs, you can request more fields using a\n`fields` query parameter. E.g. to get some more information about\nthe repository of matched files (the `%2B` is a URL-encoded `+`):\n\n```\ncurl 'https://api.bitbucket.org/2.0/workspaces/my-workspace/search/code'\\\n '?search_query=foo&fields=%2Bvalues.file.commit.repository'\n{\n \"size\": 1,\n \"page\": 1,\n \"pagelen\": 10,\n \"query_substituted\": false,\n \"values\": [\n {\n \"type\": \"code_search_result\",\n \"content_match_count\": 1,\n \"content_matches\": [...],\n \"path_matches\": [...],\n \"file\": {\n \"commit\": {\n \"type\": \"commit\",\n \"hash\": \"ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/commit/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo/commits/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b\"\n }\n },\n \"repository\": {\n \"name\": \"demo\",\n \"type\": \"repository\",\n \"full_name\": \"my-workspace/demo\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/my-workspace/demo\"\n },\n \"avatar\": {\n \"href\": \"https://bytebucket.org/ravatar/%7B850e1749-781a-4115-9316-df39d0600e7a%7D?ts=default\"\n }\n },\n \"uuid\": \"{850e1749-781a-4115-9316-df39d0600e7a}\"\n }\n },\n \"type\": \"commit_file\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/my-workspace/demo/src/ad6964b5fe2880dbd9ddcad1c89000f1dbcbc24b/src/foo.py\"\n }\n },\n \"path\": \"src/foo.py\"\n }\n }\n ]\n}\n```\n\nTry `fields=%2Bvalues.*.*.*.*` to get an idea what's possible.\n" + } } } }, @@ -18943,64 +18894,64 @@ "description": "A workspace is where you create repositories, collaborate on\nyour code, and organize different streams of work in your Bitbucket\nCloud account. Workspaces replace the use of teams and users in API\ncalls.\n" } ], - "x-revision": "4ec1005c9aa8", + "x-revision": "50c586d353cb", "x-atlassian-narrative": { "documents": [ { - "body": "\nThe purpose of this section is to describe how to authenticate when making API calls using the Bitbucket REST API.\n\n-----\n\n* [Oauth 2](#oauth-2)\n * [Making requests](#making-requests)\n * [Repository cloning](#repository-cloning)\n * [Refresh tokens](#refresh-tokens)\n* [Scopes](#scopes)\n* [Basic auth](#basic-auth)\n* [App passwords](#app-passwords)\n\n---\n\n### OAuth 2.0\n\nOur OAuth 2 implementation is merged in with our existing OAuth 1 in\nsuch a way that existing OAuth 1 consumers automatically become\nvalid OAuth 2 clients. The only thing you need to do is edit your\nexisting consumer and configure a callback URL.\n\nOnce that is in place, you'll have the following 2 URLs:\n\n https://bitbucket.org/site/oauth2/authorize\n https://bitbucket.org/site/oauth2/access_token\n\nFor obtaining access/bearer tokens, we support three of RFC-6749's grant\nflows, plus a custom Bitbucket flow for exchanging JWT tokens for access tokens.\nNote that Resource Owner Password Credentials Grant (4.3) is no longer supported.\n\n\n#### 1. Authorization Code Grant (4.1)\n\nThe full-blown 3-LO flow. Request authorization from the end user by\nsending their browser to:\n\n https://bitbucket.org/site/oauth2/authorize?client_id={client_id}&response_type=code\n\nThe callback includes the `?code={}` query parameter that you can swap\nfor an access token:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=authorization_code -d code={code}\n\n\n#### 2. Implicit Grant (4.2)\n\nThis flow is useful for browser-based add-ons that operate without server-side backends.\n\nRequest the end user for authorization by directing the browser to:\n\n https://bitbucket.org/site/oauth2/authorize?client_id={client_id}&response_type=token\n\nThat will redirect to your preconfigured callback URL with a fragment\ncontaining the access token\n(`#access_token={token}&token_type=bearer`) where your page's js can\npull it out of the URL.\n\n\n#### 3. Client Credentials Grant (4.4)\n\nSomewhat like our existing \"2-LO\" flow for OAuth 1. Obtain an access\ntoken that represents not an end user, but the owner of the\nclient/consumer:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=client_credentials\n\n\n#### 4. Bitbucket Cloud JWT Grant (urn:bitbucket:oauth2:jwt)\n\nIf your Atlassian Connect add-on uses JWT authentication, you can swap a\nJWT for an OAuth access token. The resulting access token represents the\naccount for which the add-on is installed.\n\nMake sure you send the JWT token in the Authorization request header\nusing the \"JWT\" scheme (case sensitive). Note that this custom scheme\nmakes this different from HTTP Basic Auth (and so you cannot use \"curl\n-u\").\n\n $ curl -X POST -H \"Authorization: JWT {jwt_token}\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=urn:bitbucket:oauth2:jwt\n\n\n#### Making Requests\n\nOnce you have an access token, as per RFC-6750, you can use it in a request in any of\nthe following ways (in decreasing order of desirability):\n\n1. Send it in a request header: `Authorization: Bearer {access_token}`\n2. Include it in a (application/x-www-form-urlencoded) POST body as `access_token={access_token}`\n3. Put it in the query string of a non-POST: `?access_token={access_token}`\n\n\n#### Repository Cloning\n\nSince add-ons will not be able to upload their own SSH keys to clone\nwith, access tokens can be used as Basic HTTP Auth credentials to\nclone securely over HTTPS. This is much like GitHub, yet slightly\ndifferent:\n\n $ git clone https://x-token-auth:{access_token}@bitbucket.org/user/repo.git\n\nThe literal string `x-token-auth` as a substitute for username is\nrequired (note the difference with GitHub where the actual token is in\nthe username field).\n\n\n#### Refresh Tokens\n\nOur access tokens expire in one hour. When this happens you'll get 401\nresponses.\n\nMost access tokens grant responses (Implicit and JWT excluded). Therefore, you should include a\nrefresh token that can then be used to generate a new access token,\nwithout the need for end user participation:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=refresh_token -d refresh_token={refresh_token}\n\n\n### Scopes\n\nBitbucket's API applies a number of privilege scopes to endpoints. In order to access an endpoint, a request will need to have the necessary scopes.\n\nScopes are declared in the descriptor as a list of strings, with each string being the name of a unique scope.\n\nA descriptor lacking the `scopes` element is implicitly assumed to require all scopes and as a result, Bitbucket will require end users authorizing/installing the add-on\nto explicitly accept all scopes.\n\nOur best practice suggests you add the scopes your add-on needs, but no more than it needs.\n\nInvalid scope strings will cause the descriptor to be rejected and the installation to fail.\n\nFollowing is the set of all currently available scopes.\n\n#### repository\n\nGives the add-on read access to all the repositories the authorizing user has access to.\nNote that this scope does not give access to a repository's pull requests.\n\n* access to the repo's source code\n* clone over https\n* access the the file browsing API\n* download zip archives of the repo's contents\n* the ability to view and use the issue tracker on any repo (created issues, comment, vote, etc)\n* the ability to view and use the wiki on any repo (create/edit pages)\n\n#### repository:write\n\nGives the add-on write (not admin) access to all the repositories the authorizing user has access to. No distinction is made between public or private repos. This scope implies `repository`, which does not need to be requested separately.\nThis scope alone does not give access to the pull requests API.\n\n* push access over https\n* fork repos\n\n#### repository:admin\n\nGives the add-on admin access to all the repositories the authorizing user has access to. No distinction is made between public or private repos. This scope does not imply `repository` or `repository:write`. It gives access to the admin features of a repo only, not direct access to its contents. Of course it can be (mis)used to grant read access to another user account who can then clone the repo, but repos that need to read of write source code would also request explicit read or write.\nThis scope comes with access to the following functionality:\n\n* view and manipulate committer mappings\n* list and edit deploy keys\n* ability to delete the repo\n* view and edit repo permissions\n* view and edit branch permissions\n* import and export the issue tracker\n* enable and disable the issue tracker\n* list and edit issue tracker version, milestones and components\n* enable and disable the wiki\n* list and edit default reviewers\n* list and edit repo links (Jira/Bamboo/Custom)\n* list and edit the repository web hooks\n* initiate a repo ownership transfer\n\n#### snippet\n\nGives the add-on read access to all the snippets the authorizing user has access to.\nNo distinction is made between public and private snippets (public snippets are accessible without any form of authentication).\n\n* view any snippet\n* create snippet comments\n\n#### snippet:write\n\nGives the add-on write access to all the snippets the authorizing user can edit.\nNo distinction is made between public and private snippets (public snippets are accessible without any form of authentication).\nThis implies the Snippet Read scope which does not need to be requested separately.\n\n* edit snippets\n* delete snippets\n\n#### issue\n\nAbility to interact with issue trackers the way non-repo members can.\nThis scope does not imply any other scopes and does not give implicit access to the repository the issue is attached to.\n\n* view, list and search issues\n* create new issues\n* comment on issues\n* watch issues\n* vote for issues\n\n#### issue:write\n\nThis implies `issue`, but adds the ability to transition and delete issues.\nThis scope does not imply any other scopes and does not give implicit access to the repository the issue is attached to.\n\n* transition issues\n* delete issues\n\n#### wiki\n\nGives access to wikis. No distinction is made between read and write as wikis are always editable by anyone.\nThis scope does not imply any other scopes and does not give implicit access to the repository the wiki is attached to.\n\n* view wikis\n* create pages\n* edit pages\n* push to wikis\n* clone wikis\n\n#### pullrequest\n\nGives the add-on read access to pull requests.\nThis scope implies `repository`, giving read access to the pull request's destination repository.\n\n* see and list pull requests\n* create and resolve tasks\n\n#### pullrequest:write\n\nImplies `pullrequest` but adds the ability to create, merge and decline pull requests.\nThis scope implies `repository:write`, giving write access to the pull request's destination repository. This is necessary to facilitate merging.\n\n* merge pull requests\n* decline pull requests\n* create pull requests\n* comment on pull requests\n* approve pull requests\n\n#### email\n\nAbility to see the user's primary email address. This should make it easier to use Bitbucket Cloud as a login provider to add-ons or external applications.\n\n#### account\n\nAbility to see all the user's account information. Note that this does not include any ability to mutate any of the data.\n\n* see all email addresses\n* language\n* location\n* website\n* full name\n* SSH keys\n* user groups\n\n#### account:write\n\nAbility to change properties on the user's account.\n\n* delete the authorizing user's account\n* manage the user's groups\n* manupilate a user's email addresses\n* change username, display name and avatar\n\n#### team\n\nThe ability to find out what teams the current user is part of. This is covered by the teams endpoint.\n\n* information about all the groups and teams I am a member or admin of\n\n\n#### team:write\n\nImplies `team`, but adds the ability to manage the teams that the authorizing user is an admin on.\n\n* manage team permissions\n\n#### webhook\n\nGives access to webhooks. This scope is required for any webhook\nrelated operation.\n\nThis scope gives read access to existing webhook subscriptions on all\nresources you can access, without needing further scopes. This means that\na client can list all existing webhook subscriptions on repository\n`foo/bar` (assuming the principal user has access to this repo). The\nadditional `repository` scope is not required for this.\n\nLikewise, existing webhook subscriptions for a repo's issue tracker can be\nretrieved without holding the `issue` scope. All that is required is the\n`webhook` scope.\n\nHowever, to create a webhook for `issue:created`, the client will need to\nhave both the `webhook` as well as `issue` scope.\n\n* list webhook subscriptions on any accessible repository, user, team, or snippet\n* create/update/delete webhook subscriptions\n\n### pipeline\n\nGives read-only access to pipelines, steps, deployment environments and variables.\n\n### pipeline:write\n\nGives write access to pipelines. This scope allows a user to:\n* Stop pipelines\n* Rerun failed pipelines\n* Resume halted pipelines\n* Trigger manual pipelines.\n\nThis scope is not needed to trigger a build via a push. The act to doing push will trigger the build. The token doing the push only needs repository:write scope.\n\nThis does not give write access to create variables.\n\n### pipeline:variable\n\nGives write access to create variables in pipelines at the various levels:\n* Workspace\n* Repository\n* Deployment\n\n### runner\n\nGives read-only access to pipelines runners setup against a workspace or repository.\n\n### runner:write\n\nGives write access to create/edit/disable/delete pipelines runners setup against a workspace or repository.\n\n### Basic auth\n\nBasic HTTP Authentication as per [RFC-2617](https://tools.ietf.org/html/rfc2617) (Digest not supported). Note that Basic Auth is available only with username and [app password](https://bitbucket.org/account/settings/app-passwords/) as credentials.\n\n### App passwords\n\nApp passwords allow users to make API calls to their Bitbucket account through apps such as Sourcetree.\n\nSome important points about app passwords:\n\n* You cannot view an app password or adjust permissions after you create the app password. Because app passwords are encrypted on our database and cannot be viewed by anyone. They are essentially designed to be disposable. If you need to change the scopes or lost the password just create a new one.\n* You cannot use them to log into your Bitbucket account.\n* You cannot use app passwords to manage team actions.\n\n App passwords are tied to an individual account's credentials and should not be shared. If you're sharing your app password you're essentially giving direct, authenticated, access to everything that password has been scoped to do with the Bitbucket API's.\n\n* You can use them for API call authentication, even if you don't have two-step verification enabled.\n* You can set permission scopes (specific access rights) for each app password.\n\n#### Create an app password\n\nTo create an app password:\n\n1. Select **Avatar > Bitbucket settings**.\n2. [Click **App passwords** in the Access management section.](https://bitbucket.org/account/settings/app-passwords/)\n3. Click **Create app password**.\n4. Give the app password a name related to the application that will use the password.\n5. Select the specific access and permissions you want this application password to have.\n6. Copy the generated password and either record or paste it into the application you want to give access. The password is only displayed this one time.\n\nThat's all there is to creating an app password. See your applications documentation for how to apply the app password for a specific application.", - "title": "Authentication methods", "anchor": "authentication", + "title": "Authentication methods", "description": "How to authenticate API actions", - "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTcuNjQ3MyAxODYuODEzOCI+CiAgPGRlZnM+CiAgICA8c3R5bGU+CiAgICAgIC5jbHMtMSB7CiAgICAgICAgaXNvbGF0aW9uOiBpc29sYXRlOwogICAgICB9CgogICAgICAuY2xzLTIgewogICAgICAgIGZpbGw6ICNkZTM1MGI7CiAgICAgIH0KCiAgICAgIC5jbHMtMyB7CiAgICAgICAgZmlsbDogI2ZmNTYzMDsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBmaWxsOiAjZGZlMWU1OwogICAgICAgIG1peC1ibGVuZC1tb2RlOiBtdWx0aXBseTsKICAgICAgfQoKICAgICAgLmNscy01IHsKICAgICAgICBmaWxsOiAjZmFmYmZjOwogICAgICB9CgogICAgICAuY2xzLTYgewogICAgICAgIGZpbGw6ICNlYmVjZjA7CiAgICAgIH0KCiAgICAgIC5jbHMtNyB7CiAgICAgICAgZmlsbDogbm9uZTsKICAgICAgICBzdHJva2U6ICMwMDY1ZmY7CiAgICAgICAgc3Ryb2tlLW1pdGVybGltaXQ6IDEwOwogICAgICAgIHN0cm9rZS13aWR0aDogMnB4OwogICAgICB9CgogICAgICAuY2xzLTggewogICAgICAgIGZpbGw6ICM1ZTZjODQ7CiAgICAgIH0KCiAgICAgIC5jbHMtOSB7CiAgICAgICAgZmlsbDogIzI1Mzg1ODsKICAgICAgfQoKICAgICAgLmNscy0xMCB7CiAgICAgICAgZmlsbDogIzI2ODRmZjsKICAgICAgfQoKICAgICAgLmNscy0xMSB7CiAgICAgICAgZmlsbDogIzAwNjVmZjsKICAgICAgfQogICAgPC9zdHlsZT4KICA8L2RlZnM+CiAgPHRpdGxlPlNlY3VyaXR5IHdpdGggS2V5PC90aXRsZT4KICA8ZyBjbGFzcz0iY2xzLTEiPgogICAgPGcgaWQ9IkxheWVyXzIiIGRhdGEtbmFtZT0iTGF5ZXIgMiI+CiAgICAgIDxnIGlkPSJPYmplY3RzIj4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik00Mi4wNjcyLDBoLjYxMTRhOCw4LDAsMCwxLDgsOFYyMy4yMzM4YTAsMCwwLDAsMSwwLDBIMzQuMDY3MmEwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDQyLjA2NzIsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xMDguMjIsMGguNjExNGE4LDgsMCwwLDEsOCw4VjIzLjIzMzhhMCwwLDAsMCwxLDAsMEgxMDAuMjJhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSwxMDguMjIsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xNzQuMzcyMiwwaC42MTE0YTgsOCwwLDAsMSw4LDhWMjMuMjMzOGEwLDAsMCwwLDEsMCwwSDE2Ni4zNzIyYTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMTc0LjM3MjIsMFoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjM0LjA2NzIiIHk9IjIzLjIzMzgiIHdpZHRoPSIxNjMuNTgiIGhlaWdodD0iMTYzLjU4Ii8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNDIuMDY3MiwwSDU5LjI5YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDM0LjA2NzJhMCwwLDAsMCwxLDAsMFY4YTgsOCwwLDAsMSw4LThaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTA3LjI0NTgsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDk5LjI0NThhMCwwLDAsMCwxLDAsMFY4YTgsOCwwLDAsMSw4LThaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTcyLjQyNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDE2NC40MjQ0YTAsMCwwLDAsMSwwLDBWOGE4LDgsMCwwLDEsOC04WiIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMyIgeD0iMTcuNDU1OCIgeT0iMjMuMjMzOCIgd2lkdGg9IjE2My41OCIgaGVpZ2h0PSIxNjMuNTgiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTMiIGQ9Ik0yNS40NTU4LDBINDIuNjc4NmE4LDgsMCwwLDEsOCw4VjIzLjIyMjhhMCwwLDAsMCwxLDAsMEgxNy40NTU4YTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMjUuNDU1OCwwWiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMyIgZD0iTTkwLjYzNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDgyLjYzNDRhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSw5MC42MzQ0LDBaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMTU1LjgxMywwaDE3LjIyMjhhOCw4LDAsMCwxLDgsOFYyMy4yMjI4YTAsMCwwLDAsMSwwLDBIMTQ3LjgxM2EwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDE1NS44MTMsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTMiIGQ9Ik0yNS40NTU4LDBINDIuNjc4NmE4LDgsMCwwLDEsOCw4VjIzLjIyMjhhMCwwLDAsMCwxLDAsMEgxNy40NTU4YTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMjUuNDU1OCwwWiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMyIgZD0iTTkwLjYzNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDgyLjYzNDRhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSw5MC42MzQ0LDBaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMTU1LjgxMywwaDE3LjIyMjhhOCw4LDAsMCwxLDgsOFYyMy4yMjI4YTAsMCwwLDAsMSwwLDBIMTQ3LjgxM2EwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDE1NS44MTMsMFoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjM1Ljc1OTYiIHk9IjU2LjgwNjUiIHdpZHRoPSIzMy4yMjI4IiBoZWlnaHQ9IjE1LjYwMzgiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjEzMS4yMDE2IiB5PSIxMzYuOTYxNSIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTU3LjM3MDksNzEuNjAzNmg3MC43NWE5LDksMCwwLDEsOSw5djM1LjM3NDlhNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLTQ0LjM3NDgsNDQuMzc0OGgwYTQ0LjM3NDgsNDQuMzc0OCwwLDAsMS00NC4zNzQ4LTQ0LjM3NDhWODAuNjAzNkE5LDksMCwwLDEsNTcuMzcwOSw3MS42MDM2WiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNSIgZD0iTTY2LjM3MSw2Ni42NjE3aDcwLjc1YTksOSwwLDAsMSw5LDl2MzUuMzc0OWE0NC4zNzQ4LDQ0LjM3NDgsMCwwLDEtNDQuMzc0OCw0NC4zNzQ4aDBBNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLDU3LjM3MSwxMTEuMDM2NlY3NS42NjE3YTksOSwwLDAsMSw5LTlaIi8+CiAgICAgICAgPHBhdGggaWQ9Il9SZWN0YW5nbGVfIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTYiIGQ9Ik02MS4zNzEsNjYuNjYxN2g3MC43NWE5LDksMCwwLDEsOSw5djM1LjM3NDlhNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLTQ0LjM3NDgsNDQuMzc0OGgwQTQ0LjM3NDgsNDQuMzc0OCwwLDAsMSw1Mi4zNzEsMTExLjAzNjZWNzUuNjYxN0E5LDksMCwwLDEsNjEuMzcxLDY2LjY2MTdaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy03IiBkPSJNOTYuNzQ1OSwxNDcuNzQ0MWEzNi43NDg3LDM2Ljc0ODcsMCwwLDEtMzYuNzA3NC0zNi43MDc0Vjc4LjA1ODRhMy43MzMzLDMuNzMzMywwLDAsMSwzLjcyOS0zLjcyOWg2NS45NTYzYTMuNzMzMywzLjczMzMsMCwwLDEsMy43MjksMy43Mjl2MzIuOTc4NEEzNi43NDg2LDM2Ljc0ODYsMCwwLDEsOTYuNzQ1OSwxNDcuNzQ0MVoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMDAuNjg5MywxNjMuMzE2N1YxMTEuMDk3M2EzLjk0NDMsMy45NDQzLDAsMCwwLTcuODg4NywwdjUyLjIyYTIyLjUyNTIsMjIuNTI1MiwwLDAsMC0xOC41NDc5LDIyLjE0YzAsLjQ1Ni4wMTc4LjkwNzguMDQ0NywxLjM1NzFIODIuMjFjLS4wNDE0LS40NDc0LS4wNjg4LS44OTktLjA2ODgtMS4zNTcxYTE0LjYyLDE0LjYyLDAsMCwxLDE0LjU5NzQtMTQuNjA0MWwuMDA2MS4wMDA2LjAwNjgtLjAwMDdBMTQuNjIxMSwxNC42MjExLDAsMCwxLDExMS4zNSwxODUuNDU2NmMwLC40NTgxLS4wMjczLjkxLS4wNjg4LDEuMzU3MWg3LjkxMjhjLjAyNjktLjQ0OTMuMDQ0Ny0uOTAxMS4wNDQ3LTEuMzU3MUEyMi41MjU5LDIyLjUyNTksMCwwLDAsMTAwLjY4OTMsMTYzLjMxNjdaIi8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxNy40NTU4IiB5PSIzNi40NzAyIiB3aWR0aD0iMzMuMjIyOCIgaGVpZ2h0PSIxNS42MDM4Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxNy40NTU4IiB5PSIxNTguMTIxNyIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMiIgeD0iMTQ3LjgxMyIgeT0iMzYuNDcwMiIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMiIgeD0iMTUwLjA2NDMiIHk9IjE1Ny41NTEzIiB3aWR0aD0iMzMuMjIyOCIgaGVpZ2h0PSIxNS42MDM4Ii8+CiAgICAgICAgPHBhdGggaWQ9Il9QYXRoXyIgZGF0YS1uYW1lPSImbHQ7UGF0aCZndDsiIGNsYXNzPSJjbHMtOCIgZD0iTTEwNy41MjU0LDEwMS4wMDI3YTExLjc3OTQsMTEuNzc5NCwwLDEsMC0xOS44Niw4LjU1NDhBNC4wNDE3LDQuMDQxNywwLDAsMSw4OC44NSwxMTMuNjJsLTIuMTA0LDcuMjY4MWEzLDMsMCwwLDAsMi44ODE3LDMuODM0MmgxMi4yMzcxYTMsMywwLDAsMCwyLjg4MTctMy44MzQybC0yLjA5NTktNy4yNGE0LjA3NDMsNC4wNzQzLDAsMCwxLDEuMTgwOC00LjA5NDVBMTEuNzE3MiwxMS43MTcyLDAsMCwwLDEwNy41MjU0LDEwMS4wMDI3WiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtOSIgZD0iTTEwNC43NDYxLDEyMC44ODc3bC0yLjA5NTktNy4yNGE0LjA3NDQsNC4wNzQ0LDAsMCwxLDEuMTgwOC00LjA5NDUsMTEuNzYyOSwxMS43NjI5LDAsMCwwLTUuMDYtMTkuOTMxMywxMS45MSwxMS45MSwwLDAsMC04Ljc5OCwxMC45OTQ5LDExLjcxODUsMTEuNzE4NSwwLDAsMCwzLjY5MjksOC45NDFBNC4wNDE2LDQuMDQxNiwwLDAsMSw5NC44NSwxMTMuNjJsLTMuMjE0LDExLjEwMjNoMTAuMjI4OEEzLDMsMCwwLDAsMTA0Ljc0NjEsMTIwLjg4NzdaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0xMCIgZD0iTTgxLjc5NzUsMTAwLjMxYTMuOTQzOSwzLjk0MzksMCwwLDAtMy45NDQzLTMuOTQ0M0g0MS4wNDE3YTMuOTQ0MywzLjk0NDMsMCwwLDAsMCw3Ljg4ODdINzcuODUzMkEzLjk0MzksMy45NDM5LDAsMCwwLDgxLjc5NzUsMTAwLjMxWiIvPgogICAgICAgIDxwYXRoIGlkPSJfUGF0aF8yIiBkYXRhLW5hbWU9IiZsdDtQYXRoJmd0OyIgY2xhc3M9ImNscy0xMSIgZD0iTTQxLjA0MTYsMTA0LjI1MzlIOTYuODUzMmEzLjk0NDMsMy45NDQzLDAsMCwwLDAtNy44ODg3SDQxLjA0MTZhMy45NDQzLDMuOTQ0MywwLDAsMCwwLDcuODg4N1oiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTEwIiBkPSJNODEuNzk3NSwxMDAuMzFhMy45NDM5LDMuOTQzOSwwLDAsMC0zLjk0NDMtMy45NDQzSDQxLjA0MTdhMy45NDQzLDMuOTQ0MywwLDAsMCwwLDcuODg4N0g3Ny44NTMyQTMuOTQzOSwzLjk0MzksMCwwLDAsODEuNzk3NSwxMDAuMzFaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0xMCIgZD0iTTIyLjQ5MzIsMTIyLjgwMjlBMjIuNDkyOSwyMi40OTI5LDAsMSwxLDQ0Ljk4NTgsMTAwLjMxLDIyLjUxODUsMjIuNTE4NSwwLDAsMSwyMi40OTMyLDEyMi44MDI5Wm0wLTM3LjA5NzJBMTQuNjA0MiwxNC42MDQyLDAsMSwwLDM3LjA5NzIsMTAwLjMxLDE0LjYyMDcsMTQuNjIwNywwLDAsMCwyMi40OTMyLDg1LjcwNTdaIi8+CiAgICAgIDwvZz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo=" + "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTcuNjQ3MyAxODYuODEzOCI+CiAgPGRlZnM+CiAgICA8c3R5bGU+CiAgICAgIC5jbHMtMSB7CiAgICAgICAgaXNvbGF0aW9uOiBpc29sYXRlOwogICAgICB9CgogICAgICAuY2xzLTIgewogICAgICAgIGZpbGw6ICNkZTM1MGI7CiAgICAgIH0KCiAgICAgIC5jbHMtMyB7CiAgICAgICAgZmlsbDogI2ZmNTYzMDsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBmaWxsOiAjZGZlMWU1OwogICAgICAgIG1peC1ibGVuZC1tb2RlOiBtdWx0aXBseTsKICAgICAgfQoKICAgICAgLmNscy01IHsKICAgICAgICBmaWxsOiAjZmFmYmZjOwogICAgICB9CgogICAgICAuY2xzLTYgewogICAgICAgIGZpbGw6ICNlYmVjZjA7CiAgICAgIH0KCiAgICAgIC5jbHMtNyB7CiAgICAgICAgZmlsbDogbm9uZTsKICAgICAgICBzdHJva2U6ICMwMDY1ZmY7CiAgICAgICAgc3Ryb2tlLW1pdGVybGltaXQ6IDEwOwogICAgICAgIHN0cm9rZS13aWR0aDogMnB4OwogICAgICB9CgogICAgICAuY2xzLTggewogICAgICAgIGZpbGw6ICM1ZTZjODQ7CiAgICAgIH0KCiAgICAgIC5jbHMtOSB7CiAgICAgICAgZmlsbDogIzI1Mzg1ODsKICAgICAgfQoKICAgICAgLmNscy0xMCB7CiAgICAgICAgZmlsbDogIzI2ODRmZjsKICAgICAgfQoKICAgICAgLmNscy0xMSB7CiAgICAgICAgZmlsbDogIzAwNjVmZjsKICAgICAgfQogICAgPC9zdHlsZT4KICA8L2RlZnM+CiAgPHRpdGxlPlNlY3VyaXR5IHdpdGggS2V5PC90aXRsZT4KICA8ZyBjbGFzcz0iY2xzLTEiPgogICAgPGcgaWQ9IkxheWVyXzIiIGRhdGEtbmFtZT0iTGF5ZXIgMiI+CiAgICAgIDxnIGlkPSJPYmplY3RzIj4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik00Mi4wNjcyLDBoLjYxMTRhOCw4LDAsMCwxLDgsOFYyMy4yMzM4YTAsMCwwLDAsMSwwLDBIMzQuMDY3MmEwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDQyLjA2NzIsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xMDguMjIsMGguNjExNGE4LDgsMCwwLDEsOCw4VjIzLjIzMzhhMCwwLDAsMCwxLDAsMEgxMDAuMjJhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSwxMDguMjIsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xNzQuMzcyMiwwaC42MTE0YTgsOCwwLDAsMSw4LDhWMjMuMjMzOGEwLDAsMCwwLDEsMCwwSDE2Ni4zNzIyYTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMTc0LjM3MjIsMFoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjM0LjA2NzIiIHk9IjIzLjIzMzgiIHdpZHRoPSIxNjMuNTgiIGhlaWdodD0iMTYzLjU4Ii8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNNDIuMDY3MiwwSDU5LjI5YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDM0LjA2NzJhMCwwLDAsMCwxLDAsMFY4YTgsOCwwLDAsMSw4LThaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTA3LjI0NTgsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDk5LjI0NThhMCwwLDAsMCwxLDAsMFY4YTgsOCwwLDAsMSw4LThaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTcyLjQyNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDE2NC40MjQ0YTAsMCwwLDAsMSwwLDBWOGE4LDgsMCwwLDEsOC04WiIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMyIgeD0iMTcuNDU1OCIgeT0iMjMuMjMzOCIgd2lkdGg9IjE2My41OCIgaGVpZ2h0PSIxNjMuNTgiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTMiIGQ9Ik0yNS40NTU4LDBINDIuNjc4NmE4LDgsMCwwLDEsOCw4VjIzLjIyMjhhMCwwLDAsMCwxLDAsMEgxNy40NTU4YTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMjUuNDU1OCwwWiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMyIgZD0iTTkwLjYzNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDgyLjYzNDRhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSw5MC42MzQ0LDBaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMTU1LjgxMywwaDE3LjIyMjhhOCw4LDAsMCwxLDgsOFYyMy4yMjI4YTAsMCwwLDAsMSwwLDBIMTQ3LjgxM2EwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDE1NS44MTMsMFoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTMiIGQ9Ik0yNS40NTU4LDBINDIuNjc4NmE4LDgsMCwwLDEsOCw4VjIzLjIyMjhhMCwwLDAsMCwxLDAsMEgxNy40NTU4YTAsMCwwLDAsMSwwLDBWOEE4LDgsMCwwLDEsMjUuNDU1OCwwWiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtMyIgZD0iTTkwLjYzNDQsMGgxNy4yMjI4YTgsOCwwLDAsMSw4LDhWMjMuMjIyOGEwLDAsMCwwLDEsMCwwSDgyLjYzNDRhMCwwLDAsMCwxLDAsMFY4QTgsOCwwLDAsMSw5MC42MzQ0LDBaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMTU1LjgxMywwaDE3LjIyMjhhOCw4LDAsMCwxLDgsOFYyMy4yMjI4YTAsMCwwLDAsMSwwLDBIMTQ3LjgxM2EwLDAsMCwwLDEsMCwwVjhBOCw4LDAsMCwxLDE1NS44MTMsMFoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjM1Ljc1OTYiIHk9IjU2LjgwNjUiIHdpZHRoPSIzMy4yMjI4IiBoZWlnaHQ9IjE1LjYwMzgiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTIiIHg9IjEzMS4yMDE2IiB5PSIxMzYuOTYxNSIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNCIgZD0iTTU3LjM3MDksNzEuNjAzNmg3MC43NWE5LDksMCwwLDEsOSw5djM1LjM3NDlhNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLTQ0LjM3NDgsNDQuMzc0OGgwYTQ0LjM3NDgsNDQuMzc0OCwwLDAsMS00NC4zNzQ4LTQ0LjM3NDhWODAuNjAzNkE5LDksMCwwLDEsNTcuMzcwOSw3MS42MDM2WiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNSIgZD0iTTY2LjM3MSw2Ni42NjE3aDcwLjc1YTksOSwwLDAsMSw5LDl2MzUuMzc0OWE0NC4zNzQ4LDQ0LjM3NDgsMCwwLDEtNDQuMzc0OCw0NC4zNzQ4aDBBNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLDU3LjM3MSwxMTEuMDM2NlY3NS42NjE3YTksOSwwLDAsMSw5LTlaIi8+CiAgICAgICAgPHBhdGggaWQ9Il9SZWN0YW5nbGVfIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTYiIGQ9Ik02MS4zNzEsNjYuNjYxN2g3MC43NWE5LDksMCwwLDEsOSw5djM1LjM3NDlhNDQuMzc0OCw0NC4zNzQ4LDAsMCwxLTQ0LjM3NDgsNDQuMzc0OGgwQTQ0LjM3NDgsNDQuMzc0OCwwLDAsMSw1Mi4zNzEsMTExLjAzNjZWNzUuNjYxN0E5LDksMCwwLDEsNjEuMzcxLDY2LjY2MTdaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy03IiBkPSJNOTYuNzQ1OSwxNDcuNzQ0MWEzNi43NDg3LDM2Ljc0ODcsMCwwLDEtMzYuNzA3NC0zNi43MDc0Vjc4LjA1ODRhMy43MzMzLDMuNzMzMywwLDAsMSwzLjcyOS0zLjcyOWg2NS45NTYzYTMuNzMzMywzLjczMzMsMCwwLDEsMy43MjksMy43Mjl2MzIuOTc4NEEzNi43NDg2LDM2Ljc0ODYsMCwwLDEsOTYuNzQ1OSwxNDcuNzQ0MVoiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMDAuNjg5MywxNjMuMzE2N1YxMTEuMDk3M2EzLjk0NDMsMy45NDQzLDAsMCwwLTcuODg4NywwdjUyLjIyYTIyLjUyNTIsMjIuNTI1MiwwLDAsMC0xOC41NDc5LDIyLjE0YzAsLjQ1Ni4wMTc4LjkwNzguMDQ0NywxLjM1NzFIODIuMjFjLS4wNDE0LS40NDc0LS4wNjg4LS44OTktLjA2ODgtMS4zNTcxYTE0LjYyLDE0LjYyLDAsMCwxLDE0LjU5NzQtMTQuNjA0MWwuMDA2MS4wMDA2LjAwNjgtLjAwMDdBMTQuNjIxMSwxNC42MjExLDAsMCwxLDExMS4zNSwxODUuNDU2NmMwLC40NTgxLS4wMjczLjkxLS4wNjg4LDEuMzU3MWg3LjkxMjhjLjAyNjktLjQ0OTMuMDQ0Ny0uOTAxMS4wNDQ3LTEuMzU3MUEyMi41MjU5LDIyLjUyNTksMCwwLDAsMTAwLjY4OTMsMTYzLjMxNjdaIi8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxNy40NTU4IiB5PSIzNi40NzAyIiB3aWR0aD0iMzMuMjIyOCIgaGVpZ2h0PSIxNS42MDM4Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxNy40NTU4IiB5PSIxNTguMTIxNyIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMiIgeD0iMTQ3LjgxMyIgeT0iMzYuNDcwMiIgd2lkdGg9IjMzLjIyMjgiIGhlaWdodD0iMTUuNjAzOCIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMiIgeD0iMTUwLjA2NDMiIHk9IjE1Ny41NTEzIiB3aWR0aD0iMzMuMjIyOCIgaGVpZ2h0PSIxNS42MDM4Ii8+CiAgICAgICAgPHBhdGggaWQ9Il9QYXRoXyIgZGF0YS1uYW1lPSImbHQ7UGF0aCZndDsiIGNsYXNzPSJjbHMtOCIgZD0iTTEwNy41MjU0LDEwMS4wMDI3YTExLjc3OTQsMTEuNzc5NCwwLDEsMC0xOS44Niw4LjU1NDhBNC4wNDE3LDQuMDQxNywwLDAsMSw4OC44NSwxMTMuNjJsLTIuMTA0LDcuMjY4MWEzLDMsMCwwLDAsMi44ODE3LDMuODM0MmgxMi4yMzcxYTMsMywwLDAsMCwyLjg4MTctMy44MzQybC0yLjA5NTktNy4yNGE0LjA3NDMsNC4wNzQzLDAsMCwxLDEuMTgwOC00LjA5NDVBMTEuNzE3MiwxMS43MTcyLDAsMCwwLDEwNy41MjU0LDEwMS4wMDI3WiIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtOSIgZD0iTTEwNC43NDYxLDEyMC44ODc3bC0yLjA5NTktNy4yNGE0LjA3NDQsNC4wNzQ0LDAsMCwxLDEuMTgwOC00LjA5NDUsMTEuNzYyOSwxMS43NjI5LDAsMCwwLTUuMDYtMTkuOTMxMywxMS45MSwxMS45MSwwLDAsMC04Ljc5OCwxMC45OTQ5LDExLjcxODUsMTEuNzE4NSwwLDAsMCwzLjY5MjksOC45NDFBNC4wNDE2LDQuMDQxNiwwLDAsMSw5NC44NSwxMTMuNjJsLTMuMjE0LDExLjEwMjNoMTAuMjI4OEEzLDMsMCwwLDAsMTA0Ljc0NjEsMTIwLjg4NzdaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0xMCIgZD0iTTgxLjc5NzUsMTAwLjMxYTMuOTQzOSwzLjk0MzksMCwwLDAtMy45NDQzLTMuOTQ0M0g0MS4wNDE3YTMuOTQ0MywzLjk0NDMsMCwwLDAsMCw3Ljg4ODdINzcuODUzMkEzLjk0MzksMy45NDM5LDAsMCwwLDgxLjc5NzUsMTAwLjMxWiIvPgogICAgICAgIDxwYXRoIGlkPSJfUGF0aF8yIiBkYXRhLW5hbWU9IiZsdDtQYXRoJmd0OyIgY2xhc3M9ImNscy0xMSIgZD0iTTQxLjA0MTYsMTA0LjI1MzlIOTYuODUzMmEzLjk0NDMsMy45NDQzLDAsMCwwLDAtNy44ODg3SDQxLjA0MTZhMy45NDQzLDMuOTQ0MywwLDAsMCwwLDcuODg4N1oiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTEwIiBkPSJNODEuNzk3NSwxMDAuMzFhMy45NDM5LDMuOTQzOSwwLDAsMC0zLjk0NDMtMy45NDQzSDQxLjA0MTdhMy45NDQzLDMuOTQ0MywwLDAsMCwwLDcuODg4N0g3Ny44NTMyQTMuOTQzOSwzLjk0MzksMCwwLDAsODEuNzk3NSwxMDAuMzFaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0xMCIgZD0iTTIyLjQ5MzIsMTIyLjgwMjlBMjIuNDkyOSwyMi40OTI5LDAsMSwxLDQ0Ljk4NTgsMTAwLjMxLDIyLjUxODUsMjIuNTE4NSwwLDAsMSwyMi40OTMyLDEyMi44MDI5Wm0wLTM3LjA5NzJBMTQuNjA0MiwxNC42MDQyLDAsMSwwLDM3LjA5NzIsMTAwLjMxLDE0LjYyMDcsMTQuNjIwNywwLDAsMCwyMi40OTMyLDg1LjcwNTdaIi8+CiAgICAgIDwvZz4KICAgIDwvZz4KICA8L2c+Cjwvc3ZnPgo='", + "body": "\nThe purpose of this section is to describe how to authenticate when making API calls using the Bitbucket REST API.\n\n-----\n\n* [Oauth 2](#oauth-2)\n * [Making requests](#making-requests)\n * [Repository cloning](#repository-cloning)\n * [Refresh tokens](#refresh-tokens)\n* [Scopes](#scopes)\n* [Basic auth](#basic-auth)\n* [App passwords](#app-passwords)\n\n---\n\n### OAuth 2.0\n\nOur OAuth 2 implementation is merged in with our existing OAuth 1 in\nsuch a way that existing OAuth 1 consumers automatically become\nvalid OAuth 2 clients. The only thing you need to do is edit your\nexisting consumer and configure a callback URL.\n\nOnce that is in place, you'll have the following 2 URLs:\n\n https://bitbucket.org/site/oauth2/authorize\n https://bitbucket.org/site/oauth2/access_token\n\nFor obtaining access/bearer tokens, we support three of RFC-6749's grant\nflows, plus a custom Bitbucket flow for exchanging JWT tokens for access tokens.\nNote that Resource Owner Password Credentials Grant (4.3) is no longer supported.\n\n\n#### 1. Authorization Code Grant (4.1)\n\nThe full-blown 3-LO flow. Request authorization from the end user by\nsending their browser to:\n\n https://bitbucket.org/site/oauth2/authorize?client_id={client_id}&response_type=code\n\nThe callback includes the `?code={}` query parameter that you can swap\nfor an access token:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=authorization_code -d code={code}\n\n\n#### 2. Implicit Grant (4.2)\n\nThis flow is useful for browser-based add-ons that operate without server-side backends.\n\nRequest the end user for authorization by directing the browser to:\n\n https://bitbucket.org/site/oauth2/authorize?client_id={client_id}&response_type=token\n\nThat will redirect to your preconfigured callback URL with a fragment\ncontaining the access token\n(`#access_token={token}&token_type=bearer`) where your page's js can\npull it out of the URL.\n\n\n#### 3. Client Credentials Grant (4.4)\n\nSomewhat like our existing \"2-LO\" flow for OAuth 1. Obtain an access\ntoken that represents not an end user, but the owner of the\nclient/consumer:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=client_credentials\n\n\n#### 4. Bitbucket Cloud JWT Grant (urn:bitbucket:oauth2:jwt)\n\nIf your Atlassian Connect add-on uses JWT authentication, you can swap a\nJWT for an OAuth access token. The resulting access token represents the\naccount for which the add-on is installed.\n\nMake sure you send the JWT token in the Authorization request header\nusing the \"JWT\" scheme (case sensitive). Note that this custom scheme\nmakes this different from HTTP Basic Auth (and so you cannot use \"curl\n-u\").\n\n $ curl -X POST -H \"Authorization: JWT {jwt_token}\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=urn:bitbucket:oauth2:jwt\n\n\n#### Making Requests\n\nOnce you have an access token, as per RFC-6750, you can use it in a request in any of\nthe following ways (in decreasing order of desirability):\n\n1. Send it in a request header: `Authorization: Bearer {access_token}`\n2. Include it in a (application/x-www-form-urlencoded) POST body as `access_token={access_token}`\n3. Put it in the query string of a non-POST: `?access_token={access_token}`\n\n\n#### Repository Cloning\n\nSince add-ons will not be able to upload their own SSH keys to clone\nwith, access tokens can be used as Basic HTTP Auth credentials to\nclone securely over HTTPS. This is much like GitHub, yet slightly\ndifferent:\n\n $ git clone https://x-token-auth:{access_token}@bitbucket.org/user/repo.git\n\nThe literal string `x-token-auth` as a substitute for username is\nrequired (note the difference with GitHub where the actual token is in\nthe username field).\n\n\n#### Refresh Tokens\n\nOur access tokens expire in one hour. When this happens you'll get 401\nresponses.\n\nMost access tokens grant responses (Implicit and JWT excluded). Therefore, you should include a\nrefresh token that can then be used to generate a new access token,\nwithout the need for end user participation:\n\n $ curl -X POST -u \"client_id:secret\" \\\n https://bitbucket.org/site/oauth2/access_token \\\n -d grant_type=refresh_token -d refresh_token={refresh_token}\n\n\n### Scopes\n\nBitbucket's API applies a number of privilege scopes to endpoints. In order to access an endpoint, a request will need to have the necessary scopes.\n\nScopes are declared in the descriptor as a list of strings, with each string being the name of a unique scope.\n\nA descriptor lacking the `scopes` element is implicitly assumed to require all scopes and as a result, Bitbucket will require end users authorizing/installing the add-on\nto explicitly accept all scopes.\n\nOur best practice suggests you add the scopes your add-on needs, but no more than it needs.\n\nInvalid scope strings will cause the descriptor to be rejected and the installation to fail.\n\nFollowing is the set of all currently available scopes.\n\n#### repository\n\nGives the add-on read access to all the repositories the authorizing user has access to.\nNote that this scope does not give access to a repository's pull requests.\n\n* access to the repo's source code\n* clone over https\n* access the the file browsing API\n* download zip archives of the repo's contents\n* the ability to view and use the issue tracker on any repo (created issues, comment, vote, etc)\n* the ability to view and use the wiki on any repo (create/edit pages)\n\n#### repository:write\n\nGives the add-on write (not admin) access to all the repositories the authorizing user has access to. No distinction is made between public or private repos. This scope implies `repository`, which does not need to be requested separately.\nThis scope alone does not give access to the pull requests API.\n\n* push access over https\n* fork repos\n\n#### repository:admin\n\nGives the add-on admin access to all the repositories the authorizing user has access to. No distinction is made between public or private repos. This scope does not imply `repository` or `repository:write`. It gives access to the admin features of a repo only, not direct access to its contents. Of course it can be (mis)used to grant read access to another user account who can then clone the repo, but repos that need to read of write source code would also request explicit read or write.\nThis scope comes with access to the following functionality:\n\n* view and manipulate committer mappings\n* list and edit deploy keys\n* ability to delete the repo\n* view and edit repo permissions\n* view and edit branch permissions\n* import and export the issue tracker\n* enable and disable the issue tracker\n* list and edit issue tracker version, milestones and components\n* enable and disable the wiki\n* list and edit default reviewers\n* list and edit repo links (Jira/Bamboo/Custom)\n* list and edit the repository web hooks\n* initiate a repo ownership transfer\n\n#### snippet\n\nGives the add-on read access to all the snippets the authorizing user has access to.\nNo distinction is made between public and private snippets (public snippets are accessible without any form of authentication).\n\n* view any snippet\n* create snippet comments\n\n#### snippet:write\n\nGives the add-on write access to all the snippets the authorizing user can edit.\nNo distinction is made between public and private snippets (public snippets are accessible without any form of authentication).\nThis implies the Snippet Read scope which does not need to be requested separately.\n\n* edit snippets\n* delete snippets\n\n#### issue\n\nAbility to interact with issue trackers the way non-repo members can.\nThis scope does not imply any other scopes and does not give implicit access to the repository the issue is attached to.\n\n* view, list and search issues\n* create new issues\n* comment on issues\n* watch issues\n* vote for issues\n\n#### issue:write\n\nThis implies `issue`, but adds the ability to transition and delete issues.\nThis scope does not imply any other scopes and does not give implicit access to the repository the issue is attached to.\n\n* transition issues\n* delete issues\n\n#### wiki\n\nGives access to wikis. No distinction is made between read and write as wikis are always editable by anyone.\nThis scope does not imply any other scopes and does not give implicit access to the repository the wiki is attached to.\n\n* view wikis\n* create pages\n* edit pages\n* push to wikis\n* clone wikis\n\n#### pullrequest\n\nGives the add-on read access to pull requests.\nThis scope implies `repository`, giving read access to the pull request's destination repository.\n\n* see and list pull requests\n* create and resolve tasks\n* comment on pull requests\n\n#### pullrequest:write\n\nImplies `pullrequest` but adds the ability to create, merge and decline pull requests.\nThis scope implies `repository:write`, giving write access to the pull request's destination repository. This is necessary to facilitate merging.\n\n* merge pull requests\n* decline pull requests\n* create pull requests\n* approve pull requests\n\n#### project\n\nGives the app `repository` scope permissions for every repository under every project that the authorizing user has read access to.\n\n#### project:write\n\nThis scope is deprecated, and has been made obsolete by `project:admin`. Please see the deprecation notice [here](/cloud/bitbucket/deprecation-notice-project-write-scope).\n\n#### project:admin\n\nGives the app admin access to all the projects the authorizing user has access to. No distinction is made between public or private projects. This scope does not imply `project`, or `repository:write` on any repositories under the project. It gives access to the admin features of a project only, not direct access to its repositories' contents.\n\n* ability to create the project\n* ability to update the project\n* ability to delete the project\n\n#### email\n\nAbility to see the user's primary email address. This should make it easier to use Bitbucket Cloud as a login provider to add-ons or external applications.\n\n#### account\n\nAbility to see all the user's account information. Note that this does not include any ability to mutate any of the data.\n\n* see all email addresses\n* language\n* location\n* website\n* full name\n* SSH keys\n* user groups\n\n#### account:write\n\nAbility to change properties on the user's account.\n\n* delete the authorizing user's account\n* manage the user's groups\n* manupilate a user's email addresses\n* change username, display name and avatar\n\n#### webhook\n\nGives access to webhooks. This scope is required for any webhook\nrelated operation.\n\nThis scope gives read access to existing webhook subscriptions on all\nresources you can access, without needing further scopes. This means that\na client can list all existing webhook subscriptions on repository\n`foo/bar` (assuming the principal user has access to this repo). The\nadditional `repository` scope is not required for this.\n\nLikewise, existing webhook subscriptions for a repo's issue tracker can be\nretrieved without holding the `issue` scope. All that is required is the\n`webhook` scope.\n\nHowever, to create a webhook for `issue:created`, the client will need to\nhave both the `webhook` as well as `issue` scope.\n\n* list webhook subscriptions on any accessible repository, user, team, or snippet\n* create/update/delete webhook subscriptions\n\n#### pipeline\n\nGives read-only access to pipelines, steps, deployment environments and variables.\n\n#### pipeline:write\n\nGives write access to pipelines. This scope allows a user to:\n* Stop pipelines\n* Rerun failed pipelines\n* Resume halted pipelines\n* Trigger manual pipelines.\n\nThis scope is not needed to trigger a build via a push. The act to doing push will trigger the build. The token doing the push only needs repository:write scope.\n\nThis does not give write access to create variables.\n\n#### pipeline:variable\n\nGives write access to create variables in pipelines at the various levels:\n* Workspace\n* Repository\n* Deployment\n\n#### runner\n\nGives read-only access to pipelines runners setup against a workspace or repository.\n\n#### runner:write\n\nGives write access to create/edit/disable/delete pipelines runners setup against a workspace or repository.\n\n### Basic auth\n\nBasic HTTP Authentication as per [RFC-2617](https://tools.ietf.org/html/rfc2617) (Digest not supported). Note that Basic Auth is available only with username and [app password](https://bitbucket.org/account/settings/app-passwords/) as credentials.\n\n### App passwords\n\nApp passwords allow users to make API calls to their Bitbucket account through apps such as Sourcetree.\n\nSome important points about app passwords:\n\n* You cannot view an app password or adjust permissions after you create the app password. Because app passwords are encrypted on our database and cannot be viewed by anyone. They are essentially designed to be disposable. If you need to change the scopes or lost the password just create a new one.\n* You cannot use them to log into your Bitbucket account.\n* You cannot use app passwords to manage team actions.\n\n App passwords are tied to an individual account's credentials and should not be shared. If you're sharing your app password you're essentially giving direct, authenticated, access to everything that password has been scoped to do with the Bitbucket API's.\n\n* You can use them for API call authentication, even if you don't have two-step verification enabled.\n* You can set permission scopes (specific access rights) for each app password.\n\n#### Create an app password\n\nTo create an app password:\n\n1. Select **Avatar > Bitbucket settings**.\n2. [Click **App passwords** in the Access management section.](https://bitbucket.org/account/settings/app-passwords/)\n3. Click **Create app password**.\n4. Give the app password a name related to the application that will use the password.\n5. Select the specific access and permissions you want this application password to have.\n6. Copy the generated password and either record or paste it into the application you want to give access. The password is only displayed this one time.\n\nThat's all there is to creating an app password. See your applications documentation for how to apply the app password for a specific application." }, { - "body": "\nYou can query the 2.0 API for specific objects using a simple language which resembles SQL.\n\nNote that filtering and querying by username has been deprecated, due to privacy changes. \nSee the [announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-changes-gdpr/#changes-to-querying) \nfor details.\n\n---\n\n* [Supported endpoints](#supported-endpoints)\n* [Operators](#operators)\n* [Data types](#data-types)\n* [Querying](#querying)\n* [Sorting query results](#sorting-query-results)\n\n----\n\n### Supported endpoints\n\nMost 2.0 API resources that return paginated collections of objects support a single, shared, generic querying language that is used to filter down a result set.\n\nThis includes, but is in no way limited to:\n\n /2.0/repositories/{username}\n /2.0/repositories/{username}/{slug}/refs\n /2.0/repositories/{username}/{slug}/refs/branches\n /2.0/repositories/{username}/{slug}/refs/tags\n /2.0/repositories/{username}/{slug}/forks\n /2.0/repositories/{username}/{slug}/src\n /2.0/repositories/{username}/{slug}/issues\n /2.0/repositories/{username}/{slug}/pullrequests\n\nFiltering and sorting supports several distinct operators and data types as well as basic features, like logical operators (AND, OR).\nAs examples, the following queries could be used on the issue tracker endpoint (`/2.0/repositories/{workspace}/{slug}/issues/`):\n\n\t(state = \"open\" OR state = \"new\") AND assignee = null\n\treporter.nickname != \"evzijst\" AND priority >= \"major\"\n\t(title ~ \"unicode\" OR content.raw ~ \"unicode\") AND created_on > 2015-10-04T14:00:00-07:00\n\nFilter queries can be added to the URL using the q= query parameter. To sort the response, add sort=. Note that the entire query string is put in the q parameter and hence needs to be URL-encoded as shown in the following example:\n\n\t/2.0/repositories/foo/bar/issues?q=state=\"new\"&sort=-updated_on\n\n\n### Operators\n\nFiltering and sorting supports the following operators:\n\n| Operator | Definition | Example |\n|----------|--------------------------------|----------------------------|\n| \"=\" | test for equality | `nickname = \"evzijst\"` |\n| \"!=\" | not equal | `is_private != true` |\n| \"~\" | case-insensitive text contains | `description ~ \"beef\"` |\n| \"!~\" | case-insensitive not contains | `description !~ \"fubar\"` |\n| \">\" | greater than | `priority > \"major\"` |\n| \">=\" | greater than or equal | `priority <= \"trivial\"` |\n| \"<\" | less than | `id < 1234` |\n| \"<=\" | less than or equal | `updated_on <= 2015-03-04` |\n\n### Data types\n\nFiltering and sorting supports the following data types:\n\n| Type | Description | Example |\n|--------------|-----------------------------------------|---------------|\n| **String** | any text inside double quotes | `\"foo\"` |\n| **Number** | arbitrary precision integers and floats | `1, -10.302` |\n| **Null** | to test for the absence of a value | `null` |\n| **boolean** | the unquoted strings true or false | `true, false` |\n| **datetime** | an unquoted [ISO-8601][iso-8601] date time string with the timezone offset, milliseconds and entire time component being optional | `2015-03-04T14:08:59.123+02:00`, `2015-03-04T14:08:59` Date time strings are assumed to be in UTC, unless an explicit timezone offset is provided |\n\n[https://en.wikipedia.org/wiki/ISO_8601]: /iso-8601\n\n### Querying\n\nObjects can be filtered based on their properties. In principle, every element in an object's JSON document schema can be used as a filter criterion.\n\nNote that while the array of objects in a paginated response is wrapped in an\nenvelope with a `values` element, this prefix should not be included in the\nquery fields (so use `/2.0/repositories/foo/bar/issues?q=state=\"new\"`, not\n`/2.0/repositories/foo/bar/issues?q=values.state=\"new\"`).\n\n\n### Examples\n\nFields that contain embedded instances of other object types (e.g. owner is an embedded user object, while parent is an embedded repository) can be traversed recursively. For instance:\n\n\tparent.owner.nickname = \"bitbucket\"\n\nTo find pull requests which merge into master, come from a fork of the repo rather than a branch inside the repo, and on which I am a reviewer:\n\n```\nsource.repository.full_name != \"main/repo\" AND state = \"OPEN\" AND reviewers.nickname = \"evzijst\" AND destination.branch.name = \"master\"\n```\n```\n/2.0/repositories/main/repo/pullrequests?q=source.repository.full_name+%21%3D+%22main%2Frepo%22+AND+state+%3D+%22OPEN%22+AND+reviewers.nickname+%3D+%22evzijst%22+AND+destination.branch.name+%3D+%22master%22\n```\n\nTo find new or on-hold issues related to the UI, created or updated in the last day (SF local time), that have not yet been assigned to anyone:\n\n```\n(state = \"new\" OR state = \"on hold\") AND assignee = null AND component = \"UI\" and updated_on > 2015-11-11T00:00:00-07:00\n```\n```\n/2.0/repositories/main/repo/issues?q=%28state+%3D+%22new%22+OR+state+%3D+%22on+hold%22%29+AND+assignee+%3D+null+AND+component+%3D+%22UI%22+and+updated_on+%3E+2015-11-11T00%3A00%3A00-07%3A00\n```\n\nTo find all tags with the string \"2015\" in the name:\n\n```\nname ~ \"2015\"\n```\n```\n/2.0/repositories/{username}/{slug}/refs/tags?q=name+%7E+%222015%22\n```\nOr all my branches:\n\n```\nname ~ \"erik/\"\n```\n```\n/2.0/repositories/{username}/{slug}/refs/?q=name+%7E+%22erik%2F%22\n```\n### Sorting query results\n\nYou can sort result sets using the ?sort= query parameter, available on the same resources that support filtering:\n\n* In principle, every field that can be queried can also be used as a key for sorting.\n* By default the sort order is ascending. To reverse the order, prefix the field name with a hyphen (e.g. ?sort=-updated_on).\n* Only one field can be sorted on. Compound fields (e.g. sort on state first, followed by updated_on) are not supported.\n\n\n", - "title": "Filter and sort API objects", "anchor": "filtering", + "title": "Filter and sort API objects", "description": "Query the 2.0 API for specific objects", - "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTk0LjE5MTkgMTQ3LjYwOTIiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGlzb2xhdGlvbjogaXNvbGF0ZTsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBmaWxsOiAjY2ZkNGRiOwogICAgICB9CgogICAgICAuY2xzLTMsIC5jbHMtNCB7CiAgICAgICAgZmlsbDogIzg3NzdkOTsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBtaXgtYmxlbmQtbW9kZTogbXVsdGlwbHk7CiAgICAgIH0KCiAgICAgIC5jbHMtNSB7CiAgICAgICAgZmlsbDogIzAwNjVmZjsKICAgICAgfQoKICAgICAgLmNscy02IHsKICAgICAgICBmaWxsOiAjY2NlMGZmOwogICAgICB9CgogICAgICAuY2xzLTcgewogICAgICAgIGZpbGw6IHVybCgjbGluZWFyLWdyYWRpZW50KTsKICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB4MT0iNDE2LjMwODIiIHkxPSI3NS4wNDc5IiB4Mj0iNTg0Ljg1NTYiIHkyPSI3NS4wNDc5IiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC00NDMuOTQ2NyAxMjMuMDY4Nikgcm90YXRlKC0xMy43OTc2KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmZmYiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIwLjY5MDgiIHN0b3AtY29sb3I9IiNmZmYiIHN0b3Atb3BhY2l0eT0iMC4xIi8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogIDwvZGVmcz4KICA8dGl0bGU+TWFnbmlmeWluZyBHbGFzczwvdGl0bGU+CiAgPGcgY2xhc3M9ImNscy0xIj4KICAgIDxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPgogICAgICA8ZyBpZD0iT2JqZWN0cyI+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTMxLjExMjUsOTQuOTMwN2wtOS44ODc4LTUuOTg4OC04LjMyOTIsMTMuNzUxOSw5Ljg4NzgsNS45ODg4YTE1LjYwMywxNS42MDMsMCwwLDEsNS44OCw2LjM4MzVoMGExNS42MDMsMTUuNjAzLDAsMCwwLDUuODgsNi4zODM1bDQwLjExNDMsMjQuMjk2NGExMi44NjY0LDEyLjg2NjQsMCwwLDAsMTcuNjcwOS00LjM0aDBhMTIuODY2NCwxMi44NjY0LDAsMCwwLTQuMzQtMTcuNjcwOUwxNDcuODc0OSw5OS40MzkyYTE1LjYwMywxNS42MDMsMCwwLDAtOC4zODEyLTIuMjU0MmgwQTE1LjYwMywxNS42MDMsMCwwLDEsMTMxLjExMjUsOTQuOTMwN1oiLz4KICAgICAgICA8cGF0aCBpZD0iX1BhdGhfIiBkYXRhLW5hbWU9IiZsdDtQYXRoJmd0OyIgY2xhc3M9ImNscy0zIiBkPSJNMTMxLjExMjUsOTQuOTMwN2wtMy4wMTE4LTEuODI0MkE4LjAzODgsOC4wMzg4LDAsMCwwLDExNy4wNiw5NS44MTc4aDBhOC4wMzg4LDguMDM4OCwwLDAsMCwyLjcxMTMsMTEuMDQwNWwzLjAxMTgsMS44MjQyYTE1LjYwMywxNS42MDMsMCwwLDEsNS44OCw2LjM4MzVoMGExNS42MDMsMTUuNjAzLDAsMCwwLDUuODgsNi4zODM1bDQwLjExNDMsMjQuMjk2NGExMi44NjY0LDEyLjg2NjQsMCwwLDAsMTcuNjcwOS00LjM0aDBhMTIuODY2NCwxMi44NjY0LDAsMCwwLTQuMzQtMTcuNjcwOUwxNDcuODc0OSw5OS40MzkyYTE1LjYwMywxNS42MDMsMCwwLDAtOC4zODEyLTIuMjU0M2gwQTE1LjYwMywxNS42MDMsMCwwLDEsMTMxLjExMjUsOTQuOTMwN1oiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMzkuMTQzNyw5Ny4xNzkyYTE1LjU5NzMsMTUuNTk3MywwLDAsMS04LjAzMTItMi4yNDg1bC0zLjAxMTgtMS44MjQyQTguMDM4OCw4LjAzODgsMCwwLDAsMTE3LjA2LDk1LjgxNzhoMGE4LjAzODgsOC4wMzg4LDAsMCwwLDIuNzExMywxMS4wNDA1bDMuMDExOCwxLjgyNDJhMTUuNTk3LDE1LjU5NywwLDAsMSw1LjcwNjksNi4wNjQ4LDY3Ljg0ODEsNjcuODQ4MSwwLDAsMCwxMC42NTM2LTE3LjU2ODFaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy01IiBkPSJNODMuMjUzNywxMzIuNTU2QTY3LjIzNDgsNjcuMjM0OCwwLDAsMSw5LjcxLDMyLjQyOTUsNjYuNzk3NCw2Ni43OTc0LDAsMCwxLDUxLjE4MzcsMS45NjY2bC4wMDA3LDBBNjYuNzk2Miw2Ni43OTYyLDAsMCwxLDEwMi4wNTEsOS43NTI1aDBBNjcuMjM0Niw2Ny4yMzQ2LDAsMCwxLDgzLjI1MzcsMTMyLjU1NloiLz4KICAgICAgICA8cGF0aCBpZD0iX1BhdGhfMiIgZGF0YS1uYW1lPSImbHQ7UGF0aCZndDsiIGNsYXNzPSJjbHMtNiIgZD0iTTIzLjQzOSw0MC43NDgyQTUxLjE5MDgsNTEuMTkwOCwwLDAsMCwxMTEuMDEsOTMuNzg4OSw1MS4xOTA4LDUxLjE5MDgsMCwwLDAsMjMuNDM5LDQwLjc0ODJaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy03IiBkPSJNNzkuNDMzLDExNi45ODJBNTEuMjE2Miw1MS4yMTYyLDAsMCwwLDExOC40MjQxLDY3LjAzN2E0OS4xMzkxLDQ5LjEzOTEsMCwwLDEtNS4wODY3LDIuMjc4OWMtMTUuNzAyOSw1Ljk2MDktMjkuNjg5NSwyLjExLTM2LjQ5ODcuMTMwOC0yMC40MzA3LTUuOTM5LTI0Ljc5LTE3LjM3ODUtMzkuMDQxNC0yNC41ODIzYTQ4LjMwOTIsNDguMzA5MiwwLDAsMC0xNC4wOTM5LTQuNTNjLS4wODYyLjEzOTUtLjE3OTMuMjczLS4yNjQ0LjQxMzVBNTEuMTkwNyw1MS4xOTA3LDAsMCwwLDc5LjQzMywxMTYuOTgyWiIvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K" + "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTk0LjE5MTkgMTQ3LjYwOTIiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGlzb2xhdGlvbjogaXNvbGF0ZTsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBmaWxsOiAjY2ZkNGRiOwogICAgICB9CgogICAgICAuY2xzLTMsIC5jbHMtNCB7CiAgICAgICAgZmlsbDogIzg3NzdkOTsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBtaXgtYmxlbmQtbW9kZTogbXVsdGlwbHk7CiAgICAgIH0KCiAgICAgIC5jbHMtNSB7CiAgICAgICAgZmlsbDogIzAwNjVmZjsKICAgICAgfQoKICAgICAgLmNscy02IHsKICAgICAgICBmaWxsOiAjY2NlMGZmOwogICAgICB9CgogICAgICAuY2xzLTcgewogICAgICAgIGZpbGw6IHVybCgjbGluZWFyLWdyYWRpZW50KTsKICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB4MT0iNDE2LjMwODIiIHkxPSI3NS4wNDc5IiB4Mj0iNTg0Ljg1NTYiIHkyPSI3NS4wNDc5IiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC00NDMuOTQ2NyAxMjMuMDY4Nikgcm90YXRlKC0xMy43OTc2KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmZmYiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIwLjY5MDgiIHN0b3AtY29sb3I9IiNmZmYiIHN0b3Atb3BhY2l0eT0iMC4xIi8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogIDwvZGVmcz4KICA8dGl0bGU+TWFnbmlmeWluZyBHbGFzczwvdGl0bGU+CiAgPGcgY2xhc3M9ImNscy0xIj4KICAgIDxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPgogICAgICA8ZyBpZD0iT2JqZWN0cyI+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTMxLjExMjUsOTQuOTMwN2wtOS44ODc4LTUuOTg4OC04LjMyOTIsMTMuNzUxOSw5Ljg4NzgsNS45ODg4YTE1LjYwMywxNS42MDMsMCwwLDEsNS44OCw2LjM4MzVoMGExNS42MDMsMTUuNjAzLDAsMCwwLDUuODgsNi4zODM1bDQwLjExNDMsMjQuMjk2NGExMi44NjY0LDEyLjg2NjQsMCwwLDAsMTcuNjcwOS00LjM0aDBhMTIuODY2NCwxMi44NjY0LDAsMCwwLTQuMzQtMTcuNjcwOUwxNDcuODc0OSw5OS40MzkyYTE1LjYwMywxNS42MDMsMCwwLDAtOC4zODEyLTIuMjU0MmgwQTE1LjYwMywxNS42MDMsMCwwLDEsMTMxLjExMjUsOTQuOTMwN1oiLz4KICAgICAgICA8cGF0aCBpZD0iX1BhdGhfIiBkYXRhLW5hbWU9IiZsdDtQYXRoJmd0OyIgY2xhc3M9ImNscy0zIiBkPSJNMTMxLjExMjUsOTQuOTMwN2wtMy4wMTE4LTEuODI0MkE4LjAzODgsOC4wMzg4LDAsMCwwLDExNy4wNiw5NS44MTc4aDBhOC4wMzg4LDguMDM4OCwwLDAsMCwyLjcxMTMsMTEuMDQwNWwzLjAxMTgsMS44MjQyYTE1LjYwMywxNS42MDMsMCwwLDEsNS44OCw2LjM4MzVoMGExNS42MDMsMTUuNjAzLDAsMCwwLDUuODgsNi4zODM1bDQwLjExNDMsMjQuMjk2NGExMi44NjY0LDEyLjg2NjQsMCwwLDAsMTcuNjcwOS00LjM0aDBhMTIuODY2NCwxMi44NjY0LDAsMCwwLTQuMzQtMTcuNjcwOUwxNDcuODc0OSw5OS40MzkyYTE1LjYwMywxNS42MDMsMCwwLDAtOC4zODEyLTIuMjU0M2gwQTE1LjYwMywxNS42MDMsMCwwLDEsMTMxLjExMjUsOTQuOTMwN1oiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMzkuMTQzNyw5Ny4xNzkyYTE1LjU5NzMsMTUuNTk3MywwLDAsMS04LjAzMTItMi4yNDg1bC0zLjAxMTgtMS44MjQyQTguMDM4OCw4LjAzODgsMCwwLDAsMTE3LjA2LDk1LjgxNzhoMGE4LjAzODgsOC4wMzg4LDAsMCwwLDIuNzExMywxMS4wNDA1bDMuMDExOCwxLjgyNDJhMTUuNTk3LDE1LjU5NywwLDAsMSw1LjcwNjksNi4wNjQ4LDY3Ljg0ODEsNjcuODQ4MSwwLDAsMCwxMC42NTM2LTE3LjU2ODFaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy01IiBkPSJNODMuMjUzNywxMzIuNTU2QTY3LjIzNDgsNjcuMjM0OCwwLDAsMSw5LjcxLDMyLjQyOTUsNjYuNzk3NCw2Ni43OTc0LDAsMCwxLDUxLjE4MzcsMS45NjY2bC4wMDA3LDBBNjYuNzk2Miw2Ni43OTYyLDAsMCwxLDEwMi4wNTEsOS43NTI1aDBBNjcuMjM0Niw2Ny4yMzQ2LDAsMCwxLDgzLjI1MzcsMTMyLjU1NloiLz4KICAgICAgICA8cGF0aCBpZD0iX1BhdGhfMiIgZGF0YS1uYW1lPSImbHQ7UGF0aCZndDsiIGNsYXNzPSJjbHMtNiIgZD0iTTIzLjQzOSw0MC43NDgyQTUxLjE5MDgsNTEuMTkwOCwwLDAsMCwxMTEuMDEsOTMuNzg4OSw1MS4xOTA4LDUxLjE5MDgsMCwwLDAsMjMuNDM5LDQwLjc0ODJaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy03IiBkPSJNNzkuNDMzLDExNi45ODJBNTEuMjE2Miw1MS4yMTYyLDAsMCwwLDExOC40MjQxLDY3LjAzN2E0OS4xMzkxLDQ5LjEzOTEsMCwwLDEtNS4wODY3LDIuMjc4OWMtMTUuNzAyOSw1Ljk2MDktMjkuNjg5NSwyLjExLTM2LjQ5ODcuMTMwOC0yMC40MzA3LTUuOTM5LTI0Ljc5LTE3LjM3ODUtMzkuMDQxNC0yNC41ODIzYTQ4LjMwOTIsNDguMzA5MiwwLDAsMC0xNC4wOTM5LTQuNTNjLS4wODYyLjEzOTUtLjE3OTMuMjczLS4yNjQ0LjQxMzVBNTEuMTkwNyw1MS4xOTA3LDAsMCwwLDc5LjQzMywxMTYuOTgyWiIvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K'", + "body": "\nYou can query the 2.0 API for specific objects using a simple language which resembles SQL.\n\nNote that filtering and querying by username has been deprecated, due to privacy changes. \nSee the [announcement](https://developer.atlassian.com/cloud/bitbucket/bitbucket-api-changes-gdpr/#changes-to-querying) \nfor details.\n\n---\n\n* [Supported endpoints](#supported-endpoints)\n* [Operators](#operators)\n* [Data types](#data-types)\n* [Querying](#querying)\n* [Sorting query results](#sorting-query-results)\n\n----\n\n### Supported endpoints\n\nMost 2.0 API resources that return paginated collections of objects support a single, shared, generic querying language that is used to filter down a result set.\n\nThis includes, but is in no way limited to:\n\n /2.0/repositories/{username}\n /2.0/repositories/{username}/{slug}/refs\n /2.0/repositories/{username}/{slug}/refs/branches\n /2.0/repositories/{username}/{slug}/refs/tags\n /2.0/repositories/{username}/{slug}/forks\n /2.0/repositories/{username}/{slug}/src\n /2.0/repositories/{username}/{slug}/issues\n /2.0/repositories/{username}/{slug}/pullrequests\n\nFiltering and sorting supports several distinct operators and data types as well as basic features, like logical operators (AND, OR).\nAs examples, the following queries could be used on the issue tracker endpoint (`/2.0/repositories/{workspace}/{slug}/issues/`):\n\n\t(state = \"open\" OR state = \"new\") AND assignee = null\n\treporter.nickname != \"evzijst\" AND priority >= \"major\"\n\t(title ~ \"unicode\" OR content.raw ~ \"unicode\") AND created_on > 2015-10-04T14:00:00-07:00\n\nFilter queries can be added to the URL using the q= query parameter. To sort the response, add sort=. Note that the entire query string is put in the q parameter and hence needs to be URL-encoded as shown in the following example:\n\n\t/2.0/repositories/foo/bar/issues?q=state=\"new\"&sort=-updated_on\n\n\n### Operators\n\nFiltering and sorting supports the following operators:\n\n| Operator | Definition | Example |\n|----------|--------------------------------|----------------------------|\n| \"=\" | test for equality | `nickname = \"evzijst\"` |\n| \"!=\" | not equal | `is_private != true` |\n| \"~\" | case-insensitive text contains | `description ~ \"beef\"` |\n| \"!~\" | case-insensitive not contains | `description !~ \"fubar\"` |\n| \">\" | greater than | `priority > \"major\"` |\n| \">=\" | greater than or equal | `priority <= \"trivial\"` |\n| \"<\" | less than | `id < 1234` |\n| \"<=\" | less than or equal | `updated_on <= 2015-03-04` |\n\n### Data types\n\nFiltering and sorting supports the following data types:\n\n| Type | Description | Example |\n|--------------|-----------------------------------------|---------------|\n| **String** | any text inside double quotes | `\"foo\"` |\n| **Number** | arbitrary precision integers and floats | `1, -10.302` |\n| **Null** | to test for the absence of a value | `null` |\n| **boolean** | the unquoted strings true or false | `true, false` |\n| **datetime** | an unquoted [ISO-8601][iso-8601] date time string with the timezone offset, milliseconds and entire time component being optional | `2015-03-04T14:08:59.123+02:00`, `2015-03-04T14:08:59` Date time strings are assumed to be in UTC, unless an explicit timezone offset is provided |\n\n[https://en.wikipedia.org/wiki/ISO_8601]: /iso-8601\n\n### Querying\n\nObjects can be filtered based on their properties. In principle, every element in an object's JSON document schema can be used as a filter criterion.\n\nNote that while the array of objects in a paginated response is wrapped in an\nenvelope with a `values` element, this prefix should not be included in the\nquery fields (so use `/2.0/repositories/foo/bar/issues?q=state=\"new\"`, not\n`/2.0/repositories/foo/bar/issues?q=values.state=\"new\"`).\n\n\n### Examples\n\nFields that contain embedded instances of other object types (e.g. owner is an embedded user object, while parent is an embedded repository) can be traversed recursively. For instance:\n\n\tparent.owner.nickname = \"bitbucket\"\n\nTo find pull requests which merge into master, come from a fork of the repo rather than a branch inside the repo, and on which I am a reviewer:\n\n```\nsource.repository.full_name != \"main/repo\" AND state = \"OPEN\" AND reviewers.nickname = \"evzijst\" AND destination.branch.name = \"master\"\n```\n```\n/2.0/repositories/main/repo/pullrequests?q=source.repository.full_name+%21%3D+%22main%2Frepo%22+AND+state+%3D+%22OPEN%22+AND+reviewers.nickname+%3D+%22evzijst%22+AND+destination.branch.name+%3D+%22master%22\n```\n\nTo find new or on-hold issues related to the UI, created or updated in the last day (SF local time), that have not yet been assigned to anyone:\n\n```\n(state = \"new\" OR state = \"on hold\") AND assignee = null AND component = \"UI\" and updated_on > 2015-11-11T00:00:00-07:00\n```\n```\n/2.0/repositories/main/repo/issues?q=%28state+%3D+%22new%22+OR+state+%3D+%22on+hold%22%29+AND+assignee+%3D+null+AND+component+%3D+%22UI%22+and+updated_on+%3E+2015-11-11T00%3A00%3A00-07%3A00\n```\n\nTo find all tags with the string \"2015\" in the name:\n\n```\nname ~ \"2015\"\n```\n```\n/2.0/repositories/{username}/{slug}/refs/tags?q=name+%7E+%222015%22\n```\nOr all my branches:\n\n```\nname ~ \"erik/\"\n```\n```\n/2.0/repositories/{username}/{slug}/refs/?q=name+%7E+%22erik%2F%22\n```\n### Sorting query results\n\nYou can sort result sets using the ?sort= query parameter, available on the same resources that support filtering:\n\n* In principle, every field that can be queried can also be used as a key for sorting.\n* By default the sort order is ascending. To reverse the order, prefix the field name with a hyphen (e.g. ?sort=-updated_on).\n* Only one field can be sorted on. Compound fields (e.g. sort on state first, followed by updated_on) are not supported.\n\n\n" }, { - "body": "\nEndpoints that return collections of objects should always apply pagination.\nPaginated collections are always wrapped in the following wrapper object:\n\n```json\n{\n \"size\": 5421,\n \"page\": 2,\n \"pagelen\": 10,\n \"next\": \"https://api.bitbucket.org/2.0/repositories/pypy/pypy/commits?page=3\",\n \"previous\": \"https://api.bitbucket.org/2.0/repositories/pypy/pypy/commits?page=1\",\n \"values\": [\n ...\n ]\n}\n```\n\nPagination is often page-bound, with a query parameter page indicating which\npage is to be returned.\n\nHowever, clients are not expected to construct URLs themselves by manipulating\nthe page number query parameter. Instead, the response contains a link to the\nnext page. This link should be treated as an opaque location that is not to be\nconstructed by clients or even assumed to be predictable. The only contract\naround the next link is that it will return the next chunk of results.\n\nLack of a next link in the response indicates the end of the collection.\n\nThe paginated response contains the following fields:\n\n| Field | Value |\n|------------|----------|\n| `size` | Total number of objects in the response. This is an optional element that is not provided in all responses, as it can be expensive to compute. |\n| `page` | Page number of the current results. This is an optional element that is not provided in all responses. |\n| `pagelen` | Current number of objects on the existing page. Globally, the minimum length is 10 and the maximum is 100. Some APIs may specify a different default. |\n| `next` | Link to the next page if it exists. The last page of a collection does not have this value. Use this link to navigate the result set and refrain from constructing your own URLs. |\n| `previous` | Link to previous page if it exists. A collections first page does not have this value. This is an optional element that is not provided in all responses. Some result sets strictly support forward navigation and never provide previous links. Clients must anticipate that backwards navigation is not always available. Use this link to navigate the result set and refrain from constructing your own URLs. |\n| `values` | The list of objects. This contains at most `pagelen` objects. |\n\nThe link to the next page is included such that you don't have to hardcode or construct any links. Only values and next are guaranteed (except the last page, which lacks next). This is because the previous and size values can be expensive for some data sets.\n\nIt is important to realize that Bitbucket support both list-based pagination and iterator-based pagination. List-based pagination assumes that the collection is a discrete, immutable, consistently ordered, finite array of objects with a fixed size. Clients navigate a list-based collection by requesting offset-based chunks. In Bitbucket Cloud, list-based responses include the optional size, page, and previous element. The the next and previous links typically resemble something like /foo/bar?page=4.\n\nHowever, not all result sets can be treated as immutable and finite – much like how programming languages tend to distinguish between lists and arrays on one hand and iterators or stream on the other. Where an list-based pagination offers random access into any point in a collection, iterator-based pagination can only navigate forward one element at a time. In Bitbucket such iterator-based pagination contains the next link and pagelen elements, but not necessarily anything else. In these cases, the next link's value often contains an unpredictable hash instead of an explicit page number. The commits resource uses iterator-based pagination.\n", - "title": "Pagination", "anchor": "pagination", + "title": "Pagination", "description": "Learn more about pagination", - "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjM4LjgyIDE1MS42Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2IyZDRmZjt9LmNscy0ye2ZpbGw6IzRjOWFmZjt9LmNscy0ze2ZpbGw6IzAwNTJjYzt9LmNscy00e29wYWNpdHk6MC42O30uY2xzLTV7ZmlsbDp1cmwoI2xpbmVhci1ncmFkaWVudCk7fS5jbHMtNntmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTIpO30uY2xzLTd7ZmlsbDp1cmwoI2xpbmVhci1ncmFkaWVudC0zKTt9LmNscy04e2ZpbGw6dXJsKCNsaW5lYXItZ3JhZGllbnQtNCk7fS5jbHMtOXtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTUpO30uY2xzLTEwLC5jbHMtMTEsLmNscy0xMntmaWxsOm5vbmU7fS5jbHMtMTB7c3Ryb2tlOiMzMzg0ZmY7fS5jbHMtMTAsLmNscy0xMSwuY2xzLTEyLC5jbHMtMTN7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9LmNscy0xMXtzdHJva2U6I2ZmYWIwMDt9LmNscy0xMntzdHJva2U6I2ZhZmJmYzt9LmNscy0xM3tmaWxsOiNmZmFiMDA7c3Ryb2tlOiMyNjg0ZmY7fTwvc3R5bGU+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHkxPSI2NS4xNyIgeDI9Ijg2LjM4IiB5Mj0iNjUuMTciIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM0YzlhZmYiLz48c3RvcCBvZmZzZXQ9IjAuMDgiIHN0b3AtY29sb3I9IiM0YzlhZmYiIHN0b3Atb3BhY2l0eT0iMC45NCIvPjxzdG9wIG9mZnNldD0iMC4yNCIgc3RvcC1jb2xvcj0iIzRjOWFmZiIgc3RvcC1vcGFjaXR5PSIwLjc4Ii8+PHN0b3Agb2Zmc2V0PSIwLjQ1IiBzdG9wLWNvbG9yPSIjNGM5YWZmIiBzdG9wLW9wYWNpdHk9IjAuNTMiLz48c3RvcCBvZmZzZXQ9IjAuNTUiIHN0b3AtY29sb3I9IiM0YzlhZmYiIHN0b3Atb3BhY2l0eT0iMC40Ii8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC0yIiB4MT0iMTUyLjQ0IiB5MT0iNjUuMTciIHgyPSIyMzguODIiIHkyPSI2NS4xNyIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudCIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTMiIHgxPSIxOC44NSIgeTE9IjExOC43OCIgeDI9IjEyNi4wOCIgeTI9IjExLjU2IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwLjA2IiBzdG9wLWNvbG9yPSIjMDA2NWZmIi8+PHN0b3Agb2Zmc2V0PSIwLjE5IiBzdG9wLWNvbG9yPSIjMDA2NWZmIiBzdG9wLW9wYWNpdHk9IjAuOTQiLz48c3RvcCBvZmZzZXQ9IjAuNDYiIHN0b3AtY29sb3I9IiMwMDY1ZmYiIHN0b3Atb3BhY2l0eT0iMC43OCIvPjxzdG9wIG9mZnNldD0iMC44MiIgc3RvcC1jb2xvcj0iIzAwNjVmZiIgc3RvcC1vcGFjaXR5PSIwLjUzIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDA2NWZmIiBzdG9wLW9wYWNpdHk9IjAuNCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNCIgeDE9IjExMi43NSIgeTE9IjExOC43OCIgeDI9IjIxOS45NyIgeTI9IjExLjU2IiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50LTMiLz48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC01IiB4MT0iNTAuOTciIHkxPSIxMzMuNjEiIHgyPSIxODcuODYiIHkyPSItMy4yOCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMC42NiIgc3RvcC1jb2xvcj0iIzI1Mzg1OCIvPjxzdG9wIG9mZnNldD0iMC44OCIgc3RvcC1jb2xvcj0iIzI1Mzg1OCIgc3RvcC1vcGFjaXR5PSIwLjgzIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMjUzODU4IiBzdG9wLW9wYWNpdHk9IjAuNyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjx0aXRsZT5WaWV3IFZlcnNpb25zPC90aXRsZT48ZyBpZD0iTGF5ZXJfMiIgZGF0YS1uYW1lPSJMYXllciAyIj48ZyBpZD0iU29mdHdhcmUiPjxjaXJjbGUgY2xhc3M9ImNscy0xIiBjeD0iOTQuNTMiIGN5PSIxNDcuOTMiIHI9IjMuNjciLz48Y2lyY2xlIGNsYXNzPSJjbHMtMiIgY3g9IjEwNi45NCIgY3k9IjE0Ny45MyIgcj0iMy42NyIvPjxjaXJjbGUgY2xhc3M9ImNscy0zIiBjeD0iMTE5LjM0IiBjeT0iMTQ3LjkzIiByPSIzLjY3Ii8+PGNpcmNsZSBjbGFzcz0iY2xzLTIiIGN4PSIxMzEuNzUiIGN5PSIxNDcuOTMiIHI9IjMuNjciLz48Y2lyY2xlIGNsYXNzPSJjbHMtMSIgY3g9IjE0NC4xNiIgY3k9IjE0Ny45MyIgcj0iMy42NyIvPjxnIGNsYXNzPSJjbHMtNCI+PHJlY3QgaWQ9Il9SZWN0YW5nbGVfIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTUiIHk9IjI1LjkyIiB3aWR0aD0iODYuMzgiIGhlaWdodD0iNzguNDkiLz48L2c+PGcgY2xhc3M9ImNscy00Ij48cmVjdCBpZD0iX1JlY3RhbmdsZV8yIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTYiIHg9IjE1Mi40NCIgeT0iMjUuOTIiIHdpZHRoPSI4Ni4zOCIgaGVpZ2h0PSI3OC40OSIvPjwvZz48cmVjdCBpZD0iX1JlY3RhbmdsZV8zIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTciIHg9IjE2LjI4IiB5PSIxNC4xMiIgd2lkdGg9IjExMi4zNiIgaGVpZ2h0PSIxMDIuMDkiLz48cmVjdCBpZD0iX1JlY3RhbmdsZV80IiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTgiIHg9IjExMC4xOCIgeT0iMTQuMTIiIHdpZHRoPSIxMTIuMzYiIGhlaWdodD0iMTAyLjA5Ii8+PHJlY3QgaWQ9Il9SZWN0YW5nbGVfNSIgZGF0YS1uYW1lPSImbHQ7UmVjdGFuZ2xlJmd0OyIgY2xhc3M9ImNscy05IiB4PSI0Ny42OSIgd2lkdGg9IjE0My40NSIgaGVpZ2h0PSIxMzAuMzQiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNzkuMTYiIHkxPSIxNi4xOCIgeDI9IjExNy4yNCIgeTI9IjE2LjE4Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjYyLjkzIiB5MT0iMTYuMTgiIHgyPSI3Mi42IiB5Mj0iMTYuMTgiLz48bGluZSBjbGFzcz0iY2xzLTExIiB4MT0iNzkuMTYiIHkxPSIyNi45NSIgeDI9IjExNy4yNCIgeTI9IjI2Ljk1Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjYyLjkzIiB5MT0iMjYuOTUiIHgyPSI3Mi42IiB5Mj0iMjYuOTUiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNzkuMTYiIHkxPSIzNy43MiIgeDI9IjE1MC43IiB5Mj0iMzcuNzIiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSIzNy43MiIgeDI9IjcyLjYiIHkyPSIzNy43MiIvPjxsaW5lIGNsYXNzPSJjbHMtMTEiIHgxPSIxNTAuNyIgeTE9IjQ4LjQ5IiB4Mj0iMTc1LjU5IiB5Mj0iNDguNDkiLz48bGluZSBjbGFzcz0iY2xzLTEyIiB4MT0iMTEwLjMyIiB5MT0iNDguNDkiIHgyPSIxNDMuMDUiIHkyPSI0OC40OSIvPjxsaW5lIGNsYXNzPSJjbHMtMTEiIHgxPSI3OS4xNiIgeTE9IjQ4LjQ5IiB4Mj0iMTAxLjM3IiB5Mj0iNDguNDkiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSI0OC40OSIgeDI9IjcyLjYiIHkyPSI0OC40OSIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI3OS4xNiIgeTE9IjU5LjI2IiB4Mj0iMTUwLjciIHkyPSI1OS4yNiIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjU5LjI2IiB4Mj0iNzIuNiIgeTI9IjU5LjI2Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9Ijc5LjE2IiB5MT0iNzAuMDMiIHgyPSIxNzUuNTkiIHkyPSI3MC4wMyIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjcwLjAzIiB4Mj0iNzIuNiIgeTI9IjcwLjAzIi8+PGxpbmUgY2xhc3M9ImNscy0xMSIgeDE9Ijc5LjE2IiB5MT0iODAuNzkiIHgyPSIxMTcuMjQiIHkyPSI4MC43OSIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjgwLjc5IiB4Mj0iNzIuNiIgeTI9IjgwLjc5Ii8+PGxpbmUgY2xhc3M9ImNscy0xMyIgeDE9Ijc5LjE2IiB5MT0iOTEuNTYiIHgyPSIxNDkuMDYiIHkyPSI5MS41NiIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjkxLjU2IiB4Mj0iNzIuNiIgeTI9IjkxLjU2Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjYyLjkzIiB5MT0iODAuNzkiIHgyPSI3Mi42IiB5Mj0iODAuNzkiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSI5MS41NiIgeDI9IjcyLjYiIHkyPSI5MS41NiIvPjxsaW5lIGNsYXNzPSJjbHMtMTEiIHgxPSI3OS4xNiIgeTE9IjEwMi4zMyIgeDI9IjExNy4yNCIgeTI9IjEwMi4zMyIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjEwMi4zMyIgeDI9IjcyLjYiIHkyPSIxMDIuMzMiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iMTI1Ljk4IiB5MT0iMTEzLjEiIHgyPSIxNDkuMDYiIHkyPSIxMTMuMSIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSI3OS4xNiIgeTE9IjExMy4xIiB4Mj0iMTE3LjI0IiB5Mj0iMTEzLjEiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSIxMTMuMSIgeDI9IjcyLjYiIHkyPSIxMTMuMSIvPjwvZz48L2c+PC9zdmc+" + "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjM4LjgyIDE1MS42Ij48ZGVmcz48c3R5bGU+LmNscy0xe2ZpbGw6I2IyZDRmZjt9LmNscy0ye2ZpbGw6IzRjOWFmZjt9LmNscy0ze2ZpbGw6IzAwNTJjYzt9LmNscy00e29wYWNpdHk6MC42O30uY2xzLTV7ZmlsbDp1cmwoI2xpbmVhci1ncmFkaWVudCk7fS5jbHMtNntmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTIpO30uY2xzLTd7ZmlsbDp1cmwoI2xpbmVhci1ncmFkaWVudC0zKTt9LmNscy04e2ZpbGw6dXJsKCNsaW5lYXItZ3JhZGllbnQtNCk7fS5jbHMtOXtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTUpO30uY2xzLTEwLC5jbHMtMTEsLmNscy0xMntmaWxsOm5vbmU7fS5jbHMtMTB7c3Ryb2tlOiMzMzg0ZmY7fS5jbHMtMTAsLmNscy0xMSwuY2xzLTEyLC5jbHMtMTN7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjJweDt9LmNscy0xMXtzdHJva2U6I2ZmYWIwMDt9LmNscy0xMntzdHJva2U6I2ZhZmJmYzt9LmNscy0xM3tmaWxsOiNmZmFiMDA7c3Ryb2tlOiMyNjg0ZmY7fTwvc3R5bGU+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHkxPSI2NS4xNyIgeDI9Ijg2LjM4IiB5Mj0iNjUuMTciIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiM0YzlhZmYiLz48c3RvcCBvZmZzZXQ9IjAuMDgiIHN0b3AtY29sb3I9IiM0YzlhZmYiIHN0b3Atb3BhY2l0eT0iMC45NCIvPjxzdG9wIG9mZnNldD0iMC4yNCIgc3RvcC1jb2xvcj0iIzRjOWFmZiIgc3RvcC1vcGFjaXR5PSIwLjc4Ii8+PHN0b3Agb2Zmc2V0PSIwLjQ1IiBzdG9wLWNvbG9yPSIjNGM5YWZmIiBzdG9wLW9wYWNpdHk9IjAuNTMiLz48c3RvcCBvZmZzZXQ9IjAuNTUiIHN0b3AtY29sb3I9IiM0YzlhZmYiIHN0b3Atb3BhY2l0eT0iMC40Ii8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC0yIiB4MT0iMTUyLjQ0IiB5MT0iNjUuMTciIHgyPSIyMzguODIiIHkyPSI2NS4xNyIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudCIvPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTMiIHgxPSIxOC44NSIgeTE9IjExOC43OCIgeDI9IjEyNi4wOCIgeTI9IjExLjU2IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwLjA2IiBzdG9wLWNvbG9yPSIjMDA2NWZmIi8+PHN0b3Agb2Zmc2V0PSIwLjE5IiBzdG9wLWNvbG9yPSIjMDA2NWZmIiBzdG9wLW9wYWNpdHk9IjAuOTQiLz48c3RvcCBvZmZzZXQ9IjAuNDYiIHN0b3AtY29sb3I9IiMwMDY1ZmYiIHN0b3Atb3BhY2l0eT0iMC43OCIvPjxzdG9wIG9mZnNldD0iMC44MiIgc3RvcC1jb2xvcj0iIzAwNjVmZiIgc3RvcC1vcGFjaXR5PSIwLjUzIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMDA2NWZmIiBzdG9wLW9wYWNpdHk9IjAuNCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNCIgeDE9IjExMi43NSIgeTE9IjExOC43OCIgeDI9IjIxOS45NyIgeTI9IjExLjU2IiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50LTMiLz48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudC01IiB4MT0iNTAuOTciIHkxPSIxMzMuNjEiIHgyPSIxODcuODYiIHkyPSItMy4yOCIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMC42NiIgc3RvcC1jb2xvcj0iIzI1Mzg1OCIvPjxzdG9wIG9mZnNldD0iMC44OCIgc3RvcC1jb2xvcj0iIzI1Mzg1OCIgc3RvcC1vcGFjaXR5PSIwLjgzIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMjUzODU4IiBzdG9wLW9wYWNpdHk9IjAuNyIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjx0aXRsZT5WaWV3IFZlcnNpb25zPC90aXRsZT48ZyBpZD0iTGF5ZXJfMiIgZGF0YS1uYW1lPSJMYXllciAyIj48ZyBpZD0iU29mdHdhcmUiPjxjaXJjbGUgY2xhc3M9ImNscy0xIiBjeD0iOTQuNTMiIGN5PSIxNDcuOTMiIHI9IjMuNjciLz48Y2lyY2xlIGNsYXNzPSJjbHMtMiIgY3g9IjEwNi45NCIgY3k9IjE0Ny45MyIgcj0iMy42NyIvPjxjaXJjbGUgY2xhc3M9ImNscy0zIiBjeD0iMTE5LjM0IiBjeT0iMTQ3LjkzIiByPSIzLjY3Ii8+PGNpcmNsZSBjbGFzcz0iY2xzLTIiIGN4PSIxMzEuNzUiIGN5PSIxNDcuOTMiIHI9IjMuNjciLz48Y2lyY2xlIGNsYXNzPSJjbHMtMSIgY3g9IjE0NC4xNiIgY3k9IjE0Ny45MyIgcj0iMy42NyIvPjxnIGNsYXNzPSJjbHMtNCI+PHJlY3QgaWQ9Il9SZWN0YW5nbGVfIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTUiIHk9IjI1LjkyIiB3aWR0aD0iODYuMzgiIGhlaWdodD0iNzguNDkiLz48L2c+PGcgY2xhc3M9ImNscy00Ij48cmVjdCBpZD0iX1JlY3RhbmdsZV8yIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTYiIHg9IjE1Mi40NCIgeT0iMjUuOTIiIHdpZHRoPSI4Ni4zOCIgaGVpZ2h0PSI3OC40OSIvPjwvZz48cmVjdCBpZD0iX1JlY3RhbmdsZV8zIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTciIHg9IjE2LjI4IiB5PSIxNC4xMiIgd2lkdGg9IjExMi4zNiIgaGVpZ2h0PSIxMDIuMDkiLz48cmVjdCBpZD0iX1JlY3RhbmdsZV80IiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTgiIHg9IjExMC4xOCIgeT0iMTQuMTIiIHdpZHRoPSIxMTIuMzYiIGhlaWdodD0iMTAyLjA5Ii8+PHJlY3QgaWQ9Il9SZWN0YW5nbGVfNSIgZGF0YS1uYW1lPSImbHQ7UmVjdGFuZ2xlJmd0OyIgY2xhc3M9ImNscy05IiB4PSI0Ny42OSIgd2lkdGg9IjE0My40NSIgaGVpZ2h0PSIxMzAuMzQiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNzkuMTYiIHkxPSIxNi4xOCIgeDI9IjExNy4yNCIgeTI9IjE2LjE4Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjYyLjkzIiB5MT0iMTYuMTgiIHgyPSI3Mi42IiB5Mj0iMTYuMTgiLz48bGluZSBjbGFzcz0iY2xzLTExIiB4MT0iNzkuMTYiIHkxPSIyNi45NSIgeDI9IjExNy4yNCIgeTI9IjI2Ljk1Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjYyLjkzIiB5MT0iMjYuOTUiIHgyPSI3Mi42IiB5Mj0iMjYuOTUiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNzkuMTYiIHkxPSIzNy43MiIgeDI9IjE1MC43IiB5Mj0iMzcuNzIiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSIzNy43MiIgeDI9IjcyLjYiIHkyPSIzNy43MiIvPjxsaW5lIGNsYXNzPSJjbHMtMTEiIHgxPSIxNTAuNyIgeTE9IjQ4LjQ5IiB4Mj0iMTc1LjU5IiB5Mj0iNDguNDkiLz48bGluZSBjbGFzcz0iY2xzLTEyIiB4MT0iMTEwLjMyIiB5MT0iNDguNDkiIHgyPSIxNDMuMDUiIHkyPSI0OC40OSIvPjxsaW5lIGNsYXNzPSJjbHMtMTEiIHgxPSI3OS4xNiIgeTE9IjQ4LjQ5IiB4Mj0iMTAxLjM3IiB5Mj0iNDguNDkiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSI0OC40OSIgeDI9IjcyLjYiIHkyPSI0OC40OSIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI3OS4xNiIgeTE9IjU5LjI2IiB4Mj0iMTUwLjciIHkyPSI1OS4yNiIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjU5LjI2IiB4Mj0iNzIuNiIgeTI9IjU5LjI2Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9Ijc5LjE2IiB5MT0iNzAuMDMiIHgyPSIxNzUuNTkiIHkyPSI3MC4wMyIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjcwLjAzIiB4Mj0iNzIuNiIgeTI9IjcwLjAzIi8+PGxpbmUgY2xhc3M9ImNscy0xMSIgeDE9Ijc5LjE2IiB5MT0iODAuNzkiIHgyPSIxMTcuMjQiIHkyPSI4MC43OSIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjgwLjc5IiB4Mj0iNzIuNiIgeTI9IjgwLjc5Ii8+PGxpbmUgY2xhc3M9ImNscy0xMyIgeDE9Ijc5LjE2IiB5MT0iOTEuNTYiIHgyPSIxNDkuMDYiIHkyPSI5MS41NiIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjkxLjU2IiB4Mj0iNzIuNiIgeTI9IjkxLjU2Ii8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjYyLjkzIiB5MT0iODAuNzkiIHgyPSI3Mi42IiB5Mj0iODAuNzkiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSI5MS41NiIgeDI9IjcyLjYiIHkyPSI5MS41NiIvPjxsaW5lIGNsYXNzPSJjbHMtMTEiIHgxPSI3OS4xNiIgeTE9IjEwMi4zMyIgeDI9IjExNy4yNCIgeTI9IjEwMi4zMyIvPjxsaW5lIGNsYXNzPSJjbHMtMTAiIHgxPSI2Mi45MyIgeTE9IjEwMi4zMyIgeDI9IjcyLjYiIHkyPSIxMDIuMzMiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iMTI1Ljk4IiB5MT0iMTEzLjEiIHgyPSIxNDkuMDYiIHkyPSIxMTMuMSIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSI3OS4xNiIgeTE9IjExMy4xIiB4Mj0iMTE3LjI0IiB5Mj0iMTEzLjEiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iNjIuOTMiIHkxPSIxMTMuMSIgeDI9IjcyLjYiIHkyPSIxMTMuMSIvPjwvZz48L2c+PC9zdmc+'", + "body": "\nEndpoints that return collections of objects should always apply pagination.\nPaginated collections are always wrapped in the following wrapper object:\n\n```json\n{\n \"size\": 5421,\n \"page\": 2,\n \"pagelen\": 10,\n \"next\": \"https://api.bitbucket.org/2.0/repositories/pypy/pypy/commits?page=3\",\n \"previous\": \"https://api.bitbucket.org/2.0/repositories/pypy/pypy/commits?page=1\",\n \"values\": [\n ...\n ]\n}\n```\n\nPagination is often page-bound, with a query parameter page indicating which\npage is to be returned.\n\nHowever, clients are not expected to construct URLs themselves by manipulating\nthe page number query parameter. Instead, the response contains a link to the\nnext page. This link should be treated as an opaque location that is not to be\nconstructed by clients or even assumed to be predictable. The only contract\naround the next link is that it will return the next chunk of results.\n\nLack of a next link in the response indicates the end of the collection.\n\nThe paginated response contains the following fields:\n\n| Field | Value |\n|------------|----------|\n| `size` | Total number of objects in the response. This is an optional element that is not provided in all responses, as it can be expensive to compute. |\n| `page` | Page number of the current results. This is an optional element that is not provided in all responses. |\n| `pagelen` | Current number of objects on the existing page. Globally, the minimum length is 10 and the maximum is 100. Some APIs may specify a different default. |\n| `next` | Link to the next page if it exists. The last page of a collection does not have this value. Use this link to navigate the result set and refrain from constructing your own URLs. |\n| `previous` | Link to previous page if it exists. A collections first page does not have this value. This is an optional element that is not provided in all responses. Some result sets strictly support forward navigation and never provide previous links. Clients must anticipate that backwards navigation is not always available. Use this link to navigate the result set and refrain from constructing your own URLs. |\n| `values` | The list of objects. This contains at most `pagelen` objects. |\n\nThe link to the next page is included such that you don't have to hardcode or construct any links. Only values and next are guaranteed (except the last page, which lacks next). This is because the previous and size values can be expensive for some data sets.\n\nIt is important to realize that Bitbucket support both list-based pagination and iterator-based pagination. List-based pagination assumes that the collection is a discrete, immutable, consistently ordered, finite array of objects with a fixed size. Clients navigate a list-based collection by requesting offset-based chunks. In Bitbucket Cloud, list-based responses include the optional size, page, and previous element. The the next and previous links typically resemble something like /foo/bar?page=4.\n\nHowever, not all result sets can be treated as immutable and finite – much like how programming languages tend to distinguish between lists and arrays on one hand and iterators or stream on the other. Where an list-based pagination offers random access into any point in a collection, iterator-based pagination can only navigate forward one element at a time. In Bitbucket such iterator-based pagination contains the next link and pagelen elements, but not necessarily anything else. In these cases, the next link's value often contains an unpredictable hash instead of an explicit page number. The commits resource uses iterator-based pagination.\n" }, { - "body": "\nBy default, each endpoint returns the full representation of a resource and in\nsome cases that can be a lot of data. For example, retrieving a list of pull\nrequests can amount to quite a large document.\n\nFor better performance, you can ask the server to only return the fields you\nreally need and to omit unwanted data. To request a partial response and to\nadd or remove specific fields from a response, use the `fields` query\nparameter.\n\n\n### Example\n\nMost API resources embed a substantial list of links pointing to related\nresources. This saves the client from constructing its own URLs, but is\nsomewhat wasteful when the client doesn't need them.\n\nTo significantly reduce the size of the response, use `?fields=-links`:\n\n```json\n$ curl https://api.bitbucket.org/2.0/users/evzijst?fields=-links\n{\n \"nickname\": \"evzijst\",\n \"account_status\": \"active\",\n \"website\": \"\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{a288a0ab-e13b-43f0-a689-c4ef0a249875}\",\n \"created_on\": \"2010-07-07T05:16:36+00:00\",\n \"location\": null,\n \"type\": \"user\"\n}\n```\n\n### Fields parameter syntax\n\nThe `fields` parameter supports 3 modes of operation:\n\n1. Removal of select fields (e.g. `-links`)\n2. Pulling in additional fields not normally returned by an endpoint, while\n still getting all the default fields (e.g. `+reviewers`)\n3. Omitting all fields, except those specified (e.g. `owner.display_name`)\n\nThe fields parameter can contain a list of multiple comma-separated field names\n(e.g. `fields=owner.display_name,uuid,links.self.href`). The parameter itself is\nnot repeated.\n\nAs discussed at [Condensed Versus Full Objects](serialization#representations),\nmost objects that are embedded inside other objects (like how `owner` is an\nembedded `user` object in `repository`) appear in \"condensed\" form that omits\nmany fields. The `fields` parameter allows us to pull in additional fields in\nsuch cases.\n\nFor example, the embedded repository object in a pull request does not normally\ncontain its `owner`. To add that in we can use:\n`+values.destination.repository.owner`.\n\n\n### Wildcards\n\nThe asterisk can be used to match all fields on a particular level. For\nexample, removing all entries from the `links` element can be done like this:\n\n```json\n$ curl https://api.bitbucket.org/2.0/users/evzijst?fields=-links.*\n{\n \"nickname\": \"evzijst\",\n \"account_status\": \"active\",\n \"website\": \"\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{a288a0ab-e13b-43f0-a689-c4ef0a249875}\",\n \"links\": {},\n \"created_on\": \"2010-07-07T05:16:36+00:00\",\n \"location\": null,\n \"type\": \"user\"\n}\n```\n\nWildcards can be used in combination with exclusion and inclusion. For\ninstance, `-*,+foo,+bar` will remove all elements from the root level and then\nadd in `foo` and `bar`.\n\n\n### URL encoding\n\nBe aware that when using the `+foo.bar` syntax in the query string, that the\n\"+\" must be URL encoded as \"%2B\" and so the URL will be:\n\n```\nhttps://api.bitbucket.org/2.0/repositories/evzijst/interruptingcow?fields=%2Bowner.created_on\n```\n\nWithout URL escaping, \"+\" is interpreted as an encoded space which will not\nmatch any fields.\n\n\n### Field discovery\n\nWhile a resource's `self` URL, as well its \"collection\" URL typically return\nthe full object with all its fields, there are some exceptions for fields that\nare overly verbose or costly to generate.\n\nFor instance, a pull request contains the embedded lists of reviewers and\nparticipants. These fields are included from the `self` URL, but not from the\n`/pullrequests` collections resource, as it would impact performance too much.\n\nTo discover any additional fields that might not be included by default,\n`fields=*` can be used.\n\n\n### More examples\n\nIf we want to get a list of all reviewer nicknames on pull requests I created,\nwe could combine a [filter](filtering) with a partial response. This will omit\nall other data from the response:\n\n```\n/2.0/repositories/bitbucket/bitbucket/pullrequests?fields=values.id,values.reviewers.nickname,values.state&q=author.uuid%3D%22%7Bd301aafa-d676-4ee0-88be-962be7417567%7D%22\n{\n \"values\": [\n {\n \"reviewers\": [\n {\n \"nickname\": \"abhin\"\n },\n {\n \"nickname\": \"dtao\"\n },\n {\n \"nickname\": \"csomme\"\n }\n ],\n \"state\": \"OPEN\",\n \"id\": 11355\n },\n {\n \"reviewers\": [\n {\n \"nickname\": \"csomme\"\n },\n {\n \"nickname\": \"abhin\"\n },\n {\n \"nickname\": \"dstevens\"\n }\n ],\n \"state\": \"MERGED\",\n \"id\": 11347\n },\n {\n \"reviewers\": [\n {\n \"nickname\": \"csomme\"\n },\n {\n \"nickname\": \"jmooring\"\n },\n {\n \"nickname\": \"zdavis\"\n },\n {\n \"nickname\": \"flexbox\"\n }\n ],\n \"state\": \"OPEN\",\n \"id\": 11344\n }\n ]\n}\n```\n", - "title": "Partial responses", "anchor": "partial-response", + "title": "Partial responses", "description": "Tweak which fields are returned", - "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTYyLjQ0ODcgMjEwLjExMTUiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGlzb2xhdGlvbjogaXNvbGF0ZTsKICAgICAgfQoKICAgICAgLmNscy0yLCAuY2xzLTYsIC5jbHMtOCB7CiAgICAgICAgZmlsbDogbm9uZTsKICAgICAgfQoKICAgICAgLmNscy0yLCAuY2xzLTggewogICAgICAgIHN0cm9rZTogIzAwNjVmZjsKICAgICAgICBzdHJva2Utd2lkdGg6IDJweDsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBzdHJva2UtbGluZWpvaW46IHJvdW5kOwogICAgICB9CgogICAgICAuY2xzLTMgewogICAgICAgIGZpbGw6ICNlN2U4ZWM7CiAgICAgIH0KCiAgICAgIC5jbHMtNCB7CiAgICAgICAgZmlsbDogI2ZmZTM4MDsKICAgICAgfQoKICAgICAgLmNscy01IHsKICAgICAgICBmaWxsOiAjZmZmMGIyOwogICAgICB9CgogICAgICAuY2xzLTYgewogICAgICAgIHN0cm9rZTogI2ZmOTkxZjsKICAgICAgICBzdHJva2Utd2lkdGg6IDEuODE1NnB4OwogICAgICB9CgogICAgICAuY2xzLTYsIC5jbHMtOCB7CiAgICAgICAgc3Ryb2tlLW1pdGVybGltaXQ6IDEwOwogICAgICB9CgogICAgICAuY2xzLTcgewogICAgICAgIG1peC1ibGVuZC1tb2RlOiBtdWx0aXBseTsKICAgICAgICBmaWxsOiB1cmwoI2xpbmVhci1ncmFkaWVudCk7CiAgICAgIH0KCiAgICAgIC5jbHMtOSB7CiAgICAgICAgZmlsbDogI2Y0ZjVmNzsKICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB4MT0iMTEzLjM4MTgiIHkxPSI0OS40MyIgeDI9IjE1My43ODkzIiB5Mj0iOS4wMjI1IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgIDxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2ZhZmJmYyIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjAuMjc4NiIgc3RvcC1jb2xvcj0iI2VmZjFmMyIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjAuNzY4OCIgc3RvcC1jb2xvcj0iI2QxZDZkZCIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNjMWM3ZDAiLz4KICAgIDwvbGluZWFyR3JhZGllbnQ+CiAgPC9kZWZzPgogIDx0aXRsZT5Eb2N1bWVudCBUYWJsZTwvdGl0bGU+CiAgPGcgY2xhc3M9ImNscy0xIj4KICAgIDxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPgogICAgICA8ZyBpZD0iT2JqZWN0cyI+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0yIiB4MT0iMTcuNDcxIiB5MT0iMTcxLjc1NzMiIHgyPSI3OS4yOTgiIHkyPSIxNzEuNzU3MyIvPgogICAgICAgIDxwb2x5Z29uIGlkPSJfUGF0aF8iIGRhdGEtbmFtZT0iJmx0O1BhdGgmZ3Q7IiBjbGFzcz0iY2xzLTMiIHBvaW50cz0iMTYyLjQ0NSAzOC43MTEgMTYyLjQ0NSAyMTAuMTExIDAgMjEwLjExMSAwIDAgMTIzLjcwNCAwIDE2Mi40MTUgMzguNzExIDE2Mi40NDUgMzguNzExIi8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy00IiB4PSIxOC45MTE3IiB5PSI3OC4xNTQyIiB3aWR0aD0iNDcuODQ4NSIgaGVpZ2h0PSI3OS42NTM3Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy01IiB4PSIxOC45MTE3IiB5PSI1MS42MDMiIHdpZHRoPSIxMjMuNDUyOSIgaGVpZ2h0PSIyNi41NTEyIi8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy02IiB4PSIxOC45MTE3IiB5PSI1MS42MDMiIHdpZHRoPSIxMjMuNDUyOSIgaGVpZ2h0PSIxMDYuMjA0OSIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtNiIgeDE9IjY2Ljc2MDEiIHkxPSI1MS42MDMiIHgyPSI2Ni43NjAxIiB5Mj0iMTU3LjgwNzkiLz4KICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTYiIHgxPSI5MS4zMjg0IiB5MT0iNTEuNjAzIiB4Mj0iOTEuMzI4NCIgeTI9IjE1Ny44MDc5Ii8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTE1Ljg5NjciIHkxPSI1MS42MDMiIHgyPSIxMTUuODk2NyIgeTI9IjE1Ny44MDc5Ii8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTguOTExNyIgeTE9Ijc4LjE1NDIiIHgyPSIxNDIuMzY0NiIgeTI9Ijc4LjE1NDIiLz4KICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTYiIHgxPSIxOC45MTE3IiB5MT0iMTA0LjcwNTUiIHgyPSIxNDIuMzY0NiIgeTI9IjEwNC43MDU1Ii8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTguOTExNyIgeTE9IjEzMS4yNTY3IiB4Mj0iMTQyLjM2NDYiIHkyPSIxMzEuMjU2NyIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtNyIgcG9pbnRzPSIxNjIuNDQ1IDM4LjcxMSAxNjIuNDE1IDM4LjcxMSAxMjMuODcyIDAuMTY5IDEyMy44NzIgNTkuOTIxIDE2Mi40NDUgMzkuMTM3IDE2Mi40NDUgMzguNzExIi8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy04IiB4MT0iMTguMzk3MyIgeTE9IjE4MS4zNDQiIHgyPSI3OS4xMTM1IiB5Mj0iMTgxLjM0NCIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtOCIgeDE9IjE4LjM5NzMiIHkxPSIxOTAuOTMwNiIgeDI9IjUxLjMwNDkiIHkyPSIxOTAuOTMwNiIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtOCIgeDE9IjE4LjM5NzMiIHkxPSIxNzEuNzU3MyIgeDI9Ijc5LjExMzUiIHkyPSIxNzEuNzU3MyIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtOCIgeDE9IjE4LjM5NzMiIHkxPSIzNi4xNzA1IiB4Mj0iNzkuMTEzNSIgeTI9IjM2LjE3MDUiLz4KICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTgiIHgxPSIxOC4zOTczIiB5MT0iMjYuNTgzOCIgeDI9Ijc5LjExMzUiIHkyPSIyNi41ODM4Ii8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy05IiBwb2ludHM9IjE2Mi40NDkgMzguNzQyIDEyMy43MDcgMzguNzQyIDEyMy43MDcgMCAxNjIuNDQ5IDM4Ljc0MiIvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K" + "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTYyLjQ0ODcgMjEwLjExMTUiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGlzb2xhdGlvbjogaXNvbGF0ZTsKICAgICAgfQoKICAgICAgLmNscy0yLCAuY2xzLTYsIC5jbHMtOCB7CiAgICAgICAgZmlsbDogbm9uZTsKICAgICAgfQoKICAgICAgLmNscy0yLCAuY2xzLTggewogICAgICAgIHN0cm9rZTogIzAwNjVmZjsKICAgICAgICBzdHJva2Utd2lkdGg6IDJweDsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBzdHJva2UtbGluZWpvaW46IHJvdW5kOwogICAgICB9CgogICAgICAuY2xzLTMgewogICAgICAgIGZpbGw6ICNlN2U4ZWM7CiAgICAgIH0KCiAgICAgIC5jbHMtNCB7CiAgICAgICAgZmlsbDogI2ZmZTM4MDsKICAgICAgfQoKICAgICAgLmNscy01IHsKICAgICAgICBmaWxsOiAjZmZmMGIyOwogICAgICB9CgogICAgICAuY2xzLTYgewogICAgICAgIHN0cm9rZTogI2ZmOTkxZjsKICAgICAgICBzdHJva2Utd2lkdGg6IDEuODE1NnB4OwogICAgICB9CgogICAgICAuY2xzLTYsIC5jbHMtOCB7CiAgICAgICAgc3Ryb2tlLW1pdGVybGltaXQ6IDEwOwogICAgICB9CgogICAgICAuY2xzLTcgewogICAgICAgIG1peC1ibGVuZC1tb2RlOiBtdWx0aXBseTsKICAgICAgICBmaWxsOiB1cmwoI2xpbmVhci1ncmFkaWVudCk7CiAgICAgIH0KCiAgICAgIC5jbHMtOSB7CiAgICAgICAgZmlsbDogI2Y0ZjVmNzsKICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB4MT0iMTEzLjM4MTgiIHkxPSI0OS40MyIgeDI9IjE1My43ODkzIiB5Mj0iOS4wMjI1IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgIDxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2ZhZmJmYyIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjAuMjc4NiIgc3RvcC1jb2xvcj0iI2VmZjFmMyIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjAuNzY4OCIgc3RvcC1jb2xvcj0iI2QxZDZkZCIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNjMWM3ZDAiLz4KICAgIDwvbGluZWFyR3JhZGllbnQ+CiAgPC9kZWZzPgogIDx0aXRsZT5Eb2N1bWVudCBUYWJsZTwvdGl0bGU+CiAgPGcgY2xhc3M9ImNscy0xIj4KICAgIDxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPgogICAgICA8ZyBpZD0iT2JqZWN0cyI+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0yIiB4MT0iMTcuNDcxIiB5MT0iMTcxLjc1NzMiIHgyPSI3OS4yOTgiIHkyPSIxNzEuNzU3MyIvPgogICAgICAgIDxwb2x5Z29uIGlkPSJfUGF0aF8iIGRhdGEtbmFtZT0iJmx0O1BhdGgmZ3Q7IiBjbGFzcz0iY2xzLTMiIHBvaW50cz0iMTYyLjQ0NSAzOC43MTEgMTYyLjQ0NSAyMTAuMTExIDAgMjEwLjExMSAwIDAgMTIzLjcwNCAwIDE2Mi40MTUgMzguNzExIDE2Mi40NDUgMzguNzExIi8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy00IiB4PSIxOC45MTE3IiB5PSI3OC4xNTQyIiB3aWR0aD0iNDcuODQ4NSIgaGVpZ2h0PSI3OS42NTM3Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy01IiB4PSIxOC45MTE3IiB5PSI1MS42MDMiIHdpZHRoPSIxMjMuNDUyOSIgaGVpZ2h0PSIyNi41NTEyIi8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy02IiB4PSIxOC45MTE3IiB5PSI1MS42MDMiIHdpZHRoPSIxMjMuNDUyOSIgaGVpZ2h0PSIxMDYuMjA0OSIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtNiIgeDE9IjY2Ljc2MDEiIHkxPSI1MS42MDMiIHgyPSI2Ni43NjAxIiB5Mj0iMTU3LjgwNzkiLz4KICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTYiIHgxPSI5MS4zMjg0IiB5MT0iNTEuNjAzIiB4Mj0iOTEuMzI4NCIgeTI9IjE1Ny44MDc5Ii8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTE1Ljg5NjciIHkxPSI1MS42MDMiIHgyPSIxMTUuODk2NyIgeTI9IjE1Ny44MDc5Ii8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTguOTExNyIgeTE9Ijc4LjE1NDIiIHgyPSIxNDIuMzY0NiIgeTI9Ijc4LjE1NDIiLz4KICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTYiIHgxPSIxOC45MTE3IiB5MT0iMTA0LjcwNTUiIHgyPSIxNDIuMzY0NiIgeTI9IjEwNC43MDU1Ii8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTguOTExNyIgeTE9IjEzMS4yNTY3IiB4Mj0iMTQyLjM2NDYiIHkyPSIxMzEuMjU2NyIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtNyIgcG9pbnRzPSIxNjIuNDQ1IDM4LjcxMSAxNjIuNDE1IDM4LjcxMSAxMjMuODcyIDAuMTY5IDEyMy44NzIgNTkuOTIxIDE2Mi40NDUgMzkuMTM3IDE2Mi40NDUgMzguNzExIi8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy04IiB4MT0iMTguMzk3MyIgeTE9IjE4MS4zNDQiIHgyPSI3OS4xMTM1IiB5Mj0iMTgxLjM0NCIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtOCIgeDE9IjE4LjM5NzMiIHkxPSIxOTAuOTMwNiIgeDI9IjUxLjMwNDkiIHkyPSIxOTAuOTMwNiIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtOCIgeDE9IjE4LjM5NzMiIHkxPSIxNzEuNzU3MyIgeDI9Ijc5LjExMzUiIHkyPSIxNzEuNzU3MyIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtOCIgeDE9IjE4LjM5NzMiIHkxPSIzNi4xNzA1IiB4Mj0iNzkuMTEzNSIgeTI9IjM2LjE3MDUiLz4KICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTgiIHgxPSIxOC4zOTczIiB5MT0iMjYuNTgzOCIgeDI9Ijc5LjExMzUiIHkyPSIyNi41ODM4Ii8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy05IiBwb2ludHM9IjE2Mi40NDkgMzguNzQyIDEyMy43MDcgMzguNzQyIDEyMy43MDcgMCAxNjIuNDQ5IDM4Ljc0MiIvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K'", + "body": "\nBy default, each endpoint returns the full representation of a resource and in\nsome cases that can be a lot of data. For example, retrieving a list of pull\nrequests can amount to quite a large document.\n\nFor better performance, you can ask the server to only return the fields you\nreally need and to omit unwanted data. To request a partial response and to\nadd or remove specific fields from a response, use the `fields` query\nparameter.\n\n\n### Example\n\nMost API resources embed a substantial list of links pointing to related\nresources. This saves the client from constructing its own URLs, but is\nsomewhat wasteful when the client doesn't need them.\n\nTo significantly reduce the size of the response, use `?fields=-links`:\n\n```json\n$ curl https://api.bitbucket.org/2.0/users/evzijst?fields=-links\n{\n \"nickname\": \"evzijst\",\n \"account_status\": \"active\",\n \"website\": \"\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{a288a0ab-e13b-43f0-a689-c4ef0a249875}\",\n \"created_on\": \"2010-07-07T05:16:36+00:00\",\n \"location\": null,\n \"type\": \"user\"\n}\n```\n\n### Fields parameter syntax\n\nThe `fields` parameter supports 3 modes of operation:\n\n1. Removal of select fields (e.g. `-links`)\n2. Pulling in additional fields not normally returned by an endpoint, while\n still getting all the default fields (e.g. `+reviewers`)\n3. Omitting all fields, except those specified (e.g. `owner.display_name`)\n\nThe fields parameter can contain a list of multiple comma-separated field names\n(e.g. `fields=owner.display_name,uuid,links.self.href`). The parameter itself is\nnot repeated.\n\nAs discussed at [Condensed Versus Full Objects](serialization#representations),\nmost objects that are embedded inside other objects (like how `owner` is an\nembedded `user` object in `repository`) appear in \"condensed\" form that omits\nmany fields. The `fields` parameter allows us to pull in additional fields in\nsuch cases.\n\nFor example, the embedded repository object in a pull request does not normally\ncontain its `owner`. To add that in we can use:\n`+values.destination.repository.owner`.\n\n\n### Wildcards\n\nThe asterisk can be used to match all fields on a particular level. For\nexample, removing all entries from the `links` element can be done like this:\n\n```json\n$ curl https://api.bitbucket.org/2.0/users/evzijst?fields=-links.*\n{\n \"nickname\": \"evzijst\",\n \"account_status\": \"active\",\n \"website\": \"\",\n \"display_name\": \"Erik van Zijst\",\n \"uuid\": \"{a288a0ab-e13b-43f0-a689-c4ef0a249875}\",\n \"links\": {},\n \"created_on\": \"2010-07-07T05:16:36+00:00\",\n \"location\": null,\n \"type\": \"user\"\n}\n```\n\nWildcards can be used in combination with exclusion and inclusion. For\ninstance, `-*,+foo,+bar` will remove all elements from the root level and then\nadd in `foo` and `bar`.\n\n\n### URL encoding\n\nBe aware that when using the `+foo.bar` syntax in the query string, that the\n\"+\" must be URL encoded as \"%2B\" and so the URL will be:\n\n```\nhttps://api.bitbucket.org/2.0/repositories/evzijst/interruptingcow?fields=%2Bowner.created_on\n```\n\nWithout URL escaping, \"+\" is interpreted as an encoded space which will not\nmatch any fields.\n\n\n### Field discovery\n\nWhile a resource's `self` URL, as well its \"collection\" URL typically return\nthe full object with all its fields, there are some exceptions for fields that\nare overly verbose or costly to generate.\n\nFor instance, a pull request contains the embedded lists of reviewers and\nparticipants. These fields are included from the `self` URL, but not from the\n`/pullrequests` collections resource, as it would impact performance too much.\n\nTo discover any additional fields that might not be included by default,\n`fields=*` can be used.\n\n\n### More examples\n\nIf we want to get a list of all reviewer nicknames on pull requests I created,\nwe could combine a [filter](filtering) with a partial response. This will omit\nall other data from the response:\n\n```\n/2.0/repositories/bitbucket/bitbucket/pullrequests?fields=values.id,values.reviewers.nickname,values.state&q=author.uuid%3D%22%7Bd301aafa-d676-4ee0-88be-962be7417567%7D%22\n{\n \"values\": [\n {\n \"reviewers\": [\n {\n \"nickname\": \"abhin\"\n },\n {\n \"nickname\": \"dtao\"\n },\n {\n \"nickname\": \"csomme\"\n }\n ],\n \"state\": \"OPEN\",\n \"id\": 11355\n },\n {\n \"reviewers\": [\n {\n \"nickname\": \"csomme\"\n },\n {\n \"nickname\": \"abhin\"\n },\n {\n \"nickname\": \"dstevens\"\n }\n ],\n \"state\": \"MERGED\",\n \"id\": 11347\n },\n {\n \"reviewers\": [\n {\n \"nickname\": \"csomme\"\n },\n {\n \"nickname\": \"jmooring\"\n },\n {\n \"nickname\": \"zdavis\"\n },\n {\n \"nickname\": \"flexbox\"\n }\n ],\n \"state\": \"OPEN\",\n \"id\": 11344\n }\n ]\n}\n```\n" }, { - "body": "\n----\n\n* [Open API Specification](#open-api-specification)\n* [JSON Schema](#json-schema)\n* [Condensed Versus Full Objects](#condensed-versus-full-objects)\n\n____\n\n\n### Open API Specification\n\nBitbucket uses the [Open API Specification](https://openapis.org) (OAI,\nformerly known as Swagger) to describe its APIs. Our OAI specification schema\nis hosted at [https://api.bitbucket.org/swagger.json](https://api.bitbucket.org/swagger.json)\nand serves as the canonical definition and comprehensive declaration of all\navailable endpoints.\n\nThe OAI specification makes writing client applications easier by:\nauto-generating boilerplate code (like data object classes) and dealing with\nauthentication and error handling.\n\nYou can find a comprehensive set of open tools for the OAI specification at:\n[https://github.com/swagger-api](https://github.com/swagger-api).\n\n\n### JSON Schema\n\nBitbucket uses JSON Schema to describe the layout of every type of object\nconsumed or produced by the API. These schemas are collected under the\n`#definitions` element of our swagger.json file.\n\nWhen an endpoint expects an object as part of a POST or PUT, it also expects\nthe object to validate against the JSON schemas. The same applies to objects\nreturned by an endpoint.\n\n\n### Condensed Versus Full Objects\n\nMost objects in Bitbucket come both in \"full\" and \"partial\" representation.\nThe full representation is when all elements are included. This is the layout\nreturned by a resource's `self` location (e.g. `/2.0/repositories/foo/bar`),\nas well as resource collection endpoints (e.g. `/2.0/repositories`).\n\nHowever, Bitbucket objects often embed other objects. For example, a `repository`\nobject embeds a `user` object for its owner. Likewise, a `pullrequest` object\nembeds its `repository` object.\n\nThese related objects are embedded, or inlined, to reduce the \"chatter\" when\nclients make frequent followup API calls to collect information on common,\nrelated information.\n\nEmbedded related objects are typically limited in their fields to avoid such\nobject graphs from becoming too deep and noisy. They often exclude their own\nnested objects in an attempt to strike a balance between performance and\nutility.\n\nAn object's embedded or condensed representation tends to be standardized,\nmeaning the fields included is the same set, regardless of where the object\nwas embedded.\n", - "title": "Schemas and Serialization", "anchor": "serialization", + "title": "Schemas and Serialization", "description": "Learn more about object representations", - "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMjAuNzYyNCAyMDUuNTg2Ij4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLmNscy0xIHsKICAgICAgICBpc29sYXRpb246IGlzb2xhdGU7CiAgICAgIH0KCiAgICAgIC5jbHMtMiwgLmNscy02IHsKICAgICAgICBtaXgtYmxlbmQtbW9kZTogbXVsdGlwbHk7CiAgICAgIH0KCiAgICAgIC5jbHMtMTMsIC5jbHMtMywgLmNscy00LCAuY2xzLTYgewogICAgICAgIGZpbGw6IG5vbmU7CiAgICAgICAgc3Ryb2tlOiAjYzFjN2QwOwogICAgICAgIHN0cm9rZS1saW5lY2FwOiByb3VuZDsKICAgICAgICBzdHJva2UtbWl0ZXJsaW1pdDogMTA7CiAgICAgICAgc3Ryb2tlLXdpZHRoOiAycHg7CiAgICAgIH0KCiAgICAgIC5jbHMtNCB7CiAgICAgICAgc3Ryb2tlLWRhc2hhcnJheTogMy43ODE2IDUuMjk0MzsKICAgICAgfQoKICAgICAgLmNscy01IHsKICAgICAgICBmaWxsOiAjMDA2NWZmOwogICAgICB9CgogICAgICAuY2xzLTYgewogICAgICAgIHN0cm9rZS1kYXNoYXJyYXk6IDMuOTIxOSA1LjQ5MDc7CiAgICAgIH0KCiAgICAgIC5jbHMtNyB7CiAgICAgICAgZmlsbDogIzAwNTJjYzsKICAgICAgfQoKICAgICAgLmNscy04IHsKICAgICAgICBmaWxsOiAjNGM5YWZmOwogICAgICB9CgogICAgICAuY2xzLTkgewogICAgICAgIGZpbGw6ICMwMDQ5YjA7CiAgICAgIH0KCiAgICAgIC5jbHMtMTAgewogICAgICAgIGZpbGw6ICM1N2Q5YTM7CiAgICAgIH0KCiAgICAgIC5jbHMtMTEgewogICAgICAgIGZpbGw6ICM3OWYyYzA7CiAgICAgIH0KCiAgICAgIC5jbHMtMTIgewogICAgICAgIGZpbGw6ICMzNmIzN2U7CiAgICAgIH0KCiAgICAgIC5jbHMtMTMgewogICAgICAgIHN0cm9rZS1kYXNoYXJyYXk6IDMuODY4MSA1LjQxNTQ7CiAgICAgIH0KCiAgICAgIC5jbHMtMTQgewogICAgICAgIGZpbGw6ICM0MjUyNmU7CiAgICAgIH0KCiAgICAgIC5jbHMtMTUgewogICAgICAgIGZpbGw6ICMzNDQ1NjM7CiAgICAgIH0KCiAgICAgIC5jbHMtMTYgewogICAgICAgIGZpbGw6ICM1MDVmNzk7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIDx0aXRsZT5JbnRlZ3JhdGlvbnM8L3RpdGxlPgogIDxnIGNsYXNzPSJjbHMtMSI+CiAgICA8ZyBpZD0iTGF5ZXJfMiIgZGF0YS1uYW1lPSJMYXllciAyIj4KICAgICAgPGcgaWQ9Ik9iamVjdHMiPgogICAgICAgIDxnIGNsYXNzPSJjbHMtMiI+CiAgICAgICAgICA8Zz4KICAgICAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iNzUuMjExNCIgeTE9IjE4Ny44NTczIiB4Mj0iNzcuMDI0OCIgeTI9IjE4Ny4xMTA5Ii8+CiAgICAgICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtNCIgeDE9IjgxLjkyMDUiIHkxPSIxODUuMDk1NiIgeDI9IjEzOC4yMjEyIiB5Mj0iMTYxLjkyMDMiLz4KICAgICAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMTQwLjY2OSIgeTE9IjE2MC45MTI2IiB4Mj0iMTQyLjQ4MjQiIHkyPSIxNjAuMTY2MiIvPgogICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTUiIHBvaW50cz0iMTk0LjUyNiAyNi41NTIgMTc2LjkwMSAzOC4yNDEgMTU5LjI3IDI2LjU1MiAxNzYuOTAxIDE0Ljg3IDE5NC41MjYgMjYuNTUyIi8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTgzLjcxMzUiIHkxPSI0My4yMTg4IiB4Mj0iMTgzLjcxMzUiIHkyPSI5Ny44ODMyIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy03IiBwb2ludHM9IjE3Ni45MDEgMzguMjQxIDE3Ni45MDEgNTguMTY2IDE1OS4yNyA0Ni40NzcgMTU5LjI3IDI2LjU1MiAxNzYuOTAxIDM4LjI0MSIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtOCIgcG9pbnRzPSIxOTQuNTI2IDI2LjU1MiAxOTQuNTI2IDQ2LjQ3NyAxNzYuOTAxIDU4LjE2NiAxNzYuOTAxIDM4LjI0MSAxOTQuNTI2IDI2LjU1MiIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtNiIgeDE9IjQ3Ljk0ODgiIHkxPSI0Mi4yMTg4IiB4Mj0iMTU5LjExNzIiIHkyPSI0Mi4yMTg4Ii8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy01IiBwb2ludHM9IjIyMC43NjIgOTkuNzUyIDE2Ny44MTcgMTM0Ljg2NCAxMTQuODU0IDk5Ljc1MiAxNjcuODE3IDY0LjY1NyAyMjAuNzYyIDk5Ljc1MiIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtOSIgcG9pbnRzPSIxNjcuODE3IDEzNC44NjQgMTY3LjgxNyAxOTQuNzE4IDExNC44NTQgMTU5LjYwNiAxMTQuODU0IDk5Ljc1MiAxNjcuODE3IDEzNC44NjQiLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTgiIHBvaW50cz0iMjIwLjc2MiA5OS43NTIgMjIwLjc2MiAxNTkuNjA2IDE2Ny44MTcgMTk0LjcxOCAxNjcuODE3IDEzNC44NjQgMjIwLjc2MiA5OS43NTIiLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTEwIiBwb2ludHM9IjExMC41NDEgMjEuNjA0IDc3Ljk0OSA0My4yMTkgNDUuMzQ1IDIxLjYwNCA3Ny45NDkgMCAxMTAuNTQxIDIxLjYwNCIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtMTEiIHBvaW50cz0iMTEwLjU0MSAyMS42MDQgMTEwLjU0MSA1OC40NDkgNzcuOTQ5IDgwLjA2NCA3Ny45NDkgNDMuMjE5IDExMC41NDEgMjEuNjA0Ii8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy01IiBwb2ludHM9IjE0MS4xOSAxNDguMDczIDE2Ny44MTMgMTMwLjQxNyAxOTQuNDQ0IDE0OC4wNzMgMTY3LjgxMyAxNjUuNzE5IDE0MS4xOSAxNDguMDczIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy05IiBwb2ludHM9IjE2Ny44MTMgMTMwLjQxNyAxNjcuODEzIDEwMC4zMjEgMTk0LjQ0NCAxMTcuOTc2IDE5NC40NDQgMTQ4LjA3MyAxNjcuODEzIDEzMC40MTciLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTgiIHBvaW50cz0iMTQxLjE5IDE0OC4wNzMgMTQxLjE5IDExNy45NzYgMTY3LjgxMyAxMDAuMzIxIDE2Ny44MTMgMTMwLjQxNyAxNDEuMTkgMTQ4LjA3MyIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtMTIiIHBvaW50cz0iNDUuMzQ1IDIxLjYwNCA0NS4zNDUgNDQuOTg0IDU3LjIzMSA1Mi44NjQgNTcuMjMxIDY2LjI5NiA3Ny45NDkgODAuMDY0IDc3Ljk0OSA0My4yMTkgNDUuMzQ1IDIxLjYwNCIvPgogICAgICAgIDxnIGNsYXNzPSJjbHMtMiI+CiAgICAgICAgICA8Zz4KICAgICAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjQuNjQzOCIgeTE9Ijg2Ljk1NDQiIHgyPSIyNi4wMTU3IiB5Mj0iODUuNTUzMSIvPgogICAgICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTEzIiB4MT0iMjkuODA0IiB5MT0iODEuNjgzNCIgeDI9IjYwLjM4MTEiIHkyPSI1MC40NDkyIi8+CiAgICAgICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjYyLjI3NTIiIHkxPSI0OC41MTQzIiB4Mj0iNjMuNjQ3IiB5Mj0iNDcuMTEzIi8+CiAgICAgICAgICA8L2c+CiAgICAgICAgPC9nPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtNSIgcG9pbnRzPSIzNS4yNTUgODkuNjQ1IDE3LjczNiAxMDEuNDkyIDAgODkuOTYyIDE3LjUyNSA3OC4xMjEgMzUuMjU1IDg5LjY0NSIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtNyIgcG9pbnRzPSIxNy43MzYgMTAxLjQ5MiAxNy45MTUgMTIxLjQxNiAwLjE3OSAxMDkuODg3IDAgODkuOTYyIDE3LjczNiAxMDEuNDkyIi8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMjAuNTg0OSIgeTE9IjEwNS41MzA1IiB4Mj0iNjUuODc0OSIgeTI9IjE3MS4zNjgxIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy04IiBwb2ludHM9IjM1LjI1NSA4OS42NDUgMzUuNDM0IDEwOS41NjkgMTcuOTE1IDEyMS40MTYgMTcuNzM2IDEwMS40OTIgMzUuMjU1IDg5LjY0NSIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtMTQiIHBvaW50cz0iOTIuMzk0IDE3My44MTUgNzQuODc1IDE4NS42NjIgNTcuMTM5IDE3NC4xMzIgNzQuNjY0IDE2Mi4yOTEgOTIuMzk0IDE3My44MTUiLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTE1IiBwb2ludHM9Ijc0Ljg3NSAxODUuNjYyIDc1LjA1NCAyMDUuNTg2IDU3LjMxOSAxOTQuMDU3IDU3LjEzOSAxNzQuMTMyIDc0Ljg3NSAxODUuNjYyIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy0xNiIgcG9pbnRzPSI5Mi4zOTQgMTczLjgxNSA5Mi41NzQgMTkzLjczOSA3NS4wNTQgMjA1LjU4NiA3NC44NzUgMTg1LjY2MiA5Mi4zOTQgMTczLjgxNSIvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K" + "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyMjAuNzYyNCAyMDUuNTg2Ij4KICA8ZGVmcz4KICAgIDxzdHlsZT4KICAgICAgLmNscy0xIHsKICAgICAgICBpc29sYXRpb246IGlzb2xhdGU7CiAgICAgIH0KCiAgICAgIC5jbHMtMiwgLmNscy02IHsKICAgICAgICBtaXgtYmxlbmQtbW9kZTogbXVsdGlwbHk7CiAgICAgIH0KCiAgICAgIC5jbHMtMTMsIC5jbHMtMywgLmNscy00LCAuY2xzLTYgewogICAgICAgIGZpbGw6IG5vbmU7CiAgICAgICAgc3Ryb2tlOiAjYzFjN2QwOwogICAgICAgIHN0cm9rZS1saW5lY2FwOiByb3VuZDsKICAgICAgICBzdHJva2UtbWl0ZXJsaW1pdDogMTA7CiAgICAgICAgc3Ryb2tlLXdpZHRoOiAycHg7CiAgICAgIH0KCiAgICAgIC5jbHMtNCB7CiAgICAgICAgc3Ryb2tlLWRhc2hhcnJheTogMy43ODE2IDUuMjk0MzsKICAgICAgfQoKICAgICAgLmNscy01IHsKICAgICAgICBmaWxsOiAjMDA2NWZmOwogICAgICB9CgogICAgICAuY2xzLTYgewogICAgICAgIHN0cm9rZS1kYXNoYXJyYXk6IDMuOTIxOSA1LjQ5MDc7CiAgICAgIH0KCiAgICAgIC5jbHMtNyB7CiAgICAgICAgZmlsbDogIzAwNTJjYzsKICAgICAgfQoKICAgICAgLmNscy04IHsKICAgICAgICBmaWxsOiAjNGM5YWZmOwogICAgICB9CgogICAgICAuY2xzLTkgewogICAgICAgIGZpbGw6ICMwMDQ5YjA7CiAgICAgIH0KCiAgICAgIC5jbHMtMTAgewogICAgICAgIGZpbGw6ICM1N2Q5YTM7CiAgICAgIH0KCiAgICAgIC5jbHMtMTEgewogICAgICAgIGZpbGw6ICM3OWYyYzA7CiAgICAgIH0KCiAgICAgIC5jbHMtMTIgewogICAgICAgIGZpbGw6ICMzNmIzN2U7CiAgICAgIH0KCiAgICAgIC5jbHMtMTMgewogICAgICAgIHN0cm9rZS1kYXNoYXJyYXk6IDMuODY4MSA1LjQxNTQ7CiAgICAgIH0KCiAgICAgIC5jbHMtMTQgewogICAgICAgIGZpbGw6ICM0MjUyNmU7CiAgICAgIH0KCiAgICAgIC5jbHMtMTUgewogICAgICAgIGZpbGw6ICMzNDQ1NjM7CiAgICAgIH0KCiAgICAgIC5jbHMtMTYgewogICAgICAgIGZpbGw6ICM1MDVmNzk7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgogIDx0aXRsZT5JbnRlZ3JhdGlvbnM8L3RpdGxlPgogIDxnIGNsYXNzPSJjbHMtMSI+CiAgICA8ZyBpZD0iTGF5ZXJfMiIgZGF0YS1uYW1lPSJMYXllciAyIj4KICAgICAgPGcgaWQ9Ik9iamVjdHMiPgogICAgICAgIDxnIGNsYXNzPSJjbHMtMiI+CiAgICAgICAgICA8Zz4KICAgICAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iNzUuMjExNCIgeTE9IjE4Ny44NTczIiB4Mj0iNzcuMDI0OCIgeTI9IjE4Ny4xMTA5Ii8+CiAgICAgICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtNCIgeDE9IjgxLjkyMDUiIHkxPSIxODUuMDk1NiIgeDI9IjEzOC4yMjEyIiB5Mj0iMTYxLjkyMDMiLz4KICAgICAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMTQwLjY2OSIgeTE9IjE2MC45MTI2IiB4Mj0iMTQyLjQ4MjQiIHkyPSIxNjAuMTY2MiIvPgogICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTUiIHBvaW50cz0iMTk0LjUyNiAyNi41NTIgMTc2LjkwMSAzOC4yNDEgMTU5LjI3IDI2LjU1MiAxNzYuOTAxIDE0Ljg3IDE5NC41MjYgMjYuNTUyIi8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMTgzLjcxMzUiIHkxPSI0My4yMTg4IiB4Mj0iMTgzLjcxMzUiIHkyPSI5Ny44ODMyIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy03IiBwb2ludHM9IjE3Ni45MDEgMzguMjQxIDE3Ni45MDEgNTguMTY2IDE1OS4yNyA0Ni40NzcgMTU5LjI3IDI2LjU1MiAxNzYuOTAxIDM4LjI0MSIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtOCIgcG9pbnRzPSIxOTQuNTI2IDI2LjU1MiAxOTQuNTI2IDQ2LjQ3NyAxNzYuOTAxIDU4LjE2NiAxNzYuOTAxIDM4LjI0MSAxOTQuNTI2IDI2LjU1MiIvPgogICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtNiIgeDE9IjQ3Ljk0ODgiIHkxPSI0Mi4yMTg4IiB4Mj0iMTU5LjExNzIiIHkyPSI0Mi4yMTg4Ii8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy01IiBwb2ludHM9IjIyMC43NjIgOTkuNzUyIDE2Ny44MTcgMTM0Ljg2NCAxMTQuODU0IDk5Ljc1MiAxNjcuODE3IDY0LjY1NyAyMjAuNzYyIDk5Ljc1MiIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtOSIgcG9pbnRzPSIxNjcuODE3IDEzNC44NjQgMTY3LjgxNyAxOTQuNzE4IDExNC44NTQgMTU5LjYwNiAxMTQuODU0IDk5Ljc1MiAxNjcuODE3IDEzNC44NjQiLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTgiIHBvaW50cz0iMjIwLjc2MiA5OS43NTIgMjIwLjc2MiAxNTkuNjA2IDE2Ny44MTcgMTk0LjcxOCAxNjcuODE3IDEzNC44NjQgMjIwLjc2MiA5OS43NTIiLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTEwIiBwb2ludHM9IjExMC41NDEgMjEuNjA0IDc3Ljk0OSA0My4yMTkgNDUuMzQ1IDIxLjYwNCA3Ny45NDkgMCAxMTAuNTQxIDIxLjYwNCIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtMTEiIHBvaW50cz0iMTEwLjU0MSAyMS42MDQgMTEwLjU0MSA1OC40NDkgNzcuOTQ5IDgwLjA2NCA3Ny45NDkgNDMuMjE5IDExMC41NDEgMjEuNjA0Ii8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy01IiBwb2ludHM9IjE0MS4xOSAxNDguMDczIDE2Ny44MTMgMTMwLjQxNyAxOTQuNDQ0IDE0OC4wNzMgMTY3LjgxMyAxNjUuNzE5IDE0MS4xOSAxNDguMDczIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy05IiBwb2ludHM9IjE2Ny44MTMgMTMwLjQxNyAxNjcuODEzIDEwMC4zMjEgMTk0LjQ0NCAxMTcuOTc2IDE5NC40NDQgMTQ4LjA3MyAxNjcuODEzIDEzMC40MTciLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTgiIHBvaW50cz0iMTQxLjE5IDE0OC4wNzMgMTQxLjE5IDExNy45NzYgMTY3LjgxMyAxMDAuMzIxIDE2Ny44MTMgMTMwLjQxNyAxNDEuMTkgMTQ4LjA3MyIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtMTIiIHBvaW50cz0iNDUuMzQ1IDIxLjYwNCA0NS4zNDUgNDQuOTg0IDU3LjIzMSA1Mi44NjQgNTcuMjMxIDY2LjI5NiA3Ny45NDkgODAuMDY0IDc3Ljk0OSA0My4yMTkgNDUuMzQ1IDIxLjYwNCIvPgogICAgICAgIDxnIGNsYXNzPSJjbHMtMiI+CiAgICAgICAgICA8Zz4KICAgICAgICAgICAgPGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjQuNjQzOCIgeTE9Ijg2Ljk1NDQiIHgyPSIyNi4wMTU3IiB5Mj0iODUuNTUzMSIvPgogICAgICAgICAgICA8bGluZSBjbGFzcz0iY2xzLTEzIiB4MT0iMjkuODA0IiB5MT0iODEuNjgzNCIgeDI9IjYwLjM4MTEiIHkyPSI1MC40NDkyIi8+CiAgICAgICAgICAgIDxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjYyLjI3NTIiIHkxPSI0OC41MTQzIiB4Mj0iNjMuNjQ3IiB5Mj0iNDcuMTEzIi8+CiAgICAgICAgICA8L2c+CiAgICAgICAgPC9nPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtNSIgcG9pbnRzPSIzNS4yNTUgODkuNjQ1IDE3LjczNiAxMDEuNDkyIDAgODkuOTYyIDE3LjUyNSA3OC4xMjEgMzUuMjU1IDg5LjY0NSIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtNyIgcG9pbnRzPSIxNy43MzYgMTAxLjQ5MiAxNy45MTUgMTIxLjQxNiAwLjE3OSAxMDkuODg3IDAgODkuOTYyIDE3LjczNiAxMDEuNDkyIi8+CiAgICAgICAgPGxpbmUgY2xhc3M9ImNscy02IiB4MT0iMjAuNTg0OSIgeTE9IjEwNS41MzA1IiB4Mj0iNjUuODc0OSIgeTI9IjE3MS4zNjgxIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy04IiBwb2ludHM9IjM1LjI1NSA4OS42NDUgMzUuNDM0IDEwOS41NjkgMTcuOTE1IDEyMS40MTYgMTcuNzM2IDEwMS40OTIgMzUuMjU1IDg5LjY0NSIvPgogICAgICAgIDxwb2x5Z29uIGNsYXNzPSJjbHMtMTQiIHBvaW50cz0iOTIuMzk0IDE3My44MTUgNzQuODc1IDE4NS42NjIgNTcuMTM5IDE3NC4xMzIgNzQuNjY0IDE2Mi4yOTEgOTIuMzk0IDE3My44MTUiLz4KICAgICAgICA8cG9seWdvbiBjbGFzcz0iY2xzLTE1IiBwb2ludHM9Ijc0Ljg3NSAxODUuNjYyIDc1LjA1NCAyMDUuNTg2IDU3LjMxOSAxOTQuMDU3IDU3LjEzOSAxNzQuMTMyIDc0Ljg3NSAxODUuNjYyIi8+CiAgICAgICAgPHBvbHlnb24gY2xhc3M9ImNscy0xNiIgcG9pbnRzPSI5Mi4zOTQgMTczLjgxNSA5Mi41NzQgMTkzLjczOSA3NS4wNTQgMjA1LjU4NiA3NC44NzUgMTg1LjY2MiA5Mi4zOTQgMTczLjgxNSIvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K'", + "body": "\n----\n\n* [Open API Specification](#open-api-specification)\n* [JSON Schema](#json-schema)\n* [Condensed Versus Full Objects](#condensed-versus-full-objects)\n\n____\n\n\n### Open API Specification\n\nBitbucket uses the [Open API Specification](https://openapis.org) (OAI,\nformerly known as Swagger) to describe its APIs. Our OAI specification schema\nis hosted at [https://api.bitbucket.org/swagger.json](https://api.bitbucket.org/swagger.json)\nand serves as the canonical definition and comprehensive declaration of all\navailable endpoints.\n\nThe OAI specification makes writing client applications easier by:\nauto-generating boilerplate code (like data object classes) and dealing with\nauthentication and error handling.\n\nYou can find a comprehensive set of open tools for the OAI specification at:\n[https://github.com/swagger-api](https://github.com/swagger-api).\n\n\n### JSON Schema\n\nBitbucket uses JSON Schema to describe the layout of every type of object\nconsumed or produced by the API. These schemas are collected under the\n`#definitions` element of our swagger.json file.\n\nWhen an endpoint expects an object as part of a POST or PUT, it also expects\nthe object to validate against the JSON schemas. The same applies to objects\nreturned by an endpoint.\n\n\n### Condensed Versus Full Objects\n\nMost objects in Bitbucket come both in \"full\" and \"partial\" representation.\nThe full representation is when all elements are included. This is the layout\nreturned by a resource's `self` location (e.g. `/2.0/repositories/foo/bar`),\nas well as resource collection endpoints (e.g. `/2.0/repositories`).\n\nHowever, Bitbucket objects often embed other objects. For example, a `repository`\nobject embeds a `user` object for its owner. Likewise, a `pullrequest` object\nembeds its `repository` object.\n\nThese related objects are embedded, or inlined, to reduce the \"chatter\" when\nclients make frequent followup API calls to collect information on common,\nrelated information.\n\nEmbedded related objects are typically limited in their fields to avoid such\nobject graphs from becoming too deep and noisy. They often exclude their own\nnested objects in an attempt to strike a balance between performance and\nutility.\n\nAn object's embedded or condensed representation tends to be standardized,\nmeaning the fields included is the same set, regardless of where the object\nwas embedded.\n" }, { - "body": "\nYou should be familiar with REST architecture before writing an integration. Read this overview page to gain a good understanding of Bitbucket's REST implementation.\n\n----\n\n* [URI structure](#uri-structure)\n* [HTTP methods](#http-methods)\n* [UUID](#universally-unique-identifier)\n * [User object and UUID](#user-object-and-uuid)\n * [Repository object and UUID](#repository-object-and-uuid)\n * [Team object and UUID](#team-object-and-uuid)\n* [Standard error responses](#standardized-error-responses)\n* [Standard ISO-8601 timestamps](#standard-iso-8601-timestamps)\n\n----\n\n\n### URI structure\n\nAll Bitbucket Cloud requests start with the `https://api.bitbucket.org/2.0` prefix (for the 2.0 API) and `https://api.bitbucket.org/1.0` prefix (1.0 API).\n\nThe next segment of the URI path depends on the endpoint of the request. For example, using the curl command and the repositories endpoint you can list all the issues on Bitbucket's tutorial repository:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/tutorials/tutorials.bitbucket.org\n```\nGiven a specific endpoint, you can then drill down to a particular aspect or resource of that endpoint. The issues resource on a repository is an example:\n\n```\ncurl https://api.bitbucket.org/1.0/repositories/tutorials/tutorials.bitbucket.org/issues\n```\n\n#### HTTP methods\n\nA given endpoint or resource has a series of actions (or methods) associated with it. The Bitbucket service supports these standard HTTP methods:\n\n| Call | Description |\n|------|-------------|\n| GET | Retrieves information. |\n| PUT | Updates existing information. |\n| POST | Creates new information. |\n| DELETE | Removes existing information. |\n\nFor example, you can call use the POST action on the issues resource and create an issue on the issue tracker.\n\n**Specifying content length**\n\nYou can get a `411 Length Required` response. If this happens, the API requires a Content-Length header but the client is not sending it. You should add the header yourself, for example using the curl client:\n\n```\ncurl -r PUT --header \"Content-Length: 0\" -u user:app_password https://api.bitbucket.org/1.0/emails/rap@atlassian.com\n```\n\n### Universally Unique Identifier\n\nUUID's provide a single point of recognition for users, teams, and repositories. The UUID is distinct from the username, team name, and repository name fields and remains the same even when those fields change. For example when a user changes their username or moves a repository you will need to modify calls which use those identifiers but not if you are pointing to the UUID.\n\n#### UUID examples and structure\n\nUUID's work with both the 1.0 and 2.0 APIs for the user, team, and repository objects. The following examples the following characters are replacements for curly brackets: `%7B` replaces `{` and `%7D` replaces `}`. You will see this structure in the following example sections.\n\n#### User object and UUID\n\nWhen you make a call using either the username or the UUID for that user the response is the same.\n\n**Call with username**:\n\n```\ncurl https://api.bitbucket.org/2.0/users/tutorials\n```\n\n***Call with UUID for the user**:\n\n```\ncurl https://api.bitbucket.org/2.0/users/%7Bc788b2da-b7a2-404c-9e26-d3f077557007%7D\n```\n\n**Response**\n```JSON\n{\n \"username\": \"tutorials\",\n \"nickname\": \"tutorials\",\n \"account_status\": \"active\",\n \"website\": \"https://tutorials.bitbucket.org/\",\n \"display_name\": \"tutorials account\",\n \"uuid\": \"{c788b2da-b7a2-404c-9e26-d3f077557007}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/tutorials\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/tutorials\"\n },\n \"followers\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/followers\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Nov/25/tutorials-avatar-1563784409-6_avatar.png\"\n },\n \"following\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/following\"\n }\n },\n \"created_on\": \"2011-12-20T16:34:07.132459+00:00\",\n \"location\": \"Santa Monica, CA\",\n \"type\": \"user\"\n}\n```\n\n#### Repository object and UUID\n\nOnce you have the UUID for a repository you no longer need a username or team name to make the API call so long as you use an empty field. This helps you resolve repositories no matter if the username or team name changes.\n\n**Call with team name (1team) and repository name (moxie)**:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/1team/moxie\n```\n**Call with UUID and empty field**:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/%7B%7D/%7B21fa9bf8-b5b2-4891-97ed-d590bad0f871%7D\n```\n\n**Call with UUID and teamname**:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/1team/%7B21fa9bf8-b5b2-4891-97ed-d590bad0f871%7D\n```\n\n**Response**\n\n```JSON\n{\n \"created_on\": \"2013-11-08T01:11:03.222520+00:00\",\n \"description\": \"\",\n \"fork_policy\": \"allow_forks\",\n \"full_name\": \"1team/moxie\",\n \"has_issues\": false,\n \"has_wiki\": false,\n \"is_private\": false,\n \"language\": \"\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/1team/moxie/avatar/32/\"\n },\n \"branches\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/refs/branches\"\n },\n \"clone\": [\n {\n \"href\": \"https://bitbucket.org/1team/moxie.git\",\n \"name\": \"https\"\n },\n {\n \"href\": \"ssh://git@bitbucket.org/1team/moxie.git\",\n \"name\": \"ssh\"\n }\n ],\n \"commits\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/commits\"\n },\n \"downloads\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/downloads\"\n },\n \"forks\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/forks\"\n },\n \"hooks\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/hooks\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/1team/moxie\"\n },\n \"pullrequests\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/pullrequests\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie\"\n },\n \"tags\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/refs/tags\"\n },\n \"watchers\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/watchers\"\n }\n },\n \"name\": \"moxie\",\n \"owner\": {\n \"display_name\": \"the team\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/1team/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/1team/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/teams/1team\"\n }\n },\n \"type\": \"team\",\n \"username\": \"1team\",\n \"uuid\": \"{aa559944-83c9-4963-a9a8-69ac8d9cf5d2}\"\n },\n \"project\": {\n \"key\": \"PROJ\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/user/1team/projects/PROJ/avatar/32\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/account/user/1team/projects/PROJ\"\n }\n },\n \"name\": \"Untitled project\",\n \"type\": \"project\",\n \"uuid\": \"{ab52aaeb-16ad-4fb0-bb1d-47e4f00367ff}\"\n },\n \"scm\": \"git\",\n \"size\": 33348,\n \"type\": \"repository\",\n \"updated_on\": \"2013-11-08T01:11:03.263237+00:00\",\n \"uuid\": \"{21fa9bf8-b5b2-4891-97ed-d590bad0f871}\",\n \"website\": \"\"\n}\n```\n\n#### Team object and UUID\n\nThis example shows a call for a list of team members using both the team name and with the UUID for the team object. As the call is unauthenticated in the following example the response object will only show members with public profiles. The response is the same in either case.\n\n**Call with teamname**\n\n```\ncurl https://api.bitbucket.org/2.0/teams/1team/members\n```\n**Call with UUID for team object**\n\n```\ncurl https://api.bitbucket.org/2.0/teams/%7Baa559944-83c9-4963-a9a8-69ac8d9cf5d2%7D/members\n```\n\n**Response**\n\n```JSON\n{\n \"page\": 1,\n \"pagelen\": 50,\n \"size\": 2,\n \"values\": [\n {\n \"created_on\": \"2011-12-20T16:34:07.132459+00:00\",\n \"display_name\": \"tutorials account\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/tutorials/avatar/32/\"\n },\n \"followers\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/followers\"\n },\n \"following\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/following\"\n },\n \"hooks\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/hooks\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/tutorials/\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/tutorials\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials\"\n },\n \"snippets\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/tutorials\"\n }\n },\n \"location\": null,\n \"type\": \"user\",\n \"username\": \"tutorials\",\n \"nickname\": \"tutorials\",\n \"account_status\": \"active\",\n \"uuid\": \"{c788b2da-b7a2-404c-9e26-d3f077557007}\",\n \"website\": \"https://tutorials.bitbucket.org/\"\n },\n {\n \"created_on\": \"2013-12-10T14:44:13+00:00\",\n \"display_name\": \"Dan Stevens [Atlassian]\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/dans9190/avatar/32/\"\n },\n \"followers\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/dans9190/followers\"\n },\n \"following\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/dans9190/following\"\n },\n \"hooks\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/dans9190/hooks\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/dans9190/\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/dans9190\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/dans9190\"\n },\n \"snippets\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/dans9190\"\n }\n },\n \"location\": null,\n \"type\": \"user\",\n \"username\": \"dans9190\",\n \"nickname\": \"dans9190\",\n \"account_status\": \"active\",\n \"uuid\": \"{1cd06601-cd0e-4fce-be03-e9ac226978b7}\",\n \"website\": \"\"\n }\n ]\n}\n```\n\n### Standardized error responses\n\nThe 2.0 API standardizes the error response layout. The 2.0 API serves a JSON\nobject along with the appropriate HTTP status code. The JSON object provides a\ndetailed problem description.\n\n```json\n{\n \"type\": \"error\",\n \"error\": {\n \"message\": \"Bad request\",\n \"fields\": {\n \"src\": [\n \"This field is required.\"\n ]\n },\n \"detail\": \"You must specify a valid source branch when creating a pull request.\",\n \"id\": \"d23a1cc5178f7637f3d9bf2d13824258\",\n \"data\": {\n \"extra\": \"Optional, endpoint-specific data to further augment the error.\"\n }\n }\n}\n```\n\nThis object contains an error element which contains the following nested\nelements:\n\n| Element | Description |\n|---------|-------------|\n| message | A short description of the problem. This element is always present. Its value may be localized. |\n| fields | This optional element is used in response to POST or PUT operations in which clients have provided invalid input. It contains a list of one or more client-provided fields that failed validation. The values may be localized. |\n| detail | An optional detailed explanation of the failure. Its value may be localized.\n| id | An optional unique error identifier that identifies the error in Bitbucket's logging system. If you feel you hit a bug in an API and this field is provided, please mention it if you decide to contact support as it will greatly help us narrow down the problem. |\n\n### Standard ISO-8601 timestamps\n\nAll 2.0 APIs use standardized ISO-8601 timestamps. In most cases, our APIs return UTC timestamps and for these, the timezone offset part will be 00:00. In rare cases where the original localized timestamp has significance, the timezone offset may identify the event's original timezone.\n", - "title": "URI, UUID, and structures", "anchor": "uri-uuid", + "title": "URI, UUID, and structures", "description": "URL's, UUID's, errors, and timestamps", - "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTc5LjI2IDE3Ny42NSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50KTt9LmNscy0ye2ZpbGw6IzA5MWU0Mjt9LmNscy0xMCwuY2xzLTExLC5jbHMtMywuY2xzLTQsLmNscy05e2ZpbGw6bm9uZTt9LmNscy0ze3N0cm9rZTojOTljMWZmO30uY2xzLTMsLmNscy00e3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MDt9LmNscy0xMiwuY2xzLTQsLmNscy05e3N0cm9rZTojZTVlOGVjO30uY2xzLTV7ZmlsbDojMzQ0NTYzO30uY2xzLTZ7ZmlsbDojZmY4YjAwO30uY2xzLTd7ZmlsbDojZmZjNDAwO30uY2xzLTh7ZmlsbDojMDA2NWZmO30uY2xzLTEwLC5jbHMtMTEsLmNscy0xMiwuY2xzLTEzLC5jbHMtOXtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2Utd2lkdGg6MnB4O30uY2xzLTEwe3N0cm9rZTojZmZhYjAwO30uY2xzLTExLC5jbHMtMTN7c3Ryb2tlOiMwMDY1ZmY7fS5jbHMtMTJ7ZmlsbDojOTljMWZmO30uY2xzLTEze2ZpbGw6I2U1ZThlYzt9PC9zdHlsZT48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudCIgeDE9IjAuNCIgeTE9IjE3OC4wNSIgeDI9IjE3OC44NSIgeTI9Ii0wLjQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMwOTFlNDIiLz48c3RvcCBvZmZzZXQ9IjAuMDciIHN0b3AtY29sb3I9IiMwZDIyNDUiLz48c3RvcCBvZmZzZXQ9IjAuNDkiIHN0b3AtY29sb3I9IiMxZjMyNTMiLz48c3RvcCBvZmZzZXQ9IjAuNzkiIHN0b3AtY29sb3I9IiMyNTM4NTgiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48dGl0bGU+Q29kZTwvdGl0bGU+PGcgaWQ9IkxheWVyXzIiIGRhdGEtbmFtZT0iTGF5ZXIgMiI+PGcgaWQ9IlNvZnR3YXJlIj48cmVjdCBpZD0iX1JlY3RhbmdsZV8iIGRhdGEtbmFtZT0iJmx0O1JlY3RhbmdsZSZndDsiIGNsYXNzPSJjbHMtMSIgd2lkdGg9IjE3OS4yNiIgaGVpZ2h0PSIxNzcuNjUiLz48cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xNzkuMjYsMjYuNjRIMFY3MS43NUExNjYuNDEsMTY2LjQxLDAsMCwwLDYzLjI0LDU5LjUxYTE4OC40MSwxODguNDEsMCwwLDAsMTcuMzktOC4zNmMxOC40NC05LjQzLDQ4LjM3LTE3LjksOTguNjItMTNabS0xNTkuNDQsMzRoMFptMC0xNC4wOGgwWiIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjE5LjgxIiB5MT0iNDYuNTgiIHgyPSIyNS4wNyIgeTI9IjQ2LjU4Ii8+PGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjUuMDciIHkxPSI2MC42NiIgeDI9IjE5LjgxIiB5Mj0iNjAuNjYiLz48bGluZSBjbGFzcz0iY2xzLTMiIHgxPSIxOS44MSIgeTE9Ijc0Ljc0IiB4Mj0iMjUuMDciIHkyPSI3NC43NCIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjI1LjA3IiB5MT0iODguODIiIHgyPSIxOS44MSIgeTI9Ijg4LjgyIi8+PGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjUuMDciIHkxPSIxMDIuODkiIHgyPSIxOS44MSIgeTI9IjEwMi44OSIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjI1LjA3IiB5MT0iMTE2Ljk3IiB4Mj0iMTkuODEiIHkyPSIxMTYuOTciLz48bGluZSBjbGFzcz0iY2xzLTMiIHgxPSIyNS4wNyIgeTE9IjEzMS4wNSIgeDI9IjE5LjgxIiB5Mj0iMTMxLjA1Ii8+PGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjUuMDciIHkxPSIxNDUuMTMiIHgyPSIxOS44MSIgeTI9IjE0NS4xMyIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjI1LjA3IiB5MT0iMTU5LjIxIiB4Mj0iMTkuODEiIHkyPSIxNTkuMjEiLz48bGluZSBjbGFzcz0iY2xzLTQiIHgxPSI1NS44OSIgeTE9IjEzMS4wNSIgeDI9IjkzLjk5IiB5Mj0iMTMxLjA1Ii8+PGxpbmUgY2xhc3M9ImNscy00IiB4MT0iNTUuODkiIHkxPSIxNDUuMTMiIHgyPSIxMzkuNjciIHkyPSIxNDUuMTMiLz48cmVjdCBjbGFzcz0iY2xzLTUiIHdpZHRoPSIxNzkuMjYiIGhlaWdodD0iMjYuNjQiLz48Y2lyY2xlIGNsYXNzPSJjbHMtNiIgY3g9IjEzLjUiIGN5PSIxMi4wOCIgcj0iNS4xMSIvPjxjaXJjbGUgY2xhc3M9ImNscy03IiBjeD0iMzAuMTgiIGN5PSIxMi4wOCIgcj0iNS4xMSIvPjxjaXJjbGUgY2xhc3M9ImNscy04IiBjeD0iNDYuODYiIGN5PSIxMi4wOCIgcj0iNS4xMSIvPjxwYXRoIGNsYXNzPSJjbHMtOSIgZD0iTTc1LjQxLDg4LjgyIi8+PHBhdGggY2xhc3M9ImNscy05IiBkPSJNMzIuODksODguODIiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iMzIuODkiIHkxPSI3NC43NCIgeDI9Ijc1LjQxIiB5Mj0iNzQuNzQiLz48bGluZSBjbGFzcz0iY2xzLTExIiB4MT0iMzIuODkiIHkxPSI2MC42NiIgeDI9Ijc1LjQxIiB5Mj0iNjAuNjYiLz48bGluZSBjbGFzcz0iY2xzLTkiIHgxPSIzMi44OSIgeTE9IjQ2LjU4IiB4Mj0iNTUuODkiIHkyPSI0Ni41OCIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjQ2LjU4IiB4Mj0iMjUuMDciIHkyPSI0Ni41OCIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjYwLjY2IiB4Mj0iMjUuMDciIHkyPSI2MC42NiIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9Ijc0Ljc0IiB4Mj0iMjUuMDciIHkyPSI3NC43NCIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9Ijg4LjgyIiB4Mj0iMjUuMDciIHkyPSI4OC44MiIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjEwMi44OSIgeDI9IjI1LjA3IiB5Mj0iMTAyLjg5Ii8+PGxpbmUgY2xhc3M9ImNscy0xMiIgeDE9IjE5LjgxIiB5MT0iMTE2Ljk3IiB4Mj0iMjUuMDciIHkyPSIxMTYuOTciLz48bGluZSBjbGFzcz0iY2xzLTEyIiB4MT0iMTkuODEiIHkxPSIxMzEuMDUiIHgyPSIyNS4wNyIgeTI9IjEzMS4wNSIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjE0NS4xMyIgeDI9IjI1LjA3IiB5Mj0iMTQ1LjEzIi8+PGxpbmUgY2xhc3M9ImNscy0xMiIgeDE9IjE5LjgxIiB5MT0iMTU5LjIxIiB4Mj0iMjUuMDciIHkyPSIxNTkuMjEiLz48bGluZSBpZD0iX0xpbmVfIiBkYXRhLW5hbWU9IiZsdDtMaW5lJmd0OyIgY2xhc3M9ImNscy0xMCIgeDE9Ijg0LjI0IiB5MT0iMTE2Ljk3IiB4Mj0iMTU2LjY1IiB5Mj0iMTE2Ljk3Ii8+PHBhdGggY2xhc3M9ImNscy05IiBkPSJNMzIuODksMTE3aDBaIi8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjEwMiIgeTE9IjEzMS4wNSIgeDI9IjE2My42MiIgeTI9IjEzMS4wNSIvPjxsaW5lIGNsYXNzPSJjbHMtMTMiIHgxPSI1NS44OSIgeTE9IjEzMS4wNSIgeDI9IjkzLjk5IiB5Mj0iMTMxLjA1Ii8+PGxpbmUgY2xhc3M9ImNscy0xMyIgeDE9IjU1Ljg5IiB5MT0iMTQ1LjEzIiB4Mj0iMTM5LjY3IiB5Mj0iMTQ1LjEzIi8+PGxpbmUgY2xhc3M9ImNscy05IiB4MT0iNzguOSIgeTE9IjE1OS4yMSIgeDI9IjExMy41MSIgeTI9IjE1OS4yMSIvPjxsaW5lIGNsYXNzPSJjbHMtOSIgeDE9IjU5LjYxIiB5MT0iODguODIiIHgyPSI5OS4zMyIgeTI9Ijg4LjgyIi8+PGxpbmUgY2xhc3M9ImNscy05IiB4MT0iNTkuNjEiIHkxPSIxMDIuODkiIHgyPSI5OS4zMyIgeTI9IjEwMi44OSIvPjxjaXJjbGUgY2xhc3M9ImNscy02IiBjeD0iMTMuNSIgY3k9IjEyLjA4IiByPSI1LjExIi8+PGNpcmNsZSBjbGFzcz0iY2xzLTciIGN4PSIzMC4xOCIgY3k9IjEyLjA4IiByPSI1LjExIi8+PGNpcmNsZSBjbGFzcz0iY2xzLTgiIGN4PSI0Ni44NiIgY3k9IjEyLjA4IiByPSI1LjExIi8+PC9nPjwvZz48L3N2Zz4=" + "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTc5LjI2IDE3Ny42NSI+PGRlZnM+PHN0eWxlPi5jbHMtMXtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50KTt9LmNscy0ye2ZpbGw6IzA5MWU0Mjt9LmNscy0xMCwuY2xzLTExLC5jbHMtMywuY2xzLTQsLmNscy05e2ZpbGw6bm9uZTt9LmNscy0ze3N0cm9rZTojOTljMWZmO30uY2xzLTMsLmNscy00e3N0cm9rZS1saW5lY2FwOnJvdW5kO3N0cm9rZS1saW5lam9pbjpyb3VuZDtzdHJva2Utd2lkdGg6MDt9LmNscy0xMiwuY2xzLTQsLmNscy05e3N0cm9rZTojZTVlOGVjO30uY2xzLTV7ZmlsbDojMzQ0NTYzO30uY2xzLTZ7ZmlsbDojZmY4YjAwO30uY2xzLTd7ZmlsbDojZmZjNDAwO30uY2xzLTh7ZmlsbDojMDA2NWZmO30uY2xzLTEwLC5jbHMtMTEsLmNscy0xMiwuY2xzLTEzLC5jbHMtOXtzdHJva2UtbWl0ZXJsaW1pdDoxMDtzdHJva2Utd2lkdGg6MnB4O30uY2xzLTEwe3N0cm9rZTojZmZhYjAwO30uY2xzLTExLC5jbHMtMTN7c3Ryb2tlOiMwMDY1ZmY7fS5jbHMtMTJ7ZmlsbDojOTljMWZmO30uY2xzLTEze2ZpbGw6I2U1ZThlYzt9PC9zdHlsZT48bGluZWFyR3JhZGllbnQgaWQ9ImxpbmVhci1ncmFkaWVudCIgeDE9IjAuNCIgeTE9IjE3OC4wNSIgeDI9IjE3OC44NSIgeTI9Ii0wLjQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMwOTFlNDIiLz48c3RvcCBvZmZzZXQ9IjAuMDciIHN0b3AtY29sb3I9IiMwZDIyNDUiLz48c3RvcCBvZmZzZXQ9IjAuNDkiIHN0b3AtY29sb3I9IiMxZjMyNTMiLz48c3RvcCBvZmZzZXQ9IjAuNzkiIHN0b3AtY29sb3I9IiMyNTM4NTgiLz48L2xpbmVhckdyYWRpZW50PjwvZGVmcz48dGl0bGU+Q29kZTwvdGl0bGU+PGcgaWQ9IkxheWVyXzIiIGRhdGEtbmFtZT0iTGF5ZXIgMiI+PGcgaWQ9IlNvZnR3YXJlIj48cmVjdCBpZD0iX1JlY3RhbmdsZV8iIGRhdGEtbmFtZT0iJmx0O1JlY3RhbmdsZSZndDsiIGNsYXNzPSJjbHMtMSIgd2lkdGg9IjE3OS4yNiIgaGVpZ2h0PSIxNzcuNjUiLz48cGF0aCBjbGFzcz0iY2xzLTIiIGQ9Ik0xNzkuMjYsMjYuNjRIMFY3MS43NUExNjYuNDEsMTY2LjQxLDAsMCwwLDYzLjI0LDU5LjUxYTE4OC40MSwxODguNDEsMCwwLDAsMTcuMzktOC4zNmMxOC40NC05LjQzLDQ4LjM3LTE3LjksOTguNjItMTNabS0xNTkuNDQsMzRoMFptMC0xNC4wOGgwWiIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjE5LjgxIiB5MT0iNDYuNTgiIHgyPSIyNS4wNyIgeTI9IjQ2LjU4Ii8+PGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjUuMDciIHkxPSI2MC42NiIgeDI9IjE5LjgxIiB5Mj0iNjAuNjYiLz48bGluZSBjbGFzcz0iY2xzLTMiIHgxPSIxOS44MSIgeTE9Ijc0Ljc0IiB4Mj0iMjUuMDciIHkyPSI3NC43NCIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjI1LjA3IiB5MT0iODguODIiIHgyPSIxOS44MSIgeTI9Ijg4LjgyIi8+PGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjUuMDciIHkxPSIxMDIuODkiIHgyPSIxOS44MSIgeTI9IjEwMi44OSIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjI1LjA3IiB5MT0iMTE2Ljk3IiB4Mj0iMTkuODEiIHkyPSIxMTYuOTciLz48bGluZSBjbGFzcz0iY2xzLTMiIHgxPSIyNS4wNyIgeTE9IjEzMS4wNSIgeDI9IjE5LjgxIiB5Mj0iMTMxLjA1Ii8+PGxpbmUgY2xhc3M9ImNscy0zIiB4MT0iMjUuMDciIHkxPSIxNDUuMTMiIHgyPSIxOS44MSIgeTI9IjE0NS4xMyIvPjxsaW5lIGNsYXNzPSJjbHMtMyIgeDE9IjI1LjA3IiB5MT0iMTU5LjIxIiB4Mj0iMTkuODEiIHkyPSIxNTkuMjEiLz48bGluZSBjbGFzcz0iY2xzLTQiIHgxPSI1NS44OSIgeTE9IjEzMS4wNSIgeDI9IjkzLjk5IiB5Mj0iMTMxLjA1Ii8+PGxpbmUgY2xhc3M9ImNscy00IiB4MT0iNTUuODkiIHkxPSIxNDUuMTMiIHgyPSIxMzkuNjciIHkyPSIxNDUuMTMiLz48cmVjdCBjbGFzcz0iY2xzLTUiIHdpZHRoPSIxNzkuMjYiIGhlaWdodD0iMjYuNjQiLz48Y2lyY2xlIGNsYXNzPSJjbHMtNiIgY3g9IjEzLjUiIGN5PSIxMi4wOCIgcj0iNS4xMSIvPjxjaXJjbGUgY2xhc3M9ImNscy03IiBjeD0iMzAuMTgiIGN5PSIxMi4wOCIgcj0iNS4xMSIvPjxjaXJjbGUgY2xhc3M9ImNscy04IiBjeD0iNDYuODYiIGN5PSIxMi4wOCIgcj0iNS4xMSIvPjxwYXRoIGNsYXNzPSJjbHMtOSIgZD0iTTc1LjQxLDg4LjgyIi8+PHBhdGggY2xhc3M9ImNscy05IiBkPSJNMzIuODksODguODIiLz48bGluZSBjbGFzcz0iY2xzLTEwIiB4MT0iMzIuODkiIHkxPSI3NC43NCIgeDI9Ijc1LjQxIiB5Mj0iNzQuNzQiLz48bGluZSBjbGFzcz0iY2xzLTExIiB4MT0iMzIuODkiIHkxPSI2MC42NiIgeDI9Ijc1LjQxIiB5Mj0iNjAuNjYiLz48bGluZSBjbGFzcz0iY2xzLTkiIHgxPSIzMi44OSIgeTE9IjQ2LjU4IiB4Mj0iNTUuODkiIHkyPSI0Ni41OCIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjQ2LjU4IiB4Mj0iMjUuMDciIHkyPSI0Ni41OCIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjYwLjY2IiB4Mj0iMjUuMDciIHkyPSI2MC42NiIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9Ijc0Ljc0IiB4Mj0iMjUuMDciIHkyPSI3NC43NCIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9Ijg4LjgyIiB4Mj0iMjUuMDciIHkyPSI4OC44MiIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjEwMi44OSIgeDI9IjI1LjA3IiB5Mj0iMTAyLjg5Ii8+PGxpbmUgY2xhc3M9ImNscy0xMiIgeDE9IjE5LjgxIiB5MT0iMTE2Ljk3IiB4Mj0iMjUuMDciIHkyPSIxMTYuOTciLz48bGluZSBjbGFzcz0iY2xzLTEyIiB4MT0iMTkuODEiIHkxPSIxMzEuMDUiIHgyPSIyNS4wNyIgeTI9IjEzMS4wNSIvPjxsaW5lIGNsYXNzPSJjbHMtMTIiIHgxPSIxOS44MSIgeTE9IjE0NS4xMyIgeDI9IjI1LjA3IiB5Mj0iMTQ1LjEzIi8+PGxpbmUgY2xhc3M9ImNscy0xMiIgeDE9IjE5LjgxIiB5MT0iMTU5LjIxIiB4Mj0iMjUuMDciIHkyPSIxNTkuMjEiLz48bGluZSBpZD0iX0xpbmVfIiBkYXRhLW5hbWU9IiZsdDtMaW5lJmd0OyIgY2xhc3M9ImNscy0xMCIgeDE9Ijg0LjI0IiB5MT0iMTE2Ljk3IiB4Mj0iMTU2LjY1IiB5Mj0iMTE2Ljk3Ii8+PHBhdGggY2xhc3M9ImNscy05IiBkPSJNMzIuODksMTE3aDBaIi8+PGxpbmUgY2xhc3M9ImNscy0xMCIgeDE9IjEwMiIgeTE9IjEzMS4wNSIgeDI9IjE2My42MiIgeTI9IjEzMS4wNSIvPjxsaW5lIGNsYXNzPSJjbHMtMTMiIHgxPSI1NS44OSIgeTE9IjEzMS4wNSIgeDI9IjkzLjk5IiB5Mj0iMTMxLjA1Ii8+PGxpbmUgY2xhc3M9ImNscy0xMyIgeDE9IjU1Ljg5IiB5MT0iMTQ1LjEzIiB4Mj0iMTM5LjY3IiB5Mj0iMTQ1LjEzIi8+PGxpbmUgY2xhc3M9ImNscy05IiB4MT0iNzguOSIgeTE9IjE1OS4yMSIgeDI9IjExMy41MSIgeTI9IjE1OS4yMSIvPjxsaW5lIGNsYXNzPSJjbHMtOSIgeDE9IjU5LjYxIiB5MT0iODguODIiIHgyPSI5OS4zMyIgeTI9Ijg4LjgyIi8+PGxpbmUgY2xhc3M9ImNscy05IiB4MT0iNTkuNjEiIHkxPSIxMDIuODkiIHgyPSI5OS4zMyIgeTI9IjEwMi44OSIvPjxjaXJjbGUgY2xhc3M9ImNscy02IiBjeD0iMTMuNSIgY3k9IjEyLjA4IiByPSI1LjExIi8+PGNpcmNsZSBjbGFzcz0iY2xzLTciIGN4PSIzMC4xOCIgY3k9IjEyLjA4IiByPSI1LjExIi8+PGNpcmNsZSBjbGFzcz0iY2xzLTgiIGN4PSI0Ni44NiIgY3k9IjEyLjA4IiByPSI1LjExIi8+PC9nPjwvZz48L3N2Zz4='", + "body": "\nYou should be familiar with REST architecture before writing an integration. Read this overview page to gain a good understanding of Bitbucket's REST implementation.\n\n----\n\n* [URI structure](#uri-structure)\n* [HTTP methods](#http-methods)\n* [UUID](#universally-unique-identifier)\n * [User object and UUID](#user-object-and-uuid)\n * [Repository object and UUID](#repository-object-and-uuid)\n * [Team object and UUID](#team-object-and-uuid)\n* [Standard error responses](#standardized-error-responses)\n* [Standard ISO-8601 timestamps](#standard-iso-8601-timestamps)\n\n----\n\n\n### URI structure\n\nAll Bitbucket Cloud requests start with the `https://api.bitbucket.org/2.0` prefix (for the 2.0 API) and `https://api.bitbucket.org/1.0` prefix (1.0 API).\n\nThe next segment of the URI path depends on the endpoint of the request. For example, using the curl command and the repositories endpoint you can list all the issues on Bitbucket's tutorial repository:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/tutorials/tutorials.bitbucket.org\n```\nGiven a specific endpoint, you can then drill down to a particular aspect or resource of that endpoint. The issues resource on a repository is an example:\n\n```\ncurl https://api.bitbucket.org/1.0/repositories/tutorials/tutorials.bitbucket.org/issues\n```\n\n#### HTTP methods\n\nA given endpoint or resource has a series of actions (or methods) associated with it. The Bitbucket service supports these standard HTTP methods:\n\n| Call | Description |\n|------|-------------|\n| GET | Retrieves information. |\n| PUT | Updates existing information. |\n| POST | Creates new information. |\n| DELETE | Removes existing information. |\n\nFor example, you can call use the POST action on the issues resource and create an issue on the issue tracker.\n\n**Specifying content length**\n\nYou can get a `411 Length Required` response. If this happens, the API requires a Content-Length header but the client is not sending it. You should add the header yourself, for example using the curl client:\n\n```\ncurl -r PUT --header \"Content-Length: 0\" -u user:app_password https://api.bitbucket.org/1.0/emails/rap@atlassian.com\n```\n\n### Universally Unique Identifier\n\nUUID's provide a single point of recognition for users, teams, and repositories. The UUID is distinct from the username, team name, and repository name fields and remains the same even when those fields change. For example when a user changes their username or moves a repository you will need to modify calls which use those identifiers but not if you are pointing to the UUID.\n\n#### UUID examples and structure\n\nUUID's work with both the 1.0 and 2.0 APIs for the user, team, and repository objects. The following examples the following characters are replacements for curly brackets: `%7B` replaces `{` and `%7D` replaces `}`. You will see this structure in the following example sections.\n\n#### User object and UUID\n\nWhen you make a call using either the username or the UUID for that user the response is the same.\n\n**Call with username**:\n\n```\ncurl https://api.bitbucket.org/2.0/users/tutorials\n```\n\n***Call with UUID for the user**:\n\n```\ncurl https://api.bitbucket.org/2.0/users/%7Bc788b2da-b7a2-404c-9e26-d3f077557007%7D\n```\n\n**Response**\n```JSON\n{\n \"username\": \"tutorials\",\n \"nickname\": \"tutorials\",\n \"account_status\": \"active\",\n \"website\": \"https://tutorials.bitbucket.org/\",\n \"display_name\": \"tutorials account\",\n \"uuid\": \"{c788b2da-b7a2-404c-9e26-d3f077557007}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/tutorials\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/tutorials\"\n },\n \"followers\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/followers\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Nov/25/tutorials-avatar-1563784409-6_avatar.png\"\n },\n \"following\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/following\"\n }\n },\n \"created_on\": \"2011-12-20T16:34:07.132459+00:00\",\n \"location\": \"Santa Monica, CA\",\n \"type\": \"user\"\n}\n```\n\n#### Repository object and UUID\n\nOnce you have the UUID for a repository you no longer need a username or team name to make the API call so long as you use an empty field. This helps you resolve repositories no matter if the username or team name changes.\n\n**Call with team name (1team) and repository name (moxie)**:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/1team/moxie\n```\n**Call with UUID and empty field**:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/%7B%7D/%7B21fa9bf8-b5b2-4891-97ed-d590bad0f871%7D\n```\n\n**Call with UUID and teamname**:\n\n```\ncurl https://api.bitbucket.org/2.0/repositories/1team/%7B21fa9bf8-b5b2-4891-97ed-d590bad0f871%7D\n```\n\n**Response**\n\n```JSON\n{\n \"created_on\": \"2013-11-08T01:11:03.222520+00:00\",\n \"description\": \"\",\n \"fork_policy\": \"allow_forks\",\n \"full_name\": \"1team/moxie\",\n \"has_issues\": false,\n \"has_wiki\": false,\n \"is_private\": false,\n \"language\": \"\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/1team/moxie/avatar/32/\"\n },\n \"branches\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/refs/branches\"\n },\n \"clone\": [\n {\n \"href\": \"https://bitbucket.org/1team/moxie.git\",\n \"name\": \"https\"\n },\n {\n \"href\": \"ssh://git@bitbucket.org/1team/moxie.git\",\n \"name\": \"ssh\"\n }\n ],\n \"commits\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/commits\"\n },\n \"downloads\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/downloads\"\n },\n \"forks\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/forks\"\n },\n \"hooks\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/hooks\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/1team/moxie\"\n },\n \"pullrequests\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/pullrequests\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie\"\n },\n \"tags\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/refs/tags\"\n },\n \"watchers\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/1team/moxie/watchers\"\n }\n },\n \"name\": \"moxie\",\n \"owner\": {\n \"display_name\": \"the team\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/1team/avatar/32/\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/1team/\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/teams/1team\"\n }\n },\n \"type\": \"team\",\n \"username\": \"1team\",\n \"uuid\": \"{aa559944-83c9-4963-a9a8-69ac8d9cf5d2}\"\n },\n \"project\": {\n \"key\": \"PROJ\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/user/1team/projects/PROJ/avatar/32\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/account/user/1team/projects/PROJ\"\n }\n },\n \"name\": \"Untitled project\",\n \"type\": \"project\",\n \"uuid\": \"{ab52aaeb-16ad-4fb0-bb1d-47e4f00367ff}\"\n },\n \"scm\": \"git\",\n \"size\": 33348,\n \"type\": \"repository\",\n \"updated_on\": \"2013-11-08T01:11:03.263237+00:00\",\n \"uuid\": \"{21fa9bf8-b5b2-4891-97ed-d590bad0f871}\",\n \"website\": \"\"\n}\n```\n\n#### Team object and UUID\n\nThis example shows a call for a list of team members using both the team name and with the UUID for the team object. As the call is unauthenticated in the following example the response object will only show members with public profiles. The response is the same in either case.\n\n**Call with teamname**\n\n```\ncurl https://api.bitbucket.org/2.0/teams/1team/members\n```\n**Call with UUID for team object**\n\n```\ncurl https://api.bitbucket.org/2.0/teams/%7Baa559944-83c9-4963-a9a8-69ac8d9cf5d2%7D/members\n```\n\n**Response**\n\n```JSON\n{\n \"page\": 1,\n \"pagelen\": 50,\n \"size\": 2,\n \"values\": [\n {\n \"created_on\": \"2011-12-20T16:34:07.132459+00:00\",\n \"display_name\": \"tutorials account\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/tutorials/avatar/32/\"\n },\n \"followers\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/followers\"\n },\n \"following\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/following\"\n },\n \"hooks\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/hooks\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/tutorials/\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/tutorials\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials\"\n },\n \"snippets\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/tutorials\"\n }\n },\n \"location\": null,\n \"type\": \"user\",\n \"username\": \"tutorials\",\n \"nickname\": \"tutorials\",\n \"account_status\": \"active\",\n \"uuid\": \"{c788b2da-b7a2-404c-9e26-d3f077557007}\",\n \"website\": \"https://tutorials.bitbucket.org/\"\n },\n {\n \"created_on\": \"2013-12-10T14:44:13+00:00\",\n \"display_name\": \"Dan Stevens [Atlassian]\",\n \"links\": {\n \"avatar\": {\n \"href\": \"https://bitbucket.org/account/dans9190/avatar/32/\"\n },\n \"followers\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/dans9190/followers\"\n },\n \"following\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/dans9190/following\"\n },\n \"hooks\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/dans9190/hooks\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/dans9190/\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/dans9190\"\n },\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/dans9190\"\n },\n \"snippets\": {\n \"href\": \"https://api.bitbucket.org/2.0/snippets/dans9190\"\n }\n },\n \"location\": null,\n \"type\": \"user\",\n \"username\": \"dans9190\",\n \"nickname\": \"dans9190\",\n \"account_status\": \"active\",\n \"uuid\": \"{1cd06601-cd0e-4fce-be03-e9ac226978b7}\",\n \"website\": \"\"\n }\n ]\n}\n```\n\n### Standardized error responses\n\nThe 2.0 API standardizes the error response layout. The 2.0 API serves a JSON\nobject along with the appropriate HTTP status code. The JSON object provides a\ndetailed problem description.\n\n```json\n{\n \"type\": \"error\",\n \"error\": {\n \"message\": \"Bad request\",\n \"fields\": {\n \"src\": [\n \"This field is required.\"\n ]\n },\n \"detail\": \"You must specify a valid source branch when creating a pull request.\",\n \"id\": \"d23a1cc5178f7637f3d9bf2d13824258\",\n \"data\": {\n \"extra\": \"Optional, endpoint-specific data to further augment the error.\"\n }\n }\n}\n```\n\nThis object contains an error element which contains the following nested\nelements:\n\n| Element | Description |\n|---------|-------------|\n| message | A short description of the problem. This element is always present. Its value may be localized. |\n| fields | This optional element is used in response to POST or PUT operations in which clients have provided invalid input. It contains a list of one or more client-provided fields that failed validation. The values may be localized. |\n| detail | An optional detailed explanation of the failure. Its value may be localized.\n| id | An optional unique error identifier that identifies the error in Bitbucket's logging system. If you feel you hit a bug in an API and this field is provided, please mention it if you decide to contact support as it will greatly help us narrow down the problem. |\n\n### Standard ISO-8601 timestamps\n\nAll 2.0 APIs use standardized ISO-8601 timestamps. In most cases, our APIs return UTC timestamps and for these, the timezone offset part will be 00:00. In rare cases where the original localized timestamp has significance, the timezone offset may identify the event's original timezone.\n" }, { - "body": "\nThis section describes [Cross-origin resource sharing](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) (CORS), what content types we support in requests and responses, and hyperlinking resources in each json responses.\n\n\n----\n\n* [CORS](#cors)\n* [Supported content types](#supported-content-types)\n* [Resource links](#resource-links)\n\n----\n\n### Cors\n\nThe Bitbucket API supports Cross-origin resource sharing to allow requests for restricted resources across domains. For more information you can refer to:\n\n* [Wikipedia article on CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)\n* [W3C CORS recommendation](https://www.w3.org/TR/cors/)\n\nSending a general request from the api to bitbucket.com:\n\n`curl -i https://api.bitbucket.org -H \"origin: http://bitbucket.com\"`\n\nGives this result:\n\n HTTP/1.1 302 FOUND\n Server: nginx/1.6.2\n Vary: Cookie\n Cache-Control: max-age=900\n Content-Type: text/html; charset=utf-8\n Strict-Transport-Security: max-age=31536000\n Date: Tue, 21 Jun 2016 17:54:37 GMT\n Location: http://confluence.atlassian.com/x/IYBGDQ\n X-Served-By: app-110\n X-Static-Version: 2c820eb0d2b3\n ETag: \"d41d8cd98f00b204e9800998ecf8427e\"\n X-Content-Type-Options: nosniff\n X-Render-Time: 0.00379920005798\n Connection: Keep-Alive\n X-Version: 2c820eb0d2b3\n X-Frame-Options: SAMEORIGIN\n X-Request-Count: 383\n X-Cache-Info: cached\n Content-Length: 0\n\nSending the same request with the CORS check -X OPTIONS in the call:\n\n`curl -i https://api.bitbucket.org -H \"origin: http://bitbucket.com\" -X OPTIONS`\n\nGives this result:\n\n HTTP/1.1 302 FOUND\n Server: nginx/1.6.2\n Vary: Cookie\n Cache-Control: max-age=900\n Content-Type: text/html; charset=utf-8\n Access-Control-Expose-Headers: Accept-Ranges, Content-Encoding, Content-Length, Content-Type, ETag, Last-Modified\n Strict-Transport-Security: max-age=31536000\n Date: Tue, 21 Jun 2016 18:04:30 GMT\n Access-Control-Max-Age: 86400\n Location: http://confluence.atlassian.com/x/IYBGDQ\n X-Served-By: app-111\n Access-Control-Allow-Origin: *\n X-Static-Version: 2c820eb0d2b3\n ETag: \"d41d8cd98f00b204e9800998ecf8427e\"\n X-Content-Type-Options: nosniff\n X-Render-Time: 0.00371098518372\n Connection: keep-alive\n X-Version: 2c820eb0d2b3\n X-Frame-Options: SAMEORIGIN\n X-Request-Count: 357\n Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS\n Access-Control-Allow-Headers: Accept, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, Range, X-CsrftokenX-Requested-With\n X-Cache-Info: not cacheable; request wasn't a GET or HEAD\n Content-Length: 0\n\n\n\n### Supported content types\n\nThe default and primary content type for 2.0 APIs is JSON. This applies both to responses from the server and to the request bodies provided by the client.\n\nUnless documented otherwise, whenever creating a new (POST) or modifying an existing (PUT) object, your client must provide the object's normal representation. Not every object element can be mutated. For example, a repository's created_on date is an auto-generated, immutable field. Your client can omit immutable fields from a request body.\n\nIn some cases, a resource might also accept regular application/x-www-url-form-encoded POST and PUT bodies. Such bodies can be more convenient in scripts and command line usage. Requests bodies can contain contain nested elements or they can be flat (without nested elements). Clients can send flat request bodies as either as application/json or as application/x-www-url-form-encoded. Nested objects always require JSON.\n\n### Resource links\n\nEvery 2.0 object contains a links element that points to related resources or alternate representations. Use links to quickly discover and traverse to related objects. Links serve a \"self-documenting\" function for each endpoint. For example, the following request for a specific user:\n\n\n`$ curl https://api.bitbucket.org/2.0/users/tutorials`\n\n```json\n{\n \"username\": \"tutorials\",\n \"nickname\": \"tutorials\",\n \"account_status\": \"active\",\n \"website\": \"https://tutorials.bitbucket.org/\",\n \"display_name\": \"tutorials account\",\n \"uuid\": \"{c788b2da-b7a2-404c-9e26-d3f077557007}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/tutorials\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/tutorials\"\n },\n \"followers\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/followers\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Nov/25/tutorials-avatar-1563784409-6_avatar.png\"\n },\n \"following\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/following\"\n }\n },\n \"created_on\": \"2011-12-20T16:34:07.132459+00:00\",\n \"location\": \"Santa Monica, CA\",\n \"type\": \"user\"\n}\n```\nLinks can be actual REST API resources or they can be informational. In this example, informative resources include the user's avatar and the HTML URL for the user's Bitbucket account. Your client should avoid hardcoding an API's URL and instead use the URLs returned in API responses.\n\nA link's key is its `rel` (relationship) attribute and it contains a mandatory href element. For example, the following link:\n\n```json\n\"self\": {\n \"href\": \"https://api.bitbucket.org/api/2.0/users/tutorials\"\n}\n```\n\nThe rel for this link is self and the href is https://api.bitbucket.org/api/2.0/users/tutorials. A single rel key can contain an list (array) of href objects. Your client should anticipate that any rel key can contain one or more href objects.\n\nFinally, links can also contain optional elements. Two common optional elements are the name element and the title element. They are often used to disambiguate links that share the same rel key. In the example below, the repository object that contains a clone link with two href objects. Each object contains the optional name element to clarify its use.\n\n```json\n\"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/evzijst/bitbucket\"\n },\n \"clone\": [\n {\n \"href\": \"https://api.bitbucket.org/evzijst/bitbucket.git\",\n \"name\": \"https\"\n },\n {\n \"href\": \"ssh://git@bitbucket.org/erik/bitbucket.git\",\n \"name\": \"ssh\"\n }\n ],\n ...\n}\n```\nLinks can support [URI Templates](https://tools.ietf.org/html/rfc6570); Those that do contain a `\"templated\": \"true\"` element.\n", - "title": "Cors and hypermedia", "anchor": "cors-hypermedia", + "title": "Cors and hypermedia", "description": "Learn about resources and linking", - "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjM2LjYgMjE4LjQzIj48ZGVmcz48c3R5bGU+LmNscy0xe2lzb2xhdGlvbjppc29sYXRlO30uY2xzLTIsLmNscy0zLC5jbHMtNHtmaWxsOm5vbmU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjExcHg7fS5jbHMtMntzdHJva2U6dXJsKCNsaW5lYXItZ3JhZGllbnQpO30uY2xzLTN7c3Ryb2tlOnVybCgjTmV3X0dyYWRpZW50X1N3YXRjaF8xNCk7fS5jbHMtNHtzdHJva2U6dXJsKCNOZXdfR3JhZGllbnRfU3dhdGNoXzEpO30uY2xzLTV7ZmlsbDojNDI1MjZlO30uY2xzLTZ7ZmlsbDojZmY1NjMwO30uY2xzLTEwLC5jbHMtNywuY2xzLTh7bWl4LWJsZW5kLW1vZGU6bXVsdGlwbHk7fS5jbHMtN3tmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTIpO30uY2xzLTh7ZmlsbDp1cmwoI2xpbmVhci1ncmFkaWVudC0zKTt9LmNscy05e2ZpbGw6IzAwNjVmZjt9LmNscy0xMHtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTQpO308L3N0eWxlPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB5MT0iMTY3Ljg3IiB4Mj0iMTkxLjU2IiB5Mj0iMTY3Ljg3IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNTA1Zjc5Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMzQ0NTYzIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9Ik5ld19HcmFkaWVudF9Td2F0Y2hfMTQiIHgxPSIxMTIuODMiIHkxPSIxMzEuNzIiIHgyPSIyMzYuNiIgeTI9IjEzMS43MiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzAwNTJjYyIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzI2ODRmZiIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJOZXdfR3JhZGllbnRfU3dhdGNoXzEiIHgxPSI0NS4wNiIgeTE9Ijg2LjY5IiB4Mj0iMTY4Ljg4IiB5Mj0iODYuNjkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNkZTM1MGIiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZjc0NTIiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTIiIHgxPSIzNDQ3LjkzIiB5MT0iLTkxOC43OSIgeDI9IjM0NTEuNCIgeTI9Ii0xMDMyLjc2IiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC01NzIuMzggMzcwNC4yOSkgcm90YXRlKC02NC4zNCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAuMzEiIHN0b3AtY29sb3I9IiNjMWM3ZDAiIHN0b3Atb3BhY2l0eT0iMCIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2MxYzdkMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtMyIgeDE9IjM1MDYuNiIgeTE9Ii03OTYuNjYiIHgyPSIzNTEwLjA3IiB5Mj0iLTkxMC42MyIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudC0yIi8+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNCIgeDE9IjM1ODEuNjgiIHkxPSItOTA2LjgyIiB4Mj0iMzU4NS4xNiIgeTI9Ii0xMDIwLjc5IiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50LTIiLz48L2RlZnM+PHRpdGxlPldlYmhvb2tzPC90aXRsZT48ZyBjbGFzcz0iY2xzLTEiPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJTb2Z0d2FyZSI+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTg2LjA2LDE2Ny44N0gxMTcuNzJBMjcuNTgsMjcuNTgsMCwwLDAsOTIuMjQsMTg1YTQ1LjA2LDQ1LjA2LDAsMSwxLTQxLjY4LTYyLjE5Ii8+PHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMTE4LjMzLDUwLjUybDM0LjE1LDU5LjE4YTI3LjU5LDI3LjU5LDAsMCwwLDI3LjU4LDEzLjUxLDQ1LjA2LDQ1LjA2LDAsMSwxLTMzLDY3LjE5Ii8+PHBhdGggY2xhc3M9ImNscy00IiBkPSJNNTAuNTYsMTY3Ljg3bDM0LjE4LTU5LjE2YTI3LjU5LDI3LjU5LDAsMCwwLTIuMDktMzAuNjQsNDUuMDYsNDUuMDYsMCwxLDEsNzQuNy01Ii8+PHBhdGggY2xhc3M9ImNscy01IiBkPSJNMTg2LjA2LDE5OS42NmEzMS43OSwzMS43OSwwLDEsMSwzMS43OS0zMS43OUEzMS44MiwzMS44MiwwLDAsMSwxODYuMDYsMTk5LjY2WiIvPjxnIGlkPSJfR3JvdXBfIiBkYXRhLW5hbWU9IiZsdDtHcm91cCZndDsiPjxwYXRoIGNsYXNzPSJjbHMtNiIgZD0iTTQ5LjU2LDE5OS42NGEzMS43OSwzMS43OSwwLDEsMSwzMi43Ny0zMC43N0EzMS44MiwzMS44MiwwLDAsMSw0OS41NiwxOTkuNjRaIi8+PC9nPjxwYXRoIGNsYXNzPSJjbHMtNyIgZD0iTTU0LjEyLDE4MC4zNmE1OC45LDU4LjksMCwwLDAtMS41OS0xMS4yN3MtMi4zNS05LjQ0LTcuMzMtMTYuODNhNDMuODksNDMuODksMCwwLDAtMTEuNzMtMTEuMTcsMzEuNzcsMzEuNzcsMCwwLDAsMjkuMjgsNTYuMTNDNTguMjksMTkyLjQ0LDU0LjY4LDE4Ni45LDU0LjEyLDE4MC4zNloiLz48cGF0aCBjbGFzcz0iY2xzLTgiIGQ9Ik0xODkuNjIsMTgwLjM2QTU4LjksNTguOSwwLDAsMCwxODgsMTY5LjA4cy0yLjM1LTkuNDQtNy4zMy0xNi44M0E0My44OSw0My44OSwwLDAsMCwxNjksMTQxLjA4YTMxLjc3LDMxLjc3LDAsMCwwLDI5LjI4LDU2LjEzQzE5My43OSwxOTIuNDQsMTkwLjE4LDE4Ni45LDE4OS42MiwxODAuMzZaIi8+PGcgaWQ9Il9Hcm91cF8yIiBkYXRhLW5hbWU9IiZsdDtHcm91cCZndDsiPjxwYXRoIGNsYXNzPSJjbHMtOSIgZD0iTTg5LjksMzMuNzhhMzIuNDYsMzIuNDYsMCwxLDEsMTEuODgsNDQuMzRBMzIuNDksMzIuNDksMCwwLDEsODkuOSwzMy43OFoiLz48L2c+PHBhdGggY2xhc3M9ImNscy0xMCIgZD0iTTEyMi4yMyw2Ni4xM2E1OC45LDU4LjksMCwwLDAtMS41OS0xMS4yN3MtMi4zNS05LjQ0LTcuMzMtMTYuODNjLTMuMzctNS05LjA2LTkuNzktMTUuNDMtMTMuNDhBMzIuNDQsMzIuNDQsMCwwLDAsMTI4Ljc3LDgwLjZDMTI1LjMxLDc2LjM5LDEyMi43LDcxLjYxLDEyMi4yMyw2Ni4xM1oiLz48L2c+PC9nPjwvZz48L3N2Zz4=" + "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjM2LjYgMjE4LjQzIj48ZGVmcz48c3R5bGU+LmNscy0xe2lzb2xhdGlvbjppc29sYXRlO30uY2xzLTIsLmNscy0zLC5jbHMtNHtmaWxsOm5vbmU7c3Ryb2tlLWxpbmVjYXA6cm91bmQ7c3Ryb2tlLW1pdGVybGltaXQ6MTA7c3Ryb2tlLXdpZHRoOjExcHg7fS5jbHMtMntzdHJva2U6dXJsKCNsaW5lYXItZ3JhZGllbnQpO30uY2xzLTN7c3Ryb2tlOnVybCgjTmV3X0dyYWRpZW50X1N3YXRjaF8xNCk7fS5jbHMtNHtzdHJva2U6dXJsKCNOZXdfR3JhZGllbnRfU3dhdGNoXzEpO30uY2xzLTV7ZmlsbDojNDI1MjZlO30uY2xzLTZ7ZmlsbDojZmY1NjMwO30uY2xzLTEwLC5jbHMtNywuY2xzLTh7bWl4LWJsZW5kLW1vZGU6bXVsdGlwbHk7fS5jbHMtN3tmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTIpO30uY2xzLTh7ZmlsbDp1cmwoI2xpbmVhci1ncmFkaWVudC0zKTt9LmNscy05e2ZpbGw6IzAwNjVmZjt9LmNscy0xMHtmaWxsOnVybCgjbGluZWFyLWdyYWRpZW50LTQpO308L3N0eWxlPjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB5MT0iMTY3Ljg3IiB4Mj0iMTkxLjU2IiB5Mj0iMTY3Ljg3IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjNTA1Zjc5Ii8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjMzQ0NTYzIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9Ik5ld19HcmFkaWVudF9Td2F0Y2hfMTQiIHgxPSIxMTIuODMiIHkxPSIxMzEuNzIiIHgyPSIyMzYuNiIgeTI9IjEzMS43MiIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzAwNTJjYyIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzI2ODRmZiIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJOZXdfR3JhZGllbnRfU3dhdGNoXzEiIHgxPSI0NS4wNiIgeTE9Ijg2LjY5IiB4Mj0iMTY4Ljg4IiB5Mj0iODYuNjkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNkZTM1MGIiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNmZjc0NTIiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50LTIiIHgxPSIzNDQ3LjkzIiB5MT0iLTkxOC43OSIgeDI9IjM0NTEuNCIgeTI9Ii0xMDMyLjc2IiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC01NzIuMzggMzcwNC4yOSkgcm90YXRlKC02NC4zNCkiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj48c3RvcCBvZmZzZXQ9IjAuMzEiIHN0b3AtY29sb3I9IiNjMWM3ZDAiIHN0b3Atb3BhY2l0eT0iMCIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2MxYzdkMCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtMyIgeDE9IjM1MDYuNiIgeTE9Ii03OTYuNjYiIHgyPSIzNTEwLjA3IiB5Mj0iLTkxMC42MyIgeGxpbms6aHJlZj0iI2xpbmVhci1ncmFkaWVudC0yIi8+PGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQtNCIgeDE9IjM1ODEuNjgiIHkxPSItOTA2LjgyIiB4Mj0iMzU4NS4xNiIgeTI9Ii0xMDIwLjc5IiB4bGluazpocmVmPSIjbGluZWFyLWdyYWRpZW50LTIiLz48L2RlZnM+PHRpdGxlPldlYmhvb2tzPC90aXRsZT48ZyBjbGFzcz0iY2xzLTEiPjxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPjxnIGlkPSJTb2Z0d2FyZSI+PHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTg2LjA2LDE2Ny44N0gxMTcuNzJBMjcuNTgsMjcuNTgsMCwwLDAsOTIuMjQsMTg1YTQ1LjA2LDQ1LjA2LDAsMSwxLTQxLjY4LTYyLjE5Ii8+PHBhdGggY2xhc3M9ImNscy0zIiBkPSJNMTE4LjMzLDUwLjUybDM0LjE1LDU5LjE4YTI3LjU5LDI3LjU5LDAsMCwwLDI3LjU4LDEzLjUxLDQ1LjA2LDQ1LjA2LDAsMSwxLTMzLDY3LjE5Ii8+PHBhdGggY2xhc3M9ImNscy00IiBkPSJNNTAuNTYsMTY3Ljg3bDM0LjE4LTU5LjE2YTI3LjU5LDI3LjU5LDAsMCwwLTIuMDktMzAuNjQsNDUuMDYsNDUuMDYsMCwxLDEsNzQuNy01Ii8+PHBhdGggY2xhc3M9ImNscy01IiBkPSJNMTg2LjA2LDE5OS42NmEzMS43OSwzMS43OSwwLDEsMSwzMS43OS0zMS43OUEzMS44MiwzMS44MiwwLDAsMSwxODYuMDYsMTk5LjY2WiIvPjxnIGlkPSJfR3JvdXBfIiBkYXRhLW5hbWU9IiZsdDtHcm91cCZndDsiPjxwYXRoIGNsYXNzPSJjbHMtNiIgZD0iTTQ5LjU2LDE5OS42NGEzMS43OSwzMS43OSwwLDEsMSwzMi43Ny0zMC43N0EzMS44MiwzMS44MiwwLDAsMSw0OS41NiwxOTkuNjRaIi8+PC9nPjxwYXRoIGNsYXNzPSJjbHMtNyIgZD0iTTU0LjEyLDE4MC4zNmE1OC45LDU4LjksMCwwLDAtMS41OS0xMS4yN3MtMi4zNS05LjQ0LTcuMzMtMTYuODNhNDMuODksNDMuODksMCwwLDAtMTEuNzMtMTEuMTcsMzEuNzcsMzEuNzcsMCwwLDAsMjkuMjgsNTYuMTNDNTguMjksMTkyLjQ0LDU0LjY4LDE4Ni45LDU0LjEyLDE4MC4zNloiLz48cGF0aCBjbGFzcz0iY2xzLTgiIGQ9Ik0xODkuNjIsMTgwLjM2QTU4LjksNTguOSwwLDAsMCwxODgsMTY5LjA4cy0yLjM1LTkuNDQtNy4zMy0xNi44M0E0My44OSw0My44OSwwLDAsMCwxNjksMTQxLjA4YTMxLjc3LDMxLjc3LDAsMCwwLDI5LjI4LDU2LjEzQzE5My43OSwxOTIuNDQsMTkwLjE4LDE4Ni45LDE4OS42MiwxODAuMzZaIi8+PGcgaWQ9Il9Hcm91cF8yIiBkYXRhLW5hbWU9IiZsdDtHcm91cCZndDsiPjxwYXRoIGNsYXNzPSJjbHMtOSIgZD0iTTg5LjksMzMuNzhhMzIuNDYsMzIuNDYsMCwxLDEsMTEuODgsNDQuMzRBMzIuNDksMzIuNDksMCwwLDEsODkuOSwzMy43OFoiLz48L2c+PHBhdGggY2xhc3M9ImNscy0xMCIgZD0iTTEyMi4yMyw2Ni4xM2E1OC45LDU4LjksMCwwLDAtMS41OS0xMS4yN3MtMi4zNS05LjQ0LTcuMzMtMTYuODNjLTMuMzctNS05LjA2LTkuNzktMTUuNDMtMTMuNDhBMzIuNDQsMzIuNDQsMCwwLDAsMTI4Ljc3LDgwLjZDMTI1LjMxLDc2LjM5LDEyMi43LDcxLjYxLDEyMi4yMyw2Ni4xM1oiLz48L2c+PC9nPjwvZz48L3N2Zz4='", + "body": "\nThis section describes [Cross-origin resource sharing](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing) (CORS), what content types we support in requests and responses, and hyperlinking resources in each json responses.\n\n\n----\n\n* [CORS](#cors)\n* [Supported content types](#supported-content-types)\n* [Resource links](#resource-links)\n\n----\n\n### Cors\n\nThe Bitbucket API supports Cross-origin resource sharing to allow requests for restricted resources across domains. For more information you can refer to:\n\n* [Wikipedia article on CORS](https://en.wikipedia.org/wiki/Cross-origin_resource_sharing)\n* [W3C CORS recommendation](https://www.w3.org/TR/cors/)\n\nSending a general request from the api to bitbucket.com:\n\n`curl -i https://api.bitbucket.org -H \"origin: http://bitbucket.com\"`\n\nGives this result:\n\n HTTP/1.1 302 FOUND\n Server: nginx/1.6.2\n Vary: Cookie\n Cache-Control: max-age=900\n Content-Type: text/html; charset=utf-8\n Strict-Transport-Security: max-age=31536000\n Date: Tue, 21 Jun 2016 17:54:37 GMT\n Location: http://confluence.atlassian.com/x/IYBGDQ\n X-Served-By: app-110\n X-Static-Version: 2c820eb0d2b3\n ETag: \"d41d8cd98f00b204e9800998ecf8427e\"\n X-Content-Type-Options: nosniff\n X-Render-Time: 0.00379920005798\n Connection: Keep-Alive\n X-Version: 2c820eb0d2b3\n X-Frame-Options: SAMEORIGIN\n X-Request-Count: 383\n X-Cache-Info: cached\n Content-Length: 0\n\nSending the same request with the CORS check -X OPTIONS in the call:\n\n`curl -i https://api.bitbucket.org -H \"origin: http://bitbucket.com\" -X OPTIONS`\n\nGives this result:\n\n HTTP/1.1 302 FOUND\n Server: nginx/1.6.2\n Vary: Cookie\n Cache-Control: max-age=900\n Content-Type: text/html; charset=utf-8\n Access-Control-Expose-Headers: Accept-Ranges, Content-Encoding, Content-Length, Content-Type, ETag, Last-Modified\n Strict-Transport-Security: max-age=31536000\n Date: Tue, 21 Jun 2016 18:04:30 GMT\n Access-Control-Max-Age: 86400\n Location: http://confluence.atlassian.com/x/IYBGDQ\n X-Served-By: app-111\n Access-Control-Allow-Origin: *\n X-Static-Version: 2c820eb0d2b3\n ETag: \"d41d8cd98f00b204e9800998ecf8427e\"\n X-Content-Type-Options: nosniff\n X-Render-Time: 0.00371098518372\n Connection: keep-alive\n X-Version: 2c820eb0d2b3\n X-Frame-Options: SAMEORIGIN\n X-Request-Count: 357\n Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS\n Access-Control-Allow-Headers: Accept, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, Origin, Range, X-CsrftokenX-Requested-With\n X-Cache-Info: not cacheable; request wasn't a GET or HEAD\n Content-Length: 0\n\n\n\n### Supported content types\n\nThe default and primary content type for 2.0 APIs is JSON. This applies both to responses from the server and to the request bodies provided by the client.\n\nUnless documented otherwise, whenever creating a new (POST) or modifying an existing (PUT) object, your client must provide the object's normal representation. Not every object element can be mutated. For example, a repository's created_on date is an auto-generated, immutable field. Your client can omit immutable fields from a request body.\n\nIn some cases, a resource might also accept regular application/x-www-url-form-encoded POST and PUT bodies. Such bodies can be more convenient in scripts and command line usage. Requests bodies can contain contain nested elements or they can be flat (without nested elements). Clients can send flat request bodies as either as application/json or as application/x-www-url-form-encoded. Nested objects always require JSON.\n\n### Resource links\n\nEvery 2.0 object contains a links element that points to related resources or alternate representations. Use links to quickly discover and traverse to related objects. Links serve a \"self-documenting\" function for each endpoint. For example, the following request for a specific user:\n\n\n`$ curl https://api.bitbucket.org/2.0/users/tutorials`\n\n```json\n{\n \"username\": \"tutorials\",\n \"nickname\": \"tutorials\",\n \"account_status\": \"active\",\n \"website\": \"https://tutorials.bitbucket.org/\",\n \"display_name\": \"tutorials account\",\n \"uuid\": \"{c788b2da-b7a2-404c-9e26-d3f077557007}\",\n \"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials\"\n },\n \"repositories\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/tutorials\"\n },\n \"html\": {\n \"href\": \"https://bitbucket.org/tutorials\"\n },\n \"followers\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/followers\"\n },\n \"avatar\": {\n \"href\": \"https://bitbucket-assetroot.s3.amazonaws.com/c/photos/2013/Nov/25/tutorials-avatar-1563784409-6_avatar.png\"\n },\n \"following\": {\n \"href\": \"https://api.bitbucket.org/2.0/users/tutorials/following\"\n }\n },\n \"created_on\": \"2011-12-20T16:34:07.132459+00:00\",\n \"location\": \"Santa Monica, CA\",\n \"type\": \"user\"\n}\n```\nLinks can be actual REST API resources or they can be informational. In this example, informative resources include the user's avatar and the HTML URL for the user's Bitbucket account. Your client should avoid hardcoding an API's URL and instead use the URLs returned in API responses.\n\nA link's key is its `rel` (relationship) attribute and it contains a mandatory href element. For example, the following link:\n\n```json\n\"self\": {\n \"href\": \"https://api.bitbucket.org/api/2.0/users/tutorials\"\n}\n```\n\nThe rel for this link is self and the href is https://api.bitbucket.org/api/2.0/users/tutorials. A single rel key can contain an list (array) of href objects. Your client should anticipate that any rel key can contain one or more href objects.\n\nFinally, links can also contain optional elements. Two common optional elements are the name element and the title element. They are often used to disambiguate links that share the same rel key. In the example below, the repository object that contains a clone link with two href objects. Each object contains the optional name element to clarify its use.\n\n```json\n\"links\": {\n \"self\": {\n \"href\": \"https://api.bitbucket.org/2.0/repositories/evzijst/bitbucket\"\n },\n \"clone\": [\n {\n \"href\": \"https://api.bitbucket.org/evzijst/bitbucket.git\",\n \"name\": \"https\"\n },\n {\n \"href\": \"ssh://git@bitbucket.org/erik/bitbucket.git\",\n \"name\": \"ssh\"\n }\n ],\n ...\n}\n```\nLinks can support [URI Templates](https://tools.ietf.org/html/rfc6570); Those that do contain a `\"templated\": \"true\"` element.\n" }, { - "body": "\nYou can use the Atlassian Connect for Bitbucket Cloud to build add-ons which\ncan connect with the Bitbucket UI and your own application set. An add-on could\nbe an integration with another existing service, new features for the Atlassian\napplication, or even a new product that runs within the Atlassian application.\n\nFor complete information see:\n[Atlassian Connect for Bitbucket Cloud](https://developer.atlassian.com/bitbucket/index.html)\n", - "title": "Atlassian Connect", "anchor": "bb-connect", + "title": "Atlassian Connect", "description": "Build Bitbucket add-ons with Connect", - "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjU3LjMxNjMgMTYyLjU5OTQiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGlzb2xhdGlvbjogaXNvbGF0ZTsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBmaWxsOiAjMzQ0NTYzOwogICAgICB9CgogICAgICAuY2xzLTMgewogICAgICAgIGZpbGw6ICMxZGI5ZDQ7CiAgICAgIH0KCiAgICAgIC5jbHMtNCB7CiAgICAgICAgZmlsbDogIzAwNTdkODsKICAgICAgfQoKICAgICAgLmNscy01LCAuY2xzLTcsIC5jbHMtOSB7CiAgICAgICAgbWl4LWJsZW5kLW1vZGU6IG11bHRpcGx5OwogICAgICB9CgogICAgICAuY2xzLTUgewogICAgICAgIGZpbGw6IHVybCgjTjc1KTsKICAgICAgfQoKICAgICAgLmNscy02IHsKICAgICAgICBmaWxsOiAjMDA2NWZmOwogICAgICB9CgogICAgICAuY2xzLTcgewogICAgICAgIGZpbGw6IHVybCgjTjc1LTIpOwogICAgICB9CgogICAgICAuY2xzLTggewogICAgICAgIGZpbGw6IHVybCgjbGluZWFyLWdyYWRpZW50KTsKICAgICAgfQoKICAgICAgLmNscy05IHsKICAgICAgICBmaWxsOiB1cmwoI043NS0zKTsKICAgICAgfQoKICAgICAgLmNscy0xMCB7CiAgICAgICAgZmlsbDogdXJsKCNUMjAwLVQ3NSk7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8bGluZWFyR3JhZGllbnQgaWQ9Ik43NSIgeDE9Ii0yMTg5LjU1NiIgeTE9IjI4MDguMjI4NCIgeDI9Ii0yMDkxLjE1NTEiIHkyPSIyODA4LjIyODQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMC4xNjgxLCAwLjk4NTgsIDAuOTg1OCwgLTAuMTY4MSwgLTIzNzMuNzM2LCAyNzIyLjEyNzgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgIDxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2U1ZThlYyIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNlNWU4ZWMiIHN0b3Atb3BhY2l0eT0iMC4xIi8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJONzUtMiIgZGF0YS1uYW1lPSJONzUiIHgxPSItMjE2OC41OTQ3IiB5MT0iMjkzNS4wNTU2IiB4Mj0iLTIwNzAuMTkzNyIgeTI9IjI5MzUuMDU1NiIgeGxpbms6aHJlZj0iI043NSIvPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHgxPSIxOTAuMzU0NyIgeTE9IjE1OS45NjciIHgyPSIyNTkuOTQ4NyIgeTI9IjkwLjM3MyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMzNDQ1NjMiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNWU2Yzg0Ii8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJONzUtMyIgZGF0YS1uYW1lPSJONzUiIHgxPSItMjI1Ny4zOTc3IiB5MT0iMjg4NC4yMjYzIiB4Mj0iLTIxNTguOTk2NyIgeTI9IjI4ODQuMjI2MyIgeGxpbms6aHJlZj0iI043NSIvPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJUMjAwLVQ3NSIgeDE9IjEyNi4wMjU3IiB5MT0iODUuMTA4IiB4Mj0iMTk1LjYxOTciIHkyPSIxNS41MTQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM2RjN2RjIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzljZTNlZSIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICA8L2RlZnM+CiAgPHRpdGxlPkFkZCBPbiBCbG9ja3MgMjwvdGl0bGU+CiAgPGcgY2xhc3M9ImNscy0xIj4KICAgIDxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPgogICAgICA8ZyBpZD0iT2JqZWN0cyI+CiAgICAgICAgPHJlY3QgaWQ9Il9SZWN0YW5nbGVfIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTIiIHg9IjEyOC42NTgxIiB5PSI4Ny43NDA1IiB3aWR0aD0iNjQuMzI5MSIgaGVpZ2h0PSI3NC44NTkiLz4KICAgICAgICA8cmVjdCBpZD0iX1JlY3RhbmdsZV8yIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTMiIHg9IjY0LjMyOTEiIHk9IjEyLjg4MTUiIHdpZHRoPSI2NC4zMjkxIiBoZWlnaHQ9Ijc0Ljg1OSIvPgogICAgICAgIDxyZWN0IGlkPSJfUmVjdGFuZ2xlXzMiIGRhdGEtbmFtZT0iJmx0O1JlY3RhbmdsZSZndDsiIGNsYXNzPSJjbHMtNCIgeT0iODcuNzQwNSIgd2lkdGg9IjY0LjMyOTEiIGhlaWdodD0iNzQuODU5Ii8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy01IiBkPSJNNjQuMzI5MSw4Ny43NEg1OC42NTIzYTYyLjc4NzcsNjIuNzg3NywwLDAsMC0yNC41Njg0LDIwLjc2MjFjLTguMjYwNywxMi4xOTYtNC4zNDM3LDE4LjI0MTUtMTEuNjYzNywzMS42Mzk1QzE2LjUxLDE1MC45Niw4LjA1MSwxNTcuODIsMCwxNjIuNTU2di4wNDM1aDY0LjMyOVoiLz4KICAgICAgICA8cmVjdCBpZD0iX1JlY3RhbmdsZV80IiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTYiIHg9IjY0LjMyOTEiIHk9Ijg3Ljc0MDUiIHdpZHRoPSI2NC4zMjkxIiBoZWlnaHQ9Ijc0Ljg1OSIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNyIgZD0iTTE5Mi45ODcyLDg3Ljc0SDE4My4zNDNhNjIuNzg3Nyw2Mi43ODc3LDAsMCwwLTI0LjU2ODQsMjAuNzYyMWMtOC4yNjA3LDEyLjE5Ni00LjM0MzgsMTguMjQxNS0xMS42NjM3LDMxLjYzOTVhNTYuNzI0Niw1Ni43MjQ2LDAsMCwxLTE4LjQ1MjcsMTkuOTF2Mi41NDc0aDY0LjMyOVoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTgiIHg9IjE5Mi45ODcyIiB5PSI4Ny43NDA1IiB3aWR0aD0iNjQuMzI5MSIgaGVpZ2h0PSI3NC44NTkiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTkiIGQ9Ik0xMjguNjU4MiwxMi44ODE1SDExNi41OUE2MS45NjQ2LDYxLjk2NDYsMCwwLDAsMTAxLjMyMzMsMjguMjE1QzkzLjA2MjUsNDAuNDEwOSw5Ni45Nzk0LDQ2LjQ1NjUsODkuNjYsNTkuODU0NCw4My4wMzE2LDcxLjk4NTcsNzMuMTk3LDc5LjE1MTQsNjQuMzI5MSw4My45MTA4djMuODNoNjQuMzI5MVoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTEwIiB4PSIxMjguNjU4MSIgeT0iMTIuODgxNSIgd2lkdGg9IjY0LjMyOTEiIGhlaWdodD0iNzQuODU5Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy00IiB4PSIxMC4xNjA1IiB5PSI3NC44NTkiIHdpZHRoPSIyNC43NTM2IiBoZWlnaHQ9IjEyLjg4MTUiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTQiIHg9IjUxLjk1MjMiIHk9Ijc0Ljg1OSIgd2lkdGg9IjI0Ljc1MzYiIGhlaWdodD0iMTIuODgxNSIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtNCIgeD0iOTMuNzQ0MSIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxMzguODE4NiIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxODAuNjEwNCIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIyMjIuNDAyMiIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0zIiB4PSI3NC40ODk1IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0zIiB4PSIxMTYuMjgxNCIgd2lkdGg9IjI0Ljc1MzYiIGhlaWdodD0iMTIuODgxNSIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMyIgeD0iMTU4LjA3MzIiIHdpZHRoPSIyNC43NTM2IiBoZWlnaHQ9IjEyLjg4MTUiLz4KICAgICAgPC9nPgogICAgPC9nPgogIDwvZz4KPC9zdmc+Cg==" + "icon": "data:image/svg+xml;base64,b'PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMjU3LjMxNjMgMTYyLjU5OTQiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGlzb2xhdGlvbjogaXNvbGF0ZTsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBmaWxsOiAjMzQ0NTYzOwogICAgICB9CgogICAgICAuY2xzLTMgewogICAgICAgIGZpbGw6ICMxZGI5ZDQ7CiAgICAgIH0KCiAgICAgIC5jbHMtNCB7CiAgICAgICAgZmlsbDogIzAwNTdkODsKICAgICAgfQoKICAgICAgLmNscy01LCAuY2xzLTcsIC5jbHMtOSB7CiAgICAgICAgbWl4LWJsZW5kLW1vZGU6IG11bHRpcGx5OwogICAgICB9CgogICAgICAuY2xzLTUgewogICAgICAgIGZpbGw6IHVybCgjTjc1KTsKICAgICAgfQoKICAgICAgLmNscy02IHsKICAgICAgICBmaWxsOiAjMDA2NWZmOwogICAgICB9CgogICAgICAuY2xzLTcgewogICAgICAgIGZpbGw6IHVybCgjTjc1LTIpOwogICAgICB9CgogICAgICAuY2xzLTggewogICAgICAgIGZpbGw6IHVybCgjbGluZWFyLWdyYWRpZW50KTsKICAgICAgfQoKICAgICAgLmNscy05IHsKICAgICAgICBmaWxsOiB1cmwoI043NS0zKTsKICAgICAgfQoKICAgICAgLmNscy0xMCB7CiAgICAgICAgZmlsbDogdXJsKCNUMjAwLVQ3NSk7CiAgICAgIH0KICAgIDwvc3R5bGU+CiAgICA8bGluZWFyR3JhZGllbnQgaWQ9Ik43NSIgeDE9Ii0yMTg5LjU1NiIgeTE9IjI4MDguMjI4NCIgeDI9Ii0yMDkxLjE1NTEiIHkyPSIyODA4LjIyODQiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMC4xNjgxLCAwLjk4NTgsIDAuOTg1OCwgLTAuMTY4MSwgLTIzNzMuNzM2LCAyNzIyLjEyNzgpIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+CiAgICAgIDxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2U1ZThlYyIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiNlNWU4ZWMiIHN0b3Atb3BhY2l0eT0iMC4xIi8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJONzUtMiIgZGF0YS1uYW1lPSJONzUiIHgxPSItMjE2OC41OTQ3IiB5MT0iMjkzNS4wNTU2IiB4Mj0iLTIwNzAuMTkzNyIgeTI9IjI5MzUuMDU1NiIgeGxpbms6aHJlZj0iI043NSIvPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJsaW5lYXItZ3JhZGllbnQiIHgxPSIxOTAuMzU0NyIgeTE9IjE1OS45NjciIHgyPSIyNTkuOTQ4NyIgeTI9IjkwLjM3MyIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiMzNDQ1NjMiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNWU2Yzg0Ii8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJONzUtMyIgZGF0YS1uYW1lPSJONzUiIHgxPSItMjI1Ny4zOTc3IiB5MT0iMjg4NC4yMjYzIiB4Mj0iLTIxNTguOTk2NyIgeTI9IjI4ODQuMjI2MyIgeGxpbms6aHJlZj0iI043NSIvPgogICAgPGxpbmVhckdyYWRpZW50IGlkPSJUMjAwLVQ3NSIgeDE9IjEyNi4wMjU3IiB5MT0iODUuMTA4IiB4Mj0iMTk1LjYxOTciIHkyPSIxNS41MTQiIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjM2RjN2RjIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzljZTNlZSIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICA8L2RlZnM+CiAgPHRpdGxlPkFkZCBPbiBCbG9ja3MgMjwvdGl0bGU+CiAgPGcgY2xhc3M9ImNscy0xIj4KICAgIDxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPgogICAgICA8ZyBpZD0iT2JqZWN0cyI+CiAgICAgICAgPHJlY3QgaWQ9Il9SZWN0YW5nbGVfIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTIiIHg9IjEyOC42NTgxIiB5PSI4Ny43NDA1IiB3aWR0aD0iNjQuMzI5MSIgaGVpZ2h0PSI3NC44NTkiLz4KICAgICAgICA8cmVjdCBpZD0iX1JlY3RhbmdsZV8yIiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTMiIHg9IjY0LjMyOTEiIHk9IjEyLjg4MTUiIHdpZHRoPSI2NC4zMjkxIiBoZWlnaHQ9Ijc0Ljg1OSIvPgogICAgICAgIDxyZWN0IGlkPSJfUmVjdGFuZ2xlXzMiIGRhdGEtbmFtZT0iJmx0O1JlY3RhbmdsZSZndDsiIGNsYXNzPSJjbHMtNCIgeT0iODcuNzQwNSIgd2lkdGg9IjY0LjMyOTEiIGhlaWdodD0iNzQuODU5Ii8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy01IiBkPSJNNjQuMzI5MSw4Ny43NEg1OC42NTIzYTYyLjc4NzcsNjIuNzg3NywwLDAsMC0yNC41Njg0LDIwLjc2MjFjLTguMjYwNywxMi4xOTYtNC4zNDM3LDE4LjI0MTUtMTEuNjYzNywzMS42Mzk1QzE2LjUxLDE1MC45Niw4LjA1MSwxNTcuODIsMCwxNjIuNTU2di4wNDM1aDY0LjMyOVoiLz4KICAgICAgICA8cmVjdCBpZD0iX1JlY3RhbmdsZV80IiBkYXRhLW5hbWU9IiZsdDtSZWN0YW5nbGUmZ3Q7IiBjbGFzcz0iY2xzLTYiIHg9IjY0LjMyOTEiIHk9Ijg3Ljc0MDUiIHdpZHRoPSI2NC4zMjkxIiBoZWlnaHQ9Ijc0Ljg1OSIvPgogICAgICAgIDxwYXRoIGNsYXNzPSJjbHMtNyIgZD0iTTE5Mi45ODcyLDg3Ljc0SDE4My4zNDNhNjIuNzg3Nyw2Mi43ODc3LDAsMCwwLTI0LjU2ODQsMjAuNzYyMWMtOC4yNjA3LDEyLjE5Ni00LjM0MzgsMTguMjQxNS0xMS42NjM3LDMxLjYzOTVhNTYuNzI0Niw1Ni43MjQ2LDAsMCwxLTE4LjQ1MjcsMTkuOTF2Mi41NDc0aDY0LjMyOVoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTgiIHg9IjE5Mi45ODcyIiB5PSI4Ny43NDA1IiB3aWR0aD0iNjQuMzI5MSIgaGVpZ2h0PSI3NC44NTkiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTkiIGQ9Ik0xMjguNjU4MiwxMi44ODE1SDExNi41OUE2MS45NjQ2LDYxLjk2NDYsMCwwLDAsMTAxLjMyMzMsMjguMjE1QzkzLjA2MjUsNDAuNDEwOSw5Ni45Nzk0LDQ2LjQ1NjUsODkuNjYsNTkuODU0NCw4My4wMzE2LDcxLjk4NTcsNzMuMTk3LDc5LjE1MTQsNjQuMzI5MSw4My45MTA4djMuODNoNjQuMzI5MVoiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTEwIiB4PSIxMjguNjU4MSIgeT0iMTIuODgxNSIgd2lkdGg9IjY0LjMyOTEiIGhlaWdodD0iNzQuODU5Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy00IiB4PSIxMC4xNjA1IiB5PSI3NC44NTkiIHdpZHRoPSIyNC43NTM2IiBoZWlnaHQ9IjEyLjg4MTUiLz4KICAgICAgICA8cmVjdCBjbGFzcz0iY2xzLTQiIHg9IjUxLjk1MjMiIHk9Ijc0Ljg1OSIgd2lkdGg9IjI0Ljc1MzYiIGhlaWdodD0iMTIuODgxNSIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtNCIgeD0iOTMuNzQ0MSIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxMzguODE4NiIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIxODAuNjEwNCIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0yIiB4PSIyMjIuNDAyMiIgeT0iNzQuODU5IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0zIiB4PSI3NC40ODk1IiB3aWR0aD0iMjQuNzUzNiIgaGVpZ2h0PSIxMi44ODE1Ii8+CiAgICAgICAgPHJlY3QgY2xhc3M9ImNscy0zIiB4PSIxMTYuMjgxNCIgd2lkdGg9IjI0Ljc1MzYiIGhlaWdodD0iMTIuODgxNSIvPgogICAgICAgIDxyZWN0IGNsYXNzPSJjbHMtMyIgeD0iMTU4LjA3MzIiIHdpZHRoPSIyNC43NTM2IiBoZWlnaHQ9IjEyLjg4MTUiLz4KICAgICAgPC9nPgogICAgPC9nPgogIDwvZz4KPC9zdmc+Cg=='", + "body": "\nYou can use the Atlassian Connect for Bitbucket Cloud to build add-ons which\ncan connect with the Bitbucket UI and your own application set. An add-on could\nbe an integration with another existing service, new features for the Atlassian\napplication, or even a new product that runs within the Atlassian application.\n\nFor complete information see:\n[Atlassian Connect for Bitbucket Cloud](https://developer.atlassian.com/bitbucket/index.html)\n" } ] }, @@ -19044,17 +18995,6 @@ "description": "The new snippet object.", "required": true }, - "issue_comment": { - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/issue_comment" - } - } - }, - "description": "The updated comment.", - "required": true - }, "pipeline_variable2": { "content": { "application/json": { @@ -19082,47 +19022,47 @@ "description": "Basic HTTP Authentication as per [RFC-2617](https://tools.ietf.org/html/rfc2617) (Digest not supported). Note that Basic Auth is available only with username and app password as credentials.", "scheme": "basic" }, - "api_key": { - "description": "API Keys can be used as Basic HTTP Authentication credentials and provide a substitute for the account's actual username and password. API Keys are only available to team accounts and there is only 1 key per account. API Keys do not support scopes and have therefore access to all contents of the account.", - "type": "apiKey", - "name": "Authorization", - "in": "header" - }, "oauth2": { - "description": "OAuth 2 as per [RFC-6749](https://tools.ietf.org/html/rfc6749).", "type": "oauth2", + "description": "OAuth 2 as per [RFC-6749](https://tools.ietf.org/html/rfc6749).", "flows": { "authorizationCode": { "authorizationUrl": "https://bitbucket.org/site/oauth2/authorize", "tokenUrl": "https://bitbucket.org/site/oauth2/access_token", "scopes": { - "wiki": "Read and modify your repositories' wikis", - "pullrequest:write": "Read and modify your repositories and their pull requests", - "runner": "Access your workspaces/repositories' runners", - "runner:write": "Access and edit your workspaces/repositories' runners", - "pipeline:variable": "Access your repositories' build pipelines and configure their variables", - "project:write": "Read and modify your workspace's project settings, and read and transfer repositories within your workspace's projects", - "pipeline:write": "Access and rerun your repositories' build pipelines", - "snippet": "Read your snippets", - "repository:delete": "Delete your repositories", - "repository:write": "Read and modify your repositories", - "issue": "Read your repositories' issues", "email": "Read your account's primary email address", - "repository": "Read your repositories", - "issue:write": "Read and modify your repositories' issues", - "webhook": "Read and modify your repositories' webhooks", - "pipeline": "Access your repositories' build pipelines", - "snippet:write": "Read and modify your snippets", "account": "Read your account information", - "repository:admin": "Administer your repositories", - "pullrequest": "Read your repositories and their pull requests", - "project": "Read your workspace's project settings and read repositories contained within your workspace's projects", + "account:write": "Read and modify your account information", "team": "Read your team membership information", "team:write": "Read and modify your team membership information", - "account:write": "Read and modify your account information" + "repository": "Read your repositories", + "repository:write": "Read and modify your repositories", + "repository:admin": "Administer your repositories", + "repository:delete": "Delete your repositories", + "project": "Read your workspace's project settings and read repositories contained within your workspace's projects", + "project:admin": "Read and modify settings for projects in your workspace", + "pipeline": "Access your repositories' build pipelines", + "pipeline:write": "Access and rerun your repositories' build pipelines", + "pipeline:variable": "Access your repositories' build pipelines and configure their variables", + "runner": "Access your workspaces/repositories' runners", + "runner:write": "Access and edit your workspaces/repositories' runners", + "issue": "Read your repositories' issues", + "issue:write": "Read and modify your repositories' issues", + "pullrequest": "Read your repositories and their pull requests", + "pullrequest:write": "Read and modify your repositories and their pull requests", + "snippet": "Read your snippets", + "snippet:write": "Read and modify your snippets", + "webhook": "Read and modify your repositories' webhooks", + "wiki": "Read and modify your repositories' wikis" } } } + }, + "api_key": { + "name": "Authorization", + "type": "apiKey", + "description": "API Keys can be used as Basic HTTP Authentication credentials and provide a substitute for the account's actual username and password. API Keys are only available to team accounts and there is only 1 key per account. API Keys do not support scopes and have therefore access to all contents of the account.", + "in": "header" } }, "schemas": { @@ -19134,10 +19074,6 @@ { "type": "object", "properties": { - "account_status": { - "type": "string", - "description": "The status of the account. Currently the only possible value is \"active\", but more values may be added in the future." - }, "created_on": { "type": "string", "format": "date-time" @@ -19145,108 +19081,8 @@ "display_name": { "type": "string" }, - "has_2fa_enabled": { - "type": "boolean" - }, "links": { - "type": "object", - "properties": { - "avatar": { - "type": "object", - "title": "Link", - "description": "A link to a resource related to this object.", - "properties": { - "href": { - "type": "string", - "format": "uri" - }, - "name": { - "type": "string" - } - }, - "additionalProperties": false - }, - "followers": { - "type": "object", - "title": "Link", - "description": "A link to a resource related to this object.", - "properties": { - "href": { - "type": "string", - "format": "uri" - }, - "name": { - "type": "string" - } - }, - "additionalProperties": false - }, - "following": { - "type": "object", - "title": "Link", - "description": "A link to a resource related to this object.", - "properties": { - "href": { - "type": "string", - "format": "uri" - }, - "name": { - "type": "string" - } - }, - "additionalProperties": false - }, - "html": { - "type": "object", - "title": "Link", - "description": "A link to a resource related to this object.", - "properties": { - "href": { - "type": "string", - "format": "uri" - }, - "name": { - "type": "string" - } - }, - "additionalProperties": false - }, - "repositories": { - "type": "object", - "title": "Link", - "description": "A link to a resource related to this object.", - "properties": { - "href": { - "type": "string", - "format": "uri" - }, - "name": { - "type": "string" - } - }, - "additionalProperties": false - }, - "self": { - "type": "object", - "title": "Link", - "description": "A link to a resource related to this object.", - "properties": { - "href": { - "type": "string", - "format": "uri" - }, - "name": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "nickname": { - "type": "string", - "description": "Account name defined by the owner. Should be used instead of the \"username\" field. Note that \"nickname\" cannot be used in place of \"username\" in URLs and queries, as \"nickname\" is not guaranteed to be unique." + "$ref": "#/components/schemas/account_links" }, "username": { "type": "string", @@ -19254,9 +19090,6 @@ }, "uuid": { "type": "string" - }, - "website": { - "type": "string" } }, "additionalProperties": true @@ -19265,6 +19098,44 @@ "title": "Account", "description": "An account object." }, + "account_links": { + "type": "object", + "title": "Account Links", + "description": "Links related to an Account.", + "properties": { + "avatar": { + "$ref": "#/components/schemas/link" + } + }, + "additionalProperties": true + }, + "app_user": { + "allOf": [ + { + "$ref": "#/components/schemas/account" + }, + { + "type": "object", + "properties": { + "account_id": { + "type": "string", + "description": "The user's Atlassian account ID." + }, + "account_status": { + "type": "string", + "description": "The status of the account. Currently the only possible value is \"active\", but more values may be added in the future." + }, + "kind": { + "type": "string", + "description": "The kind of App User." + } + }, + "additionalProperties": true + } + ], + "title": "App User", + "description": "An app user object." + }, "application_property": { "additionalProperties": true, "type": "object", @@ -19421,10 +19292,6 @@ "branch": { "$ref": "#/components/schemas/branch" }, - "branch_does_not_exist": { - "type": "boolean", - "description": "Indicates if the indicated branch exists on the repository (`false`)or not (`true`). This is useful for determining a fallback to the mainbranch when a repository is inheriting its project's branching model." - }, "name": { "type": "string", "description": "Name of the target branch. Will be listed here even when the target branch does not exist. Will be `null` if targeting the main branch and the repository is empty." @@ -19443,10 +19310,6 @@ "branch": { "$ref": "#/components/schemas/branch" }, - "branch_does_not_exist": { - "type": "boolean", - "description": "Indicates if the indicated branch exists on the repository (`false`)or not (`true`). This is useful for determining a fallback to the mainbranch when a repository is inheriting its project's branching model." - }, "name": { "type": "string", "description": "Name of the target branch. Will be listed here even when the target branch does not exist. Will be `null` if targeting the main branch and the repository is empty." @@ -19503,10 +19366,6 @@ "development": { "type": "object", "properties": { - "branch_does_not_exist": { - "type": "boolean", - "description": "Optional and only returned for a repository's branching model. Indicates ifthe indicated branch exists on the repository (`false`)or not (`true`). This is useful for determining a fallback to the mainbranch when a repository is inheriting its project's branching model." - }, "is_valid": { "type": "boolean", "description": "Indicates if the configured branch is valid, that is, if the configured branch actually exists currently. Is always `true` when `use_mainbranch` is `true` (even if the main branch does not exist). This field is read-only. This field is ignored when updating/creating settings." @@ -19546,10 +19405,6 @@ "production": { "type": "object", "properties": { - "branch_does_not_exist": { - "type": "boolean", - "description": "Optional and only returned for a repository's branching model. Indicates ifthe indicated branch exists on the repository (`false`)or not (`true`). This is useful for determining a fallback to the mainbranch when a repository is inheriting its project's branching model." - }, "enabled": { "type": "boolean", "description": "Indicates if branch is enabled or not." @@ -19584,23 +19439,6 @@ { "type": "object", "properties": { - "branch_match_kind": { - "type": "string", - "description": "Indicates how the restriction is matched against a branch. The default is `glob`.", - "enum": ["branching_model", "glob"] - }, - "branch_type": { - "type": "string", - "description": "Apply the restriction to branches of this type. Active when `branch_match_kind` is `branching_model`. The branch type will be calculated using the branching model configured for the repository.", - "enum": [ - "feature", - "bugfix", - "release", - "hotfix", - "development", - "production" - ] - }, "groups": { "type": "array", "items": { @@ -19608,69 +19446,14 @@ }, "minItems": 0 }, - "id": { - "type": "integer", - "description": "The branch restriction status' id." - }, - "kind": { - "type": "string", - "description": "The type of restriction that is being applied.", - "enum": [ - "require_tasks_to_be_completed", - "allow_auto_merge_when_builds_pass", - "require_passing_builds_to_merge", - "force", - "require_all_dependencies_merged", - "require_commits_behind", - "restrict_merges", - "enforce_merge_checks", - "reset_pullrequest_changes_requested_on_change", - "require_no_changes_requested", - "smart_reset_pullrequest_approvals", - "push", - "require_approvals_to_merge", - "require_default_reviewer_approvals_to_merge", - "reset_pullrequest_approvals_on_change", - "delete" - ] - }, - "links": { - "type": "object", - "properties": { - "self": { - "type": "object", - "title": "Link", - "description": "A link to a resource related to this object.", - "properties": { - "href": { - "type": "string", - "format": "uri" - }, - "name": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "pattern": { - "type": "string", - "description": "Apply the restriction to branches that match this pattern. Active when `branch_match_kind` is `glob`. Will be empty when `branch_match_kind` is `branching_model`." - }, "users": { "type": "array", "items": { "$ref": "#/components/schemas/account" }, "minItems": 0 - }, - "value": { - "type": "integer" } }, - "required": ["kind", "branch_match_kind", "pattern"], "additionalProperties": true } ], @@ -19794,7 +19577,7 @@ "format": "date-time" }, "user": { - "$ref": "#/components/schemas/user" + "$ref": "#/components/schemas/account" } }, "additionalProperties": true @@ -19940,7 +19723,7 @@ "state": { "type": "string", "description": "Provides some indication of the status of this commit", - "enum": ["SUCCESSFUL", "FAILED", "INPROGRESS", "STOPPED"] + "enum": ["FAILED", "SUCCESSFUL", "INPROGRESS", "STOPPED"] }, "updated_on": { "type": "string", @@ -20017,6 +19800,24 @@ "x-bb-default-fields": ["uuid", "commitHash"], "x-bb-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/commits/{commitHash}/reports/{uuid}" }, + "default_reviewer_and_type": { + "type": "object", + "title": "Default Reviewer and Type", + "description": "Object containing a user that is a default reviewer and the type of reviewer", + "properties": { + "reviewer_type": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/user" + } + }, + "required": ["type"], + "additionalProperties": true + }, "deploy_key": { "allOf": [ { @@ -20574,6 +20375,80 @@ "required": ["type"], "additionalProperties": true }, + "effective_repo_branching_model": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "branch_types": { + "type": "array", + "description": "The active branch types.", + "items": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "description": "The kind of branch.", + "enum": ["feature", "bugfix", "release", "hotfix"] + }, + "prefix": { + "type": "string", + "description": "The prefix for this branch type. A branch with this prefix will be classified as per `kind`. The prefix must be a valid prefix for a branch and must always exist. It cannot be blank, empty or `null`." + } + }, + "required": ["kind", "prefix"], + "additionalProperties": false + }, + "minItems": 0, + "maxItems": 4, + "uniqueItems": true + }, + "development": { + "type": "object", + "properties": { + "branch": { + "$ref": "#/components/schemas/branch" + }, + "name": { + "type": "string", + "description": "Name of the target branch. Will be listed here even when the target branch does not exist. Will be `null` if targeting the main branch and the repository is empty." + }, + "use_mainbranch": { + "type": "boolean", + "description": "Indicates if the setting points at an explicit branch (`false`) or tracks the main branch (`true`)." + } + }, + "required": ["name", "use_mainbranch"], + "additionalProperties": false + }, + "production": { + "type": "object", + "properties": { + "branch": { + "$ref": "#/components/schemas/branch" + }, + "name": { + "type": "string", + "description": "Name of the target branch. Will be listed here even when the target branch does not exist. Will be `null` if targeting the main branch and the repository is empty." + }, + "use_mainbranch": { + "type": "boolean", + "description": "Indicates if the setting points at an explicit branch (`false`) or tracks the main branch (`true`)." + } + }, + "required": ["name", "use_mainbranch"], + "additionalProperties": false + } + }, + "additionalProperties": true + } + ], + "title": "Effective Repository Branching Model", + "description": "A repository's effective branching model" + }, "error": { "type": "object", "title": "Error", @@ -20714,31 +20589,31 @@ "type": "string", "description": "The event identifier.", "enum": [ - "pullrequest:unapproved", + "pullrequest:updated", "issue:comment_created", - "repo:imported", - "repo:created", - "repo:commit_comment_created", - "pullrequest:approved", - "pullrequest:comment_updated", "issue:updated", - "project:updated", - "repo:deleted", + "repo:fork", "pullrequest:changes_request_created", "pullrequest:comment_created", - "repo:commit_status_updated", - "pullrequest:updated", - "issue:created", - "repo:fork", - "pullrequest:comment_deleted", - "repo:commit_status_created", + "repo:created", "repo:updated", + "pullrequest:comment_deleted", "pullrequest:rejected", - "pullrequest:fulfilled", + "issue:created", + "repo:imported", + "pullrequest:unapproved", + "pullrequest:approved", + "pullrequest:comment_updated", + "project:updated", "pullrequest:created", + "pullrequest:fulfilled", + "repo:commit_status_created", "pullrequest:changes_request_removed", - "repo:transfer", - "repo:push" + "repo:push", + "repo:commit_comment_created", + "repo:commit_status_updated", + "repo:deleted", + "repo:transfer" ] }, "label": { @@ -20757,7 +20632,7 @@ "type": "object", "properties": { "assignee": { - "$ref": "#/components/schemas/user" + "$ref": "#/components/schemas/account" }, "component": { "$ref": "#/components/schemas/component" @@ -20900,7 +20775,7 @@ "enum": ["trivial", "minor", "major", "critical", "blocker"] }, "reporter": { - "$ref": "#/components/schemas/user" + "$ref": "#/components/schemas/account" }, "repository": { "$ref": "#/components/schemas/repository" @@ -21165,7 +21040,7 @@ "type": "string" }, "user": { - "$ref": "#/components/schemas/user" + "$ref": "#/components/schemas/account" } }, "required": ["type"], @@ -21253,6 +21128,21 @@ "x-bb-detail-fields": ["connected"], "x-bb-url": "/api/{target_user.uuid}/jira/sites/{cloudId}?atlassian_account_id={user.account_id}" }, + "link": { + "type": "object", + "title": "Link", + "description": "A link to a resource related to this object.", + "properties": { + "href": { + "type": "string", + "format": "uri" + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false + }, "milestone": { "allOf": [ { @@ -21358,6 +21248,29 @@ } } }, + "paginated_accounts": { + "title": "Paginated Accounts", + "description": "A paginated list of accounts.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/account" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, "paginated_annotations": { "title": "Paginated Annotations", "description": "A paginated list of annotations.", @@ -21518,6 +21431,29 @@ } ] }, + "paginated_default_reviewer_and_type": { + "title": "Paginated Default Reviewer and Type", + "description": "A paginated list of default reviewers with reviewer type.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/default_reviewer_and_type" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, "paginated_deploy_keys": { "title": "Paginated Deploy Keys", "description": "A paginated list of deploy keys.", @@ -21943,6 +21879,29 @@ } ] }, + "paginated_project_deploy_keys": { + "title": "Paginated Project Deploy Keys", + "description": "A paginated list of project deploy keys.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/project_deploy_key" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, "paginated_projects": { "title": "Paginated Projects", "description": "A paginated list of projects", @@ -22263,52 +22222,6 @@ } ] }, - "paginated_team_permissions": { - "title": "Paginated Team Permissions", - "description": "A paginated list of team permissions.", - "allOf": [ - { - "$ref": "#/components/schemas/paginated" - }, - { - "type": "object", - "properties": { - "values": { - "type": "array", - "items": { - "$ref": "#/components/schemas/team_permission" - }, - "minItems": 0, - "uniqueItems": true, - "description": "The values of the current page." - } - } - } - ] - }, - "paginated_teams": { - "title": "Paginated Teams", - "description": "A paginated list of teams.", - "allOf": [ - { - "$ref": "#/components/schemas/paginated" - }, - { - "type": "object", - "properties": { - "values": { - "type": "array", - "items": { - "$ref": "#/components/schemas/team" - }, - "minItems": 0, - "uniqueItems": true, - "description": "The values of the current page." - } - } - } - ] - }, "paginated_treeentries": { "title": "Paginated Tree Entry", "description": "A paginated list of commit_file and/or commit_directory objects.", @@ -22332,29 +22245,6 @@ } ] }, - "paginated_users": { - "title": "Paginated Users", - "description": "A paginated list of users.", - "allOf": [ - { - "$ref": "#/components/schemas/paginated" - }, - { - "type": "object", - "properties": { - "values": { - "type": "array", - "items": { - "$ref": "#/components/schemas/user" - }, - "minItems": 0, - "uniqueItems": true, - "description": "The values of the current page." - } - } - } - ] - }, "paginated_versions": { "title": "Paginated Versions", "description": "A paginated list of issue tracker versions.", @@ -22472,7 +22362,7 @@ "enum": ["approved", "changes_requested", null] }, "user": { - "$ref": "#/components/schemas/user" + "$ref": "#/components/schemas/account" } }, "additionalProperties": true @@ -23679,6 +23569,136 @@ "title": "Project", "description": "A Bitbucket project.\n Projects are used by teams to organize repositories." }, + "project_branching_model": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "branch_types": { + "type": "array", + "description": "The active branch types.", + "items": { + "type": "object", + "properties": { + "kind": { + "type": "string", + "description": "The kind of branch.", + "enum": ["feature", "bugfix", "release", "hotfix"] + }, + "prefix": { + "type": "string", + "description": "The prefix for this branch type. A branch with this prefix will be classified as per `kind`. The prefix must be a valid prefix for a branch and must always exist. It cannot be blank, empty or `null`." + } + }, + "required": ["kind", "prefix"], + "additionalProperties": false + }, + "minItems": 0, + "maxItems": 4, + "uniqueItems": true + }, + "development": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the target branch. If inherited by a repository, it will default to the main branch if the specified branch does not exist." + }, + "use_mainbranch": { + "type": "boolean", + "description": "Indicates if the setting points at an explicit branch (`false`) or tracks the main branch (`true`)." + } + }, + "required": ["name", "use_mainbranch"], + "additionalProperties": false + }, + "production": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the target branch. If inherited by a repository, it will default to the main branch if the specified branch does not exist." + }, + "use_mainbranch": { + "type": "boolean", + "description": "Indicates if the setting points at an explicit branch (`false`) or tracks the main branch (`true`)." + } + }, + "required": ["name", "use_mainbranch"], + "additionalProperties": false + } + }, + "additionalProperties": true + } + ], + "title": "Project Branching Model", + "description": "A project's branching model" + }, + "project_deploy_key": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "added_on": { + "type": "string", + "format": "date-time" + }, + "comment": { + "type": "string", + "description": "The comment parsed from the deploy key (if present)" + }, + "created_by": { + "$ref": "#/components/schemas/account" + }, + "key": { + "type": "string", + "description": "The deploy key value." + }, + "label": { + "type": "string", + "description": "The user-defined label for the deploy key" + }, + "last_used": { + "type": "string", + "format": "date-time" + }, + "links": { + "type": "object", + "properties": { + "self": { + "type": "object", + "title": "Link", + "description": "A link to a resource related to this object.", + "properties": { + "href": { + "type": "string", + "format": "uri" + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "project": { + "$ref": "#/components/schemas/project" + } + }, + "additionalProperties": true + } + ], + "title": "Project Deploy Key", + "description": "Represents deploy key for a project." + }, "pullrequest": { "allOf": [ { @@ -23970,7 +23990,7 @@ "state": { "type": "string", "description": "The pull request's current status.", - "enum": ["MERGED", "SUPERSEDED", "OPEN", "DECLINED"] + "enum": ["OPEN", "MERGED", "DECLINED", "SUPERSEDED"] }, "summary": { "type": "object", @@ -24614,7 +24634,7 @@ }, "permission": { "type": "string", - "enum": ["admin", "write", "read", "none"] + "enum": ["read", "write", "admin", "none"] }, "repository": { "$ref": "#/components/schemas/repository" @@ -24626,6 +24646,21 @@ "required": ["type"], "additionalProperties": true }, + "repository_inheritance_state": { + "type": "object", + "title": "Repository Inheritance State", + "description": "A json object representing the repository's inheritance state values", + "properties": { + "override_settings": { + "type": "object" + }, + "type": { + "type": "string" + } + }, + "required": ["type"], + "additionalProperties": true + }, "repository_permission": { "type": "object", "title": "Repository Permission", @@ -24633,7 +24668,7 @@ "properties": { "permission": { "type": "string", - "enum": ["admin", "write", "read", "none"] + "enum": ["read", "write", "admin", "none"] }, "repository": { "$ref": "#/components/schemas/repository" @@ -24676,7 +24711,7 @@ }, "permission": { "type": "string", - "enum": ["admin", "write", "read", "none"] + "enum": ["read", "write", "admin", "none"] }, "repository": { "$ref": "#/components/schemas/repository" @@ -25073,28 +25108,7 @@ }, "additionalProperties": false }, - "team": { - "type": "object", - "properties": { - "events": { - "type": "object", - "title": "Link", - "description": "A link to a resource related to this object.", - "properties": { - "href": { - "type": "string", - "format": "uri" - }, - "name": { - "type": "string" - } - }, - "additionalProperties": false - } - }, - "additionalProperties": false - }, - "user": { + "workspace": { "type": "object", "properties": { "events": { @@ -25152,34 +25166,46 @@ }, { "type": "object", - "properties": {}, + "properties": { + "links": { + "$ref": "#/components/schemas/team_links" + } + }, "additionalProperties": true } ], "title": "Team", "description": "A team object." }, - "team_permission": { - "type": "object", - "title": "Team Permission", - "description": "A user's permission for a given team.", - "properties": { - "permission": { - "type": "string", - "enum": ["admin", "collaborator", "member"] + "team_links": { + "allOf": [ + { + "$ref": "#/components/schemas/account_links" }, - "team": { - "$ref": "#/components/schemas/team" - }, - "type": { - "type": "string" - }, - "user": { - "$ref": "#/components/schemas/user" + { + "type": "object", + "properties": { + "html": { + "$ref": "#/components/schemas/link" + }, + "members": { + "$ref": "#/components/schemas/link" + }, + "projects": { + "$ref": "#/components/schemas/link" + }, + "repositories": { + "$ref": "#/components/schemas/link" + }, + "self": { + "$ref": "#/components/schemas/link" + } + }, + "additionalProperties": true } - }, - "required": ["type"], - "additionalProperties": true + ], + "title": "Team Links", + "description": "Links related to a Team." }, "treeentry": { "type": "object", @@ -25212,8 +25238,25 @@ "type": "string", "description": "The user's Atlassian account ID." }, + "account_status": { + "type": "string", + "description": "The status of the account. Currently the only possible value is \"active\", but more values may be added in the future." + }, + "has_2fa_enabled": { + "type": "boolean" + }, "is_staff": { "type": "boolean" + }, + "links": { + "$ref": "#/components/schemas/user_links" + }, + "nickname": { + "type": "string", + "description": "Account name defined by the owner. Should be used instead of the \"username\" field. Note that \"nickname\" cannot be used in place of \"username\" in URLs and queries, as \"nickname\" is not guaranteed to be unique." + }, + "website": { + "type": "string" } }, "additionalProperties": true @@ -25222,6 +25265,30 @@ "title": "User", "description": "A user object." }, + "user_links": { + "allOf": [ + { + "$ref": "#/components/schemas/account_links" + }, + { + "type": "object", + "properties": { + "html": { + "$ref": "#/components/schemas/link" + }, + "repositories": { + "$ref": "#/components/schemas/link" + }, + "self": { + "$ref": "#/components/schemas/link" + } + }, + "additionalProperties": true + } + ], + "title": "User Links", + "description": "Links related to a User." + }, "version": { "allOf": [ { @@ -25289,31 +25356,31 @@ "items": { "type": "string", "enum": [ - "pullrequest:unapproved", + "pullrequest:updated", "issue:comment_created", - "repo:imported", - "repo:created", - "repo:commit_comment_created", - "pullrequest:approved", - "pullrequest:comment_updated", "issue:updated", - "project:updated", - "repo:deleted", + "repo:fork", "pullrequest:changes_request_created", "pullrequest:comment_created", - "repo:commit_status_updated", - "pullrequest:updated", - "issue:created", - "repo:fork", - "pullrequest:comment_deleted", - "repo:commit_status_created", + "repo:created", "repo:updated", + "pullrequest:comment_deleted", "pullrequest:rejected", - "pullrequest:fulfilled", + "issue:created", + "repo:imported", + "pullrequest:unapproved", + "pullrequest:approved", + "pullrequest:comment_updated", + "project:updated", "pullrequest:created", + "pullrequest:fulfilled", + "repo:commit_status_created", "pullrequest:changes_request_removed", - "repo:transfer", - "repo:push" + "repo:push", + "repo:commit_comment_created", + "repo:commit_status_updated", + "repo:deleted", + "repo:transfer" ] }, "minItems": 1, @@ -25325,7 +25392,7 @@ "subject_type": { "type": "string", "description": "The type of entity. Set to either `repository` or `workspace` based on where the subscription is defined.", - "enum": ["workspace", "user", "repository", "team"] + "enum": ["repository", "workspace"] }, "url": { "type": "string", diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json index 1e8db8185f..f7f1ffceac 100644 --- a/plugins/bitbucket-cloud-common/package.json +++ b/plugins/bitbucket-cloud-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-bitbucket-cloud-common", "description": "Common functionalities for bitbucket-cloud plugins", - "version": "0.1.3", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,7 +21,7 @@ "clean": "backstage-cli package clean", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "refresh-schema": "scripts/prepare-schema.js && prettier --check bitbucket-cloud.oas.json -w", + "refresh-schema": "scripts/prepare-schema.js && yarn run -T prettier --check bitbucket-cloud.oas.json -w", "generate-models": "scripts/generate-models.sh", "reduce-models": "scripts/reduce-models.js", "update-models": "yarn refresh-schema && yarn generate-models && yarn reduce-models" diff --git a/plugins/bitbucket-cloud-common/scripts/adjust-models.js b/plugins/bitbucket-cloud-common/scripts/adjust-models.js index 5d6eea845b..80b8f88903 100755 --- a/plugins/bitbucket-cloud-common/scripts/adjust-models.js +++ b/plugins/bitbucket-cloud-common/scripts/adjust-models.js @@ -99,6 +99,18 @@ function setPaginatedResultItemType(modelsModule) { }); } +function fixTeamLinksExtension(modelsModule) { + const accountLinks = modelsModule.getInterfaceOrThrow('AccountLinks'); + const teamLinks = modelsModule.getInterfaceOrThrow('TeamLinks'); + teamLinks.addExtends(accountLinks.getName()); + accountLinks.getProperties().forEach(prop => { + const other = teamLinks.getProperty(prop.getName()); + if (other && other.getText() === prop.getText()) { + other.remove(); + } + }); +} + const project = new tsMorph.Project({ tsConfigFilePath: '../../tsconfig.json', skipAddingFilesFromTsConfig: true, @@ -111,5 +123,6 @@ const modelsModule = modelsFile.getModuleOrThrow('Models'); cleanupWrongAllOfModels(modelsModule); makePaginatedGeneric(modelsModule); setPaginatedResultItemType(modelsModule); +fixTeamLinksExtension(modelsModule); project.saveSync(); diff --git a/plugins/bitbucket-cloud-common/scripts/generate-models.sh b/plugins/bitbucket-cloud-common/scripts/generate-models.sh index 7dd3170fd2..29256cc935 100755 --- a/plugins/bitbucket-cloud-common/scripts/generate-models.sh +++ b/plugins/bitbucket-cloud-common/scripts/generate-models.sh @@ -22,4 +22,4 @@ PLUGIN_DIR="${SCRIPT_DIR}/.." yarn --cwd "${PLUGIN_DIR}" openapi-generator-cli generate --generator-key backstage rm -d "${PLUGIN_DIR}/src/apis" # empty dir or fails "${SCRIPT_DIR}"/adjust-models.js -yarn --cwd "${PLUGIN_DIR}" prettier --check . -w +yarn run -T prettier --check . -w diff --git a/plugins/bitbucket-cloud-common/scripts/prepare-schema.js b/plugins/bitbucket-cloud-common/scripts/prepare-schema.js index 7454aecb40..aa32295cfc 100755 --- a/plugins/bitbucket-cloud-common/scripts/prepare-schema.js +++ b/plugins/bitbucket-cloud-common/scripts/prepare-schema.js @@ -214,19 +214,6 @@ const preventHardToDetectDuplicateInterfacesDueToAllOf = json => { return json; }; -const removeBuggyDescription = json => { - const valueProp = - json.components.schemas.branchrestriction.allOf[1].properties.value; - if (!valueProp.description.startsWith(' { const schema = json.components.schemas.pipeline_selector; const extension = schema.allOf[1]; @@ -269,7 +256,6 @@ fetch(SCHEMA_SOURCE) .then(addPaginatedDefinition) .then(paginatedDefinitionsExtendPaginated) .then(preventHardToDetectDuplicateInterfacesDueToAllOf) - .then(removeBuggyDescription) .then(resolveConflictingInheritance) .then(escapeTsdocInDescription) .then(relativeToAbsoluteUrls) diff --git a/plugins/bitbucket-cloud-common/src/models/index.ts b/plugins/bitbucket-cloud-common/src/models/index.ts index 14cb8865b8..580f23a13f 100644 --- a/plugins/bitbucket-cloud-common/src/models/index.ts +++ b/plugins/bitbucket-cloud-common/src/models/index.ts @@ -33,33 +33,20 @@ export namespace Models { * @public */ export interface Account extends ModelObject { - /** - * The status of the account. Currently the only possible value is "active", but more values may be added in the future. - */ - account_status?: string; created_on?: string; display_name?: string; - has_2fa_enabled?: boolean; links?: AccountLinks; - /** - * Account name defined by the owner. Should be used instead of the "username" field. Note that "nickname" cannot be used in place of "username" in URLs and queries, as "nickname" is not guaranteed to be unique. - */ - nickname?: string; username?: string; uuid?: string; - website?: string; } /** + * Links related to an Account. * @public */ export interface AccountLinks { + [key: string]: unknown; avatar?: Link; - followers?: Link; - following?: Link; - html?: Link; - repositories?: Link; - self?: Link; } /** @@ -278,7 +265,7 @@ export namespace Models { participated_on?: string; role?: ParticipantRoleEnum; state?: ParticipantStateEnum; - user?: User; + user?: Account; } /** @@ -516,17 +503,19 @@ export namespace Models { * A team object. * @public */ - export interface Team extends Account {} + export interface Team extends Account { + links?: TeamLinks; + } /** - * A user object. + * Links related to a Team. * @public */ - export interface User extends Account { - /** - * The user's Atlassian account ID. - */ - account_id?: string; - is_staff?: boolean; + export interface TeamLinks extends AccountLinks { + html?: Link; + members?: Link; + projects?: Link; + repositories?: Link; + self?: Link; } } diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md index 55eb78d63e..9e6d7a9306 100644 --- a/plugins/bitrise/CHANGELOG.md +++ b/plugins/bitrise/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-bitrise +## 0.1.37 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.1.37-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.1.37-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.1.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.1.36 ### Patch Changes diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 1a7e586e5c..4240262c07 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-bitrise", "description": "A Backstage plugin that integrates towards Bitrise", - "version": "0.1.36", + "version": "0.1.37", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index ea6f2810e7..47dbcd7f53 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + ## 0.1.9 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/api-report.md b/plugins/catalog-backend-module-aws/api-report.md index b1f6358979..a911fb5ebb 100644 --- a/plugins/catalog-backend-module-aws/api-report.md +++ b/plugins/catalog-backend-module-aws/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorParser } from '@backstage/plugin-catalog-backend'; @@ -12,6 +13,7 @@ import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { TaskRunner } from '@backstage/backend-tasks'; import { UrlReader } from '@backstage/backend-common'; @@ -77,7 +79,8 @@ export class AwsS3EntityProvider implements EntityProvider { configRoot: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): AwsS3EntityProvider[]; // (undocumented) @@ -85,4 +88,9 @@ export class AwsS3EntityProvider implements EntityProvider { // (undocumented) refresh(logger: Logger): Promise; } + +// @alpha +export const awsS3EntityProviderCatalogModule: ( + options?: undefined, +) => BackendFeature; ``` diff --git a/plugins/catalog-backend-module-aws/config.d.ts b/plugins/catalog-backend-module-aws/config.d.ts index 6f84ca6daf..d6efe10c90 100644 --- a/plugins/catalog-backend-module-aws/config.d.ts +++ b/plugins/catalog-backend-module-aws/config.d.ts @@ -14,11 +14,10 @@ * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + export interface Config { catalog?: { - /** - * List of processor-specific options and attributes - */ processors?: { /** * AwsOrganizationCloudAccountProcessor configuration @@ -32,9 +31,6 @@ export interface Config { }; }; }; - /** - * List of provider-specific options and attributes - */ providers?: { /** * AwsS3EntityProvider configuration @@ -45,22 +41,23 @@ export interface Config { | { /** * (Required) AWS S3 Bucket Name - * @visibility backend */ bucketName: string; /** * (Optional) AWS S3 Object key prefix * If not set, all keys will be accepted, no filtering will be applied. - * @visibility backend */ prefix?: string; /** * (Optional) AWS Region. * If not set, AWS_REGION environment variable or aws config file will be used. * @see https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/setting-region.html - * @visibility backend */ region?: string; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } | Record< string, @@ -83,6 +80,10 @@ export interface Config { * @visibility backend */ region?: string; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } >; }; diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index c921aad767..37acb57254 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,12 +1,13 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", "description": "A Backstage catalog backend module that helps integrate towards AWS", - "version": "0.1.9", + "version": "0.1.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { "access": "public", + "alphaTypes": "dist/index.alpha.d.ts", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, @@ -23,22 +24,24 @@ "backstage" ], "scripts": { - "build": "backstage-cli package build", + "start": "backstage-cli package start", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "clean": "backstage-cli package clean" }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", "aws-sdk": "^2.840.0", "lodash": "^4.17.21", @@ -47,14 +50,17 @@ "winston": "^3.2.1" }, "devDependencies": { + "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/lodash": "^4.14.151", "aws-sdk-mock": "^5.2.1", + "luxon": "^3.0.0", "yaml": "^2.0.0" }, "files": [ - "dist", - "config.d.ts" + "alpha", + "config.d.ts", + "dist" ], "configSchema": "config.d.ts" } diff --git a/plugins/catalog-backend-module-aws/src/index.ts b/plugins/catalog-backend-module-aws/src/index.ts index 212308edaa..804fd97e91 100644 --- a/plugins/catalog-backend-module-aws/src/index.ts +++ b/plugins/catalog-backend-module-aws/src/index.ts @@ -22,4 +22,5 @@ export * from './processors'; export * from './providers'; +export { awsS3EntityProviderCatalogModule } from './service/AwsS3EntityProviderCatalogModule'; export * from './types'; diff --git a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts index 3d59d738cf..134a3c6338 100644 --- a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts +++ b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.test.ts @@ -15,7 +15,11 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { AwsS3EntityProvider } from './AwsS3EntityProvider'; @@ -83,6 +87,7 @@ describe('AwsS3EntityProvider', () => { expectedBaseUrl: string, names: Record, integrationConfig?: object, + scheduleInConfig?: boolean, ) => { const config = new ConfigReader({ integrations: { @@ -97,15 +102,25 @@ describe('AwsS3EntityProvider', () => { }, }); + const schedulingConfig: Record = {}; + const schedule = new PersistingTaskRunner(); const entityProviderConnection: EntityProviderConnection = { applyMutation: jest.fn(), refresh: jest.fn(), }; + if (scheduleInConfig) { + schedulingConfig.scheduler = { + createScheduledTaskRunner: (_: any) => schedule, + } as unknown as PluginTaskScheduler; + } else { + schedulingConfig.schedule = schedule; + } + const provider = AwsS3EntityProvider.fromConfig(config, { + ...schedulingConfig, logger, - schedule, })[0]; expect(provider.getProviderName()).toEqual(`awsS3-provider:${providerId}`); @@ -224,4 +239,81 @@ describe('AwsS3EntityProvider', () => { }, ); }); + + it('fail without schedule and scheduler', () => { + const config = new ConfigReader({ + catalog: { + providers: { + awsS3: { + test: { + bucketName: 'bucket-1', + prefix: 'sub/dir/', + region: 'eu-west-1', + }, + }, + }, + }, + }); + + expect(() => + AwsS3EntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const scheduler = { + createScheduledTaskRunner: (_: any) => jest.fn(), + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + catalog: { + providers: { + awsS3: { + test: { + bucketName: 'bucket-1', + prefix: 'sub/dir/', + region: 'eu-west-1', + }, + }, + }, + }, + }); + + expect(() => + AwsS3EntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for awsS3-provider:test', + ); + }); + + // eslint-disable-next-line jest/expect-expect + it('single simple provider config with schedule in config', async () => { + return expectMutation( + 'regionalStatic', + { + bucketName: 'bucket-1', + prefix: 'sub/dir/', + region: 'eu-west-1', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, + 'https://s3.eu-west-1.amazonaws.com/bucket-1/sub/dir/', + { + 'key1.yaml': 'generated-7f6d5861b0b3401a38b5fe62e6c7ca11da5fd6d8', + 'key2.yaml': 'generated-a290be145586042af7d80715626399c9d661718d', + 'key3.yaml': 'generated-8d75f78ed9fa618ce433b226dc24eeab441f3a2d', + 'key 4.yaml': 'generated-1e0249dcb5805fc2ce6ac2d3c4d2a3ef4f1270c0', + }, + undefined, + true, + ); + }); }); diff --git a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts index cee5eea1a4..80bc4d459b 100644 --- a/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts +++ b/plugins/catalog-backend-module-aws/src/providers/AwsS3EntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { AwsS3Integration, ScmIntegrations } from '@backstage/integration'; import { @@ -49,7 +49,8 @@ export class AwsS3EntityProvider implements EntityProvider { configRoot: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): AwsS3EntityProvider[] { const providerConfigs = readAwsS3Configs(configRoot); @@ -67,22 +68,35 @@ export class AwsS3EntityProvider implements EntityProvider { throw new Error('No integration found for awsS3'); } - return providerConfigs.map( - providerConfig => - new AwsS3EntityProvider( - providerConfig, - integration, - options.logger, - options.schedule, - ), - ); + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + + return providerConfigs.map(providerConfig => { + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for awsS3-provider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + + return new AwsS3EntityProvider( + providerConfig, + integration, + options.logger, + taskRunner, + ); + }); } private constructor( private readonly config: AwsS3Config, integration: AwsS3Integration, logger: Logger, - schedule: TaskRunner, + taskRunner: TaskRunner, ) { this.logger = logger.child({ target: this.getProviderName(), @@ -99,13 +113,13 @@ export class AwsS3EntityProvider implements EntityProvider { s3ForcePathStyle: integration.config.s3ForcePathStyle, }); - this.scheduleFn = this.createScheduleFn(schedule); + this.scheduleFn = this.createScheduleFn(taskRunner); } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn(taskRunner: TaskRunner): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; - return schedule.run({ + return taskRunner.run({ id: taskId, fn: async () => { const logger = this.logger.child({ diff --git a/plugins/catalog-backend-module-aws/src/providers/config.test.ts b/plugins/catalog-backend-module-aws/src/providers/config.test.ts index 534d4da35e..fb2f3c0790 100644 --- a/plugins/catalog-backend-module-aws/src/providers/config.test.ts +++ b/plugins/catalog-backend-module-aws/src/providers/config.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; import { readAwsS3Configs } from './config'; describe('readAwsS3Configs', () => { @@ -54,17 +55,26 @@ describe('readAwsS3Configs', () => { const provider3 = { bucketName: 'bucket-3', }; + const provider4 = { + bucketName: 'bucket-4', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }; const config = { catalog: { providers: { - awsS3: { provider1, provider2, provider3 }, + awsS3: { provider1, provider2, provider3, provider4 }, }, }, }; const actual = readAwsS3Configs(new ConfigReader(config)); - expect(actual).toHaveLength(3); + expect(actual).toHaveLength(4); expect(actual[0]).toEqual({ ...provider1, id: 'provider1', @@ -77,6 +87,14 @@ describe('readAwsS3Configs', () => { ...provider3, id: 'provider3', }); + expect(actual[3]).toEqual({ + ...provider4, + id: 'provider4', + schedule: { + ...provider4.schedule, + frequency: Duration.fromISO(provider4.schedule.frequency), + }, + }); }); it('fails if bucketName is missing', () => { diff --git a/plugins/catalog-backend-module-aws/src/providers/config.ts b/plugins/catalog-backend-module-aws/src/providers/config.ts index 5fdf2351c3..5ef2729bf0 100644 --- a/plugins/catalog-backend-module-aws/src/providers/config.ts +++ b/plugins/catalog-backend-module-aws/src/providers/config.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { AwsS3Config } from './types'; @@ -46,10 +47,15 @@ function readAwsS3Config(id: string, config: Config): AwsS3Config { const region = config.getOptionalString('region'); const prefix = config.getOptionalString('prefix'); + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { id, bucketName, region, prefix, + schedule, }; } diff --git a/plugins/catalog-backend-module-aws/src/providers/types.ts b/plugins/catalog-backend-module-aws/src/providers/types.ts index 3508bf919c..204fa154c6 100644 --- a/plugins/catalog-backend-module-aws/src/providers/types.ts +++ b/plugins/catalog-backend-module-aws/src/providers/types.ts @@ -14,9 +14,12 @@ * limitations under the License. */ +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; + export type AwsS3Config = { id: string; bucketName: string; prefix?: string; region?: string; + schedule?: TaskScheduleDefinition; }; diff --git a/plugins/catalog-backend-module-aws/src/service/AwsS3EntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-aws/src/service/AwsS3EntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..3167291bca --- /dev/null +++ b/plugins/catalog-backend-module-aws/src/service/AwsS3EntityProviderCatalogModule.test.ts @@ -0,0 +1,84 @@ +/* + * 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 { getVoidLogger } from '@backstage/backend-common'; +import { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { Duration } from 'luxon'; +import { awsS3EntityProviderCatalogModule } from './AwsS3EntityProviderCatalogModule'; +import { AwsS3EntityProvider } from '../providers'; + +describe('awsS3EntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + catalog: { + providers: { + awsS3: { + bucketName: 'module-test', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [awsS3EntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'awsS3-provider:default', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-aws/src/service/AwsS3EntityProviderCatalogModule.ts b/plugins/catalog-backend-module-aws/src/service/AwsS3EntityProviderCatalogModule.ts new file mode 100644 index 0000000000..79269ad53a --- /dev/null +++ b/plugins/catalog-backend-module-aws/src/service/AwsS3EntityProviderCatalogModule.ts @@ -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 { + createBackendModule, + loggerToWinstonLogger, + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { AwsS3EntityProvider } from '../providers'; + +/** + * Registers the AwsS3EntityProvider with the catalog processing extension point. + * + * @alpha + */ +export const awsS3EntityProviderCatalogModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'awsS3EntityProvider', + register(env) { + env.registerInit({ + deps: { + config: configServiceRef, + catalog: catalogProcessingExtensionPoint, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ config, catalog, logger, scheduler }) { + catalog.addEntityProvider( + AwsS3EntityProvider.fromConfig(config, { + logger: loggerToWinstonLogger(logger), + scheduler, + }), + ); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index 51d9752af9..7c40b06228 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## 0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + ## 0.1.7 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/api-report.md b/plugins/catalog-backend-module-azure/api-report.md index 22f5015c9b..84ffaf39e9 100644 --- a/plugins/catalog-backend-module-azure/api-report.md +++ b/plugins/catalog-backend-module-azure/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; import { Config } from '@backstage/config'; @@ -10,6 +11,7 @@ import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { TaskRunner } from '@backstage/backend-tasks'; @@ -45,7 +47,8 @@ export class AzureDevOpsEntityProvider implements EntityProvider { configRoot: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): AzureDevOpsEntityProvider[]; // (undocumented) @@ -53,4 +56,9 @@ export class AzureDevOpsEntityProvider implements EntityProvider { // (undocumented) refresh(logger: Logger): Promise; } + +// @alpha +export const azureDevOpsEntityProviderCatalogModule: ( + options?: undefined, +) => BackendFeature; ``` diff --git a/plugins/catalog-backend-module-azure/config.d.ts b/plugins/catalog-backend-module-azure/config.d.ts index 6e423c9415..940695c8ea 100644 --- a/plugins/catalog-backend-module-azure/config.d.ts +++ b/plugins/catalog-backend-module-azure/config.d.ts @@ -14,34 +14,35 @@ * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + interface AzureDevOpsConfig { /** * (Optional) The DevOps host; leave empty for `dev.azure.com`, otherwise set to your self-hosted instance host. - * @visibility backend */ host: string; /** * (Required) Your organization slug. - * @visibility backend */ organization: string; /** * (Required) Your project slug. - * @visibility backend */ project: string; /** * (Optional) The repository name. Wildcards are supported as show on the examples above. * If not set, all repositories will be searched. - * @visibility backend */ repository?: string; /** * (Optional) Where to find catalog-info.yaml files. Wildcards are supported. * If not set, defaults to /catalog-info.yaml. - * @visibility backend */ path?: string; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } export interface Config { diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index afcfdf9f0f..b4396a32af 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,12 +1,13 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", "description": "A Backstage catalog backend module that helps integrate towards Azure", - "version": "0.1.7", + "version": "0.1.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { "access": "public", + "alphaTypes": "dist/index.alpha.d.ts", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, @@ -23,22 +24,24 @@ "backstage" ], "scripts": { - "build": "backstage-cli package build", + "start": "backstage-cli package start", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "clean": "backstage-cli package clean" }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", "lodash": "^4.17.21", "msw": "^0.47.0", @@ -49,11 +52,13 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/lodash": "^4.14.151" + "@types/lodash": "^4.14.151", + "luxon": "^3.0.0" }, "files": [ - "dist", - "config.d.ts" + "alpha", + "config.d.ts", + "dist" ], "configSchema": "config.d.ts" } diff --git a/plugins/catalog-backend-module-azure/src/index.ts b/plugins/catalog-backend-module-azure/src/index.ts index a71c40ee6c..aa3a94fda2 100644 --- a/plugins/catalog-backend-module-azure/src/index.ts +++ b/plugins/catalog-backend-module-azure/src/index.ts @@ -22,3 +22,4 @@ export { AzureDevOpsDiscoveryProcessor } from './processors'; export { AzureDevOpsEntityProvider } from './providers'; +export { azureDevOpsEntityProviderCatalogModule } from './service/AzureDevOpsEntityProviderCatalogModule'; diff --git a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts index c57455d8d6..f7a63f259e 100644 --- a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts +++ b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.test.ts @@ -15,7 +15,11 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { CodeSearchResultItem } from '../lib'; @@ -52,6 +56,7 @@ describe('AzureDevOpsEntityProvider', () => { expectedBaseUrl: string, names: Record, integrationConfig?: object, + scheduleInConfig?: boolean, ) => { const config = new ConfigReader({ integrations: { @@ -74,9 +79,18 @@ describe('AzureDevOpsEntityProvider', () => { refresh: jest.fn(), }; + const schedulingConfig: Record = {}; + if (scheduleInConfig) { + schedulingConfig.scheduler = { + createScheduledTaskRunner: (_: any) => schedule, + } as unknown as PluginTaskScheduler; + } else { + schedulingConfig.schedule = schedule; + } + const provider = AzureDevOpsEntityProvider.fromConfig(config, { + ...schedulingConfig, logger, - schedule, })[0]; expect(provider.getProviderName()).toEqual( `AzureDevOpsEntityProvider:${providerId}`, @@ -193,4 +207,92 @@ describe('AzureDevOpsEntityProvider', () => { }, ); }); + + it('fail without schedule and scheduler', () => { + const config = new ConfigReader({ + catalog: { + providers: { + azureDevOps: { + test: { + organization: 'myorganization', + project: 'myproject', + }, + }, + }, + }, + }); + + expect(() => + AzureDevOpsEntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const scheduler = { + createScheduledTaskRunner: (_: any) => jest.fn(), + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + catalog: { + providers: { + azureDevOps: { + test: { + organization: 'myorganization', + project: 'myproject', + }, + }, + }, + }, + }); + + expect(() => + AzureDevOpsEntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for AzureDevOpsEntityProvider:test', + ); + }); + + // eslint-disable-next-line jest/expect-expect + it('single simple provider config with schedule in config', async () => { + return expectMutation( + 'allReposMultipleFiles', + { + organization: 'myorganization', + project: 'myproject', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, + [ + { + fileName: 'catalog-info.yaml', + path: '/catalog-info.yaml', + repository: { + name: 'myrepo', + }, + }, + { + fileName: 'catalog-info.yaml', + path: '/catalog-info.yaml', + repository: { + name: 'myotherrepo', + }, + }, + ], + 'https://dev.azure.com/myorganization/myproject', + { + 'myrepo?path=/catalog-info.yaml': + 'generated-87865246726bb12a8c4fb4f914443f1fbb91648c', + 'myotherrepo?path=/catalog-info.yaml': + 'generated-2deccac384c34d0dca37be0ebb4b1c8cf6913fe1', + }, + ); + }); }); diff --git a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts index aca30f367a..b824d176ee 100644 --- a/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts +++ b/plugins/catalog-backend-module-azure/src/providers/AzureDevOpsEntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { AzureIntegration, ScmIntegrations } from '@backstage/integration'; import { @@ -45,11 +45,16 @@ export class AzureDevOpsEntityProvider implements EntityProvider { configRoot: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): AzureDevOpsEntityProvider[] { const providerConfigs = readAzureDevOpsConfigs(configRoot); + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + return providerConfigs.map(providerConfig => { const integration = ScmIntegrations.fromConfig(configRoot).azure.byHost( providerConfig.host, @@ -61,11 +66,21 @@ export class AzureDevOpsEntityProvider implements EntityProvider { ); } + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for AzureDevOpsEntityProvider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + return new AzureDevOpsEntityProvider( providerConfig, integration, options.logger, - options.schedule, + taskRunner, ); }); } @@ -74,19 +89,19 @@ export class AzureDevOpsEntityProvider implements EntityProvider { private readonly config: AzureDevOpsConfig, private readonly integration: AzureIntegration, logger: Logger, - schedule: TaskRunner, + taskRunner: TaskRunner, ) { this.logger = logger.child({ target: this.getProviderName(), }); - this.scheduleFn = this.createScheduleFn(schedule); + this.scheduleFn = this.createScheduleFn(taskRunner); } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn(taskRunner: TaskRunner): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; - return schedule.run({ + return taskRunner.run({ id: taskId, fn: async () => { const logger = this.logger.child({ diff --git a/plugins/catalog-backend-module-azure/src/providers/config.test.ts b/plugins/catalog-backend-module-azure/src/providers/config.test.ts index 236c9a48d4..a09079f1c3 100644 --- a/plugins/catalog-backend-module-azure/src/providers/config.test.ts +++ b/plugins/catalog-backend-module-azure/src/providers/config.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; import { readAzureDevOpsConfigs } from './config'; describe('readAzureDevOpsConfigs', () => { @@ -33,17 +34,27 @@ describe('readAzureDevOpsConfigs', () => { project: 'myproject', repository: 'service-*', }; + const provider4 = { + organization: 'mycompany', + project: 'myproject', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }; const config = { catalog: { providers: { - azureDevOps: { provider1, provider2, provider3 }, + azureDevOps: { provider1, provider2, provider3, provider4 }, }, }, }; const actual = readAzureDevOpsConfigs(new ConfigReader(config)); - expect(actual).toHaveLength(3); + expect(actual).toHaveLength(4); expect(actual[0]).toEqual({ ...provider1, path: '/catalog-info.yaml', @@ -63,5 +74,16 @@ describe('readAzureDevOpsConfigs', () => { path: '/catalog-info.yaml', id: 'provider3', }); + expect(actual[3]).toEqual({ + ...provider4, + host: 'dev.azure.com', + path: '/catalog-info.yaml', + repository: '*', + id: 'provider4', + schedule: { + ...provider4.schedule, + frequency: Duration.fromISO(provider4.schedule.frequency), + }, + }); }); }); diff --git a/plugins/catalog-backend-module-azure/src/providers/config.ts b/plugins/catalog-backend-module-azure/src/providers/config.ts index 1d143fc882..c84c242488 100644 --- a/plugins/catalog-backend-module-azure/src/providers/config.ts +++ b/plugins/catalog-backend-module-azure/src/providers/config.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { AzureDevOpsConfig } from './types'; @@ -42,6 +43,10 @@ function readAzureDevOpsConfig(id: string, config: Config): AzureDevOpsConfig { const repository = config.getOptionalString('repository') || '*'; const path = config.getOptionalString('path') || '/catalog-info.yaml'; + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { id, host, @@ -49,5 +54,6 @@ function readAzureDevOpsConfig(id: string, config: Config): AzureDevOpsConfig { project, repository, path, + schedule, }; } diff --git a/plugins/catalog-backend-module-azure/src/providers/types.ts b/plugins/catalog-backend-module-azure/src/providers/types.ts index 15ea00ff13..b22a8551a5 100644 --- a/plugins/catalog-backend-module-azure/src/providers/types.ts +++ b/plugins/catalog-backend-module-azure/src/providers/types.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; + export type AzureDevOpsConfig = { id: string; host: string; @@ -21,4 +23,5 @@ export type AzureDevOpsConfig = { project: string; repository: string; path: string; + schedule?: TaskScheduleDefinition; }; diff --git a/plugins/catalog-backend-module-azure/src/service/AzureDevOpsEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-azure/src/service/AzureDevOpsEntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..e3a0449a8e --- /dev/null +++ b/plugins/catalog-backend-module-azure/src/service/AzureDevOpsEntityProviderCatalogModule.test.ts @@ -0,0 +1,87 @@ +/* + * 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 { getVoidLogger } from '@backstage/backend-common'; +import { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { Duration } from 'luxon'; +import { azureDevOpsEntityProviderCatalogModule } from './AzureDevOpsEntityProviderCatalogModule'; +import { AzureDevOpsEntityProvider } from '../providers'; + +describe('azureDevOpsEntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + catalog: { + providers: { + azureDevOps: { + test: { + organization: 'myorganization', + project: 'myproject', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [azureDevOpsEntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'AzureDevOpsEntityProvider:test', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-azure/src/service/AzureDevOpsEntityProviderCatalogModule.ts b/plugins/catalog-backend-module-azure/src/service/AzureDevOpsEntityProviderCatalogModule.ts new file mode 100644 index 0000000000..f3621bee23 --- /dev/null +++ b/plugins/catalog-backend-module-azure/src/service/AzureDevOpsEntityProviderCatalogModule.ts @@ -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 { + createBackendModule, + loggerToWinstonLogger, + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { AzureDevOpsEntityProvider } from '../providers'; + +/** + * Registers the AzureDevOpsEntityProvider with the catalog processing extension point. + * + * @alpha + */ +export const azureDevOpsEntityProviderCatalogModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'azureDevOpsEntityProvider', + register(env) { + env.registerInit({ + deps: { + config: configServiceRef, + catalog: catalogProcessingExtensionPoint, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ config, catalog, logger, scheduler }) { + catalog.addEntityProvider( + AzureDevOpsEntityProvider.fromConfig(config, { + logger: loggerToWinstonLogger(logger), + scheduler, + }), + ); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md index 453f6124ba..a024b12275 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -1,5 +1,65 @@ # @backstage/plugin-catalog-backend-module-bitbucket-cloud +## 0.1.4 + +### Patch Changes + +- f66e696e7b: Bitbucket Cloud provider: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/bitbucketCloud/discovery + +- a9b91d39bb: Add `bitbucketCloudCatalogModule` (new backend-plugin-api, alpha). +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/plugin-bitbucket-cloud-common@0.2.0 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + +## 0.1.4-next.2 + +### Patch Changes + +- f66e696e7b: Bitbucket Cloud provider: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/bitbucketCloud/discovery + +- a9b91d39bb: Add `bitbucketCloudCatalogModule` (new backend-plugin-api, alpha). +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/integration@1.3.2-next.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-cloud/api-report.md b/plugins/catalog-backend-module-bitbucket-cloud/api-report.md index b886cc224f..162ff8f67e 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/api-report.md +++ b/plugins/catalog-backend-module-bitbucket-cloud/api-report.md @@ -3,10 +3,12 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { TaskRunner } from '@backstage/backend-tasks'; // @public @@ -18,7 +20,8 @@ export class BitbucketCloudEntityProvider implements EntityProvider { config: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): BitbucketCloudEntityProvider[]; // (undocumented) @@ -28,4 +31,9 @@ export class BitbucketCloudEntityProvider implements EntityProvider { // (undocumented) refresh(logger: Logger): Promise; } + +// @alpha (undocumented) +export const bitbucketCloudEntityProviderCatalogModule: ( + options?: undefined, +) => BackendFeature; ``` diff --git a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts index 29289bb70e..8fa58413c0 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + export interface Config { catalog?: { /** @@ -53,6 +55,10 @@ export interface Config { */ projectKey?: RegExp; }; + /** + * (Optional) TaskScheduleDefinition for the discovery. + */ + schedule?: TaskScheduleDefinitionConfig; } | Record< string, @@ -83,6 +89,10 @@ export interface Config { */ projectKey?: RegExp; }; + /** + * (Optional) TaskScheduleDefinition for the discovery. + */ + schedule?: TaskScheduleDefinitionConfig; } >; }; diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json index 4fdebd65bd..f3564463cc 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/package.json +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -1,12 +1,13 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { "access": "public", + "alphaTypes": "dist/index.alpha.d.ts", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, @@ -23,20 +24,22 @@ "backstage" ], "scripts": { - "build": "backstage-cli package build", + "start": "backstage-cli package start", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "clean": "backstage-cli package clean" }, "dependencies": { + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/config": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-bitbucket-cloud-common": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "uuid": "^8.0.0", "winston": "^3.2.1" }, @@ -44,11 +47,13 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", + "luxon": "^3.0.0", "msw": "^0.47.0" }, "files": [ - "dist", - "config.d.ts" + "alpha", + "config.d.ts", + "dist" ], "configSchema": "config.d.ts" } diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProvider.test.ts index 641150acbf..7d780cd064 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProvider.test.ts @@ -15,7 +15,11 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; @@ -77,6 +81,75 @@ describe('BitbucketCloudEntityProvider', () => { ); }); + it('fail without schedule and scheduler', () => { + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketCloud: { + workspace: 'test-ws', + }, + }, + }, + }); + + expect(() => + BitbucketCloudEntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided.'); + }); + + it('fail with scheduler but no schedule config', () => { + const scheduler = jest.fn() as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketCloud: { + workspace: 'test-ws', + }, + }, + }, + }); + + expect(() => + BitbucketCloudEntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for bitbucketCloud-provider:default.', + ); + }); + + it('single simple provider config with schedule in config', () => { + const scheduler = { + createScheduledTaskRunner: (_: any) => jest.fn(), + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketCloud: { + workspace: 'test-ws', + schedule: { + frequency: 'PT30M', + timeout: 'PT3M', + }, + }, + }, + }, + }); + + const providers = BitbucketCloudEntityProvider.fromConfig(config, { + logger, + scheduler, + }); + + expect(providers).toHaveLength(1); + expect(providers[0].getProviderName()).toEqual( + 'bitbucketCloud-provider:default', + ); + }); + it('multiple provider configs', () => { const schedule = new PersistingTaskRunner(); const config = new ConfigReader({ diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProvider.ts index 74d642ed9b..f3334a6574 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { BitbucketCloudIntegration, @@ -58,7 +58,8 @@ export class BitbucketCloudEntityProvider implements EntityProvider { config: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): BitbucketCloudEntityProvider[] { const integrations = ScmIntegrations.fromConfig(config); @@ -69,29 +70,42 @@ export class BitbucketCloudEntityProvider implements EntityProvider { throw new Error('No integration for bitbucket.org available'); } - return readProviderConfigs(config).map( - providerConfig => - new BitbucketCloudEntityProvider( - providerConfig, - integration, - options.logger, - options.schedule, - ), - ); + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + + return readProviderConfigs(config).map(providerConfig => { + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for bitbucketCloud-provider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + + return new BitbucketCloudEntityProvider( + providerConfig, + integration, + options.logger, + taskRunner, + ); + }); } private constructor( config: BitbucketCloudEntityProviderConfig, integration: BitbucketCloudIntegration, logger: Logger, - schedule: TaskRunner, + taskRunner: TaskRunner, ) { this.client = BitbucketCloudClient.fromConfig(integration.config); this.config = config; this.logger = logger.child({ target: this.getProviderName(), }); - this.scheduleFn = this.createScheduleFn(schedule); + this.scheduleFn = this.createScheduleFn(taskRunner); } private createScheduleFn(schedule: TaskRunner): () => Promise { diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.test.ts index a42bd2cde7..5a2cc99437 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.test.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; import { readProviderConfigs } from './BitbucketCloudEntityProviderConfig'; describe('readProviderConfigs', () => { @@ -68,13 +69,22 @@ describe('readProviderConfigs', () => { repoSlug: 'repoSlug.*filter', }, }, + providerWithSchedule: { + workspace: 'test-ws5', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, }, }, }, }); const providerConfigs = readProviderConfigs(config); - expect(providerConfigs).toHaveLength(4); + expect(providerConfigs).toHaveLength(5); expect(providerConfigs[0]).toEqual({ id: 'providerWorkspaceOnly', workspace: 'test-ws1', @@ -111,5 +121,20 @@ describe('readProviderConfigs', () => { repoSlug: /^repoSlug.*filter$/, }, }); + expect(providerConfigs[4]).toEqual({ + id: 'providerWithSchedule', + workspace: 'test-ws5', + catalogPath: '/catalog-info.yaml', + filters: { + projectKey: undefined, + repoSlug: undefined, + }, + schedule: { + frequency: Duration.fromISO('PT30M'), + timeout: { + minutes: 3, + }, + }, + }); }); }); diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.ts index 95e995f8c3..def88b0951 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + readTaskScheduleDefinitionFromConfig, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; const DEFAULT_CATALOG_PATH = '/catalog-info.yaml'; @@ -27,6 +31,7 @@ export type BitbucketCloudEntityProviderConfig = { projectKey?: RegExp; repoSlug?: RegExp; }; + schedule?: TaskScheduleDefinition; }; export function readProviderConfigs( @@ -61,6 +66,10 @@ function readProviderConfig( const projectKeyPattern = config.getOptionalString('filters.projectKey'); const repoSlugPattern = config.getOptionalString('filters.repoSlug'); + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { id, catalogPath, @@ -71,6 +80,7 @@ function readProviderConfig( : undefined, repoSlug: repoSlugPattern ? compileRegExp(repoSlugPattern) : undefined, }, + schedule, }; } diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/index.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/index.ts index 1c15ad4e8f..c1f04b8877 100644 --- a/plugins/catalog-backend-module-bitbucket-cloud/src/index.ts +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/index.ts @@ -21,3 +21,4 @@ */ export { BitbucketCloudEntityProvider } from './BitbucketCloudEntityProvider'; +export { bitbucketCloudEntityProviderCatalogModule } from './service/BitbucketCloudEntityProviderCatalogModule'; diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/service/BitbucketCloudCatalogModule.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/service/BitbucketCloudCatalogModule.test.ts new file mode 100644 index 0000000000..6307bee282 --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/service/BitbucketCloudCatalogModule.test.ts @@ -0,0 +1,84 @@ +/* + * 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 { ConfigReader } from '@backstage/config'; +import { getVoidLogger } from '@backstage/backend-common'; +import { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { bitbucketCloudEntityProviderCatalogModule } from './BitbucketCloudEntityProviderCatalogModule'; +import { Duration } from 'luxon'; +import { BitbucketCloudEntityProvider } from '../BitbucketCloudEntityProvider'; + +describe('bitbucketCloudEntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketCloud: { + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + workspace: 'test-ws', + }, + }, + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [bitbucketCloudEntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'bitbucketCloud-provider:default', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/service/BitbucketCloudEntityProviderCatalogModule.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/service/BitbucketCloudEntityProviderCatalogModule.ts new file mode 100644 index 0000000000..d137bd7166 --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/service/BitbucketCloudEntityProviderCatalogModule.ts @@ -0,0 +1,52 @@ +/* + * 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 { + configServiceRef, + createBackendModule, + loggerServiceRef, + loggerToWinstonLogger, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { BitbucketCloudEntityProvider } from '../BitbucketCloudEntityProvider'; + +/** + * @alpha + */ +export const bitbucketCloudEntityProviderCatalogModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'bitbucketCloudEntityProvider', + register(env) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + config: configServiceRef, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ catalog, config, logger, scheduler }) { + const winstonLogger = loggerToWinstonLogger(logger); + const providers = BitbucketCloudEntityProvider.fromConfig(config, { + logger: winstonLogger, + scheduler, + }); + + catalog.addEntityProvider(providers); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md index 5ec5a515bb..9c9b61247f 100644 --- a/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket-server/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-catalog-backend-module-bitbucket-server +## 0.1.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + ## 0.1.1 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket-server/api-report.md b/plugins/catalog-backend-module-bitbucket-server/api-report.md index 7d95e148a3..a38a4d8c35 100644 --- a/plugins/catalog-backend-module-bitbucket-server/api-report.md +++ b/plugins/catalog-backend-module-bitbucket-server/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { BitbucketServerIntegrationConfig } from '@backstage/integration'; import { Config } from '@backstage/config'; import { Entity } from '@backstage/catalog-model'; @@ -10,6 +11,7 @@ import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { Response as Response_2 } from 'node-fetch'; import { TaskRunner } from '@backstage/backend-tasks'; @@ -55,8 +57,9 @@ export class BitbucketServerEntityProvider implements EntityProvider { config: Config, options: { logger: Logger; - schedule: TaskRunner; parser?: BitbucketServerLocationParser; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): BitbucketServerEntityProvider[]; // (undocumented) @@ -65,6 +68,11 @@ export class BitbucketServerEntityProvider implements EntityProvider { refresh(logger: Logger): Promise; } +// @alpha (undocumented) +export const bitbucketServerEntityProviderCatalogModule: ( + options?: undefined, +) => BackendFeature; + // @public (undocumented) export type BitbucketServerListOptions = { [key: string]: number | undefined; diff --git a/plugins/catalog-backend-module-bitbucket-server/config.d.ts b/plugins/catalog-backend-module-bitbucket-server/config.d.ts index d8a09c92a4..67f694d419 100644 --- a/plugins/catalog-backend-module-bitbucket-server/config.d.ts +++ b/plugins/catalog-backend-module-bitbucket-server/config.d.ts @@ -14,11 +14,10 @@ * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + export interface Config { catalog?: { - /** - * List of provider-specific options and attributes - */ providers?: { /** * BitbucketServerEntityProvider configuration @@ -48,6 +47,10 @@ export interface Config { */ projectKey?: RegExp; }; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } | Record< string, @@ -73,6 +76,10 @@ export interface Config { */ projectKey?: RegExp; }; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } >; }; diff --git a/plugins/catalog-backend-module-bitbucket-server/package.json b/plugins/catalog-backend-module-bitbucket-server/package.json index d4e27a8ec6..30d5cef631 100644 --- a/plugins/catalog-backend-module-bitbucket-server/package.json +++ b/plugins/catalog-backend-module-bitbucket-server/package.json @@ -1,11 +1,12 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket-server", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { "access": "public", + "alphaTypes": "dist/index.alpha.d.ts", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, @@ -23,21 +24,23 @@ ], "scripts": { "start": "backstage-cli package start", - "build": "backstage-cli package build", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", - "clean": "backstage-cli package clean", "prepack": "backstage-cli package prepack", - "postpack": "backstage-cli package postpack" + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@types/node-fetch": "^2.5.12", "node-fetch": "^2.6.7", "uuid": "^8.0.0", @@ -46,11 +49,13 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", + "luxon": "^3.0.0", "msw": "^0.47.0" }, "files": [ - "dist", - "config.d.ts" + "alpha", + "config.d.ts", + "dist" ], "configSchema": "config.d.ts" } diff --git a/plugins/catalog-backend-module-bitbucket-server/src/index.ts b/plugins/catalog-backend-module-bitbucket-server/src/index.ts index 34ac3a9966..f139078cfb 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/index.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/index.ts @@ -29,3 +29,4 @@ export type { } from './lib'; export { BitbucketServerEntityProvider } from './providers'; export type { BitbucketServerLocationParser } from './providers'; +export { bitbucketServerEntityProviderCatalogModule } from './service/BitbucketServerEntityProviderCatalogModule'; diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts index 39689b6c1d..20299e3452 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.test.ts @@ -15,14 +15,18 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; -import { BitbucketServerEntityProvider } from './BitbucketServerEntityProvider'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { BitbucketServerPagedResponse } from '../lib/BitbucketServerClient'; +import { BitbucketServerEntityProvider } from './BitbucketServerEntityProvider'; +import { BitbucketServerPagedResponse } from '../lib'; class PersistingTaskRunner implements TaskRunner { private tasks: TaskInvocationDefinition[] = []; @@ -367,4 +371,163 @@ describe('BitbucketServerEntityProvider', () => { entities: expectedEntities, }); }); + + it('fail without schedule and scheduler', () => { + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketServer: { + host: 'bitbucket.mycompany.com', + }, + }, + }, + integrations: { + bitbucketServer: [ + { + host: 'bitbucket.mycompany.com', + }, + ], + }, + }); + + expect(() => + BitbucketServerEntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const scheduler = { + createScheduledTaskRunner: (_: any) => jest.fn(), + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketServer: { + host: 'bitbucket.mycompany.com', + }, + }, + }, + integrations: { + bitbucketServer: [ + { + host: 'bitbucket.mycompany.com', + }, + ], + }, + }); + + expect(() => + BitbucketServerEntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for bitbucketServer-provider:default', + ); + }); + + it('apply full update with schedule in config', async () => { + const host = 'bitbucket.mycompany.com'; + const config = new ConfigReader({ + integrations: { + bitbucketServer: [ + { + host: host, + }, + ], + }, + catalog: { + providers: { + bitbucketServer: { + mainProvider: { + host: host, + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, + }, + }, + }, + }); + const schedule = new PersistingTaskRunner(); + const scheduler = { + createScheduledTaskRunner: (_: any) => schedule, + } as unknown as PluginTaskScheduler; + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + const provider = BitbucketServerEntityProvider.fromConfig(config, { + logger, + scheduler, + })[0]; + expect(provider.getProviderName()).toEqual( + 'bitbucketServer-provider:mainProvider', + ); + + setupStubs( + [ + { key: 'project-test', repos: ['repo-test'] }, + { key: 'other-project', repos: ['other-repo'] }, + ], + `https://${host}`, + ); + await provider.connect(entityProviderConnection); + + const taskDef = schedule.getTasks()[0]; + expect(taskDef.id).toEqual('bitbucketServer-provider:mainProvider:refresh'); + await (taskDef.fn as () => Promise)(); + + const expectedEntities = [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:https://${host}/projects/project-test/repos/repo-test/browse/catalog-info.yaml`, + 'backstage.io/managed-by-origin-location': `url:https://${host}/projects/project-test/repos/repo-test/browse/catalog-info.yaml`, + }, + name: 'generated-77f4323822420990f8c3e3c981d38c2dec4ae3a6', + }, + spec: { + presence: 'optional', + target: `https://${host}/projects/project-test/repos/repo-test/browse/catalog-info.yaml`, + type: 'url', + }, + }, + locationKey: 'bitbucketServer-provider:mainProvider', + }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:https://${host}/projects/other-project/repos/other-repo/browse/catalog-info.yaml`, + 'backstage.io/managed-by-origin-location': `url:https://${host}/projects/other-project/repos/other-repo/browse/catalog-info.yaml`, + }, + name: 'generated-d8d4944c30c2906dfee172ddda9537f9893b2c0f', + }, + spec: { + presence: 'optional', + target: `https://${host}/projects/other-project/repos/other-repo/browse/catalog-info.yaml`, + type: 'url', + }, + }, + locationKey: 'bitbucketServer-provider:mainProvider', + }, + ]; + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: expectedEntities, + }); + }); }); diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts index 92cecf642a..447b71ee58 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProvider.ts @@ -14,29 +14,29 @@ * limitations under the License. */ +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; +import { Entity } from '@backstage/catalog-model'; +import { Config } from '@backstage/config'; +import { InputError } from '@backstage/errors'; +import { + BitbucketServerIntegration, + ScmIntegrations, +} from '@backstage/integration'; import { EntityProvider, EntityProviderConnection, } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; -import { Config } from '@backstage/config'; -import { TaskRunner } from '@backstage/backend-tasks'; import * as uuid from 'uuid'; -import { - BitbucketServerLocationParser, - defaultBitbucketServerLocationParser, -} from './BitbucketServerLocationParser'; -import { - BitbucketServerIntegration, - ScmIntegrations, -} from '@backstage/integration'; import { BitbucketServerClient, paginated } from '../lib'; -import { InputError } from '@backstage/errors'; import { BitbucketServerEntityProviderConfig, readProviderConfigs, } from './BitbucketServerEntityProviderConfig'; -import { Entity } from '@backstage/catalog-model'; +import { + BitbucketServerLocationParser, + defaultBitbucketServerLocationParser, +} from './BitbucketServerLocationParser'; /** * Discovers catalog files located in Bitbucket Server. @@ -58,12 +58,17 @@ export class BitbucketServerEntityProvider implements EntityProvider { config: Config, options: { logger: Logger; - schedule: TaskRunner; parser?: BitbucketServerLocationParser; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): BitbucketServerEntityProvider[] { const integrations = ScmIntegrations.fromConfig(config); + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + return readProviderConfigs(config).map(providerConfig => { const integration = integrations.bitbucketServer.byHost( providerConfig.host, @@ -73,11 +78,22 @@ export class BitbucketServerEntityProvider implements EntityProvider { `No BitbucketServer integration found that matches host ${providerConfig.host}`, ); } + + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for bitbucketServer-provider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + return new BitbucketServerEntityProvider( providerConfig, integration, options.logger, - options.schedule, + taskRunner, options.parser, ); }); @@ -87,7 +103,7 @@ export class BitbucketServerEntityProvider implements EntityProvider { config: BitbucketServerEntityProviderConfig, integration: BitbucketServerIntegration, logger: Logger, - schedule: TaskRunner, + taskRunner: TaskRunner, parser?: BitbucketServerLocationParser, ) { this.integration = integration; @@ -96,13 +112,13 @@ export class BitbucketServerEntityProvider implements EntityProvider { this.logger = logger.child({ target: this.getProviderName(), }); - this.scheduleFn = this.createScheduleFn(schedule); + this.scheduleFn = this.createScheduleFn(taskRunner); } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn(taskRunner: TaskRunner): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; - return schedule.run({ + return taskRunner.run({ id: taskId, fn: async () => { const logger = this.logger.child({ diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts index 712b26cbb0..af8639566f 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; import { readProviderConfigs } from './BitbucketServerEntityProviderConfig'; describe('readProviderConfigs', () => { @@ -63,13 +64,22 @@ describe('readProviderConfigs', () => { host: 'bitbucket2.mycompany.com', catalogPath: 'custom/path/catalog-info.yaml', }, + thirdProvider: { + host: 'bitbucket3.mycompany.com', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, }, }, }, }); const providerConfigs = readProviderConfigs(config); - expect(providerConfigs).toHaveLength(2); + expect(providerConfigs).toHaveLength(3); expect(providerConfigs[0]).toEqual({ id: 'mainProvider', catalogPath: '/catalog-info.yaml', @@ -88,6 +98,21 @@ describe('readProviderConfigs', () => { repoSlug: undefined, }, }); + expect(providerConfigs[2]).toEqual({ + id: 'thirdProvider', + catalogPath: '/catalog-info.yaml', + host: 'bitbucket3.mycompany.com', + filters: { + projectKey: undefined, + repoSlug: undefined, + }, + schedule: { + frequency: Duration.fromISO('PT30M'), + timeout: { + minutes: 3, + }, + }, + }); }); it('single provider config with filters', () => { diff --git a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts index 8a3decab43..b2ed7a8556 100644 --- a/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-bitbucket-server/src/providers/BitbucketServerEntityProviderConfig.ts @@ -14,6 +14,10 @@ * limitations under the License. */ +import { + readTaskScheduleDefinitionFromConfig, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; const DEFAULT_CATALOG_PATH = '/catalog-info.yaml'; @@ -27,6 +31,7 @@ export type BitbucketServerEntityProviderConfig = { projectKey?: RegExp; repoSlug?: RegExp; }; + schedule?: TaskScheduleDefinition; }; export function readProviderConfigs( @@ -60,6 +65,10 @@ function readProviderConfig( const projectKeyPattern = config.getOptionalString('filters.projectKey'); const repoSlugPattern = config.getOptionalString('filters.repoSlug'); + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { id, host, @@ -68,5 +77,6 @@ function readProviderConfig( projectKey: projectKeyPattern ? new RegExp(projectKeyPattern) : undefined, repoSlug: repoSlugPattern ? new RegExp(repoSlugPattern) : undefined, }, + schedule, }; } diff --git a/plugins/catalog-backend-module-bitbucket-server/src/service/BitbucketServerEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-bitbucket-server/src/service/BitbucketServerEntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..d2ce4dbd3d --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-server/src/service/BitbucketServerEntityProviderCatalogModule.test.ts @@ -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 { ConfigReader } from '@backstage/config'; +import { getVoidLogger } from '@backstage/backend-common'; +import { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { bitbucketServerEntityProviderCatalogModule } from './BitbucketServerEntityProviderCatalogModule'; +import { Duration } from 'luxon'; +import { BitbucketServerEntityProvider } from '../providers'; + +describe('bitbucketServerEntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketServer: { + host: 'bitbucket.mycompany.com', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + integrations: { + bitbucketServer: [ + { + host: 'bitbucket.mycompany.com', + }, + ], + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [bitbucketServerEntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'bitbucketServer-provider:default', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-bitbucket-server/src/service/BitbucketServerEntityProviderCatalogModule.ts b/plugins/catalog-backend-module-bitbucket-server/src/service/BitbucketServerEntityProviderCatalogModule.ts new file mode 100644 index 0000000000..e89eee7b45 --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-server/src/service/BitbucketServerEntityProviderCatalogModule.ts @@ -0,0 +1,52 @@ +/* + * 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 { + configServiceRef, + createBackendModule, + loggerServiceRef, + loggerToWinstonLogger, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { BitbucketServerEntityProvider } from '../providers'; + +/** + * @alpha + */ +export const bitbucketServerEntityProviderCatalogModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'bitbucketServerEntityProvider', + register(env) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + config: configServiceRef, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ catalog, config, logger, scheduler }) { + const winstonLogger = loggerToWinstonLogger(logger); + const providers = BitbucketServerEntityProvider.fromConfig(config, { + logger: winstonLogger, + scheduler, + }); + + catalog.addEntityProvider(providers); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md index 9af08254eb..ebc6e7b9df 100644 --- a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,71 @@ # @backstage/plugin-catalog-backend-module-bitbucket +## 0.2.4 + +### Patch Changes + +- 23f9199a0f: Deprecate `@backstage/plugin-catalog-backend-module-bitbucket`. + + Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` + or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.2.4-next.2 + +### Patch Changes + +- 23f9199a0f: Deprecate `@backstage/plugin-catalog-backend-module-bitbucket`. + + Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` + or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.2 + +## 0.2.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## 0.2.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-bitbucket-cloud-common@0.2.0-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + ## 0.2.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket/api-report.md b/plugins/catalog-backend-module-bitbucket/api-report.md index 34acd6e76d..053c6040a4 100644 --- a/plugins/catalog-backend-module-bitbucket/api-report.md +++ b/plugins/catalog-backend-module-bitbucket/api-report.md @@ -12,7 +12,7 @@ import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; import { ScmIntegrationRegistry } from '@backstage/integration'; -// @public (undocumented) +// @public @deprecated (undocumented) export class BitbucketDiscoveryProcessor implements CatalogProcessor { constructor(options: { integrations: ScmIntegrationRegistry; @@ -37,7 +37,7 @@ export class BitbucketDiscoveryProcessor implements CatalogProcessor { ): Promise; } -// @public +// @public @deprecated export type BitbucketRepositoryParser = (options: { integration: BitbucketIntegration; target: string; diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json index 171493dfc2..2d4eae3b8f 100644 --- a/plugins/catalog-backend-module-bitbucket/package.json +++ b/plugins/catalog-backend-module-bitbucket/package.json @@ -1,7 +1,8 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket", - "version": "0.2.3", + "version": "0.2.4", + "deprecated": true, "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts b/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts index 14bf90ee62..6924765483 100644 --- a/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts +++ b/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts @@ -41,7 +41,10 @@ import { const DEFAULT_BRANCH = 'master'; const DEFAULT_CATALOG_LOCATION = '/catalog-info.yaml'; -/** @public */ +/** + * @public + * @deprecated Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. + */ export class BitbucketDiscoveryProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrationRegistry; private readonly parser: BitbucketRepositoryParser; @@ -70,6 +73,9 @@ export class BitbucketDiscoveryProcessor implements CatalogProcessor { this.integrations = options.integrations; this.parser = options.parser || defaultRepositoryParser; this.logger = options.logger; + this.logger.warn( + 'Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead.', + ); } getProcessorName(): string { diff --git a/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.ts b/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.ts index 57a162e1e4..97225e2579 100644 --- a/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.ts +++ b/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketRepositoryParser.ts @@ -26,6 +26,7 @@ import { Logger } from 'winston'; * results. * * @public + * @deprecated Please migrate to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` or `@backstage/plugin-catalog-backend-module-bitbucket-server` instead. */ export type BitbucketRepositoryParser = (options: { integration: BitbucketIntegration; diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 2dc4229172..e5ba22ced6 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## 0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + ## 0.1.4 ### Patch Changes diff --git a/plugins/catalog-backend-module-gerrit/api-report.md b/plugins/catalog-backend-module-gerrit/api-report.md index 7eae94c2a8..45847ea93d 100644 --- a/plugins/catalog-backend-module-gerrit/api-report.md +++ b/plugins/catalog-backend-module-gerrit/api-report.md @@ -3,10 +3,12 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { Config } from '@backstage/config'; import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { TaskRunner } from '@backstage/backend-tasks'; // @public (undocumented) @@ -18,7 +20,8 @@ export class GerritEntityProvider implements EntityProvider { configRoot: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): GerritEntityProvider[]; // (undocumented) @@ -27,5 +30,10 @@ export class GerritEntityProvider implements EntityProvider { refresh(logger: Logger): Promise; } +// @alpha (undocumented) +export const gerritEntityProviderCatalogModule: ( + options?: undefined, +) => BackendFeature; + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index 6a115ea481..9b57cf949b 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,11 +1,12 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.1.4", + "version": "0.1.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { "access": "public", + "alphaTypes": "dist/index.alpha.d.ts", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, @@ -20,21 +21,23 @@ }, "scripts": { "start": "backstage-cli package start", - "build": "backstage-cli package build", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", - "clean": "backstage-cli package clean", "prepack": "backstage-cli package prepack", - "postpack": "backstage-cli package postpack" + "postpack": "backstage-cli package postpack", + "clean": "backstage-cli package clean" }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "fs-extra": "10.1.0", "msw": "^0.47.0", "node-fetch": "^2.6.7", @@ -44,7 +47,8 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/fs-extra": "^9.0.1" + "@types/fs-extra": "^9.0.1", + "luxon": "^3.0.0" }, "files": [ "dist", diff --git a/plugins/catalog-backend-module-gerrit/src/index.ts b/plugins/catalog-backend-module-gerrit/src/index.ts index 133772d4b1..38ede69926 100644 --- a/plugins/catalog-backend-module-gerrit/src/index.ts +++ b/plugins/catalog-backend-module-gerrit/src/index.ts @@ -15,3 +15,4 @@ */ export { GerritEntityProvider } from './providers/GerritEntityProvider'; +export { gerritEntityProviderCatalogModule } from './service/GerritEntityProviderCatalogModule'; diff --git a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts index d6ad0ff668..5d258ee94f 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.test.ts @@ -15,14 +15,18 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { ConfigReader } from '@backstage/config'; -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; -import { rest } from 'msw'; import fs from 'fs-extra'; -import path from 'path'; +import { rest } from 'msw'; import { setupServer } from 'msw/node'; +import path from 'path'; import { GerritEntityProvider } from './GerritEntityProvider'; const server = setupServer(); @@ -207,4 +211,93 @@ describe('GerritEntityProvider', () => { }), ).toThrow(/No gerrit integration/); }); + + it('fail without schedule and scheduler', () => { + expect(() => + GerritEntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const scheduler = { + createScheduledTaskRunner: (_: any) => jest.fn(), + } as unknown as PluginTaskScheduler; + expect(() => + GerritEntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for gerrit-provider:active-training', + ); + }); + + it('discovers projects from the api with schedule in config', async () => { + const configWithSchedule = new ConfigReader({ + catalog: { + providers: { + gerrit: { + 'active-training': { + host: 'g.com', + query: 'state=ACTIVE&prefix=training', + branch: 'main', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, + }, + }, + }, + integrations: { + gerrit: [ + { + host: 'g.com', + baseUrl: 'https://g.com/gerrit', + gitilesBaseUrl: 'https:/g.com/gitiles', + }, + ], + }, + }); + const scheduler = { + createScheduledTaskRunner: (_: any) => schedule, + } as unknown as PluginTaskScheduler; + + const repoBuffer = fs.readFileSync( + path.resolve(__dirname, '__fixtures__/listProjectsBody.txt'), + ); + const expected = getJsonFixture('expectedProviderEntities.json'); + + server.use( + rest.get('https://g.com/gerrit/projects/', (_, res, ctx) => + res( + ctx.status(200), + ctx.set('Content-Type', 'application/json'), + ctx.body(repoBuffer), + ), + ), + ); + + const provider = GerritEntityProvider.fromConfig(configWithSchedule, { + logger, + scheduler, + })[0]; + expect(provider.getProviderName()).toEqual( + 'gerrit-provider:active-training', + ); + + await provider.connect(entityProviderConnection); + + const taskDef = schedule.getTasks()[0]; + expect(taskDef.id).toEqual('gerrit-provider:active-training:refresh'); + await (taskDef.fn as () => Promise)(); + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith( + expected, + ); + }); }); diff --git a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts index beb4052073..ec08dbe4be 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/GerritEntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { InputError } from '@backstage/errors'; import { @@ -49,9 +49,14 @@ export class GerritEntityProvider implements EntityProvider { configRoot: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): GerritEntityProvider[] { + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + const providerConfigs = readGerritConfigs(configRoot); const integrations = ScmIntegrations.fromConfig(configRoot).gerrit; const providers: GerritEntityProvider[] = []; @@ -63,12 +68,23 @@ export class GerritEntityProvider implements EntityProvider { `No gerrit integration found that matches host ${providerConfig.host}`, ); } + + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for gerrit-provider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + providers.push( new GerritEntityProvider( providerConfig, integration, options.logger, - options.schedule, + taskRunner, ), ); }); @@ -79,14 +95,14 @@ export class GerritEntityProvider implements EntityProvider { config: GerritProviderConfig, integration: GerritIntegration, logger: Logger, - schedule: TaskRunner, + taskRunner: TaskRunner, ) { this.config = config; this.integration = integration; this.logger = logger.child({ target: this.getProviderName(), }); - this.scheduleFn = this.createScheduleFn(schedule); + this.scheduleFn = this.createScheduleFn(taskRunner); } getProviderName(): string { @@ -98,10 +114,10 @@ export class GerritEntityProvider implements EntityProvider { await this.scheduleFn(); } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn(taskRunner: TaskRunner): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; - return schedule.run({ + return taskRunner.run({ id: taskId, fn: async () => { const logger = this.logger.child({ diff --git a/plugins/catalog-backend-module-gerrit/src/providers/config.test.ts b/plugins/catalog-backend-module-gerrit/src/providers/config.test.ts index 0422b4e4ea..ecf68d9f3c 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/config.test.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/config.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; import { readGerritConfigs } from './config'; describe('readGerritConfigs', () => { @@ -29,12 +30,24 @@ describe('readGerritConfigs', () => { query: 'state=ACTIVE', branch: 'main', }; + const provider3 = { + host: 'gerrit1.com', + query: 'state=ACTIVE', + branch: 'main', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }; const config = { catalog: { providers: { gerrit: { 'active-g1': provider1, 'active-g2': provider2, + 'active-g3': provider3, }, }, }, @@ -42,10 +55,19 @@ describe('readGerritConfigs', () => { const actual = readGerritConfigs(new ConfigReader(config)); - expect(actual).toHaveLength(2); + expect(actual).toHaveLength(3); expect(actual[0]).toEqual({ ...provider1, id: 'active-g1' }); expect(actual[1]).toEqual({ ...provider2, id: 'active-g2' }); + expect(actual[2]).toEqual({ + ...provider3, + id: 'active-g3', + schedule: { + ...provider3.schedule, + frequency: Duration.fromISO(provider3.schedule.frequency), + }, + }); }); + it('provides default values', () => { const provider = { host: 'gerrit1.com', diff --git a/plugins/catalog-backend-module-gerrit/src/providers/config.ts b/plugins/catalog-backend-module-gerrit/src/providers/config.ts index 7a28ddd8ea..dd7ee9ec43 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/config.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/config.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GerritProviderConfig } from './types'; @@ -22,11 +23,16 @@ function readGerritConfig(id: string, config: Config): GerritProviderConfig { const host = config.getString('host'); const query = config.getString('query'); + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { branch, host, id, query, + schedule, }; } diff --git a/plugins/catalog-backend-module-gerrit/src/providers/types.ts b/plugins/catalog-backend-module-gerrit/src/providers/types.ts index 1e96bd1ee4..4a8329df86 100644 --- a/plugins/catalog-backend-module-gerrit/src/providers/types.ts +++ b/plugins/catalog-backend-module-gerrit/src/providers/types.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; + export type GerritProjectInfo = { id: string; name: string; @@ -28,4 +30,5 @@ export type GerritProviderConfig = { query: string; id: string; branch?: string; + schedule?: TaskScheduleDefinition; }; diff --git a/plugins/catalog-backend-module-gerrit/src/service/GerritEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-gerrit/src/service/GerritEntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..53e012209b --- /dev/null +++ b/plugins/catalog-backend-module-gerrit/src/service/GerritEntityProviderCatalogModule.test.ts @@ -0,0 +1,97 @@ +/* + * 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 { getVoidLogger } from '@backstage/backend-common'; +import { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { Duration } from 'luxon'; +import { gerritEntityProviderCatalogModule } from './GerritEntityProviderCatalogModule'; +import { GerritEntityProvider } from '../providers/GerritEntityProvider'; + +describe('gerritEntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + catalog: { + providers: { + gerrit: { + test: { + host: 'g.com', + query: 'state=ACTIVE&prefix=training', + branch: 'main', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }, + integrations: { + gerrit: [ + { + host: 'g.com', + baseUrl: 'https://g.com/gerrit', + gitilesBaseUrl: 'https:/g.com/gitiles', + }, + ], + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [gerritEntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'gerrit-provider:test', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-gerrit/src/service/GerritEntityProviderCatalogModule.ts b/plugins/catalog-backend-module-gerrit/src/service/GerritEntityProviderCatalogModule.ts new file mode 100644 index 0000000000..32d582e397 --- /dev/null +++ b/plugins/catalog-backend-module-gerrit/src/service/GerritEntityProviderCatalogModule.ts @@ -0,0 +1,52 @@ +/* + * 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 { + configServiceRef, + createBackendModule, + loggerServiceRef, + loggerToWinstonLogger, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { GerritEntityProvider } from '../providers/GerritEntityProvider'; + +/** + * @alpha + */ +export const gerritEntityProviderCatalogModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'gerritEntityProvider', + register(env) { + env.registerInit({ + deps: { + catalog: catalogProcessingExtensionPoint, + config: configServiceRef, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ catalog, config, logger, scheduler }) { + const winstonLogger = loggerToWinstonLogger(logger); + const providers = GerritEntityProvider.fromConfig(config, { + logger: winstonLogger, + scheduler, + }); + + catalog.addEntityProvider(providers); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index fa49109016..9c89e00de4 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,103 @@ # @backstage/plugin-catalog-backend-module-github +## 0.1.8 + +### Patch Changes + +- 8749df3d02: `GitHubEntityProvider`: Add option to configure schedule via `app-config.yaml` instead of in code. + + Please find how to configure the schedule at the config at + https://backstage.io/docs/integrations/github/discovery + +- 7022aebf35: Added `GithubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. +- 51046b58b0: Use schedule from config at backend module. + + Also, it removes `GithubEntityProviderCatalogModuleOptions` + in favor of config-only for the backend module setup + like at other similar modules. + +- 7edb5909e8: Add missing config schema for the `GitHubEntityProvider`. +- be9474b103: Replaces in-code uses of `GitHub` by `Github` and deprecates old versions. + + Deprecates + + - `GitHubEntityProvider` replaced by `GithubEntityProvider` + - `GitHubLocationAnalyzer` replaced by `GithubLocationAnalyzer` + - `GitHubLocationAnalyzerOptions` replaced by `GithubLocationAnalyzerOptions` + - `GitHubOrgEntityProvider` replaced by `GithubOrgEntityProvider` + - `GitHubOrgEntityProviderOptions` replaced by `GithubOrgEntityProviderOptions` + + Renames + + - `GitHubLocationAnalyzer` to `GithubLocationAnalyzer` + - `GitHubLocationAnalyzerOptions` to `GithubLocationAnalyzerOptions` + +- a35a27df70: Updated the `moduleId` of the experimental module export. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.1.8-next.2 + +### Patch Changes + +- 7022aebf35: Added `GitHubLocationAnalyzer`. This can be used to add to the `CatalogBuilder`. When added this will be used by `RepoLocationAnalyzer` to figure out if the given URL that you are trying to import from the /catalog-import page already contains catalog-info.yaml files. +- 7edb5909e8: Add missing config schema for the `GitHubEntityProvider`. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## 0.1.8-next.1 + +### Patch Changes + +- a35a27df70: Updated the `moduleId` of the experimental module export. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + - @backstage/plugin-catalog-node@1.1.1-next.1 + +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/plugin-catalog-node@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + ## 0.1.7 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/api-report.md b/plugins/catalog-backend-module-github/api-report.md index 8e8055bafc..5b94f7169e 100644 --- a/plugins/catalog-backend-module-github/api-report.md +++ b/plugins/catalog-backend-module-github/api-report.md @@ -3,19 +3,23 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { AnalyzeOptions } from '@backstage/plugin-catalog-backend'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; import { Config } from '@backstage/config'; +import { Entity } from '@backstage/catalog-model'; import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { GithubCredentialsProvider } from '@backstage/integration'; import { GitHubIntegrationConfig } from '@backstage/integration'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { ScmLocationAnalyzer } from '@backstage/plugin-catalog-backend'; import { TaskRunner } from '@backstage/backend-tasks'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; // @public export class GithubDiscoveryProcessor implements CatalogProcessor { @@ -42,7 +46,7 @@ export class GithubDiscoveryProcessor implements CatalogProcessor { ): Promise; } -// @public +// @public @deprecated (undocumented) export class GitHubEntityProvider implements EntityProvider { // (undocumented) connect(connection: EntityProviderConnection): Promise; @@ -51,7 +55,8 @@ export class GitHubEntityProvider implements EntityProvider { config: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): GitHubEntityProvider[]; // (undocumented) @@ -60,14 +65,52 @@ export class GitHubEntityProvider implements EntityProvider { refresh(logger: Logger): Promise; } -// @alpha -export const githubEntityProviderCatalogModule: ( - options?: GithubEntityProviderCatalogModuleOptions | undefined, -) => BackendFeature; +// @public +export class GithubEntityProvider implements EntityProvider { + // (undocumented) + connect(connection: EntityProviderConnection): Promise; + // (undocumented) + static fromConfig( + config: Config, + options: { + logger: Logger; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; + }, + ): GithubEntityProvider[]; + // (undocumented) + getProviderName(): string; + // (undocumented) + refresh(logger: Logger): Promise; +} // @alpha -export type GithubEntityProviderCatalogModuleOptions = { - schedule?: TaskScheduleDefinition; +export const githubEntityProviderCatalogModule: ( + options?: undefined, +) => BackendFeature; + +// @public (undocumented) +export class GithubLocationAnalyzer implements ScmLocationAnalyzer { + constructor(options: GithubLocationAnalyzerOptions); + // (undocumented) + analyze({ url, catalogFilename }: AnalyzeOptions): Promise<{ + existing: { + location: { + type: string; + target: string; + }; + isRegistered: boolean; + entity: Entity; + }[]; + }>; + // (undocumented) + supports(url: string): boolean; +} + +// @public (undocumented) +export type GithubLocationAnalyzerOptions = { + config: Config; + discovery: PluginEndpointDiscovery; }; // @public @@ -103,8 +146,17 @@ export class GithubMultiOrgReaderProcessor implements CatalogProcessor { ): Promise; } +// @public @deprecated (undocumented) +export class GitHubOrgEntityProvider extends GithubOrgEntityProvider { + // (undocumented) + static fromConfig( + config: Config, + options: GitHubOrgEntityProviderOptions, + ): GitHubOrgEntityProvider; +} + // @public -export class GitHubOrgEntityProvider implements EntityProvider { +export class GithubOrgEntityProvider implements EntityProvider { constructor(options: { id: string; orgUrl: string; @@ -117,15 +169,18 @@ export class GitHubOrgEntityProvider implements EntityProvider { // (undocumented) static fromConfig( config: Config, - options: GitHubOrgEntityProviderOptions, - ): GitHubOrgEntityProvider; + options: GithubOrgEntityProviderOptions, + ): GithubOrgEntityProvider; // (undocumented) getProviderName(): string; read(options?: { logger?: Logger }): Promise; } +// @public @deprecated (undocumented) +export type GitHubOrgEntityProviderOptions = GithubOrgEntityProviderOptions; + // @public -export interface GitHubOrgEntityProviderOptions { +export interface GithubOrgEntityProviderOptions { githubCredentialsProvider?: GithubCredentialsProvider; id: string; logger: Logger; diff --git a/plugins/catalog-backend-module-github/config.d.ts b/plugins/catalog-backend-module-github/config.d.ts index a1702799c2..691caedc46 100644 --- a/plugins/catalog-backend-module-github/config.d.ts +++ b/plugins/catalog-backend-module-github/config.d.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + export interface Config { catalog?: { processors?: { @@ -45,5 +47,127 @@ export interface Config { }>; }; }; + + providers?: { + /** + * GithubEntityProvider configuration + * + * Uses "default" as default id for the single config variant. + */ + github?: + | { + /** + * (Optional) The hostname of your GitHub Enterprise instance. + * Default: `github.com`. + */ + host?: string; + /** + * (Required) Name of your organization account/workspace. + */ + organization: string; + /** + * (Optional) Path where to look for `catalog-info.yaml` files. + * You can use wildcards - `*` or `**` - to search the path and/or the filename + * Default: `/catalog-info.yaml`. + */ + catalogPath?: string; + /** + * (Optional) Filter configuration. + */ + filters?: { + /** + * (Optional) String used to filter results based on the branch name. + */ + branch?: string; + /** + * (Optional) Regular expression used to filter results based on the repository name. + */ + repository?: string; + /** + * (Optional) GitHub topic-based filters. + */ + topic?: { + /** + * (Optional) An array of strings used to filter in results based on their associated GitHub topics. + * If configured, only repositories with one (or more) topic(s) present in the inclusion + * filter will be ingested. + * + * If `include` and `exclude` are used, `exclude` has higher priority. + */ + include?: string[]; + /** + * (Optional) An array of strings used to filter out results based on their associated GitHub topics. + * If configured, all repositories _except_ those with one (or more) topics(s) present in + * the exclusion filter will be ingested. + * + * If `include` and `exclude` are used, `exclude` has higher priority. + */ + exclude?: string[]; + }; + }; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; + } + | Record< + string, + { + /** + * (Optional) The hostname of your GitHub Enterprise instance. + * Default: `github.com`. + */ + host?: string; + /** + * (Required) Name of your organization account/workspace. + */ + organization: string; + /** + * (Optional) Path where to look for `catalog-info.yaml` files. + * You can use wildcards - `*` or `**` - to search the path and/or the filename + * Default: `/catalog-info.yaml`. + */ + catalogPath?: string; + /** + * (Optional) Filter configuration. + */ + filters?: { + /** + * (Optional) String used to filter results based on the branch name. + */ + branch?: string; + /** + * (Optional) Regular expression used to filter results based on the repository name. + */ + repository?: string; + /** + * (Optional) GitHub topic-based filters. + */ + topic?: { + /** + * (Optional) An array of strings used to filter in results based on their associated GitHub topics. + * If configured, only repositories with one (or more) topic(s) present in the inclusion + * filter will be ingested. + * + * If `include` and `exclude` are used, `exclude` has higher priority. + */ + include?: string[]; + /** + * (Optional) An array of strings used to filter out results based on their associated GitHub topics. + * If configured, all repositories _except_ those with one (or more) topics(s) present in + * the exclusion filter will be ingested. + * + * If `include` and `exclude` are used, `exclude` has higher priority. + */ + exclude?: string[]; + }; + }; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; + } + >; + }; }; } diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index 278f108a7b..e1a3ed39b8 100644 --- a/plugins/catalog-backend-module-github/package.json +++ b/plugins/catalog-backend-module-github/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-github", "description": "A Backstage catalog backend module that helps integrate towards GitHub", - "version": "0.1.7", + "version": "0.1.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,6 +36,7 @@ "@backstage/backend-common": "workspace:^", "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", + "@backstage/catalog-client": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", @@ -44,6 +45,8 @@ "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", "@octokit/graphql": "^5.0.0", + "@octokit/rest": "^19.0.3", + "git-url-parse": "^13.0.0", "lodash": "^4.17.21", "msw": "^0.47.0", "node-fetch": "^2.6.7", @@ -53,7 +56,8 @@ "devDependencies": { "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", - "@types/lodash": "^4.14.151" + "@types/lodash": "^4.14.151", + "luxon": "^3.0.0" }, "files": [ "dist", diff --git a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts new file mode 100644 index 0000000000..936f8a3576 --- /dev/null +++ b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.test.ts @@ -0,0 +1,156 @@ +/* + * 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. + */ + +const octokit = { + search: { + code: jest.fn(), + }, + repos: { + get: jest.fn(), + }, +}; + +jest.mock('@octokit/rest', () => { + class Octokit { + constructor() { + return octokit; + } + } + return { Octokit }; +}); + +import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { GithubLocationAnalyzer } from './GithubLocationAnalyzer'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { setupServer } from 'msw/node'; +import { rest } from 'msw'; +import { ConfigReader } from '@backstage/config'; + +const server = setupServer(); + +describe('GithubLocationAnalyzer', () => { + const mockDiscoveryApi: jest.Mocked = { + getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7007'), + getExternalBaseUrl: jest.fn(), + }; + const config = new ConfigReader({ + integrations: { + github: [ + { + host: 'h.com', + token: 't', + }, + ], + }, + }); + + setupRequestMockHandlers(server); + + beforeEach(() => { + server.use( + rest.post('http://localhost:7007/locations', async (_, res, ctx) => { + return res( + ctx.status(201), + ctx.json({ + location: 'test', + exists: false, + entities: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + name: 'test-entity', + }, + spec: { + type: 'url', + target: 'whatever', + }, + }, + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + title: 'Test Entity', + name: 'test-entity-2', + description: 'The expected description 2', + }, + spec: { + type: 'some-type', + lifecycle: 'experimental', + owner: 'someone', + }, + }, + ], + }), + ); + }), + ); + + octokit.repos.get.mockResolvedValue({ + data: { default_branch: 'my_default_branch' }, + }); + }); + + it('should analyze', async () => { + octokit.search.code.mockImplementation((opts: { q: string }) => { + if (opts.q === 'filename:catalog-info.yaml repo:foo/bar') { + return Promise.resolve({ + data: { items: [{ path: 'catalog-info.yaml' }], total_count: 1 }, + }); + } + return Promise.reject(); + }); + + const analyzer = new GithubLocationAnalyzer({ + discovery: mockDiscoveryApi, + config, + }); + const result = await analyzer.analyze({ + url: 'https://github.com/foo/bar', + }); + + expect(result.existing[0].isRegistered).toBeFalsy(); + expect(result.existing[0].location).toEqual({ + type: 'url', + target: + 'https://github.com/foo/bar/blob/my_default_branch/catalog-info.yaml', + }); + }); + it('should use the provided entity filename for search', async () => { + octokit.search.code.mockImplementation((opts: { q: string }) => { + if (opts.q === 'filename:anvil.yaml repo:foo/bar') { + return Promise.resolve({ + data: { items: [{ path: 'anvil.yaml' }], total_count: 1 }, + }); + } + return Promise.reject(); + }); + + const analyzer = new GithubLocationAnalyzer({ + discovery: mockDiscoveryApi, + config, + }); + const result = await analyzer.analyze({ + url: 'https://github.com/foo/bar', + catalogFilename: 'anvil.yaml', + }); + + expect(result.existing[0].location).toEqual({ + type: 'url', + target: 'https://github.com/foo/bar/blob/my_default_branch/anvil.yaml', + }); + }); +}); diff --git a/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts new file mode 100644 index 0000000000..32ef8bf946 --- /dev/null +++ b/plugins/catalog-backend-module-github/src/analyzers/GithubLocationAnalyzer.ts @@ -0,0 +1,106 @@ +/* + * 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 { CatalogApi, CatalogClient } from '@backstage/catalog-client'; +import { ScmIntegrations } from '@backstage/integration'; +import { Octokit } from '@octokit/rest'; +import { trimEnd } from 'lodash'; +import parseGitUrl from 'git-url-parse'; +import { + AnalyzeOptions, + ScmLocationAnalyzer, +} from '@backstage/plugin-catalog-backend'; +import { PluginEndpointDiscovery } from '@backstage/backend-common'; +import { Config } from '@backstage/config'; + +/** @public */ +export type GithubLocationAnalyzerOptions = { + config: Config; + discovery: PluginEndpointDiscovery; +}; + +/** @public */ +export class GithubLocationAnalyzer implements ScmLocationAnalyzer { + private readonly catalogClient: CatalogApi; + private readonly config: Config; + + constructor(options: GithubLocationAnalyzerOptions) { + this.config = options.config; + this.catalogClient = new CatalogClient({ discoveryApi: options.discovery }); + } + + supports(url: string) { + const integrations = ScmIntegrations.fromConfig(this.config); + const integration = integrations.byUrl(url); + return integration?.type === 'github'; + } + + async analyze({ url, catalogFilename }: AnalyzeOptions) { + const { owner, name: repo } = parseGitUrl(url); + + const catalogFile = catalogFilename || 'catalog-info.yaml'; + + const query = `filename:${catalogFile} repo:${owner}/${repo}`; + + const integration = ScmIntegrations.fromConfig(this.config).github.byUrl( + url, + ); + if (!integration) { + throw new Error('Make sure you have a GitHub integration configured'); + } + + const octokitClient = new Octokit({ + auth: integration.config.token, + baseUrl: integration.config.apiBaseUrl, + }); + + const searchResult = await octokitClient.search + .code({ q: query }) + .catch(e => { + throw new Error(`Couldn't search repository for metadata file, ${e}`); + }); + + const exists = searchResult.data.total_count > 0; + if (exists) { + const repoInformation = await octokitClient.repos + .get({ owner, repo }) + .catch(e => { + throw new Error(`Couldn't fetch repo data, ${e}`); + }); + const defaultBranch = repoInformation.data.default_branch; + + const result = await Promise.all( + searchResult.data.items + .map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`) + .map(async target => { + const addLocationResult = await this.catalogClient.addLocation({ + type: 'url', + target, + dryRun: true, + }); + return addLocationResult.entities.map(e => ({ + location: { type: 'url', target }, + isRegistered: !!addLocationResult.exists, + entity: e, + })); + }), + ); + + return { existing: result.flat() }; + } + return { existing: [] }; + } +} diff --git a/plugins/catalog-backend-module-github/src/deprecated.ts b/plugins/catalog-backend-module-github/src/deprecated.ts new file mode 100644 index 0000000000..d094bb4031 --- /dev/null +++ b/plugins/catalog-backend-module-github/src/deprecated.ts @@ -0,0 +1,86 @@ +/* + * Copyright 2020 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 { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; +import { Config } from '@backstage/config'; +import { + EntityProvider, + EntityProviderConnection, +} from '@backstage/plugin-catalog-backend'; +import { Logger } from 'winston'; +import { GithubEntityProvider } from './providers/GithubEntityProvider'; +import { + GithubOrgEntityProvider, + GithubOrgEntityProviderOptions, +} from './providers/GithubOrgEntityProvider'; + +/** + * @public + * @deprecated Use {@link GithubOrgEntityProvider} instead. + */ +export class GitHubOrgEntityProvider extends GithubOrgEntityProvider { + static fromConfig(config: Config, options: GitHubOrgEntityProviderOptions) { + options.logger.warn( + '[Deprecated] Use GithubOrgEntityProvider instead of GitHubOrgEntityProvider.', + ); + return GithubOrgEntityProvider.fromConfig( + config, + options as GithubOrgEntityProviderOptions, + ) as GitHubOrgEntityProvider; + } +} + +/** + * @public + * @deprecated Use {@link GithubOrgEntityProviderOptions} instead. + */ +export type GitHubOrgEntityProviderOptions = GithubOrgEntityProviderOptions; + +/** + * @public + * @deprecated Use {@link GithubEntityProvider} instead. + */ +export class GitHubEntityProvider implements EntityProvider { + static fromConfig( + config: Config, + options: { + logger: Logger; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; + }, + ): GitHubEntityProvider[] { + options.logger.warn( + '[Deprecated] Please use GithubEntityProvider instead of GitHubEntityProvider.', + ); + return GithubEntityProvider.fromConfig(config, options).map( + delegate => new GitHubEntityProvider(delegate), + ); + } + + private constructor(private readonly delegate: GithubEntityProvider) {} + + connect(connection: EntityProviderConnection): Promise { + return this.delegate.connect(connection); + } + + getProviderName(): string { + return this.delegate.getProviderName(); + } + + refresh(logger: Logger): Promise { + return this.delegate.refresh(logger); + } +} diff --git a/plugins/catalog-backend-module-github/src/index.ts b/plugins/catalog-backend-module-github/src/index.ts index d793d48f1e..f38035db15 100644 --- a/plugins/catalog-backend-module-github/src/index.ts +++ b/plugins/catalog-backend-module-github/src/index.ts @@ -20,12 +20,15 @@ * @packageDocumentation */ +export { GithubLocationAnalyzer } from './analyzers/GithubLocationAnalyzer'; +export type { GithubLocationAnalyzerOptions } from './analyzers/GithubLocationAnalyzer'; +export type { GithubMultiOrgConfig } from './lib'; export { GithubDiscoveryProcessor } from './processors/GithubDiscoveryProcessor'; export { GithubMultiOrgReaderProcessor } from './processors/GithubMultiOrgReaderProcessor'; export { GithubOrgReaderProcessor } from './processors/GithubOrgReaderProcessor'; -export { GitHubEntityProvider } from './providers/GitHubEntityProvider'; -export { GitHubOrgEntityProvider } from './providers/GitHubOrgEntityProvider'; -export type { GitHubOrgEntityProviderOptions } from './providers/GitHubOrgEntityProvider'; -export type { GithubMultiOrgConfig } from './lib'; -export { githubEntityProviderCatalogModule } from './module'; -export type { GithubEntityProviderCatalogModuleOptions } from './module'; +export { GithubEntityProvider } from './providers/GithubEntityProvider'; +export { GithubOrgEntityProvider } from './providers/GithubOrgEntityProvider'; +export type { GithubOrgEntityProviderOptions } from './providers/GithubOrgEntityProvider'; +export { githubEntityProviderCatalogModule } from './service/GithubEntityProviderCatalogModule'; + +export * from './deprecated'; diff --git a/plugins/catalog-backend-module-github/src/lib/index.ts b/plugins/catalog-backend-module-github/src/lib/index.ts index 26ec3e8516..6402b69121 100644 --- a/plugins/catalog-backend-module-github/src/lib/index.ts +++ b/plugins/catalog-backend-module-github/src/lib/index.ts @@ -22,4 +22,4 @@ export { getOrganizationUsers, } from './github'; export { assignGroupsToUsers, buildOrgHierarchy } from './org'; -export { parseGitHubOrgUrl } from './util'; +export { parseGithubOrgUrl } from './util'; diff --git a/plugins/catalog-backend-module-github/src/lib/util.test.ts b/plugins/catalog-backend-module-github/src/lib/util.test.ts index c530d2bcc9..7aca6839ea 100644 --- a/plugins/catalog-backend-module-github/src/lib/util.test.ts +++ b/plugins/catalog-backend-module-github/src/lib/util.test.ts @@ -14,17 +14,17 @@ * limitations under the License. */ -import { GithubTopicFilters } from '../providers/GitHubEntityProviderConfig'; -import { parseGitHubOrgUrl, satisfiesTopicFilter } from './util'; +import { GithubTopicFilters } from '../providers/GithubEntityProviderConfig'; +import { parseGithubOrgUrl, satisfiesTopicFilter } from './util'; -describe('parseGitHubOrgUrl', () => { +describe('parseGithubOrgUrl', () => { it('only supports clean org urls, and decodes them', () => { - expect(() => parseGitHubOrgUrl('https://github.com')).toThrow(); - expect(() => parseGitHubOrgUrl('https://github.com/org/foo')).toThrow(); + expect(() => parseGithubOrgUrl('https://github.com')).toThrow(); + expect(() => parseGithubOrgUrl('https://github.com/org/foo')).toThrow(); expect(() => - parseGitHubOrgUrl('https://github.com/org/foo/teams'), + parseGithubOrgUrl('https://github.com/org/foo/teams'), ).toThrow(); - expect(parseGitHubOrgUrl('https://github.com/foo%32')).toEqual({ + expect(parseGithubOrgUrl('https://github.com/foo%32')).toEqual({ org: 'foo2', }); }); diff --git a/plugins/catalog-backend-module-github/src/lib/util.ts b/plugins/catalog-backend-module-github/src/lib/util.ts index 238ed9868a..ece463bb40 100644 --- a/plugins/catalog-backend-module-github/src/lib/util.ts +++ b/plugins/catalog-backend-module-github/src/lib/util.ts @@ -14,9 +14,9 @@ * limitations under the License. */ -import { GithubTopicFilters } from '../providers/GitHubEntityProviderConfig'; +import { GithubTopicFilters } from '../providers/GithubEntityProviderConfig'; -export function parseGitHubOrgUrl(urlString: string): { org: string } { +export function parseGithubOrgUrl(urlString: string): { org: string } { const path = new URL(urlString).pathname.substr(1).split('/'); // /backstage diff --git a/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.ts b/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.ts index c6f2f5d0e2..0ef81ac907 100644 --- a/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-github/src/processors/GithubOrgReaderProcessor.ts @@ -35,7 +35,7 @@ import { buildOrgHierarchy, getOrganizationTeams, getOrganizationUsers, - parseGitHubOrgUrl, + parseGithubOrgUrl, } from '../lib'; type GraphQL = typeof graphql; @@ -45,7 +45,7 @@ type GraphQL = typeof graphql; * * @remarks * - * Consider using {@link GitHubOrgEntityProvider} instead. + * Consider using {@link GithubOrgEntityProvider} instead. * * @public */ @@ -94,7 +94,7 @@ export class GithubOrgReaderProcessor implements CatalogProcessor { } const { client, tokenType } = await this.createClient(location.target); - const { org } = parseGitHubOrgUrl(location.target); + const { org } = parseGithubOrgUrl(location.target); // Read out all of the raw data const startTimestamp = Date.now(); diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts similarity index 64% rename from plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.test.ts rename to plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts index 63fb77288b..a63069d552 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.test.ts @@ -15,10 +15,14 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; -import { GitHubEntityProvider } from './GitHubEntityProvider'; +import { GithubEntityProvider } from './GithubEntityProvider'; import * as helpers from '../lib/github'; jest.mock('../lib/github', () => { @@ -41,13 +45,13 @@ class PersistingTaskRunner implements TaskRunner { const logger = getVoidLogger(); -describe('GitHubEntityProvider', () => { +describe('GithubEntityProvider', () => { afterEach(() => jest.resetAllMocks()); it('no provider config', () => { const schedule = new PersistingTaskRunner(); const config = new ConfigReader({}); - const providers = GitHubEntityProvider.fromConfig(config, { + const providers = GithubEntityProvider.fromConfig(config, { logger, schedule, }); @@ -66,7 +70,7 @@ describe('GitHubEntityProvider', () => { }, }, }); - const providers = GitHubEntityProvider.fromConfig(config, { + const providers = GithubEntityProvider.fromConfig(config, { logger, schedule, }); @@ -89,7 +93,7 @@ describe('GitHubEntityProvider', () => { }); expect(() => - GitHubEntityProvider.fromConfig(config, { + GithubEntityProvider.fromConfig(config, { logger, schedule, }), @@ -112,7 +116,7 @@ describe('GitHubEntityProvider', () => { }, }, }); - const providers = GitHubEntityProvider.fromConfig(config, { + const providers = GithubEntityProvider.fromConfig(config, { logger, schedule, }); @@ -149,7 +153,7 @@ describe('GitHubEntityProvider', () => { refresh: jest.fn(), }; - const provider = GitHubEntityProvider.fromConfig(config, { + const provider = GithubEntityProvider.fromConfig(config, { logger, schedule, })[0]; @@ -237,7 +241,7 @@ describe('GitHubEntityProvider', () => { refresh: jest.fn(), }; - const provider = GitHubEntityProvider.fromConfig(config, { + const provider = GithubEntityProvider.fromConfig(config, { logger, schedule, })[0]; @@ -311,7 +315,7 @@ describe('GitHubEntityProvider', () => { refresh: jest.fn(), }; - const provider = GitHubEntityProvider.fromConfig(config, { + const provider = GithubEntityProvider.fromConfig(config, { logger, schedule, })[0]; @@ -381,132 +385,201 @@ describe('GitHubEntityProvider', () => { entities: expectedEntities, }); }); -}); -it('apply full update on scheduled execution with topic exclusion taking priority over topic inclusion', async () => { - const config = new ConfigReader({ - catalog: { - providers: { - github: { - myProvider: { - organization: 'test-org', - catalogPath: 'custom/path/catalog-custom.yaml', - filters: { - branch: 'main', - repository: 'test-.*', - topic: { - exclude: ['backstage-exclude'], - include: ['backstage-include'], + it('apply full update on scheduled execution with topic exclusion taking priority over topic inclusion', async () => { + const config = new ConfigReader({ + catalog: { + providers: { + github: { + myProvider: { + organization: 'test-org', + catalogPath: 'custom/path/catalog-custom.yaml', + filters: { + branch: 'main', + repository: 'test-.*', + topic: { + exclude: ['backstage-exclude'], + include: ['backstage-include'], + }, }, }, }, }, }, - }, + }); + const schedule = new PersistingTaskRunner(); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + refresh: jest.fn(), + }; + + const provider = GithubEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + + const mockGetOrganizationRepositories = jest.spyOn( + helpers, + 'getOrganizationRepositories', + ); + + mockGetOrganizationRepositories.mockReturnValue( + Promise.resolve({ + repositories: [ + { + name: 'test-repo', + url: 'https://github.com/test-org/test-repo', + repositoryTopics: { + nodes: [ + { + topic: { name: 'backstage-include' }, + }, + ], + }, + isArchived: false, + defaultBranchRef: { + name: 'main', + }, + }, + { + name: 'test-repo-2', + url: 'https://github.com/test-org/test-repo-2', + repositoryTopics: { + nodes: [ + { + topic: { name: 'backstage-include' }, + }, + { + topic: { name: 'backstage-exclude' }, + }, + ], + }, + isArchived: false, + defaultBranchRef: { + name: 'main', + }, + }, + { + name: 'test-repo-3', + url: 'https://github.com/test-org/test-repo-3', + repositoryTopics: { + nodes: [ + { + topic: { name: 'backstage-exclude' }, + }, + ], + }, + isArchived: false, + defaultBranchRef: { + name: 'main', + }, + }, + ], + }), + ); + + await provider.connect(entityProviderConnection); + + const taskDef = schedule.getTasks()[0]; + expect(taskDef.id).toEqual('github-provider:myProvider:refresh'); + await (taskDef.fn as () => Promise)(); + + const url = `https://github.com/test-org/test-repo/blob/main/custom/path/catalog-custom.yaml`; + const expectedEntities = [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Location', + metadata: { + annotations: { + 'backstage.io/managed-by-location': `url:${url}`, + 'backstage.io/managed-by-origin-location': `url:${url}`, + }, + name: 'generated-5e4b9498097f15434e88c477cfba6c079aa8ca7f', + }, + spec: { + presence: 'optional', + target: `${url}`, + type: 'url', + }, + }, + locationKey: 'github-provider:myProvider', + }, + ]; + + expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); + expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ + type: 'full', + entities: expectedEntities, + }); }); - const schedule = new PersistingTaskRunner(); - const entityProviderConnection: EntityProviderConnection = { - applyMutation: jest.fn(), - refresh: jest.fn(), - }; - const provider = GitHubEntityProvider.fromConfig(config, { - logger, - schedule, - })[0]; - - const mockGetOrganizationRepositories = jest.spyOn( - helpers, - 'getOrganizationRepositories', - ); - - mockGetOrganizationRepositories.mockReturnValue( - Promise.resolve({ - repositories: [ - { - name: 'test-repo', - url: 'https://github.com/test-org/test-repo', - repositoryTopics: { - nodes: [ - { - topic: { name: 'backstage-include' }, - }, - ], + it('fail without schedule and scheduler', () => { + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', }, - isArchived: false, - defaultBranchRef: { - name: 'main', - }, - }, - { - name: 'test-repo-2', - url: 'https://github.com/test-org/test-repo-2', - repositoryTopics: { - nodes: [ - { - topic: { name: 'backstage-include' }, - }, - { - topic: { name: 'backstage-exclude' }, - }, - ], - }, - isArchived: false, - defaultBranchRef: { - name: 'main', - }, - }, - { - name: 'test-repo-3', - url: 'https://github.com/test-org/test-repo-3', - repositoryTopics: { - nodes: [ - { - topic: { name: 'backstage-exclude' }, - }, - ], - }, - isArchived: false, - defaultBranchRef: { - name: 'main', - }, - }, - ], - }), - ); - - await provider.connect(entityProviderConnection); - - const taskDef = schedule.getTasks()[0]; - expect(taskDef.id).toEqual('github-provider:myProvider:refresh'); - await (taskDef.fn as () => Promise)(); - - const url = `https://github.com/test-org/test-repo/blob/main/custom/path/catalog-custom.yaml`; - const expectedEntities = [ - { - entity: { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Location', - metadata: { - annotations: { - 'backstage.io/managed-by-location': `url:${url}`, - 'backstage.io/managed-by-origin-location': `url:${url}`, - }, - name: 'generated-5e4b9498097f15434e88c477cfba6c079aa8ca7f', - }, - spec: { - presence: 'optional', - target: `${url}`, - type: 'url', }, }, - locationKey: 'github-provider:myProvider', - }, - ]; + }); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledTimes(1); - expect(entityProviderConnection.applyMutation).toHaveBeenCalledWith({ - type: 'full', - entities: expectedEntities, + expect(() => + GithubEntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const scheduler = { + createScheduledTaskRunner: (_: any) => jest.fn(), + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', + }, + }, + }, + }); + + expect(() => + GithubEntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for github-provider:default', + ); + }); + + it('single simple provider config with schedule in config', async () => { + const schedule = new PersistingTaskRunner(); + const scheduler = { + createScheduledTaskRunner: (_: any) => schedule, + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'test-org', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }); + const providers = GithubEntityProvider.fromConfig(config, { + logger, + scheduler, + }); + + expect(providers).toHaveLength(1); + expect(providers[0].getProviderName()).toEqual('github-provider:default'); }); }); diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts similarity index 85% rename from plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.ts rename to plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts index 3909b5a3c2..2004cb7469 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProvider.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GithubCredentialsProvider, @@ -35,8 +35,8 @@ import * as uuid from 'uuid'; import { Logger } from 'winston'; import { readProviderConfigs, - GitHubEntityProviderConfig, -} from './GitHubEntityProviderConfig'; + GithubEntityProviderConfig, +} from './GithubEntityProviderConfig'; import { getOrganizationRepositories, Repository } from '../lib/github'; import { satisfiesTopicFilter } from '../lib/util'; @@ -48,8 +48,8 @@ import { satisfiesTopicFilter } from '../lib/util'; * * @public */ -export class GitHubEntityProvider implements EntityProvider { - private readonly config: GitHubEntityProviderConfig; +export class GithubEntityProvider implements EntityProvider { + private readonly config: GithubEntityProviderConfig; private readonly logger: Logger; private readonly integration: GitHubIntegrationConfig; private readonly scheduleFn: () => Promise; @@ -60,9 +60,14 @@ export class GitHubEntityProvider implements EntityProvider { config: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, - ): GitHubEntityProvider[] { + ): GithubEntityProvider[] { + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + const integrations = ScmIntegrations.fromConfig(config); return readProviderConfigs(config).map(providerConfig => { @@ -75,27 +80,37 @@ export class GitHubEntityProvider implements EntityProvider { ); } - return new GitHubEntityProvider( + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for github-provider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + + return new GithubEntityProvider( providerConfig, integration, options.logger, - options.schedule, + taskRunner, ); }); } private constructor( - config: GitHubEntityProviderConfig, + config: GithubEntityProviderConfig, integration: GitHubIntegration, logger: Logger, - schedule: TaskRunner, + taskRunner: TaskRunner, ) { this.config = config; this.integration = integration.config; this.logger = logger.child({ target: this.getProviderName(), }); - this.scheduleFn = this.createScheduleFn(schedule); + this.scheduleFn = this.createScheduleFn(taskRunner); this.githubCredentialsProvider = SingleInstanceGithubCredentialsProvider.create(integration.config); } @@ -111,14 +126,14 @@ export class GitHubEntityProvider implements EntityProvider { return await this.scheduleFn(); } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn(taskRunner: TaskRunner): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; - return schedule.run({ + return taskRunner.run({ id: taskId, fn: async () => { const logger = this.logger.child({ - class: GitHubEntityProvider.prototype.constructor.name, + class: GithubEntityProvider.prototype.constructor.name, taskId, taskInstanceId: uuid.v4(), }); @@ -141,7 +156,7 @@ export class GitHubEntityProvider implements EntityProvider { const matchingTargets = this.matchesFilters(targets); const entities = matchingTargets .map(repository => this.createLocationUrl(repository)) - .map(GitHubEntityProvider.toLocationSpec) + .map(GithubEntityProvider.toLocationSpec) .map(location => { return { locationKey: this.getProviderName(), diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts similarity index 82% rename from plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.test.ts rename to plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts index 66a5463bf5..c80546bdff 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.test.ts @@ -15,7 +15,8 @@ */ import { ConfigReader } from '@backstage/config'; -import { readProviderConfigs } from './GitHubEntityProviderConfig'; +import { Duration } from 'luxon'; +import { readProviderConfigs } from './GithubEntityProviderConfig'; describe('readProviderConfigs', () => { afterEach(() => jest.resetAllMocks()); @@ -81,13 +82,22 @@ describe('readProviderConfigs', () => { organization: 'test-org1', host: 'ghe.internal.com', }, + providerWithSchedule: { + organization: 'test-org1', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, }, }, }, }); const providerConfigs = readProviderConfigs(config); - expect(providerConfigs).toHaveLength(6); + expect(providerConfigs).toHaveLength(7); expect(providerConfigs[0]).toEqual({ id: 'providerOrganizationOnly', organization: 'test-org1', @@ -101,6 +111,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + schedule: undefined, }); expect(providerConfigs[1]).toEqual({ id: 'providerCustomCatalogPath', @@ -115,6 +126,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + schedule: undefined, }); expect(providerConfigs[2]).toEqual({ id: 'providerWithRepositoryFilter', @@ -129,6 +141,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + schedule: undefined, }); expect(providerConfigs[3]).toEqual({ id: 'providerWithBranchFilter', @@ -143,6 +156,7 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + schedule: undefined, }); expect(providerConfigs[4]).toEqual({ id: 'providerWithTopicFilter', @@ -157,6 +171,7 @@ describe('readProviderConfigs', () => { exclude: ['backstage-exclude'], }, }, + schedule: undefined, }); expect(providerConfigs[5]).toEqual({ id: 'providerWithHost', @@ -171,6 +186,27 @@ describe('readProviderConfigs', () => { exclude: undefined, }, }, + schedule: undefined, + }); + expect(providerConfigs[6]).toEqual({ + id: 'providerWithSchedule', + organization: 'test-org1', + catalogPath: '/catalog-info.yaml', + host: 'github.com', + filters: { + repository: undefined, + branch: undefined, + topic: { + include: undefined, + exclude: undefined, + }, + }, + schedule: { + frequency: Duration.fromISO('PT30M'), + timeout: { + minutes: 3, + }, + }, }); }); }); diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.ts b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts similarity index 87% rename from plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.ts rename to plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts index d40b206751..18c0a3915d 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubEntityProviderConfig.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubEntityProviderConfig.ts @@ -14,12 +14,16 @@ * limitations under the License. */ +import { + readTaskScheduleDefinitionFromConfig, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; const DEFAULT_CATALOG_PATH = '/catalog-info.yaml'; const DEFAULT_PROVIDER_ID = 'default'; -export type GitHubEntityProviderConfig = { +export type GithubEntityProviderConfig = { id: string; catalogPath: string; organization: string; @@ -29,6 +33,7 @@ export type GitHubEntityProviderConfig = { branch?: string; topic?: GithubTopicFilters; }; + schedule?: TaskScheduleDefinition; }; export type GithubTopicFilters = { @@ -38,7 +43,7 @@ export type GithubTopicFilters = { export function readProviderConfigs( config: Config, -): GitHubEntityProviderConfig[] { +): GithubEntityProviderConfig[] { const providersConfig = config.getOptionalConfig('catalog.providers.github'); if (!providersConfig) { return []; @@ -59,7 +64,7 @@ export function readProviderConfigs( function readProviderConfig( id: string, config: Config, -): GitHubEntityProviderConfig { +): GithubEntityProviderConfig { const organization = config.getString('organization'); const catalogPath = config.getOptionalString('catalogPath') ?? DEFAULT_CATALOG_PATH; @@ -73,6 +78,10 @@ function readProviderConfig( 'filters.topic.exclude', ); + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { id, catalogPath, @@ -88,8 +97,10 @@ function readProviderConfig( exclude: topicFilterExclude, }, }, + schedule, }; } + /** * Compiles a RegExp while enforcing the pattern to contain * the start-of-line and end-of-line anchors. diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts similarity index 97% rename from plugins/catalog-backend-module-github/src/providers/GitHubOrgEntityProvider.test.ts rename to plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts index 2d05346f01..1d8d125d91 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts @@ -23,13 +23,13 @@ import { import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { graphql } from '@octokit/graphql'; import { - GitHubOrgEntityProvider, + GithubOrgEntityProvider, withLocations, -} from './GitHubOrgEntityProvider'; +} from './GithubOrgEntityProvider'; jest.mock('@octokit/graphql'); -describe('GitHubOrgEntityProvider', () => { +describe('GithubOrgEntityProvider', () => { describe('read', () => { afterEach(() => jest.resetAllMocks()); @@ -100,7 +100,7 @@ describe('GitHubOrgEntityProvider', () => { getCredentials: mockGetCredentials, }; - const entityProvider = new GitHubOrgEntityProvider({ + const entityProvider = new GithubOrgEntityProvider({ id: 'my-id', githubCredentialsProvider, orgUrl: 'https://github.com/backstage', diff --git a/plugins/catalog-backend-module-github/src/providers/GitHubOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts similarity index 92% rename from plugins/catalog-backend-module-github/src/providers/GitHubOrgEntityProvider.ts rename to plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts index bb994c193c..f8fafbe53c 100644 --- a/plugins/catalog-backend-module-github/src/providers/GitHubOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.ts @@ -41,15 +41,15 @@ import { buildOrgHierarchy, getOrganizationTeams, getOrganizationUsers, - parseGitHubOrgUrl, + parseGithubOrgUrl, } from '../lib'; /** - * Options for {@link GitHubOrgEntityProvider}. + * Options for {@link GithubOrgEntityProvider}. * * @public */ -export interface GitHubOrgEntityProviderOptions { +export interface GithubOrgEntityProviderOptions { /** * A unique, stable identifier for this provider. * @@ -96,12 +96,12 @@ export interface GitHubOrgEntityProviderOptions { * * @public */ -export class GitHubOrgEntityProvider implements EntityProvider { +export class GithubOrgEntityProvider implements EntityProvider { private readonly credentialsProvider: GithubCredentialsProvider; private connection?: EntityProviderConnection; private scheduleFn?: () => Promise; - static fromConfig(config: Config, options: GitHubOrgEntityProviderOptions) { + static fromConfig(config: Config, options: GithubOrgEntityProviderOptions) { const integrations = ScmIntegrations.fromConfig(config); const gitHubConfig = integrations.github.byUrl(options.orgUrl)?.config; @@ -115,7 +115,7 @@ export class GitHubOrgEntityProvider implements EntityProvider { target: options.orgUrl, }); - const provider = new GitHubOrgEntityProvider({ + const provider = new GithubOrgEntityProvider({ id: options.id, orgUrl: options.orgUrl, logger, @@ -146,7 +146,7 @@ export class GitHubOrgEntityProvider implements EntityProvider { /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */ getProviderName() { - return `GitHubOrgEntityProvider:${this.options.id}`; + return `GithubOrgEntityProvider:${this.options.id}`; } /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */ @@ -176,7 +176,7 @@ export class GitHubOrgEntityProvider implements EntityProvider { headers, }); - const { org } = parseGitHubOrgUrl(this.options.orgUrl); + const { org } = parseGithubOrgUrl(this.options.orgUrl); const { users } = await getOrganizationUsers(client, org, tokenType); const { groups, groupMemberUsers } = await getOrganizationTeams( client, @@ -202,7 +202,7 @@ export class GitHubOrgEntityProvider implements EntityProvider { markCommitComplete(); } - private schedule(schedule: GitHubOrgEntityProviderOptions['schedule']) { + private schedule(schedule: GithubOrgEntityProviderOptions['schedule']) { if (!schedule || schedule === 'manual') { return; } @@ -213,7 +213,7 @@ export class GitHubOrgEntityProvider implements EntityProvider { id, fn: async () => { const logger = this.options.logger.child({ - class: GitHubOrgEntityProvider.prototype.constructor.name, + class: GithubOrgEntityProvider.prototype.constructor.name, taskId: id, taskInstanceId: uuid.v4(), }); diff --git a/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..b555927d2c --- /dev/null +++ b/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.test.ts @@ -0,0 +1,84 @@ +/* + * 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 { getVoidLogger } from '@backstage/backend-common'; +import { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { Duration } from 'luxon'; +import { githubEntityProviderCatalogModule } from './GithubEntityProviderCatalogModule'; +import { GithubEntityProvider } from '../providers/GithubEntityProvider'; + +describe('githubEntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + catalog: { + providers: { + github: { + organization: 'module-test', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [githubEntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'github-provider:default', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-github/src/module.ts b/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.ts similarity index 60% rename from plugins/catalog-backend-module-github/src/module.ts rename to plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.ts index 4eccef0611..8fdee886a3 100644 --- a/plugins/catalog-backend-module-github/src/module.ts +++ b/plugins/catalog-backend-module-github/src/service/GithubEntityProviderCatalogModule.ts @@ -21,46 +21,30 @@ import { loggerServiceRef, schedulerServiceRef, } from '@backstage/backend-plugin-api'; -import { TaskScheduleDefinition } from '@backstage/backend-tasks'; import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; -import { GitHubEntityProvider } from './providers/GitHubEntityProvider'; +import { GithubEntityProvider } from '../providers/GithubEntityProvider'; /** - * Options for {@link githubEntityProviderCatalogModule}. - * - * @alpha - */ -export type GithubEntityProviderCatalogModuleOptions = { - schedule?: TaskScheduleDefinition; -}; - -/** - * Registers the GitHubEntityProvider with the catalog processing extension point. + * Registers the {@link GithubEntityProvider} with the catalog processing extension point. * * @alpha */ export const githubEntityProviderCatalogModule = createBackendModule({ pluginId: 'catalog', - moduleId: 'github-entity-provider', - register(env, options?: GithubEntityProviderCatalogModuleOptions) { + moduleId: 'githubEntityProvider', + register(env) { env.registerInit({ deps: { - config: configServiceRef, catalog: catalogProcessingExtensionPoint, + config: configServiceRef, logger: loggerServiceRef, scheduler: schedulerServiceRef, }, - async init({ config, catalog, logger, scheduler }) { - const scheduleDef = options?.schedule ?? { - frequency: { seconds: 600 }, - timeout: { seconds: 900 }, - initialDelay: { seconds: 3 }, - }; - + async init({ catalog, config, logger, scheduler }) { catalog.addEntityProvider( - GitHubEntityProvider.fromConfig(config, { + GithubEntityProvider.fromConfig(config, { logger: loggerToWinstonLogger(logger), - schedule: scheduler.createScheduledTaskRunner(scheduleDef), + scheduler, }), ); }, diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index d6b3cfb3d7..65979281e7 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## 0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + ## 0.1.7 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/api-report.md b/plugins/catalog-backend-module-gitlab/api-report.md index 1009da8209..9ed90a2c42 100644 --- a/plugins/catalog-backend-module-gitlab/api-report.md +++ b/plugins/catalog-backend-module-gitlab/api-report.md @@ -3,6 +3,7 @@ > Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). ```ts +import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogProcessor } from '@backstage/plugin-catalog-backend'; import { CatalogProcessorEmit } from '@backstage/plugin-catalog-backend'; import { Config } from '@backstage/config'; @@ -10,6 +11,7 @@ import { EntityProvider } from '@backstage/plugin-catalog-backend'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; import { LocationSpec } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; +import { PluginTaskScheduler } from '@backstage/backend-tasks'; import { TaskRunner } from '@backstage/backend-tasks'; // @public @@ -21,7 +23,8 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { config: Config, options: { logger: Logger; - schedule: TaskRunner; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; }, ): GitlabDiscoveryEntityProvider[]; // (undocumented) @@ -30,6 +33,11 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { refresh(logger: Logger): Promise; } +// @alpha +export const gitlabDiscoveryEntityProviderCatalogModule: ( + options?: undefined, +) => BackendFeature; + // @public export class GitLabDiscoveryProcessor implements CatalogProcessor { // (undocumented) diff --git a/plugins/catalog-backend-module-gitlab/config.d.ts b/plugins/catalog-backend-module-gitlab/config.d.ts index 82f1a5db7c..ac75a04c2a 100644 --- a/plugins/catalog-backend-module-gitlab/config.d.ts +++ b/plugins/catalog-backend-module-gitlab/config.d.ts @@ -14,11 +14,10 @@ * limitations under the License. */ +import { TaskScheduleDefinitionConfig } from '@backstage/backend-tasks'; + export interface Config { catalog?: { - /** - * List of provider-specific options and attributes - */ providers?: { /** * GitlabDiscoveryEntityProvider configuration @@ -28,27 +27,27 @@ export interface Config { { /** * (Required) Gitlab's host name. - * @visibility backend */ host: string; /** * (Optional) Gitlab's group[/subgroup] where the discovery is done. * If not defined the whole project will be scanned. - * @visibility backend */ group?: string; /** * (Optional) Default branch to read the catalog-info.yaml file. * If not set, 'master' will be used. - * @visibility backend */ branch?: string; /** * (Optional) The name used for the catalog file. * If not set, 'catalog-info.yaml' will be used. - * @visibility backend */ entityFilename?: string; + /** + * (Optional) TaskScheduleDefinition for the refresh. + */ + schedule?: TaskScheduleDefinitionConfig; } >; }; diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index 457be11480..a0a0cfc38f 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,12 +1,13 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", "description": "A Backstage catalog backend module that helps integrate towards GitLab", - "version": "0.1.7", + "version": "0.1.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", "publishConfig": { "access": "public", + "alphaTypes": "dist/index.alpha.d.ts", "main": "dist/index.cjs.js", "types": "dist/index.d.ts" }, @@ -23,22 +24,24 @@ "backstage" ], "scripts": { - "build": "backstage-cli package build", + "start": "backstage-cli package start", + "build": "backstage-cli package build --experimental-type-build", "lint": "backstage-cli package lint", "test": "backstage-cli package test", "prepack": "backstage-cli package prepack", "postpack": "backstage-cli package postpack", - "clean": "backstage-cli package clean", - "start": "backstage-cli package start" + "clean": "backstage-cli package clean" }, "dependencies": { "@backstage/backend-common": "workspace:^", + "@backstage/backend-plugin-api": "workspace:^", "@backstage/backend-tasks": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-catalog-backend": "workspace:^", + "@backstage/plugin-catalog-node": "workspace:^", "@backstage/types": "workspace:^", "lodash": "^4.17.21", "msw": "^0.47.0", @@ -50,9 +53,13 @@ "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@types/lodash": "^4.14.151", - "@types/uuid": "^8.0.0" + "@types/uuid": "^8.0.0", + "luxon": "^3.0.0" }, "files": [ + "alpha", + "config.d.ts", "dist" - ] + ], + "configSchema": "config.d.ts" } diff --git a/plugins/catalog-backend-module-gitlab/src/index.ts b/plugins/catalog-backend-module-gitlab/src/index.ts index 3182210c81..27aac9e78f 100644 --- a/plugins/catalog-backend-module-gitlab/src/index.ts +++ b/plugins/catalog-backend-module-gitlab/src/index.ts @@ -22,3 +22,4 @@ export { GitLabDiscoveryProcessor } from './GitLabDiscoveryProcessor'; export { GitlabDiscoveryEntityProvider } from './providers'; +export { gitlabDiscoveryEntityProviderCatalogModule } from './service/GitlabDiscoveryEntityProviderCatalogModule'; diff --git a/plugins/catalog-backend-module-gitlab/src/lib/types.ts b/plugins/catalog-backend-module-gitlab/src/lib/types.ts index 8a3c33c8f2..86fe1aa385 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/types.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/types.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { TaskScheduleDefinition } from '@backstage/backend-tasks'; + export type GitlabGroupDescription = { id: number; web_url: string; @@ -36,4 +38,5 @@ export type GitlabProviderConfig = { branch: string; catalogFile: string; projectPattern: RegExp; + schedule?: TaskScheduleDefinition; }; diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts index 3f3c4e7f91..ae19edc48c 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.test.ts @@ -15,13 +15,17 @@ */ import { getVoidLogger } from '@backstage/backend-common'; -import { TaskInvocationDefinition, TaskRunner } from '@backstage/backend-tasks'; +import { + PluginTaskScheduler, + TaskInvocationDefinition, + TaskRunner, +} from '@backstage/backend-tasks'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import { ConfigReader } from '@backstage/config'; import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; -import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; -import { GitlabDiscoveryEntityProvider } from './GitlabDiscoveryEntityProvider'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; +import { GitlabDiscoveryEntityProvider } from './GitlabDiscoveryEntityProvider'; class PersistingTaskRunner implements TaskRunner { private tasks: TaskInvocationDefinition[] = []; @@ -338,4 +342,111 @@ describe('GitlabDiscoveryEntityProvider', () => { ], }); }); + + it('fail without schedule and scheduler', () => { + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'test-gitlab', + apiBaseUrl: 'https://api.gitlab.example/api/v4', + token: '1234', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'test-gitlab', + group: 'test-group', + }, + }, + }, + }, + }); + + expect(() => + GitlabDiscoveryEntityProvider.fromConfig(config, { + logger, + }), + ).toThrow('Either schedule or scheduler must be provided'); + }); + + it('fail with scheduler but no schedule config', () => { + const scheduler = { + createScheduledTaskRunner: (_: any) => jest.fn(), + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'test-gitlab', + apiBaseUrl: 'https://api.gitlab.example/api/v4', + token: '1234', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'test-gitlab', + group: 'test-group', + }, + }, + }, + }, + }); + + expect(() => + GitlabDiscoveryEntityProvider.fromConfig(config, { + logger, + scheduler, + }), + ).toThrow( + 'No schedule provided neither via code nor config for GitlabDiscoveryEntityProvider:test-id', + ); + }); + + it('single simple provider config with schedule in config', async () => { + const schedule = new PersistingTaskRunner(); + const scheduler = { + createScheduledTaskRunner: (_: any) => schedule, + } as unknown as PluginTaskScheduler; + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'test-gitlab', + apiBaseUrl: 'https://api.gitlab.example/api/v4', + token: '1234', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'test-gitlab', + group: 'test-group', + schedule: { + frequency: 'PT30M', + timeout: 'PT3M', + }, + }, + }, + }, + }, + }); + const providers = GitlabDiscoveryEntityProvider.fromConfig(config, { + logger, + scheduler, + }); + + expect(providers).toHaveLength(1); + expect(providers[0].getProviderName()).toEqual( + 'GitlabDiscoveryEntityProvider:test-id', + ); + }); }); diff --git a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts index ffb123edf7..cb4629b820 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/GitlabDiscoveryEntityProvider.ts @@ -14,14 +14,16 @@ * limitations under the License. */ -import { TaskRunner } from '@backstage/backend-tasks'; +import { PluginTaskScheduler, TaskRunner } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GitLabIntegration, ScmIntegrations } from '@backstage/integration'; import { EntityProvider, EntityProviderConnection, + LocationSpec, + locationSpecToLocationEntity, } from '@backstage/plugin-catalog-backend'; -import { LocationSpec } from '@backstage/plugin-catalog-backend'; +import * as uuid from 'uuid'; import { Logger } from 'winston'; import { GitLabClient, @@ -30,8 +32,6 @@ import { paginated, readGitlabConfigs, } from '../lib'; -import * as uuid from 'uuid'; -import { locationSpecToLocationEntity } from '@backstage/plugin-catalog-backend'; type Result = { scanned: number; @@ -51,8 +51,16 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { static fromConfig( config: Config, - options: { logger: Logger; schedule: TaskRunner }, + options: { + logger: Logger; + schedule?: TaskRunner; + scheduler?: PluginTaskScheduler; + }, ): GitlabDiscoveryEntityProvider[] { + if (!options.schedule && !options.scheduler) { + throw new Error('Either schedule or scheduler must be provided.'); + } + const providerConfigs = readGitlabConfigs(config); const integrations = ScmIntegrations.fromConfig(config).gitlab; const providers: GitlabDiscoveryEntityProvider[] = []; @@ -64,11 +72,23 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { `No gitlab integration found that matches host ${providerConfig.host}`, ); } + + if (!options.schedule && !providerConfig.schedule) { + throw new Error( + `No schedule provided neither via code nor config for GitlabDiscoveryEntityProvider:${providerConfig.id}.`, + ); + } + + const taskRunner = + options.schedule ?? + options.scheduler!.createScheduledTaskRunner(providerConfig.schedule!); + providers.push( new GitlabDiscoveryEntityProvider({ ...options, config: providerConfig, integration, + taskRunner, }), ); }); @@ -79,14 +99,14 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { config: GitlabProviderConfig; integration: GitLabIntegration; logger: Logger; - schedule: TaskRunner; + taskRunner: TaskRunner; }) { this.config = options.config; this.integration = options.integration; this.logger = options.logger.child({ target: this.getProviderName(), }); - this.scheduleFn = this.createScheduleFn(options.schedule); + this.scheduleFn = this.createScheduleFn(options.taskRunner); } getProviderName(): string { @@ -98,10 +118,10 @@ export class GitlabDiscoveryEntityProvider implements EntityProvider { await this.scheduleFn(); } - private createScheduleFn(schedule: TaskRunner): () => Promise { + private createScheduleFn(taskRunner: TaskRunner): () => Promise { return async () => { const taskId = `${this.getProviderName()}:refresh`; - return schedule.run({ + return taskRunner.run({ id: taskId, fn: async () => { const logger = this.logger.child({ diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts index cb36e5166c..6b337bf7cd 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.test.ts @@ -15,6 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; +import { Duration } from 'luxon'; import { readGitlabConfigs } from './config'; describe('config', () => { @@ -53,6 +54,7 @@ describe('config', () => { host: 'host', catalogFile: 'catalog-info.yaml', projectPattern: /[\s\S]*/, + schedule: undefined, }), ); }); @@ -83,6 +85,49 @@ describe('config', () => { host: 'host', catalogFile: 'custom-file.yaml', projectPattern: /[\s\S]*/, + schedule: undefined, + }), + ); + }); + + it('valid config with schedule', () => { + const config = new ConfigReader({ + catalog: { + providers: { + gitlab: { + test: { + group: 'group', + host: 'host', + schedule: { + frequency: 'PT30M', + timeout: { + minutes: 3, + }, + }, + }, + }, + }, + }, + }); + + const result = readGitlabConfigs(config); + expect(result).toHaveLength(1); + result.forEach(r => + expect(r).toStrictEqual({ + id: 'test', + group: 'group', + branch: 'master', + host: 'host', + catalogFile: 'catalog-info.yaml', + projectPattern: /[\s\S]*/, + schedule: { + frequency: Duration.fromISO('PT30M'), + timeout: { + minutes: 3, + }, + initialDelay: undefined, + scope: undefined, + }, }), ); }); diff --git a/plugins/catalog-backend-module-gitlab/src/providers/config.ts b/plugins/catalog-backend-module-gitlab/src/providers/config.ts index 392922860f..3dfc00745e 100644 --- a/plugins/catalog-backend-module-gitlab/src/providers/config.ts +++ b/plugins/catalog-backend-module-gitlab/src/providers/config.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { readTaskScheduleDefinitionFromConfig } from '@backstage/backend-tasks'; import { Config } from '@backstage/config'; import { GitlabProviderConfig } from '../lib'; @@ -35,6 +36,10 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { config.getOptionalString('projectPattern') ?? /[\s\S]*/, ); + const schedule = config.has('schedule') + ? readTaskScheduleDefinitionFromConfig(config.getConfig('schedule')) + : undefined; + return { id, group, @@ -42,6 +47,7 @@ function readGitlabConfig(id: string, config: Config): GitlabProviderConfig { host, catalogFile, projectPattern, + schedule, }; } diff --git a/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.test.ts b/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.test.ts new file mode 100644 index 0000000000..0b803734b1 --- /dev/null +++ b/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.test.ts @@ -0,0 +1,96 @@ +/* + * 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 { getVoidLogger } from '@backstage/backend-common'; +import { + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { + PluginTaskScheduler, + TaskScheduleDefinition, +} from '@backstage/backend-tasks'; +import { startTestBackend } from '@backstage/backend-test-utils'; +import { ConfigReader } from '@backstage/config'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { Duration } from 'luxon'; +import { gitlabDiscoveryEntityProviderCatalogModule } from './GitlabDiscoveryEntityProviderCatalogModule'; +import { GitlabDiscoveryEntityProvider } from '../providers'; + +describe('gitlabDiscoveryEntityProviderCatalogModule', () => { + it('should register provider at the catalog extension point', async () => { + let addedProviders: Array | undefined; + let usedSchedule: TaskScheduleDefinition | undefined; + + const extensionPoint = { + addEntityProvider: (providers: any) => { + addedProviders = providers; + }, + }; + const runner = jest.fn(); + const scheduler = { + createScheduledTaskRunner: (schedule: TaskScheduleDefinition) => { + usedSchedule = schedule; + return runner; + }, + } as unknown as PluginTaskScheduler; + + const config = new ConfigReader({ + integrations: { + gitlab: [ + { + host: 'test-gitlab', + apiBaseUrl: 'https://api.gitlab.example/api/v4', + token: '1234', + }, + ], + }, + catalog: { + providers: { + gitlab: { + 'test-id': { + host: 'test-gitlab', + group: 'test-group', + schedule: { + frequency: 'P1M', + timeout: 'PT3M', + }, + }, + }, + }, + }, + }); + + await startTestBackend({ + extensionPoints: [[catalogProcessingExtensionPoint, extensionPoint]], + services: [ + [configServiceRef, config], + [loggerServiceRef, getVoidLogger()], + [schedulerServiceRef, scheduler], + ], + features: [gitlabDiscoveryEntityProviderCatalogModule()], + }); + + expect(usedSchedule?.frequency).toEqual(Duration.fromISO('P1M')); + expect(usedSchedule?.timeout).toEqual(Duration.fromISO('PT3M')); + expect(addedProviders?.length).toEqual(1); + expect(addedProviders?.pop()?.getProviderName()).toEqual( + 'GitlabDiscoveryEntityProvider:test-id', + ); + expect(runner).not.toHaveBeenCalled(); + }); +}); diff --git a/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.ts b/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.ts new file mode 100644 index 0000000000..dc11841e20 --- /dev/null +++ b/plugins/catalog-backend-module-gitlab/src/service/GitlabDiscoveryEntityProviderCatalogModule.ts @@ -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 { + createBackendModule, + loggerToWinstonLogger, + configServiceRef, + loggerServiceRef, + schedulerServiceRef, +} from '@backstage/backend-plugin-api'; +import { catalogProcessingExtensionPoint } from '@backstage/plugin-catalog-node'; +import { GitlabDiscoveryEntityProvider } from '../providers'; + +/** + * Registers the GitlabDiscoveryEntityProvider with the catalog processing extension point. + * + * @alpha + */ +export const gitlabDiscoveryEntityProviderCatalogModule = createBackendModule({ + pluginId: 'catalog', + moduleId: 'gitlabDiscoveryEntityProvider', + register(env) { + env.registerInit({ + deps: { + config: configServiceRef, + catalog: catalogProcessingExtensionPoint, + logger: loggerServiceRef, + scheduler: schedulerServiceRef, + }, + async init({ config, catalog, logger, scheduler }) { + catalog.addEntityProvider( + GitlabDiscoveryEntityProvider.fromConfig(config, { + logger: loggerToWinstonLogger(logger), + scheduler, + }), + ); + }, + }); + }, +}); diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 7e21490644..8045a29598 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.5.4 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 0.5.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## 0.5.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## 0.5.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + ## 0.5.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index ea00832f98..eb97de27d0 100644 --- a/plugins/catalog-backend-module-ldap/package.json +++ b/plugins/catalog-backend-module-ldap/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-ldap", "description": "A Backstage catalog backend module that helps integrate towards LDAP", - "version": "0.5.3", + "version": "0.5.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index aa601f1c3f..4112931043 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + +## 0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + ## 0.4.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 5a5b90832e..df63bc71dd 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", "description": "A Backstage catalog backend module that helps integrate towards Microsoft Graph", - "version": "0.4.2", + "version": "0.4.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-openapi/CHANGELOG.md b/plugins/catalog-backend-module-openapi/CHANGELOG.md index 81f356168e..a11be4a5f2 100644 --- a/plugins/catalog-backend-module-openapi/CHANGELOG.md +++ b/plugins/catalog-backend-module-openapi/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-catalog-backend-module-openapi +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/config@1.0.3 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.1.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## 0.1.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + - @backstage/plugin-catalog-node@1.1.1-next.1 + +## 0.1.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/plugin-catalog-node@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + ## 0.1.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-openapi/package.json b/plugins/catalog-backend-module-openapi/package.json index f3081f575b..1f32a1a4f2 100644 --- a/plugins/catalog-backend-module-openapi/package.json +++ b/plugins/catalog-backend-module-openapi/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-openapi", "description": "A Backstage catalog backend module that helps with OpenAPI specifications", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend-module-openapi/src/lib/bundle.test.ts b/plugins/catalog-backend-module-openapi/src/lib/bundle.test.ts index 0ecd9f7fd1..57ecd54f26 100644 --- a/plugins/catalog-backend-module-openapi/src/lib/bundle.test.ts +++ b/plugins/catalog-backend-module-openapi/src/lib/bundle.test.ts @@ -88,6 +88,33 @@ describe('bundleOpenApiSpecification', () => { resolveUrl, ); + expect(result).toEqual(expectedResult.trimStart()); + }); + it('should use the urlreaders to fetch $refs', async () => { + const spec = ` + openapi: "3.0.0" + info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT + servers: + - url: http://petstore.swagger.io/v1 + paths: + /pets: + get: + $ref: "https://foo.com/paths/pets/list.yaml" + `; + + read.mockResolvedValue(list); + + const result = await bundleOpenApiSpecification( + spec, + 'https://github.com/owner/repo/blob/main/catalog-info.yaml', + read, + resolveUrl, + ); + expect(result).toEqual(expectedResult.trimStart()); }); }); diff --git a/plugins/catalog-backend-module-openapi/src/lib/bundle.ts b/plugins/catalog-backend-module-openapi/src/lib/bundle.ts index 6ac8d211e2..8dd880c234 100644 --- a/plugins/catalog-backend-module-openapi/src/lib/bundle.ts +++ b/plugins/catalog-backend-module-openapi/src/lib/bundle.ts @@ -47,11 +47,21 @@ export async function bundleOpenApiSpecification( return await read(url); }, }; + const httpUrlReaderResolver: SwaggerParser.ResolverOptions = { + canRead: ref => { + const protocol = getProtocol(ref.url); + return protocol === 'http' || protocol === 'https'; + }, + read: async ref => { + const url = resolveUrl(ref.url, baseUrl); + return await read(url); + }, + }; const options: SwaggerParser.Options = { resolve: { file: fileUrlReaderResolver, - http: true, + http: httpUrlReaderResolver, }, }; const specObject = parse(specification); diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index d702df4af0..60ecdae3b9 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,121 @@ # @backstage/plugin-catalog-backend +## 1.5.0 + +### Minor Changes + +- b2e6cb6acf: Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. + + Moved the following types from this package to `@backstage/plugin-catalog-backend`. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- eb25f7e12d: The exported permission rules and the API of `createCatalogConditionalDecision` have changed to reflect the breaking changes made to the `PermissionRule` type. Note that all involved types are exported from `@backstage/plugin-catalog-backend/alpha` + +### Patch Changes + +- 8cb6e10105: Fixed a bug where entities provided without a location key would always replace existing entities, rather than updating them. +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- 63296ebcd4: Allow Placeholder value to be any value, not only string. +- 74022e0163: Make sure to stitch entities correctly after deletion, to ensure that their relations are updated. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-scaffolder-common@1.2.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 1.5.0-next.2 + +### Minor Changes + +- b2e6cb6acf: Added a new method `addLocationAnalyzers` to the `CatalogBuilder`. With this you can add location analyzers to your catalog. These analyzers will be used by the /analyze-location endpoint to decide if the provided URL contains any catalog-info.yaml files already or not. + + Moved the following types from this package to `@backstage/plugin-catalog-backend`. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- eb25f7e12d: The exported permission rules and the API of `createCatalogConditionalDecision` have changed to reflect the breaking changes made to the `PermissionRule` type. Note that all involved types are exported from `@backstage/plugin-catalog-backend/alpha` + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + +## 1.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.7-next.1 + - @backstage/plugin-catalog-node@1.1.1-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + - @backstage/plugin-scaffolder-common@1.2.1-next.1 + +## 1.4.1-next.0 + +### Patch Changes + +- 8cb6e10105: Fixed a bug where entities provided without a location key would always replace existing entities, rather than updating them. +- 63296ebcd4: Allow Placeholder value to be any value, not only string. +- 74022e0163: Make sure to stitch entities correctly after deletion, to ensure that their relations are updated. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-catalog-node@1.1.1-next.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 1.4.0 ### Minor Changes diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md index f18d396114..10b9afc291 100644 --- a/plugins/catalog-backend/api-report.md +++ b/plugins/catalog-backend/api-report.md @@ -5,6 +5,11 @@ ```ts /// +import { AnalyzeLocationEntityField as AnalyzeLocationEntityField_2 } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationExistingEntity as AnalyzeLocationExistingEntity_2 } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationGenerateEntity as AnalyzeLocationGenerateEntity_2 } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationRequest as AnalyzeLocationRequest_2 } from '@backstage/plugin-catalog-common'; +import { AnalyzeLocationResponse as AnalyzeLocationResponse_2 } from '@backstage/plugin-catalog-common'; import { BackendFeature } from '@backstage/backend-plugin-api'; import { CatalogApi } from '@backstage/catalog-client'; import { CatalogEntityDocument } from '@backstage/plugin-catalog-common'; @@ -40,6 +45,7 @@ import { PermissionCondition } from '@backstage/plugin-permission-common'; import { PermissionCriteria } from '@backstage/plugin-permission-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PermissionRule } from '@backstage/plugin-permission-node'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { processingResult } from '@backstage/plugin-catalog-node'; @@ -51,39 +57,25 @@ import { TokenManager } from '@backstage/backend-common'; import { UrlReader } from '@backstage/backend-common'; import { Validators } from '@backstage/catalog-model'; -// @public (undocumented) -export type AnalyzeLocationEntityField = { - field: string; - state: - | 'analysisSuggestedValue' - | 'analysisSuggestedNoValue' - | 'needsUserInput'; - value: string | null; - description: string; -}; +// @public @deprecated +export type AnalyzeLocationEntityField = AnalyzeLocationEntityField_2; -// @public -export type AnalyzeLocationExistingEntity = { - location: LocationSpec; - isRegistered: boolean; - entity: Entity; -}; +// @public @deprecated +export type AnalyzeLocationExistingEntity = AnalyzeLocationExistingEntity_2; -// @public -export type AnalyzeLocationGenerateEntity = { - entity: RecursivePartial; - fields: AnalyzeLocationEntityField[]; -}; +// @public @deprecated +export type AnalyzeLocationGenerateEntity = AnalyzeLocationGenerateEntity_2; + +// @public @deprecated (undocumented) +export type AnalyzeLocationRequest = AnalyzeLocationRequest_2; + +// @public @deprecated (undocumented) +export type AnalyzeLocationResponse = AnalyzeLocationResponse_2; // @public (undocumented) -export type AnalyzeLocationRequest = { - location: LocationSpec; -}; - -// @public (undocumented) -export type AnalyzeLocationResponse = { - existingEntityFiles: AnalyzeLocationExistingEntity[]; - generateEntities: AnalyzeLocationGenerateEntity[]; +export type AnalyzeOptions = { + url: string; + catalogFilename?: string; }; // @public (undocumented) @@ -133,6 +125,9 @@ export class CatalogBuilder { addEntityProvider( ...providers: Array> ): CatalogBuilder; + addLocationAnalyzers( + ...analyzers: Array> + ): CatalogBuilder; // @alpha addPermissionRules( ...permissionRules: Array< @@ -177,37 +172,52 @@ export const catalogConditions: Conditions<{ Entity, EntitiesSearchFilter, 'catalog-entity', - [annotation: string, value?: string | undefined] + { + value?: string | undefined; + annotation: string; + } >; hasLabel: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [label: string] + { + label: string; + } >; hasMetadata: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [key: string, value?: string | undefined] + { + value?: string | undefined; + key: string; + } >; hasSpec: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [key: string, value?: string | undefined] + { + value?: string | undefined; + key: string; + } >; isEntityKind: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [kinds: string[]] + { + kinds: string[]; + } >; isEntityOwner: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [claims: string[]] + { + claims: string[]; + } >; }>; @@ -221,8 +231,9 @@ export type CatalogEnvironment = { }; // @alpha -export type CatalogPermissionRule = - PermissionRule; +export type CatalogPermissionRule< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule; // @alpha export const catalogPlugin: (options?: undefined) => BackendFeature; @@ -280,12 +291,14 @@ export class CodeOwnersProcessor implements CatalogProcessor { export const createCatalogConditionalDecision: ( permission: ResourcePermission<'catalog-entity'>, conditions: PermissionCriteria< - PermissionCondition<'catalog-entity', unknown[]> + PermissionCondition<'catalog-entity', PermissionRuleParams> >, ) => ConditionalPolicyDecision; // @alpha -export const createCatalogPermissionRule: ( +export const createCatalogPermissionRule: < + TParams extends PermissionRuleParams = undefined, +>( rule: PermissionRule, ) => PermissionRule; @@ -448,37 +461,52 @@ export const permissionRules: { Entity, EntitiesSearchFilter, 'catalog-entity', - [annotation: string, value?: string | undefined] + { + value?: string | undefined; + annotation: string; + } >; hasLabel: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [label: string] + { + label: string; + } >; hasMetadata: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [key: string, value?: string | undefined] + { + value?: string | undefined; + key: string; + } >; hasSpec: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [key: string, value?: string | undefined] + { + value?: string | undefined; + key: string; + } >; isEntityKind: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [kinds: string[]] + { + kinds: string[]; + } >; isEntityOwner: PermissionRule< Entity, EntitiesSearchFilter, 'catalog-entity', - [claims: string[]] + { + claims: string[]; + } >; }; @@ -531,6 +559,14 @@ export type ProcessingIntervalFunction = () => number; export { processingResult }; +// @public (undocumented) +export type ScmLocationAnalyzer = { + supports(url: string): boolean; + analyze(options: AnalyzeOptions): Promise<{ + existing: AnalyzeLocationExistingEntity[]; + }>; +}; + // @public (undocumented) export class UrlReaderProcessor implements CatalogProcessor { constructor(options: { reader: UrlReader; logger: Logger }); diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index f834b0e2e8..d3e44d47b1 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend", "description": "The Backstage backend plugin that provides the Backstage catalog", - "version": "1.4.0", + "version": "1.5.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts index f6fc7fa5fa..3dc5325178 100644 --- a/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts +++ b/plugins/catalog-backend/src/database/DefaultProcessingDatabase.ts @@ -784,7 +784,10 @@ export class DefaultProcessingDatabase implements ProcessingDatabase { if (!oldRef) { // Add any entity that does not exist in the database toAdd.push(upsertItem); - } else if (oldRef.locationKey !== item.deferred.locationKey) { + } else if ( + (oldRef?.locationKey ?? undefined) !== + (item.deferred.locationKey ?? undefined) + ) { // Remove and then re-add any entity that exists, but with a different location key toRemove.push(item.ref); toAdd.push(upsertItem); diff --git a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts index b84dff7de8..761a0f8c0a 100644 --- a/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts +++ b/plugins/catalog-backend/src/ingestion/LocationAnalyzer.ts @@ -18,34 +18,32 @@ import { Logger } from 'winston'; import parseGitUrl from 'git-url-parse'; import { Entity } from '@backstage/catalog-model'; import { ScmIntegrationRegistry } from '@backstage/integration'; +import { LocationAnalyzer, ScmLocationAnalyzer } from './types'; import { AnalyzeLocationRequest, AnalyzeLocationResponse, - LocationAnalyzer, -} from './types'; +} from '@backstage/plugin-catalog-common'; export class RepoLocationAnalyzer implements LocationAnalyzer { private readonly logger: Logger; private readonly scmIntegrations: ScmIntegrationRegistry; + private readonly analyzers: ScmLocationAnalyzer[]; - constructor(logger: Logger, scmIntegrations: ScmIntegrationRegistry) { + constructor( + logger: Logger, + scmIntegrations: ScmIntegrationRegistry, + analyzers: ScmLocationAnalyzer[], + ) { this.logger = logger; this.scmIntegrations = scmIntegrations; + this.analyzers = analyzers; } async analyzeLocation( request: AnalyzeLocationRequest, ): Promise { - const { owner, name } = parseGitUrl(request.location.target); - const entity: Entity = { - apiVersion: 'backstage.io/v1alpha1', - kind: 'Component', - metadata: { - name: name, - }, - spec: { type: 'other', lifecycle: 'unknown' }, - }; - const integration = this.scmIntegrations.byUrl(request.location.target); + const { owner, name } = parseGitUrl(request.location.target); + let annotationPrefix; switch (integration?.type) { case 'azure': @@ -64,6 +62,33 @@ export class RepoLocationAnalyzer implements LocationAnalyzer { break; } + const analyzer = this.analyzers.find(a => + a.supports(request.location.target), + ); + if (analyzer) { + const analyzerResult = await analyzer.analyze({ + url: request.location.target, + }); + if (analyzerResult.existing.length > 0) { + this.logger.debug( + `entity for ${request.location.target} already exists.`, + ); + return { + existingEntityFiles: analyzerResult.existing, + generateEntities: [], + }; + } + } + + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: name, + }, + spec: { type: 'other', lifecycle: 'unknown' }, + }; + if (annotationPrefix) { entity.metadata.annotations = { [`${annotationPrefix}/project-slug`]: `${owner}/${name}`, diff --git a/plugins/catalog-backend/src/ingestion/index.ts b/plugins/catalog-backend/src/ingestion/index.ts index c97d029b5c..0b00809043 100644 --- a/plugins/catalog-backend/src/ingestion/index.ts +++ b/plugins/catalog-backend/src/ingestion/index.ts @@ -21,4 +21,6 @@ export type { AnalyzeLocationRequest, AnalyzeLocationResponse, LocationAnalyzer, + ScmLocationAnalyzer, + AnalyzeOptions, } from './types'; diff --git a/plugins/catalog-backend/src/ingestion/types.ts b/plugins/catalog-backend/src/ingestion/types.ts index 2602bd080f..78339b3b23 100644 --- a/plugins/catalog-backend/src/ingestion/types.ts +++ b/plugins/catalog-backend/src/ingestion/types.ts @@ -14,9 +14,57 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; -import { RecursivePartial } from '../util/RecursivePartial'; -import { LocationSpec } from '@backstage/plugin-catalog-node'; +import { + AnalyzeLocationRequest as NonDeprecatedAnalyzeLocationRequest, + AnalyzeLocationResponse as NonDeprecatedAnalyzeLocationResponse, + AnalyzeLocationExistingEntity as NonDeprecatedAnalyzeLocationExistingEntity, + AnalyzeLocationGenerateEntity as NonDeprecatedAnalyzeLocationGenerateEntity, + AnalyzeLocationEntityField as NonDeprecatedAnalyzeLocationEntityField, +} from '@backstage/plugin-catalog-common'; + +/** + * @public + * @deprecated use the same type from `@backstage/plugin-catalog-common` instead + */ +export type AnalyzeLocationRequest = NonDeprecatedAnalyzeLocationRequest; +/** + * @public + * @deprecated use the same type from `@backstage/plugin-catalog-common` instead + */ +export type AnalyzeLocationResponse = NonDeprecatedAnalyzeLocationResponse; + +/** + * If the folder pointed to already contained catalog info yaml files, they are + * read and emitted like this so that the frontend can inform the user that it + * located them and can make sure to register them as well if they weren't + * already + * @public + * @deprecated use the same type from `@backstage/plugin-catalog-common` instead + */ +export type AnalyzeLocationExistingEntity = + NonDeprecatedAnalyzeLocationExistingEntity; +/** + * This is some form of representation of what the analyzer could deduce. + * We should probably have a chat about how this can best be conveyed to + * the frontend. It'll probably contain a (possibly incomplete) entity, plus + * enough info for the frontend to know what form data to show to the user + * for overriding/completing the info. + * @public + * @deprecated use the same type from `@backstage/plugin-catalog-common` instead + */ +export type AnalyzeLocationGenerateEntity = + NonDeprecatedAnalyzeLocationGenerateEntity; + +/** + * + * This is where I get really vague. Something like this perhaps? Or it could be + * something like a json-schema that contains enough info for the frontend to + * be able to present a form and explanations + * @public + * @deprecated use the same type from `@backstage/plugin-catalog-common` instead + */ +export type AnalyzeLocationEntityField = + NonDeprecatedAnalyzeLocationEntityField; /** @public */ export type LocationAnalyzer = { @@ -30,71 +78,19 @@ export type LocationAnalyzer = { location: AnalyzeLocationRequest, ): Promise; }; - /** @public */ -export type AnalyzeLocationRequest = { - location: LocationSpec; +export type AnalyzeOptions = { + url: string; + catalogFilename?: string; }; /** @public */ -export type AnalyzeLocationResponse = { - existingEntityFiles: AnalyzeLocationExistingEntity[]; - generateEntities: AnalyzeLocationGenerateEntity[]; -}; - -/** - * If the folder pointed to already contained catalog info yaml files, they are - * read and emitted like this so that the frontend can inform the user that it - * located them and can make sure to register them as well if they weren't - * already - * @public - */ -export type AnalyzeLocationExistingEntity = { - location: LocationSpec; - isRegistered: boolean; - entity: Entity; -}; - -/** - * This is some form of representation of what the analyzer could deduce. - * We should probably have a chat about how this can best be conveyed to - * the frontend. It'll probably contain a (possibly incomplete) entity, plus - * enough info for the frontend to know what form data to show to the user - * for overriding/completing the info. - * @public - */ -export type AnalyzeLocationGenerateEntity = { - // Some form of partial representation of the entity - entity: RecursivePartial; - // Lists the suggestions that the user may want to override - fields: AnalyzeLocationEntityField[]; -}; - -// This is where I get really vague. Something like this perhaps? Or it could be -// something like a json-schema that contains enough info for the frontend to -// be able to present a form and explanations -/** @public */ -export type AnalyzeLocationEntityField = { - /** - * e.g. "spec.owner"? The frontend needs to know how to "inject" the field into the - * entity again if the user wants to change it - */ - field: string; - - /** The outcome of the analysis for this particular field */ - state: - | 'analysisSuggestedValue' - | 'analysisSuggestedNoValue' - | 'needsUserInput'; - - // If the analysis did suggest a value, this is where it would be. Not sure if we want - // to limit this to strings or if we want it to be any JsonValue - value: string | null; - /** - * A text to show to the user to inform about the choices made. Like, it could say - * "Found a CODEOWNERS file that covers this target, so we suggest leaving this - * field empty; which would currently make it owned by X" where X is taken from the - * codeowners file. - */ - description: string; +export type ScmLocationAnalyzer = { + /** The method that decides if this analyzer can work with the provided url */ + supports(url: string): boolean; + /** This function can return an array of already existing entities */ + analyze(options: AnalyzeOptions): Promise<{ + /** Existing entities in the analyzed location */ + existing: AnalyzeLocationExistingEntity[]; + }>; }; diff --git a/plugins/catalog-backend/src/integration.test.ts b/plugins/catalog-backend/src/integration.test.ts new file mode 100644 index 0000000000..dcfe9a1489 --- /dev/null +++ b/plugins/catalog-backend/src/integration.test.ts @@ -0,0 +1,677 @@ +/* + * 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 { Knex } from 'knex'; +import { Logger } from 'winston'; +import { ConfigReader } from '@backstage/config'; +import { JsonObject } from '@backstage/types'; +import { CatalogProcessingEngine, EntityProvider } from './index'; +import { DatabaseManager, getVoidLogger } from '@backstage/backend-common'; +import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +import { + Entity, + EntityPolicies, + stringifyEntityRef, +} from '@backstage/catalog-model'; +import { defaultEntityDataParser } from './modules/util/parse'; +import { DefaultCatalogProcessingOrchestrator } from './processing/DefaultCatalogProcessingOrchestrator'; +import { applyDatabaseMigrations } from './database/migrations'; +import { DefaultProcessingDatabase } from './database/DefaultProcessingDatabase'; +import { ScmIntegrations } from '@backstage/integration'; +import { DefaultCatalogRulesEnforcer } from './ingestion/CatalogRules'; +import { Stitcher } from './stitching/Stitcher'; +import { DefaultEntitiesCatalog } from './service/DefaultEntitiesCatalog'; +import { + DefaultCatalogProcessingEngine, + ProgressTracker, +} from './processing/DefaultCatalogProcessingEngine'; +import { createHash } from 'crypto'; +import { DefaultRefreshService } from './service/DefaultRefreshService'; +import { connectEntityProviders } from './processing/connectEntityProviders'; +import { EntitiesCatalog } from './catalog/types'; +import { RefreshOptions, RefreshService } from './service/types'; +import { + CatalogProcessorEmit, + EntityProviderConnection, + LocationSpec, + processingResult, +} from '@backstage/plugin-catalog-node'; +import { RefreshStateItem } from './database/types'; + +const voidLogger = getVoidLogger(); + +class TestProvider implements EntityProvider { + #connection?: EntityProviderConnection; + + getProviderName(): string { + return 'test'; + } + + async connect(connection: EntityProviderConnection): Promise { + this.#connection = connection; + } + + getConnection() { + if (!this.#connection) { + throw new Error('Provider is not connected yet'); + } + return this.#connection; + } +} + +class ProxyProgressTracker implements ProgressTracker { + #inner: ProgressTracker; + + constructor(inner: ProgressTracker) { + this.#inner = inner; + } + + processStart(item: RefreshStateItem) { + return this.#inner.processStart(item, voidLogger); + } + + setTracker(tracker: ProgressTracker) { + this.#inner = tracker; + } +} + +class NoopProgressTracker implements ProgressTracker { + static emptyTracking = { + markFailed() {}, + markProcessorsCompleted() {}, + markSuccessfulWithChanges() {}, + markSuccessfulWithErrors() {}, + markSuccessfulWithNoChanges() {}, + }; + + processStart() { + return NoopProgressTracker.emptyTracking; + } +} + +class WaitingProgressTracker implements ProgressTracker { + #resolve: (errors: Record) => void; + #promise: Promise>; + #counts = new Map(); + #errors = new Map(); + #inFlight = new Array>(); + + constructor(private readonly entityRefs?: Set) { + let resolve: (errors: Record) => void; + this.#promise = new Promise>(_resolve => { + resolve = _resolve; + }); + this.#resolve = resolve!; + } + + processStart(item: RefreshStateItem) { + if (this.entityRefs && !this.entityRefs.has(item.entityRef)) { + return NoopProgressTracker.emptyTracking; + } + + let resolve: () => void; + this.#inFlight.push( + new Promise(_resolve => { + resolve = _resolve; + }), + ); + + const currentCount = this.#counts.get(item.id) ?? 0; + this.#counts.set(item.id, currentCount); + + const onDone = () => { + this.#counts.set(item.id, currentCount + 1); + + if (Array.from(this.#counts.values()).every(c => c >= 2)) { + this.#resolve(Object.fromEntries(this.#errors)); + } + }; + return { + markFailed: (error: Error) => { + this.#errors.set(item.entityRef, error); + onDone(); + resolve(); + }, + markProcessorsCompleted() {}, + markSuccessfulWithChanges: () => { + this.#errors.delete(item.entityRef); + this.#counts.set(item.id, 0); + resolve(); + }, + markSuccessfulWithErrors: () => { + this.#errors.delete(item.entityRef); + onDone(); + resolve(); + }, + markSuccessfulWithNoChanges: () => { + onDone(); + resolve(); + }, + }; + } + + async wait(): Promise> { + return this.#promise; + } + + async waitForFinish(): Promise { + await Promise.all(this.#inFlight.slice()); + } +} + +class TestHarness { + readonly #catalog: EntitiesCatalog; + readonly #engine: CatalogProcessingEngine; + readonly #refresh: RefreshService; + readonly #provider: TestProvider; + readonly #proxyProgressTracker: ProxyProgressTracker; + + static async create(options?: { + config?: JsonObject; + logger?: Logger; + db?: Knex; + permissions?: PermissionEvaluator; + processEntity?( + entity: Entity, + location: LocationSpec, + emit: CatalogProcessorEmit, + ): Promise; + onProcessingError?(event: { + unprocessedEntity: Entity; + errors: Error[]; + }): void; + }) { + const config = new ConfigReader( + options?.config ?? { + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', + }, + }, + }, + ); + const logger = options?.logger ?? getVoidLogger(); + const db = + options?.db ?? + (await DatabaseManager.fromConfig(config, { logger }) + .forPlugin('catalog') + .getClient()); + + await applyDatabaseMigrations(db); + + const processingDatabase = new DefaultProcessingDatabase({ + database: db, + logger, + refreshInterval: () => 0.05, + }); + + const integrations = ScmIntegrations.fromConfig(config); + const rulesEnforcer = DefaultCatalogRulesEnforcer.fromConfig(config); + const orchestrator = new DefaultCatalogProcessingOrchestrator({ + processors: [ + { + getProcessorName: () => 'test', + async validateEntityKind() { + return true; + }, + async preProcessEntity( + entity: Entity, + location: LocationSpec, + emit: CatalogProcessorEmit, + ) { + if (options?.processEntity) { + return options?.processEntity(entity, location, emit); + } + return entity; + }, + }, + ], + integrations, + rulesEnforcer, + logger, + parser: defaultEntityDataParser, + policy: EntityPolicies.allOf([]), + }); + const stitcher = new Stitcher(db, logger); + const catalog = new DefaultEntitiesCatalog(db, stitcher); + const proxyProgressTracker = new ProxyProgressTracker( + new NoopProgressTracker(), + ); + + const engine = new DefaultCatalogProcessingEngine( + logger, + processingDatabase, + orchestrator, + stitcher, + () => createHash('sha1'), + 50, + event => { + if (options?.onProcessingError) { + options.onProcessingError(event); + } else { + throw new Error( + `Catalog processing error, ${event.errors.join(', ')}`, + ); + } + }, + proxyProgressTracker, + ); + + const refresh = new DefaultRefreshService({ database: processingDatabase }); + + const provider = new TestProvider(); + + await connectEntityProviders(processingDatabase, [provider]); + + return new TestHarness( + catalog, + engine, + refresh, + provider, + proxyProgressTracker, + ); + } + + constructor( + catalog: EntitiesCatalog, + engine: CatalogProcessingEngine, + refresh: RefreshService, + provider: TestProvider, + proxyProgressTracker: ProxyProgressTracker, + ) { + this.#catalog = catalog; + this.#engine = engine; + this.#refresh = refresh; + this.#provider = provider; + this.#proxyProgressTracker = proxyProgressTracker; + } + + async process(entityRefs?: Set) { + const tracker = new WaitingProgressTracker(entityRefs); + this.#proxyProgressTracker.setTracker(tracker); + + this.#engine.start(); + + const errors = await tracker.wait(); + + this.#engine.stop(); + await tracker.waitForFinish(); + + this.#proxyProgressTracker.setTracker(new NoopProgressTracker()); + + return errors; + } + + async setInputEntities(entities: (Entity & { locationKey?: string })[]) { + return this.#provider.getConnection().applyMutation({ + type: 'full', + entities: entities.map(({ locationKey, ...entity }) => ({ + entity, + locationKey, + })), + }); + } + + async getOutputEntities(): Promise> { + const { entities } = await this.#catalog.entities(); + return Object.fromEntries(entities.map(e => [stringifyEntityRef(e), e])); + } + + async refresh(options: RefreshOptions) { + return this.#refresh.refresh(options); + } +} + +describe('Catalog Backend Integration', () => { + it('should add entities and update errors', async () => { + let triggerError = false; + + const harness = await TestHarness.create({ + async processEntity(entity: Entity) { + if (triggerError) { + throw new Error('NOPE'); + } + return entity; + }, + }); + + await harness.setInputEntities([ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test', + annotations: { + 'backstage.io/managed-by-location': 'url:.', + 'backstage.io/managed-by-origin-location': 'url:.', + }, + }, + }, + ]); + + await expect(harness.getOutputEntities()).resolves.toEqual({}); + await expect(harness.process()).resolves.toEqual({}); + + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/test': { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: expect.objectContaining({ name: 'test' }), + relations: [], + }, + }); + + triggerError = true; + + await expect(harness.process()).resolves.toEqual({}); + + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/test': { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: expect.objectContaining({ name: 'test' }), + relations: [], + status: { + items: [ + { + level: 'error', + type: 'backstage.io/catalog-processing', + message: + 'InputError: Processor Object threw an error while preprocessing; caused by Error: NOPE', + error: { + name: 'InputError', + message: + 'Processor Object threw an error while preprocessing; caused by Error: NOPE', + cause: { + name: 'Error', + message: 'NOPE', + stack: expect.stringMatching(/^Error: NOPE/), + }, + }, + }, + ], + }, + }, + }); + }); + + it('should orphan entities', async () => { + const generatedApis = ['api-1', 'api-2']; + + const harness = await TestHarness.create({ + async processEntity( + entity: Entity, + location: LocationSpec, + emit: CatalogProcessorEmit, + ) { + if (entity.metadata.name === 'test') { + for (const api of generatedApis) { + emit( + processingResult.entity(location, { + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { + name: api, + annotations: { + 'backstage.io/managed-by-location': 'url:.', + 'backstage.io/managed-by-origin-location': 'url:.', + }, + }, + }), + ); + } + } + return entity; + }, + }); + + await harness.setInputEntities([ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'test', + annotations: { + 'backstage.io/managed-by-location': 'url:.', + 'backstage.io/managed-by-origin-location': 'url:.', + }, + }, + }, + ]); + + await expect(harness.getOutputEntities()).resolves.toEqual({}); + await expect(harness.process()).resolves.toEqual({}); + + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/test': { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: expect.objectContaining({ name: 'test' }), + relations: [], + }, + 'api:default/api-1': expect.objectContaining({ + metadata: expect.objectContaining({ name: 'api-1' }), + }), + 'api:default/api-2': expect.objectContaining({ + metadata: expect.objectContaining({ name: 'api-2' }), + }), + }); + + generatedApis.pop(); + + await expect(harness.process()).resolves.toEqual({}); + + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/test': { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: expect.objectContaining({ name: 'test' }), + relations: [], + }, + 'api:default/api-1': expect.objectContaining({ + metadata: expect.objectContaining({ name: 'api-1' }), + }), + 'api:default/api-2': expect.objectContaining({ + metadata: expect.objectContaining({ + name: 'api-2', + annotations: expect.objectContaining({ + 'backstage.io/orphan': 'true', + }), + }), + }), + }); + }); + + it('should not replace matching provided entities', async () => { + const harness = await TestHarness.create(); + + const entityA = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'a', + annotations: { + 'backstage.io/managed-by-location': 'url:.', + 'backstage.io/managed-by-origin-location': 'url:.', + }, + }, + }; + const entityB = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'b', + annotations: { + 'backstage.io/managed-by-location': 'url:.', + 'backstage.io/managed-by-origin-location': 'url:.', + }, + }, + }; + + const entities = [entityA, { locationKey: 'loc', ...entityB }]; + + await harness.setInputEntities(entities); + await expect(harness.process()).resolves.toEqual({}); + + const outputEntities = await harness.getOutputEntities(); + + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/a': expect.anything(), + 'component:default/b': expect.anything(), + }); + + await harness.setInputEntities(entities); + await expect(harness.process()).resolves.toEqual({}); + + await expect(harness.getOutputEntities()).resolves.toEqual(outputEntities); + }); + + // NOTE(freben): This test documents existing behavior, but it would be more correct to mark the cycle as orphans + it('leaves behind orphaned cycles without orphan markers', async () => { + function mkEntity(name: string) { + return { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name, + annotations: { + 'backstage.io/managed-by-location': 'url:.', + 'backstage.io/managed-by-origin-location': 'url:.', + }, + }, + }; + } + + const harness = await TestHarness.create({ + async processEntity( + entity: Entity, + location: LocationSpec, + emit: CatalogProcessorEmit, + ) { + if (entity.spec?.noEmit) { + return entity; + } + switch (entity.metadata.name) { + case 'a': + emit(processingResult.entity(location, mkEntity('b'))); + break; + case 'b': + emit(processingResult.entity(location, mkEntity('c'))); + break; + case 'c': + emit(processingResult.entity(location, mkEntity('d'))); + break; + case 'd': + emit(processingResult.entity(location, mkEntity('b'))); + break; + default: + } + return entity; + }, + }); + + await harness.setInputEntities([ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'a', + annotations: { + 'backstage.io/managed-by-location': 'url:.', + 'backstage.io/managed-by-origin-location': 'url:.', + }, + }, + }, + ]); + + await expect(harness.getOutputEntities()).resolves.toEqual({}); + await expect(harness.process()).resolves.toEqual({}); + + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/a': expect.objectContaining({ + metadata: expect.objectContaining({ name: 'a' }), + }), + 'component:default/b': expect.objectContaining({ + metadata: expect.objectContaining({ name: 'b' }), + }), + 'component:default/c': expect.objectContaining({ + metadata: expect.objectContaining({ name: 'c' }), + }), + 'component:default/d': expect.objectContaining({ + metadata: expect.objectContaining({ name: 'd' }), + }), + }); + // NOTE(freben): Avoid .toHaveProperty here, since it treats dots as path separators + expect( + (await harness.getOutputEntities())['component:default/b'].metadata + .annotations!['backstage.io/orphan'], + ).toBeUndefined(); + expect( + (await harness.getOutputEntities())['component:default/c'].metadata + .annotations!['backstage.io/orphan'], + ).toBeUndefined(); + expect( + (await harness.getOutputEntities())['component:default/d'].metadata + .annotations!['backstage.io/orphan'], + ).toBeUndefined(); + + await harness.setInputEntities([ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'a', + annotations: { + 'backstage.io/managed-by-location': 'url:.', + 'backstage.io/managed-by-origin-location': 'url:.', + }, + }, + spec: { noEmit: true }, + }, + ]); + + await expect(harness.process()).resolves.toEqual({}); + + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/a': expect.objectContaining({ + metadata: expect.objectContaining({ name: 'a' }), + }), + 'component:default/b': expect.objectContaining({ + metadata: expect.objectContaining({ name: 'b' }), + }), + 'component:default/c': expect.objectContaining({ + metadata: expect.objectContaining({ name: 'c' }), + }), + 'component:default/d': expect.objectContaining({ + metadata: expect.objectContaining({ name: 'd' }), + }), + }); + // TODO(freben): Ideally these should be orphaned now + expect( + (await harness.getOutputEntities())['component:default/b'].metadata + .annotations!['backstage.io/orphan'], + ).toBeUndefined(); + expect( + (await harness.getOutputEntities())['component:default/c'].metadata + .annotations!['backstage.io/orphan'], + ).toBeUndefined(); + expect( + (await harness.getOutputEntities())['component:default/d'].metadata + .annotations!['backstage.io/orphan'], + ).toBeUndefined(); + }); +}); diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts index cb11add9ed..5b34c98ca6 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.test.ts @@ -41,7 +41,9 @@ describe('createPropertyRule', () => { name: 'test-component', }, }, - 'org.name', + { + key: 'org.name', + }, ), ).toBe(false); }); @@ -57,7 +59,9 @@ describe('createPropertyRule', () => { tags: [], }, }, - 'tags', + { + key: 'tags', + }, ), ).toBe(false); }); @@ -75,7 +79,9 @@ describe('createPropertyRule', () => { }, }, }, - 'org.name', + { + key: 'org.name', + }, ), ).toBe(true); }); @@ -91,7 +97,9 @@ describe('createPropertyRule', () => { tags: ['java'], }, }, - 'tags', + { + key: 'tags', + }, ), ).toBe(true); }); @@ -108,8 +116,10 @@ describe('createPropertyRule', () => { name: 'test-component', }, }, - 'org.name', - 'test-org', + { + key: 'org.name', + value: 'test-org', + }, ), ).toBe(false); }); @@ -127,8 +137,10 @@ describe('createPropertyRule', () => { }, }, }, - 'org.name', - 'test-org', + { + key: 'org.name', + value: 'test-org', + }, ), ).toBe(false); }); @@ -144,8 +156,10 @@ describe('createPropertyRule', () => { tags: ['java'], }, }, - 'tags', - 'python', + { + key: 'tags', + value: 'python', + }, ), ).toBe(false); }); @@ -163,8 +177,10 @@ describe('createPropertyRule', () => { }, }, }, - 'org.name', - 'test-org', + { + key: 'org.name', + value: 'test-org', + }, ), ).toBe(true); }); @@ -180,8 +196,10 @@ describe('createPropertyRule', () => { tags: ['java', 'java11'], }, }, - 'tags', - 'java', + { + key: 'tags', + value: 'java', + }, ), ).toBe(true); }); @@ -190,7 +208,11 @@ describe('createPropertyRule', () => { describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(toQuery('backstage.io/test-component')).toEqual({ + expect( + toQuery({ + key: 'backstage.io/test-component', + }), + ).toEqual({ key: 'metadata.backstage.io/test-component', }); }); diff --git a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts index 57de5eb922..19a4423846 100644 --- a/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts +++ b/plugins/catalog-backend/src/permissions/rules/createPropertyRule.ts @@ -15,16 +15,25 @@ */ import { get } from 'lodash'; -import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { createCatalogPermissionRule } from './util'; +import { z } from 'zod'; export const createPropertyRule = (propertyType: 'metadata' | 'spec') => createCatalogPermissionRule({ name: `HAS_${propertyType.toUpperCase()}`, description: `Allow entities which have the specified ${propertyType} subfield.`, resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - apply: (resource: Entity, key: string, value?: string) => { + paramsSchema: z.object({ + key: z + .string() + .describe(`Property within the entities ${propertyType} to match on`), + value: z + .string() + .optional() + .describe(`Value of the given property to match on`), + }), + apply: (resource, { key, value }) => { const foundValue = get(resource[propertyType], key); if (Array.isArray(foundValue)) { @@ -38,7 +47,7 @@ export const createPropertyRule = (propertyType: 'metadata' | 'spec') => } return !!foundValue; }, - toQuery: (key: string, value?: string) => ({ + toQuery: ({ key, value }) => ({ key: `${propertyType}.${key}`, ...(value !== undefined && { values: [value] }), }), diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts index 609114be3d..73fce20a71 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.test.ts @@ -31,7 +31,9 @@ describe('hasAnnotation permission rule', () => { }, }, }, - 'backstage.io/test-annotation', + { + annotation: 'backstage.io/test-annotation', + }, ), ).toEqual(false); }); @@ -46,7 +48,9 @@ describe('hasAnnotation permission rule', () => { name: 'test-component', }, }, - 'backstage.io/test-annotation', + { + annotation: 'backstage.io/test-annotation', + }, ), ).toEqual(false); expect( @@ -58,8 +62,10 @@ describe('hasAnnotation permission rule', () => { name: 'test-component', }, }, - 'backstage.io/test-annotation', - 'some value', + { + annotation: 'backstage.io/test-annotation', + value: 'some value', + }, ), ).toEqual(false); }); @@ -78,7 +84,9 @@ describe('hasAnnotation permission rule', () => { }, }, }, - 'backstage.io/test-annotation', + { + annotation: 'backstage.io/test-annotation', + }, ), ).toEqual(true); }); @@ -97,8 +105,10 @@ describe('hasAnnotation permission rule', () => { }, }, }, - 'backstage.io/test-annotation', - 'baz', + { + annotation: 'backstage.io/test-annotation', + value: 'baz', + }, ), ).toEqual(false); }); @@ -117,8 +127,10 @@ describe('hasAnnotation permission rule', () => { }, }, }, - 'backstage.io/test-annotation', - 'bar', + { + annotation: 'backstage.io/test-annotation', + value: 'bar', + }, ), ).toEqual(true); }); @@ -126,7 +138,11 @@ describe('hasAnnotation permission rule', () => { describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(hasAnnotation.toQuery('backstage.io/test-annotation')).toEqual({ + expect( + hasAnnotation.toQuery({ + annotation: 'backstage.io/test-annotation', + }), + ).toEqual({ key: 'metadata.annotations.backstage.io/test-annotation', }); }); @@ -134,7 +150,10 @@ describe('hasAnnotation permission rule', () => { it('returns an appropriate catalog-backend filter with values', () => { expect( - hasAnnotation.toQuery('backstage.io/test-annotation', 'foo'), + hasAnnotation.toQuery({ + annotation: 'backstage.io/test-annotation', + value: 'foo', + }), ).toEqual({ key: 'metadata.annotations.backstage.io/test-annotation', values: ['foo'], diff --git a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts index 22dbd307a6..c24620f41c 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasAnnotation.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { z } from 'zod'; import { createCatalogPermissionRule } from './util'; /** @@ -31,12 +31,19 @@ export const hasAnnotation = createCatalogPermissionRule({ description: 'Allow entities which are annotated with the specified annotation', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - apply: (resource: Entity, annotation: string, value?: string) => + paramsSchema: z.object({ + annotation: z.string().describe('Name of the annotation to match on'), + value: z + .string() + .optional() + .describe('Value of the annotation to match on'), + }), + apply: (resource, { annotation, value }) => !!resource.metadata.annotations?.hasOwnProperty(annotation) && (value === undefined ? true : resource.metadata.annotations?.[annotation] === value), - toQuery: (annotation: string, value?: string) => + toQuery: ({ annotation, value }) => value === undefined ? { key: `metadata.annotations.${annotation}`, diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts index a1b9cb5ad0..8aa43402c5 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.test.ts @@ -31,7 +31,9 @@ describe('hasLabel permission rule', () => { }, }, }, - 'backstage.io/testlabel', + { + label: 'backstage.io/testlabel', + }, ), ).toEqual(false); }); @@ -46,7 +48,9 @@ describe('hasLabel permission rule', () => { name: 'test-component', }, }, - 'backstage.io/testlabel', + { + label: 'backstage.io/testlabel', + }, ), ).toEqual(false); }); @@ -65,7 +69,7 @@ describe('hasLabel permission rule', () => { }, }, }, - 'backstage.io/testlabel', + { label: 'backstage.io/testlabel' }, ), ).toEqual(true); }); @@ -73,7 +77,11 @@ describe('hasLabel permission rule', () => { describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(hasLabel.toQuery('backstage.io/testlabel')).toEqual({ + expect( + hasLabel.toQuery({ + label: 'backstage.io/testlabel', + }), + ).toEqual({ key: 'metadata.labels.backstage.io/testlabel', }); }); diff --git a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts index 9f37cbfb59..376534ca6f 100644 --- a/plugins/catalog-backend/src/permissions/rules/hasLabel.ts +++ b/plugins/catalog-backend/src/permissions/rules/hasLabel.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { z } from 'zod'; import { createCatalogPermissionRule } from './util'; /** @@ -27,9 +27,12 @@ export const hasLabel = createCatalogPermissionRule({ name: 'HAS_LABEL', description: 'Allow entities which have the specified label metadata.', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - apply: (resource: Entity, label: string) => + paramsSchema: z.object({ + label: z.string().describe('Name of the label to match one'), + }), + apply: (resource, { label }) => !!resource.metadata.labels?.hasOwnProperty(label), - toQuery: (label: string) => ({ + toQuery: ({ label }) => ({ key: `metadata.labels.${label}`, }), }); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts index e11e102728..28983f6bec 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.test.ts @@ -27,7 +27,11 @@ describe('isEntityKind', () => { name: 'some-component', }, }; - expect(isEntityKind.apply(component, ['b'])).toBe(true); + expect( + isEntityKind.apply(component, { + kinds: ['b'], + }), + ).toBe(true); }); it('returns false when entity is not the correct kind', () => { @@ -38,13 +42,21 @@ describe('isEntityKind', () => { name: 'some-component', }, }; - expect(isEntityKind.apply(component, ['c'])).toBe(false); + expect( + isEntityKind.apply(component, { + kinds: ['c'], + }), + ).toBe(false); }); }); describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(isEntityKind.toQuery(['b'])).toEqual({ + expect( + isEntityKind.toQuery({ + kinds: ['b'], + }), + ).toEqual({ key: 'kind', values: ['b'], }); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts index 3bd600e8fe..eee7a790b2 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityKind.ts @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { z } from 'zod'; import { EntitiesSearchFilter } from '../../catalog/types'; import { createCatalogPermissionRule } from './util'; @@ -27,11 +27,16 @@ export const isEntityKind = createCatalogPermissionRule({ name: 'IS_ENTITY_KIND', description: 'Allow entities with the specified kind', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - apply(resource: Entity, kinds: string[]) { + paramsSchema: z.object({ + kinds: z + .array(z.string()) + .describe('List of kinds to match at least one of'), + }), + apply(resource, { kinds }) { const resourceKind = resource.kind.toLocaleLowerCase('en-US'); return kinds.some(kind => kind.toLocaleLowerCase('en-US') === resourceKind); }, - toQuery(kinds: string[]): EntitiesSearchFilter { + toQuery({ kinds }): EntitiesSearchFilter { return { key: 'kind', values: kinds.map(kind => kind.toLocaleLowerCase('en-US')), diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts index 11304c768c..f09e484c32 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.test.ts @@ -33,9 +33,11 @@ describe('isEntityOwner', () => { }, ], }; - expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe( - true, - ); + expect( + isEntityOwner.apply(component, { + claims: ['user:default/spiderman'], + }), + ).toBe(true); }); it('returns false when entity is not owned by the given user', () => { @@ -52,9 +54,11 @@ describe('isEntityOwner', () => { }, ], }; - expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe( - false, - ); + expect( + isEntityOwner.apply(component, { + claims: ['user:default/spiderman'], + }), + ).toBe(false); }); it('returns false when entity does not have an owner', () => { @@ -65,15 +69,21 @@ describe('isEntityOwner', () => { name: 'some-component', }, }; - expect(isEntityOwner.apply(component, ['user:default/spiderman'])).toBe( - false, - ); + expect( + isEntityOwner.apply(component, { + claims: ['user:default/spiderman'], + }), + ).toBe(false); }); }); describe('toQuery', () => { it('returns an appropriate catalog-backend filter', () => { - expect(isEntityOwner.toQuery(['user:default/spiderman'])).toEqual({ + expect( + isEntityOwner.toQuery({ + claims: ['user:default/spiderman'], + }), + ).toEqual({ key: 'relations.ownedBy', values: ['user:default/spiderman'], }); diff --git a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts index 5403da2310..c3bfff5c2e 100644 --- a/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts +++ b/plugins/catalog-backend/src/permissions/rules/isEntityOwner.ts @@ -14,8 +14,9 @@ * limitations under the License. */ -import { Entity, RELATION_OWNED_BY } from '@backstage/catalog-model'; +import { RELATION_OWNED_BY } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { z } from 'zod'; import { createCatalogPermissionRule } from './util'; /** @@ -28,7 +29,14 @@ export const isEntityOwner = createCatalogPermissionRule({ name: 'IS_ENTITY_OWNER', description: 'Allow entities owned by the current user', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, - apply: (resource: Entity, claims: string[]) => { + paramsSchema: z.object({ + claims: z + .array(z.string()) + .describe( + `List of claims to match at least one on within ${RELATION_OWNED_BY}`, + ), + }), + apply: (resource, { claims }) => { if (!resource.relations) { return false; } @@ -37,7 +45,7 @@ export const isEntityOwner = createCatalogPermissionRule({ .filter(relation => relation.type === RELATION_OWNED_BY) .some(relation => claims.includes(relation.targetRef)); }, - toQuery: (claims: string[]) => ({ + toQuery: ({ claims }) => ({ key: 'relations.ownedBy', values: claims, }), diff --git a/plugins/catalog-backend/src/permissions/rules/util.ts b/plugins/catalog-backend/src/permissions/rules/util.ts index 36b351595d..ea25e115d2 100644 --- a/plugins/catalog-backend/src/permissions/rules/util.ts +++ b/plugins/catalog-backend/src/permissions/rules/util.ts @@ -16,6 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { makeCreatePermissionRule, PermissionRule, @@ -29,8 +30,9 @@ import { EntitiesSearchFilter } from '../../catalog/types'; * * @alpha */ -export type CatalogPermissionRule = - PermissionRule; +export type CatalogPermissionRule< + TParams extends PermissionRuleParams = PermissionRuleParams, +> = PermissionRule; /** * Helper function for creating correctly-typed diff --git a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts index fb9518c783..2cc0e2dc40 100644 --- a/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts +++ b/plugins/catalog-backend/src/processing/DefaultCatalogProcessingEngine.ts @@ -31,8 +31,9 @@ import { startTaskPipeline } from './TaskPipeline'; const CACHE_TTL = 5; +export type ProgressTracker = ReturnType; + export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { - private readonly tracker = progressTracker(); private stopFunc?: () => void; constructor( @@ -46,6 +47,7 @@ export class DefaultCatalogProcessingEngine implements CatalogProcessingEngine { unprocessedEntity: Entity; errors: Error[]; }) => Promise | void, + private readonly tracker: ProgressTracker = progressTracker(), ) {} async start() { diff --git a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts index b15d1df4bb..66764dc89f 100644 --- a/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/AuthorizedEntitiesCatalog.test.ts @@ -65,7 +65,7 @@ describe('AuthorizedEntitiesCatalog', () => { fakePermissionApi.authorizeConditional.mockResolvedValue([ { result: AuthorizeResult.CONDITIONAL, - conditions: { rule: 'IS_ENTITY_KIND', params: [['b']] }, + conditions: { rule: 'IS_ENTITY_KIND', params: { kinds: ['b'] } }, }, ]); const catalog = createCatalog(isEntityKind); @@ -117,7 +117,7 @@ describe('AuthorizedEntitiesCatalog', () => { fakePermissionApi.authorizeConditional.mockResolvedValue([ { result: AuthorizeResult.CONDITIONAL, - conditions: { rule: 'IS_ENTITY_KIND', params: [['b']] }, + conditions: { rule: 'IS_ENTITY_KIND', params: { kinds: ['b'] } }, }, ]); fakeCatalog.entities.mockResolvedValue({ entities: [] }); @@ -136,7 +136,7 @@ describe('AuthorizedEntitiesCatalog', () => { fakePermissionApi.authorizeConditional.mockResolvedValue([ { result: AuthorizeResult.CONDITIONAL, - conditions: { rule: 'IS_ENTITY_KIND', params: [['b']] }, + conditions: { rule: 'IS_ENTITY_KIND', params: { kinds: ['b'] } }, }, ]); fakeCatalog.entities.mockResolvedValue({ @@ -272,7 +272,7 @@ describe('AuthorizedEntitiesCatalog', () => { fakePermissionApi.authorizeConditional.mockResolvedValue([ { result: AuthorizeResult.CONDITIONAL, - conditions: { rule: 'IS_ENTITY_KIND', params: [['b']] }, + conditions: { rule: 'IS_ENTITY_KIND', params: { kinds: ['b'] } }, }, ]); const catalog = createCatalog(isEntityKind); diff --git a/plugins/catalog-backend/src/service/CatalogBuilder.ts b/plugins/catalog-backend/src/service/CatalogBuilder.ts index 25f9fde313..d5bc7057dd 100644 --- a/plugins/catalog-backend/src/service/CatalogBuilder.ts +++ b/plugins/catalog-backend/src/service/CatalogBuilder.ts @@ -56,7 +56,7 @@ import { yamlPlaceholderResolver, } from '../modules/core/PlaceholderProcessor'; import { defaultEntityDataParser } from '../modules/util/parse'; -import { LocationAnalyzer } from '../ingestion/types'; +import { LocationAnalyzer, ScmLocationAnalyzer } from '../ingestion/types'; import { CatalogProcessingEngine } from '../processing'; import { DefaultProcessingDatabase } from '../database/DefaultProcessingDatabase'; import { applyDatabaseMigrations } from '../database/migrations'; @@ -115,6 +115,10 @@ export type CatalogEnvironment = { * after the processors' pre-processing steps. All policies are given the * chance to inspect the entity, and all of them have to pass in order for * the entity to be considered valid from an overall point of view. + * - Location analyzers can be added. These are responsible for analyzing + * repositories when onboarding them into the catalog, by finding + * catalog-info.yaml files and other artifacts that can help automatically + * register or create catalog data on the user's behalf. * - Placeholder resolvers can be replaced or added. These run on the raw * structured data between the parsing and pre-processing steps, to replace * dollar-prefixed entries with their actual values (like $file). @@ -135,6 +139,7 @@ export class CatalogBuilder { private fieldFormatValidators: Partial; private entityProviders: EntityProvider[]; private processors: CatalogProcessor[]; + private locationAnalyzers: ScmLocationAnalyzer[]; private processorsReplace: boolean; private parser: CatalogProcessorParser | undefined; private onProcessingError?: (event: { @@ -165,6 +170,7 @@ export class CatalogBuilder { this.fieldFormatValidators = {}; this.entityProviders = []; this.processors = []; + this.locationAnalyzers = []; this.processorsReplace = false; this.parser = undefined; this.permissionRules = Object.values(catalogPermissionRules); @@ -335,6 +341,21 @@ export class CatalogBuilder { ]; } + /** + * Adds Location Analyzers. These are responsible for analyzing + * repositories when onboarding them into the catalog, by finding + * catalog-info.yaml files and other artifacts that can help automatically + * register or create catalog data on the user's behalf. + * + * @param locationAnalyzers - One or more location analyzers + */ + addLocationAnalyzers( + ...analyzers: Array> + ): CatalogBuilder { + this.locationAnalyzers.push(...analyzers.flat()); + return this; + } + /** * Sets up the catalog to use a custom parser for entity data. * @@ -409,7 +430,11 @@ export class CatalogBuilder { parser, policy, }); - const unauthorizedEntitiesCatalog = new DefaultEntitiesCatalog(dbClient); + const stitcher = new Stitcher(dbClient, logger); + const unauthorizedEntitiesCatalog = new DefaultEntitiesCatalog( + dbClient, + stitcher, + ); let permissionEvaluator: PermissionEvaluator; if ('authorizeConditional' in permissions) { @@ -453,7 +478,6 @@ export class CatalogBuilder { permissions: catalogPermissions, rules: this.permissionRules, }); - const stitcher = new Stitcher(dbClient, logger); const locationStore = new DefaultLocationStore(dbClient); const configLocationProvider = new ConfigLocationEntityProvider(config); @@ -475,7 +499,8 @@ export class CatalogBuilder { ); const locationAnalyzer = - this.locationAnalyzer ?? new RepoLocationAnalyzer(logger, integrations); + this.locationAnalyzer ?? + new RepoLocationAnalyzer(logger, integrations, this.locationAnalyzers); const locationService = new AuthorizedLocationService( new DefaultLocationService(locationStore, orchestrator, { allowedLocationTypes: this.allowedLocationType, diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts index 43e208197f..49d4fe9e19 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.test.ts @@ -25,12 +25,15 @@ import { DbRefreshStateRow, DbSearchRow, } from '../database/tables'; +import { Stitcher } from '../stitching/Stitcher'; import { DefaultEntitiesCatalog } from './DefaultEntitiesCatalog'; describe('DefaultEntitiesCatalog', () => { const databases = TestDatabases.create({ ids: ['POSTGRES_13', 'POSTGRES_9', 'SQLITE_3'], }); + const stitch = jest.fn(); + const stitcher: Stitcher = { stitch } as any; async function createDatabase(databaseId: TestDatabaseId) { const knex = await databases.init(databaseId); @@ -122,6 +125,10 @@ describe('DefaultEntitiesCatalog', () => { ); } + afterEach(() => { + jest.resetAllMocks(); + }); + describe('entityAncestry', () => { it.each(databases.eachSupportedId())( 'should return the ancestry with one parent, %p', @@ -151,7 +158,7 @@ describe('DefaultEntitiesCatalog', () => { await addEntity(knex, parent, [{ entity: grandparent }]); await addEntity(knex, root, [{ entity: parent }]); - const catalog = new DefaultEntitiesCatalog(knex); + const catalog = new DefaultEntitiesCatalog(knex, stitcher); const result = await catalog.entityAncestry('k:default/root'); expect(result.rootEntityRef).toEqual('k:default/root'); @@ -181,7 +188,7 @@ describe('DefaultEntitiesCatalog', () => { 'should throw error if the entity does not exist, %p', async databaseId => { const { knex } = await createDatabase(databaseId); - const catalog = new DefaultEntitiesCatalog(knex); + const catalog = new DefaultEntitiesCatalog(knex, stitcher); await expect(() => catalog.entityAncestry('k:default/root'), ).rejects.toThrow('No such entity k:default/root'); @@ -224,7 +231,7 @@ describe('DefaultEntitiesCatalog', () => { await addEntity(knex, parent2, [{ entity: grandparent }]); await addEntity(knex, root, [{ entity: parent1 }, { entity: parent2 }]); - const catalog = new DefaultEntitiesCatalog(knex); + const catalog = new DefaultEntitiesCatalog(knex, stitcher); const result = await catalog.entityAncestry('k:default/root'); expect(result.rootEntityRef).toEqual('k:default/root'); @@ -280,7 +287,7 @@ describe('DefaultEntitiesCatalog', () => { }; await addEntityToSearch(knex, entity1); await addEntityToSearch(knex, entity2); - const catalog = new DefaultEntitiesCatalog(knex); + const catalog = new DefaultEntitiesCatalog(knex, stitcher); const testFilter = { key: 'spec.test', @@ -313,7 +320,7 @@ describe('DefaultEntitiesCatalog', () => { }; await addEntityToSearch(knex, entity1); await addEntityToSearch(knex, entity2); - const catalog = new DefaultEntitiesCatalog(knex); + const catalog = new DefaultEntitiesCatalog(knex, stitcher); const testFilter = { not: { @@ -360,7 +367,7 @@ describe('DefaultEntitiesCatalog', () => { await addEntityToSearch(knex, entity2); await addEntityToSearch(knex, entity3); await addEntityToSearch(knex, entity4); - const catalog = new DefaultEntitiesCatalog(knex); + const catalog = new DefaultEntitiesCatalog(knex, stitcher); const testFilter1 = { key: 'metadata.org', @@ -415,7 +422,7 @@ describe('DefaultEntitiesCatalog', () => { }; await addEntityToSearch(knex, entity1); await addEntityToSearch(knex, entity2); - const catalog = new DefaultEntitiesCatalog(knex); + const catalog = new DefaultEntitiesCatalog(knex, stitcher); const testFilter1 = { key: 'metadata.org', @@ -457,7 +464,7 @@ describe('DefaultEntitiesCatalog', () => { }; await addEntityToSearch(knex, entity1); await addEntityToSearch(knex, entity2); - const catalog = new DefaultEntitiesCatalog(knex); + const catalog = new DefaultEntitiesCatalog(knex, stitcher); const testFilter = { key: 'kind', @@ -501,7 +508,7 @@ describe('DefaultEntitiesCatalog', () => { }, [], ); - const catalog = new DefaultEntitiesCatalog(knex); + const catalog = new DefaultEntitiesCatalog(knex, stitcher); const { entities } = await catalog.entities(); @@ -557,10 +564,16 @@ describe('DefaultEntitiesCatalog', () => { metadata: { name: 'root' }, spec: {}, }; - const unrelated: Entity = { + const unrelated1: Entity = { apiVersion: 'a', kind: 'k', - metadata: { name: 'unrelated' }, + metadata: { name: 'unrelated1' }, + spec: {}, + }; + const unrelated2: Entity = { + apiVersion: 'a', + kind: 'k', + metadata: { name: 'unrelated2' }, spec: {}, }; @@ -571,10 +584,23 @@ describe('DefaultEntitiesCatalog', () => { { entity: parent1 }, { entity: parent2 }, ]); - await addEntity(knex, unrelated, []); + await addEntity(knex, unrelated1, []); + await addEntity(knex, unrelated2, []); await knex('refresh_state').update({ result_hash: 'not-changed' }); + await knex('relations').insert({ + originating_entity_id: uid, + type: 't', + source_entity_ref: 'k:default/root', + target_entity_ref: 'k:default/unrelated1', + }); + await knex('relations').insert({ + originating_entity_id: uid, + type: 't', + source_entity_ref: 'k:default/unrelated2', + target_entity_ref: 'k:default/root', + }); - const catalog = new DefaultEntitiesCatalog(knex); + const catalog = new DefaultEntitiesCatalog(knex, stitcher); await catalog.removeEntityByUid(uid); await expect( @@ -586,8 +612,12 @@ describe('DefaultEntitiesCatalog', () => { { entity_ref: 'k:default/grandparent', result_hash: 'not-changed' }, { entity_ref: 'k:default/parent1', result_hash: 'child-was-deleted' }, { entity_ref: 'k:default/parent2', result_hash: 'child-was-deleted' }, - { entity_ref: 'k:default/unrelated', result_hash: 'not-changed' }, + { entity_ref: 'k:default/unrelated1', result_hash: 'not-changed' }, + { entity_ref: 'k:default/unrelated2', result_hash: 'not-changed' }, ]); + expect(stitch).toHaveBeenCalledWith( + new Set(['k:default/unrelated1', 'k:default/unrelated2']), + ); }, ); }); @@ -616,7 +646,7 @@ describe('DefaultEntitiesCatalog', () => { metadata: { name: 'two' }, spec: {}, }); - const catalog = new DefaultEntitiesCatalog(knex); + const catalog = new DefaultEntitiesCatalog(knex, stitcher); await expect(catalog.facets({ facets: ['kind'] })).resolves.toEqual({ facets: { @@ -654,7 +684,7 @@ describe('DefaultEntitiesCatalog', () => { }, spec: {}, }); - const catalog = new DefaultEntitiesCatalog(knex); + const catalog = new DefaultEntitiesCatalog(knex, stitcher); await expect( catalog.facets({ @@ -698,7 +728,7 @@ describe('DefaultEntitiesCatalog', () => { }, spec: {}, }); - const catalog = new DefaultEntitiesCatalog(knex); + const catalog = new DefaultEntitiesCatalog(knex, stitcher); await expect( catalog.facets({ diff --git a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts index fa11903447..dc84b12484 100644 --- a/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/service/DefaultEntitiesCatalog.ts @@ -38,8 +38,10 @@ import { DbPageInfo, DbRefreshStateReferencesRow, DbRefreshStateRow, + DbRelationsRow, DbSearchRow, } from '../database/tables'; +import { Stitcher } from '../stitching/Stitcher'; function parsePagination(input?: EntityPagination): { limit?: number; @@ -158,7 +160,10 @@ function parseFilter( } export class DefaultEntitiesCatalog implements EntitiesCatalog { - constructor(private readonly database: Knex) {} + constructor( + private readonly database: Knex, + private readonly stitcher: Stitcher, + ) {} async entities(request?: EntitiesRequest): Promise { const db = this.database; @@ -244,6 +249,7 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { await this.database('refresh_state') .update({ result_hash: 'child-was-deleted', + next_update_at: this.database.fn.now(), }) .whereIn('entity_ref', function parents(builder) { return builder @@ -256,9 +262,35 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog { .select('refresh_state_references.source_entity_ref'); }); + // Stitch the entities that the deleted one had relations to. If we do not + // do this, the entities in the other end of the relations will still look + // like they have a relation to the entity that was deleted, despite not + // having any corresponding rows in the relations table. + const relationPeers = await this.database + .from('relations') + .innerJoin('refresh_state', { + 'refresh_state.entity_ref': 'relations.target_entity_ref', + }) + .where('relations.originating_entity_id', '=', uid) + .andWhere('refresh_state.entity_id', '!=', uid) + .select({ ref: 'relations.target_entity_ref' }) + .union(other => + other + .from('relations') + .innerJoin('refresh_state', { + 'refresh_state.entity_ref': 'relations.source_entity_ref', + }) + .where('relations.originating_entity_id', '=', uid) + .andWhere('refresh_state.entity_id', '!=', uid) + .select({ ref: 'relations.source_entity_ref' }), + ); + + // Perform the actual deletion await this.database('refresh_state') .where('entity_id', uid) .delete(); + + await this.stitcher.stitch(new Set(relationPeers.map(p => p.ref))); } async entityAncestry(rootRef: string): Promise { diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 16a36eb8c4..f383df7364 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -36,6 +36,7 @@ import { } from '@backstage/plugin-permission-node'; import { RESOURCE_TYPE_CATALOG_ENTITY } from '@backstage/plugin-catalog-common'; import { CatalogProcessingOrchestrator } from '../processing/types'; +import { z } from 'zod'; describe('createRouter readonly disabled', () => { let entitiesCatalog: jest.Mocked; @@ -695,6 +696,9 @@ describe('NextRouter permissioning', () => { name: 'FAKE_RULE', description: 'fake rule', resourceType: RESOURCE_TYPE_CATALOG_ENTITY, + paramsSchema: z.object({ + foo: z.string(), + }), apply: () => true, toQuery: () => ({ key: '', values: [] }), }); @@ -758,7 +762,9 @@ describe('NextRouter permissioning', () => { conditions: { rule: 'FAKE_RULE', resourceType: 'catalog-entity', - params: ['user:default/spiderman'], + params: { + foo: 'user:default/spiderman', + }, }, }, ], diff --git a/plugins/catalog-backend/src/service/createRouter.ts b/plugins/catalog-backend/src/service/createRouter.ts index 639893dcc1..5267519c33 100644 --- a/plugins/catalog-backend/src/service/createRouter.ts +++ b/plugins/catalog-backend/src/service/createRouter.ts @@ -98,7 +98,7 @@ export async function createRouter( ); await refreshService.refresh(refreshOptions); - res.status(200).send(); + res.status(200).end(); }); } @@ -229,9 +229,15 @@ export async function createRouter( router.post('/analyze-location', async (req, res) => { const body = await validateRequestBody( req, - z.object({ location: locationInput }), + z.object({ + location: locationInput, + catalogFilename: z.string().optional(), + }), ); - const schema = z.object({ location: locationInput }); + const schema = z.object({ + location: locationInput, + catalogFilename: z.string().optional(), + }); const output = await locationAnalyzer.analyzeLocation(schema.parse(body)); res.status(200).json(output); }); diff --git a/plugins/catalog-common/CHANGELOG.md b/plugins/catalog-common/CHANGELOG.md index 57600eb6a5..2ecbca904f 100644 --- a/plugins/catalog-common/CHANGELOG.md +++ b/plugins/catalog-common/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-catalog-common +## 1.0.7 + +### Patch Changes + +- 823acaa88b: Moved the following types from `@backstage/plugin-catalog-backend` to this package. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-common@1.1.0 + +## 1.0.7-next.2 + +### Patch Changes + +- 823acaa88b: Moved the following types from `@backstage/plugin-catalog-backend` to this package. + + - AnalyzeLocationResponse + - AnalyzeLocationRequest + - AnalyzeLocationExistingEntity + - AnalyzeLocationGenerateEntity + - AnalyzeLocationEntityField + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + +## 1.0.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## 1.0.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 1.0.6 ### Patch Changes diff --git a/plugins/catalog-common/api-report.md b/plugins/catalog-common/api-report.md index b7eaf99d8b..1bef626c79 100644 --- a/plugins/catalog-common/api-report.md +++ b/plugins/catalog-common/api-report.md @@ -4,9 +4,46 @@ ```ts import { BasicPermission } from '@backstage/plugin-permission-common'; +import { Entity } from '@backstage/catalog-model'; import { IndexableDocument } from '@backstage/plugin-search-common'; import { ResourcePermission } from '@backstage/plugin-permission-common'; +// @public (undocumented) +export type AnalyzeLocationEntityField = { + field: string; + state: + | 'analysisSuggestedValue' + | 'analysisSuggestedNoValue' + | 'needsUserInput'; + value: string | null; + description: string; +}; + +// @public +export type AnalyzeLocationExistingEntity = { + location: LocationSpec; + isRegistered: boolean; + entity: Entity; +}; + +// @public +export type AnalyzeLocationGenerateEntity = { + entity: RecursivePartial; + fields: AnalyzeLocationEntityField[]; +}; + +// @public (undocumented) +export type AnalyzeLocationRequest = { + location: LocationSpec; + catalogFilename?: string; +}; + +// @public (undocumented) +export type AnalyzeLocationResponse = { + existingEntityFiles: AnalyzeLocationExistingEntity[]; + generateEntities: AnalyzeLocationGenerateEntity[]; +}; + // @alpha export const catalogEntityCreatePermission: BasicPermission; @@ -55,6 +92,13 @@ export const catalogPermissions: ( | ResourcePermission<'catalog-entity'> )[]; +// @public +export type LocationSpec = { + type: string; + target: string; + presence?: 'optional' | 'required'; +}; + // @alpha export const RESOURCE_TYPE_CATALOG_ENTITY = 'catalog-entity'; ``` diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 357636341a..b700002f9c 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-common", "description": "Common functionalities for the catalog plugin", - "version": "1.0.6", + "version": "1.0.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,6 +33,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { + "@backstage/catalog-model": "workspace:^", "@backstage/plugin-permission-common": "workspace:^", "@backstage/plugin-search-common": "workspace:^" }, diff --git a/plugins/catalog-common/src/common.ts b/plugins/catalog-common/src/common.ts new file mode 100644 index 0000000000..c926a94b28 --- /dev/null +++ b/plugins/catalog-common/src/common.ts @@ -0,0 +1,32 @@ +/* + * 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. + */ + +/** + * Holds the entity location information. + * + * @remarks + * + * `presence` flag: when using repo importer plugin, location is being created before the component yaml file is merged to the main branch. + * This flag is then set to indicate that the file can be not present. + * default value: 'required'. + * + * @public + */ +export type LocationSpec = { + type: string; + target: string; + presence?: 'optional' | 'required'; +}; diff --git a/plugins/catalog-common/src/index.ts b/plugins/catalog-common/src/index.ts index 614948e56c..95bd65bc92 100644 --- a/plugins/catalog-common/src/index.ts +++ b/plugins/catalog-common/src/index.ts @@ -35,3 +35,5 @@ export { export type { CatalogEntityPermission } from './permissions'; export * from './search'; +export * from './ingestion'; +export type { LocationSpec } from './common'; diff --git a/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts b/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts new file mode 100644 index 0000000000..08ae5ef0cb --- /dev/null +++ b/plugins/catalog-common/src/ingestion/LocationAnalyzer.ts @@ -0,0 +1,88 @@ +/* + * 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 { LocationSpec } from '../common'; +import { Entity } from '@backstage/catalog-model'; +import { RecursivePartial } from './RecursivePartial'; + +/** @public */ +export type AnalyzeLocationRequest = { + location: LocationSpec; + catalogFilename?: string; +}; + +/** @public */ +export type AnalyzeLocationResponse = { + existingEntityFiles: AnalyzeLocationExistingEntity[]; + generateEntities: AnalyzeLocationGenerateEntity[]; +}; + +/** + * If the folder pointed to already contained catalog info yaml files, they are + * read and emitted like this so that the frontend can inform the user that it + * located them and can make sure to register them as well if they weren't + * already + * @public + */ +export type AnalyzeLocationExistingEntity = { + location: LocationSpec; + isRegistered: boolean; + entity: Entity; +}; + +/** + * This is some form of representation of what the analyzer could deduce. + * We should probably have a chat about how this can best be conveyed to + * the frontend. It'll probably contain a (possibly incomplete) entity, plus + * enough info for the frontend to know what form data to show to the user + * for overriding/completing the info. + * @public + */ +export type AnalyzeLocationGenerateEntity = { + // Some form of partial representation of the entity + entity: RecursivePartial; + // Lists the suggestions that the user may want to override + fields: AnalyzeLocationEntityField[]; +}; + +// This is where I get really vague. Something like this perhaps? Or it could be +// something like a json-schema that contains enough info for the frontend to +// be able to present a form and explanations +/** @public */ +export type AnalyzeLocationEntityField = { + /** + * e.g. "spec.owner"? The frontend needs to know how to "inject" the field into the + * entity again if the user wants to change it + */ + field: string; + + /** The outcome of the analysis for this particular field */ + state: + | 'analysisSuggestedValue' + | 'analysisSuggestedNoValue' + | 'needsUserInput'; + + // If the analysis did suggest a value, this is where it would be. Not sure if we want + // to limit this to strings or if we want it to be any JsonValue + value: string | null; + /** + * A text to show to the user to inform about the choices made. Like, it could say + * "Found a CODEOWNERS file that covers this target, so we suggest leaving this + * field empty; which would currently make it owned by X" where X is taken from the + * codeowners file. + */ + description: string; +}; diff --git a/plugins/catalog-common/src/ingestion/RecursivePartial.test.ts b/plugins/catalog-common/src/ingestion/RecursivePartial.test.ts new file mode 100644 index 0000000000..ab8d50534e --- /dev/null +++ b/plugins/catalog-common/src/ingestion/RecursivePartial.test.ts @@ -0,0 +1,31 @@ +/* + * Copyright 2020 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 { RecursivePartial } from './RecursivePartial'; + +describe('RecursivePartial', () => { + it('is recursive', () => { + type X = { + required: { + required: string; + }; + }; + const x: RecursivePartial = { + required: {}, + }; + expect(x).toEqual({ required: {} }); + }); +}); diff --git a/plugins/catalog-common/src/ingestion/RecursivePartial.ts b/plugins/catalog-common/src/ingestion/RecursivePartial.ts new file mode 100644 index 0000000000..c452836f34 --- /dev/null +++ b/plugins/catalog-common/src/ingestion/RecursivePartial.ts @@ -0,0 +1,27 @@ +/* + * Copyright 2020 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. + */ + +/** + * Makes all keys of an entire hierarchy optional. + * @ignore + */ +export type RecursivePartial = { + [P in keyof T]?: T[P] extends (infer U)[] + ? RecursivePartial[] + : T[P] extends object + ? RecursivePartial + : T[P]; +}; diff --git a/plugins/catalog-common/src/ingestion/index.ts b/plugins/catalog-common/src/ingestion/index.ts new file mode 100644 index 0000000000..aced124bb4 --- /dev/null +++ b/plugins/catalog-common/src/ingestion/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 { + AnalyzeLocationResponse, + AnalyzeLocationRequest, + AnalyzeLocationExistingEntity, + AnalyzeLocationGenerateEntity, + AnalyzeLocationEntityField, +} from './LocationAnalyzer'; diff --git a/plugins/catalog-customized/CHANGELOG.md b/plugins/catalog-customized/CHANGELOG.md index 409c007eea..3f659df9b3 100644 --- a/plugins/catalog-customized/CHANGELOG.md +++ b/plugins/catalog-customized/CHANGELOG.md @@ -1,5 +1,37 @@ # @internal/plugin-catalog-customized +## 0.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/plugin-catalog@1.6.0 + +## 0.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + +## 0.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-catalog@1.6.0-next.1 + +## 0.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.5.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + ## 0.0.2 ### Patch Changes diff --git a/plugins/catalog-customized/package.json b/plugins/catalog-customized/package.json index a8ffbd7494..bb5564c135 100644 --- a/plugins/catalog-customized/package.json +++ b/plugins/catalog-customized/package.json @@ -1,7 +1,7 @@ { "name": "@internal/plugin-catalog-customized", "description": "The internal Backstage Customizable plugin for browsing the Backstage catalog", - "version": "0.0.2", + "version": "0.0.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index 09a41b65c5..a6a2f5770b 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,54 @@ # @backstage/plugin-catalog-graph +## 0.2.22 + +### Patch Changes + +- bde1e8c8e2: Added `curve` prop to the `DependencyGraph` component to select the type of layout +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/catalog-client@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.2.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.2.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.2.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.2.21 ### Patch Changes diff --git a/plugins/catalog-graph/api-report.md b/plugins/catalog-graph/api-report.md index ed4ca2a2c9..95f497fe95 100644 --- a/plugins/catalog-graph/api-report.md +++ b/plugins/catalog-graph/api-report.md @@ -30,6 +30,7 @@ export const CatalogGraphPage: (props: { mergeRelations?: boolean | undefined; direction?: Direction | undefined; showFilters?: boolean | undefined; + curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined; } | undefined; }) => JSX.Element; @@ -120,6 +121,7 @@ export const EntityRelationsGraph: (props: { renderLabel?: | DependencyGraphTypes.RenderLabelFunction | undefined; + curve?: 'curveStepBefore' | 'curveMonotoneX' | undefined; }) => JSX.Element; // @public diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 88a45c4bde..ccfbd5160e 100644 --- a/plugins/catalog-graph/package.json +++ b/plugins/catalog-graph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-graph", - "version": "0.2.21", + "version": "0.2.22", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx index 8e59c127d9..a6a6c2a711 100644 --- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.tsx @@ -40,6 +40,7 @@ import { EntityRelationsGraph, RelationPairs, } from '../EntityRelationsGraph'; +import { CurveFilter } from './CurveFilter'; import { DirectionFilter } from './DirectionFilter'; import { MaxDepthFilter } from './MaxDepthFilter'; import { SelectedKindsFilter } from './SelectedKindsFilter'; @@ -110,6 +111,7 @@ export const CatalogGraphPage = (props: { mergeRelations?: boolean; direction?: Direction; showFilters?: boolean; + curve?: 'curveStepBefore' | 'curveMonotoneX'; }; }) => { const { relationPairs = ALL_RELATION_PAIRS, initialState } = props; @@ -130,6 +132,8 @@ export const CatalogGraphPage = (props: { setMergeRelations, direction, setDirection, + curve, + setCurve, rootEntityNames, setRootEntityNames, showFilters, @@ -201,6 +205,7 @@ export const CatalogGraphPage = (props: { relationPairs={relationPairs} /> + diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx new file mode 100644 index 0000000000..0be0fca66b --- /dev/null +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.test.tsx @@ -0,0 +1,47 @@ +/* + * Copyright 2021 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 { render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { CurveFilter } from './CurveFilter'; + +describe('', () => { + test('should display current curve label', () => { + const onChange = jest.fn(); + const { getByText } = render( + , + ); + + expect(getByText('Monotone X')).toBeInTheDocument(); + }); + + test('should select an alternative curve factory', async () => { + const onChange = jest.fn(); + const { getByText, getByTestId } = render( + , + ); + + expect(getByText('Step Before')).toBeInTheDocument(); + + await userEvent.click(getByTestId('select')); + await userEvent.click(getByText('Monotone X')); + + await waitFor(() => { + expect(getByText('Monotone X')).toBeInTheDocument(); + expect(onChange).toHaveBeenCalledWith('curveMonotoneX'); + }); + }); +}); diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx new file mode 100644 index 0000000000..6cda334cf1 --- /dev/null +++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CurveFilter.tsx @@ -0,0 +1,49 @@ +/* + * Copyright 2021 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 { Select } from '@backstage/core-components'; +import { Box } from '@material-ui/core'; +import React, { useCallback } from 'react'; + +type Curve = 'curveStepBefore' | 'curveMonotoneX'; +const CURVE_DISPLAY_NAMES: Record = { + curveMonotoneX: 'Monotone X', + curveStepBefore: 'Step Before', +}; + +export type Props = { + value: Curve; + onChange: (value: 'curveStepBefore' | 'curveMonotoneX') => void; +}; + +const curves: Array = ['curveMonotoneX', 'curveStepBefore']; + +export const CurveFilter = ({ value, onChange }: Props) => { + const handleChange = useCallback(v => onChange(v as Curve), [onChange]); + + return ( + + setSelectedKind(String(value))} + /> + + ); }; diff --git a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx index 30dda011d4..2502fbbb6e 100644 --- a/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx +++ b/plugins/catalog-react/src/components/EntityRefLink/EntityRefLinks.tsx @@ -1,5 +1,5 @@ /* - * Copyright 2020 The Backstage Authors + * 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. @@ -13,41 +13,72 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - import { Entity, CompoundEntityRef } from '@backstage/catalog-model'; import React from 'react'; import { EntityRefLink } from './EntityRefLink'; import { LinkProps } from '@backstage/core-components'; +import { FetchedEntityRefLinks } from './FetchedEntityRefLinks'; /** * Props for {@link EntityRefLink}. * * @public */ -export type EntityRefLinksProps = { - entityRefs: (string | Entity | CompoundEntityRef)[]; - defaultKind?: string; -} & Omit; +export type EntityRefLinksProps< + TRef extends string | CompoundEntityRef | Entity, +> = ( + | { + defaultKind?: string; + entityRefs: TRef[]; + fetchEntities?: false; + getTitle?(entity: TRef): string | undefined; + } + | { + defaultKind?: string; + entityRefs: TRef[]; + fetchEntities: true; + getTitle(entity: Entity): string | undefined; + } +) & + Omit; /** * Shows a list of clickable links to entities. * * @public */ -export function EntityRefLinks(props: EntityRefLinksProps) { - const { entityRefs, defaultKind, ...linkProps } = props; +export function EntityRefLinks< + TRef extends string | CompoundEntityRef | Entity, +>(props: EntityRefLinksProps) { + const { entityRefs, defaultKind, fetchEntities, getTitle, ...linkProps } = + props; + + if (fetchEntities) { + return ( + + ); + } + return ( <> - {entityRefs.map((r, i) => ( - - {i > 0 && ', '} - - - ))} + {entityRefs.map((r: TRef, i: number) => { + return ( + + {i > 0 && ', '} + + + ); + })} ); } diff --git a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.tsx b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.tsx new file mode 100644 index 0000000000..36e2c0450d --- /dev/null +++ b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.test.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 { FetchedEntityRefLinks } from './FetchedEntityRefLinks'; +import { entityRouteRef } from '../../routes'; +import { renderInTestApp, TestApiProvider } from '@backstage/test-utils'; +import { Entity } from '@backstage/catalog-model'; +import React from 'react'; +import { JsonObject } from '@backstage/types'; +import { catalogApiRef } from '../../api'; +import { CatalogApi } from '@backstage/catalog-client'; + +describe('', () => { + const getTitle = (e: Entity): string => + (e.spec?.profile!! as JsonObject).displayName!!.toString()!!; + + it('should fetch entities and render the custom display text', async () => { + const entityRefs = [ + { + kind: 'Component', + namespace: 'default', + name: 'software', + }, + { + kind: 'API', + namespace: 'default', + name: 'interface', + }, + ]; + + const catalogApi: Partial = { + getEntities: () => + Promise.resolve({ + items: entityRefs.map(ref => ({ + apiVersion: 'backstage.io/v1alpha1', + kind: ref.kind, + metadata: { + name: ref.name, + namespace: ref.namespace, + }, + spec: { + profile: { + displayName: ref.name.toLocaleUpperCase('en-US'), + }, + type: 'organization', + }, + })), + }), + }; + + const rendered = await renderInTestApp( + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, + ); + + expect(rendered.getByText('SOFTWARE')).toHaveAttribute( + 'href', + '/catalog/default/component/software', + ); + + expect(rendered.getByText('INTERFACE')).toHaveAttribute( + 'href', + '/catalog/default/api/interface', + ); + }); + + it('should use entities as they are provided and render the custom display text', async () => { + const entityRefs = [ + { + kind: 'Component', + namespace: 'default', + name: 'tool', + }, + { + kind: 'API', + namespace: 'default', + name: 'implementation', + }, + ].map(ref => ({ + apiVersion: 'backstage.io/v1alpha1', + kind: ref.kind, + metadata: { + name: ref.name, + namespace: ref.namespace, + }, + spec: { + profile: { + displayName: ref.name.toLocaleUpperCase('en-US'), + }, + type: 'organization', + }, + })); + + const catalogApi: Partial = {}; + + const rendered = await renderInTestApp( + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, + ); + + expect(rendered.getByText('TOOL')).toHaveAttribute( + 'href', + '/catalog/default/component/tool', + ); + + expect(rendered.getByText('IMPLEMENTATION')).toHaveAttribute( + 'href', + '/catalog/default/api/implementation', + ); + }); + + it('should handle heterogeneous array of values to render the custom display text', async () => { + const entityRefs = [ + ...[ + { + kind: 'Component', + namespace: 'default', + name: 'tool', + }, + { + kind: 'API', + namespace: 'default', + name: 'implementation', + }, + ].map(ref => ({ + apiVersion: 'backstage.io/v1alpha1', + kind: ref.kind, + metadata: { + name: ref.name, + namespace: ref.namespace, + }, + spec: { + profile: { + displayName: ref.name.toLocaleUpperCase('en-US'), + }, + type: 'organization', + }, + })), + { + kind: 'Component', + namespace: 'default', + name: 'interface', + }, + ]; + + const catalogApi: Partial = { + getEntities: () => + Promise.resolve({ + items: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'interface', + namespace: 'default', + }, + spec: { + profile: { + displayName: 'INTERFACE', + }, + type: 'organization', + }, + }, + ], + }), + }; + + const rendered = await renderInTestApp( + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name/*': entityRouteRef, + }, + }, + ); + + expect(rendered.getByText('TOOL')).toHaveAttribute( + 'href', + '/catalog/default/component/tool', + ); + + expect(rendered.getByText('IMPLEMENTATION')).toHaveAttribute( + 'href', + '/catalog/default/api/implementation', + ); + + expect(rendered.getByText('INTERFACE')).toHaveAttribute( + 'href', + '/catalog/default/component/interface', + ); + }); +}); diff --git a/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx new file mode 100644 index 0000000000..ea4a32b511 --- /dev/null +++ b/plugins/catalog-react/src/components/EntityRefLink/FetchedEntityRefLinks.tsx @@ -0,0 +1,108 @@ +/* + * 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 { + Entity, + CompoundEntityRef, + parseEntityRef, +} from '@backstage/catalog-model'; +import React from 'react'; +import { EntityRefLink } from './EntityRefLink'; +import { ErrorPanel, LinkProps, Progress } from '@backstage/core-components'; +import useAsync from 'react-use/lib/useAsync'; +import { catalogApiRef } from '../../api'; +import { useApi } from '@backstage/core-plugin-api'; + +/** + * Props for {@link FetchedEntityRefLinks}. + * + * @public + */ +export type FetchedEntityRefLinksProps< + TRef extends string | CompoundEntityRef | Entity, +> = { + defaultKind?: string; + entityRefs: TRef[]; + getTitle(entity: Entity): string | undefined; +} & Omit; + +/** + * Shows a list of clickable links to entities with auto-fetching of entities + * for customising a displayed text via title attribute. + * + * @public + */ +export function FetchedEntityRefLinks< + TRef extends string | CompoundEntityRef | Entity, +>(props: FetchedEntityRefLinksProps) { + const { entityRefs, defaultKind, getTitle, ...linkProps } = props; + + const catalogApi = useApi(catalogApiRef); + + const { + value: entities = new Array(), + loading, + error, + } = useAsync(async () => { + const refs = entityRefs.reduce((acc, current) => { + return 'metadata' in current ? acc : [...acc, parseEntityRef(current)]; + }, new Array()); + + const pureEntities = entityRefs.filter( + ref => 'metadata' in ref, + ) as Array; + + return refs.length > 0 + ? [ + ...( + await catalogApi.getEntities({ + filter: refs.map(ref => ({ + kind: ref.kind, + 'metadata.namespace': ref.namespace, + 'metadata.name': ref.name, + })), + }) + ).items, + ...pureEntities, + ] + : pureEntities; + }, [entityRefs]); + + if (loading) { + return ; + } + + if (error) { + return ; + } + + return ( + <> + {entities.map((r: Entity, i) => { + return ( + + {i > 0 && ', '} + + + ); + })} + + ); +} diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx index 3353312955..d8c89e0858 100644 --- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx +++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx @@ -17,6 +17,7 @@ import { CatalogApi } from '@backstage/catalog-client'; import { Entity } from '@backstage/catalog-model'; import { + alertApiRef, ConfigApi, configApiRef, IdentityApi, @@ -91,6 +92,7 @@ const wrapper = ({ [identityApiRef, mockIdentityApi], [storageApiRef, MockStorageApi.create()], [starredEntitiesApiRef, new MockStarredEntitiesApi()], + [alertApiRef, { post: jest.fn() }], ]} > diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 57a1c60174..b40f04f501 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,154 @@ # @backstage/plugin-catalog +## 1.6.0 + +### Minor Changes + +- d558f41d3a: Added new column `Label` to `CatalogTable.columns`, this new column allows you make use of labels from metadata. + For example: category and visibility are type of labels associated with API entity illustrated below. + + YAML code snippet for API entity + + ```yaml + apiVersion: backstage.io/v1alpha1 + kind: API + metadata: + name: sample-api + description: API for sample + links: + - url: http://localhost:8080/swagger-ui.html + title: Swagger UI + tags: + - http + labels: + category: legacy + visibility: protected + ``` + + Consumers can customise columns to include label column and show in api-docs list + + ```typescript + const columns = [ + CatalogTable.columns.createNameColumn({ defaultKind: 'API' }), + CatalogTable.columns.createLabelColumn('category', { title: 'Category' }), + CatalogTable.columns.createLabelColumn('visibility', { + title: 'Visibility', + defaultValue: 'public', + }), + ]; + ``` + +### Patch Changes + +- 4efadb6968: Implemented the visual parts of `EntityKindPicker` so that it can be shown alongside the other filters on the left side of your catalog pages. +- 182000c663: Added `emptyContent` property to CatalogTable and DefaultCatalogPage to support customization of the Catalog Table. +- e89e1f614d: Added support for copy entity URL in entity page context menu +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## 1.6.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-search-react@1.2.0-next.2 + +## 1.6.0-next.1 + +### Minor Changes + +- d558f41d3a: Added new column `Label` to `CatalogTable.columns`, this new column allows you make use of labels from metadata. + For example: category and visibility are type of labels associated with API entity illustrated below. + + YAML code snippet for API entity + + ```yaml + apiVersion: backstage.io/v1alpha1 + kind: API + metadata: + name: sample-api + description: API for sample + links: + - url: http://localhost:8080/swagger-ui.html + title: Swagger UI + tags: + - http + labels: + category: legacy + visibility: protected + ``` + + Consumers can customise columns to include label column and show in api-docs list + + ```typescript + const columns = [ + CatalogTable.columns.createNameColumn({ defaultKind: 'API' }), + CatalogTable.columns.createLabelColumn('category', { title: 'Category' }), + CatalogTable.columns.createLabelColumn('visibility', { + title: 'Visibility', + defaultValue: 'public', + }), + ]; + ``` + +### Patch Changes + +- 4efadb6968: Implemented the visual parts of `EntityKindPicker` so that it can be shown alongside the other filters on the left side of your catalog pages. +- e89e1f614d: Added support for copy entity URL in entity page context menu +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.7-next.1 + +## 1.5.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 1.5.1 ### Patch Changes diff --git a/plugins/catalog/api-report.md b/plugins/catalog/api-report.md index 377b62a1e6..3f72df00e2 100644 --- a/plugins/catalog/api-report.md +++ b/plugins/catalog/api-report.md @@ -148,6 +148,15 @@ export const CatalogTable: { } | undefined, ): TableColumn; + createLabelColumn( + key: string, + options?: + | { + title?: string | undefined; + defaultValue?: string | undefined; + } + | undefined, + ): TableColumn; }>; }; @@ -158,6 +167,8 @@ export interface CatalogTableProps { // (undocumented) columns?: TableColumn[]; // (undocumented) + emptyContent?: ReactNode; + // (undocumented) subtitle?: string; // (undocumented) tableOptions?: TableProps['options']; @@ -187,6 +198,8 @@ export interface DefaultCatalogPageProps { // (undocumented) columns?: TableColumn[]; // (undocumented) + emptyContent?: ReactNode; + // (undocumented) initialKind?: string; // (undocumented) initiallySelectedFilter?: UserListFilterKind; diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 99361b6a3d..ad7e9a75c2 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog", "description": "The Backstage plugin for browsing the Backstage catalog", - "version": "1.5.1", + "version": "1.6.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.tsx b/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.tsx index 67eedb9acc..b0aaeae658 100644 --- a/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.tsx +++ b/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.tsx @@ -69,6 +69,7 @@ export function CatalogKindHeader(props: CatalogKindHeaderProps) { .then(response => response.facets.kind?.map(f => f.value).sort() || []); }); const { + filters, updateFilters, queryParameters: { kind: kindParameter }, } = useEntityList(); @@ -81,6 +82,14 @@ export function CatalogKindHeader(props: CatalogKindHeaderProps) { queryParamKind ?? initialFilter, ); + // Set selected kind from filters; this happens when the kind filter is + // updated from another component + useEffect(() => { + if (filters.kind?.value) { + setSelectedKind(filters.kind?.value); + } + }, [filters.kind]); + useEffect(() => { updateFilters({ kind: selectedKind ? new EntityKindFilter(selectedKind) : undefined, diff --git a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx index 39c19d717a..c5208c94f5 100644 --- a/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx +++ b/plugins/catalog/src/components/CatalogPage/DefaultCatalogPage.tsx @@ -34,8 +34,9 @@ import { EntityTypePicker, UserListFilterKind, UserListPicker, + EntityKindPicker, } from '@backstage/plugin-catalog-react'; -import React from 'react'; +import React, { ReactNode } from 'react'; import { createComponentRouteRef } from '../../routes'; import { CatalogTable, CatalogTableRow } from '../CatalogTable'; import { CatalogKindHeader } from '../CatalogKindHeader'; @@ -52,6 +53,7 @@ export interface DefaultCatalogPageProps { actions?: TableProps['actions']; initialKind?: string; tableOptions?: TableProps['options']; + emptyContent?: ReactNode; } export function DefaultCatalogPage(props: DefaultCatalogPageProps) { @@ -61,6 +63,7 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) { initiallySelectedFilter = 'owned', initialKind = 'component', tableOptions = {}, + emptyContent, } = props; const orgName = useApi(configApiRef).getOptionalString('organization.name') ?? 'Backstage'; @@ -83,6 +86,7 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) { + @@ -95,6 +99,7 @@ export function DefaultCatalogPage(props: DefaultCatalogPageProps) { columns={columns} actions={actions} tableOptions={tableOptions} + emptyContent={emptyContent} /> diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx index bd78cecaf0..bb2f302a37 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx @@ -331,4 +331,42 @@ describe('CatalogTable component', () => { expect(getByText('Should be rendered')).toBeInTheDocument(); }); + + it('should render the label column with customised title and value as specified', async () => { + const columns = [ + CatalogTable.columns.createNameColumn({ defaultKind: 'API' }), + CatalogTable.columns.createLabelColumn('category', { title: 'Category' }), + ]; + const entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'API', + metadata: { + name: 'APIWithLabel', + labels: { category: 'generic' }, + }, + }; + const expectedColumns = ['Name', 'Category', 'Actions']; + + const { getAllByRole, getByText } = await renderInTestApp( + + + + + , + { + mountedRoutes: { + '/catalog/:namespace/:kind/:name': entityRouteRef, + }, + }, + ); + + const columnHeader = getAllByRole('button').filter( + c => c.tagName === 'SPAN', + ); + const columnHeaderLabels = columnHeader.map(c => c.textContent); + expect(columnHeaderLabels).toEqual(expectedColumns); + + const labelCellValue = getByText('generic'); + expect(labelCellValue).toBeInTheDocument(); + }); }); diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx index 6bc18d7a90..935e002375 100644 --- a/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx +++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.tsx @@ -39,7 +39,7 @@ import OpenInNew from '@material-ui/icons/OpenInNew'; import Star from '@material-ui/icons/Star'; import StarBorder from '@material-ui/icons/StarBorder'; import { capitalize } from 'lodash'; -import React, { useMemo } from 'react'; +import React, { ReactNode, useMemo } from 'react'; import { columnFactories } from './columns'; import { CatalogTableRow } from './types'; @@ -52,6 +52,7 @@ export interface CatalogTableProps { columns?: TableColumn[]; actions?: TableProps['actions']; tableOptions?: TableProps['options']; + emptyContent?: ReactNode; subtitle?: string; } @@ -63,7 +64,7 @@ const YellowStar = withStyles({ /** @public */ export const CatalogTable = (props: CatalogTableProps) => { - const { columns, actions, tableOptions, subtitle } = props; + const { columns, actions, tableOptions, subtitle, emptyContent } = props; const { isStarredEntity, toggleStarredEntity } = useStarredEntities(); const { loading, error, entities, filters } = useEntityList(); @@ -228,6 +229,7 @@ export const CatalogTable = (props: CatalogTableProps) => { data={rows} actions={actions || defaultActions} subtitle={subtitle} + emptyContent={emptyContent} /> ); }; diff --git a/plugins/catalog/src/components/CatalogTable/columns.tsx b/plugins/catalog/src/components/CatalogTable/columns.tsx index 19d160c0e2..7f3cd5ef82 100644 --- a/plugins/catalog/src/components/CatalogTable/columns.tsx +++ b/plugins/catalog/src/components/CatalogTable/columns.tsx @@ -162,4 +162,35 @@ export const columnFactories = Object.freeze({ searchable: true, }; }, + createLabelColumn( + key: string, + options?: { title?: string; defaultValue?: string }, + ): TableColumn { + return { + title: options?.title || 'Label', + field: 'entity.metadata.labels', + cellStyle: { + padding: '0px 16px 0px 20px', + }, + render: ({ entity }: { entity: Entity }) => { + const labels: Record | undefined = + entity.metadata?.labels; + const specifiedLabelValue = + (labels && labels[key]) || options?.defaultValue; + return ( + <> + {specifiedLabelValue && ( + + )} + + ); + }, + width: 'auto', + }; + }, }); diff --git a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx index 41610ed13c..49952bdffe 100644 --- a/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx +++ b/plugins/catalog/src/components/EntityContextMenu/EntityContextMenu.tsx @@ -26,12 +26,14 @@ import { import { makeStyles } from '@material-ui/core/styles'; import BugReportIcon from '@material-ui/icons/BugReport'; import MoreVert from '@material-ui/icons/MoreVert'; -import React, { useState } from 'react'; +import FileCopyTwoToneIcon from '@material-ui/icons/FileCopyTwoTone'; +import React, { useCallback, useState } from 'react'; import { IconComponent } from '@backstage/core-plugin-api'; import { useEntityPermission } from '@backstage/plugin-catalog-react'; import { catalogEntityDeletePermission } from '@backstage/plugin-catalog-common'; import { BackstageTheme } from '@backstage/theme'; import { UnregisterEntity, UnregisterEntityOptions } from './UnregisterEntity'; +import { useApi, alertApiRef } from '@backstage/core-plugin-api'; /** @public */ export type EntityContextMenuClassKey = 'button'; @@ -84,6 +86,14 @@ export function EntityContextMenu(props: EntityContextMenuProps) { setAnchorEl(undefined); }; + const alertApi = useApi(alertApiRef); + + const copyToClipboard = useCallback(() => { + navigator.clipboard + .writeText(window.location.toString()) + .then(() => alertApi.post({ message: 'Copied!', severity: 'info' })); + }, [alertApi]); + const extraItems = UNSTABLE_extraContextMenuItems && [ ...UNSTABLE_extraContextMenuItems.map(item => ( + { + onClose(); + copyToClipboard(); + }} + > + + + + + diff --git a/plugins/cicd-statistics-module-gitlab/CHANGELOG.md b/plugins/cicd-statistics-module-gitlab/CHANGELOG.md index 01dd968ad6..2d3dbc8eed 100644 --- a/plugins/cicd-statistics-module-gitlab/CHANGELOG.md +++ b/plugins/cicd-statistics-module-gitlab/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-cicd-statistics-module-gitlab +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-cicd-statistics@0.1.12 + - @backstage/core-plugin-api@1.0.7 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/plugin-cicd-statistics@0.1.12-next.2 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-cicd-statistics@0.1.12-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-cicd-statistics@0.1.12-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + ## 0.1.5 ### Patch Changes diff --git a/plugins/cicd-statistics-module-gitlab/package.json b/plugins/cicd-statistics-module-gitlab/package.json index a8c4ef7dec..e0fdf00b7b 100644 --- a/plugins/cicd-statistics-module-gitlab/package.json +++ b/plugins/cicd-statistics-module-gitlab/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cicd-statistics-module-gitlab", "description": "CI/CD Statistics plugin module; Gitlab CICD", - "version": "0.1.5", + "version": "0.1.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cicd-statistics/CHANGELOG.md b/plugins/cicd-statistics/CHANGELOG.md index 6bda1cf68d..24e4a2f6e3 100644 --- a/plugins/cicd-statistics/CHANGELOG.md +++ b/plugins/cicd-statistics/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-cicd-statistics +## 0.1.12 + +### Patch Changes + +- e05e0f021b: Align on the version of `@material-ui/icons` used, to `^4.9.1` like other packages in the main repo +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-plugin-api@1.0.7 + +## 0.1.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + +## 0.1.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + +## 0.1.12-next.0 + +### Patch Changes + +- e05e0f021b: Align on the version of `@material-ui/icons` used, to `^4.9.1` like other packages in the main repo +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + ## 0.1.11 ### Patch Changes diff --git a/plugins/cicd-statistics/package.json b/plugins/cicd-statistics/package.json index 1e1a7f95ad..d198ff0934 100644 --- a/plugins/cicd-statistics/package.json +++ b/plugins/cicd-statistics/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cicd-statistics", "description": "A frontend plugin visualizing CI/CD pipeline statistics (build time)", - "version": "0.1.11", + "version": "0.1.12", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index f020e67b0f..8d5199d550 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-circleci +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.3.9 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 7363bd98f0..98b6334c35 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-circleci", "description": "A Backstage plugin that integrates towards Circle CI", - "version": "0.3.9", + "version": "0.3.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index 5c01b5be4b..30a7410a08 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-cloudbuild +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.3.9 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 3b20b28c3d..eb01b8df4a 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cloudbuild", "description": "A Backstage plugin that integrates towards Google Cloud Build", - "version": "0.3.9", + "version": "0.3.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/code-climate/CHANGELOG.md b/plugins/code-climate/CHANGELOG.md index 418421aafe..367eb96673 100644 --- a/plugins/code-climate/CHANGELOG.md +++ b/plugins/code-climate/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-code-climate +## 0.1.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.1.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.1.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.1.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.1.9 ### Patch Changes diff --git a/plugins/code-climate/package.json b/plugins/code-climate/package.json index 1863e3e080..548f93233c 100644 --- a/plugins/code-climate/package.json +++ b/plugins/code-climate/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-code-climate", - "version": "0.1.9", + "version": "0.1.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/code-coverage-backend/CHANGELOG.md b/plugins/code-coverage-backend/CHANGELOG.md index 9001fbeab0..891194c7a4 100644 --- a/plugins/code-coverage-backend/CHANGELOG.md +++ b/plugins/code-coverage-backend/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-code-coverage-backend +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index 3cf9b2f087..147a2b92ac 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage-backend", "description": "A Backstage backend plugin that helps you keep track of your code coverage", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/code-coverage/CHANGELOG.md b/plugins/code-coverage/CHANGELOG.md index b8e384fbd7..c724283ddb 100644 --- a/plugins/code-coverage/CHANGELOG.md +++ b/plugins/code-coverage/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-code-coverage +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + ## 0.2.2 ### Patch Changes diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index ab4e071ccc..f8507e60a4 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-code-coverage", "description": "A Backstage plugin that helps you keep track of your code coverage", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/codescene/CHANGELOG.md b/plugins/codescene/CHANGELOG.md index b5d70d9d17..cdfaf94a71 100644 --- a/plugins/codescene/CHANGELOG.md +++ b/plugins/codescene/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-codescene +## 0.1.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.1.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## 0.1.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + ## 0.1.4 ### Patch Changes diff --git a/plugins/codescene/package.json b/plugins/codescene/package.json index 884b751347..4f8f9a365f 100644 --- a/plugins/codescene/package.json +++ b/plugins/codescene/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-codescene", - "version": "0.1.4", + "version": "0.1.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index 88d12a9da1..af63c96be1 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-config-schema +## 0.1.33 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## 0.1.33-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## 0.1.33-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## 0.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + ## 0.1.32 ### Patch Changes diff --git a/plugins/config-schema/dev/example-schema.json b/plugins/config-schema/dev/example-schema.json index 86f05d500d..bb90595340 100644 --- a/plugins/config-schema/dev/example-schema.json +++ b/plugins/config-schema/dev/example-schema.json @@ -499,28 +499,6 @@ } } }, - "homepage": { - "type": "object", - "properties": { - "clocks": { - "type": "array", - "items": { - "type": "object", - "required": ["label", "timezone"], - "properties": { - "label": { - "visibility": "frontend", - "type": "string" - }, - "timezone": { - "visibility": "frontend", - "type": "string" - } - } - } - } - } - }, "integrations": { "description": "Configuration for integrations towards various external repository provider systems", "type": "object", diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index cf2cb6cf90..b6a23dd2aa 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-config-schema", "description": "A Backstage plugin that lets you browse the configuration schema of your app", - "version": "0.1.32", + "version": "0.1.33", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index 1bb7d5aa75..02f9902fce 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-cost-insights +## 0.11.32 + +### Patch Changes + +- a94c2ed1b7: Fixed bug in `CostOverviewBreakdownChart` component where some datasets caused the cost overview breakdown chart to tear. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + +## 0.11.32-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + +## 0.11.32-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + +## 0.11.32-next.0 + +### Patch Changes + +- a94c2ed1b7: Fixed bug in `CostOverviewBreakdownChart` component where some datasets caused the cost overview breakdown chart to tear. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-cost-insights-common@0.1.1 + ## 0.11.31 ### Patch Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index fc128f405f..e325f75e0a 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-cost-insights", "description": "A Backstage plugin that helps you keep track of your cloud spend", - "version": "0.11.31", + "version": "0.11.32", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewBreakdownChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewBreakdownChart.tsx index d0731326f2..5b912c09a6 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewBreakdownChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewBreakdownChart.tsx @@ -113,8 +113,8 @@ export const CostOverviewBreakdownChart = ({ {} as Record>, ); - const chartData: Record[] = Object.keys(breakdownsByDate).map( - date => { + const chartData: Record[] = Object.keys(breakdownsByDate) + .map(date => { const costsForDate = Object.keys(breakdownsByDate[date]).reduce( (dateCosts, breakdown) => { // Group costs for items that belong to 'Other' in the chart. @@ -131,8 +131,8 @@ export const CostOverviewBreakdownChart = ({ ...costsForDate, date: Date.parse(date), }; - }, - ); + }) + .sort((a, b) => a.date - b.date); const sortedBreakdowns = costBreakdown.sort( (a, b) => aggregationSum(a.aggregation) - aggregationSum(b.aggregation), diff --git a/plugins/dynatrace/CHANGELOG.md b/plugins/dynatrace/CHANGELOG.md index ae367016e3..125976f131 100644 --- a/plugins/dynatrace/CHANGELOG.md +++ b/plugins/dynatrace/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-dynatrace +## 1.0.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 1.0.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 1.0.0-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.2.0 ### Minor Changes diff --git a/plugins/dynatrace/package.json b/plugins/dynatrace/package.json index d8b3cb06a8..7e024c439b 100644 --- a/plugins/dynatrace/package.json +++ b/plugins/dynatrace/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-dynatrace", - "version": "0.2.0", + "version": "1.0.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.test.tsx b/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.test.tsx index aaa3ea0024..5b9c0d7b3f 100644 --- a/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.test.tsx +++ b/plugins/dynatrace/src/components/Problems/ProblemsTable/ProblemsTable.test.tsx @@ -31,6 +31,10 @@ describe('ProblemsTable', () => { , , ); - expect(await rendered.findByTitle('Search')).toBeInTheDocument(); + + // Checking for Title from mocked problems + expect( + await rendered.findByText('this IS a big problem'), + ).toBeInTheDocument(); }); }); diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index 5bc46323d8..08eb87afc6 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,45 @@ # @internal/plugin-todo-list-backend +## 1.0.6 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 1.0.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 1.0.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + +## 1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + ## 1.0.5 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 295aad4021..5bffe602ab 100644 --- a/plugins/example-todo-list-backend/package.json +++ b/plugins/example-todo-list-backend/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-backend", - "version": "1.0.5", + "version": "1.0.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/example-todo-list-backend/src/service/router.ts b/plugins/example-todo-list-backend/src/service/router.ts index 2973b6eab6..a1ae98eac9 100644 --- a/plugins/example-todo-list-backend/src/service/router.ts +++ b/plugins/example-todo-list-backend/src/service/router.ts @@ -51,7 +51,7 @@ export async function createRouter( router.get('/health', (_, response) => { logger.info('PONG!'); - response.send({ status: 'ok' }); + response.json({ status: 'ok' }); }); router.get('/todos', async (_req, res) => { diff --git a/plugins/example-todo-list-common/CHANGELOG.md b/plugins/example-todo-list-common/CHANGELOG.md index 3754f768ab..09b70db880 100644 --- a/plugins/example-todo-list-common/CHANGELOG.md +++ b/plugins/example-todo-list-common/CHANGELOG.md @@ -1,5 +1,33 @@ # @internal/plugin-todo-list-common +## 1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 + +## 1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + +## 1.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.6.5-next.1 + +## 1.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.6.5-next.0 + ## 1.0.4 ### Patch Changes diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index 32dfe96ef4..17d67e99f7 100644 --- a/plugins/example-todo-list-common/package.json +++ b/plugins/example-todo-list-common/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list-common", - "version": "1.0.4", + "version": "1.0.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index be969303aa..9ce9eaae57 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,41 @@ # @internal/plugin-todo-list +## 1.0.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 1.0.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 1.0.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## 1.0.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 1.0.5 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index 9b66c0c0a9..8bbf1f2e20 100644 --- a/plugins/example-todo-list/package.json +++ b/plugins/example-todo-list/package.json @@ -1,6 +1,6 @@ { "name": "@internal/plugin-todo-list", - "version": "1.0.5", + "version": "1.0.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/explore-react/CHANGELOG.md b/plugins/explore-react/CHANGELOG.md index e35dae9e32..19e15c302f 100644 --- a/plugins/explore-react/CHANGELOG.md +++ b/plugins/explore-react/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-explore-react +## 0.0.22 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7 + +## 0.0.22-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.2 + +## 0.0.22-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.1 + +## 0.0.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.0 + ## 0.0.21 ### Patch Changes diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index 66ac17f785..9b5d3792cc 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore-react", "description": "A frontend library for Backstage plugins that want to interact with the explore plugin", - "version": "0.0.21", + "version": "0.0.22", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index 3e803d2d6b..09569f7206 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-explore +## 0.3.41 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/plugin-explore-react@0.0.22 + +## 0.3.41-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-explore-react@0.0.22-next.2 + +## 0.3.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-explore-react@0.0.22-next.1 + +## 0.3.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-explore-react@0.0.22-next.0 + ## 0.3.40 ### Patch Changes diff --git a/plugins/explore/dev/index.tsx b/plugins/explore/dev/index.tsx index 71795a95b2..19e43d4d0f 100644 --- a/plugins/explore/dev/index.tsx +++ b/plugins/explore/dev/index.tsx @@ -16,12 +16,60 @@ import { Entity } from '@backstage/catalog-model'; import { createDevApp } from '@backstage/dev-utils'; +import { CatalogEntityPage, EntityLayout } from '@backstage/plugin-catalog'; import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react'; import { exploreToolsConfigRef } from '@backstage/plugin-explore-react'; import React from 'react'; import { ExplorePage, explorePlugin } from '../src'; import { exampleTools } from '../src/util/examples'; +const catalogApi: Partial = { + async getEntityByRef(): Promise { + return { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'example', + annotations: { + 'backstage.io/managed-by-location': 'file:/path/to/catalog-info.yaml', + }, + }, + spec: { + type: 'service', + lifecycle: 'production', + owner: 'guest', + }, + } as Entity; + }, + async getEntities() { + const domainNames = [ + 'playback', + 'artists', + 'payments', + 'analytics', + 'songs', + 'devops', + ]; + return { + items: domainNames.map( + (n, i) => + ({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'Domain', + metadata: { + name: n, + description: `Everything about ${n}`, + tags: i % 2 === 0 ? [n] : undefined, + }, + spec: { + owner: `${n}@example.com`, + }, + } as Entity), + ), + }; + }, +}; + createDevApp() .registerPlugin(explorePlugin) .registerApi({ @@ -36,37 +84,18 @@ createDevApp() .registerApi({ api: catalogApiRef, deps: {}, - factory: () => - ({ - async getEntities() { - const domainNames = [ - 'playback', - 'artists', - 'payments', - 'analytics', - 'songs', - 'devops', - ]; - - return { - items: domainNames.map( - (n, i) => - ({ - apiVersion: 'backstage.io/v1alpha1', - kind: 'Domain', - metadata: { - name: n, - description: `Everything about ${n}`, - tags: i % 2 === 0 ? [n] : undefined, - }, - spec: { - owner: `${n}@example.com`, - }, - } as Entity), - ), - }; - }, - } as CatalogApi), + factory: () => catalogApi as CatalogApi, }) .addPage({ element: , title: 'Explore' }) + .addPage({ + path: '/catalog/:namespace/:kind/:name', + element: , + children: ( + + +

Some Content Here

+
+
+ ), + }) .render(); diff --git a/plugins/explore/package.json b/plugins/explore/package.json index b9b9257a3a..0116a85d6a 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-explore", "description": "A Backstage plugin for building an exploration page of your software ecosystem", - "version": "0.3.40", + "version": "0.3.41", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -54,6 +54,7 @@ "@backstage/cli": "workspace:^", "@backstage/core-app-api": "workspace:^", "@backstage/dev-utils": "workspace:^", + "@backstage/plugin-catalog": "workspace:^", "@backstage/test-utils": "workspace:^", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/firehydrant/CHANGELOG.md b/plugins/firehydrant/CHANGELOG.md index eae65d3c36..3516fa3d45 100644 --- a/plugins/firehydrant/CHANGELOG.md +++ b/plugins/firehydrant/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-firehydrant +## 0.1.27 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## 0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.1.26 ### Patch Changes diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json index 37aa7b259b..97831712ef 100644 --- a/plugins/firehydrant/package.json +++ b/plugins/firehydrant/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-firehydrant", "description": "A Backstage plugin that integrates towards FireHydrant", - "version": "0.1.26", + "version": "0.1.27", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index 3126dbc0e1..231655bedc 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-fossa +## 0.2.42 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.2.42-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## 0.2.42-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.2.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + ## 0.2.41 ### Patch Changes diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index bf249c1b9d..c5be2f84cb 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-fossa", "description": "A Backstage plugin that integrates towards FOSSA", - "version": "0.2.41", + "version": "0.2.42", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gcalendar/CHANGELOG.md b/plugins/gcalendar/CHANGELOG.md index 2be25841e7..3dbedd4f66 100644 --- a/plugins/gcalendar/CHANGELOG.md +++ b/plugins/gcalendar/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/plugin-gcalendar +## 0.3.6 + +### Patch Changes + +- 4c2ed7ecf1: Fixed loader showing when user not signed in +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.3.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## 0.3.6-next.1 + +### Patch Changes + +- 4c2ed7ecf1: Fixed loader showing when user not signed in +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + ## 0.3.5 ### Patch Changes diff --git a/plugins/gcalendar/package.json b/plugins/gcalendar/package.json index 9ef566e95c..96b15932e6 100644 --- a/plugins/gcalendar/package.json +++ b/plugins/gcalendar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gcalendar", - "version": "0.3.5", + "version": "0.3.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx b/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx index a5575bc190..0a9b517293 100644 --- a/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx +++ b/plugins/gcalendar/src/components/CalendarCard/CalendarCard.tsx @@ -50,10 +50,13 @@ export const CalendarCard = () => { useAsync(async () => signIn(true), [signIn]); - const { isLoading: isCalendarLoading, data: calendars = [] } = - useCalendarsQuery({ - enabled: isSignedIn, - }); + const { + isLoading: isCalendarLoading, + isFetching: isCalendarFetching, + data: calendars = [], + } = useCalendarsQuery({ + enabled: isSignedIn, + }); const primaryCalendarId = calendars.find(c => c.primary === true)?.id; const defaultSelectedCalendars = primaryCalendarId ? [primaryCalendarId] : []; const [storedCalendars, setStoredCalendars] = useStoredCalendars( @@ -69,6 +72,11 @@ export const CalendarCard = () => { timeZone: date.zoneName, }); + const showLoader = + (isCalendarLoading && isCalendarFetching) || + isEventLoading || + !isInitialized; + return ( { calendars={calendars} selectedCalendars={storedCalendars} setSelectedCalendars={setStoredCalendars} - disabled={isCalendarLoading || !isSignedIn} + disabled={ + (isCalendarFetching && isCalendarLoading) || !isSignedIn + } /> ) : ( @@ -114,8 +124,8 @@ export const CalendarCard = () => { }} > - {(isCalendarLoading || isEventLoading || !isInitialized) && ( - + {showLoader && ( + )} diff --git a/plugins/gcp-projects/CHANGELOG.md b/plugins/gcp-projects/CHANGELOG.md index d4e54838c4..82e4638637 100644 --- a/plugins/gcp-projects/CHANGELOG.md +++ b/plugins/gcp-projects/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-gcp-projects +## 0.3.29 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.3.29-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.3.29-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## 0.3.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.3.28 ### Patch Changes diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index d2926de41b..b180c40f45 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gcp-projects", "description": "A Backstage plugin that helps you manage projects in GCP", - "version": "0.3.28", + "version": "0.3.29", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/git-release-manager/CHANGELOG.md b/plugins/git-release-manager/CHANGELOG.md index 0c7f930e60..9280dc38c2 100644 --- a/plugins/git-release-manager/CHANGELOG.md +++ b/plugins/git-release-manager/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-git-release-manager +## 0.3.23 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## 0.3.23-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## 0.3.23-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/theme@0.2.16 + +## 0.3.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + ## 0.3.22 ### Patch Changes diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 89061cc510..dee542a967 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-git-release-manager", "description": "A Backstage plugin that helps you manage releases in git", - "version": "0.3.22", + "version": "0.3.23", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index 4a0ca4dce0..a4c0eed8c2 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-github-actions +## 0.5.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## 0.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## 0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/theme@0.2.16 + +## 0.5.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + ## 0.5.9 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 596e844c11..3540e00549 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-actions", "description": "A Backstage plugin that integrates towards GitHub Actions", - "version": "0.5.9", + "version": "0.5.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/github-deployments/CHANGELOG.md b/plugins/github-deployments/CHANGELOG.md index 244a610d0c..120740492c 100644 --- a/plugins/github-deployments/CHANGELOG.md +++ b/plugins/github-deployments/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/plugin-github-deployments +## 0.1.41 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## 0.1.41-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + +## 0.1.41-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + +## 0.1.41-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + ## 0.1.40 ### Patch Changes diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index a0f94015dd..eaaaac9ae1 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-deployments", "description": "A Backstage plugin that integrates towards GitHub Deployments", - "version": "0.1.40", + "version": "0.1.41", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/github-issues/CHANGELOG.md b/plugins/github-issues/CHANGELOG.md index 8519b5cc08..8d5aa17129 100644 --- a/plugins/github-issues/CHANGELOG.md +++ b/plugins/github-issues/CHANGELOG.md @@ -1,5 +1,63 @@ # @backstage/plugin-github-issues +## 0.1.2 + +### Patch Changes + +- 8c7bff2bb4: Updated the `luxon` dependency to 3.x +- 719ccbb963: Properly filter on relations instead of the spec, when finding by owner +- df226e124c: Add filtering and ordering to the graphql query +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## 0.1.2-next.2 + +### Patch Changes + +- df226e124c: Add filtering and ordering to the graphql query +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## 0.1.2-next.1 + +### Patch Changes + +- 8c7bff2bb4: Updated the `luxon` dependency to 3.x +- 719ccbb963: Properly filter on relations instead of the spec, when finding by owner +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/theme@0.2.16 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + ## 0.1.1 ### Patch Changes diff --git a/plugins/github-issues/README.md b/plugins/github-issues/README.md index eeb93ab27b..d35f2c5d3f 100644 --- a/plugins/github-issues/README.md +++ b/plugins/github-issues/README.md @@ -61,3 +61,20 @@ However, you can configure the plugin with props: - `itemsPerPage: number = 10` - Issues in the list are paginated, number of issues on a single page is controlled with this prop - `itemsPerRepo: number = 40` - the plugin doesn't download all Issues available on GitHub. By default, it will get at most 40 Issues - this prop controls this behaviour +- `filterBy: object` - the plugin can be configured to filter the query by `assignee`, `createdBy`, `labels`, `states`, `mentioned` or `milestone`. +- `orderBy: object = { field: 'UPDATED_AT', direction: 'DESC' }` - The ordering that the issues are returned can be configured by the `orderBy` field. + +### `filterBy` and `orderBy` example + +```ts + +``` diff --git a/plugins/github-issues/api-report.md b/plugins/github-issues/api-report.md index eb69fc8e8e..41b4f87064 100644 --- a/plugins/github-issues/api-report.md +++ b/plugins/github-issues/api-report.md @@ -11,6 +11,30 @@ import { RouteRef } from '@backstage/core-plugin-api'; // @public (undocumented) export const GitHubIssuesCard: (props: GitHubIssuesProps) => JSX.Element; +// @public (undocumented) +export interface GithubIssuesFilters { + // (undocumented) + assignee?: string; + // (undocumented) + createdBy?: string; + // (undocumented) + labels?: string[]; + // (undocumented) + mentioned?: string; + // (undocumented) + milestone?: string; + // (undocumented) + states?: ('OPEN' | 'CLOSED')[]; +} + +// @public (undocumented) +export interface GithubIssuesOrdering { + // (undocumented) + direction?: 'ASC' | 'DESC'; + // (undocumented) + field: 'CREATED_AT' | 'UPDATED_AT' | 'COMMENTS'; +} + // @public (undocumented) export const GitHubIssuesPage: (props: GitHubIssuesProps) => JSX.Element; @@ -27,7 +51,17 @@ export const gitHubIssuesPlugin: BackstagePlugin< export type GitHubIssuesProps = { itemsPerPage?: number; itemsPerRepo?: number; + filterBy?: GithubIssuesFilters; + orderBy?: GithubIssuesOrdering; }; +// @public (undocumented) +export interface GitubIssuesByRepoOptions { + // (undocumented) + filterBy?: GithubIssuesFilters; + // (undocumented) + orderBy?: GithubIssuesOrdering; +} + // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/github-issues/package.json b/plugins/github-issues/package.json index c393e4db8a..0249fe3a94 100644 --- a/plugins/github-issues/package.json +++ b/plugins/github-issues/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-issues", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,7 +32,7 @@ "@material-ui/core": "^4.12.4", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "^4.0.0-alpha.61", - "luxon": "^2.4.0", + "luxon": "^3.0.0", "octokit": "^2.0.4", "react-use": "^17.4.0" }, diff --git a/plugins/github-issues/src/api/gitHubIssuesApi.test.ts b/plugins/github-issues/src/api/gitHubIssuesApi.test.ts index 4d1c3ffff3..b2be54a2d1 100644 --- a/plugins/github-issues/src/api/gitHubIssuesApi.test.ts +++ b/plugins/github-issues/src/api/gitHubIssuesApi.test.ts @@ -20,84 +20,152 @@ jest.mock('octokit', () => ({ import { ConfigApi, ErrorApi } from '@backstage/core-plugin-api'; import { ForwardedError } from '@backstage/errors'; -import { gitHubIssuesApi } from './gitHubIssuesApi'; +import { createFilterByClause, gitHubIssuesApi } from './gitHubIssuesApi'; +import type { GithubIssuesFilters } from './gitHubIssuesApi'; + +function getFragment( + filterBy = '', + orderBy = 'field: UPDATED_AT, direction: DESC', +) { + return ( + '\n' + + ' \n' + + ' fragment issues on Repository {\n' + + ' issues(\n' + + ' states: OPEN\n' + + ' first: 10\n' + + ` filterBy: { ${filterBy} }\n` + + ` orderBy: { ${orderBy} }\n` + + ' ) {\n' + + ' totalCount\n' + + ' edges {\n' + + ' node {\n' + + ' assignees(first: 10) {\n' + + ' edges {\n' + + ' node {\n' + + ' avatarUrl\n' + + ' login\n' + + ' }\n' + + ' }\n' + + ' }\n' + + ' author {\n' + + ' login\n' + + ' }\n' + + ' repository {\n' + + ' nameWithOwner\n' + + ' }\n' + + ' title\n' + + ' url\n' + + ' participants {\n' + + ' totalCount\n' + + ' }\n' + + ' updatedAt\n' + + ' createdAt\n' + + ' comments(last: 1) {\n' + + ' totalCount\n' + + ' }\n' + + ' }\n' + + ' }\n' + + ' }\n' + + ' }\n' + + ' \n' + + '\n' + + ' query {\n' + + ' \n' + + ' yoyo: repository(name: "yo-yo", owner: "mrwolny") {\n' + + ' ...issues\n' + + ' }\n' + + ' ,\n' + + ' yoyox: repository(name: "yoyo", owner: "mrwolny") {\n' + + ' ...issues\n' + + ' }\n' + + ' ,\n' + + ' yoyoxx: repository(name: "yo.yo", owner: "mrwolny") {\n' + + ' ...issues\n' + + ' }\n' + + ' \n' + + ' } \n' + + ' ' + ); +} describe('gitHubIssuesApi', () => { describe('fetchIssuesByRepoFromGitHub', () => { - it('should call GitHub API with correct query with fragment for each repo', async () => { - const api = gitHubIssuesApi( + let api: ReturnType; + + afterEach(() => { + jest.clearAllMocks(); + }); + + beforeEach(() => { + api = gitHubIssuesApi( { getAccessToken: jest.fn() }, { getOptionalConfigArray: jest.fn(), } as unknown as ConfigApi, { post: jest.fn() } as unknown as ErrorApi, ); + }); + it('should call GitHub API with correct query with fragment for each repo', async () => { await api.fetchIssuesByRepoFromGitHub( ['mrwolny/yo-yo', 'mrwolny/yoyo', 'mrwolny/yo.yo'], 10, ); + + expect(mockGraphQLQuery).toHaveBeenCalledTimes(1); + expect(mockGraphQLQuery).toHaveBeenCalledWith(getFragment()); + }); + + it('should call Github API with the correct filterBy and orderBy clauses', async () => { + await api.fetchIssuesByRepoFromGitHub( + ['mrwolny/yo-yo', 'mrwolny/yoyo', 'mrwolny/yo.yo'], + 10, + { + filterBy: { + labels: ['bug'], + states: ['OPEN'], + assignee: 'someone', + }, + orderBy: { + field: 'COMMENTS', + direction: 'ASC', + }, + }, + ); + + const expectedFilterBy = `labels: [ "bug"], states: OPEN, assignee: "someone"`; + const expectedOrderBy = 'field: COMMENTS, direction: ASC'; + expect(mockGraphQLQuery).toHaveBeenCalledTimes(1); expect(mockGraphQLQuery).toHaveBeenCalledWith( - '\n' + - ' \n' + - ' fragment issues on Repository {\n' + - ' issues(\n' + - ' states: OPEN\n' + - ' first: 10\n' + - ' orderBy: { field: UPDATED_AT, direction: DESC }\n' + - ' ) {\n' + - ' totalCount\n' + - ' edges {\n' + - ' node {\n' + - ' assignees(first: 10) {\n' + - ' edges {\n' + - ' node {\n' + - ' avatarUrl\n' + - ' login\n' + - ' }\n' + - ' }\n' + - ' }\n' + - ' author {\n' + - ' login\n' + - ' }\n' + - ' repository {\n' + - ' nameWithOwner\n' + - ' }\n' + - ' title\n' + - ' url\n' + - ' participants {\n' + - ' totalCount\n' + - ' }\n' + - ' updatedAt\n' + - ' createdAt\n' + - ' comments(last: 1) {\n' + - ' totalCount\n' + - ' }\n' + - ' }\n' + - ' }\n' + - ' }\n' + - ' }\n' + - ' \n' + - '\n' + - ' query {\n' + - ' \n' + - ' yoyo: repository(name: "yo-yo", owner: "mrwolny") {\n' + - ' ...issues\n' + - ' }\n' + - ' ,\n' + - ' yoyox: repository(name: "yoyo", owner: "mrwolny") {\n' + - ' ...issues\n' + - ' }\n' + - ' ,\n' + - ' yoyoxx: repository(name: "yo.yo", owner: "mrwolny") {\n' + - ' ...issues\n' + - ' }\n' + - ' \n' + - ' } \n' + - ' ', + getFragment(expectedFilterBy, expectedOrderBy), ); }); + + describe('filterBy', () => { + const cases: [GithubIssuesFilters | undefined, string][] = [ + [{}, ''], + [undefined, ''], + [{ states: ['OPEN'] }, 'states: OPEN'], + [ + { labels: ['bug', 'enhancement'], assignee: 'someone' }, + `labels: [ \"bug\", \"enhancement\"], assignee: "someone"`, + ], + [ + { + createdBy: 'someone', + mentioned: 'someone else', + milestone: 'milestone', + }, + `createdBy: \"someone\", mentioned: \"someone else\", milestone: \"milestone\"`, + ], + ]; + + test.each(cases)('filterBy(%s) should be %s', (filterBy, expected) => { + expect(createFilterByClause(filterBy)).toEqual(expected); + }); + }); }); it('should return data for repos with successfully retrieved issues when GitHub returns partial failure', async () => { diff --git a/plugins/github-issues/src/api/gitHubIssuesApi.ts b/plugins/github-issues/src/api/gitHubIssuesApi.ts index 816345940c..b4be445d13 100644 --- a/plugins/github-issues/src/api/gitHubIssuesApi.ts +++ b/plugins/github-issues/src/api/gitHubIssuesApi.ts @@ -73,6 +73,34 @@ export type IssuesByRepo = Record; /** @internal */ export type GitHubIssuesApi = ReturnType; +/** + * @public + */ +export interface GithubIssuesFilters { + assignee?: string; + createdBy?: string; + labels?: string[]; + mentioned?: string; + milestone?: string; + states?: ('OPEN' | 'CLOSED')[]; +} + +/** + * @public + */ +export interface GithubIssuesOrdering { + field: 'CREATED_AT' | 'UPDATED_AT' | 'COMMENTS'; + direction?: 'ASC' | 'DESC'; +} + +/** + * @public + */ +export interface GitubIssuesByRepoOptions { + filterBy?: GithubIssuesFilters; + orderBy?: GithubIssuesOrdering; +} + /** @internal */ export const gitHubIssuesApiRef = createApiRef({ id: 'plugin.githubissues.service', @@ -103,6 +131,13 @@ export const gitHubIssuesApi = ( const fetchIssuesByRepoFromGitHub = async ( repos: Array, itemsPerRepo: number, + { + filterBy, + orderBy = { + field: 'UPDATED_AT', + direction: 'DESC', + }, + }: GitubIssuesByRepoOptions = {}, ): Promise => { const graphql = await getOctokit(); const safeNames: Array = []; @@ -129,7 +164,10 @@ export const gitHubIssuesApi = ( let issuesByRepo: IssuesByRepo = {}; try { issuesByRepo = await graphql( - createIssueByRepoQuery(repositories, itemsPerRepo), + createIssueByRepoQuery(repositories, itemsPerRepo, { + filterBy, + orderBy, + }), ); } catch (e) { if (e.data) { @@ -151,6 +189,34 @@ export const gitHubIssuesApi = ( return { fetchIssuesByRepoFromGitHub }; }; +function formatFilterValue( + value: GithubIssuesFilters[keyof GithubIssuesFilters], +): string { + if (Array.isArray(value)) { + return `[ ${value.map(formatFilterValue).join(', ')}]`; + } + + return typeof value === 'string' ? `\"${value}\"` : `${value}`; +} + +/** @internal */ +export function createFilterByClause(filterBy?: GithubIssuesFilters): string { + if (!filterBy) { + return ''; + } + + return Object.entries(filterBy) + .filter(value => value) + .map(([field, value]) => { + if (field === 'states') { + return `${field}: ${value.join(', ')}`; + } + + return `${field}: ${formatFilterValue(value)}`; + }) + .join(', '); +} + function createIssueByRepoQuery( repositories: Array<{ safeName: string; @@ -158,13 +224,15 @@ function createIssueByRepoQuery( owner: string; }>, itemsPerRepo: number, + { filterBy, orderBy }: GitubIssuesByRepoOptions, ): string { const fragment = ` fragment issues on Repository { issues( states: OPEN first: ${itemsPerRepo} - orderBy: { field: UPDATED_AT, direction: DESC } + filterBy: { ${createFilterByClause(filterBy)} } + orderBy: { field: ${orderBy?.field}, direction: ${orderBy?.direction} } ) { totalCount edges { diff --git a/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.tsx b/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.tsx index 574c15d3b3..cb6639c83c 100644 --- a/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.tsx +++ b/plugins/github-issues/src/components/GitHubIssues/GitHubIssues.tsx @@ -24,6 +24,10 @@ import { useGetIssuesByRepoFromGitHub } from '../../hooks/useGetIssuesByRepoFrom import { IssuesList } from './IssuesList'; import { NoRepositoriesInfo } from './NoRepositoriesInfo'; +import type { + GithubIssuesFilters, + GithubIssuesOrdering, +} from '../../api/gitHubIssuesApi'; /** * @public @@ -31,17 +35,22 @@ import { NoRepositoriesInfo } from './NoRepositoriesInfo'; export type GitHubIssuesProps = { itemsPerPage?: number; itemsPerRepo?: number; + filterBy?: GithubIssuesFilters; + orderBy?: GithubIssuesOrdering; }; export const GitHubIssues = (props: GitHubIssuesProps) => { - const { itemsPerPage = 10, itemsPerRepo = 40 } = props; + const { itemsPerPage = 10, itemsPerRepo = 40, filterBy, orderBy } = props; const { repositories } = useEntityGitHubRepositories(); const { isLoading, gitHubIssuesByRepo: issuesByRepository, retry, - } = useGetIssuesByRepoFromGitHub(repositories, itemsPerRepo); + } = useGetIssuesByRepoFromGitHub(repositories, itemsPerRepo, { + filterBy, + orderBy, + }); if (!repositories.length) { return ; diff --git a/plugins/github-issues/src/hooks/useEntityGitHubRepositories.ts b/plugins/github-issues/src/hooks/useEntityGitHubRepositories.ts index 242de514ca..d7b60575ad 100644 --- a/plugins/github-issues/src/hooks/useEntityGitHubRepositories.ts +++ b/plugins/github-issues/src/hooks/useEntityGitHubRepositories.ts @@ -13,13 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Entity } from '@backstage/catalog-model'; + +import { Entity, stringifyEntityRef } from '@backstage/catalog-model'; import { useApi } from '@backstage/core-plugin-api'; -import { - catalogApiRef, - humanizeEntityRef, - useEntity, -} from '@backstage/plugin-catalog-react'; +import { catalogApiRef, useEntity } from '@backstage/plugin-catalog-react'; import { useCallback, useEffect, useState } from 'react'; const GITHUB_PROJECT_SLUG_ANNOTATION = 'github.com/project-slug'; @@ -48,7 +45,7 @@ export function useEntityGitHubRepositories() { const entitiesList = await catalogApi.getEntities({ filter: { kind: ['Component', 'API'], - 'spec.owner': humanizeEntityRef(entity, { defaultKind: 'group' }), + 'relations.ownedBy': stringifyEntityRef(entity), }, }); diff --git a/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts b/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts index 8d1d0c2056..693be9e4d9 100644 --- a/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts +++ b/plugins/github-issues/src/hooks/useGetIssuesByRepoFromGitHub.ts @@ -16,11 +16,12 @@ import { useApi } from '@backstage/core-plugin-api'; import useAsyncRetry from 'react-use/lib/useAsyncRetry'; -import { gitHubIssuesApiRef } from '../api'; +import { gitHubIssuesApiRef, GitubIssuesByRepoOptions } from '../api'; export const useGetIssuesByRepoFromGitHub = ( repos: Array, itemsPerRepo: number, + options?: GitubIssuesByRepoOptions, ) => { const gitHubIssuesApi = useApi(gitHubIssuesApiRef); @@ -33,6 +34,7 @@ export const useGetIssuesByRepoFromGitHub = ( return await gitHubIssuesApi.fetchIssuesByRepoFromGitHub( repos, itemsPerRepo, + options, ); } diff --git a/plugins/github-issues/src/index.ts b/plugins/github-issues/src/index.ts index 5f6c248db7..7717ba5f33 100644 --- a/plugins/github-issues/src/index.ts +++ b/plugins/github-issues/src/index.ts @@ -20,3 +20,8 @@ export { } from './plugin'; export type { GitHubIssuesProps } from './components/GitHubIssues'; +export type { + GithubIssuesFilters, + GithubIssuesOrdering, + GitubIssuesByRepoOptions, +} from './api/gitHubIssuesApi'; diff --git a/plugins/github-pull-requests-board/CHANGELOG.md b/plugins/github-pull-requests-board/CHANGELOG.md index 061fca1187..d5c65069dd 100644 --- a/plugins/github-pull-requests-board/CHANGELOG.md +++ b/plugins/github-pull-requests-board/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-github-pull-requests-board +## 0.1.4 + +### Patch Changes + +- 719ccbb963: Properly filter on relations instead of the spec, when finding by owner +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## 0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/theme@0.2.16 + +## 0.1.4-next.1 + +### Patch Changes + +- 719ccbb963: Properly filter on relations instead of the spec, when finding by owner +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/theme@0.2.16 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + ## 0.1.3 ### Patch Changes diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json index 62826918a9..e4e9d0827a 100644 --- a/plugins/github-pull-requests-board/package.json +++ b/plugins/github-pull-requests-board/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-github-pull-requests-board", "description": "A Backstage plugin that allows you to see all open Pull Requests for all the repositories owned by your team", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -44,7 +44,7 @@ "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "@octokit/rest": "^19.0.3", - "moment": "^2.29.1", + "luxon": "^3.0.0", "react-use": "^17.2.4" }, "devDependencies": { diff --git a/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx b/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx index b905811aea..ed0d3580f0 100644 --- a/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx +++ b/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx @@ -13,12 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +import { stringifyEntityRef } from '@backstage/catalog-model'; import { useApi } from '@backstage/core-plugin-api'; -import { - catalogApiRef, - humanizeEntityRef, - useEntity, -} from '@backstage/plugin-catalog-react'; +import { catalogApiRef, useEntity } from '@backstage/plugin-catalog-react'; import { useCallback, useEffect, useState } from 'react'; import { getProjectNameFromEntity } from '../utils/functions'; @@ -29,9 +27,7 @@ export function useUserRepositories() { const getRepositoriesNames = useCallback(async () => { const entitiesList = await catalogApi.getEntities({ - filter: { - 'spec.owner': humanizeEntityRef(teamEntity, { defaultKind: 'group' }), - }, + filter: { 'relations.ownedBy': stringifyEntityRef(teamEntity) }, }); const entitiesNames: string[] = entitiesList.items.map(componentEntity => diff --git a/plugins/github-pull-requests-board/src/utils/functions.ts b/plugins/github-pull-requests-board/src/utils/functions.ts index 69b3ecf794..5644530689 100644 --- a/plugins/github-pull-requests-board/src/utils/functions.ts +++ b/plugins/github-pull-requests-board/src/utils/functions.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { Entity } from '@backstage/catalog-model'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { Reviews, PullRequests, @@ -58,7 +58,7 @@ export const filterSameUser = (users: Author[]): Author[] => { }; export const getElapsedTime = (start: string): string => { - return moment(start).fromNow(); + return DateTime.fromISO(start).toRelative() ?? start; }; export const formatPRsByReviewDecision = ( diff --git a/plugins/gitops-profiles/CHANGELOG.md b/plugins/gitops-profiles/CHANGELOG.md index 0845dcf919..b8512bcd2e 100644 --- a/plugins/gitops-profiles/CHANGELOG.md +++ b/plugins/gitops-profiles/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-gitops-profiles +## 0.3.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.3.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## 0.3.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.3.27 ### Patch Changes diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 9c2114a91f..02e8256eec 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gitops-profiles", "description": "A Backstage plugin that helps you manage GitOps profiles", - "version": "0.3.27", + "version": "0.3.28", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/gocd/CHANGELOG.md b/plugins/gocd/CHANGELOG.md index aa676a8c77..3d140260f4 100644 --- a/plugins/gocd/CHANGELOG.md +++ b/plugins/gocd/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-gocd +## 0.1.16 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.1.16-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## 0.1.16-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.1.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + ## 0.1.15 ### Patch Changes diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index 6ed5489b61..d455c6bb98 100644 --- a/plugins/gocd/package.json +++ b/plugins/gocd/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-gocd", "description": "A Backstage plugin that integrates towards GoCD", - "version": "0.1.15", + "version": "0.1.16", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/graphiql/CHANGELOG.md b/plugins/graphiql/CHANGELOG.md index fbd18a82db..c4cbfa1646 100644 --- a/plugins/graphiql/CHANGELOG.md +++ b/plugins/graphiql/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-graphiql +## 0.2.42 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.2.42-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.2.42-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## 0.2.42-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.2.41 ### Patch Changes diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 2ad33b0f9a..245155fc13 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphiql", "description": "Backstage plugin for browsing GraphQL APIs", - "version": "0.2.41", + "version": "0.2.42", "publishConfig": { "access": "public", "main": "dist/index.esm.js", diff --git a/plugins/graphql-backend/CHANGELOG.md b/plugins/graphql-backend/CHANGELOG.md index d25668d794..427ac6206d 100644 --- a/plugins/graphql-backend/CHANGELOG.md +++ b/plugins/graphql-backend/CHANGELOG.md @@ -1,5 +1,43 @@ # @backstage/plugin-graphql-backend +## 0.1.27 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-graphql@0.3.14 + - @backstage/config@1.0.3 + +## 0.1.27-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/plugin-catalog-graphql@0.3.14-next.2 + +## 0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/plugin-catalog-graphql@0.3.14-next.1 + +## 0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-graphql@0.3.14-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + ## 0.1.26 ### Patch Changes diff --git a/plugins/graphql-backend/package.json b/plugins/graphql-backend/package.json index 8575323fe6..0f44b3be6b 100644 --- a/plugins/graphql-backend/package.json +++ b/plugins/graphql-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphql-backend", "description": "An experimental Backstage backend plugin for GraphQL", - "version": "0.1.26", + "version": "0.1.27", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/graphql-backend/src/service/router.ts b/plugins/graphql-backend/src/service/router.ts index 773eebd608..aa83b51beb 100644 --- a/plugins/graphql-backend/src/service/router.ts +++ b/plugins/graphql-backend/src/service/router.ts @@ -58,7 +58,7 @@ export async function createRouter( const router = Router(); router.get('/health', (_, response) => { - response.send({ status: 'ok' }); + response.json({ status: 'ok' }); }); const apolloMiddleware = server.getMiddleware({ path: '/' }); diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index bbeec680e2..e64ac0be7e 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-home +## 0.4.26 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-stack-overflow@0.1.6 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## 0.4.26-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-stack-overflow@0.1.6-next.2 + +## 0.4.26-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/plugin-stack-overflow@0.1.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + +## 0.4.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-stack-overflow@0.1.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.4.25 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index bfa3d388c3..516a91cd74 100644 --- a/plugins/home/package.json +++ b/plugins/home/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-home", "description": "A Backstage plugin that helps you build a home page", - "version": "0.4.25", + "version": "0.4.26", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/ilert/CHANGELOG.md b/plugins/ilert/CHANGELOG.md index 9c228fdf99..dc2a3c1959 100644 --- a/plugins/ilert/CHANGELOG.md +++ b/plugins/ilert/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-ilert +## 0.1.36 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.1.36-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## 0.1.36-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.1.36-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + ## 0.1.35 ### Patch Changes diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index 391a9e495d..ffe74c2572 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-ilert", "description": "A Backstage plugin that integrates towards iLert", - "version": "0.1.35", + "version": "0.1.36", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/jenkins-backend/CHANGELOG.md b/plugins/jenkins-backend/CHANGELOG.md index 693c0d2988..105a79e2bf 100644 --- a/plugins/jenkins-backend/CHANGELOG.md +++ b/plugins/jenkins-backend/CHANGELOG.md @@ -1,5 +1,63 @@ # @backstage/plugin-jenkins-backend +## 0.1.27 + +### Patch Changes + +- b19ea927af: Fixed a bug where `extraRequestHeaders` configuration was ignored. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-jenkins-common@0.1.9 + +## 0.1.27-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-jenkins-common@0.1.9-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.1.27-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-jenkins-common@0.1.9-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## 0.1.27-next.0 + +### Patch Changes + +- b19ea927af: Fixed a bug where `extraRequestHeaders` configuration was ignored. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-jenkins-common@0.1.9-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + ## 0.1.26 ### Patch Changes diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index 7c2203a866..89c3076e7b 100644 --- a/plugins/jenkins-backend/package.json +++ b/plugins/jenkins-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins-backend", "description": "A Backstage backend plugin that integrates towards Jenkins", - "version": "0.1.26", + "version": "0.1.27", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/jenkins-backend/src/service/jenkinsInfoProvider.test.ts b/plugins/jenkins-backend/src/service/jenkinsInfoProvider.test.ts index 8e14997fd7..626e5b0a5a 100644 --- a/plugins/jenkins-backend/src/service/jenkinsInfoProvider.test.ts +++ b/plugins/jenkins-backend/src/service/jenkinsInfoProvider.test.ts @@ -55,7 +55,7 @@ describe('JenkinsConfig', () => { baseUrl: 'https://jenkins.example.com', username: 'backstage - bot', apiKey: '123456789abcdef0123456789abcedf012', - headers: { + extraRequestHeaders: { myHeader: 'my-value', }, }, @@ -70,7 +70,7 @@ describe('JenkinsConfig', () => { baseUrl: 'https://jenkins.example.com', username: 'backstage - bot', apiKey: '123456789abcdef0123456789abcedf012', - headers: { + extraRequestHeaders: { myHeader: 'my-value', }, }, diff --git a/plugins/jenkins-backend/src/service/jenkinsInfoProvider.ts b/plugins/jenkins-backend/src/service/jenkinsInfoProvider.ts index 2ad5b60f6d..c4b4ef58dd 100644 --- a/plugins/jenkins-backend/src/service/jenkinsInfoProvider.ts +++ b/plugins/jenkins-backend/src/service/jenkinsInfoProvider.ts @@ -84,7 +84,7 @@ export class JenkinsConfig { baseUrl: c.getString('baseUrl'), username: c.getString('username'), apiKey: c.getString('apiKey'), - headers: c.getOptional('headers'), + extraRequestHeaders: c.getOptional('extraRequestHeaders'), crumbIssuer: c.getOptionalBoolean('crumbIssuer'), })) || []; diff --git a/plugins/jenkins-common/CHANGELOG.md b/plugins/jenkins-common/CHANGELOG.md index a1d29726fd..cebfb0b0a5 100644 --- a/plugins/jenkins-common/CHANGELOG.md +++ b/plugins/jenkins-common/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-jenkins-common +## 0.1.9 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-permission-common@0.7.0 + +## 0.1.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + +## 0.1.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## 0.1.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + ## 0.1.8 ### Patch Changes diff --git a/plugins/jenkins-common/package.json b/plugins/jenkins-common/package.json index fd752b9d84..01215b674d 100644 --- a/plugins/jenkins-common/package.json +++ b/plugins/jenkins-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins-common", - "version": "0.1.8", + "version": "0.1.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index ff4612ba8f..ff0df1f256 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,57 @@ # @backstage/plugin-jenkins +## 0.7.9 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-jenkins-common@0.1.9 + +## 0.7.9-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-jenkins-common@0.1.9-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## 0.7.9-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-jenkins-common@0.1.9-next.1 + +## 0.7.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-jenkins-common@0.1.9-next.0 + ## 0.7.8 ### Patch Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index 18b4507b48..9927fb38ae 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-jenkins", "description": "A Backstage plugin that integrates towards Jenkins", - "version": "0.7.8", + "version": "0.7.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kafka-backend/CHANGELOG.md b/plugins/kafka-backend/CHANGELOG.md index 8294bcd1aa..16dbd0612b 100644 --- a/plugins/kafka-backend/CHANGELOG.md +++ b/plugins/kafka-backend/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-kafka-backend +## 0.2.30 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.2.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.2.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## 0.2.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + ## 0.2.29 ### Patch Changes diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json index 5c56582454..677dad0f01 100644 --- a/plugins/kafka-backend/package.json +++ b/plugins/kafka-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka-backend", "description": "A Backstage backend plugin that integrates towards Kafka", - "version": "0.2.29", + "version": "0.2.30", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index ca3383b39e..5b98e9ffb7 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-kafka +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.3.9 ### Patch Changes diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 228b2d6bc9..966c3962a6 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kafka", "description": "A Backstage plugin that integrates towards Kafka", - "version": "0.3.9", + "version": "0.3.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 135bb3cd6e..085617f8af 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,60 @@ # @backstage/plugin-kubernetes-backend +## 0.7.3 + +### Patch Changes + +- de676888bc: Added missing cluster locator configuration schema entries, for the catalog and local proxy types +- d4a8c683be: kubernetes service locator now take request context parameters +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-kubernetes-common@0.4.3 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.7.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-kubernetes-common@0.4.3-next.2 + +## 0.7.3-next.1 + +### Patch Changes + +- d4a8c683be: kubernetes service locator now take request context parameters +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-kubernetes-common@0.4.3-next.1 + +## 0.7.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-kubernetes-common@0.4.3-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + ## 0.7.2 ### Patch Changes diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index 4219387819..d51ec4c211 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -22,7 +22,6 @@ import { Logger } from 'winston'; import { Metrics } from '@kubernetes/client-node'; import type { ObjectsByEntityResponse } from '@backstage/plugin-kubernetes-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; -import { PodStatus } from '@kubernetes/client-node/dist/top'; import { TokenCredential } from '@azure/identity'; // @alpha (undocumented) @@ -267,10 +266,10 @@ export interface KubernetesFetcher { params: ObjectFetchParams, ): Promise; // (undocumented) - fetchPodMetricsByNamespace( + fetchPodMetricsByNamespaces( clusterDetails: ClusterDetails, - namespace: string, - ): Promise; + namespaces: Set, + ): Promise; } // @alpha (undocumented) @@ -326,7 +325,10 @@ export type KubernetesObjectTypes = // @alpha export interface KubernetesServiceLocator { // (undocumented) - getClustersByEntity(entity: Entity): Promise<{ + getClustersByEntity( + entity: Entity, + requestContext: ServiceLocatorRequestContext, + ): Promise<{ clusters: ClusterDetails[]; }>; } @@ -403,6 +405,14 @@ export interface ServiceAccountClusterDetails extends ClusterDetails {} // @alpha (undocumented) export type ServiceLocatorMethod = 'multiTenant' | 'http'; +// @alpha (undocumented) +export interface ServiceLocatorRequestContext { + // (undocumented) + customResources: CustomResourceMatcher[]; + // (undocumented) + objectTypesToFetch: Set; +} + // @alpha (undocumented) export type SigningCreds = { accessKeyId: string | undefined; diff --git a/plugins/kubernetes-backend/schema.d.ts b/plugins/kubernetes-backend/config.d.ts similarity index 94% rename from plugins/kubernetes-backend/schema.d.ts rename to plugins/kubernetes-backend/config.d.ts index 59fa7a5d97..2a514394d8 100644 --- a/plugins/kubernetes-backend/schema.d.ts +++ b/plugins/kubernetes-backend/config.d.ts @@ -31,18 +31,6 @@ export interface Config { type: 'multiTenant'; }; clusterLocatorMethods: Array< - | { - /** @visibility frontend */ - type: 'gke'; - /** @visibility frontend */ - projectId: string; - /** @visibility frontend */ - region?: string; - /** @visibility frontend */ - skipTLSVerify?: boolean; - /** @visibility frontend */ - skipMetricsLookup?: boolean; - } | { /** @visibility frontend */ type: 'config'; @@ -68,6 +56,26 @@ export interface Config { skipMetricsLookup?: boolean; }>; } + | { + /** @visibility frontend */ + type: 'catalog'; + } + | { + /** @visibility frontend */ + type: 'localKubectlProxy'; + } + | { + /** @visibility frontend */ + type: 'gke'; + /** @visibility frontend */ + projectId: string; + /** @visibility frontend */ + region?: string; + /** @visibility frontend */ + skipTLSVerify?: boolean; + /** @visibility frontend */ + skipMetricsLookup?: boolean; + } >; customResources?: Array<{ group: string; diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 9adb3d4c22..3e96aeb347 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-backend", "description": "A Backstage backend plugin that integrates towards Kubernetes", - "version": "0.7.2", + "version": "0.7.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,7 +23,7 @@ "backstage", "kubernetes" ], - "configSchema": "schema.d.ts", + "configSchema": "config.d.ts", "scripts": { "start": "backstage-cli package start", "build": "backstage-cli package build", @@ -69,6 +69,6 @@ }, "files": [ "dist", - "schema.d.ts" + "config.d.ts" ] } diff --git a/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.test.ts b/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.test.ts index 2ead460848..b40503efc9 100644 --- a/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.test.ts +++ b/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.test.ts @@ -16,6 +16,7 @@ import '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; +import { ServiceLocatorRequestContext } from '../types/types'; import { MultiTenantServiceLocator } from './MultiTenantServiceLocator'; describe('MultiTenantConfigClusterLocator', () => { @@ -24,7 +25,10 @@ describe('MultiTenantConfigClusterLocator', () => { getClusters: async () => [], }); - const result = await sut.getClustersByEntity({} as Entity); + const result = await sut.getClustersByEntity( + {} as Entity, + {} as ServiceLocatorRequestContext, + ); expect(result).toStrictEqual({ clusters: [] }); }); @@ -43,7 +47,10 @@ describe('MultiTenantConfigClusterLocator', () => { }, }); - const result = await sut.getClustersByEntity({} as Entity); + const result = await sut.getClustersByEntity( + {} as Entity, + {} as ServiceLocatorRequestContext, + ); expect(result).toStrictEqual({ clusters: [ @@ -76,7 +83,10 @@ describe('MultiTenantConfigClusterLocator', () => { }, }); - const result = await sut.getClustersByEntity({} as Entity); + const result = await sut.getClustersByEntity( + {} as Entity, + {} as ServiceLocatorRequestContext, + ); expect(result).toStrictEqual({ clusters: [ diff --git a/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.ts b/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.ts index 7bafbb10f3..ed902b194a 100644 --- a/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.ts +++ b/plugins/kubernetes-backend/src/service-locator/MultiTenantServiceLocator.ts @@ -19,6 +19,7 @@ import { ClusterDetails, KubernetesClustersSupplier, KubernetesServiceLocator, + ServiceLocatorRequestContext, } from '../types/types'; // This locator assumes that every service is located on every cluster @@ -33,6 +34,7 @@ export class MultiTenantServiceLocator implements KubernetesServiceLocator { // As this implementation always returns all clusters serviceId is ignored here getClustersByEntity( _entity: Entity, + _requestContext: ServiceLocatorRequestContext, ): Promise<{ clusters: ClusterDetails[] }> { return this.clusterSupplier.getClusters().then(clusters => ({ clusters })); } diff --git a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts index ed56c2da8a..2766f9abfc 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesBuilder.test.ts @@ -30,7 +30,6 @@ import { } from '../types/types'; import { KubernetesBuilder } from './KubernetesBuilder'; import { KubernetesFanOutHandler } from './KubernetesFanOutHandler'; -import { PodStatus } from '@kubernetes/client-node'; import { CatalogApi } from '@backstage/catalog-client'; describe('KubernetesBuilder', () => { @@ -222,11 +221,11 @@ describe('KubernetesBuilder', () => { }; const fetcher: KubernetesFetcher = { - fetchPodMetricsByNamespace( + fetchPodMetricsByNamespaces( _clusterDetails: ClusterDetails, - _namespace: string, - ): Promise { - return Promise.resolve([]); + _namespaces: Set, + ): Promise { + return Promise.resolve({ errors: [], responses: [] }); }, fetchObjectsForService( _params: ObjectFetchParams, diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts index 04256ed3dc..1af5371e53 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.test.ts @@ -18,13 +18,13 @@ import { getVoidLogger } from '@backstage/backend-common'; import { ClusterDetails, CustomResource, + FetchResponseWrapper, ObjectFetchParams, } from '../types/types'; import { KubernetesFanOutHandler } from './KubernetesFanOutHandler'; -import { PodStatus } from '@kubernetes/client-node/dist/top'; const fetchObjectsForService = jest.fn(); -const fetchPodMetricsByNamespace = jest.fn(); +const fetchPodMetricsByNamespaces = jest.fn(); const getClustersByEntity = jest.fn(); @@ -55,8 +55,9 @@ const mockFetch = (mock: jest.Mock) => { }; const mockMetrics = (mock: jest.Mock) => { - mock.mockImplementation((clusterDetails: ClusterDetails, namespace: string) => - Promise.resolve(generatePodStatus(clusterDetails.name, namespace)), + mock.mockImplementation( + (clusterDetails: ClusterDetails, namespaces: Set) => + Promise.resolve(generatePodStatus(clusterDetails.name, namespaces)), ); }; @@ -143,7 +144,7 @@ function mockFetchAndGetKubernetesFanOutHandler( customResources: CustomResource[], ) { mockFetch(fetchObjectsForService); - mockMetrics(fetchPodMetricsByNamespace); + mockMetrics(fetchPodMetricsByNamespaces); return getKubernetesFanOutHandler(customResources); } @@ -153,7 +154,7 @@ function getKubernetesFanOutHandler(customResources: CustomResource[]) { logger: getVoidLogger(), fetcher: { fetchObjectsForService, - fetchPodMetricsByNamespace, + fetchPodMetricsByNamespaces, }, serviceLocator: { getClustersByEntity, @@ -164,24 +165,32 @@ function getKubernetesFanOutHandler(customResources: CustomResource[]) { function generatePodStatus( _clusterName: string, - _namespace: string, -): PodStatus[] { - return [ - { - Pod: {}, - CPU: { - CurrentUsage: 100, - RequestTotal: 101, - LimitTotal: 102, - }, - Memory: { - CurrentUsage: BigInt('1000'), - RequestTotal: BigInt('1001'), - LimitTotal: BigInt('1002'), - }, - Containers: [], - }, - ] as any; + _namespaces: Set, +): FetchResponseWrapper { + return { + errors: [], + responses: Array.from(_namespaces).map(() => { + return { + type: 'podstatus', + resources: [ + { + Pod: {}, + CPU: { + CurrentUsage: 100, + RequestTotal: 101, + LimitTotal: 102, + }, + Memory: { + CurrentUsage: BigInt('1000'), + RequestTotal: BigInt('1001'), + LimitTotal: BigInt('1002'), + }, + Containers: [], + }, + ], + }; + }), + }; } function generateMockResourcesAndErrors( @@ -292,9 +301,9 @@ describe('getKubernetesObjectsByEntity', () => { expect(getClustersByEntity.mock.calls.length).toBe(1); expect(fetchObjectsForService.mock.calls.length).toBe(1); - expect(fetchPodMetricsByNamespace.mock.calls.length).toBe(1); - expect(fetchPodMetricsByNamespace.mock.calls[0][1]).toBe( - 'ns-test-component-test-cluster', + expect(fetchPodMetricsByNamespaces.mock.calls.length).toBe(1); + expect(fetchPodMetricsByNamespaces.mock.calls[0][1]).toStrictEqual( + new Set(['ns-test-component-test-cluster']), ); expect(result).toStrictEqual({ @@ -433,7 +442,7 @@ describe('getKubernetesObjectsByEntity', () => { }), ); - mockMetrics(fetchPodMetricsByNamespace); + mockMetrics(fetchPodMetricsByNamespaces); const sut = getKubernetesFanOutHandler([]); @@ -444,9 +453,10 @@ describe('getKubernetesObjectsByEntity', () => { expect(getClustersByEntity.mock.calls.length).toBe(1); expect(fetchObjectsForService.mock.calls.length).toBe(1); - expect(fetchPodMetricsByNamespace.mock.calls.length).toBe(2); - expect(fetchPodMetricsByNamespace.mock.calls[0][1]).toBe('ns-a'); - expect(fetchPodMetricsByNamespace.mock.calls[1][1]).toBe('ns-b'); + expect(fetchPodMetricsByNamespaces.mock.calls.length).toBe(1); + expect(fetchPodMetricsByNamespaces.mock.calls[0][1]).toStrictEqual( + new Set(['ns-a', 'ns-b']), + ); expect(result).toStrictEqual({ items: [ @@ -665,6 +675,82 @@ describe('getKubernetesObjectsByEntity', () => { ], }); }); + it('retrieve objects for two clusters, one fails to fetch pod metrics', async () => { + getClustersByEntity.mockImplementation(() => + Promise.resolve({ + clusters: [ + { + name: 'test-cluster', + authProvider: 'serviceAccount', + dashboardUrl: 'https://k8s.foo.coom', + }, + { + name: 'other-cluster', + authProvider: 'google', + }, + ], + }), + ); + + mockFetch(fetchObjectsForService); + + // To simulate the partial failure, return a valid response for the first call, + // and an error for the second call. + fetchPodMetricsByNamespaces + .mockImplementationOnce( + (clusterDetails: ClusterDetails, namespaces: Set) => + Promise.resolve(generatePodStatus(clusterDetails.name, namespaces)), + ) + .mockResolvedValueOnce({ + errors: [ + { + errorType: 'NOT_FOUND', + resourcePath: '/some/path', + statusCode: 404, + }, + ], + responses: [], + }); + + const sut = getKubernetesFanOutHandler([]); + + const result = await sut.getKubernetesObjectsByEntity({ + entity, + auth: { + google: 'google_token_123', + }, + }); + + expect(getClustersByEntity.mock.calls.length).toBe(1); + expect(fetchObjectsForService.mock.calls.length).toBe(2); + expect(result).toStrictEqual({ + items: [ + { + cluster: { + dashboardUrl: 'https://k8s.foo.coom', + name: 'test-cluster', + }, + errors: [], + podMetrics: [POD_METRICS_FIXTURE], + resources: resourcesByCluster('test-cluster'), + }, + { + cluster: { + name: 'other-cluster', + }, + errors: [ + { + errorType: 'NOT_FOUND', + resourcePath: '/some/path', + statusCode: 404, + }, + ], + podMetrics: [], + resources: resourcesByCluster('other-cluster'), + }, + ], + }); + }); }); describe('getCustomResourcesByEntity', () => { diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index 23163d1004..1fa9d23837 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -27,6 +27,7 @@ import { CustomResource, CustomResourcesByEntity, KubernetesObjectsByEntity, + ServiceLocatorRequestContext, } from '../types/types'; import { KubernetesAuthTranslator } from '../kubernetes-auth-translator/types'; import { KubernetesAuthTranslatorGenerator } from '../kubernetes-auth-translator/KubernetesAuthTranslatorGenerator'; @@ -40,6 +41,7 @@ import { PodFetchResponse, KubernetesRequestAuth, CustomResourceMatcher, + PodStatusFetchResponse, } from '@backstage/plugin-kubernetes-common'; import { ContainerStatus, @@ -162,19 +164,22 @@ const toClientSafeContainer = ( }; const toClientSafePodMetrics = ( - podMetrics: PodStatus[][], + podMetrics: PodStatusFetchResponse[], ): ClientPodStatus[] => { - return podMetrics.flat().map((pd: PodStatus): ClientPodStatus => { - return { - pod: pd.Pod, - memory: toClientSafeResource(pd.Memory), - cpu: toClientSafeResource(pd.CPU), - containers: pd.Containers.map(toClientSafeContainer), - }; - }); + return podMetrics + .map(r => r.resources) + .flat() + .map((pd: PodStatus): ClientPodStatus => { + return { + pod: pd.Pod, + memory: toClientSafeResource(pd.Memory), + cpu: toClientSafeResource(pd.CPU), + containers: pd.Containers.map(toClientSafeContainer), + }; + }); }; -type responseWithMetrics = [FetchResponseWrapper, PodStatus[][]]; +type responseWithMetrics = [FetchResponseWrapper, PodStatusFetchResponse[]]; export class KubernetesFanOutHandler { private readonly logger: Logger; @@ -231,7 +236,10 @@ export class KubernetesFanOutHandler { entity.metadata?.name; const clusterDetailsDecoratedForAuth: ClusterDetails[] = - await this.decorateClusterDetailsWithAuth(entity, auth); + await this.decorateClusterDetailsWithAuth(entity, auth, { + objectTypesToFetch: objectTypesToFetch, + customResources: customResources ?? [], + }); this.logger.info( `entity.metadata.name=${entityName} clusterDetails=[${clusterDetailsDecoratedForAuth @@ -274,9 +282,10 @@ export class KubernetesFanOutHandler { private async decorateClusterDetailsWithAuth( entity: Entity, auth: KubernetesRequestAuth, + requestContext: ServiceLocatorRequestContext, ) { const clusterDetails: ClusterDetails[] = await ( - await this.serviceLocator.getClustersByEntity(entity) + await this.serviceLocator.getClustersByEntity(entity, requestContext) ).clusters; // Execute all of these async actions simultaneously/without blocking sequentially as no common object is modified by them @@ -345,11 +354,17 @@ export class KubernetesFanOutHandler { .filter(isString), ); - const podMetrics = Array.from(namespaces).map(ns => - this.fetcher.fetchPodMetricsByNamespace(clusterDetails, ns), + if (namespaces.size === 0) { + return [result, []]; + } + + const podMetrics = await this.fetcher.fetchPodMetricsByNamespaces( + clusterDetails, + namespaces, ); - return Promise.all([result, Promise.all(podMetrics)]); + result.errors.push(...podMetrics.errors); + return [result, podMetrics.responses as PodStatusFetchResponse[]]; } private getAuthTranslator(provider: string): KubernetesAuthTranslator { diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts index 25a5c9312b..c2560e58b0 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.test.ts @@ -17,6 +17,12 @@ import { getVoidLogger } from '@backstage/backend-common'; import { KubernetesClientBasedFetcher } from './KubernetesFetcher'; import { ObjectToFetch } from '../types/types'; +import { topPods } from '@kubernetes/client-node'; + +jest.mock('@kubernetes/client-node', () => ({ + ...jest.requireActual('@kubernetes/client-node'), + topPods: jest.fn(), +})); const OBJECTS_TO_FETCH = new Set([ { @@ -33,63 +39,52 @@ const OBJECTS_TO_FETCH = new Set([ }, ]); +const POD_METRICS_FIXTURE = { + containers: [], + cpu: { + currentUsage: 100, + limitTotal: 102, + requestTotal: 101, + }, + memory: { + currentUsage: '1000', + limitTotal: '1002', + requestTotal: '1001', + }, + pod: {}, +}; + describe('KubernetesFetcher', () => { - let clientMock: any; - let kubernetesClientProvider: any; - let sut: KubernetesClientBasedFetcher; + describe('fetchObjectsForService', () => { + let clientMock: any; + let kubernetesClientProvider: any; + let sut: KubernetesClientBasedFetcher; - beforeEach(() => { - jest.resetAllMocks(); - clientMock = { - listClusterCustomObject: jest.fn(), - listNamespacedCustomObject: jest.fn(), - addInterceptor: jest.fn(), - }; + beforeEach(() => { + jest.resetAllMocks(); + clientMock = { + listClusterCustomObject: jest.fn(), + listNamespacedCustomObject: jest.fn(), + addInterceptor: jest.fn(), + }; - kubernetesClientProvider = { - getCustomObjectsClient: jest.fn(() => clientMock), - }; + kubernetesClientProvider = { + getCustomObjectsClient: jest.fn(() => clientMock), + }; - sut = new KubernetesClientBasedFetcher({ - kubernetesClientProvider, - logger: getVoidLogger(), - }); - }); - - const testErrorResponse = async (errorResponse: any, expectedResult: any) => { - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'pod-name', - }, - }, - ], - }, + sut = new KubernetesClientBasedFetcher({ + kubernetesClientProvider, + logger: getVoidLogger(), + }); }); - clientMock.listClusterCustomObject.mockRejectedValue(errorResponse); - - const result = await sut.fetchObjectsForService({ - serviceId: 'some-service', - clusterDetails: { - name: 'cluster1', - url: 'http://localhost:9999', - serviceAccountToken: 'token', - authProvider: 'serviceAccount', - }, - objectTypesToFetch: OBJECTS_TO_FETCH, - labelSelector: '', - customResources: [], - }); - - expect(result).toStrictEqual({ - errors: [expectedResult], - responses: [ - { - type: 'pods', - resources: [ + const testErrorResponse = async ( + errorResponse: any, + expectedResult: any, + ) => { + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ { metadata: { name: 'pod-name', @@ -97,82 +92,72 @@ describe('KubernetesFetcher', () => { }, ], }, - ], - }); + }); - expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(2); + clientMock.listClusterCustomObject.mockRejectedValue(errorResponse); - expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ - '', - 'v1', - 'pods', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); + const result = await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + objectTypesToFetch: OBJECTS_TO_FETCH, + labelSelector: '', + customResources: [], + }); - expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ - '', - 'v1', - 'services', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect( - kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, - ).toBe(2); - }; - - it('should return pods, services', async () => { - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ + expect(result).toStrictEqual({ + errors: [expectedResult], + responses: [ { - metadata: { - name: 'pod-name', - }, + type: 'pods', + resources: [ + { + metadata: { + name: 'pod-name', + }, + }, + ], }, ], - }, - }); + }); - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'service-name', - }, - }, - ], - }, - }); + expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(2); - const result = await sut.fetchObjectsForService({ - serviceId: 'some-service', - clusterDetails: { - name: 'cluster1', - url: 'http://localhost:9999', - serviceAccountToken: 'token', - authProvider: 'serviceAccount', - }, - objectTypesToFetch: OBJECTS_TO_FETCH, - labelSelector: '', - customResources: [], - }); + expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ + '', + 'v1', + 'pods', + '', + false, + '', + '', + 'backstage.io/kubernetes-id=some-service', + ]); - expect(result).toStrictEqual({ - errors: [], - responses: [ - { - type: 'pods', - resources: [ + expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ + '', + 'v1', + 'services', + '', + false, + '', + '', + 'backstage.io/kubernetes-id=some-service', + ]); + + expect( + kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, + ).toBe(2); + }; + + it('should return pods, services', async () => { + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ { metadata: { name: 'pod-name', @@ -180,9 +165,11 @@ describe('KubernetesFetcher', () => { }, ], }, - { - type: 'services', - resources: [ + }); + + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ { metadata: { name: 'service-name', @@ -190,100 +177,79 @@ describe('KubernetesFetcher', () => { }, ], }, - ], - }); + }); - expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(2); - - expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ - '', - 'v1', - 'pods', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ - '', - 'v1', - 'services', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect( - kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, - ).toBe(2); - }); - it('should return pods, services and customobjects', async () => { - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'pod-name', - }, - }, - ], - }, - }); - - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'service-name', - }, - }, - ], - }, - }); - - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'something-else', - }, - }, - ], - }, - }); - - const result = await sut.fetchObjectsForService({ - serviceId: 'some-service', - clusterDetails: { - name: 'cluster1', - url: 'http://localhost:9999', - serviceAccountToken: 'token', - authProvider: 'serviceAccount', - }, - objectTypesToFetch: OBJECTS_TO_FETCH, - labelSelector: '', - customResources: [ - { - objectType: 'customresources', - group: 'some-group', - apiVersion: 'v2', - plural: 'things', + const result = await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', }, - ], - }); + objectTypesToFetch: OBJECTS_TO_FETCH, + labelSelector: '', + customResources: [], + }); - expect(result).toStrictEqual({ - errors: [], - responses: [ - { - type: 'pods', - resources: [ + expect(result).toStrictEqual({ + errors: [], + responses: [ + { + type: 'pods', + resources: [ + { + metadata: { + name: 'pod-name', + }, + }, + ], + }, + { + type: 'services', + resources: [ + { + metadata: { + name: 'service-name', + }, + }, + ], + }, + ], + }); + + expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(2); + + expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ + '', + 'v1', + 'pods', + '', + false, + '', + '', + 'backstage.io/kubernetes-id=some-service', + ]); + + expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ + '', + 'v1', + 'services', + '', + false, + '', + '', + 'backstage.io/kubernetes-id=some-service', + ]); + + expect( + kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, + ).toBe(2); + }); + it('should return pods, services and customobjects', async () => { + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ { metadata: { name: 'pod-name', @@ -291,9 +257,11 @@ describe('KubernetesFetcher', () => { }, ], }, - { - type: 'services', - resources: [ + }); + + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ { metadata: { name: 'service-name', @@ -301,9 +269,11 @@ describe('KubernetesFetcher', () => { }, ], }, - { - type: 'customresources', - resources: [ + }); + + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ { metadata: { name: 'something-else', @@ -311,216 +281,358 @@ describe('KubernetesFetcher', () => { }, ], }, - ], + }); + + const result = await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + objectTypesToFetch: OBJECTS_TO_FETCH, + labelSelector: '', + customResources: [ + { + objectType: 'customresources', + group: 'some-group', + apiVersion: 'v2', + plural: 'things', + }, + ], + }); + + expect(result).toStrictEqual({ + errors: [], + responses: [ + { + type: 'pods', + resources: [ + { + metadata: { + name: 'pod-name', + }, + }, + ], + }, + { + type: 'services', + resources: [ + { + metadata: { + name: 'service-name', + }, + }, + ], + }, + { + type: 'customresources', + resources: [ + { + metadata: { + name: 'something-else', + }, + }, + ], + }, + ], + }); + + expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(3); + + expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ + '', + 'v1', + 'pods', + '', + false, + '', + '', + 'backstage.io/kubernetes-id=some-service', + ]); + + expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ + '', + 'v1', + 'services', + '', + false, + '', + '', + 'backstage.io/kubernetes-id=some-service', + ]); + + expect(clientMock.listClusterCustomObject.mock.calls[2]).toEqual([ + 'some-group', + 'v2', + 'things', + '', + false, + '', + '', + 'backstage.io/kubernetes-id=some-service', + ]); + + expect( + kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, + ).toBe(3); }); - - expect(clientMock.listClusterCustomObject.mock.calls.length).toBe(3); - - expect(clientMock.listClusterCustomObject.mock.calls[0]).toEqual([ - '', - 'v1', - 'pods', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect(clientMock.listClusterCustomObject.mock.calls[1]).toEqual([ - '', - 'v1', - 'services', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect(clientMock.listClusterCustomObject.mock.calls[2]).toEqual([ - 'some-group', - 'v2', - 'things', - '', - false, - '', - '', - 'backstage.io/kubernetes-id=some-service', - ]); - - expect( - kubernetesClientProvider.getCustomObjectsClient.mock.calls.length, - ).toBe(3); - }); - // they're in testErrorResponse - // eslint-disable-next-line jest/expect-expect - it('should return pods, bad request error', async () => { - await testErrorResponse( - { - response: { + // they're in testErrorResponse + // eslint-disable-next-line jest/expect-expect + it('should return pods, bad request error', async () => { + await testErrorResponse( + { + response: { + statusCode: 400, + request: { + uri: { + pathname: '/some/path', + }, + }, + }, + }, + { + errorType: 'BAD_REQUEST', + resourcePath: '/some/path', statusCode: 400, - request: { - uri: { - pathname: '/some/path', + }, + ); + }); + // they're in testErrorResponse + // eslint-disable-next-line jest/expect-expect + it('should return pods, unauthorized error', async () => { + await testErrorResponse( + { + response: { + statusCode: 401, + request: { + uri: { + pathname: '/some/path', + }, }, }, }, - }, - { - errorType: 'BAD_REQUEST', - resourcePath: '/some/path', - statusCode: 400, - }, - ); - }); - // they're in testErrorResponse - // eslint-disable-next-line jest/expect-expect - it('should return pods, unauthorized error', async () => { - await testErrorResponse( - { - response: { + { + errorType: 'UNAUTHORIZED_ERROR', + resourcePath: '/some/path', statusCode: 401, - request: { - uri: { - pathname: '/some/path', + }, + ); + }); + // they're in testErrorResponse + // eslint-disable-next-line jest/expect-expect + it('should return pods, system error', async () => { + await testErrorResponse( + { + response: { + statusCode: 500, + request: { + uri: { + pathname: '/some/path', + }, }, }, }, - }, - { - errorType: 'UNAUTHORIZED_ERROR', - resourcePath: '/some/path', - statusCode: 401, - }, - ); - }); - // they're in testErrorResponse - // eslint-disable-next-line jest/expect-expect - it('should return pods, system error', async () => { - await testErrorResponse( - { - response: { + { + errorType: 'SYSTEM_ERROR', + resourcePath: '/some/path', statusCode: 500, - request: { - uri: { - pathname: '/some/path', + }, + ); + }); + // they're in testErrorResponse + // eslint-disable-next-line jest/expect-expect + it('should return pods, unknown error', async () => { + await testErrorResponse( + { + response: { + statusCode: 900, + request: { + uri: { + pathname: '/some/path', + }, }, }, }, - }, - { - errorType: 'SYSTEM_ERROR', - resourcePath: '/some/path', - statusCode: 500, - }, - ); - }); - // they're in testErrorResponse - // eslint-disable-next-line jest/expect-expect - it('should return pods, unknown error', async () => { - await testErrorResponse( - { - response: { + { + errorType: 'UNKNOWN_ERROR', + resourcePath: '/some/path', statusCode: 900, - request: { - uri: { - pathname: '/some/path', - }, - }, }, - }, - { - errorType: 'UNKNOWN_ERROR', - resourcePath: '/some/path', - statusCode: 900, - }, - ); + ); + }); + it('should always add a labelSelector query', async () => { + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ + { + metadata: { + name: 'pod-name', + }, + }, + ], + }, + }); + + clientMock.listClusterCustomObject.mockResolvedValueOnce({ + body: { + items: [ + { + metadata: { + name: 'service-name', + }, + }, + ], + }, + }); + + await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + objectTypesToFetch: OBJECTS_TO_FETCH, + labelSelector: '', + customResources: [], + }); + + const mockCall = clientMock.listClusterCustomObject.mock.calls[0]; + const actualSelector = mockCall[mockCall.length - 1]; + const expectedSelector = 'backstage.io/kubernetes-id=some-service'; + expect(actualSelector).toBe(expectedSelector); + }); + it('should use namespace if provided', async () => { + clientMock.listNamespacedCustomObject.mockResolvedValueOnce({ + body: { + items: [ + { + metadata: { + name: 'pod-name', + }, + }, + ], + }, + }); + + clientMock.listNamespacedCustomObject.mockResolvedValueOnce({ + body: { + items: [ + { + metadata: { + name: 'service-name', + }, + }, + ], + }, + }); + + await sut.fetchObjectsForService({ + serviceId: 'some-service', + clusterDetails: { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + objectTypesToFetch: OBJECTS_TO_FETCH, + labelSelector: '', + namespace: 'some-namespace', + customResources: [], + }); + + const mockCall = clientMock.listNamespacedCustomObject.mock.calls[0]; + const namespace = mockCall[2]; + expect(namespace).toBe('some-namespace'); + }); }); - it('should always add a labelSelector query', async () => { - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ + + describe('fetchPodMetricsByNamespaces', () => { + let kubernetesClientProvider: any; + let sut: KubernetesClientBasedFetcher; + + beforeEach(() => { + jest.resetAllMocks(); + + kubernetesClientProvider = { + getMetricsClient: jest.fn(), + getCoreClientByClusterDetails: jest.fn(), + }; + + sut = new KubernetesClientBasedFetcher({ + kubernetesClientProvider, + logger: getVoidLogger(), + }); + }); + + it('should return pod metrics', async () => { + (topPods as jest.Mock).mockResolvedValue(POD_METRICS_FIXTURE); + + const result = await sut.fetchPodMetricsByNamespaces( + { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + new Set(['ns-a', 'ns-b']), + ); + expect(result).toStrictEqual({ + errors: [], + responses: [ { - metadata: { - name: 'pod-name', - }, + type: 'podstatus', + resources: POD_METRICS_FIXTURE, + }, + { + type: 'podstatus', + resources: POD_METRICS_FIXTURE, }, ], - }, + }); }); - - clientMock.listClusterCustomObject.mockResolvedValueOnce({ - body: { - items: [ - { - metadata: { - name: 'service-name', + it('should return pod metrics and error', async () => { + const topPodsMock = topPods as jest.Mock; + topPodsMock + .mockResolvedValueOnce(POD_METRICS_FIXTURE) + .mockRejectedValueOnce({ + response: { + statusCode: 404, + request: { + uri: { + pathname: '/some/path', + }, }, }, - ], - }, - }); + }); - await sut.fetchObjectsForService({ - serviceId: 'some-service', - clusterDetails: { - name: 'cluster1', - url: 'http://localhost:9999', - serviceAccountToken: 'token', - authProvider: 'serviceAccount', - }, - objectTypesToFetch: OBJECTS_TO_FETCH, - labelSelector: '', - customResources: [], - }); - - const mockCall = clientMock.listClusterCustomObject.mock.calls[0]; - const actualSelector = mockCall[mockCall.length - 1]; - const expectedSelector = 'backstage.io/kubernetes-id=some-service'; - expect(actualSelector).toBe(expectedSelector); - }); - it('should use namespace if provided', async () => { - clientMock.listNamespacedCustomObject.mockResolvedValueOnce({ - body: { - items: [ + const result = await sut.fetchPodMetricsByNamespaces( + { + name: 'cluster1', + url: 'http://localhost:9999', + serviceAccountToken: 'token', + authProvider: 'serviceAccount', + }, + new Set(['ns-a', 'ns-b']), + ); + expect(result).toStrictEqual({ + errors: [ { - metadata: { - name: 'pod-name', - }, + errorType: 'NOT_FOUND', + resourcePath: '/some/path', + statusCode: 404, }, ], - }, - }); - - clientMock.listNamespacedCustomObject.mockResolvedValueOnce({ - body: { - items: [ + responses: [ { - metadata: { - name: 'service-name', - }, + type: 'podstatus', + resources: POD_METRICS_FIXTURE, }, ], - }, + }); }); - - await sut.fetchObjectsForService({ - serviceId: 'some-service', - clusterDetails: { - name: 'cluster1', - url: 'http://localhost:9999', - serviceAccountToken: 'token', - authProvider: 'serviceAccount', - }, - objectTypesToFetch: OBJECTS_TO_FETCH, - labelSelector: '', - namespace: 'some-namespace', - customResources: [], - }); - - const mockCall = clientMock.listNamespacedCustomObject.mock.calls[0]; - const namespace = mockCall[2]; - expect(namespace).toBe('some-namespace'); }); }); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts index 1189dfa390..fd6400529b 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFetcher.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import { CoreV1Api, topPods } from '@kubernetes/client-node'; +import { topPods } from '@kubernetes/client-node'; import lodash, { Dictionary } from 'lodash'; import { Logger } from 'winston'; import { @@ -29,13 +29,9 @@ import { FetchResponse, KubernetesFetchError, KubernetesErrorTypes, + PodStatusFetchResponse, } from '@backstage/plugin-kubernetes-common'; import { KubernetesClientProvider } from './KubernetesClientProvider'; -import { PodStatus } from '@kubernetes/client-node/dist/top'; - -export interface Clients { - core: CoreV1Api; -} export interface KubernetesClientBasedFetcherOptions { kubernetesClientProvider: KubernetesClientProvider; @@ -66,6 +62,8 @@ const statusCodeToErrorType = (statusCode: number): KubernetesErrorTypes => { return 'BAD_REQUEST'; case 401: return 'UNAUTHORIZED_ERROR'; + case 404: + return 'NOT_FOUND'; case 500: return 'SYSTEM_ERROR'; default: @@ -104,10 +102,10 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { return Promise.all(fetchResults).then(fetchResultsToResponseWrapper); } - fetchPodMetricsByNamespace( + fetchPodMetricsByNamespaces( clusterDetails: ClusterDetails, - namespace: string, - ): Promise { + namespaces: Set, + ): Promise { const metricsClient = this.kubernetesClientProvider.getMetricsClient(clusterDetails); const coreApi = @@ -115,7 +113,18 @@ export class KubernetesClientBasedFetcher implements KubernetesFetcher { clusterDetails, ); - return topPods(coreApi, metricsClient, namespace); + const fetchResults = Array.from(namespaces).map(ns => + topPods(coreApi, metricsClient, ns) + .then(r => { + return { + type: 'podstatus', + resources: r, + } as PodStatusFetchResponse; + }) + .catch(this.captureKubernetesErrorsRethrowOthers.bind(this)), + ); + + return Promise.all(fetchResults).then(fetchResultsToResponseWrapper); } private captureKubernetesErrorsRethrowOthers(e: any): KubernetesFetchError { diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 5ddfe7367d..ed43935202 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -25,7 +25,6 @@ import type { KubernetesRequestBody, ObjectsByEntityResponse, } from '@backstage/plugin-kubernetes-common'; -import { PodStatus } from '@kubernetes/client-node/dist/top'; /** * @@ -53,10 +52,10 @@ export interface KubernetesFetcher { fetchObjectsForService( params: ObjectFetchParams, ): Promise; - fetchPodMetricsByNamespace( + fetchPodMetricsByNamespaces( clusterDetails: ClusterDetails, - namespace: string, - ): Promise; + namespaces: Set, + ): Promise; } /** @@ -120,12 +119,23 @@ export interface KubernetesClustersSupplier { getClusters(): Promise; } +/** + * @alpha + */ +export interface ServiceLocatorRequestContext { + objectTypesToFetch: Set; + customResources: CustomResourceMatcher[]; +} + /** * Used to locate which cluster(s) a service is running on * @alpha */ export interface KubernetesServiceLocator { - getClustersByEntity(entity: Entity): Promise<{ clusters: ClusterDetails[] }>; + getClustersByEntity( + entity: Entity, + requestContext: ServiceLocatorRequestContext, + ): Promise<{ clusters: ClusterDetails[] }>; } /** diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index dd5e74b384..5f561ede04 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-kubernetes-common +## 0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + +## 0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.1 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + ## 0.4.2 ### Patch Changes diff --git a/plugins/kubernetes-common/api-report.md b/plugins/kubernetes-common/api-report.md index 85015662c6..8509fa97cc 100644 --- a/plugins/kubernetes-common/api-report.md +++ b/plugins/kubernetes-common/api-report.md @@ -5,6 +5,7 @@ ```ts import { Entity } from '@backstage/catalog-model'; import type { JsonObject } from '@backstage/types'; +import { PodStatus } from '@kubernetes/client-node'; import { V1ConfigMap } from '@kubernetes/client-node'; import { V1CronJob } from '@kubernetes/client-node'; import { V1DaemonSet } from '@kubernetes/client-node'; @@ -147,7 +148,8 @@ export type FetchResponse = | IngressesFetchResponse | CustomResourceFetchResponse | StatefulSetsFetchResponse - | DaemonSetsFetchResponse; + | DaemonSetsFetchResponse + | PodStatusFetchResponse; // @public (undocumented) export interface HorizontalPodAutoscalersFetchResponse { @@ -177,6 +179,7 @@ export interface JobsFetchResponse { export type KubernetesErrorTypes = | 'BAD_REQUEST' | 'UNAUTHORIZED_ERROR' + | 'NOT_FOUND' | 'SYSTEM_ERROR' | 'UNKNOWN_ERROR'; @@ -230,6 +233,14 @@ export interface PodFetchResponse { type: 'pods'; } +// @public (undocumented) +export interface PodStatusFetchResponse { + // (undocumented) + resources: Array; + // (undocumented) + type: 'podstatus'; +} + // @public (undocumented) export interface ReplicaSetsFetchResponse { // (undocumented) diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index 5baa12800d..afc3aefc34 100644 --- a/plugins/kubernetes-common/package.json +++ b/plugins/kubernetes-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes-common", "description": "Common functionalities for kubernetes, to be shared between kubernetes and kubernetes-backend plugin", - "version": "0.4.2", + "version": "0.4.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts index 149caadb15..3feddd7f4c 100644 --- a/plugins/kubernetes-common/src/types.ts +++ b/plugins/kubernetes-common/src/types.ts @@ -16,6 +16,7 @@ import type { JsonObject } from '@backstage/types'; import { + PodStatus, V1ConfigMap, V1CronJob, V1DaemonSet, @@ -134,7 +135,8 @@ export type FetchResponse = | IngressesFetchResponse | CustomResourceFetchResponse | StatefulSetsFetchResponse - | DaemonSetsFetchResponse; + | DaemonSetsFetchResponse + | PodStatusFetchResponse; /** @public */ export interface PodFetchResponse { @@ -214,6 +216,12 @@ export interface DaemonSetsFetchResponse { resources: Array; } +/** @public */ +export interface PodStatusFetchResponse { + type: 'podstatus'; + resources: Array; +} + /** @public */ export interface KubernetesFetchError { errorType: KubernetesErrorTypes; @@ -225,6 +233,7 @@ export interface KubernetesFetchError { export type KubernetesErrorTypes = | 'BAD_REQUEST' | 'UNAUTHORIZED_ERROR' + | 'NOT_FOUND' | 'SYSTEM_ERROR' | 'UNKNOWN_ERROR'; diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index b5909b8c01..ebc378d85c 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,61 @@ # @backstage/plugin-kubernetes +## 0.7.3 + +### Patch Changes + +- 51af8361de: Add useCustomResources react hook for fetching Kubernetes Custom Resources +- 35a6cfe257: Fix infinite call bug in `useCustomResources` hook +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-kubernetes-common@0.4.3 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## 0.7.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-kubernetes-common@0.4.3-next.2 + +## 0.7.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-kubernetes-common@0.4.3-next.1 + +## 0.7.3-next.0 + +### Patch Changes + +- 51af8361de: Add useCustomResources react hook for fetching Kubernetes Custom Resources +- 35a6cfe257: Fix infinite call bug in `useCustomResources` hook +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-kubernetes-common@0.4.3-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.7.2 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index a6c3171529..d56fb01c55 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-kubernetes", "description": "A Backstage plugin that integrates towards Kubernetes", - "version": "0.7.2", + "version": "0.7.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/kubernetes/src/hooks/useCustomResources.ts b/plugins/kubernetes/src/hooks/useCustomResources.ts index c0ec7d91ea..c53e00d81c 100644 --- a/plugins/kubernetes/src/hooks/useCustomResources.ts +++ b/plugins/kubernetes/src/hooks/useCustomResources.ts @@ -40,9 +40,9 @@ export const useCustomResources = ( ): KubernetesObjects => { const kubernetesApi = useApi(kubernetesApiRef); const kubernetesAuthProvidersApi = useApi(kubernetesAuthProvidersApiRef); - - const getCustomObjects = - useCallback(async (): Promise => { + const matchersString = JSON.stringify(customResourceMatchers); + const getCustomObjects = useCallback( + async (): Promise => { const auth = await generateAuth( entity, kubernetesApi, @@ -53,12 +53,10 @@ export const useCustomResources = ( customResources: customResourceMatchers, entity, }); - }, [ - kubernetesApi, - entity, - kubernetesAuthProvidersApi, - customResourceMatchers, - ]); + }, + // eslint-disable-next-line react-hooks/exhaustive-deps + [kubernetesApi, entity, kubernetesAuthProvidersApi, matchersString], + ); const { value, loading, error, retry } = useAsyncRetry( () => getCustomObjects(), diff --git a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts index 87d6a0fe8b..aad2f48fd3 100644 --- a/plugins/kubernetes/src/hooks/useKubernetesObjects.ts +++ b/plugins/kubernetes/src/hooks/useKubernetesObjects.ts @@ -36,22 +36,21 @@ export const useKubernetesObjects = ( ): KubernetesObjects => { const kubernetesApi = useApi(kubernetesApiRef); const kubernetesAuthProvidersApi = useApi(kubernetesAuthProvidersApiRef); - const getCustomObjects = - useCallback(async (): Promise => { - const auth = await generateAuth( - entity, - kubernetesApi, - kubernetesAuthProvidersApi, - ); - return await kubernetesApi.getObjectsByEntity({ - auth, - entity, - }); - }, [kubernetesApi, entity, kubernetesAuthProvidersApi]); + const getObjects = useCallback(async (): Promise => { + const auth = await generateAuth( + entity, + kubernetesApi, + kubernetesAuthProvidersApi, + ); + return await kubernetesApi.getObjectsByEntity({ + auth, + entity, + }); + }, [kubernetesApi, entity, kubernetesAuthProvidersApi]); const { value, loading, error, retry } = useAsyncRetry( - () => getCustomObjects(), - [getCustomObjects], + () => getObjects(), + [getObjects], ); useInterval(() => retry(), intervalMs); diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index 7980f276d6..2ab7368212 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-lighthouse +## 0.3.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## 0.3.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.3.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + +## 0.3.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.3.9 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 78ae3237cb..f7243d2ce6 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-lighthouse", "description": "A Backstage plugin that integrates towards Lighthouse", - "version": "0.3.9", + "version": "0.3.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/newrelic-dashboard/CHANGELOG.md b/plugins/newrelic-dashboard/CHANGELOG.md index bd9d5cd4ca..d8bfa338d2 100644 --- a/plugins/newrelic-dashboard/CHANGELOG.md +++ b/plugins/newrelic-dashboard/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-newrelic-dashboard +## 0.2.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.2.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/newrelic-dashboard/package.json b/plugins/newrelic-dashboard/package.json index 986ebd44ab..803ba77c1f 100644 --- a/plugins/newrelic-dashboard/package.json +++ b/plugins/newrelic-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-newrelic-dashboard", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/newrelic/CHANGELOG.md b/plugins/newrelic/CHANGELOG.md index 66adaffc65..8722383768 100644 --- a/plugins/newrelic/CHANGELOG.md +++ b/plugins/newrelic/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-newrelic +## 0.3.28 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.3.28-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.3.28-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## 0.3.28-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.3.27 ### Patch Changes diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index c76f73c41a..8f8b58835d 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-newrelic", "description": "A Backstage plugin that integrates towards New Relic", - "version": "0.3.27", + "version": "0.3.28", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 7e535db9e5..d5d0425128 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,51 @@ # @backstage/plugin-org +## 0.5.10 + +### Patch Changes + +- f2b4b55636: consistently show parent and child relations in group profile card +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.5.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.5.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.5.10-next.0 + +### Patch Changes + +- f2b4b55636: consistently show parent and child relations in group profile card +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.5.9 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 9fb4b45a32..4fde7c83a6 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-org", "description": "A Backstage plugin that helps you create entity pages for your organization", - "version": "0.5.9", + "version": "0.5.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index b4fdd60fae..157b091fc9 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-pagerduty +## 0.5.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.5.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## 0.5.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + ## 0.5.2 ### Patch Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 4909465deb..4d22758614 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-pagerduty", "description": "A Backstage plugin that integrates towards PagerDuty", - "version": "0.5.2", + "version": "0.5.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/periskop-backend/CHANGELOG.md b/plugins/periskop-backend/CHANGELOG.md index d23f635d8d..2bedbee959 100644 --- a/plugins/periskop-backend/CHANGELOG.md +++ b/plugins/periskop-backend/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-periskop-backend +## 0.1.8 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## 0.1.8-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## 0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + ## 0.1.7 ### Patch Changes diff --git a/plugins/periskop-backend/package.json b/plugins/periskop-backend/package.json index 4704370394..3db0d54859 100644 --- a/plugins/periskop-backend/package.json +++ b/plugins/periskop-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-periskop-backend", - "version": "0.1.7", + "version": "0.1.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/periskop-backend/src/service/router.ts b/plugins/periskop-backend/src/service/router.ts index 4f8ae12c31..38a43c5050 100644 --- a/plugins/periskop-backend/src/service/router.ts +++ b/plugins/periskop-backend/src/service/router.ts @@ -43,7 +43,7 @@ export async function createRouter( router.get('/health', (_, response) => { logger.info('PONG!'); - response.send({ status: 'ok' }); + response.json({ status: 'ok' }); }); router.get('/:locationName/:serviceName', async (request, response) => { diff --git a/plugins/periskop/CHANGELOG.md b/plugins/periskop/CHANGELOG.md index b4258a2d73..f27132f8f7 100644 --- a/plugins/periskop/CHANGELOG.md +++ b/plugins/periskop/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-periskop +## 0.1.8 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.1.8-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## 0.1.8-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + ## 0.1.7 ### Patch Changes diff --git a/plugins/periskop/package.json b/plugins/periskop/package.json index 94ec1fdc21..1661731d2f 100644 --- a/plugins/periskop/package.json +++ b/plugins/periskop/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-periskop", - "version": "0.1.7", + "version": "0.1.8", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 9dad2e00fb..a656f43ed4 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-permission-backend +## 0.5.12 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.5.12-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.5.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + +## 0.5.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + ## 0.5.11 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index 234e633482..624accca06 100644 --- a/plugins/permission-backend/package.json +++ b/plugins/permission-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-backend", - "version": "0.5.11", + "version": "0.5.12", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts index 4c50e1646f..80782848e7 100644 --- a/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts +++ b/plugins/permission-backend/src/service/PermissionIntegrationClient.test.ts @@ -30,6 +30,7 @@ import { createPermissionRule, } from '@backstage/plugin-permission-node'; import { PermissionIntegrationClient } from './PermissionIntegrationClient'; +import { z } from 'zod'; describe('PermissionIntegrationClient', () => { describe('applyConditions', () => { @@ -38,8 +39,12 @@ describe('PermissionIntegrationClient', () => { const mockConditions: PermissionCriteria = { not: { allOf: [ - { rule: 'RULE_1', resourceType: 'test-resource', params: [] }, - { rule: 'RULE_2', resourceType: 'test-resource', params: ['abc'] }, + { rule: 'RULE_1', resourceType: 'test-resource', params: {} }, + { + rule: 'RULE_2', + resourceType: 'test-resource', + params: { foo: 'abc' }, + }, ], }, }; @@ -279,7 +284,10 @@ describe('PermissionIntegrationClient', () => { name: 'RULE_1', description: 'Test rule 1', resourceType: 'test-resource', - apply: (_resource: any, input: 'yes' | 'no') => input === 'yes', + paramsSchema: z.object({ + input: z.enum(['yes', 'no']), + }), + apply: (_resource, { input }) => input === 'yes', toQuery: () => { throw new Error('Not implemented'); }, @@ -288,7 +296,11 @@ describe('PermissionIntegrationClient', () => { name: 'RULE_2', description: 'Test rule 2', resourceType: 'test-resource', - apply: (_resource: any, input: 'yes' | 'no') => input === 'yes', + + paramsSchema: z.object({ + input: z.enum(['yes', 'no']), + }), + apply: (_resource, { input }) => input === 'yes', toQuery: () => { throw new Error('Not implemented'); }, @@ -344,7 +356,9 @@ describe('PermissionIntegrationClient', () => { conditions: { rule: 'RULE_1', resourceType: 'test-resource', - params: ['no'], + params: { + input: 'no', + }, }, }, ]), @@ -365,13 +379,17 @@ describe('PermissionIntegrationClient', () => { { rule: 'RULE_1', resourceType: 'test-resource', - params: ['yes'], + params: { + input: 'yes', + }, }, { not: { rule: 'RULE_2', resourceType: 'test-resource', - params: ['no'], + params: { + input: 'no', + }, }, }, ], @@ -382,12 +400,16 @@ describe('PermissionIntegrationClient', () => { { rule: 'RULE_1', resourceType: 'test-resource', - params: ['no'], + params: { + input: 'no', + }, }, { rule: 'RULE_2', resourceType: 'test-resource', - params: ['yes'], + params: { + input: 'yes', + }, }, ], }, diff --git a/plugins/permission-backend/src/service/router.ts b/plugins/permission-backend/src/service/router.ts index 684cdebefd..b7e77fdba9 100644 --- a/plugins/permission-backend/src/service/router.ts +++ b/plugins/permission-backend/src/service/router.ts @@ -179,7 +179,7 @@ export async function createRouter( router.use(express.json()); router.get('/health', (_, response) => { - response.send({ status: 'ok' }); + response.json({ status: 'ok' }); }); router.post( diff --git a/plugins/permission-common/CHANGELOG.md b/plugins/permission-common/CHANGELOG.md index b906ac0038..c06b9a2ad1 100644 --- a/plugins/permission-common/CHANGELOG.md +++ b/plugins/permission-common/CHANGELOG.md @@ -1,5 +1,95 @@ # @backstage/plugin-permission-common +## 0.7.0 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 0.7.0-next.2 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## 0.6.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## 0.6.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + ## 0.6.4 ### Patch Changes diff --git a/plugins/permission-common/api-report.md b/plugins/permission-common/api-report.md index c0c351d6cd..1c2476d20d 100644 --- a/plugins/permission-common/api-report.md +++ b/plugins/permission-common/api-report.md @@ -4,6 +4,7 @@ ```ts import { Config } from '@backstage/config'; +import { JsonPrimitive } from '@backstage/types'; // @public export type AllOfCriteria = { @@ -172,11 +173,11 @@ export class PermissionClient implements PermissionEvaluator { // @public export type PermissionCondition< TResourceType extends string = string, - TParams extends unknown[] = unknown[], + TParams extends PermissionRuleParams = PermissionRuleParams, > = { resourceType: TResourceType; rule: string; - params: TParams; + params?: TParams; }; // @public @@ -203,6 +204,14 @@ export type PermissionMessageBatch = { items: IdentifiedPermissionMessage[]; }; +// @public +export type PermissionRuleParam = undefined | JsonPrimitive | JsonPrimitive[]; + +// @public +export type PermissionRuleParams = + | undefined + | Record; + // @public export type PolicyDecision = | DefinitivePolicyDecision diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index e83131387a..8fb92a0d2b 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-common", "description": "Isomorphic types and client for Backstage permissions and authorization", - "version": "0.6.4", + "version": "0.7.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { @@ -43,6 +43,7 @@ "dependencies": { "@backstage/config": "workspace:^", "@backstage/errors": "workspace:^", + "@backstage/types": "workspace:^", "cross-fetch": "^3.1.5", "uuid": "^8.0.0", "zod": "^3.11.6" diff --git a/plugins/permission-common/src/PermissionClient.test.ts b/plugins/permission-common/src/PermissionClient.test.ts index 9b2bdcb9ff..bcc6ddd6f6 100644 --- a/plugins/permission-common/src/PermissionClient.test.ts +++ b/plugins/permission-common/src/PermissionClient.test.ts @@ -227,7 +227,7 @@ describe('PermissionClient', () => { conditions: { resourceType: 'test-resource', rule: 'FOO', - params: ['bar'], + params: { foo: 'bar' }, }, }), ); @@ -275,7 +275,7 @@ describe('PermissionClient', () => { conditions: { rule: 'FOO', resourceType: 'test-resource', - params: ['bar'], + params: { foo: 'bar' }, }, }), ); diff --git a/plugins/permission-common/src/PermissionClient.ts b/plugins/permission-common/src/PermissionClient.ts index 9a7bb0bba4..c5f56be159 100644 --- a/plugins/permission-common/src/PermissionClient.ts +++ b/plugins/permission-common/src/PermissionClient.ts @@ -41,7 +41,7 @@ const permissionCriteriaSchema: z.ZodSchema< .object({ rule: z.string(), resourceType: z.string(), - params: z.array(z.unknown()), + params: z.record(z.any()), }) .or(z.object({ anyOf: z.array(permissionCriteriaSchema).nonempty() })) .or(z.object({ allOf: z.array(permissionCriteriaSchema).nonempty() })) diff --git a/plugins/permission-common/src/types/api.ts b/plugins/permission-common/src/types/api.ts index f29a639d18..5e7a2bb349 100644 --- a/plugins/permission-common/src/types/api.ts +++ b/plugins/permission-common/src/types/api.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { JsonPrimitive } from '@backstage/types'; import { ResourcePermission } from '.'; import { Permission } from './permission'; @@ -101,11 +102,11 @@ export type PolicyDecision = */ export type PermissionCondition< TResourceType extends string = string, - TParams extends unknown[] = unknown[], + TParams extends PermissionRuleParams = PermissionRuleParams, > = { resourceType: TResourceType; rule: string; - params: TParams; + params?: TParams; }; /** @@ -148,6 +149,22 @@ export type PermissionCriteria = | NotCriteria | TQuery; +/** + * A parameter to a permission rule. + * + * @public + */ +export type PermissionRuleParam = undefined | JsonPrimitive | JsonPrimitive[]; + +/** + * Types that can be used as parameters to permission rules. + * + * @public + */ +export type PermissionRuleParams = + | undefined + | Record; + /** * An individual request sent to the permission backend. * @public diff --git a/plugins/permission-common/src/types/index.ts b/plugins/permission-common/src/types/index.ts index f244a70b1a..5702f47c1c 100644 --- a/plugins/permission-common/src/types/index.ts +++ b/plugins/permission-common/src/types/index.ts @@ -33,6 +33,8 @@ export type { PolicyDecision, PermissionCondition, PermissionCriteria, + PermissionRuleParam, + PermissionRuleParams, AllOfCriteria, AnyOfCriteria, NotCriteria, diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index 0543ac4e43..9082ab8337 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,106 @@ # @backstage/plugin-permission-node +## 0.7.0 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- 9335ad115e: Exported types for the .metadata endpoint of the permission router +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.7.0-next.2 + +### Minor Changes + +- 46b4a72cee: **BREAKING**: When defining permission rules, it's now necessary to provide a [ZodSchema](https://github.com/colinhacks/zod) that specifies the parameters the rule expects. This has been added to help better describe the parameters in the response of the metadata endpoint and to validate the parameters before a rule is executed. + + To help with this, we have also made a change to the API of permission rules. Before, the permission rules `toQuery` and `apply` signature expected parameters to be separate arguments, like so... + + ```ts + createPermissionRule({ + apply: (resource, foo, bar) => true, + toQuery: (foo, bar) => {}, + }); + ``` + + The API has now changed to expect the parameters as a single object + + ```ts + createPermissionRule({ + paramSchema: z.object({ + foo: z.string().describe('Foo value to match'), + bar: z.string().describe('Bar value to match'), + }), + apply: (resource, { foo, bar }) => true, + toQuery: ({ foo, bar }) => {}, + }); + ``` + + One final change made is to limit the possible values for a parameter to primitives and arrays of primitives. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.6.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## 0.6.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + ## 0.6.5 ### Patch Changes diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md index 702d363137..3f712fe362 100644 --- a/plugins/permission-node/api-report.md +++ b/plugins/permission-node/api-report.md @@ -19,11 +19,14 @@ import { Permission } from '@backstage/plugin-permission-common'; import { PermissionCondition } from '@backstage/plugin-permission-common'; import { PermissionCriteria } from '@backstage/plugin-permission-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; import { PolicyDecision } from '@backstage/plugin-permission-common'; import { QueryPermissionRequest } from '@backstage/plugin-permission-common'; import { ResourcePermission } from '@backstage/plugin-permission-common'; import { TokenManager } from '@backstage/backend-common'; +import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; // @public export type ApplyConditionsRequest = { @@ -53,7 +56,9 @@ export type Condition = TRule extends PermissionRule< infer TResourceType, infer TParams > - ? (...params: TParams) => PermissionCondition + ? undefined extends TParams + ? () => PermissionCondition + : (params: TParams) => PermissionCondition : never; // @public @@ -74,7 +79,7 @@ export const createConditionExports: < TResource, TRules extends Record< string, - PermissionRule + PermissionRule >, >(options: { pluginId: string; @@ -85,7 +90,7 @@ export const createConditionExports: < createConditionalDecision: ( permission: ResourcePermission, conditions: PermissionCriteria< - PermissionCondition + PermissionCondition >, ) => ConditionalPolicyDecision; }; @@ -93,15 +98,15 @@ export const createConditionExports: < // @public export const createConditionFactory: < TResourceType extends string, - TParams extends any[], + TParams extends PermissionRuleParams = PermissionRuleParams, >( rule: PermissionRule, -) => (...params: TParams) => PermissionCondition; +) => (params: TParams) => PermissionCondition; // @public export const createConditionTransformer: < TQuery, - TRules extends PermissionRule[], + TRules extends PermissionRule[], >( permissionRules: [...TRules], ) => ConditionTransformer; @@ -113,7 +118,12 @@ export const createPermissionIntegrationRouter: < >(options: { resourceType: TResourceType; permissions?: Permission[] | undefined; - rules: PermissionRule, unknown[]>[]; + rules: PermissionRule< + TResource, + any, + NoInfer, + PermissionRuleParams + >[]; getResources: (resourceRefs: string[]) => Promise<(TResource | undefined)[]>; }) => express.Router; @@ -122,7 +132,7 @@ export const createPermissionRule: < TResource, TQuery, TResourceType extends string, - TParams extends unknown[], + TParams extends PermissionRuleParams = undefined, >( rule: PermissionRule, ) => PermissionRule; @@ -147,10 +157,24 @@ export const makeCreatePermissionRule: < TResource, TQuery, TResourceType extends string, ->() => ( +>() => ( rule: PermissionRule, ) => PermissionRule; +// @public +export type MetadataResponse = { + permissions?: Permission[]; + rules: MetadataResponseSerializedRule[]; +}; + +// @public +export type MetadataResponseSerializedRule = { + name: string; + description: string; + resourceType: string; + paramsSchema?: ReturnType; +}; + // @public export interface PermissionPolicy { // (undocumented) @@ -165,13 +189,14 @@ export type PermissionRule< TResource, TQuery, TResourceType extends string, - TParams extends unknown[] = unknown[], + TParams extends PermissionRuleParams = PermissionRuleParams, > = { name: string; description: string; resourceType: TResourceType; - apply(resource: TResource, ...params: TParams): boolean; - toQuery(...params: TParams): PermissionCriteria; + paramsSchema?: z.ZodSchema; + apply(resource: TResource, params: NoInfer): boolean; + toQuery(params: NoInfer): PermissionCriteria; }; // @public diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 354aced054..0111c4505a 100644 --- a/plugins/permission-node/package.json +++ b/plugins/permission-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-permission-node", "description": "Common permission and authorization utilities for backend plugins", - "version": "0.6.5", + "version": "0.7.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -41,7 +41,8 @@ "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", - "zod": "^3.11.6" + "zod": "^3.11.6", + "zod-to-json-schema": "^3.18.1" }, "devDependencies": { "@backstage/backend-test-utils": "workspace:^", diff --git a/plugins/permission-node/src/integration/createConditionExports.test.ts b/plugins/permission-node/src/integration/createConditionExports.test.ts index 54d5652996..dad57e2b8b 100644 --- a/plugins/permission-node/src/integration/createConditionExports.test.ts +++ b/plugins/permission-node/src/integration/createConditionExports.test.ts @@ -18,6 +18,7 @@ import { AuthorizeResult, createPermission, } from '@backstage/plugin-permission-common'; +import { z } from 'zod'; import { createConditionExports } from './createConditionExports'; import { createPermissionRule } from './createPermissionRule'; @@ -30,23 +31,28 @@ const testIntegration = () => name: 'testRule1', description: 'Test rule 1', resourceType: 'test-resource', - apply: jest.fn( - (_resource: any, _firstParam: string, _secondParam: number) => true, - ), - toQuery: jest.fn((firstParam: string, secondParam: number) => ({ + paramsSchema: z.object({ + foo: z.string(), + bar: z.number(), + }), + apply: (_resource: any, _params) => true, + toQuery: params => ({ query: 'testRule1', - params: [firstParam, secondParam], - })), + params, + }), }), testRule2: createPermissionRule({ name: 'testRule2', description: 'Test rule 2', resourceType: 'test-resource', - apply: jest.fn((_resource: any, _firstParam: object) => false), - toQuery: jest.fn((firstParam: object) => ({ + paramsSchema: z.object({ + foo: z.string().optional(), + }), + apply: (_resource: any) => false, + toQuery: params => ({ query: 'testRule2', - params: [firstParam], - })), + params, + }), }), }, }); @@ -56,16 +62,24 @@ describe('createConditionExports', () => { it('creates condition factories for the supplied rules', () => { const { conditions } = testIntegration(); - expect(conditions.testRule1('a', 1)).toEqual({ + expect( + conditions.testRule1({ + foo: 'a', + bar: 1, + }), + ).toEqual({ rule: 'testRule1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }); - expect(conditions.testRule2({ baz: 'quux' })).toEqual({ + expect(conditions.testRule2({})).toEqual({ rule: 'testRule2', resourceType: 'test-resource', - params: [{ baz: 'quux' }], + params: {}, }); }); }); @@ -85,7 +99,10 @@ describe('createConditionExports', () => { { rule: 'testRule1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, ], }), @@ -98,7 +115,10 @@ describe('createConditionExports', () => { { rule: 'testRule1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, ], }, diff --git a/plugins/permission-node/src/integration/createConditionExports.ts b/plugins/permission-node/src/integration/createConditionExports.ts index ba34c07fed..1c82434add 100644 --- a/plugins/permission-node/src/integration/createConditionExports.ts +++ b/plugins/permission-node/src/integration/createConditionExports.ts @@ -36,7 +36,9 @@ export type Condition = TRule extends PermissionRule< infer TResourceType, infer TParams > - ? (...params: TParams) => PermissionCondition + ? undefined extends TParams + ? () => PermissionCondition + : (params: TParams) => PermissionCondition : never; /** diff --git a/plugins/permission-node/src/integration/createConditionFactory.test.ts b/plugins/permission-node/src/integration/createConditionFactory.test.ts index 1941b5ac0b..520fcc01f9 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.test.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.test.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { z } from 'zod'; import { createConditionFactory } from './createConditionFactory'; import { createPermissionRule } from './createPermissionRule'; @@ -22,8 +23,11 @@ describe('createConditionFactory', () => { name: 'test-rule', description: 'test-description', resourceType: 'test-resource', - apply: jest.fn(), - toQuery: jest.fn(), + paramsSchema: z.object({ + foo: z.string(), + }), + apply: (_resource, _params) => true, + toQuery: _params => ({}), }); it('returns a function', () => { @@ -33,10 +37,17 @@ describe('createConditionFactory', () => { describe('return value', () => { it('constructs a condition with the rule name and supplied params', () => { const conditionFactory = createConditionFactory(testRule); - expect(conditionFactory('a', 'b', 1, 2)).toEqual({ + + expect( + conditionFactory({ + foo: 'bar', + }), + ).toEqual({ rule: 'test-rule', resourceType: 'test-resource', - params: ['a', 'b', 1, 2], + params: { + foo: 'bar', + }, }); }); }); diff --git a/plugins/permission-node/src/integration/createConditionFactory.ts b/plugins/permission-node/src/integration/createConditionFactory.ts index 15a5b4fd08..7f8cb1ced8 100644 --- a/plugins/permission-node/src/integration/createConditionFactory.ts +++ b/plugins/permission-node/src/integration/createConditionFactory.ts @@ -14,7 +14,10 @@ * limitations under the License. */ -import { PermissionCondition } from '@backstage/plugin-permission-common'; +import { + PermissionCondition, + PermissionRuleParams, +} from '@backstage/plugin-permission-common'; import { PermissionRule } from '../types'; /** @@ -33,12 +36,17 @@ import { PermissionRule } from '../types'; * * @public */ -export const createConditionFactory = - ( - rule: PermissionRule, - ) => - (...params: TParams): PermissionCondition => ({ - rule: rule.name, - resourceType: rule.resourceType, - params, - }); +export const createConditionFactory = < + TResourceType extends string, + TParams extends PermissionRuleParams = PermissionRuleParams, +>( + rule: PermissionRule, +) => { + return (params: TParams): PermissionCondition => { + return { + rule: rule.name, + resourceType: rule.resourceType, + params, + }; + }; +}; diff --git a/plugins/permission-node/src/integration/createConditionTransformer.test.ts b/plugins/permission-node/src/integration/createConditionTransformer.test.ts index 917449561d..a5507f0b44 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.test.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.test.ts @@ -18,6 +18,7 @@ import { PermissionCondition, PermissionCriteria, } from '@backstage/plugin-permission-common'; +import { z } from 'zod'; import { createConditionTransformer } from './createConditionTransformer'; import { createPermissionRule } from './createPermissionRule'; @@ -26,20 +27,22 @@ const transformConditions = createConditionTransformer([ name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', + paramsSchema: z.object({ + foo: z.string(), + bar: z.number(), + }), apply: jest.fn(), - toQuery: jest.fn( - (firstParam: string, secondParam: number) => - `test-rule-1:${firstParam}/${secondParam}`, - ), + toQuery: jest.fn(({ foo, bar }) => `test-rule-1:${foo}/${bar}`), }), createPermissionRule({ name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', + paramsSchema: z.object({ + foo: z.string(), + }), apply: jest.fn(), - toQuery: jest.fn( - (firstParam: object) => `test-rule-2:${JSON.stringify(firstParam)}`, - ), + toQuery: jest.fn(({ foo }) => `test-rule-2:${foo}`), }), ]); @@ -52,7 +55,10 @@ describe('createConditionTransformer', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['abc', 123], + params: { + foo: 'abc', + bar: 123, + }, }, expectedResult: 'test-rule-1:abc/123', }, @@ -60,9 +66,11 @@ describe('createConditionTransformer', () => { conditions: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ foo: 0 }], + params: { + foo: '0', + }, }, - expectedResult: 'test-rule-2:{"foo":0}', + expectedResult: 'test-rule-2:0', }, { conditions: { @@ -70,13 +78,22 @@ describe('createConditionTransformer', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: { + foo: 'b', + }, }, - { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, expectedResult: { - anyOf: ['test-rule-1:a/1', 'test-rule-2:{}'], + anyOf: ['test-rule-1:a/1', 'test-rule-2:b'], }, }, { @@ -85,13 +102,22 @@ describe('createConditionTransformer', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: { + foo: 'b', + }, }, - { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, expectedResult: { - allOf: ['test-rule-1:a/1', 'test-rule-2:{}'], + allOf: ['test-rule-1:a/1', 'test-rule-2:b'], }, }, { @@ -99,11 +125,13 @@ describe('createConditionTransformer', () => { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], + params: { + foo: 'a', + }, }, }, expectedResult: { - not: 'test-rule-2:{}', + not: 'test-rule-2:a', }, }, { @@ -114,12 +142,17 @@ describe('createConditionTransformer', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], + params: { + foo: 'b', + }, }, ], }, @@ -129,61 +162,16 @@ describe('createConditionTransformer', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['b', 2], + params: { + foo: 'b', + bar: 2, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ c: 3 }], - }, - ], - }, - }, - ], - }, - expectedResult: { - allOf: [ - { - anyOf: ['test-rule-1:a/1', 'test-rule-2:{}'], - }, - { - not: { - allOf: ['test-rule-1:b/2', 'test-rule-2:{"c":3}'], - }, - }, - ], - }, - }, - { - conditions: { - allOf: [ - { - anyOf: [ - { - rule: 'test-rule-1', - resourceType: 'test-resource', - params: ['a', 1], - }, - { - rule: 'test-rule-2', - resourceType: 'test-resource', - params: [{ b: 2 }], - }, - ], - }, - { - not: { - allOf: [ - { - rule: 'test-rule-1', - resourceType: 'test-resource', - params: ['c', 3], - }, - { - not: { - rule: 'test-rule-2', - resourceType: 'test-resource', - params: [{ d: 4 }], + params: { + foo: 'c', }, }, ], @@ -194,11 +182,71 @@ describe('createConditionTransformer', () => { expectedResult: { allOf: [ { - anyOf: ['test-rule-1:a/1', 'test-rule-2:{"b":2}'], + anyOf: ['test-rule-1:a/1', 'test-rule-2:b'], }, { not: { - allOf: ['test-rule-1:c/3', { not: 'test-rule-2:{"d":4}' }], + allOf: ['test-rule-1:b/2', 'test-rule-2:c'], + }, + }, + ], + }, + }, + { + conditions: { + allOf: [ + { + anyOf: [ + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: { + foo: 'a', + bar: 1, + }, + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: { + foo: 'b', + }, + }, + ], + }, + { + not: { + allOf: [ + { + rule: 'test-rule-1', + resourceType: 'test-resource', + params: { + foo: 'c', + bar: 3, + }, + }, + { + not: { + rule: 'test-rule-2', + resourceType: 'test-resource', + params: { + foo: 'd', + }, + }, + }, + ], + }, + }, + ], + }, + expectedResult: { + allOf: [ + { + anyOf: ['test-rule-1:a/1', 'test-rule-2:b'], + }, + { + not: { + allOf: ['test-rule-1:c/3', { not: 'test-rule-2:d' }], }, }, ], diff --git a/plugins/permission-node/src/integration/createConditionTransformer.ts b/plugins/permission-node/src/integration/createConditionTransformer.ts index 112bb30d01..4b6f933f79 100644 --- a/plugins/permission-node/src/integration/createConditionTransformer.ts +++ b/plugins/permission-node/src/integration/createConditionTransformer.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { InputError } from '@backstage/errors'; import { AllOfCriteria, AnyOfCriteria, @@ -45,7 +46,14 @@ const mapConditions = ( }; } - return getRule(criteria.rule).toQuery(...criteria.params); + const rule = getRule(criteria.rule); + const result = rule.paramsSchema?.safeParse(criteria.params); + + if (result && !result.success) { + throw new InputError(`Parameters to rule are invalid`, result.error); + } + + return rule.toQuery(criteria.params ?? {}); }; /** diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts index ac4005c487..32d814465f 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts @@ -21,6 +21,7 @@ import { } from '@backstage/plugin-permission-common'; import express, { Express, Router } from 'express'; import request, { Response } from 'supertest'; +import { z } from 'zod'; import { createPermissionIntegrationRouter } from './createPermissionIntegrationRouter'; import { createPermissionRule } from './createPermissionRule'; @@ -39,16 +40,20 @@ const testRule1 = createPermissionRule({ name: 'test-rule-1', description: 'Test rule 1', resourceType: 'test-resource', - apply: (_resource: any, _firstParam: string, _secondParam: number) => true, - toQuery: (_firstParam: string, _secondParam: number) => ({}), + paramsSchema: z.object({ + foo: z.string(), + bar: z.number().describe('bar'), + }), + apply: (_resource: any, _params) => true, + toQuery: _params => ({}), }); const testRule2 = createPermissionRule({ name: 'test-rule-2', description: 'Test rule 2', resourceType: 'test-resource', - apply: (_resource: any, _firstParam: object) => false, - toQuery: (_firstParam: object) => ({}), + apply: (_resource: any) => false, + toQuery: () => ({}), }); describe('createPermissionIntegrationRouter', () => { @@ -79,23 +84,31 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['abc', 123], + params: { + foo: 'abc', + bar: 123, + }, }, { anyOf: [ { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', }, - { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], }, }, { @@ -105,12 +118,14 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{}], }, ], }, @@ -120,12 +135,14 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['b', 2], + params: { + foo: 'b', + bar: 2, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ c: 3 }], }, ], }, @@ -161,16 +178,21 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ foo: 0 }], }, { allOf: [ { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, + }, + { + rule: 'test-rule-2', + resourceType: 'test-resource', }, - { rule: 'test-rule-2', resourceType: 'test-resource', params: [{}] }, ], }, { @@ -180,12 +202,14 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['a', 1], + params: { + foo: 'a', + bar: 1, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ b: 2 }], }, ], }, @@ -195,13 +219,15 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: ['c', 3], + params: { + foo: 'c', + bar: 3, + }, }, { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [{ d: 4 }], }, }, ], @@ -247,7 +273,10 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: { + foo: 'a', + bar: 1, + }, }, }, { @@ -257,7 +286,6 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [], }, }, { @@ -268,7 +296,10 @@ describe('createPermissionIntegrationRouter', () => { not: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: { + foo: 'a', + bar: 1, + }, }, }, }, @@ -280,7 +311,6 @@ describe('createPermissionIntegrationRouter', () => { not: { rule: 'test-rule-2', resourceType: 'test-resource', - params: [], }, }, }, @@ -293,12 +323,14 @@ describe('createPermissionIntegrationRouter', () => { { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: { + foo: 'a', + bar: 1, + }, }, { rule: 'test-rule-2', resourceType: 'test-resource', - params: [], }, ], }, @@ -342,7 +374,9 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-incorrect-resource-1', - params: [{}], + params: { + foo: {}, + }, }, }, { @@ -352,7 +386,9 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [{}], + params: { + foo: {}, + }, }, }, { @@ -362,7 +398,9 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-incorrect-resource-2', - params: [{}], + params: { + foo: {}, + }, }, }, ], @@ -390,7 +428,7 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: {}, }, }, ], @@ -427,7 +465,10 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: { + foo: 'a', + bar: 1, + }, }, }, { @@ -437,7 +478,10 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: { + foo: 'a', + bar: 1, + }, }, }, { @@ -447,7 +491,10 @@ describe('createPermissionIntegrationRouter', () => { conditions: { rule: 'test-rule-1', resourceType: 'test-resource', - params: [], + params: { + foo: 'a', + bar: 1, + }, }, }, ], @@ -524,13 +571,32 @@ describe('createPermissionIntegrationRouter', () => { name: testRule1.name, description: testRule1.description, resourceType: testRule1.resourceType, - parameters: { count: 2 }, + paramsSchema: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: { + foo: { + type: 'string', + }, + bar: { + description: 'bar', + type: 'number', + }, + }, + required: ['foo', 'bar'], + type: 'object', + }, }, { name: testRule2.name, description: testRule2.description, resourceType: testRule2.resourceType, - parameters: { count: 1 }, + paramsSchema: { + $schema: 'http://json-schema.org/draft-07/schema#', + additionalProperties: false, + properties: {}, + type: 'object', + }, }, ], }); diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts index 28abc2f48d..23b59c9c43 100644 --- a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts +++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts @@ -17,6 +17,7 @@ import express, { Response } from 'express'; import Router from 'express-promise-router'; import { z } from 'zod'; +import zodToJsonSchema from 'zod-to-json-schema'; import { InputError } from '@backstage/errors'; import { errorHandler } from '@backstage/backend-common'; import { @@ -29,6 +30,7 @@ import { } from '@backstage/plugin-permission-common'; import { PermissionRule } from '../types'; import { + NoInfer, createGetRule, isAndCriteria, isNotCriteria, @@ -45,7 +47,7 @@ const permissionCriteriaSchema: z.ZodSchema< z.object({ rule: z.string(), resourceType: z.string(), - params: z.array(z.unknown()), + params: z.record(z.any()).optional(), }), ]), ); @@ -100,6 +102,29 @@ export type ApplyConditionsResponse = { items: ApplyConditionsResponseEntry[]; }; +/** + * Serialized permission rules, with the paramsSchema + * converted from a ZodSchema to a JsonSchema. + * + * @public + */ +export type MetadataResponseSerializedRule = { + name: string; + description: string; + resourceType: string; + paramsSchema?: ReturnType; +}; + +/** + * Response type for the .metadata endpoint. + * + * @public + */ +export type MetadataResponse = { + permissions?: Permission[]; + rules: MetadataResponseSerializedRule[]; +}; + const applyConditions = ( criteria: PermissionCriteria>, resource: TResource | undefined, @@ -124,16 +149,15 @@ const applyConditions = ( return !applyConditions(criteria.not, resource, getRule); } - return getRule(criteria.rule).apply(resource, ...criteria.params); -}; + const rule = getRule(criteria.rule); + const result = rule.paramsSchema?.safeParse(criteria.params); -/** - * Prevent use of type parameter from contributing to type inference. - * - * https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-980401795 - * @ignore - */ -type NoInfer = T extends infer S ? S : never; + if (result && !result.success) { + throw new InputError(`Parameters to rule are invalid`, result.error); + } + + return rule.apply(resource, criteria.params ?? {}); +}; /** * Create an express Router which provides an authorization route to allow @@ -190,16 +214,21 @@ export const createPermissionIntegrationRouter = < router.use(express.json()); router.get('/.well-known/backstage/permissions/metadata', (_, res) => { - const serializableRules = rules.map(rule => ({ - name: rule.name, - description: rule.description, - resourceType: rule.resourceType, - parameters: { - count: rule.toQuery.length, - }, - })); + const serializedRules: MetadataResponseSerializedRule[] = rules.map( + rule => ({ + name: rule.name, + description: rule.description, + resourceType: rule.resourceType, + paramsSchema: zodToJsonSchema(rule.paramsSchema ?? z.object({})), + }), + ); - return res.json({ permissions, rules: serializableRules }); + const responseJson: MetadataResponse = { + permissions, + rules: serializedRules, + }; + + return res.json(responseJson); }); const getRule = createGetRule(rules); diff --git a/plugins/permission-node/src/integration/createPermissionRule.ts b/plugins/permission-node/src/integration/createPermissionRule.ts index 6673fe76de..44187e58a6 100644 --- a/plugins/permission-node/src/integration/createPermissionRule.ts +++ b/plugins/permission-node/src/integration/createPermissionRule.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PermissionRule } from '../types'; /** @@ -25,7 +26,7 @@ export const createPermissionRule = < TResource, TQuery, TResourceType extends string, - TParams extends unknown[], + TParams extends PermissionRuleParams = undefined, >( rule: PermissionRule, ) => rule; @@ -40,7 +41,7 @@ export const createPermissionRule = < */ export const makeCreatePermissionRule = () => - ( + ( rule: PermissionRule, ) => createPermissionRule(rule); diff --git a/plugins/permission-node/src/integration/util.ts b/plugins/permission-node/src/integration/util.ts index 68b7fff2b8..e448068e17 100644 --- a/plugins/permission-node/src/integration/util.ts +++ b/plugins/permission-node/src/integration/util.ts @@ -22,6 +22,14 @@ import { } from '@backstage/plugin-permission-common'; import { PermissionRule } from '../types'; +/** + * Prevent use of type parameter from contributing to type inference. + * + * https://github.com/Microsoft/TypeScript/issues/14829#issuecomment-980401795 + * @ignore + */ +export type NoInfer = T extends infer S ? S : never; + /** * Utility function used to parse a PermissionCriteria * @param criteria - a PermissionCriteria diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts index 66b4a7b22b..36e30714f6 100644 --- a/plugins/permission-node/src/types.ts +++ b/plugins/permission-node/src/types.ts @@ -14,7 +14,12 @@ * limitations under the License. */ -import type { PermissionCriteria } from '@backstage/plugin-permission-common'; +import type { + PermissionCriteria, + PermissionRuleParams, +} from '@backstage/plugin-permission-common'; +import { z } from 'zod'; +import { NoInfer } from './integration/util'; /** * A conditional rule that can be provided in an @@ -36,23 +41,28 @@ export type PermissionRule< TResource, TQuery, TResourceType extends string, - TParams extends unknown[] = unknown[], + TParams extends PermissionRuleParams = PermissionRuleParams, > = { name: string; description: string; resourceType: TResourceType; + /** + * A ZodSchema that reflects the structure of the parameters that are passed to + */ + paramsSchema?: z.ZodSchema; + /** * Apply this rule to a resource already loaded from a backing data source. The params are * arguments supplied for the rule; for example, a rule could be `isOwner` with entityRefs as the * params. */ - apply(resource: TResource, ...params: TParams): boolean; + apply(resource: TResource, params: NoInfer): boolean; /** * Translate this rule to criteria suitable for use in querying a backing data store. The criteria * can be used for loading a collection of resources efficiently with conditional criteria already * applied. */ - toQuery(...params: TParams): PermissionCriteria; + toQuery(params: NoInfer): PermissionCriteria; }; diff --git a/plugins/permission-react/CHANGELOG.md b/plugins/permission-react/CHANGELOG.md index c69fe65e0a..43fce22e80 100644 --- a/plugins/permission-react/CHANGELOG.md +++ b/plugins/permission-react/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-permission-react +## 0.4.6 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + +## 0.4.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + +## 0.4.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + ## 0.4.5 ### Patch Changes diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index faaca8829f..c852ecec3e 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-permission-react", - "version": "0.4.5", + "version": "0.4.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/playlist-backend/CHANGELOG.md b/plugins/playlist-backend/CHANGELOG.md index ca4ab28ba7..2a3d2061ce 100644 --- a/plugins/playlist-backend/CHANGELOG.md +++ b/plugins/playlist-backend/CHANGELOG.md @@ -1,5 +1,105 @@ # @backstage/plugin-playlist-backend +## 0.2.0 + +### Minor Changes + +- eb25f7e12d: **BREAKING** The exported permission rules have changed to reflect the breaking changes made to the PermissionRule type. + + For example, the `playlistConditions.isOwner` API has changed from: + + ```ts + playlistConditions.isOwner(['user:default/me', 'group:default/owner']); + ``` + + to: + + ```ts + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/backend-test-utils@0.1.29 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-playlist-common@0.1.1 + +## 0.2.0-next.2 + +### Minor Changes + +- eb25f7e12d: **BREAKING** The exported permission rules have changed to reflect the breaking changes made to the PermissionRule type. + + For example, the `playlistConditions.isOwner` API has changed from: + + ```ts + playlistConditions.isOwner(['user:default/me', 'group:default/owner']); + ``` + + to: + + ```ts + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-playlist-common@0.1.1-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-test-utils@0.1.29-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + - @backstage/plugin-playlist-common@0.1.1-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-test-utils@0.1.29-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-playlist-common@0.1.1-next.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/playlist-backend/api-report.md b/plugins/playlist-backend/api-report.md index c7109efbfb..6b51f316c4 100644 --- a/plugins/playlist-backend/api-report.md +++ b/plugins/playlist-backend/api-report.md @@ -15,6 +15,7 @@ import { PermissionCriteria } from '@backstage/plugin-permission-common'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; import { PermissionPolicy } from '@backstage/plugin-permission-node'; import { PermissionRule } from '@backstage/plugin-permission-node'; +import { PermissionRuleParams } from '@backstage/plugin-permission-common'; import { PlaylistMetadata } from '@backstage/plugin-playlist-common'; import { PluginDatabaseManager } from '@backstage/backend-common'; import { PluginEndpointDiscovery } from '@backstage/backend-common'; @@ -26,7 +27,7 @@ import { ResourcePermission } from '@backstage/plugin-permission-common'; export const createPlaylistConditionalDecision: ( permission: ResourcePermission<'playlist-list'>, conditions: PermissionCriteria< - PermissionCondition<'playlist-list', unknown[]> + PermissionCondition<'playlist-list', PermissionRuleParams> >, ) => ConditionalPolicyDecision; @@ -70,13 +71,15 @@ export const playlistConditions: Conditions<{ PlaylistMetadata, ListPlaylistsFilter, 'playlist-list', - [userOwnershipRefs: string[]] + { + owners: string[]; + } >; isPublic: PermissionRule< PlaylistMetadata, ListPlaylistsFilter, 'playlist-list', - [] + undefined >; }>; diff --git a/plugins/playlist-backend/package.json b/plugins/playlist-backend/package.json index ae472da767..38e0729844 100644 --- a/plugins/playlist-backend/package.json +++ b/plugins/playlist-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-playlist-backend", - "version": "0.1.0", + "version": "0.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -39,7 +39,8 @@ "node-fetch": "^2.6.7", "uuid": "^8.2.0", "winston": "^3.2.1", - "yn": "^4.0.0" + "yn": "^4.0.0", + "zod": "^3.11.6" }, "devDependencies": { "@backstage/cli": "workspace:^", diff --git a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts index 179b0d7f9b..17ee4a840a 100644 --- a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts +++ b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.test.ts @@ -67,10 +67,9 @@ describe('DefaultPlaylistPermissionPolicy', () => { resourceType: PLAYLIST_LIST_RESOURCE_TYPE, conditions: { anyOf: [ - playlistConditions.isOwner([ - 'user:default/me', - 'group:default/owner', - ]), + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }), playlistConditions.isPublic(), ], }, @@ -89,10 +88,9 @@ describe('DefaultPlaylistPermissionPolicy', () => { resourceType: PLAYLIST_LIST_RESOURCE_TYPE, conditions: { anyOf: [ - playlistConditions.isOwner([ - 'user:default/me', - 'group:default/owner', - ]), + playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }), playlistConditions.isPublic(), ], }, @@ -109,10 +107,9 @@ describe('DefaultPlaylistPermissionPolicy', () => { result: AuthorizeResult.CONDITIONAL, pluginId: 'playlist', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, - conditions: playlistConditions.isOwner([ - 'user:default/me', - 'group:default/owner', - ]), + conditions: playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }), }); }); @@ -126,10 +123,9 @@ describe('DefaultPlaylistPermissionPolicy', () => { result: AuthorizeResult.CONDITIONAL, pluginId: 'playlist', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, - conditions: playlistConditions.isOwner([ - 'user:default/me', - 'group:default/owner', - ]), + conditions: playlistConditions.isOwner({ + owners: ['user:default/me', 'group:default/owner'], + }), }); }); }); diff --git a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts index 86d103e789..b4556fbb4f 100644 --- a/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts +++ b/plugins/playlist-backend/src/permissions/DefaultPlaylistPermissionPolicy.ts @@ -68,7 +68,9 @@ export class DefaultPlaylistPermissionPolicy implements PermissionPolicy { ) { return createPlaylistConditionalDecision(request.permission, { anyOf: [ - playlistConditions.isOwner(user?.identity.ownershipEntityRefs ?? []), + playlistConditions.isOwner({ + owners: user?.identity.ownershipEntityRefs ?? [], + }), playlistConditions.isPublic(), ], }); @@ -81,7 +83,9 @@ export class DefaultPlaylistPermissionPolicy implements PermissionPolicy { ) { return createPlaylistConditionalDecision( request.permission, - playlistConditions.isOwner(user?.identity.ownershipEntityRefs ?? []), + playlistConditions.isOwner({ + owners: user?.identity.ownershipEntityRefs ?? [], + }), ); } diff --git a/plugins/playlist-backend/src/permissions/rules.ts b/plugins/playlist-backend/src/permissions/rules.ts index cbf81bee65..99f4725972 100644 --- a/plugins/playlist-backend/src/permissions/rules.ts +++ b/plugins/playlist-backend/src/permissions/rules.ts @@ -19,6 +19,7 @@ import { PLAYLIST_LIST_RESOURCE_TYPE, PlaylistMetadata, } from '@backstage/plugin-playlist-common'; +import { z } from 'zod'; import { ListPlaylistsFilter } from '../service'; @@ -32,11 +33,13 @@ const isOwner = createPlaylistPermissionRule({ name: 'IS_OWNER', description: 'Should allow only if the playlist belongs to the user', resourceType: PLAYLIST_LIST_RESOURCE_TYPE, - apply: (list: PlaylistMetadata, userOwnershipRefs: string[]) => - userOwnershipRefs.includes(list.owner), - toQuery: (userOwnershipRefs: string[]) => ({ + paramsSchema: z.object({ + owners: z.array(z.string()).describe('List of owner entity refs'), + }), + apply: (list: PlaylistMetadata, { owners }) => owners.includes(list.owner), + toQuery: ({ owners }) => ({ key: 'owner', - values: userOwnershipRefs, + values: owners, }), }); diff --git a/plugins/playlist-common/CHANGELOG.md b/plugins/playlist-common/CHANGELOG.md index 1c7ae8af12..2887b11c01 100644 --- a/plugins/playlist-common/CHANGELOG.md +++ b/plugins/playlist-common/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-playlist-common +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.6.5-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.6.5-next.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/playlist-common/package.json b/plugins/playlist-common/package.json index 87dbc7d9f5..eccb95faf3 100644 --- a/plugins/playlist-common/package.json +++ b/plugins/playlist-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-playlist-common", "description": "Common functionalities for the playlist plugin", - "version": "0.1.0", + "version": "0.1.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/playlist/CHANGELOG.md b/plugins/playlist/CHANGELOG.md index f4be6fc149..ce6bac5960 100644 --- a/plugins/playlist/CHANGELOG.md +++ b/plugins/playlist/CHANGELOG.md @@ -1,5 +1,73 @@ # @backstage/plugin-playlist +## 0.1.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-react@1.2.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-permission-react@0.4.6 + - @backstage/plugin-playlist-common@0.1.1 + +## 0.1.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/plugin-playlist-common@0.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.7-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-react@0.4.6-next.1 + - @backstage/plugin-playlist-common@0.1.1-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-permission-react@0.4.6-next.0 + - @backstage/plugin-playlist-common@0.1.1-next.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/playlist/package.json b/plugins/playlist/package.json index 7da8d4ba08..bbc4f52f32 100644 --- a/plugins/playlist/package.json +++ b/plugins/playlist/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-playlist", - "version": "0.1.0", + "version": "0.1.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index 329827d9de..948d4cb393 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-proxy-backend +## 0.2.31 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## 0.2.31-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## 0.2.31-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + +## 0.2.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + ## 0.2.30 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 553c43051d..eafad6628c 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-proxy-backend", "description": "A Backstage backend plugin that helps you set up proxy endpoints in the backend", - "version": "0.2.30", + "version": "0.2.31", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/rollbar-backend/CHANGELOG.md b/plugins/rollbar-backend/CHANGELOG.md index 6700252ae2..13dca3c262 100644 --- a/plugins/rollbar-backend/CHANGELOG.md +++ b/plugins/rollbar-backend/CHANGELOG.md @@ -1,5 +1,39 @@ # @backstage/plugin-rollbar-backend +## 0.1.34 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + +## 0.1.34-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + +## 0.1.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + +## 0.1.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + ## 0.1.33 ### Patch Changes diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index aca54df1b2..032ba55b21 100644 --- a/plugins/rollbar-backend/package.json +++ b/plugins/rollbar-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar-backend", "description": "A Backstage backend plugin that integrates towards Rollbar", - "version": "0.1.33", + "version": "0.1.34", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/rollbar-backend/src/service/router.ts b/plugins/rollbar-backend/src/service/router.ts index a5646fdf06..36ae5581a5 100644 --- a/plugins/rollbar-backend/src/service/router.ts +++ b/plugins/rollbar-backend/src/service/router.ts @@ -48,40 +48,40 @@ export async function createRouter( router.get('/projects', async (_req, res) => { const projects = await rollbarApi.getAllProjects(); - res.status(200).header('').send(projects); + res.status(200).json(projects); }); router.get('/projects/:id', async (req, res) => { const { id } = req.params; const projects = await rollbarApi.getProject(id); - res.status(200).send(projects); + res.status(200).json(projects); }); router.get('/projects/:id/items', async (req, res) => { const { id } = req.params; const projects = await rollbarApi.getProjectItems(id); - res.status(200).send(projects); + res.status(200).json(projects); }); router.get('/projects/:id/top_active_items', async (req, res) => { const { id } = req.params; const query = req.query; const items = await rollbarApi.getTopActiveItems(id, query as any); - res.status(200).send(items); + res.status(200).json(items); }); router.get('/projects/:id/occurance_counts', async (req, res) => { const { id } = req.params; const query = req.query; const items = await rollbarApi.getOccuranceCounts(id, query as any); - res.status(200).send(items); + res.status(200).json(items); }); router.get('/projects/:id/activated_item_counts', async (req, res) => { const { id } = req.params; const query = req.query; const items = await rollbarApi.getActivatedCounts(id, query as any); - res.status(200).send(items); + res.status(200).json(items); }); } diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index c6c7e427ed..fc4d626bed 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-rollbar +## 0.4.10 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.4.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.4.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.4.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.4.9 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 6c31befc6b..84fcdd80a8 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-rollbar", "description": "A Backstage plugin that integrates towards Rollbar", - "version": "0.4.9", + "version": "0.4.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index e8f57c5426..cac90c0a56 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## 0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-scaffolder-backend@1.7.0-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + ## 0.2.11 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 200b7bffa0..8032ab5e09 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/package.json +++ b/plugins/scaffolder-backend-module-cookiecutter/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-cookiecutter", "description": "A module for the scaffolder backend that lets you template projects using cookiecutter", - "version": "0.2.11", + "version": "0.2.12", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index 649d08dca0..9abf4e928e 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.5 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 0.4.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + +## 0.4.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-scaffolder-backend@1.7.0-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + ## 0.4.4 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index 9418190ece..91da543f0e 100644 --- a/plugins/scaffolder-backend-module-rails/package.json +++ b/plugins/scaffolder-backend-module-rails/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend-module-rails", "description": "A module for the scaffolder backend that lets you template projects using Rails", - "version": "0.4.4", + "version": "0.4.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index 6a0ddacab6..2f19d95d78 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.2.10 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + +## 0.2.10-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + +## 0.2.10-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/types@1.0.0 + +## 0.2.10-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.7.0-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/types@1.0.0 + ## 0.2.9 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index a7fa872f6d..768ea468ae 100644 --- a/plugins/scaffolder-backend-module-yeoman/package.json +++ b/plugins/scaffolder-backend-module-yeoman/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend-module-yeoman", - "version": "0.2.9", + "version": "0.2.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 3c7624553c..3437603d8a 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,110 @@ # @backstage/plugin-scaffolder-backend +## 1.7.0 + +### Minor Changes + +- 253453fa14: Added a new property called `additionalTemplateGlobals` which allows you to add global functions to the scaffolder nunjucks templates. +- 17ff77154c: Update the `github:publish` action to allow passing whether pull + requests must be up to date with the default branch before merging. +- 304305dd20: Add `allowAutoMerge` option for `publish:github` action +- 694bfe2d61: Add functionality to shutdown scaffolder tasks if they are stale +- a8e9848479: Added optional `sourcePath` parameter to `publish:gitlab:merge-request` action, `targetPath` is now optional and falls back to current workspace path. + +### Patch Changes + +- 489621f613: Switching off duplicated timestamp in case of logging via task logger in a custom action +- 4880d43e25: Fixed setting default branch for Bitbucket Server +- b681275e69: Ignore .git directories in Template Editor, increase upload limit for dry-runs to 10MB. +- a35a27df70: Updated the `moduleId` of the experimental module export. +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0 + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-backend@1.5.0 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/backend-tasks@0.3.6 + - @backstage/backend-plugin-api@0.1.3 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-scaffolder-common@1.2.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/types@1.0.0 + +## 1.7.0-next.2 + +### Minor Changes + +- 17ff77154c: Update the `github:publish` action to allow passing whether pull + requests must be up to date with the default branch before merging. +- a8e9848479: Added optional `sourcePath` parameter to `publish:gitlab:merge-request` action, `targetPath` is now optional and falls back to current workspace path. + +### Patch Changes + +- 4880d43e25: Fixed setting default branch for Bitbucket Server +- Updated dependencies + - @backstage/plugin-catalog-node@1.2.0-next.2 + - @backstage/plugin-catalog-backend@1.5.0-next.2 + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-plugin-api@0.1.3-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + +## 1.7.0-next.1 + +### Patch Changes + +- 489621f613: Switching off duplicated timestamp in case of logging via task logger in a custom action +- a35a27df70: Updated the `moduleId` of the experimental module export. +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-plugin-api@0.1.3-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-catalog-backend@1.4.1-next.1 + - @backstage/plugin-catalog-node@1.1.1-next.1 + - @backstage/plugin-scaffolder-common@1.2.1-next.1 + +## 1.7.0-next.0 + +### Minor Changes + +- 253453fa14: Added a new property called `additionalTemplateGlobals` which allows you to add global functions to the scaffolder nunjucks templates. +- 304305dd20: Add `allowAutoMerge` option for `publish:github` action +- 694bfe2d61: Add functionality to shutdown scaffolder tasks if they are stale + +### Patch Changes + +- b681275e69: Ignore .git directories in Template Editor, increase upload limit for dry-runs to 10MB. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-plugin-api@0.1.3-next.0 + - @backstage/plugin-catalog-backend@1.4.1-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-catalog-node@1.1.1-next.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/types@1.0.0 + ## 1.6.0 ### Minor Changes diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 35e3dab0b5..846fa59b08 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -60,6 +60,8 @@ export const createBuiltinActions: ( // @public export interface CreateBuiltInActionsOptions { additionalTemplateFilters?: Record; + // (undocumented) + additionalTemplateGlobals?: Record; catalogClient: CatalogApi; config: Config; integrations: ScmIntegrations; @@ -108,6 +110,7 @@ export function createFetchTemplateAction(options: { reader: UrlReader; integrations: ScmIntegrations; additionalTemplateFilters?: Record; + additionalTemplateGlobals?: Record; }): TemplateAction<{ url: string; targetPath?: string | undefined; @@ -193,8 +196,10 @@ export function createGithubRepoCreateAction(options: { allowRebaseMerge?: boolean | undefined; allowSquashMerge?: boolean | undefined; allowMergeCommit?: boolean | undefined; + allowAutoMerge?: boolean | undefined; requireCodeOwnerReviews?: boolean | undefined; requiredStatusCheckContexts?: string[] | undefined; + requireBranchesToBeUpToDate?: boolean | undefined; repoVisibility?: 'internal' | 'private' | 'public' | undefined; collaborators?: | ( @@ -232,6 +237,7 @@ export function createGithubRepoPushAction(options: { gitAuthorEmail?: string | undefined; requireCodeOwnerReviews?: boolean | undefined; requiredStatusCheckContexts?: string[] | undefined; + requireBranchesToBeUpToDate?: boolean | undefined; sourcePath?: string | undefined; token?: string | undefined; }>; @@ -358,9 +364,11 @@ export function createPublishGithubAction(options: { allowRebaseMerge?: boolean | undefined; allowSquashMerge?: boolean | undefined; allowMergeCommit?: boolean | undefined; + allowAutoMerge?: boolean | undefined; sourcePath?: string | undefined; requireCodeOwnerReviews?: boolean | undefined; requiredStatusCheckContexts?: string[] | undefined; + requireBranchesToBeUpToDate?: boolean | undefined; repoVisibility?: 'internal' | 'private' | 'public' | undefined; collaborators?: | ( @@ -424,7 +432,8 @@ export const createPublishGitlabMergeRequestAction: (options: { title: string; description: string; branchName: string; - targetPath: string; + sourcePath?: string | undefined; + targetPath?: string | undefined; token?: string | undefined; commitAction?: 'update' | 'create' | 'delete' | undefined; projectid?: string | undefined; @@ -448,6 +457,7 @@ export type CreateWorkerOptions = { workingDirectory: string; logger: Logger; additionalTemplateFilters?: Record; + additionalTemplateGlobals?: Record; }; // @public @@ -538,6 +548,8 @@ export interface RouterOptions { // (undocumented) additionalTemplateFilters?: Record; // (undocumented) + additionalTemplateGlobals?: Record; + // (undocumented) catalogClient: CatalogApi; // (undocumented) config: Config; @@ -593,6 +605,7 @@ export type ScaffolderPluginOptions = { taskWorkers?: number; taskBroker?: TaskBroker; additionalTemplateFilters?: Record; + additionalTemplateGlobals?: Record; }; // @public @@ -813,4 +826,9 @@ export class TemplateActionRegistry { // @public (undocumented) export type TemplateFilter = (...args: JsonValue[]) => JsonValue | undefined; + +// @public (undocumented) +export type TemplateGlobal = + | ((...args: JsonValue[]) => JsonValue | undefined) + | JsonValue; ``` diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 613211b290..ab3b24dcdd 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-backend", "description": "The Backstage backend plugin that helps you create new things", - "version": "1.6.0", + "version": "1.7.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts index b884802a1f..09d7bdcbb2 100644 --- a/plugins/scaffolder-backend/src/ScaffolderPlugin.ts +++ b/plugins/scaffolder-backend/src/ScaffolderPlugin.ts @@ -26,7 +26,7 @@ import { } from '@backstage/backend-plugin-api'; import { ScmIntegrations } from '@backstage/integration'; import { catalogServiceRef } from '@backstage/plugin-catalog-node'; -import { TemplateFilter } from './lib'; +import { TemplateFilter, TemplateGlobal } from './lib'; import { createBuiltinActions, TaskBroker, TemplateAction } from './scaffolder'; import { createRouter } from './service/router'; @@ -39,6 +39,7 @@ export type ScaffolderPluginOptions = { taskWorkers?: number; taskBroker?: TaskBroker; additionalTemplateFilters?: Record; + additionalTemplateGlobals?: Record; }; /** @@ -101,7 +102,12 @@ export const scaffolderPlugin = createBackendPlugin({ httpRouter, catalogClient, }) { - const { additionalTemplateFilters, taskBroker, taskWorkers } = options; + const { + additionalTemplateFilters, + taskBroker, + taskWorkers, + additionalTemplateGlobals, + } = options; const log = loggerToWinstonLogger(logger); const actions = options.actions || [ @@ -112,6 +118,7 @@ export const scaffolderPlugin = createBackendPlugin({ reader, config, additionalTemplateFilters, + additionalTemplateGlobals, }), ]; @@ -130,6 +137,7 @@ export const scaffolderPlugin = createBackendPlugin({ taskBroker, taskWorkers, additionalTemplateFilters, + additionalTemplateGlobals, }); httpRouter.use(router); }, diff --git a/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.ts b/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.ts index 0fb3c2e428..4e9b6f75c9 100644 --- a/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.ts +++ b/plugins/scaffolder-backend/src/extension/ScaffolderCatalogModule.ts @@ -22,7 +22,7 @@ import { ScaffolderEntitiesProcessor } from '../processor'; * Registers the ScaffolderEntitiesProcessor with the catalog processing extension point. */ export const scaffolderCatalogModule = createBackendModule({ - moduleId: 'scaffolder.module', + moduleId: 'scaffolder', pluginId: 'catalog', register(env) { env.registerInit({ diff --git a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.test.ts b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.test.ts index 8b8d7847ac..76643131f5 100644 --- a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.test.ts +++ b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.test.ts @@ -144,6 +144,27 @@ describe('SecureTemplater', () => { ['the input value', 'another extra arg'], ]); }); + it('should make additional globals available when requested', async () => { + const mockGlobal1 = jest.fn(() => 'awesome global function'); + const mockGlobal2 = 'foo'; + const mockGlobal3 = 123456; + const renderWith = await SecureTemplater.loadRenderer({ + additionalTemplateGlobals: { mockGlobal1, mockGlobal2, mockGlobal3 }, + }); + const renderWithout = await SecureTemplater.loadRenderer(); + + const ctx = {}; + + expect(renderWith('${{ mockGlobal1() }}', ctx)).toBe( + 'awesome global function', + ); + expect(renderWith('${{ mockGlobal2 }}', ctx)).toBe('foo'); + expect(renderWith('${{ mockGlobal3 }}', ctx)).toBe('123456'); + + expect(() => renderWithout('${{ mockGlobal1() }}', ctx)).toThrow( + /Error: Unable to call `mockGlobal1`/, + ); + }); it('should not allow helpers to be rewritten', async () => { const render = await SecureTemplater.loadRenderer({ diff --git a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts index e82620a90d..7dc42658be 100644 --- a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts +++ b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts @@ -64,6 +64,16 @@ const { render, renderCompat } = (() => { } } + if (typeof additionalTemplateGlobals !== 'undefined') { + for (const [globalName, global] of Object.entries(additionalTemplateGlobals)) { + if (typeof global === 'function') { + env.addGlobal(globalName, (...args) => JSON.parse(global(...args))); + } else { + env.addGlobal(globalName, JSON.parse(global)); + } + } + } + let uninstallCompat = undefined; function render(str, values) { @@ -98,6 +108,11 @@ const { render, renderCompat } = (() => { /** @public */ export type TemplateFilter = (...args: JsonValue[]) => JsonValue | undefined; +/** @public */ +export type TemplateGlobal = + | ((...args: JsonValue[]) => JsonValue | undefined) + | JsonValue; + export interface SecureTemplaterOptions { /* Optional implementation of the parseRepoUrl filter */ parseRepoUrl?(repoUrl: string): RepoSpec; @@ -107,6 +122,8 @@ export interface SecureTemplaterOptions { /* Extra user-provided nunjucks filters */ additionalTemplateFilters?: Record; + /* Extra user-provided nunjucks globals */ + additionalTemplateGlobals?: Record; } export type SecureTemplateRenderer = ( @@ -116,8 +133,12 @@ export type SecureTemplateRenderer = ( export class SecureTemplater { static async loadRenderer(options: SecureTemplaterOptions = {}) { - const { parseRepoUrl, cookiecutterCompat, additionalTemplateFilters } = - options; + const { + parseRepoUrl, + cookiecutterCompat, + additionalTemplateFilters, + additionalTemplateGlobals, + } = options; const sandbox: Record = {}; if (parseRepoUrl) { @@ -134,7 +155,21 @@ export class SecureTemplater { ]), ); } - + if (additionalTemplateGlobals) { + sandbox.additionalTemplateGlobals = Object.fromEntries( + Object.entries(additionalTemplateGlobals) + .filter(([_, global]) => !!global) + .map(([globalName, global]) => { + if (typeof global === 'function') { + return [ + globalName, + (...args: JsonValue[]) => JSON.stringify(global(...args)), + ]; + } + return [globalName, JSON.stringify(global)]; + }), + ); + } const vm = new VM({ sandbox }); const nunjucksSource = await fs.readFile( diff --git a/plugins/scaffolder-backend/src/lib/templating/index.ts b/plugins/scaffolder-backend/src/lib/templating/index.ts index 29d77291a1..1f9e5eb855 100644 --- a/plugins/scaffolder-backend/src/lib/templating/index.ts +++ b/plugins/scaffolder-backend/src/lib/templating/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export type { TemplateFilter } from './SecureTemplater'; +export type { TemplateFilter, TemplateGlobal } from './SecureTemplater'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index fcddf127e2..e388a70f00 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -28,7 +28,7 @@ import { createCatalogWriteAction, } from './catalog'; -import { TemplateFilter } from '../../../lib'; +import { TemplateFilter, TemplateGlobal } from '../../../lib'; import { TemplateAction } from '../types'; import { createDebugLogAction } from './debug'; import { createFetchPlainAction, createFetchTemplateAction } from './fetch'; @@ -82,6 +82,7 @@ export interface CreateBuiltInActionsOptions { * Template Manifests and also template skeleton files when using `fetch:template`. */ additionalTemplateFilters?: Record; + additionalTemplateGlobals?: Record; } /** @@ -100,6 +101,7 @@ export const createBuiltinActions = ( catalogClient, config, additionalTemplateFilters, + additionalTemplateGlobals, } = options; const githubCredentialsProvider: GithubCredentialsProvider = @@ -114,6 +116,7 @@ export const createBuiltinActions = ( integrations, reader, additionalTemplateFilters, + additionalTemplateGlobals, }), createPublishGerritAction({ integrations, 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 1ead7ebcd6..d71997d496 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -26,6 +26,7 @@ import { isBinaryFile } from 'isbinaryfile'; import { TemplateFilter, SecureTemplater, + TemplateGlobal, } from '../../../../lib/templating/SecureTemplater'; /** @@ -39,8 +40,14 @@ export function createFetchTemplateAction(options: { reader: UrlReader; integrations: ScmIntegrations; additionalTemplateFilters?: Record; + additionalTemplateGlobals?: Record; }) { - const { reader, integrations, additionalTemplateFilters } = options; + const { + reader, + integrations, + additionalTemplateFilters, + additionalTemplateGlobals, + } = options; return createTemplateAction<{ url: string; @@ -218,6 +225,7 @@ export function createFetchTemplateAction(options: { const renderTemplate = await SecureTemplater.loadRenderer({ cookiecutterCompat: ctx.input.cookiecutterCompat, additionalTemplateFilters, + additionalTemplateGlobals, }); for (const location of allEntriesInTemplate) { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.test.ts index f04a735bee..5692682882 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.test.ts @@ -108,6 +108,7 @@ describe('github:repo:create', () => { allow_squash_merge: true, allow_merge_commit: true, allow_rebase_merge: true, + allow_auto_merge: false, visibility: 'private', }); @@ -127,6 +128,7 @@ describe('github:repo:create', () => { allow_squash_merge: true, allow_merge_commit: true, allow_rebase_merge: true, + allow_auto_merge: false, visibility: 'public', }); @@ -147,6 +149,7 @@ describe('github:repo:create', () => { allow_squash_merge: true, allow_merge_commit: true, allow_rebase_merge: true, + allow_auto_merge: false, visibility: 'private', }); }); @@ -171,6 +174,7 @@ describe('github:repo:create', () => { allow_squash_merge: true, allow_merge_commit: true, allow_rebase_merge: true, + allow_auto_merge: false, }); await action.handler({ @@ -190,6 +194,7 @@ describe('github:repo:create', () => { allow_squash_merge: true, allow_merge_commit: true, allow_rebase_merge: true, + allow_auto_merge: false, }); await action.handler({ @@ -210,6 +215,7 @@ describe('github:repo:create', () => { allow_squash_merge: true, allow_merge_commit: true, allow_rebase_merge: true, + allow_auto_merge: false, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts index 5bc1fa8702..9348526b20 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoCreate.ts @@ -50,8 +50,10 @@ export function createGithubRepoCreateAction(options: { allowRebaseMerge?: boolean; allowSquashMerge?: boolean; allowMergeCommit?: boolean; + allowAutoMerge?: boolean; requireCodeOwnerReviews?: boolean; requiredStatusCheckContexts?: string[]; + requireBranchesToBeUpToDate?: boolean; repoVisibility?: 'private' | 'internal' | 'public'; collaborators?: Array< | { @@ -84,11 +86,13 @@ export function createGithubRepoCreateAction(options: { access: inputProps.access, requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, + requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, repoVisibility: inputProps.repoVisibility, deleteBranchOnMerge: inputProps.deleteBranchOnMerge, allowMergeCommit: inputProps.allowMergeCommit, allowSquashMerge: inputProps.allowSquashMerge, allowRebaseMerge: inputProps.allowRebaseMerge, + allowAutoMerge: inputProps.allowAutoMerge, collaborators: inputProps.collaborators, token: inputProps.token, topics: inputProps.topics, @@ -113,6 +117,7 @@ export function createGithubRepoCreateAction(options: { allowMergeCommit = true, allowSquashMerge = true, allowRebaseMerge = true, + allowAutoMerge = false, collaborators, topics, token: providedToken, @@ -143,6 +148,7 @@ export function createGithubRepoCreateAction(options: { allowMergeCommit, allowSquashMerge, allowRebaseMerge, + allowAutoMerge, access, collaborators, topics, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.test.ts index 20755ad781..fc39e03cbc 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.test.ts @@ -283,6 +283,7 @@ describe('github:repo:push', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: true, }); @@ -302,6 +303,7 @@ describe('github:repo:push', () => { defaultBranch: 'master', requireCodeOwnerReviews: true, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: true, }); @@ -321,6 +323,7 @@ describe('github:repo:push', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: true, }); }); @@ -343,6 +346,7 @@ describe('github:repo:push', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: true, }); @@ -362,6 +366,7 @@ describe('github:repo:push', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: true, }); @@ -381,11 +386,12 @@ describe('github:repo:push', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: false, }); }); - it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requiredStatusCheckContexts', async () => { + it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requiredStatusCheckContexts and requireBranchesToBeUpToDate', async () => { mockOctokit.rest.repos.get.mockResolvedValue({ data: { clone_url: 'https://github.com/clone/url.git', @@ -403,6 +409,7 @@ describe('github:repo:push', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: true, }); @@ -411,6 +418,7 @@ describe('github:repo:push', () => { input: { ...mockContext.input, requiredStatusCheckContexts: ['statusCheck'], + requireBranchesToBeUpToDate: true, }, }); @@ -422,6 +430,28 @@ describe('github:repo:push', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: ['statusCheck'], + requireBranchesToBeUpToDate: true, + enforceAdmins: true, + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + requiredStatusCheckContexts: ['statusCheck'], + requireBranchesToBeUpToDate: false, + }, + }); + + expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ + owner: 'owner', + client: mockOctokit, + repoName: 'repository', + logger: mockContext.logger, + defaultBranch: 'master', + requireCodeOwnerReviews: false, + requiredStatusCheckContexts: ['statusCheck'], + requireBranchesToBeUpToDate: false, enforceAdmins: true, }); @@ -430,6 +460,7 @@ describe('github:repo:push', () => { input: { ...mockContext.input, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, }, }); @@ -441,6 +472,7 @@ describe('github:repo:push', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: true, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts index b80502a80c..b02a475698 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/githubRepoPush.ts @@ -50,6 +50,7 @@ export function createGithubRepoPushAction(options: { gitAuthorEmail?: string; requireCodeOwnerReviews?: boolean; requiredStatusCheckContexts?: string[]; + requireBranchesToBeUpToDate?: boolean; sourcePath?: string; token?: string; }>({ @@ -64,6 +65,7 @@ export function createGithubRepoPushAction(options: { repoUrl: inputProps.repoUrl, requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, + requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, defaultBranch: inputProps.defaultBranch, protectDefaultBranch: inputProps.protectDefaultBranch, protectEnforceAdmins: inputProps.protectEnforceAdmins, @@ -93,6 +95,7 @@ export function createGithubRepoPushAction(options: { gitAuthorEmail, requireCodeOwnerReviews = false, requiredStatusCheckContexts = [], + requireBranchesToBeUpToDate = true, token: providedToken, } = ctx.input; @@ -129,6 +132,7 @@ export function createGithubRepoPushAction(options: { repo, requireCodeOwnerReviews, requiredStatusCheckContexts, + requireBranchesToBeUpToDate, config, ctx.logger, gitCommitMessage, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/helpers.ts index fd3899b18a..2264da1dac 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/helpers.ts @@ -102,6 +102,7 @@ export async function createGithubRepoWithCollaboratorsAndTopics( allowMergeCommit: boolean, allowSquashMerge: boolean, allowRebaseMerge: boolean, + allowAutoMerge: boolean, access: string | undefined, collaborators: | ( @@ -139,6 +140,7 @@ export async function createGithubRepoWithCollaboratorsAndTopics( allow_merge_commit: allowMergeCommit, allow_squash_merge: allowSquashMerge, allow_rebase_merge: allowRebaseMerge, + allow_auto_merge: allowAutoMerge, homepage: homepage, }) : client.rest.repos.createForAuthenticatedUser({ @@ -149,6 +151,7 @@ export async function createGithubRepoWithCollaboratorsAndTopics( allow_merge_commit: allowMergeCommit, allow_squash_merge: allowSquashMerge, allow_rebase_merge: allowRebaseMerge, + allow_auto_merge: allowAutoMerge, homepage: homepage, }); @@ -245,6 +248,7 @@ export async function initRepoPushAndProtect( repo: string, requireCodeOwnerReviews: boolean, requiredStatusCheckContexts: string[], + requireBranchesToBeUpToDate: boolean, config: Config, logger: any, gitCommitMessage?: string, @@ -287,6 +291,7 @@ export async function initRepoPushAndProtect( defaultBranch, requireCodeOwnerReviews, requiredStatusCheckContexts, + requireBranchesToBeUpToDate, enforceAdmins: protectEnforceAdmins, }); } catch (e) { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/inputProperties.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/inputProperties.ts index 153b2e6941..b00d5fa132 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/inputProperties.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/github/inputProperties.ts @@ -47,6 +47,11 @@ const requiredStatusCheckContexts = { type: 'string', }, }; +const requireBranchesToBeUpToDate = { + title: 'Require Branches To Be Up To Date?', + description: `Require branches to be up to date before merging. The default value is 'true'`, + type: 'boolean', +}; const repoVisibility = { title: 'Repository Visibility', type: 'string', @@ -82,6 +87,11 @@ const allowRebaseMerge = { type: 'boolean', description: `Allow rebase merges. The default value is 'true'`, }; +const allowAutoMerge = { + title: 'Allow Auto Merges', + type: 'boolean', + description: `Allow individual PRs to merge automatically when all merge requirements are met. The default value is 'false'`, +}; const collaborators = { title: 'Collaborators', description: 'Provide additional users or teams with permissions', @@ -153,6 +163,7 @@ export { access }; export { allowMergeCommit }; export { allowRebaseMerge }; export { allowSquashMerge }; +export { allowAutoMerge }; export { collaborators }; export { defaultBranch }; export { deleteBranchOnMerge }; @@ -167,6 +178,7 @@ export { repoUrl }; export { repoVisibility }; export { requireCodeOwnerReviews }; export { requiredStatusCheckContexts }; +export { requireBranchesToBeUpToDate }; export { sourcePath }; export { token }; export { topics }; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts index d0cdcd627a..be77d27a9a 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts @@ -185,6 +185,7 @@ type BranchProtectionOptions = { logger: Logger; requireCodeOwnerReviews: boolean; requiredStatusCheckContexts?: string[]; + requireBranchesToBeUpToDate?: boolean; defaultBranch?: string; enforceAdmins?: boolean; }; @@ -196,6 +197,7 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ logger, requireCodeOwnerReviews, requiredStatusCheckContexts = [], + requireBranchesToBeUpToDate = true, defaultBranch = 'master', enforceAdmins = true, }: BranchProtectionOptions): Promise => { @@ -216,7 +218,7 @@ export const enableBranchProtectionOnDefaultRepoBranch = async ({ repo: repoName, branch: defaultBranch, required_status_checks: { - strict: true, + strict: requireBranchesToBeUpToDate, contexts: requiredStatusCheckContexts, }, restrictions: null, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServer.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServer.test.ts index 06d3dfb735..52f445098b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServer.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServer.test.ts @@ -123,7 +123,11 @@ describe('publish:bitbucketServer', () => { 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Bearer thing'); - expect(req.body).toEqual({ public: false, name: 'repo' }); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), @@ -165,7 +169,11 @@ describe('publish:bitbucketServer', () => { expect(req.headers.get('Authorization')).toBe( 'Basic dGVzdC11c2VyOnRlc3QtcGFzc3dvcmQ=', ); - expect(req.body).toEqual({ public: false, name: 'repo' }); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), @@ -206,7 +214,11 @@ describe('publish:bitbucketServer', () => { 'https://no-credentials.bitbucket.com/rest/api/1.0/projects/project/repos', (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe(`Bearer ${token}`); - expect(req.body).toEqual({ public: false, name: 'repo' }); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), @@ -327,7 +339,11 @@ describe('publish:bitbucketServer', () => { 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Bearer thing'); - expect(req.body).toEqual({ public: false, name: 'repo' }); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), @@ -371,7 +387,11 @@ describe('publish:bitbucketServer', () => { expect(req.headers.get('Authorization')).toBe( 'Basic dGVzdC11c2VyOnRlc3QtcGFzc3dvcmQ=', ); - expect(req.body).toEqual({ public: false, name: 'repo' }); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), @@ -419,7 +439,11 @@ describe('publish:bitbucketServer', () => { 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Bearer thing'); - expect(req.body).toEqual({ public: false, name: 'repo' }); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'main', + }); return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), @@ -495,7 +519,11 @@ describe('publish:bitbucketServer', () => { 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Bearer thing'); - expect(req.body).toEqual({ public: false, name: 'repo' }); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), @@ -562,7 +590,11 @@ describe('publish:bitbucketServer', () => { 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Bearer thing'); - expect(req.body).toEqual({ public: false, name: 'repo' }); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), @@ -605,7 +637,11 @@ describe('publish:bitbucketServer', () => { 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Bearer thing'); - expect(req.body).toEqual({ public: false, name: 'repo' }); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'master', + }); return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), @@ -647,7 +683,11 @@ describe('publish:bitbucketServer', () => { 'https://hosted.bitbucket.com/rest/api/1.0/projects/project/repos', (req, res, ctx) => { expect(req.headers.get('Authorization')).toBe('Bearer thing'); - expect(req.body).toEqual({ public: false, name: 'repo' }); + expect(req.body).toEqual({ + public: false, + name: 'repo', + defaultBranch: 'main', + }); return res( ctx.status(201), ctx.set('Content-Type', 'application/json'), diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServer.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServer.ts index 712a17de70..d0cde4ad31 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServer.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucketServer.ts @@ -30,6 +30,7 @@ const createRepository = async (opts: { repo: string; description?: string; repoVisibility: 'private' | 'public'; + defaultBranch: string; authorization: string; apiBaseUrl: string; }) => { @@ -39,6 +40,7 @@ const createRepository = async (opts: { description, authorization, repoVisibility, + defaultBranch, apiBaseUrl, } = opts; @@ -48,6 +50,7 @@ const createRepository = async (opts: { body: JSON.stringify({ name: repo, description: description, + defaultBranch: defaultBranch, public: repoVisibility === 'public', }), headers: { @@ -232,6 +235,7 @@ export function createPublishBitbucketServerAction(options: { project, repo, repoVisibility, + defaultBranch, description, apiBaseUrl, }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts index 6a2665bc8d..cebc968669 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts @@ -113,6 +113,7 @@ describe('publish:github', () => { allow_squash_merge: true, allow_merge_commit: true, allow_rebase_merge: true, + allow_auto_merge: false, visibility: 'private', }); @@ -132,6 +133,7 @@ describe('publish:github', () => { allow_squash_merge: true, allow_merge_commit: true, allow_rebase_merge: true, + allow_auto_merge: false, visibility: 'public', }); @@ -152,6 +154,7 @@ describe('publish:github', () => { allow_squash_merge: true, allow_merge_commit: true, allow_rebase_merge: true, + allow_auto_merge: false, visibility: 'private', }); }); @@ -176,6 +179,7 @@ describe('publish:github', () => { allow_squash_merge: true, allow_merge_commit: true, allow_rebase_merge: true, + allow_auto_merge: false, }); await action.handler({ @@ -195,6 +199,7 @@ describe('publish:github', () => { allow_squash_merge: true, allow_merge_commit: true, allow_rebase_merge: true, + allow_auto_merge: false, }); await action.handler({ @@ -215,6 +220,7 @@ describe('publish:github', () => { allow_squash_merge: true, allow_merge_commit: true, allow_rebase_merge: true, + allow_auto_merge: false, }); }); @@ -663,6 +669,7 @@ describe('publish:github', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: true, }); @@ -682,6 +689,7 @@ describe('publish:github', () => { defaultBranch: 'master', requireCodeOwnerReviews: true, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: true, }); @@ -701,6 +709,7 @@ describe('publish:github', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: true, }); }); @@ -726,6 +735,7 @@ describe('publish:github', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: true, }); @@ -745,6 +755,7 @@ describe('publish:github', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: false, }); @@ -764,11 +775,12 @@ describe('publish:github', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: true, }); }); - it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requiredStatusCheckContexts', async () => { + it('should call enableBranchProtectionOnDefaultRepoBranch with the correct values of requiredStatusCheckContexts and requireBranchesToBeUpToDate', async () => { mockOctokit.rest.users.getByUsername.mockResolvedValue({ data: { type: 'User' }, }); @@ -789,6 +801,7 @@ describe('publish:github', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: true, }); @@ -797,6 +810,7 @@ describe('publish:github', () => { input: { ...mockContext.input, requiredStatusCheckContexts: ['statusCheck'], + requireBranchesToBeUpToDate: true, }, }); @@ -808,6 +822,28 @@ describe('publish:github', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: ['statusCheck'], + requireBranchesToBeUpToDate: true, + enforceAdmins: true, + }); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + requiredStatusCheckContexts: ['statusCheck'], + requireBranchesToBeUpToDate: false, + }, + }); + + expect(enableBranchProtectionOnDefaultRepoBranch).toHaveBeenCalledWith({ + owner: 'owner', + client: mockOctokit, + repoName: 'repo', + logger: mockContext.logger, + defaultBranch: 'master', + requireCodeOwnerReviews: false, + requiredStatusCheckContexts: ['statusCheck'], + requireBranchesToBeUpToDate: false, enforceAdmins: true, }); @@ -827,6 +863,7 @@ describe('publish:github', () => { defaultBranch: 'master', requireCodeOwnerReviews: false, requiredStatusCheckContexts: [], + requireBranchesToBeUpToDate: true, enforceAdmins: true, }); }); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts index 8ce9105237..c1f8e8b5b6 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts @@ -57,9 +57,11 @@ export function createPublishGithubAction(options: { allowRebaseMerge?: boolean; allowSquashMerge?: boolean; allowMergeCommit?: boolean; + allowAutoMerge?: boolean; sourcePath?: string; requireCodeOwnerReviews?: boolean; requiredStatusCheckContexts?: string[]; + requireBranchesToBeUpToDate?: boolean; repoVisibility?: 'private' | 'internal' | 'public'; collaborators?: Array< | { @@ -93,6 +95,7 @@ export function createPublishGithubAction(options: { access: inputProps.access, requireCodeOwnerReviews: inputProps.requireCodeOwnerReviews, requiredStatusCheckContexts: inputProps.requiredStatusCheckContexts, + requireBranchesToBeUpToDate: inputProps.requireBranchesToBeUpToDate, repoVisibility: inputProps.repoVisibility, defaultBranch: inputProps.defaultBranch, protectDefaultBranch: inputProps.protectDefaultBranch, @@ -104,6 +107,7 @@ export function createPublishGithubAction(options: { allowMergeCommit: inputProps.allowMergeCommit, allowSquashMerge: inputProps.allowSquashMerge, allowRebaseMerge: inputProps.allowRebaseMerge, + allowAutoMerge: inputProps.allowAutoMerge, sourcePath: inputProps.sourcePath, collaborators: inputProps.collaborators, token: inputProps.token, @@ -126,6 +130,7 @@ export function createPublishGithubAction(options: { access, requireCodeOwnerReviews = false, requiredStatusCheckContexts = [], + requireBranchesToBeUpToDate = true, repoVisibility = 'private', defaultBranch = 'master', protectDefaultBranch = true, @@ -137,6 +142,7 @@ export function createPublishGithubAction(options: { allowMergeCommit = true, allowSquashMerge = true, allowRebaseMerge = true, + allowAutoMerge = false, collaborators, topics, token: providedToken, @@ -167,6 +173,7 @@ export function createPublishGithubAction(options: { allowMergeCommit, allowSquashMerge, allowRebaseMerge, + allowAutoMerge, access, collaborators, topics, @@ -189,6 +196,7 @@ export function createPublishGithubAction(options: { repo, requireCodeOwnerReviews, requiredStatusCheckContexts, + requireBranchesToBeUpToDate, config, ctx.logger, gitCommitMessage, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.test.ts index 76fe60a2b0..d1888fd9d2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.test.ts @@ -108,7 +108,6 @@ describe('createGitLabMergeRequest', () => { title: 'Create my new MR', branchName: 'new-mr', description: 'This MR is really good', - draft: true, targetPath: 'Subdirectory', }; mockFs({ @@ -145,7 +144,6 @@ describe('createGitLabMergeRequest', () => { branchName: 'new-mr', description: 'MR description', removeSourceBranch: true, - draft: true, targetPath: 'Subdirectory', }; mockFs({ @@ -181,7 +179,6 @@ describe('createGitLabMergeRequest', () => { branchName: 'new-mr', description: 'other MR description', removeSourceBranch: false, - draft: true, targetPath: 'Subdirectory', }; mockFs({ @@ -222,7 +219,6 @@ describe('createGitLabMergeRequest', () => { branchName: 'new-mr', description: 'This is an important change', removeSourceBranch: false, - draft: true, targetPath: 'Subdirectory', assignee: 'John Smith', }; @@ -263,7 +259,6 @@ describe('createGitLabMergeRequest', () => { branchName: 'new-mr', description: 'This is an important change', removeSourceBranch: false, - draft: true, targetPath: 'Subdirectory', assingnee: 'John Doe', }; @@ -305,7 +300,6 @@ describe('createGitLabMergeRequest', () => { branchName: 'new-mr', description: 'This is an important change', removeSourceBranch: false, - draft: true, targetPath: 'Subdirectory', }; mockFs({ @@ -344,7 +338,6 @@ describe('createGitLabMergeRequest', () => { branchName: 'new-mr', description: 'This is an important change', removeSourceBranch: false, - draft: true, targetPath: 'Subdirectory', assignee: 'Unknown', }; @@ -376,6 +369,52 @@ describe('createGitLabMergeRequest', () => { }, ); }); + + it('use workspacePath as default when no sourcePath or targetPath is specified', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'This MR is really good', + }; + mockFs({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!' }, + irrelevant: { 'bar.txt': 'Nothing to see here' }, + }, + }); + const ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath, + }; + await instance.handler(ctx); + + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'Create my new MR', + [ + { + action: 'create', + filePath: 'irrelevant/bar.txt', + encoding: 'base64', + content: 'Tm90aGluZyB0byBzZWUgaGVyZQ==', + execute_filemode: false, + }, + { + action: 'create', + filePath: 'source/foo.txt', + encoding: 'base64', + content: 'SGVsbG8gdGhlcmUh', + execute_filemode: false, + }, + ], + ); + }); }); describe('createGitLabMergeRequestWithoutCommitAction', () => { @@ -385,7 +424,6 @@ describe('createGitLabMergeRequest', () => { title: 'Create my new MR', branchName: 'new-mr', description: 'This MR is really good', - draft: true, targetPath: 'source', }; mockFs({ @@ -429,7 +467,6 @@ describe('createGitLabMergeRequest', () => { branchName: 'new-mr', description: 'MR description', commitAction: 'create', - draft: true, targetPath: 'source', }; mockFs({ @@ -472,7 +509,6 @@ describe('createGitLabMergeRequest', () => { branchName: 'new-mr', description: 'MR description', commitAction: 'update', - draft: true, targetPath: 'source', }; mockFs({ @@ -515,7 +551,6 @@ describe('createGitLabMergeRequest', () => { branchName: 'new-mr', description: 'other MR description', commitAction: 'delete', - draft: true, targetPath: 'source', }; mockFs({ @@ -551,4 +586,119 @@ describe('createGitLabMergeRequest', () => { ); }); }); + + describe('with sourcePath', () => { + it('creates a Merge Request with only relevant files', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'MR description', + sourcePath: 'source', + commitAction: 'create', + }; + + mockFs({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!' }, + irrelevant: { 'bar.txt': 'Nothing to see here' }, + }, + }); + + const ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath, + }; + + await instance.handler(ctx); + + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'Create my new MR', + [ + { + action: 'create', + filePath: 'foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + }); + + it('creates a Merge Request with only relevant files placed under different targetPath', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'MR description', + sourcePath: 'source', + targetPath: 'target', + commitAction: 'create', + }; + + mockFs({ + [workspacePath]: { + source: { 'foo.txt': 'Hello there!' }, + irrelevant: { 'bar.txt': 'Nothing to see here' }, + }, + }); + + const ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath, + }; + + await instance.handler(ctx); + + expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith( + 'owner/repo', + 'new-mr', + 'Create my new MR', + [ + { + action: 'create', + filePath: 'target/foo.txt', + content: 'SGVsbG8gdGhlcmUh', + encoding: 'base64', + execute_filemode: false, + }, + ], + ); + }); + + it('should not allow to use files outside of the workspace', async () => { + const input = { + repoUrl: 'gitlab.com?repo=repo&owner=owner', + title: 'Create my new MR', + branchName: 'new-mr', + description: 'MR description', + sourcePath: '../../test', + commitAction: 'create', + }; + + const ctx = { + createTemporaryDirectory: jest.fn(), + output: jest.fn(), + logger: getRootLogger(), + logStream: new Writable(), + input, + workspacePath, + }; + + await expect(instance.handler(ctx)).rejects.toThrow( + 'Relative path is not allowed to refer to a directory outside its parent', + ); + }); + }); }); 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 1410096a73..64761f0871 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitlabMergeRequest.ts @@ -38,7 +38,8 @@ export const createPublishGitlabMergeRequestAction = (options: { title: string; description: string; branchName: string; - targetPath: string; + sourcePath?: string; + targetPath?: string; token?: string; commitAction?: 'create' | 'delete' | 'update'; /** @deprecated Use projectPath instead */ @@ -49,7 +50,7 @@ export const createPublishGitlabMergeRequestAction = (options: { id: 'publish:gitlab:merge-request', schema: { input: { - required: ['repoUrl', 'targetPath', 'branchName'], + required: ['repoUrl', 'branchName'], type: 'object', properties: { repoUrl: { @@ -78,6 +79,12 @@ export const createPublishGitlabMergeRequestAction = (options: { title: 'Destination Branch name', description: 'The description of the merge request', }, + sourcePath: { + type: 'string', + title: 'Working Subdirectory', + description: + 'Subdirectory of working directory to copy changes from', + }, targetPath: { type: 'string', title: 'Repository Subdirectory', @@ -128,7 +135,17 @@ export const createPublishGitlabMergeRequestAction = (options: { }, }, async handler(ctx) { - const repoUrl = ctx.input.repoUrl; + const { + assignee, + branchName, + description, + repoUrl, + removeSourceBranch, + targetPath, + sourcePath, + title, + token: providedToken, + } = ctx.input; const { host, owner, repo } = parseRepoUrl(repoUrl, integrations); const projectPath = `${owner}/${repo}`; @@ -140,28 +157,24 @@ export const createPublishGitlabMergeRequestAction = (options: { const integrationConfig = integrations.gitlab.byHost(host); - const destinationBranch = ctx.input.branchName; - if (!integrationConfig) { throw new InputError( `No matching integration configuration for host ${host}, please check your integrations config`, ); } - if (!integrationConfig.config.token && !ctx.input.token) { + if (!integrationConfig.config.token && !providedToken) { throw new InputError(`No token available for host ${host}`); } - const token = ctx.input.token ?? integrationConfig.config.token!; - const tokenType = ctx.input.token ? 'oauthToken' : 'token'; + const token = providedToken ?? integrationConfig.config.token!; + const tokenType = providedToken ? 'oauthToken' : 'token'; const api = new Gitlab({ host: integrationConfig.config.baseUrl, [tokenType]: token, }); - const assignee = ctx.input.assignee; - let assigneeId = undefined; if (assignee !== undefined) { @@ -175,17 +188,25 @@ export const createPublishGitlabMergeRequestAction = (options: { } } - const targetPath = resolveSafeChildPath( - ctx.workspacePath, - ctx.input.targetPath, - ); - const fileContents = await serializeDirectoryContents(targetPath, { + let fileRoot: string; + if (sourcePath) { + fileRoot = resolveSafeChildPath(ctx.workspacePath, sourcePath); + } else if (targetPath) { + // for backward compatibility + fileRoot = resolveSafeChildPath(ctx.workspacePath, targetPath); + } else { + fileRoot = ctx.workspacePath; + } + + const fileContents = await serializeDirectoryContents(fileRoot, { gitignore: true, }); const actions: Types.CommitAction[] = fileContents.map(file => ({ action: ctx.input.commitAction ?? 'create', - filePath: path.posix.join(ctx.input.targetPath, file.path), + filePath: targetPath + ? path.posix.join(targetPath, file.path) + : file.path, encoding: 'base64', content: file.content.toString('base64'), execute_filemode: file.executable, @@ -197,7 +218,7 @@ export const createPublishGitlabMergeRequestAction = (options: { try { await api.Branches.create( projectPath, - destinationBranch, + branchName, String(defaultBranch), ); } catch (e) { @@ -205,30 +226,23 @@ export const createPublishGitlabMergeRequestAction = (options: { } try { - await api.Commits.create( - projectPath, - destinationBranch, - ctx.input.title, - actions, - ); + await api.Commits.create(projectPath, branchName, title, actions); } catch (e) { throw new InputError( - `Committing the changes to ${destinationBranch} failed ${e}`, + `Committing the changes to ${branchName} failed ${e}`, ); } try { const mergeRequestUrl = await api.MergeRequests.create( projectPath, - destinationBranch, + branchName, String(defaultBranch), - ctx.input.title, + title, { - description: ctx.input.description, - removeSourceBranch: ctx.input.removeSourceBranch - ? ctx.input.removeSourceBranch - : false, - assigneeId: assigneeId, + description, + removeSourceBranch: removeSourceBranch ? removeSourceBranch : false, + assigneeId, }, ).then((mergeRequest: { web_url: string }) => { return mergeRequest.web_url; diff --git a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts index f2c3c13509..17f9092bff 100644 --- a/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/dryrun/createDryRunner.ts @@ -25,7 +25,7 @@ import { SerializedFile, serializeDirectoryContents, } from '../../lib/files'; -import { TemplateFilter } from '../../lib/templating'; +import { TemplateFilter, TemplateGlobal } from '../../lib/templating'; import { createTemplateAction, TemplateActionRegistry } from '../actions'; import { NunjucksWorkflowRunner } from '../tasks/NunjucksWorkflowRunner'; import { TaskSecrets } from '../tasks/types'; @@ -52,6 +52,7 @@ export type TemplateTesterCreateOptions = { actionRegistry: TemplateActionRegistry; workingDirectory: string; additionalTemplateFilters?: Record; + additionalTemplateGlobals?: Record; }; /** diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts index 4f3ca032bc..5893a88485 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts @@ -31,6 +31,7 @@ import { TemplateFilter, SecureTemplater, SecureTemplateRenderer, + TemplateGlobal, } from '../../lib/templating/SecureTemplater'; import { TaskSpec, @@ -46,6 +47,7 @@ type NunjucksWorkflowRunnerOptions = { integrations: ScmIntegrations; logger: winston.Logger; additionalTemplateFilters?: Record; + additionalTemplateGlobals?: Record; }; type TemplateContext = { @@ -76,7 +78,6 @@ const createStepLogger = ({ level: process.env.LOG_LEVEL || 'info', format: winston.format.combine( winston.format.colorize(), - winston.format.timestamp(), winston.format.simple(), ), defaultMeta: {}, @@ -199,6 +200,7 @@ export class NunjucksWorkflowRunner implements WorkflowRunner { return parseRepoUrl(url, integrations); }, additionalTemplateFilters: this.options.additionalTemplateFilters, + additionalTemplateGlobals: this.options.additionalTemplateGlobals, }); try { diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts index 4fc142a991..421afb2329 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/StorageTaskBroker.test.ts @@ -126,12 +126,15 @@ describe('StorageTaskBroker', () => { const logPromise = new Promise(resolve => { const observedEvents = new Array(); - broker2.event$({ taskId, after: undefined }).subscribe(({ events }) => { - observedEvents.push(...events); - if (events.some(e => e.type === 'completion')) { - resolve(observedEvents); - } - }); + const subscription = broker2 + .event$({ taskId, after: undefined }) + .subscribe(({ events }) => { + observedEvents.push(...events); + if (events.some(e => e.type === 'completion')) { + resolve(observedEvents); + subscription.unsubscribe(); + } + }); }); const task = await broker1.claim(); await task.emitLog('log 1'); @@ -148,11 +151,12 @@ describe('StorageTaskBroker', () => { ]); const afterLogs = await new Promise(resolve => { - broker2 + const subscription = broker2 .event$({ taskId, after: logs[1].id }) - .subscribe(({ events }) => - resolve(events.map(e => e.body.message as string)), - ); + .subscribe(({ events }) => { + resolve(events.map(e => e.body.message as string)); + subscription.unsubscribe(); + }); }); expect(afterLogs).toEqual([ 'log 3', diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index 9defd71c49..52ecd1a4dc 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -20,7 +20,10 @@ import { Logger } from 'winston'; import { TemplateActionRegistry } from '../actions'; import { ScmIntegrations } from '@backstage/integration'; import { assertError } from '@backstage/errors'; -import { TemplateFilter } from '../../lib/templating/SecureTemplater'; +import { + TemplateFilter, + TemplateGlobal, +} from '../../lib/templating/SecureTemplater'; /** * TaskWorkerOptions @@ -46,6 +49,7 @@ export type CreateWorkerOptions = { workingDirectory: string; logger: Logger; additionalTemplateFilters?: Record; + additionalTemplateGlobals?: Record; }; /** @@ -64,6 +68,7 @@ export class TaskWorker { integrations, workingDirectory, additionalTemplateFilters, + additionalTemplateGlobals, } = options; const workflowRunner = new NunjucksWorkflowRunner({ @@ -72,6 +77,7 @@ export class TaskWorker { logger, workingDirectory, additionalTemplateFilters, + additionalTemplateGlobals, }); return new TaskWorker({ diff --git a/plugins/scaffolder-backend/src/service/router.ts b/plugins/scaffolder-backend/src/service/router.ts index 28dd1ca413..eff7e95c50 100644 --- a/plugins/scaffolder-backend/src/service/router.ts +++ b/plugins/scaffolder-backend/src/service/router.ts @@ -36,7 +36,7 @@ import Router from 'express-promise-router'; import { validate } from 'jsonschema'; import { Logger } from 'winston'; import { z } from 'zod'; -import { TemplateFilter } from '../lib'; +import { TemplateFilter, TemplateGlobal } from '../lib'; import { createBuiltinActions, DatabaseTaskStore, @@ -70,6 +70,7 @@ export interface RouterOptions { taskWorkers?: number; taskBroker?: TaskBroker; additionalTemplateFilters?: Record; + additionalTemplateGlobals?: Record; identity?: IdentityApi; } @@ -148,7 +149,8 @@ export async function createRouter( options: RouterOptions, ): Promise { const router = Router(); - router.use(express.json()); + // Be generous in upload size to support a wide range of templates in dry-run mode. + router.use(express.json({ limit: '10MB' })); const { logger: parentLogger, @@ -160,6 +162,7 @@ export async function createRouter( taskWorkers, scheduler, additionalTemplateFilters, + additionalTemplateGlobals, } = options; const logger = parentLogger.child({ plugin: 'scaffolder' }); @@ -207,6 +210,7 @@ export async function createRouter( logger, workingDirectory, additionalTemplateFilters, + additionalTemplateGlobals, }); workers.push(worker); } @@ -219,6 +223,7 @@ export async function createRouter( reader, config, additionalTemplateFilters, + additionalTemplateGlobals, }); actionsToRegister.forEach(action => actionRegistry.register(action)); @@ -230,6 +235,7 @@ export async function createRouter( logger, workingDirectory, additionalTemplateFilters, + additionalTemplateGlobals, }); router diff --git a/plugins/scaffolder-common/CHANGELOG.md b/plugins/scaffolder-common/CHANGELOG.md index 4d05c26cee..f2db35b47b 100644 --- a/plugins/scaffolder-common/CHANGELOG.md +++ b/plugins/scaffolder-common/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-scaffolder-common +## 1.2.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/types@1.0.0 + +## 1.2.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/types@1.0.0 + +## 1.2.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/types@1.0.0 + +## 1.2.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/types@1.0.0 + ## 1.2.0 ### Minor Changes diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index ab7508d215..2ccb5bdd4f 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder-common", "description": "Common functionalities for the scaffolder, to be shared between scaffolder and scaffolder-backend plugin", - "version": "1.2.0", + "version": "1.2.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 2c1640c5e8..d972491a64 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,111 @@ # @backstage/plugin-scaffolder +## 1.7.0 + +### Minor Changes + +- f13d5f3f06: Add support for link to TechDocs and other links defined in template entity specification metadata on TemplateCard +- 05f22193c5: EntityPickers now support flags to control when to include default namespace + in result + +### Patch Changes + +- e4f0a96424: Making the description of the GitLab repoUrl owner field more clearer by focusing it refers to the GitLab namespace. +- 92e490d6b4: Make the `/next` scaffolder work end to end with the old `TaskPage` view +- 8220f2fd83: Support custom layouts in dry run editor +- 1047baa926: Bump to `react-jsonschema-form@v5-beta` for the `NextRouter` under `@alpha` exports +- 98ae18b68f: Fixed a bug where the `allowed*` values for the `RepoUrlPicker` would be reset on render. +- 8960d83013: Add support for `allowedOrganizations` and `allowedOwners` to the `AzureRepoPicker`. +- b681275e69: Ignore .git directories in Template Editor, increase upload limit for dry-runs to 10MB. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-scaffolder-common@1.2.1 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-react@0.4.6 + +## 1.7.0-next.2 + +### Patch Changes + +- 92e490d6b4: Make the `/next` scaffolder work end to end with the old `TaskPage` view +- 1047baa926: Bump to `react-jsonschema-form@v5-beta` for the `NextRouter` under `@alpha` exports +- 98ae18b68f: Fixed a bug where the `allowed*` values for the `RepoUrlPicker` would be reset on render. +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-permission-react@0.4.6-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.2 + +## 1.7.0-next.1 + +### Patch Changes + +- e4f0a96424: Making the description of the GitLab repoUrl owner field more clearer by focusing it refers to the GitLab namespace. +- 8220f2fd83: Support custom layouts in dry run editor +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.7-next.1 + - @backstage/plugin-permission-react@0.4.6-next.1 + - @backstage/plugin-scaffolder-common@1.2.1-next.1 + +## 1.7.0-next.0 + +### Minor Changes + +- f13d5f3f06: Add support for link to TechDocs and other links defined in template entity specification metadata on TemplateCard +- 05f22193c5: EntityPickers now support flags to control when to include default namespace + in result + +### Patch Changes + +- 8960d83013: Add support for `allowedOrganizations` and `allowedOwners` to the `AzureRepoPicker`. +- b681275e69: Ignore .git directories in Template Editor, increase upload limit for dry-runs to 10MB. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-scaffolder-common@1.2.1-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-permission-react@0.4.6-next.0 + ## 1.6.0 ### Minor Changes diff --git a/plugins/scaffolder/README.md b/plugins/scaffolder/README.md index b74c03ec34..83fa01e6e5 100644 --- a/plugins/scaffolder/README.md +++ b/plugins/scaffolder/README.md @@ -79,6 +79,48 @@ export const Root = ({ children }: PropsWithChildren<{}>) => ( ``` +### Troubleshooting + +If you encounter the [issue of closing EventStream](https://github.com/backstage/backstage/issues/5535) +which auto-updates logs during task execution, you can enable long polling. To do so, +update your `packages/app/src/apis.ts` file to register a `ScaffolderClient` with the +`useLongPollingLogs` set to `true`. By default, it is `false`. + +```typescript +import { + createApiFactory, + discoveryApiRef, + fetchApiRef, + identityApiRef, +} from '@backstage/core-plugin-api'; +import { + scaffolderApiRef, + ScaffolderClient, +} from '@backstage/plugin-scaffolder'; + +export const apis: AnyApiFactory[] = [ + createApiFactory({ + api: scaffolderApiRef, + deps: { + discoveryApi: discoveryApiRef, + identityApi: identityApiRef, + scmIntegrationsApi: scmIntegrationsApiRef, + fetchApi: fetchApiRef, + }, + factory: ({ scmIntegrationsApi, discoveryApi, identityApi, fetchApi }) => + new ScaffolderClient({ + discoveryApi, + identityApi, + scmIntegrationsApi, + fetchApi, + useLongPollingLogs: true, + }), + }), + // ... other factories +``` + +This replaces the default implementation of the `scaffolderApiRef`. + ## Links - [scaffolder-backend](https://github.com/backstage/backstage/tree/master/plugins/scaffolder-backend) diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index e88d2861a7..cf69736bd4 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -15,7 +15,9 @@ import { Extension } from '@backstage/core-plugin-api'; import { ExternalRouteRef } from '@backstage/core-plugin-api'; import { FetchApi } from '@backstage/core-plugin-api'; import { FieldProps } from '@rjsf/core'; +import { FieldProps as FieldProps_2 } from '@rjsf/utils'; import { FieldValidation } from '@rjsf/core'; +import { FieldValidation as FieldValidation_2 } from '@rjsf/utils'; import type { FormProps } from '@rjsf/core'; import { IdentityApi } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; @@ -29,6 +31,16 @@ 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'; +import { UIOptionsType } from '@rjsf/utils'; +import { UiSchema } from '@rjsf/utils'; + +// @alpha +export function createNextScaffolderFieldExtension< + TReturnValue = unknown, + TInputProps extends UIOptionsType = {}, +>( + options: NextFieldExtensionOptions, +): Extension>; // @public export function createScaffolderFieldExtension< @@ -155,6 +167,38 @@ export type LogEvent = { taskId: string; }; +// @alpha +export type NextCustomFieldValidator = ( + data: TFieldReturnValue, + field: FieldValidation_2, + context: { + apiHolder: ApiHolder; + }, +) => void | Promise; + +// @alpha +export interface NextFieldExtensionComponentProps< + TFieldReturnValue, + TUiOptions = {}, +> extends PropsWithChildren> { + // (undocumented) + uiSchema?: UiSchema & { + 'ui:options'?: TUiOptions & UIOptionsType; + }; +} + +// @alpha +export type NextFieldExtensionOptions< + TFieldReturnValue = unknown, + TInputProps = unknown, +> = { + name: string; + component: ( + props: NextFieldExtensionComponentProps, + ) => JSX.Element | null; + validation?: NextCustomFieldValidator; +}; + // @alpha export type NextRouterProps = { components?: { @@ -225,6 +269,8 @@ export interface RepoUrlPickerUiOptions { // (undocumented) allowedHosts?: string[]; // (undocumented) + allowedOrganizations?: string[]; + // (undocumented) allowedOwners?: string[]; // (undocumented) allowedRepos?: string[]; @@ -396,6 +442,14 @@ export const scaffolderPlugin: BackstagePlugin< }, { registerComponent: ExternalRouteRef; + viewTechDoc: ExternalRouteRef< + { + name: string; + kind: string; + namespace: string; + }, + true + >; }, {} >; diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 0debc7dd23..0825e624d6 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-scaffolder", "description": "The Backstage plugin that helps you create new things", - "version": "1.6.0", + "version": "1.7.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -55,7 +55,11 @@ "@material-ui/lab": "4.0.0-alpha.57", "@react-hookz/web": "^15.0.0", "@rjsf/core": "^3.2.1", + "@rjsf/core-v5": "npm:@rjsf/core@^5.0.0-beta.10", "@rjsf/material-ui": "^3.2.1", + "@rjsf/material-ui-v5": "npm:@rjsf/material-ui@^5.0.0-beta.10", + "@rjsf/utils": "^5.0.0-beta.10", + "@rjsf/validator-ajv8": "^5.0.0-beta.10", "@types/json-schema": "^7.0.9", "@uiw/react-codemirror": "^4.9.3", "classnames": "^2.2.6", diff --git a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx index df46a119f2..9d1509e7a6 100644 --- a/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx +++ b/plugins/scaffolder/src/components/TemplateCard/TemplateCard.tsx @@ -14,12 +14,24 @@ * limitations under the License. */ import { + DEFAULT_NAMESPACE, Entity, + EntityLink, parseEntityRef, RELATION_OWNED_BY, stringifyEntityRef, } from '@backstage/catalog-model'; -import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; +import { + Button, + ItemCardHeader, + MarkdownContent, +} from '@backstage/core-components'; +import { + IconComponent, + useApi, + useApp, + useRouteRef, +} from '@backstage/core-plugin-api'; import { ScmIntegrationIcon, scmIntegrationsApiRef, @@ -30,6 +42,7 @@ import { getEntityRelations, getEntitySourceLocation, } from '@backstage/plugin-catalog-react'; +import { TemplateEntityV1beta3 } from '@backstage/plugin-scaffolder-common'; import { BackstageTheme } from '@backstage/theme'; import { Box, @@ -45,16 +58,10 @@ import { Typography, useTheme, } from '@material-ui/core'; +import LanguageIcon from '@material-ui/icons/Language'; import WarningIcon from '@material-ui/icons/Warning'; import React from 'react'; -import { selectedTemplateRouteRef } from '../../routes'; - -import { - Button, - ItemCardHeader, - MarkdownContent, -} from '@backstage/core-components'; -import { useApi, useRouteRef } from '@backstage/core-plugin-api'; +import { selectedTemplateRouteRef, viewTechDocRouteRef } from '../../routes'; const useStyles = makeStyles(theme => ({ cardHeader: { @@ -69,7 +76,6 @@ const useStyles = makeStyles(theme => ({ display: '-webkit-box', '-webkit-line-clamp': 10, '-webkit-box-orient': 'vertical', - paddingBottom: '0.8em', }, label: { color: theme.palette.text.secondary, @@ -80,6 +86,14 @@ const useStyles = makeStyles(theme => ({ lineHeight: 1, paddingBottom: '0.2rem', }, + linksLabel: { + padding: '0 16px', + }, + description: { + '& p': { + margin: '0px', + }, + }, leftButton: { marginRight: 'auto', }, @@ -92,6 +106,8 @@ const useStyles = makeStyles(theme => ({ }, })); +const MuiIcon = ({ icon: Icon }: { icon: IconComponent }) => ; + const useDeprecationStyles = makeStyles(theme => ({ deprecationIcon: { position: 'absolute', @@ -115,6 +131,7 @@ type TemplateProps = { title: string; type: string; name: string; + links: EntityLink[]; }; const getTemplateCardProps = ( @@ -127,6 +144,7 @@ const getTemplateCardProps = ( type: template.spec.type ?? '', description: template.metadata.description ?? '-', tags: (template.metadata?.tags as string[]) ?? [], + links: template.metadata.links ?? [], }; }; @@ -155,6 +173,7 @@ const DeprecationWarning = () => { }; export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => { + const app = useApp(); const backstageTheme = useTheme(); const templateRoute = useRouteRef(selectedTemplateRouteRef); const templateProps = getTemplateCardProps(template); @@ -170,6 +189,22 @@ export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => { const { name, namespace } = parseEntityRef(stringifyEntityRef(template)); const href = templateRoute({ templateName: name, namespace }); + // TechDocs Link + const viewTechDoc = useRouteRef(viewTechDocRouteRef); + const viewTechDocsAnnotation = + template.metadata.annotations?.['backstage.io/techdocs-ref']; + const viewTechDocsLink = + !!viewTechDocsAnnotation && + !!viewTechDoc && + viewTechDoc({ + namespace: template.metadata.namespace || DEFAULT_NAMESPACE, + kind: template.kind, + name: template.metadata.name, + }); + + const iconResolver = (key?: string): IconComponent => + key ? app.getSystemIcon(key) ?? LanguageIcon : LanguageIcon; + const scmIntegrationsApi = useApi(scmIntegrationsApiRef); const sourceLocation = getEntitySourceLocation(template, scmIntegrationsApi); @@ -184,12 +219,17 @@ export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => { classes={{ root: classes.title }} /> - + Description - + @@ -198,7 +238,11 @@ export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => { - + Tags {templateProps.tags?.map(tag => ( @@ -206,15 +250,47 @@ export const TemplateCard = ({ template, deprecated }: TemplateCardProps) => { ))} + + Links + - {sourceLocation && ( - - - - )} +
+ {sourceLocation && ( + + + + + + )} + {viewTechDocsLink && ( + + + + + + )} + {templateProps.links?.map((link, i) => ( + + + + + + ))} +
-
diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts index ed887fb56d..a4a937e6fe 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.test.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { JsonObject } from '@backstage/types'; -import { CustomFieldValidator } from '../../../extensions'; +import { NextCustomFieldValidator } from '../../../extensions'; import { createAsyncValidators } from './createAsyncValidators'; describe('createAsyncValidators', () => { @@ -79,13 +79,16 @@ describe('createAsyncValidators', () => { }, }; - const NameField: CustomFieldValidator = (value, { addError }) => { + const NameField: NextCustomFieldValidator = ( + value, + { addError }, + ) => { if (!value) { addError('something is broken here!'); } }; - const AddressField: CustomFieldValidator<{ + const AddressField: NextCustomFieldValidator<{ street?: string; postcode?: string; }> = (value, { addError }) => { @@ -101,8 +104,8 @@ describe('createAsyncValidators', () => { const validate = createAsyncValidators( schema, { - NameField: NameField as CustomFieldValidator, - AddressField: AddressField as CustomFieldValidator, + NameField: NameField as NextCustomFieldValidator, + AddressField: AddressField as NextCustomFieldValidator, }, { apiHolder: { get: jest.fn() }, diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts index d89f0d4e5f..1355387eda 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/createAsyncValidators.ts @@ -14,16 +14,16 @@ * limitations under the License. */ -import { FieldValidation } from '@rjsf/core'; +import { FieldValidation } from '@rjsf/utils'; import { JsonObject } from '@backstage/types'; import { ApiHolder } from '@backstage/core-plugin-api'; -import { CustomFieldValidator } from '../../../extensions'; +import { NextCustomFieldValidator } from '../../../extensions'; import { Draft07 as JSONSchema } from 'json-schema-library'; import { createFieldValidation } from './schema'; export const createAsyncValidators = ( rootSchema: JsonObject, - validators: Record>, + validators: Record>, context: { apiHolder: ApiHolder; }, diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts index e2d5e89932..4c647bc045 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/schema.ts @@ -14,7 +14,7 @@ * limitations under the License. */ import { JsonObject } from '@backstage/types'; -import { FieldValidation, UiSchema } from '@rjsf/core'; +import { FieldValidation, UiSchema } from '@rjsf/utils'; function isObject(value: unknown): value is JsonObject { return typeof value === 'object' && value !== null && !Array.isArray(value); @@ -119,7 +119,7 @@ export const createFieldValidation = (): FieldValidation => { const fieldValidation: FieldValidation = { __errors: [] as string[], addError: (message: string) => { - fieldValidation.__errors.push(message); + fieldValidation.__errors?.push(message); }, }; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts index d0d8590a7b..6aad6baa96 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts +++ b/plugins/scaffolder/src/next/TemplateWizardPage/Stepper/useTemplateSchema.ts @@ -15,7 +15,7 @@ */ import { featureFlagsApiRef, useApi } from '@backstage/core-plugin-api'; import { JsonObject } from '@backstage/types'; -import { UiSchema } from '@rjsf/core'; +import { UiSchema } from '@rjsf/utils'; import { TemplateParameterSchema } from '../../../types'; import { extractSchemaFromStep } from './schema'; diff --git a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx index 468db438d5..b650e4a676 100644 --- a/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx +++ b/plugins/scaffolder/src/next/TemplateWizardPage/TemplateWizardPage.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useEffect } from 'react'; +import React, { useContext, useEffect } from 'react'; import { Page, Header, @@ -22,8 +22,8 @@ import { InfoCard, MarkdownContent, } from '@backstage/core-components'; -import { FieldExtensionOptions } from '../../extensions'; -import { Navigate } from 'react-router'; +import { NextFieldExtensionOptions } from '../../extensions'; +import { Navigate, useNavigate } from 'react-router'; import { stringifyEntityRef } from '@backstage/catalog-model'; import { errorApiRef, @@ -36,10 +36,16 @@ import useAsync from 'react-use/lib/useAsync'; import { makeStyles } from '@material-ui/core'; import { Stepper } from './Stepper'; import { BackstageTheme } from '@backstage/theme'; -import { nextRouteRef, selectedTemplateRouteRef } from '../../routes'; +import { + nextRouteRef, + scaffolderTaskRouteRef, + selectedTemplateRouteRef, +} from '../../routes'; +import { SecretsContext } from '../../components/secrets/SecretsContext'; +import { JsonValue } from '@backstage/types'; export interface TemplateWizardPageProps { - customFieldExtensions: FieldExtensionOptions[]; + customFieldExtensions: NextFieldExtensionOptions[]; } const useStyles = makeStyles(() => ({ @@ -67,17 +73,32 @@ const useTemplateParameterSchema = (templateRef: string) => { export const TemplateWizardPage = (props: TemplateWizardPageProps) => { const styles = useStyles(); const rootRef = useRouteRef(nextRouteRef); + const taskRoute = useRouteRef(scaffolderTaskRouteRef); + const { secrets } = useContext(SecretsContext) ?? {}; + const scaffolderApi = useApi(scaffolderApiRef); + const navigate = useNavigate(); const { templateName, namespace } = useRouteRefParams( selectedTemplateRouteRef, ); + + const templateRef = stringifyEntityRef({ + kind: 'Template', + namespace, + name: templateName, + }); + const errorApi = useApi(errorApiRef); - const { loading, manifest, error } = useTemplateParameterSchema( - stringifyEntityRef({ - kind: 'Template', - namespace, - name: templateName, - }), - ); + const { loading, manifest, error } = useTemplateParameterSchema(templateRef); + + const onComplete = async (values: Record) => { + const { taskId } = await scaffolderApi.scaffold({ + templateRef, + values, + secrets, + }); + + navigate(taskRoute({ taskId })); + }; useEffect(() => { if (error) { @@ -113,6 +134,7 @@ export const TemplateWizardPage = (props: TemplateWizardPageProps) => { )} diff --git a/plugins/scaffolder/src/plugin.ts b/plugins/scaffolder/src/plugin.ts index 7387232948..f9b359d93a 100644 --- a/plugins/scaffolder/src/plugin.ts +++ b/plugins/scaffolder/src/plugin.ts @@ -27,6 +27,7 @@ import { nextRouteRef, registerComponentRouteRef, rootRouteRef, + viewTechDocRouteRef, } from './routes'; import { createApiFactory, @@ -68,6 +69,7 @@ export const scaffolderPlugin = createPlugin({ }, externalRoutes: { registerComponent: registerComponentRouteRef, + viewTechDoc: viewTechDocRouteRef, }, }); diff --git a/plugins/scaffolder/src/routes.ts b/plugins/scaffolder/src/routes.ts index f862b043c1..5e2d191443 100644 --- a/plugins/scaffolder/src/routes.ts +++ b/plugins/scaffolder/src/routes.ts @@ -24,6 +24,12 @@ export const registerComponentRouteRef = createExternalRouteRef({ optional: true, }); +export const viewTechDocRouteRef = createExternalRouteRef({ + id: 'view-techdoc', + optional: true, + params: ['namespace', 'kind', 'name'], +}); + export const rootRouteRef = createRouteRef({ id: 'scaffolder', }); @@ -59,6 +65,12 @@ export const scaffolderTaskRouteRef = createSubRouteRef({ path: '/tasks/:taskId', }); +export const nextScaffolderTaskRouteRef = createSubRouteRef({ + id: 'scaffolder/next/task', + parent: nextRouteRef, + path: '/tasks/:taskId', +}); + export const scaffolderListTaskRouteRef = createSubRouteRef({ id: 'scaffolder/list-tasks', parent: rootRouteRef, diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index 0ee3827314..abccf644e2 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,44 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 1.0.3 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/config@1.0.3 + +## 1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + +## 1.0.3-next.1 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-search-backend-node@1.0.3-next.1 + - @backstage/config@1.0.3-next.1 + +## 1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/config@1.0.3-next.0 + - @backstage/plugin-search-backend-node@1.0.3-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 1.0.2 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index b161905d75..c03084e2ab 100644 --- a/plugins/search-backend-module-elasticsearch/package.json +++ b/plugins/search-backend-module-elasticsearch/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-module-elasticsearch", "description": "A module for the search backend that implements search using ElasticSearch", - "version": "1.0.2", + "version": "1.0.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts index f3107df1fd..a33d8939eb 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts @@ -210,7 +210,7 @@ export class ElasticSearchSearchEngine implements SearchEngine { .multiMatchQuery(['*'], term) .fuzziness('auto') .minimumShouldMatch(1); - const pageSize = 25; + const pageSize = query.pageLimit || 25; const { page } = decodePageCursor(pageCursor); let esbRequestBodySearch = esb diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts index c81950ea41..e32c49559d 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngineIndexer.ts @@ -63,7 +63,7 @@ export class ElasticSearchSearchEngineIndexer extends BatchSearchEngineIndexer { constructor(options: ElasticSearchSearchEngineIndexerOptions) { super({ batchSize: options.batchSize }); - this.logger = options.logger; + this.logger = options.logger.child({ documentType: options.type }); this.startTimestamp = process.hrtime(); this.type = options.type; this.indexPrefix = options.indexPrefix; diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index 59b0989c58..0a50ee4e4a 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,47 @@ # @backstage/plugin-search-backend-module-pg +## 0.4.1 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/config@1.0.3 + +## 0.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + +## 0.4.1-next.1 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-search-backend-node@1.0.3-next.1 + - @backstage/config@1.0.3-next.1 + +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/plugin-search-backend-node@1.0.3-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 0.4.0 ### Minor Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index 8af9d8a4c8..355d118005 100644 --- a/plugins/search-backend-module-pg/package.json +++ b/plugins/search-backend-module-pg/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-module-pg", "description": "A module for the search backend that implements search using PostgreSQL", - "version": "0.4.0", + "version": "0.4.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts index 01fe0fff82..f53c433c2f 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts @@ -137,7 +137,7 @@ export class PgSearchEngine implements SearchEngine { query: SearchQuery, options: PgSearchQueryTranslatorOptions, ): ConcretePgSearchQuery { - const pageSize = 25; + const pageSize = query.pageLimit || 25; const { page } = decodePageCursor(query.pageCursor); const offset = page * pageSize; // We request more result to know whether there is another page diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index d35568c247..694e70c1f9 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,56 @@ # @backstage/plugin-search-backend-node +## 1.0.3 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 1.0.3-next.1 + +### Patch Changes + +- a799972bb1: The search engine has been updated to take advantage of the `pageLimit` property on search queries. If none is provided, the search engine will continue to use its default value of 25 results per page. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## 1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 1.0.2 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index 43f8597fd5..121cc66423 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend-node", "description": "A library for Backstage backend plugins that want to interact with the search backend plugin", - "version": "1.0.2", + "version": "1.0.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend-node/src/IndexBuilder.ts b/plugins/search-backend-node/src/IndexBuilder.ts index 0ba9ba0059..907519f289 100644 --- a/plugins/search-backend-node/src/IndexBuilder.ts +++ b/plugins/search-backend-node/src/IndexBuilder.ts @@ -112,13 +112,14 @@ export class IndexBuilder { }); Object.keys(this.collators).forEach(type => { + const taskLogger = this.logger.child({ documentType: type }); scheduler.addToSchedule({ id: `search_index_${type.replace('-', '_').toLocaleLowerCase('en-US')}`, scheduledRunner: this.collators[type].schedule, task: async () => { // Instantiate the collator. const collator = await this.collators[type].factory.getCollator(); - this.logger.info( + taskLogger.info( `Collating documents for ${type} via ${this.collators[type].factory.constructor.name}`, ); @@ -128,7 +129,7 @@ export class IndexBuilder { .concat(this.decorators[type] || []) .map(async factory => { const decorator = await factory.getDecorator(); - this.logger.info( + taskLogger.info( `Attached decorator via ${factory.constructor.name} to ${type} index pipeline.`, ); return decorator; @@ -144,13 +145,13 @@ export class IndexBuilder { [collator, ...decorators, indexer], (error: NodeJS.ErrnoException | null) => { if (error) { - this.logger.error( + taskLogger.error( `Collating documents for ${type} failed: ${error}`, ); reject(error); } else { // Signal index pipeline completion! - this.logger.info(`Collating documents for ${type} succeeded`); + taskLogger.info(`Collating documents for ${type} succeeded`); resolve(); } }, diff --git a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts index fde6b9412a..9d922aace6 100644 --- a/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts +++ b/plugins/search-backend-node/src/collators/NewlineDelimitedJsonCollatorFactory.ts @@ -93,7 +93,7 @@ export class NewlineDelimitedJsonCollatorFactory options.type, options.searchPattern, options.reader, - options.logger, + options.logger.child({ documentType: options.type }), options.visibilityPermission, ); } diff --git a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts index d65aa58b9d..d60f9951f4 100644 --- a/plugins/search-backend-node/src/engines/LunrSearchEngine.ts +++ b/plugins/search-backend-node/src/engines/LunrSearchEngine.ts @@ -71,8 +71,9 @@ export class LunrSearchEngine implements SearchEngine { term, filters, types, + pageLimit, }: SearchQuery): ConcreteLunrQuery => { - const pageSize = 25; + const pageSize = pageLimit || 25; return { lunrQueryBuilder: q => { diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index f29b600616..70e7e01a54 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,95 @@ # @backstage/plugin-search-backend +## 1.1.0 + +### Minor Changes + +- 16c853a6ed: Be less restrictive with unknown keys on query endpoint +- a799972bb1: The query received by search engines now contains a property called `pageLimit`, it specifies how many results to return per page when sending a query request to the search backend. + + Example: + _Returns up to 30 results per page_ + + ``` + GET /query?pageLimit=30 + ``` + + The search backend validates the page limit and this value must not exceed 100, but it doesn't set a default value for the page limit parameter, it leaves it up to each search engine to set this, so Lunr, Postgres and Elastic Search set 25 results per page as a default value. + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/plugin-permission-node@0.7.0 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-search-backend-node@1.0.3 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 1.1.0-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-permission-node@0.7.0-next.2 + - @backstage/plugin-search-backend-node@1.0.3-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## 1.1.0-next.1 + +### Minor Changes + +- 16c853a6ed: Be less restrictive with unknown keys on query endpoint +- a799972bb1: The query received by search engines now contains a property called `pageLimit`, it specifies how many results to return per page when sending a query request to the search backend. + + Example: + _Returns up to 30 results per page_ + + ``` + GET /query?pageLimit=30 + ``` + + The search backend validates the page limit and this value must not exceed 100, but it doesn't set a default value for the page limit parameter, it leaves it up to each search engine to set this, so Lunr, Postgres and Elastic Search set 25 results per page as a default value. + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-search-backend-node@1.0.3-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-auth-node@0.2.6-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-permission-node@0.6.6-next.1 + +## 1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/plugin-permission-node@0.6.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-search-backend-node@1.0.3-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 1.0.2 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index d01a8b2ff0..7977346d82 100644 --- a/plugins/search-backend/package.json +++ b/plugins/search-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-backend", "description": "The Backstage backend plugin that provides your backstage app with search", - "version": "1.0.2", + "version": "1.1.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-backend/src/service/router.test.ts b/plugins/search-backend/src/service/router.test.ts index 1437e65b84..c3eb6ea00f 100644 --- a/plugins/search-backend/src/service/router.test.ts +++ b/plugins/search-backend/src/service/router.test.ts @@ -112,6 +112,37 @@ describe('createRouter', () => { }); }); + it('should accept per page value under or equal to 100', async () => { + const response = await request(app).get(`/query?pageLimit=30`); + + expect(response.status).toEqual(200); + expect(response.body).toMatchObject({ + results: [], + }); + }); + + it('should reject per page value over 100', async () => { + const response = await request(app).get(`/query?pageLimit=200`); + + expect(response.status).toEqual(400); + expect(response.body).toMatchObject({ + error: { + message: /The page limit "200" is greater than "100"/i, + }, + }); + }); + + it('should reject a non number per page value', async () => { + const response = await request(app).get(`/query?pageLimit=twohundred`); + + expect(response.status).toEqual(400); + expect(response.body).toMatchObject({ + error: { + message: /The page limit "twohundred" is not a number"/i, + }, + }); + }); + it('removes backend-only properties from search documents', async () => { mockSearchEngine.query.mockResolvedValue({ results: [ @@ -148,6 +179,25 @@ describe('createRouter', () => { }); }); + it('is less restrictive with unknown keys on query endpoint', async () => { + const queryString = + 'term=test&%5BdocType%5D%5B0%5D=Service&filters%5BdocType%5D%5B0%5D=filter1&unknownKey1%5B2%5D=unknownValue1&unknownKey1%5B3%5D=unknownValue2&unknownKey2=unknownValue1&pageCursor'; + const response = await request(app).get(`/query?${queryString}`); + const firstArg: Object = { + docType: ['Service'], + filters: { docType: ['filter1'] }, + pageCursor: '', + term: 'test', + unknownKey1: ['unknownValue1', 'unknownValue2'], + unknownKey2: 'unknownValue1', + }; + const secondArg = { + token: undefined, + }; + expect(response.status).toEqual(200); + expect(mockSearchEngine.query).toHaveBeenCalledWith(firstArg, secondArg); + }); + describe('search result filtering', () => { beforeAll(async () => { const logger = getVoidLogger(); diff --git a/plugins/search-backend/src/service/router.ts b/plugins/search-backend/src/service/router.ts index cf9abff675..0100c35e1d 100644 --- a/plugins/search-backend/src/service/router.ts +++ b/plugins/search-backend/src/service/router.ts @@ -62,6 +62,7 @@ export type RouterOptions = { logger: Logger; }; +const maxPageLimit = 100; const allowedLocationProtocols = ['http:', 'https:']; /** @@ -79,6 +80,22 @@ export async function createRouter( .array(z.string().refine(type => Object.keys(types).includes(type))) .optional(), pageCursor: z.string().optional(), + pageLimit: z + .string() + .transform(pageLimit => parseInt(pageLimit, 10)) + .refine( + pageLimit => !isNaN(pageLimit), + pageLimit => ({ + message: `The page limit "${pageLimit}" is not a number`, + }), + ) + .refine( + pageLimit => pageLimit <= maxPageLimit, + pageLimit => ({ + message: `The page limit "${pageLimit}" is greater than "${maxPageLimit}"`, + }), + ) + .optional(), }); let permissionEvaluator: PermissionEvaluator; @@ -133,7 +150,7 @@ export async function createRouter( req: express.Request, res: express.Response, ) => { - const parseResult = requestSchema.safeParse(req.query); + const parseResult = requestSchema.passthrough().safeParse(req.query); if (!parseResult.success) { throw new InputError(`Invalid query string: ${parseResult.error}`); @@ -156,7 +173,7 @@ export async function createRouter( try { const resultSet = await engine?.query(query, { token }); - res.send(filterResultSet(toSearchResults(resultSet))); + res.json(filterResultSet(toSearchResults(resultSet))); } catch (error) { if (error.name === 'MissingIndexError') { // re-throw and let the default error handler middleware captures it and serializes it with the right response code on the standard form diff --git a/plugins/search-common/CHANGELOG.md b/plugins/search-common/CHANGELOG.md index b17e92a074..732e1e7aeb 100644 --- a/plugins/search-common/CHANGELOG.md +++ b/plugins/search-common/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-search-common +## 1.1.0 + +### Minor Changes + +- a799972bb1: There is a new property called `pageLimit` on the `SearchQuery` interface that specifies how many results should be returned per page. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0 + - @backstage/types@1.0.0 + +## 1.1.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/types@1.0.0 + +## 1.1.0-next.1 + +### Minor Changes + +- a799972bb1: There is a new property called `pageLimit` on the `SearchQuery` interface that specifies how many results should be returned per page. + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.6.5-next.1 + +## 1.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + ## 1.0.1 ### Patch Changes diff --git a/plugins/search-common/api-report.md b/plugins/search-common/api-report.md index ea9fc2d26e..61e5d22257 100644 --- a/plugins/search-common/api-report.md +++ b/plugins/search-common/api-report.md @@ -102,6 +102,8 @@ export interface SearchQuery { // (undocumented) pageCursor?: string; // (undocumented) + pageLimit?: number; + // (undocumented) term: string; // (undocumented) types?: string[]; diff --git a/plugins/search-common/package.json b/plugins/search-common/package.json index 0907468fdd..2c68b023a5 100644 --- a/plugins/search-common/package.json +++ b/plugins/search-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search-common", "description": "Common functionalities for Search, to be shared between various search-enabled plugins", - "version": "1.0.1", + "version": "1.1.0", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/plugins/search-common/src/types.ts b/plugins/search-common/src/types.ts index 4b4c25d423..54abc9e1b1 100644 --- a/plugins/search-common/src/types.ts +++ b/plugins/search-common/src/types.ts @@ -23,8 +23,9 @@ import { Readable, Transform, Writable } from 'stream'; */ export interface SearchQuery { term: string; - filters?: JsonObject; types?: string[]; + filters?: JsonObject; + pageLimit?: number; pageCursor?: string; } diff --git a/plugins/search-react/CHANGELOG.md b/plugins/search-react/CHANGELOG.md index d3ad8d6067..8eb6f526f2 100644 --- a/plugins/search-react/CHANGELOG.md +++ b/plugins/search-react/CHANGELOG.md @@ -1,5 +1,297 @@ # @backstage/plugin-search-react +## 1.2.0 + +### Minor Changes + +- 4ed1fa2480: The search query state now has an optional `pageLimit` property that determines how many results will be requested per page, it defaults to 25. + + Examples: + _Basic_ + + ```jsx + + {results => { + // Item rendering logic is omitted + }} + + ``` + + _With context_ + + ```jsx + + + {results => { + // Item rendering logic is omitted + }} + + + ``` + +- bed5a1dc6e: The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. + Possibility to provide a custom no results component if needed through the `noResultsComponent` property. + + Examples: + + _Rendering a custom no results component_ + + ```jsx + } + /> + ``` + + _Disable rendering when there are no results_ + + ```jsx + + ``` + +- 3de4bd4f19: A `` component was created for limiting the number of results shown per search page. Use this new component to give users options to select how many search results they want to display per page. The default options are 10, 25, 50, 100. + + See examples below: + + _Basic_ + + ```jsx + import React, { useState } from 'react'; + import { Grid } from '@material-ui/core'; + import { Page, Header, Content, Lifecycle } from '@backstage/core-components'; + import { + SearchBarBase, + SearchPaginationBase, + SearchResultList, + } from '@backstage/plugin-search-react'; + + const SearchPage = () => { + const [term, setTerm] = useState(''); + const [pageLimit, setPageLimit] = useState(25); + const [pageCursor, setPageCursor] = useState(); + + return ( + +
} /> + + + + + + + + + + + + + + + ); + }; + ``` + + _With context_ + + ```jsx + import React from 'react'; + import { Grid } from '@material-ui/core'; + import { Page, Header, Content, Lifecycle } from '@backstage/core-components'; + import { + SearchBar, + SearchResult, + SearchPagination, + SearchResultListLayout, + SearchContextProvider, + DefaultResultListItem, + } from '@backstage/plugin-search-react'; + + const SearchPage = () => ( + + +
} /> + + + + + + + + + + + {({ results }) => ( + ( + + )} + /> + )} + + + + + + + ); + ``` + +- 6faaa05626: The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. + Possibility to provide a custom no results component if needed through the `noResultsComponent` property. + + Examples: + + _Rendering a custom no results component_ + + ```jsx + } + title="Documentation" + noResultsComponent={} + /> + ``` + + _Disable rendering when there are no results_ + + ```jsx + } + title="Documentation" + disableRenderingWithNoResults + /> + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.2.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.2.0-next.1 + +### Minor Changes + +- 4ed1fa2480: The search query state now has an optional `pageLimit` property that determines how many results will be requested per page, it defaults to 25. + + Examples: + _Basic_ + + ```jsx + + {results => { + // Item rendering logic is omitted + }} + + ``` + + _With context_ + + ```jsx + + + {results => { + // Item rendering logic is omitted + }} + + + ``` + +- bed5a1dc6e: The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. + Possibility to provide a custom no results component if needed through the `noResultsComponent` property. + + Examples: + + _Rendering a custom no results component_ + + ```jsx + } + /> + ``` + + _Disable rendering when there are no results_ + + ```jsx + + ``` + +- 6faaa05626: The `` component now accepts an optional property `disableRenderingWithNoResults` to disable rendering when no results are returned. + Possibility to provide a custom no results component if needed through the `noResultsComponent` property. + + Examples: + + _Rendering a custom no results component_ + + ```jsx + } + title="Documentation" + noResultsComponent={} + /> + ``` + + _Disable rendering when there are no results_ + + ```jsx + } + title="Documentation" + disableRenderingWithNoResults + /> + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 1.1.0 ### Minor Changes diff --git a/plugins/search-react/api-report.md b/plugins/search-react/api-report.md index 5ec4124fcc..ef3e8b19bd 100644 --- a/plugins/search-react/api-report.md +++ b/plugins/search-react/api-report.md @@ -165,6 +165,7 @@ export type SearchContextState = { term: string; types: string[]; filters: JsonObject; + pageLimit?: number; pageCursor?: string; }; @@ -174,6 +175,7 @@ export type SearchContextValue = { setTerm: React_2.Dispatch>; setTypes: React_2.Dispatch>; setFilters: React_2.Dispatch>; + setPageLimit: React_2.Dispatch>; setPageCursor: React_2.Dispatch>; fetchNextPage?: React_2.DispatchWithoutAction; fetchPreviousPage?: React_2.DispatchWithoutAction; @@ -209,11 +211,59 @@ export type SearchFilterWrapperProps = SearchFilterComponentProps & { debug?: boolean; }; +// @public +export const SearchPagination: (props: SearchPaginationProps) => JSX.Element; + +// @public +export const SearchPaginationBase: ( + props: SearchPaginationBaseProps, +) => JSX.Element; + +// @public +export type SearchPaginationBaseProps = { + className?: string; + total?: number; + cursor?: string; + onCursorChange?: (pageCursor: string) => void; + limit?: number; + limitLabel?: ReactNode; + limitText?: SearchPaginationLimitText; + limitOptions?: SearchPaginationLimitOption[]; + onLimitChange?: (value: number) => void; +}; + +// @public +export type SearchPaginationLimitOption< + Current extends number = 101, + Accumulator extends number[] = [], +> = Accumulator['length'] extends Current + ? Accumulator[number] + : SearchPaginationLimitOption< + Current, + [...Accumulator, Accumulator['length']] + >; + +// @public +export type SearchPaginationLimitText = (params: { + from: number; + to: number; + page: number; + count: number; +}) => ReactNode; + +// @public +export type SearchPaginationProps = Omit< + SearchPaginationBaseProps, + 'pageLimit' | 'onPageLimitChange' | 'pageCursor' | 'onPageCursorChange' +>; + // @public export const SearchResult: (props: SearchResultProps) => JSX.Element; // @public -export const SearchResultApi: (props: SearchResultApiProps) => JSX.Element; +export const SearchResultApi: ( + props: SearchResultApiProps, +) => JSX.Element | null; // @public export type SearchResultApiProps = SearchResultContextProps & { @@ -226,11 +276,11 @@ export const SearchResultComponent: (props: SearchResultProps) => JSX.Element; // @public export const SearchResultContext: ( props: SearchResultContextProps, -) => JSX.Element; +) => JSX.Element | null; // @public export type SearchResultContextProps = { - children: (state: AsyncState) => JSX.Element; + children: (state: AsyncState) => JSX.Element | null; }; // @public @@ -269,13 +319,22 @@ export type SearchResultGroupLayoutProps = ListProps & { link?: ReactNode; linkProps?: Partial; filterOptions?: FilterOption[]; - renderFilterOption?: (filterOption: FilterOption) => JSX.Element; + renderFilterOption?: ( + value: FilterOption, + index: number, + array: FilterOption[], + ) => JSX.Element | null; filterFields?: string[]; renderFilterField?: (key: string) => JSX.Element | null; resultItems?: SearchResult_2[]; - renderResultItem?: (resultItem: SearchResult_2) => JSX.Element; + renderResultItem?: ( + value: SearchResult_2, + index: number, + array: SearchResult_2[], + ) => JSX.Element | null; error?: Error; loading?: boolean; + noResultsComponent?: ReactNode; }; // @public @@ -284,6 +343,7 @@ export type SearchResultGroupProps = Omit< 'loading' | 'error' | 'resultItems' | 'filterFields' > & { query: Partial; + disableRenderingWithNoResults?: boolean; }; // @public @@ -317,9 +377,14 @@ export const SearchResultListLayout: ( // @public export type SearchResultListLayoutProps = ListProps & { resultItems?: SearchResult_2[]; - renderResultItem?: (resultItem: SearchResult_2) => JSX.Element; + renderResultItem?: ( + value: SearchResult_2, + index: number, + array: SearchResult_2[], + ) => JSX.Element | null; error?: Error; loading?: boolean; + noResultsComponent?: ReactNode; }; // @public @@ -328,6 +393,7 @@ export type SearchResultListProps = Omit< 'loading' | 'error' | 'resultItems' > & { query: Partial; + disableRenderingWithNoResults?: boolean; }; // @public (undocumented) diff --git a/plugins/search-react/package.json b/plugins/search-react/package.json index d3b8105076..c4d93341e9 100644 --- a/plugins/search-react/package.json +++ b/plugins/search-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-react", - "version": "1.1.0", + "version": "1.2.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx new file mode 100644 index 0000000000..4e72c0fa25 --- /dev/null +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.stories.tsx @@ -0,0 +1,63 @@ +/* + * 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, { ComponentType } from 'react'; +import { Grid } from '@material-ui/core'; + +import { TestApiProvider } from '@backstage/test-utils'; + +import { searchApiRef, MockSearchApi } from '../../api'; +import { SearchContextProvider } from '../../context'; + +import { SearchPagination } from './SearchPagination'; + +export default { + title: 'Plugins/Search/SearchPagination', + component: SearchPagination, + decorators: [ + (Story: ComponentType<{}>) => ( + + + + + + + + + + ), + ], +}; + +export const Default = () => { + return ; +}; + +export const CustomPageLimitLabel = () => { + return ; +}; + +export const CustomPageLimitText = () => { + return ( + `${from}-${to} of more than ${to}`} + /> + ); +}; + +export const CustomPageLimitOptions = () => { + return ; +}; diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx new file mode 100644 index 0000000000..d2ecc55d31 --- /dev/null +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.test.tsx @@ -0,0 +1,188 @@ +/* + * 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 { screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { renderWithEffects, TestApiProvider } from '@backstage/test-utils'; + +import { searchApiRef } from '../../api'; +import { SearchContextProvider } from '../../context'; + +import { SearchPagination } from './SearchPagination'; + +const query = jest.fn().mockResolvedValue({ + results: [], + nextPageCursor: 'Mg==', + previousPageCursor: 'MA==', +}); + +describe('SearchPagination', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('Renders without exploding', async () => { + await renderWithEffects( + + + + + , + ); + + expect(screen.getByText('Results per page:')).toBeInTheDocument(); + expect(screen.getByText('25')).toBeInTheDocument(); + expect(screen.getByText('1-25')).toBeInTheDocument(); + expect(screen.getByLabelText('Next page')).toBeEnabled(); + expect(screen.getByLabelText('Previous page')).toBeDisabled(); + }); + + it('Define default page limit options', async () => { + await renderWithEffects( + + + + + , + ); + + await userEvent.click(screen.getByText('25')); + + const options = screen.getAllByRole('option'); + expect(options).toHaveLength(4); + expect(options[0]).toHaveTextContent('10'); + expect(options[1]).toHaveTextContent('25'); + expect(options[2]).toHaveTextContent('50'); + expect(options[3]).toHaveTextContent('100'); + }); + + it('Accept custom page limit label', async () => { + const label = 'Page limit:'; + await renderWithEffects( + + + + + , + ); + + expect(screen.getByText(label)).toBeInTheDocument(); + }); + + it('Show the total in text', async () => { + await renderWithEffects( + + + + + , + ); + + expect(screen.getByText('of 100')).toBeInTheDocument(); + }); + + it('Accept custom page limit text', async () => { + await renderWithEffects( + + + `${from}-${to} of more than ${to}`} + /> + + , + ); + + expect(screen.getByText('1-25 of more than 25')).toBeInTheDocument(); + }); + + it('Accept custom page limit options', async () => { + await renderWithEffects( + + + + + , + ); + + await userEvent.click(screen.getByText('25')); + + const options = screen.getAllByRole('option'); + expect(options).toHaveLength(4); + expect(options[0]).toHaveTextContent('5'); + expect(options[1]).toHaveTextContent('10'); + expect(options[2]).toHaveTextContent('20'); + expect(options[3]).toHaveTextContent('25'); + }); + + it('Set page limit in the context', async () => { + await renderWithEffects( + + + + + , + ); + + await userEvent.click(screen.getByText('25')); + + await userEvent.click(screen.getByText('10')); + + expect(query).toHaveBeenCalledWith( + expect.objectContaining({ + pageLimit: 10, + }), + ); + }); + + it('Set page cursor in the context', async () => { + const initialState = { + term: '', + types: [], + filters: {}, + pageCursor: 'MQ==', // page: 1 + }; + + await renderWithEffects( + + + + + , + ); + + await userEvent.click(screen.getByLabelText('Next page')); + + expect(screen.getByText('51-75')).toBeInTheDocument(); + + expect(query).toHaveBeenLastCalledWith( + expect.objectContaining({ + pageCursor: 'Mg==', // page: 2 + }), + ); + + await userEvent.click(screen.getByLabelText('Previous page')); + + expect(screen.getByText('26-50')).toBeInTheDocument(); + + expect(query).toHaveBeenLastCalledWith( + expect.objectContaining({ + pageCursor: 'MQ==', // page: 1 + }), + ); + }); +}); diff --git a/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx b/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx new file mode 100644 index 0000000000..34b6deaff5 --- /dev/null +++ b/plugins/search-react/src/components/SearchPagination/SearchPagination.tsx @@ -0,0 +1,190 @@ +/* + * 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, { + ReactNode, + ChangeEvent, + MouseEvent, + useCallback, + useMemo, +} from 'react'; +import { TablePagination } from '@material-ui/core'; +import { useSearch } from '../../context'; + +const encodePageCursor = (pageCursor: number): string => { + return Buffer.from(pageCursor.toString(), 'utf-8').toString('base64'); +}; + +const decodePageCursor = (pageCursor?: string): number => { + if (!pageCursor) return 0; + return Number(Buffer.from(pageCursor, 'base64').toString('utf-8')); +}; + +/** + * A page limit option, this value must not be greater than 100. + * @public + */ +export type SearchPaginationLimitOption< + Current extends number = 101, + Accumulator extends number[] = [], +> = Accumulator['length'] extends Current + ? Accumulator[number] + : SearchPaginationLimitOption< + Current, + [...Accumulator, Accumulator['length']] + >; + +/** + * A page limit text, this function is called with a "\{ from, to, page, count \}" object. + * @public + */ +export type SearchPaginationLimitText = (params: { + from: number; + to: number; + page: number; + count: number; +}) => ReactNode; + +/** + * Props for {@link SearchPaginationBase}. + * @public + */ +export type SearchPaginationBaseProps = { + /** + * The component class name. + */ + className?: string; + /** + * The total number of results. + * For an unknown number of items, provide -1. + * Defaults to -1. + */ + total?: number; + /** + * The cursor for the current page. + */ + cursor?: string; + /** + * Callback fired when the current page cursor is changed. + */ + onCursorChange?: (pageCursor: string) => void; + /** + * The limit of results per page. + * Set -1 to display all the results. + */ + limit?: number; + /** + * Customize the results per page label. + * Defaults to "Results per page:". + */ + limitLabel?: ReactNode; + /** + * Customize the results per page text. + * Defaults to "(\{ from, to, count \}) =\> count \> 0 ? `of $\{count\}` : `$\{from\}-$\{to\}`". + */ + limitText?: SearchPaginationLimitText; + /** + * Options for setting how many results show per page. + * If less than two options are available, no select field will be displayed. + * Use -1 for the value with a custom label to show all the results. + * Defaults to [10, 25, 50, 100]. + */ + limitOptions?: SearchPaginationLimitOption[]; + /** + * Callback fired when the number of results per page is changed. + */ + onLimitChange?: (value: number) => void; +}; + +/** + * A component with controls for search results pagination. + * @param props - See {@link SearchPaginationBaseProps}. + * @public + */ +export const SearchPaginationBase = (props: SearchPaginationBaseProps) => { + const { + total: count = -1, + cursor: pageCursor, + onCursorChange: onPageCursorChange, + limit: rowsPerPage = 25, + limitLabel: labelRowsPerPage = 'Results per page:', + limitText: labelDisplayedRows = ({ from, to }) => + count > 0 ? `of ${count}` : `${from}-${to}`, + limitOptions: rowsPerPageOptions, + onLimitChange: onPageLimitChange, + ...rest + } = props; + + const page = useMemo(() => decodePageCursor(pageCursor), [pageCursor]); + + const handlePageChange = useCallback( + (_: MouseEvent | null, newValue: number) => { + onPageCursorChange?.(encodePageCursor(newValue)); + }, + [onPageCursorChange], + ); + + const handleRowsPerPageChange = useCallback( + (e: ChangeEvent) => { + const newValue = e.target.value; + onPageLimitChange?.(parseInt(newValue, 10)); + }, + [onPageLimitChange], + ); + + return ( + + ); +}; + +/** + * Props for {@link SearchPagination}. + * @public + */ +export type SearchPaginationProps = Omit< + SearchPaginationBaseProps, + 'pageLimit' | 'onPageLimitChange' | 'pageCursor' | 'onPageCursorChange' +>; + +/** + * A component for setting the search context page limit and cursor. + * @param props - See {@link SearchPaginationProps}. + * @public + */ +export const SearchPagination = (props: SearchPaginationProps) => { + const { pageLimit, setPageLimit, pageCursor, setPageCursor } = useSearch(); + + return ( + + ); +}; diff --git a/plugins/search-react/src/components/SearchPagination/index.ts b/plugins/search-react/src/components/SearchPagination/index.ts new file mode 100644 index 0000000000..74053f0e11 --- /dev/null +++ b/plugins/search-react/src/components/SearchPagination/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 * from './SearchPagination'; diff --git a/plugins/search-react/src/components/SearchResult/SearchResult.tsx b/plugins/search-react/src/components/SearchResult/SearchResult.tsx index a3e4c0e1f0..dd11f42903 100644 --- a/plugins/search-react/src/components/SearchResult/SearchResult.tsx +++ b/plugins/search-react/src/components/SearchResult/SearchResult.tsx @@ -36,7 +36,7 @@ export type SearchResultContextProps = { /** * A child function that receives an asynchronous result set and returns a react element. */ - children: (state: AsyncState) => JSX.Element; + children: (state: AsyncState) => JSX.Element | null; }; /** @@ -98,16 +98,10 @@ export const SearchResultApi = (props: SearchResultApiProps) => { const { query, children } = props; const searchApi = useApi(searchApiRef); - const state = useAsync( - () => - searchApi.query({ - term: query.term ?? '', - types: query.types ?? [], - filters: query.filters ?? {}, - pageCursor: query.pageCursor, - }), - [query], - ); + const state = useAsync(() => { + const { term = '', types = [], filters = {}, ...rest } = query; + return searchApi.query({ ...rest, term, types, filters }); + }, [query]); return children(state); }; diff --git a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx index 3a20dec36d..fab807dd84 100644 --- a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx +++ b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.stories.tsx @@ -269,7 +269,7 @@ export const WithFilters = () => { ); }; -export const WithNoResults = () => { +export const WithDefaultNoResultsComponent = () => { const [query] = useState>({ types: ['techdocs'], }); @@ -285,6 +285,23 @@ export const WithNoResults = () => { ); }; +export const WithCustomNoResultsComponent = () => { + const [query] = useState>({ + types: ['techdocs'], + }); + + return ( + + } + title="Documentation" + noResultsComponent={} + /> + + ); +}; + const CustomResultListItem = (props: any) => { const { icon, result } = props; diff --git a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx index c1b45cc433..7943dba3f7 100644 --- a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx +++ b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.test.tsx @@ -291,6 +291,46 @@ describe('SearchResultGroup', () => { }); }); + it('Does not render result group if no results returned and disableRenderingWithNoResults prop is provided', async () => { + query.mockResolvedValueOnce({ results: [] }); + await renderWithEffects( + wrapInTestApp( + + } + title="Documentation" + disableRenderingWithNoResults + /> + , + ), + ); + + await waitFor(() => { + expect(screen.queryByText('Documentation')).not.toBeInTheDocument(); + }); + }); + + it('Should render custom component when no results returned', async () => { + query.mockResolvedValueOnce({ results: [] }); + await renderWithEffects( + wrapInTestApp( + + } + title="Documentation" + noResultsComponent="No results were found" + /> + , + ), + ); + + await waitFor(() => { + expect(screen.getByText('No results were found')).toBeInTheDocument(); + }); + }); + it('Shows an error panel when results rendering fails', async () => { query.mockRejectedValueOnce(new Error()); await renderWithEffects( diff --git a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx index 9c55996ca7..6040dbe560 100644 --- a/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx +++ b/plugins/search-react/src/components/SearchResultGroup/SearchResultGroup.tsx @@ -306,7 +306,11 @@ export type SearchResultGroupLayoutProps = ListProps & { * Function to customize how filter options are rendered. * @remarks Defaults to a menu item where its value and label bounds to the option string. */ - renderFilterOption?: (filterOption: FilterOption) => JSX.Element; + renderFilterOption?: ( + value: FilterOption, + index: number, + array: FilterOption[], + ) => JSX.Element | null; /** * A list of search filter keys, also known as filter field names. */ @@ -322,7 +326,11 @@ export type SearchResultGroupLayoutProps = ListProps & { /** * Function to customize how result items are rendered. */ - renderResultItem?: (resultItem: SearchResult) => JSX.Element; + renderResultItem?: ( + value: SearchResult, + index: number, + array: SearchResult[], + ) => JSX.Element | null; /** * If defined, will render a default error panel. */ @@ -331,6 +339,10 @@ export type SearchResultGroupLayoutProps = ListProps & { * If defined, will render a default loading progress. */ loading?: boolean; + /** + * Optional component to render when no results. Default to component. + */ + noResultsComponent?: ReactNode; }; /** @@ -350,14 +362,31 @@ export function SearchResultGroupLayout( icon, title, titleProps = {}, - link, + link = ( + <> + See all + + + ), linkProps = {}, filterOptions, - renderFilterOption, + renderFilterOption = filterOption => ( + + {filterOption} + + ), filterFields, renderFilterField, resultItems, - renderResultItem, + renderResultItem = resultItem => ( + + ), + noResultsComponent = ( + + ), ...rest } = props; @@ -401,30 +430,14 @@ export function SearchResultGroupLayout( onClick={handleClose} keepMounted > - {filterOptions.map(filterOption => - renderFilterOption ? ( - renderFilterOption(filterOption) - ) : ( - - {filterOption} - - ), - )} + {filterOptions.map(renderFilterOption)} ) : null} {filterFields?.map( filterField => renderFilterField?.(filterField) ?? null, )} - {link ?? ( - <> - See all - - - )} + {link} {loading ? : null} @@ -435,12 +448,10 @@ export function SearchResultGroupLayout( /> ) : null} {!loading && !error && resultItems?.length - ? resultItems.map(resultItem => renderResultItem?.(resultItem) ?? null) + ? resultItems.map(renderResultItem) : null} {!loading && !error && !resultItems?.length ? ( - - - + {noResultsComponent} ) : null} ); @@ -458,6 +469,10 @@ export type SearchResultGroupProps = Omit< * A search query used for requesting the results to be grouped. */ query: Partial; + /** + * Optional property to provide if component should not render the group when no results are found. + */ + disableRenderingWithNoResults?: boolean; }; /** @@ -471,9 +486,7 @@ export function SearchResultGroup( const { query, linkProps = {}, - renderResultItem = ({ document }) => ( - - ), + disableRenderingWithNoResults, ...rest } = props; @@ -495,17 +508,22 @@ export function SearchResultGroup( }} > - {({ loading, error, value }) => ( - - )} + {({ loading, error, value }) => { + if (!value?.results?.length && disableRenderingWithNoResults) { + return null; + } + + return ( + + ); + }} ); diff --git a/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx b/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx index 5762aba2d8..fcda4cec87 100644 --- a/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx +++ b/plugins/search-react/src/components/SearchResultList/SearchResultList.stories.tsx @@ -119,7 +119,7 @@ export const WithError = () => { ); }; -export const WithNoResults = () => { +export const WithDefaultNoResultsComponent = () => { const [query] = useState>({ types: ['techdocs'], }); @@ -131,6 +131,21 @@ export const WithNoResults = () => { ); }; +export const WithCustomNoResultsComponent = () => { + const [query] = useState>({ + types: ['techdocs'], + }); + + return ( + + } + /> + + ); +}; + const CustomResultListItem = (props: any) => { const { icon, result } = props; diff --git a/plugins/search-react/src/components/SearchResultList/SearchResultList.test.tsx b/plugins/search-react/src/components/SearchResultList/SearchResultList.test.tsx index 20e5a1cba1..93bbaa5602 100644 --- a/plugins/search-react/src/components/SearchResultList/SearchResultList.test.tsx +++ b/plugins/search-react/src/components/SearchResultList/SearchResultList.test.tsx @@ -121,6 +121,42 @@ describe('SearchResultList', () => { }); }); + it('Does not render result group if no results returned and disableRenderingWithNoResults prop is provided', async () => { + query.mockResolvedValueOnce({ results: [] }); + await renderWithEffects( + wrapInTestApp( + + + , + ), + ); + + await waitFor(() => { + expect(screen.queryByText('Documentation')).not.toBeInTheDocument(); + }); + }); + + it('Should render custom component when no results returned', async () => { + query.mockResolvedValueOnce({ results: [] }); + await renderWithEffects( + wrapInTestApp( + + + , + ), + ); + + await waitFor(() => { + expect(screen.getByText('No results were found')).toBeInTheDocument(); + }); + }); + it('Shows an error panel when results rendering fails', async () => { query.mockRejectedValueOnce(new Error()); await renderWithEffects( diff --git a/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx b/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx index 8f184f7080..4279e8936a 100644 --- a/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx +++ b/plugins/search-react/src/components/SearchResultList/SearchResultList.tsx @@ -14,9 +14,9 @@ * limitations under the License. */ -import React from 'react'; +import React, { ReactNode } from 'react'; -import { List, ListProps } from '@material-ui/core'; +import { List, ListItem, ListProps } from '@material-ui/core'; import { EmptyState, @@ -41,7 +41,11 @@ export type SearchResultListLayoutProps = ListProps & { /** * Function to customize how result items are rendered. */ - renderResultItem?: (resultItem: SearchResult) => JSX.Element; + renderResultItem?: ( + value: SearchResult, + index: number, + array: SearchResult[], + ) => JSX.Element | null; /** * If defined, will render a default error panel. */ @@ -50,6 +54,10 @@ export type SearchResultListLayoutProps = ListProps & { * If defined, will render a default loading progress. */ loading?: boolean; + /** + * Optional component to render when no results. Default to component. + */ + noResultsComponent?: ReactNode; }; /** @@ -58,7 +66,21 @@ export type SearchResultListLayoutProps = ListProps & { * @public */ export const SearchResultListLayout = (props: SearchResultListLayoutProps) => { - const { loading, error, resultItems, renderResultItem, ...rest } = props; + const { + loading, + error, + resultItems, + renderResultItem = resultItem => ( + + ), + noResultsComponent = ( + + ), + ...rest + } = props; return ( @@ -70,10 +92,10 @@ export const SearchResultListLayout = (props: SearchResultListLayoutProps) => { /> ) : null} {!loading && !error && resultItems?.length - ? resultItems.map(resultItem => renderResultItem?.(resultItem) ?? null) + ? resultItems.map(renderResultItem) : null} {!loading && !error && !resultItems?.length ? ( - + {noResultsComponent} ) : null} ); @@ -91,6 +113,10 @@ export type SearchResultListProps = Omit< * A search query used for requesting the results to be listed. */ query: Partial; + /** + * Optional property to provide if component should not render the component when no results are found. + */ + disableRenderingWithNoResults?: boolean; }; /** @@ -99,13 +125,7 @@ export type SearchResultListProps = Omit< * @public */ export const SearchResultList = (props: SearchResultListProps) => { - const { - query, - renderResultItem = ({ document }) => ( - - ), - ...rest - } = props; + const { query, disableRenderingWithNoResults, ...rest } = props; return ( { }} > - {({ loading, error, value }) => ( - - )} + {({ loading, error, value }) => { + if (!value?.results?.length && disableRenderingWithNoResults) { + return null; + } + + return ( + + ); + }} ); diff --git a/plugins/search-react/src/components/index.ts b/plugins/search-react/src/components/index.ts index 62d8b611a4..5f68905407 100644 --- a/plugins/search-react/src/components/index.ts +++ b/plugins/search-react/src/components/index.ts @@ -20,6 +20,7 @@ export * from './SearchAutocomplete'; export * from './SearchFilter'; export * from './SearchResult'; export * from './SearchResultPager'; +export * from './SearchPagination'; export * from './SearchResultList'; export * from './SearchResultGroup'; export * from './DefaultResultListItem'; diff --git a/plugins/search-react/src/context/SearchContext.test.tsx b/plugins/search-react/src/context/SearchContext.test.tsx index b5888fa8a9..edee2f153f 100644 --- a/plugins/search-react/src/context/SearchContext.test.tsx +++ b/plugins/search-react/src/context/SearchContext.test.tsx @@ -40,8 +40,8 @@ describe('SearchContext', () => { const initialState = { term: '', - filters: {}, types: ['*'], + filters: {}, }; beforeEach(() => { @@ -167,8 +167,8 @@ describe('SearchContext', () => { initialProps: { initialState: { ...initialState, - filters: { foo: 'bar' }, term: 'first term', + filters: { foo: 'bar' }, pageCursor: 'SOMEPAGE', }, }, @@ -194,8 +194,8 @@ describe('SearchContext', () => { initialProps: { initialState: { ...initialState, - filters: { foo: 'bar' }, term: 'first term', + filters: { foo: 'bar' }, pageCursor: 'SOMEPAGE', }, }, @@ -236,58 +236,9 @@ describe('SearchContext', () => { await waitForNextUpdate(); expect(query).toHaveBeenLastCalledWith({ - filters: {}, - types: ['*'], term, - }); - }); - - it('When filters are set', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSearch(), { - wrapper, - initialProps: { - initialState, - }, - }); - - await waitForNextUpdate(); - - const filters = { filter: 'filter' }; - - act(() => { - result.current.setFilters(filters); - }); - - await waitForNextUpdate(); - - expect(query).toHaveBeenLastCalledWith({ - filters, types: ['*'], - term: '', - }); - }); - - it('When page is set', async () => { - const { result, waitForNextUpdate } = renderHook(() => useSearch(), { - wrapper, - initialProps: { - initialState, - }, - }); - - await waitForNextUpdate(); - - act(() => { - result.current.setPageCursor('SOMEPAGE'); - }); - - await waitForNextUpdate(); - - expect(query).toHaveBeenLastCalledWith({ filters: {}, - types: ['*'], - pageCursor: 'SOMEPAGE', - term: '', }); }); @@ -311,8 +262,85 @@ describe('SearchContext', () => { expect(query).toHaveBeenLastCalledWith({ types, - filters: {}, term: '', + filters: {}, + }); + }); + + it('When filters are set', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSearch(), { + wrapper, + initialProps: { + initialState, + }, + }); + + await waitForNextUpdate(); + + const filters = { filter: 'filter' }; + + act(() => { + result.current.setFilters(filters); + }); + + await waitForNextUpdate(); + + expect(query).toHaveBeenLastCalledWith({ + filters, + term: '', + types: ['*'], + }); + }); + + it('When page limit is set', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSearch(), { + wrapper, + initialProps: { + initialState, + }, + }); + + await waitForNextUpdate(); + + const pageLimit = 30; + + act(() => { + result.current.setPageLimit(pageLimit); + }); + + await waitForNextUpdate(); + + expect(query).toHaveBeenLastCalledWith({ + pageLimit, + term: '', + types: ['*'], + filters: {}, + }); + }); + + it('When page cursor is set', async () => { + const { result, waitForNextUpdate } = renderHook(() => useSearch(), { + wrapper, + initialProps: { + initialState, + }, + }); + + await waitForNextUpdate(); + + const pageCursor = 'SOMEPAGE'; + + act(() => { + result.current.setPageCursor(pageCursor); + }); + + await waitForNextUpdate(); + + expect(query).toHaveBeenLastCalledWith({ + pageCursor, + term: '', + types: ['*'], + filters: {}, }); }); @@ -341,9 +369,9 @@ describe('SearchContext', () => { await waitForNextUpdate(); expect(query).toHaveBeenLastCalledWith({ + term: '', types: ['*'], filters: {}, - term: '', pageCursor: 'NEXT', }); }); @@ -373,9 +401,9 @@ describe('SearchContext', () => { await waitForNextUpdate(); expect(query).toHaveBeenLastCalledWith({ + term: '', types: ['*'], filters: {}, - term: '', pageCursor: 'PREVIOUS', }); }); diff --git a/plugins/search-react/src/context/SearchContext.tsx b/plugins/search-react/src/context/SearchContext.tsx index 6557f04c15..94e51c0668 100644 --- a/plugins/search-react/src/context/SearchContext.tsx +++ b/plugins/search-react/src/context/SearchContext.tsx @@ -44,6 +44,7 @@ export type SearchContextValue = { setTerm: React.Dispatch>; setTypes: React.Dispatch>; setFilters: React.Dispatch>; + setPageLimit: React.Dispatch>; setPageCursor: React.Dispatch>; fetchNextPage?: React.DispatchWithoutAction; fetchPreviousPage?: React.DispatchWithoutAction; @@ -57,6 +58,7 @@ export type SearchContextState = { term: string; types: string[]; filters: JsonObject; + pageLimit?: number; pageCursor?: string; }; @@ -98,9 +100,10 @@ export const useSearchContextCheck = () => { */ const searchInitialState: SearchContextState = { term: '', - pageCursor: undefined, - filters: {}, types: [], + filters: {}, + pageLimit: undefined, + pageCursor: undefined, }; const useSearchContextValue = ( @@ -111,6 +114,9 @@ const useSearchContextValue = ( const [term, setTerm] = useState(initialValue.term); const [types, setTypes] = useState(initialValue.types); const [filters, setFilters] = useState(initialValue.filters); + const [pageLimit, setPageLimit] = useState( + initialValue.pageLimit, + ); const [pageCursor, setPageCursor] = useState( initialValue.pageCursor, ); @@ -122,20 +128,23 @@ const useSearchContextValue = ( () => searchApi.query({ term, - filters, - pageCursor, types, + filters, + pageLimit, + pageCursor, }), - [term, types, filters, pageCursor], + [term, types, filters, pageLimit, pageCursor], ); const hasNextPage = !result.loading && !result.error && result.value?.nextPageCursor; const hasPreviousPage = !result.loading && !result.error && result.value?.previousPageCursor; + const fetchNextPage = useCallback(() => { setPageCursor(result.value?.nextPageCursor); }, [result.value?.nextPageCursor]); + const fetchPreviousPage = useCallback(() => { setPageCursor(result.value?.previousPageCursor); }, [result.value?.previousPageCursor]); @@ -158,12 +167,14 @@ const useSearchContextValue = ( const value: SearchContextValue = { result, - filters, - setFilters, term, setTerm, types, setTypes, + filters, + setFilters, + pageLimit, + setPageLimit, pageCursor, setPageCursor, fetchNextPage: hasNextPage ? fetchNextPage : undefined, diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 0d353dff58..79fce6a287 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,73 @@ # @backstage/plugin-search +## 1.0.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.0.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-react@1.2.0-next.2 + +## 1.0.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + +## 1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/version-bridge@1.0.1 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 1.0.2 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index f25c8a11f1..ec3a1d754d 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-search", "description": "The Backstage plugin that provides your backstage app with search", - "version": "1.0.2", + "version": "1.0.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index e9e11ab5eb..005fa9aaff 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-sentry +## 0.4.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.4.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.4.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.4.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.4.2 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 65cac51b8f..9a10fe7214 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sentry", "description": "A Backstage plugin that integrates towards Sentry", - "version": "0.4.2", + "version": "0.4.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/shortcuts/CHANGELOG.md b/plugins/shortcuts/CHANGELOG.md index 3664d5713b..842b0d70a8 100644 --- a/plugins/shortcuts/CHANGELOG.md +++ b/plugins/shortcuts/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-shortcuts +## 0.3.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## 0.3.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## 0.3.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + ## 0.3.1 ### Patch Changes diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index e0eda5cd4c..b37c5bba1a 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-shortcuts", "description": "A Backstage plugin that provides a shortcuts feature to the sidebar", - "version": "0.3.1", + "version": "0.3.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/sonarqube-backend/CHANGELOG.md b/plugins/sonarqube-backend/CHANGELOG.md index 485559c8b1..8b34005df1 100644 --- a/plugins/sonarqube-backend/CHANGELOG.md +++ b/plugins/sonarqube-backend/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-sonarqube-backend +## 0.1.2 + +### Patch Changes + +- 9c595302cb: Normalize on winston version ^3.2.1 +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.1.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.1.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + ## 0.1.1 ### Patch Changes diff --git a/plugins/sonarqube-backend/package.json b/plugins/sonarqube-backend/package.json index 2552407db8..baaa8cd488 100644 --- a/plugins/sonarqube-backend/package.json +++ b/plugins/sonarqube-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sonarqube-backend", - "version": "0.1.1", + "version": "0.1.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,7 +29,7 @@ "express": "^4.18.1", "express-promise-router": "^4.1.0", "node-fetch": "^2.6.7", - "winston": "^3.8.1", + "winston": "^3.2.1", "yn": "^5.0.0" }, "devDependencies": { diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index af84e29a46..bf72ac2e28 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-sonarqube +## 0.4.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.4.2-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.4.2-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.4.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.4.1 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index a06019e867..18c1b316a9 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sonarqube", "description": "", - "version": "0.4.1", + "version": "0.4.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/splunk-on-call/CHANGELOG.md b/plugins/splunk-on-call/CHANGELOG.md index 7c85846970..fe0a21bea4 100644 --- a/plugins/splunk-on-call/CHANGELOG.md +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-splunk-on-call +## 0.3.34 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.3.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.3.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.3.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.3.33 ### Patch Changes diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index f52594b8dd..5543765716 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-splunk-on-call", "description": "A Backstage plugin that integrates towards Splunk On-Call", - "version": "0.3.33", + "version": "0.3.34", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/stack-overflow-backend/CHANGELOG.md b/plugins/stack-overflow-backend/CHANGELOG.md index a12465231d..5fc3631f54 100644 --- a/plugins/stack-overflow-backend/CHANGELOG.md +++ b/plugins/stack-overflow-backend/CHANGELOG.md @@ -1,5 +1,42 @@ # @backstage/plugin-stack-overflow-backend +## 0.1.6 + +### Patch Changes + +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/cli@0.20.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/config@1.0.3-next.1 + +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.20.0-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 0.1.5 ### Patch Changes diff --git a/plugins/stack-overflow-backend/package.json b/plugins/stack-overflow-backend/package.json index 4407219bf2..2fa1a69470 100644 --- a/plugins/stack-overflow-backend/package.json +++ b/plugins/stack-overflow-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-stack-overflow-backend", - "version": "0.1.5", + "version": "0.1.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts index 688f54f005..1aa0e5d300 100644 --- a/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts +++ b/plugins/stack-overflow-backend/src/search/StackOverflowQuestionsCollatorFactory.ts @@ -76,7 +76,7 @@ export class StackOverflowQuestionsCollatorFactory this.apiKey = options.apiKey; this.maxPage = options.maxPage; this.requestParams = options.requestParams; - this.logger = options.logger; + this.logger = options.logger.child({ documentType: this.type }); } static fromConfig( diff --git a/plugins/stack-overflow/CHANGELOG.md b/plugins/stack-overflow/CHANGELOG.md index 0d1fa8a8ec..45d6f8a73f 100644 --- a/plugins/stack-overflow/CHANGELOG.md +++ b/plugins/stack-overflow/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-stack-overflow +## 0.1.6 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-home@0.4.26 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/theme@0.2.16 + +## 0.1.6-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-home@0.4.26-next.2 + +## 0.1.6-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/plugin-home@0.4.26-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/theme@0.2.16 + +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-home@0.4.26-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 0.1.5 ### Patch Changes diff --git a/plugins/stack-overflow/package.json b/plugins/stack-overflow/package.json index 2a1884280d..6d4c2aff09 100644 --- a/plugins/stack-overflow/package.json +++ b/plugins/stack-overflow/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-stack-overflow", - "version": "0.1.5", + "version": "0.1.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md index 1a07c089ab..14e195b249 100644 --- a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md +++ b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-tech-insights-backend-module-jsonfc +## 0.1.21 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/plugin-tech-insights-node@0.3.5 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-tech-insights-common@0.2.7 + +## 0.1.21-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## 0.1.21-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-tech-insights-common@0.2.7-next.1 + - @backstage/plugin-tech-insights-node@0.3.5-next.1 + +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-tech-insights-node@0.3.5-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.0 + ## 0.1.20 ### Patch Changes diff --git a/plugins/tech-insights-backend-module-jsonfc/package.json b/plugins/tech-insights-backend-module-jsonfc/package.json index 8109697054..9e4c5503c1 100644 --- a/plugins/tech-insights-backend-module-jsonfc/package.json +++ b/plugins/tech-insights-backend-module-jsonfc/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-backend-module-jsonfc", - "version": "0.1.20", + "version": "0.1.21", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-backend/CHANGELOG.md b/plugins/tech-insights-backend/CHANGELOG.md index 846dad6e33..db98317e31 100644 --- a/plugins/tech-insights-backend/CHANGELOG.md +++ b/plugins/tech-insights-backend/CHANGELOG.md @@ -1,5 +1,66 @@ # @backstage/plugin-tech-insights-backend +## 0.5.3 + +### Patch Changes + +- 296aea34da: The Tech Insights plugin supports running fact retrievers across multiple instances. Update the README to remove the stale instructions. +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- f7cbfb97ed: Modify router endpoint to handle singular and collections of request parameters similarly. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-tech-insights-node@0.3.5 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/plugin-tech-insights-common@0.2.7 + +## 0.5.3-next.2 + +### Patch Changes + +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-tech-insights-node@0.3.5-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## 0.5.3-next.1 + +### Patch Changes + +- f7cbfb97ed: Modify router endpoint to handle singular and collections of request parameters similarly. +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/plugin-tech-insights-common@0.2.7-next.1 + - @backstage/plugin-tech-insights-node@0.3.5-next.1 + +## 0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-tech-insights-node@0.3.5-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.0 + ## 0.5.2 ### Patch Changes diff --git a/plugins/tech-insights-backend/README.md b/plugins/tech-insights-backend/README.md index c5c6e66270..26dac78db5 100644 --- a/plugins/tech-insights-backend/README.md +++ b/plugins/tech-insights-backend/README.md @@ -114,22 +114,7 @@ const builder = buildTechInsightsContext({ #### Running fact retrievers in a multi-instance installation -Current logic on running scheduled fact retrievers is intended to be executed in a single instance. Running on multi-instane environment there might be some additional data accumulation when multiple fact retrievers would retrieve and persist their facts. To mitigate this it is recommended to mark a single instance to be a specific fact retriever instance. One way to do this is by using environment variables to indicate if the retrievers should be registered. This can be done for example like the code snippet below - -```diff -const builder = buildTechInsightsContext({ - logger: env.logger, - config: env.config, - database: env.database, - discovery: env.discovery, - tokenManager: env.tokenManager, - scheduler: env.scheduler, -- factRetrievers: [], -+ factRetrievers: process.env.MAIN_FACT_RETRIEVER_INSTANCE ? [myFactRetrieverRegistration] : [], -}); -``` - -Where the instance dedicated to handling retrieval of facts would have environment variable `MAIN_FACT_RETRIEVER_INSTANCE` set to true. +The Tech Insights plugin utilizes the `PluginTaskScheduler` for scheduling tasks and coordinating the task invocation across instances. See [the PluginTaskScheduler documentation](https://backstage.io/docs/reference/backend-tasks.plugintaskscheduler) for more information. ### Creating Fact Retrievers diff --git a/plugins/tech-insights-backend/package.json b/plugins/tech-insights-backend/package.json index d2d1838bc1..402cbc49d5 100644 --- a/plugins/tech-insights-backend/package.json +++ b/plugins/tech-insights-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-backend", - "version": "0.5.2", + "version": "0.5.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-backend/src/service/router.test.ts b/plugins/tech-insights-backend/src/service/router.test.ts index 8513973598..6935338a56 100644 --- a/plugins/tech-insights-backend/src/service/router.test.ts +++ b/plugins/tech-insights-backend/src/service/router.test.ts @@ -87,59 +87,94 @@ describe('Tech Insights router tests', () => { app = express().use(router); }); - - it('should be able to retrieve latest schemas', async () => { - await request(app).get('/fact-schemas').expect(200); - expect(latestSchemasMock).toHaveBeenCalled(); + describe('/fact-schemas', () => { + it('should be able to retrieve latest schemas', async () => { + await request(app).get('/fact-schemas').expect(200); + expect(latestSchemasMock).toHaveBeenCalled(); + }); }); - it('should not contain check endpoints when checker not present', async () => { - await request(app).get('/checks').expect(404); - await request(app).post('/checks/a/a/a').expect(404); + describe('/checks', () => { + it('should not contain check endpoints when checker not present', async () => { + await request(app).get('/checks').expect(404); + await request(app).post('/checks/a/a/a').expect(404); + }); }); - it('should be able to parse id request params for fact retrieval', async () => { - await request(app) - .get('/facts/latest') - .query({ - entity: 'a:a/a', - ids: ['firstId', 'secondId'], - }) - .expect(200); - expect(latestFactsByIdsMock).toHaveBeenCalledWith( - ['firstId', 'secondId'], - 'a:a/a', - ); + describe('/facts/latest', () => { + it('should be able to parse id request params for fact retrieval', async () => { + await request(app) + .get('/facts/latest') + .query({ + entity: 'a:a/a', + ids: ['firstId', 'secondId'], + }) + .expect(200); + expect(latestFactsByIdsMock).toHaveBeenCalledWith( + ['firstId', 'secondId'], + 'a:a/a', + ); + }); + it('should handle singular ids in query params correctly', async () => { + await request(app) + .get('/facts/latest') + .query({ + entity: 'a:a/a', + ids: ['secondId'], + }) + .expect(200); + expect(latestFactsByIdsMock).toHaveBeenCalledWith(['secondId'], 'a:a/a'); + }); }); - it('should be able to parse datetime request params for fact retrieval', async () => { - await request(app) - .get('/facts/range') - .query({ - entity: 'a:a/a', - ids: ['firstId', 'secondId'], - startDatetime: '2021-12-12T12:12:12', - endDatetime: '2022-11-11T11:11:11', - }) - .expect(200); - expect(factsBetweenTimestampsByIdsMock).toHaveBeenCalledWith( - ['firstId', 'secondId'], - 'a:a/a', - DateTime.fromISO('2021-12-12T12:12:12.000+00:00'), - DateTime.fromISO('2022-11-11T11:11:11.000+00:00'), - ); - }); + describe('/facts/range', () => { + it('should be able to parse datetime request params for fact retrieval', async () => { + await request(app) + .get('/facts/range') + .query({ + entity: 'a:a/a', + ids: ['firstId', 'secondId'], + startDatetime: '2021-12-12T12:12:12', + endDatetime: '2022-11-11T11:11:11', + }) + .expect(200); + expect(factsBetweenTimestampsByIdsMock).toHaveBeenCalledWith( + ['firstId', 'secondId'], + 'a:a/a', + DateTime.fromISO('2021-12-12T12:12:12.000+00:00'), + DateTime.fromISO('2022-11-11T11:11:11.000+00:00'), + ); + }); - it('should respond gracefully on parsing errors', async () => { - await request(app) - .get('/facts/range') - .query({ - entity: 'a:a/a', - ids: ['firstId', 'secondId'], - startDatetime: '2021-12-1222T12:12:12', - endDatetime: '2022-1122-11T11:11:11', - }) - .expect(422); - expect(latestFactsByIdsMock).toHaveBeenCalledTimes(0); + it('should respond gracefully on parsing errors', async () => { + await request(app) + .get('/facts/range') + .query({ + entity: 'a:a/a', + ids: ['firstId', 'secondId'], + startDatetime: '2021-12-1222T12:12:12', + endDatetime: '2022-1122-11T11:11:11', + }) + .expect(422); + expect(latestFactsByIdsMock).toHaveBeenCalledTimes(0); + }); + + it('should handle singular ids in query params correctly', async () => { + await request(app) + .get('/facts/range') + .query({ + entity: 'a:a/a', + ids: ['firstId'], + startDatetime: '2021-12-12T12:12:12', + endDatetime: '2022-11-11T11:11:11', + }) + .expect(200); + expect(factsBetweenTimestampsByIdsMock).toHaveBeenCalledWith( + ['firstId'], + 'a:a/a', + DateTime.fromISO('2021-12-12T12:12:12.000+00:00'), + DateTime.fromISO('2022-11-11T11:11:11.000+00:00'), + ); + }); }); }); diff --git a/plugins/tech-insights-backend/src/service/router.ts b/plugins/tech-insights-backend/src/service/router.ts index 4b93bf4d60..6dba290422 100644 --- a/plugins/tech-insights-backend/src/service/router.ts +++ b/plugins/tech-insights-backend/src/service/router.ts @@ -87,7 +87,7 @@ export async function createRouter< if (factChecker) { logger.info('Fact checker configured. Enabling fact checking endpoints.'); router.get('/checks', async (_req, res) => { - return res.send(await factChecker.getChecks()); + return res.json(await factChecker.getChecks()); }); router.post('/checks/run/:namespace/:kind/:name', async (req, res) => { @@ -95,7 +95,7 @@ export async function createRouter< const { checks }: { checks: string[] } = req.body; const entityTriplet = stringifyEntityRef({ namespace, kind, name }); const checkResult = await factChecker.runChecks(entityTriplet, checks); - return res.send(checkResult); + return res.json(checkResult); }); router.post('/checks/run', async (req, res) => { @@ -113,7 +113,7 @@ export async function createRouter< }; }); const results = await Promise.all(tasks); - return res.send(results); + return res.json(results); }); } else { logger.info( @@ -123,7 +123,7 @@ export async function createRouter< router.get('/fact-schemas', async (req, res) => { const ids = req.query.ids as string[]; - return res.send(await techInsightsStore.getLatestSchemas(ids)); + return res.json(await techInsightsStore.getLatestSchemas(ids)); }); /** @@ -132,8 +132,14 @@ export async function createRouter< router.get('/facts/latest', async (req, res) => { const { entity } = req.query; const { namespace, kind, name } = parseEntityRef(entity as string); - const ids = req.query.ids as string[]; - return res.send( + + if (!req.query.ids) { + return res + .status(422) + .json({ error: 'Failed to parse ids from request' }); + } + const ids = [req.query.ids].flat() as string[]; + return res.json( await techInsightsStore.getLatestFactsByIds( ids, stringifyEntityRef({ namespace, kind, name }), @@ -148,18 +154,23 @@ export async function createRouter< const { entity } = req.query; const { namespace, kind, name } = parseEntityRef(entity as string); - const ids = req.query.ids as string[]; + if (!req.query.ids) { + return res + .status(422) + .json({ error: 'Failed to parse ids from request' }); + } + const ids = [req.query.ids].flat() as string[]; const startDatetime = DateTime.fromISO(req.query.startDatetime as string); const endDatetime = DateTime.fromISO(req.query.endDatetime as string); if (!startDatetime.isValid || !endDatetime.isValid) { - return res.status(422).send({ + return res.status(422).json({ message: 'Failed to parse datetime from request', field: !startDatetime.isValid ? 'startDateTime' : 'endDateTime', value: !startDatetime.isValid ? startDatetime : endDatetime, }); } const entityTriplet = stringifyEntityRef({ namespace, kind, name }); - return res.send( + return res.json( await techInsightsStore.getFactsBetweenTimestampsByIds( ids, entityTriplet, diff --git a/plugins/tech-insights-common/CHANGELOG.md b/plugins/tech-insights-common/CHANGELOG.md index 0482f80fb0..13f502287e 100644 --- a/plugins/tech-insights-common/CHANGELOG.md +++ b/plugins/tech-insights-common/CHANGELOG.md @@ -1,5 +1,33 @@ # @backstage/plugin-tech-insights-common +## 0.2.7 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 0.2.7-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 0.2.7-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + +## 0.2.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/types@1.0.0 + ## 0.2.6 ### Patch Changes diff --git a/plugins/tech-insights-common/package.json b/plugins/tech-insights-common/package.json index fda306c580..750a8f9ce7 100644 --- a/plugins/tech-insights-common/package.json +++ b/plugins/tech-insights-common/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-common", - "version": "0.2.6", + "version": "0.2.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights-node/CHANGELOG.md b/plugins/tech-insights-node/CHANGELOG.md index fc2bc73476..d9605bd033 100644 --- a/plugins/tech-insights-node/CHANGELOG.md +++ b/plugins/tech-insights-node/CHANGELOG.md @@ -1,5 +1,51 @@ # @backstage/plugin-tech-insights-node +## 0.3.5 + +### Patch Changes + +- 0963b4d5fb: Updated package role to be `node-library`. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7 + +## 0.3.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## 0.3.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.1 + +## 0.3.5-next.0 + +### Patch Changes + +- 0963b4d5fb: Updated package role to be `node-library`. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.0 + ## 0.3.4 ### Patch Changes diff --git a/plugins/tech-insights-node/package.json b/plugins/tech-insights-node/package.json index 81554199a9..20cb0152cc 100644 --- a/plugins/tech-insights-node/package.json +++ b/plugins/tech-insights-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights-node", - "version": "0.3.4", + "version": "0.3.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/tech-insights/CHANGELOG.md b/plugins/tech-insights/CHANGELOG.md index 3418b08943..fa13c9a426 100644 --- a/plugins/tech-insights/CHANGELOG.md +++ b/plugins/tech-insights/CHANGELOG.md @@ -1,5 +1,64 @@ # @backstage/plugin-tech-insights +## 0.3.1 + +### Patch Changes + +- f3d272cf57: Make sure to reload score card contents when props change +- a60a6807bd: making available the search for the last FACTS executed +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7 + +## 0.3.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.2 + +## 0.3.1-next.1 + +### Patch Changes + +- a60a6807bd: making available the search for the last FACTS executed +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.1 + +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + - @backstage/plugin-tech-insights-common@0.2.7-next.0 + ## 0.3.0 ### Minor Changes diff --git a/plugins/tech-insights/api-report.md b/plugins/tech-insights/api-report.md index 03155a5958..2d25d6d39d 100644 --- a/plugins/tech-insights/api-report.md +++ b/plugins/tech-insights/api-report.md @@ -12,6 +12,7 @@ import { CheckResult } from '@backstage/plugin-tech-insights-common'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { DiscoveryApi } from '@backstage/core-plugin-api'; import { IdentityApi } from '@backstage/core-plugin-api'; +import { JsonValue } from '@backstage/types'; import { default as React_2 } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; @@ -51,6 +52,16 @@ export const EntityTechInsightsScorecardContent: (props: { checksId?: string[] | undefined; }) => JSX.Element; +// @public +export interface InsightFacts { + // (undocumented) + [factId: string]: { + timestamp: string; + version: string; + facts: Record; + }; +} + // @public export const jsonRulesEngineCheckResultRenderer: CheckResultRenderer; @@ -61,6 +72,8 @@ export interface TechInsightsApi { // (undocumented) getCheckResultRenderers: (types: string[]) => CheckResultRenderer[]; // (undocumented) + getFacts(entity: CompoundEntityRef, facts: string[]): Promise; + // (undocumented) runBulkChecks( entities: CompoundEntityRef[], checks?: Check[], @@ -87,6 +100,8 @@ export class TechInsightsClient implements TechInsightsApi { // (undocumented) getCheckResultRenderers(types: string[]): CheckResultRenderer[]; // (undocumented) + getFacts(entity: CompoundEntityRef, facts: string[]): Promise; + // (undocumented) runBulkChecks( entities: CompoundEntityRef[], checks?: Check[], diff --git a/plugins/tech-insights/package.json b/plugins/tech-insights/package.json index 8a839daaf3..cd32374fb5 100644 --- a/plugins/tech-insights/package.json +++ b/plugins/tech-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights", - "version": "0.3.0", + "version": "0.3.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -38,6 +38,7 @@ "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", + "qs": "^6.9.4", "react-use": "^17.2.4" }, "peerDependencies": { diff --git a/plugins/tech-insights/src/api/TechInsightsApi.ts b/plugins/tech-insights/src/api/TechInsightsApi.ts index 8ab1090304..166f1d2782 100644 --- a/plugins/tech-insights/src/api/TechInsightsApi.ts +++ b/plugins/tech-insights/src/api/TechInsightsApi.ts @@ -19,7 +19,7 @@ import { CheckResult, BulkCheckResponse, } from '@backstage/plugin-tech-insights-common'; -import { Check } from './types'; +import { Check, InsightFacts } from './types'; import { CheckResultRenderer } from '../components/CheckResultRenderer'; import { CompoundEntityRef } from '@backstage/catalog-model'; @@ -48,4 +48,5 @@ export interface TechInsightsApi { entities: CompoundEntityRef[], checks?: Check[], ): Promise; + getFacts(entity: CompoundEntityRef, facts: string[]): Promise; } diff --git a/plugins/tech-insights/src/api/TechInsightsClient.ts b/plugins/tech-insights/src/api/TechInsightsClient.ts index 8db5de3945..0f55a8f8e1 100644 --- a/plugins/tech-insights/src/api/TechInsightsClient.ts +++ b/plugins/tech-insights/src/api/TechInsightsClient.ts @@ -19,15 +19,19 @@ import { BulkCheckResponse, CheckResult, } from '@backstage/plugin-tech-insights-common'; -import { Check } from './types'; +import { Check, InsightFacts } from './types'; import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api'; import { ResponseError } from '@backstage/errors'; -import { CompoundEntityRef } from '@backstage/catalog-model'; +import { + CompoundEntityRef, + stringifyEntityRef, +} from '@backstage/catalog-model'; import { CheckResultRenderer, jsonRulesEngineCheckResultRenderer, } from '../components/CheckResultRenderer'; +import qs from 'qs'; /** @public */ export class TechInsightsClient implements TechInsightsApi { @@ -45,76 +49,85 @@ export class TechInsightsClient implements TechInsightsApi { this.renderers = options.renderers; } + async getFacts( + entity: CompoundEntityRef, + facts: string[], + ): Promise { + const query = qs.stringify({ + entity: stringifyEntityRef(entity), + ids: facts, + }); + return await this.api(`/facts/latest?${query}`); + } + getCheckResultRenderers(types: string[]): CheckResultRenderer[] { const renderers = this.renderers ?? [jsonRulesEngineCheckResultRenderer]; return renderers.filter(d => types.includes(d.type)); } async getAllChecks(): Promise { - const url = await this.discoveryApi.getBaseUrl('tech-insights'); - const { token } = await this.identityApi.getCredentials(); - const response = await fetch(`${url}/checks`, { - headers: token - ? { - Authorization: `Bearer ${token}`, - } - : undefined, - }); - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - return await response.json(); + return this.api('/checks'); } async runChecks( entityParams: CompoundEntityRef, checks?: string[], ): Promise { - const url = await this.discoveryApi.getBaseUrl('tech-insights'); - const { token } = await this.identityApi.getCredentials(); const { namespace, kind, name } = entityParams; const requestBody = { checks }; - const response = await fetch( - `${url}/checks/run/${encodeURIComponent(namespace)}/${encodeURIComponent( + return this.api( + `/checks/run/${encodeURIComponent(namespace)}/${encodeURIComponent( kind, )}/${encodeURIComponent(name)}`, { method: 'POST', body: JSON.stringify(requestBody), headers: { - 'Content-Type': 'application/json', - ...(token && { Authorization: `Bearer ${token}` }), + 'content-type': 'application/json', }, }, ); - if (!response.ok) { - throw await ResponseError.fromResponse(response); - } - return await response.json(); } async runBulkChecks( entities: CompoundEntityRef[], checks?: Check[], ): Promise { - const url = await this.discoveryApi.getBaseUrl('tech-insights'); - const { token } = await this.identityApi.getCredentials(); const checkIds = checks ? checks.map(check => check.id) : []; const requestBody = { entities, checks: checkIds.length > 0 ? checkIds : undefined, }; - const response = await fetch(`${url}/checks/run`, { + return this.api('/checks/run', { method: 'POST', body: JSON.stringify(requestBody), headers: { - 'Content-Type': 'application/json', - ...(token && { Authorization: `Bearer ${token}` }), + 'content-type': 'application/json', }, }); - if (!response.ok) { - throw await ResponseError.fromResponse(response); + } + + private async api(path: string, init?: RequestInit): Promise { + const url = await this.discoveryApi.getBaseUrl('tech-insights'); + const { token } = await this.identityApi.getCredentials(); + + const headers: HeadersInit = new Headers(init?.headers); + if (!headers.has('content-type')) + headers.set('content-type', 'application/json'); + if (token && !headers.has('authorization')) { + headers.set('authorization', `Bearer ${token}`); } - return await response.json(); + + const request = new Request(`${url}${path}`, { + ...init, + headers, + }); + + return fetch(request).then(async response => { + if (!response.ok) { + throw await ResponseError.fromResponse(response); + } + return response.json() as Promise; + }); } } diff --git a/plugins/tech-insights/src/api/types.ts b/plugins/tech-insights/src/api/types.ts index 10dfa06420..ac1f2e44ee 100644 --- a/plugins/tech-insights/src/api/types.ts +++ b/plugins/tech-insights/src/api/types.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { JsonValue } from '@backstage/types'; + /** * Represents a single check defined on the TechInsights backend. * @@ -26,3 +28,16 @@ export type Check = { description: string; factIds: string[]; }; + +/** + * Represents a Fact defined on the TechInsights backend. + * + * @public + */ +export interface InsightFacts { + [factId: string]: { + timestamp: string; + version: string; + facts: Record; + }; +} diff --git a/plugins/tech-insights/src/components/ScorecardsCard/ScorecardsCard.tsx b/plugins/tech-insights/src/components/ScorecardsCard/ScorecardsCard.tsx index 52909acad9..4bbc283829 100644 --- a/plugins/tech-insights/src/components/ScorecardsCard/ScorecardsCard.tsx +++ b/plugins/tech-insights/src/components/ScorecardsCard/ScorecardsCard.tsx @@ -16,10 +16,9 @@ import React from 'react'; import useAsync from 'react-use/lib/useAsync'; -import { Progress } from '@backstage/core-components'; +import { ErrorPanel, Progress } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { ScorecardInfo } from '../ScorecardsInfo'; -import Alert from '@material-ui/lab/Alert'; import { techInsightsApiRef } from '../../api/TechInsightsApi'; import { useEntity } from '@backstage/plugin-catalog-react'; import { getCompoundEntityRef } from '@backstage/catalog-model'; @@ -31,15 +30,16 @@ export const ScorecardsCard = (props: { }) => { const { title, description, checksId } = props; const api = useApi(techInsightsApiRef); - const { namespace, kind, name } = getCompoundEntityRef(useEntity().entity); + const { entity } = useEntity(); const { value, loading, error } = useAsync( - async () => await api.runChecks({ namespace, kind, name }, checksId), + async () => await api.runChecks(getCompoundEntityRef(entity), checksId), + [api, entity, JSON.stringify(checksId)], ); if (loading) { return ; } else if (error) { - return {error.message}; + return ; } return ( diff --git a/plugins/tech-insights/src/index.ts b/plugins/tech-insights/src/index.ts index f3dd05c3aa..9e075df038 100644 --- a/plugins/tech-insights/src/index.ts +++ b/plugins/tech-insights/src/index.ts @@ -20,7 +20,7 @@ export { } from './plugin'; export { techInsightsApiRef, TechInsightsClient } from './api'; -export type { TechInsightsApi, Check } from './api'; +export type { TechInsightsApi, Check, InsightFacts } from './api'; export { BooleanCheck } from './components/BooleanCheck'; export { jsonRulesEngineCheckResultRenderer } from './components/CheckResultRenderer'; export type { CheckResultRenderer } from './components/CheckResultRenderer'; diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index c445592f15..d7ae58f097 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,41 @@ # @backstage/plugin-tech-radar +## 0.5.17 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 0.5.17-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/theme@0.2.16 + +## 0.5.17-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/theme@0.2.16 + +## 0.5.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 0.5.16 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 19bd64d67c..bf20c2b4aa 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-tech-radar", "description": "A Backstage plugin that lets you display a Tech Radar for your organization", - "version": "0.5.16", + "version": "0.5.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index 3af34f56d8..04388b9c3d 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,69 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0 + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/test-utils@1.2.1 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-techdocs@1.3.3 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/theme@0.2.16 + +## 1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.2 + - @backstage/test-utils@1.2.1-next.2 + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-techdocs@1.3.3-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## 1.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.6.0-next.1 + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/plugin-techdocs@1.3.3-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/test-utils@1.2.1-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.1 + +## 1.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/test-utils@1.2.1-next.0 + - @backstage/plugin-catalog@1.5.2-next.0 + - @backstage/plugin-techdocs@1.3.3-next.0 + - @backstage/plugin-techdocs-react@1.0.5-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/theme@0.2.16 + ## 1.0.4 ### Patch Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index b891a28679..e5f52fed60 100644 --- a/plugins/techdocs-addons-test-utils/package.json +++ b/plugins/techdocs-addons-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-addons-test-utils", - "version": "1.0.4", + "version": "1.0.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 0d41beac1b..f05171ce9d 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,78 @@ # @backstage/plugin-techdocs-backend +## 1.4.0 + +### Minor Changes + +- 7ced1b4076: Add optional `catalogClient` argument to `createRoute` parameters + +### Patch Changes + +- 8006f8a602: In order to improve the debuggability of the search indexing process, messages logged during indexing are now tagged with a `documentType` whose value corresponds to the `type` being indexed. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-catalog-common@1.0.7 + - @backstage/plugin-techdocs-node@1.4.1 + - @backstage/plugin-permission-common@0.7.0 + - @backstage/catalog-client@1.1.1 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## 1.4.0-next.2 + +### Minor Changes + +- 7ced1b4076: Add optional `catalogClient` argument to `createRoute` parameters + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-common@1.0.7-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-permission-common@0.7.0-next.2 + - @backstage/plugin-techdocs-node@1.4.1-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## 1.3.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/plugin-catalog-common@1.0.7-next.1 + - @backstage/plugin-permission-common@0.6.5-next.1 + - @backstage/plugin-techdocs-node@1.4.1-next.1 + +## 1.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/plugin-techdocs-node@1.4.1-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/plugin-catalog-common@1.0.7-next.0 + - @backstage/plugin-permission-common@0.6.5-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 1.3.0 ### Minor Changes diff --git a/plugins/techdocs-backend/api-report.md b/plugins/techdocs-backend/api-report.md index 57e5e5170f..1f6aa1d66a 100644 --- a/plugins/techdocs-backend/api-report.md +++ b/plugins/techdocs-backend/api-report.md @@ -6,6 +6,7 @@ /// import { CatalogApi } from '@backstage/catalog-client'; +import { CatalogClient } from '@backstage/catalog-client'; import { Config } from '@backstage/config'; import { DocumentCollatorFactory } from '@backstage/plugin-search-common'; import { Entity } from '@backstage/catalog-model'; @@ -79,6 +80,7 @@ export type OutOfTheBoxDeploymentOptions = { cache: PluginCacheManager; docsBuildStrategy?: DocsBuildStrategy; buildLogTransport?: winston.transport; + catalogClient?: CatalogClient; }; // @public @@ -90,6 +92,7 @@ export type RecommendedDeploymentOptions = { cache: PluginCacheManager; docsBuildStrategy?: DocsBuildStrategy; buildLogTransport?: winston.transport; + catalogClient?: CatalogClient; }; // @public diff --git a/plugins/techdocs-backend/examples/documented-component/docs/extensions.md b/plugins/techdocs-backend/examples/documented-component/docs/extensions.md index 0fda26302d..eb9abca74a 100644 --- a/plugins/techdocs-backend/examples/documented-component/docs/extensions.md +++ b/plugins/techdocs-backend/examples/documented-component/docs/extensions.md @@ -96,17 +96,17 @@ Animals: :tiger: :horse: :turtle: :wolf: :frog: ### MDX truly sane lists -- attributes +- `attributes` -- customer - - first_name - - test - - family_name - - email -- person - - first_name - - family_name - - birth_date -- subscription_id +- `customer` + - `first_name` + - `test` + - `family_name` + - `email` +- `person` + - `first_name` + - `family_name` + - `birth_date` +- `subscription_id` -- request +- `request` diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index d31bc9fe84..f191f39f67 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-backend", "description": "The Backstage backend plugin that renders technical documentation for your components", - "version": "1.3.0", + "version": "1.4.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts index d458eeaba0..aaddb4fa51 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollatorFactory.ts @@ -90,7 +90,7 @@ export class DefaultTechDocsCollatorFactory implements DocumentCollatorFactory { this.discovery = options.discovery; this.locationTemplate = options.locationTemplate || '/docs/:namespace/:kind/:name/:path'; - this.logger = options.logger; + this.logger = options.logger.child({ documentType: this.type }); this.catalogClient = options.catalogClient || new CatalogClient({ discoveryApi: options.discovery }); diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index ca887efce2..855d8b02f4 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -58,6 +58,7 @@ export type OutOfTheBoxDeploymentOptions = { cache: PluginCacheManager; docsBuildStrategy?: DocsBuildStrategy; buildLogTransport?: winston.transport; + catalogClient?: CatalogClient; }; /** @@ -74,6 +75,7 @@ export type RecommendedDeploymentOptions = { cache: PluginCacheManager; docsBuildStrategy?: DocsBuildStrategy; buildLogTransport?: winston.transport; + catalogClient?: CatalogClient; }; /** @@ -107,7 +109,8 @@ export async function createRouter( ): Promise { const router = Router(); const { publisher, config, logger, discovery } = options; - const catalogClient = new CatalogClient({ discoveryApi: discovery }); + const catalogClient = + options.catalogClient ?? new CatalogClient({ discoveryApi: discovery }); const docsBuildStrategy = options.docsBuildStrategy ?? DefaultDocsBuildStrategy.fromConfig(config); const buildLogTransport = diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index e735bb0e04..dd22792833 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## 1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## 1.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.1 + +## 1.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-techdocs-react@1.0.5-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + ## 1.0.4 ### Patch Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index a5a054dd9f..786e167a0e 100644 --- a/plugins/techdocs-module-addons-contrib/package.json +++ b/plugins/techdocs-module-addons-contrib/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-module-addons-contrib", "description": "Plugin module for contributed TechDocs Addons", - "version": "1.0.4", + "version": "1.0.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index b14edde938..b1b1d9a4c1 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,73 @@ # @backstage/plugin-techdocs-node +## 1.4.1 + +### Patch Changes + +- 0b2a30dead: fixing techdocs-cli Docker client creation + + Docker client does not need to be created when --no-docker + option is provided. + + If you had DOCKER_CERT_PATH environment variable defined + the Docker client was looking for certificates + and breaking techdocs-cli generate command even with --no-docker + option. + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-search-common@1.1.0 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## 1.4.1-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## 1.4.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + +## 1.4.1-next.0 + +### Patch Changes + +- 0b2a30dead: fixing techdocs-cli Docker client creation + + Docker client does not need to be created when --no-docker + option is provided. + + If you had DOCKER_CERT_PATH environment variable defined + the Docker client was looking for certificates + and breaking techdocs-cli generate command even with --no-docker + option. + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 1.4.0 ### Minor Changes diff --git a/plugins/techdocs-node/api-report.md b/plugins/techdocs-node/api-report.md index 0ac8c3eaae..e27b406478 100644 --- a/plugins/techdocs-node/api-report.md +++ b/plugins/techdocs-node/api-report.md @@ -42,7 +42,7 @@ export type GeneratorBuilder = { // @public export type GeneratorOptions = { - containerRunner: ContainerRunner; + containerRunner?: ContainerRunner; logger: Logger; }; @@ -213,7 +213,7 @@ export interface TechDocsDocument extends IndexableDocument { export class TechdocsGenerator implements GeneratorBase { constructor(options: { logger: Logger; - containerRunner: ContainerRunner; + containerRunner?: ContainerRunner; config: Config; scmIntegrations: ScmIntegrationRegistry; }); diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index c096f5199c..692ca58fa9 100644 --- a/plugins/techdocs-node/package.json +++ b/plugins/techdocs-node/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-node", "description": "Common node.js functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "1.4.0", + "version": "1.4.1", "main": "src/index.ts", "types": "src/index.ts", "publishConfig": { diff --git a/plugins/techdocs-node/src/stages/generate/techdocs.ts b/plugins/techdocs-node/src/stages/generate/techdocs.ts index d80449d58c..9bbc58ebdc 100644 --- a/plugins/techdocs-node/src/stages/generate/techdocs.ts +++ b/plugins/techdocs-node/src/stages/generate/techdocs.ts @@ -55,7 +55,7 @@ export class TechdocsGenerator implements GeneratorBase { */ public static readonly defaultDockerImage = 'spotify/techdocs:v1.1.0'; private readonly logger: Logger; - private readonly containerRunner: ContainerRunner; + private readonly containerRunner?: ContainerRunner; private readonly options: GeneratorConfig; private readonly scmIntegrations: ScmIntegrationRegistry; @@ -77,7 +77,7 @@ export class TechdocsGenerator implements GeneratorBase { constructor(options: { logger: Logger; - containerRunner: ContainerRunner; + containerRunner?: ContainerRunner; config: Config; scmIntegrations: ScmIntegrationRegistry; }) { @@ -143,6 +143,11 @@ export class TechdocsGenerator implements GeneratorBase { ); break; case 'docker': + if (this.containerRunner === undefined) { + throw new Error( + "Invalid state: containerRunner cannot be undefined when runIn is 'docker'", + ); + } await this.containerRunner.runContainer({ imageName: this.options.dockerImage ?? TechdocsGenerator.defaultDockerImage, diff --git a/plugins/techdocs-node/src/stages/generate/types.ts b/plugins/techdocs-node/src/stages/generate/types.ts index bdbdb1095d..2b2bbb21c9 100644 --- a/plugins/techdocs-node/src/stages/generate/types.ts +++ b/plugins/techdocs-node/src/stages/generate/types.ts @@ -28,7 +28,7 @@ export type GeneratorRunInType = 'docker' | 'local'; * @public */ export type GeneratorOptions = { - containerRunner: ContainerRunner; + containerRunner?: ContainerRunner; logger: Logger; }; diff --git a/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts b/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts index 896e528644..4418e00c81 100644 --- a/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts +++ b/plugins/techdocs-node/src/stages/publish/googleStorage.test.ts @@ -219,13 +219,13 @@ describe('GoogleGCSPublish', () => { }, }; - beforeAll(() => { + beforeEach(() => { mockFs({ [directory]: files, }); }); - afterAll(() => { + afterEach(() => { mockFs.restore(); }); diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index 869f63f4de..fe2801aff2 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,49 @@ # @backstage/plugin-techdocs-react +## 1.0.5 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/version-bridge@1.0.1 + +## 1.0.5-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/version-bridge@1.0.1 + +## 1.0.5-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/version-bridge@1.0.1 + +## 1.0.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/version-bridge@1.0.1 + ## 1.0.4 ### Patch Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index a7f469a4a9..1e0603799d 100644 --- a/plugins/techdocs-react/package.json +++ b/plugins/techdocs-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs-react", "description": "Shared frontend utilities for TechDocs and Addons", - "version": "1.0.4", + "version": "1.0.5", "publishConfig": { "access": "public", "alphaTypes": "dist/index.alpha.d.ts", diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index ba3a0e0e8a..f266d5d1dd 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,77 @@ # @backstage/plugin-techdocs +## 1.3.3 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/plugin-search-react@1.2.0 + - @backstage/plugin-search-common@1.1.0 + - @backstage/plugin-techdocs-react@1.0.5 + - @backstage/integration-react@1.1.5 + - @backstage/core-plugin-api@1.0.7 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + - @backstage/theme@0.2.16 + +## 1.3.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/plugin-search-common@1.1.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + - @backstage/integration-react@1.1.5-next.2 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-react@1.2.0-next.2 + - @backstage/plugin-techdocs-react@1.0.5-next.2 + +## 1.3.3-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/plugin-search-react@1.2.0-next.1 + - @backstage/plugin-search-common@1.1.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + - @backstage/integration-react@1.1.5-next.1 + - @backstage/theme@0.2.16 + - @backstage/plugin-techdocs-react@1.0.5-next.1 + +## 1.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/plugin-techdocs-react@1.0.5-next.0 + - @backstage/integration-react@1.1.5-next.0 + - @backstage/plugin-search-react@1.1.1-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/plugin-search-common@1.0.2-next.0 + ## 1.3.2 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index ec8c1bf515..4e8a9dfc96 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-techdocs", "description": "The Backstage plugin that renders technical documentation for your components", - "version": "1.3.2", + "version": "1.3.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx index 0e811af6e0..e886454594 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx @@ -175,13 +175,14 @@ export const useTechDocsReaderDom = ( // detect if CTRL or META keys are pressed so that links can be opened in a new tab with `window.open` const modifierActive = event.ctrlKey || event.metaKey; const parsedUrl = new URL(url); + const fullPath = `${parsedUrl.pathname}${parsedUrl.search}${parsedUrl.hash}`; // hash exists when anchor is clicked on secondary sidebar if (parsedUrl.hash) { if (modifierActive) { - window.open(`${parsedUrl.pathname}${parsedUrl.hash}`, '_blank'); + window.open(fullPath, '_blank'); } else { - navigate(`${parsedUrl.pathname}${parsedUrl.hash}`); + navigate(fullPath); // Scroll to hash if it's on the current page transformedElement ?.querySelector(`[id="${parsedUrl.hash.slice(1)}"]`) @@ -189,9 +190,9 @@ export const useTechDocsReaderDom = ( } } else { if (modifierActive) { - window.open(parsedUrl.pathname, '_blank'); + window.open(fullPath, '_blank'); } else { - navigate(parsedUrl.pathname); + navigate(fullPath); } } }, diff --git a/plugins/todo-backend/CHANGELOG.md b/plugins/todo-backend/CHANGELOG.md index 72e176f85a..48fe718759 100644 --- a/plugins/todo-backend/CHANGELOG.md +++ b/plugins/todo-backend/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-todo-backend +## 0.1.34 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/catalog-client@1.1.1 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + - @backstage/integration@1.3.2 + +## 0.1.34-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/catalog-client@1.1.1-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/integration@1.3.2-next.2 + +## 0.1.34-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-client@1.1.1-next.1 + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/integration@1.3.2-next.1 + +## 0.1.34-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/catalog-client@1.1.1-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/integration@1.3.2-next.0 + ## 0.1.33 ### Patch Changes diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index 5ac93bc1bd..3c59254f89 100644 --- a/plugins/todo-backend/package.json +++ b/plugins/todo-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-todo-backend", "description": "A Backstage backend plugin that lets you browse TODO comments in your source code", - "version": "0.1.33", + "version": "0.1.34", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/todo/CHANGELOG.md b/plugins/todo/CHANGELOG.md index f4cd9f07ca..d126f9a9ad 100644 --- a/plugins/todo/CHANGELOG.md +++ b/plugins/todo/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-todo +## 0.2.12 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.2.12-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## 0.2.12-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.2.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + ## 0.2.11 ### Patch Changes diff --git a/plugins/todo/package.json b/plugins/todo/package.json index 9c9956a1ef..ef061277c4 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-todo", "description": "A Backstage plugin that lets you browse TODO comments in your source code", - "version": "0.2.11", + "version": "0.2.12", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/user-settings-backend/CHANGELOG.md b/plugins/user-settings-backend/CHANGELOG.md index 10c62e7b09..5cd32e82f5 100644 --- a/plugins/user-settings-backend/CHANGELOG.md +++ b/plugins/user-settings-backend/CHANGELOG.md @@ -1,5 +1,55 @@ # @backstage/plugin-user-settings-backend +## 0.1.1 + +### Patch Changes + +- f3463b176b: Use `Response.status` instead of `.send(number)` +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- 82ac9bcfe5: Fix wrong import statement in `README.md`. +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/backend-common@0.15.2 + - @backstage/plugin-auth-node@0.2.6 + - @backstage/errors@1.1.2 + - @backstage/types@1.0.0 + +## 0.1.1-next.2 + +### Patch Changes + +- f3463b176b: Use `Response.status` instead of `.send(number)` +- 2d3a5f09ab: Use `response.json` rather than `response.send` where appropriate, as outlined in `SECURITY.md` +- Updated dependencies + - @backstage/backend-common@0.15.2-next.2 + - @backstage/plugin-auth-node@0.2.6-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/types@1.0.0 + +## 0.1.1-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/types@1.0.0 + - @backstage/plugin-auth-node@0.2.6-next.1 + +## 0.1.1-next.0 + +### Patch Changes + +- 82ac9bcfe5: Fix wrong import statement in `README.md`. +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/plugin-auth-node@0.2.6-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/types@1.0.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/user-settings-backend/README.md b/plugins/user-settings-backend/README.md index d12ee0aa89..bbd69cb18e 100644 --- a/plugins/user-settings-backend/README.md +++ b/plugins/user-settings-backend/README.md @@ -54,7 +54,7 @@ To make use of the user settings backend, replace the `WebStorage` with the + discoveryApiRef, + fetchApiRef, errorApiRef, -+ identityApi, ++ identityApiRef, + storageApiRef, } from '@backstage/core-plugin-api'; +import { UserSettingsStorage } from '@backstage/plugin-user-settings'; diff --git a/plugins/user-settings-backend/package.json b/plugins/user-settings-backend/package.json index 429d12d3d3..47aa6b5978 100644 --- a/plugins/user-settings-backend/package.json +++ b/plugins/user-settings-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-user-settings-backend", "description": "The Backstage backend plugin to manage user settings", - "version": "0.1.0", + "version": "0.1.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/user-settings-backend/src/service/router.ts b/plugins/user-settings-backend/src/service/router.ts index 6792042585..ebbb6716a3 100644 --- a/plugins/user-settings-backend/src/service/router.ts +++ b/plugins/user-settings-backend/src/service/router.ts @@ -114,7 +114,7 @@ export async function createRouterInternal(options: { await options.userSettingsStore.delete({ userEntityRef, bucket, key }); - res.send(204).end(); + res.status(204).end(); }); router.use(errorHandler()); diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 42d11f365a..dfced55e24 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,79 @@ # @backstage/plugin-user-settings +## 0.5.0 + +### Minor Changes + +- 5543e86660: **BREAKING**: The `apiRef` passed to `ProviderSettingsItem` now needs to + implement `ProfileInfoApi & SessionApi`, rather than just the latter. This is + unlikely to have an effect on most users though, since the builtin auth + providers generally implement both. + + Fixed settings page showing providers as logged out when the user is using more + than one provider, and displayed some additional login information. + +### Patch Changes + +- 06d61d1266: Handle errors that may occur when the user logs out +- 44c9a95dcf: Prevent `.set()` to execute a request to the StorageClient if the user is `guest` +- 174f02a00a: Update installation instructions +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-app-api@1.1.1 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## 0.5.0-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@1.1.1-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## 0.5.0-next.1 + +### Patch Changes + +- 06d61d1266: Handle errors that may occur when the user logs out +- 44c9a95dcf: Prevent `.set()` to execute a request to the StorageClient if the user is `guest` +- 174f02a00a: Update installation instructions +- Updated dependencies + - @backstage/core-app-api@1.1.1-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + +## 0.5.0-next.0 + +### Minor Changes + +- 5543e86660: **BREAKING**: The `apiRef` passed to `ProviderSettingsItem` now needs to + implement `ProfileInfoApi & SessionApi`, rather than just the latter. This is + unlikely to have an effect on most users though, since the builtin auth + providers generally implement both. + + Fixed settings page showing providers as logged out when the user is using more + than one provider, and displayed some additional login information. + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-app-api@1.1.1-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + - @backstage/types@1.0.0 + ## 0.4.8 ### Patch Changes diff --git a/plugins/user-settings/README.md b/plugins/user-settings/README.md index abe66120fe..ce07d76257 100644 --- a/plugins/user-settings/README.md +++ b/plugins/user-settings/README.md @@ -29,11 +29,11 @@ import { Settings as SidebarSettings } from '@backstage/plugin-user-settings'; Add the page to the App routing: ```ts -import { Router as SettingsRouter } from '@backstage/plugin-user-settings'; +import { UserSettingsPage } from '@backstage/plugin-user-settings'; const AppRoutes = () => ( - } /> + } /> ); ``` diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index e00cf09bb8..33a57ccc86 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-user-settings", "description": "A Backstage plugin that provides a settings page", - "version": "0.4.8", + "version": "0.5.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.test.ts b/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.test.ts index 0ebd96d19c..a421c413dc 100644 --- a/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.test.ts +++ b/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.test.ts @@ -38,6 +38,10 @@ describe('Persistent Storage API', () => { const mockIdentityApi: Partial = { getCredentials: async () => ({ token: 'a-token' }), }; + const mockIdentityApiFallback: Partial = { + // This API recreates the guest mode, where the WebStorage is used as fallback + getCredentials: async () => ({}), + }; const createPersistentStorage = ( args?: Partial<{ @@ -56,6 +60,23 @@ describe('Persistent Storage API', () => { }); }; + const createPersistentStorageFallback = ( + args?: Partial<{ + fetchApi: FetchApi; + discoveryApi: DiscoveryApi; + errorApi: ErrorApi; + namespace?: string; + }>, + ): StorageApi => { + return UserSettingsStorage.create({ + errorApi: mockErrorApi, + fetchApi: new MockFetchApi(), + discoveryApi: mockDiscoveryApi, + identityApi: mockIdentityApiFallback as IdentityApi, + ...args, + }); + }; + afterEach(() => { jest.resetAllMocks(); }); @@ -124,6 +145,33 @@ describe('Persistent Storage API', () => { await storage.set('my-key', dummyValue); }); + it('should fallback set when user not logged in', async () => { + const storage = createPersistentStorageFallback(); + + const selectedKeyNextHandler = jest.fn(); + const dummyValue = 'my-value'; + + await new Promise(resolve => { + storage.observe$('my-key').subscribe({ + next: snapshot => { + selectedKeyNextHandler(snapshot); + if (snapshot.presence === 'present') { + resolve(); + } + }, + }); + + storage.set('my-key', dummyValue); + }); + + expect(selectedKeyNextHandler).toHaveBeenCalledTimes(1); + expect(selectedKeyNextHandler).toHaveBeenCalledWith({ + key: 'my-key', + value: dummyValue, + presence: 'present', + }); + }); + it('should subscribe to key changes when setting a new value', async () => { const storage = createPersistentStorage(); diff --git a/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.ts b/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.ts index 8905b4aeea..ef4d3ead5d 100644 --- a/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.ts +++ b/plugins/user-settings/src/apis/StorageApi/UserSettingsStorage.ts @@ -118,6 +118,7 @@ export class UserSettingsStorage implements StorageApi { if (!(await this.isSignedIn())) { await this.fallback.set(key, data); this.notifyChanges({ key, presence: 'present', value: data }); + return; } const fetchUrl = await this.getFetchUrl(key); diff --git a/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx b/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx index ab6ad8dff9..a5962d47ab 100644 --- a/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx +++ b/plugins/user-settings/src/components/AuthProviders/ProviderSettingsItem.tsx @@ -32,6 +32,7 @@ import { ProfileInfoApi, ProfileInfo, useApi, + errorApiRef, IconComponent, } from '@backstage/core-plugin-api'; import { ProviderSettingsAvatar } from './ProviderSettingsAvatar'; @@ -46,6 +47,7 @@ export const ProviderSettingsItem = (props: { const { title, description, icon: Icon, apiRef } = props; const api = useApi(apiRef); + const errorApi = useApi(errorApiRef); const [signedIn, setSignedIn] = useState(false); const emptyProfile: ProfileInfo = {}; const [profile, setProfile] = useState(emptyProfile); @@ -126,7 +128,10 @@ export const ProviderSettingsItem = (props: { diff --git a/plugins/user-settings/src/components/AuthProviders/UserSettingsAuthProviders.test.tsx b/plugins/user-settings/src/components/AuthProviders/UserSettingsAuthProviders.test.tsx index 4e14dec2ef..fe5440d355 100644 --- a/plugins/user-settings/src/components/AuthProviders/UserSettingsAuthProviders.test.tsx +++ b/plugins/user-settings/src/components/AuthProviders/UserSettingsAuthProviders.test.tsx @@ -26,7 +26,7 @@ import { UserSettingsAuthProviders } from './UserSettingsAuthProviders'; import { ApiProvider, ConfigReader } from '@backstage/core-app-api'; import { configApiRef, googleAuthApiRef } from '@backstage/core-plugin-api'; -const mockSignInHandler = jest.fn().mockReturnValue(''); +const mockSignInHandler = jest.fn().mockReturnValue(Promise.resolve()); const mockGoogleAuth = { sessionState$: () => ({ [Symbol.observable]: jest.fn(), diff --git a/plugins/user-settings/src/components/General/UserSettingsMenu.test.tsx b/plugins/user-settings/src/components/General/UserSettingsMenu.test.tsx index c8d30cd338..9c501bc775 100644 --- a/plugins/user-settings/src/components/General/UserSettingsMenu.test.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsMenu.test.tsx @@ -14,8 +14,15 @@ * limitations under the License. */ -import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; -import { fireEvent } from '@testing-library/react'; +import { + MockErrorApi, + renderInTestApp, + TestApiProvider, + renderWithEffects, + wrapInTestApp, +} from '@backstage/test-utils'; +import { errorApiRef, identityApiRef } from '@backstage/core-plugin-api'; +import { fireEvent, waitFor } from '@testing-library/react'; import React from 'react'; import { UserSettingsMenu } from './UserSettingsMenu'; @@ -30,4 +37,29 @@ describe('', () => { expect(rendered.getByText('Sign Out')).toBeInTheDocument(); }); + + it('handles errors that occur when signing out', async () => { + const failingIdentityApi = { + signOut: jest.fn().mockRejectedValue(new Error('Logout error')), + }; + const mockErrorApi = new MockErrorApi({ collect: true }); + const rendered = await renderInTestApp( + + + , + ); + + const menuButton = rendered.getByLabelText('more'); + fireEvent.click(menuButton); + fireEvent.click(rendered.getByText('Sign Out')); + + await waitFor(() => { + expect(mockErrorApi.getErrors()).toHaveLength(1); + }); + }); }); diff --git a/plugins/user-settings/src/components/General/UserSettingsMenu.tsx b/plugins/user-settings/src/components/General/UserSettingsMenu.tsx index 2483f6cd36..c83bdd5b74 100644 --- a/plugins/user-settings/src/components/General/UserSettingsMenu.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsMenu.tsx @@ -18,10 +18,15 @@ import React from 'react'; import { IconButton, ListItemIcon, Menu, MenuItem } from '@material-ui/core'; import SignOutIcon from '@material-ui/icons/MeetingRoom'; import MoreVertIcon from '@material-ui/icons/MoreVert'; -import { identityApiRef, useApi } from '@backstage/core-plugin-api'; +import { + identityApiRef, + errorApiRef, + useApi, +} from '@backstage/core-plugin-api'; /** @public */ export const UserSettingsMenu = () => { + const errorApi = useApi(errorApiRef); const identityApi = useApi(identityApiRef); const [open, setOpen] = React.useState(false); const [anchorEl, setAnchorEl] = React.useState( @@ -48,7 +53,12 @@ export const UserSettingsMenu = () => { - identityApi.signOut()}> + + identityApi.signOut().catch(error => errorApi.post(error)) + } + > diff --git a/plugins/vault-backend/CHANGELOG.md b/plugins/vault-backend/CHANGELOG.md index fbdf60722d..dd9f5bb8bf 100644 --- a/plugins/vault-backend/CHANGELOG.md +++ b/plugins/vault-backend/CHANGELOG.md @@ -1,5 +1,52 @@ # @backstage/plugin-vault-backend +## 0.2.3 + +### Patch Changes + +- 9c595302cb: Normalize on winston version ^3.2.1 +- dae0bbe522: VaultBuilder.tsx renamed to VaultBuilder in order for module to be correctly loaded. +- Updated dependencies + - @backstage/backend-common@0.15.2 + - @backstage/backend-test-utils@0.1.29 + - @backstage/backend-tasks@0.3.6 + - @backstage/config@1.0.3 + - @backstage/errors@1.1.2 + +## 0.2.3-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.6-next.2 + - @backstage/backend-common@0.15.2-next.2 + - @backstage/backend-test-utils@0.1.29-next.2 + - @backstage/config@1.0.3-next.2 + - @backstage/errors@1.1.2-next.2 + +## 0.2.3-next.1 + +### Patch Changes + +- dae0bbe522: VaultBuilder.tsx renamed to VaultBuilder in order for module to be correctly loaded. +- Updated dependencies + - @backstage/backend-common@0.15.2-next.1 + - @backstage/backend-tasks@0.3.6-next.1 + - @backstage/backend-test-utils@0.1.29-next.1 + - @backstage/config@1.0.3-next.1 + - @backstage/errors@1.1.2-next.1 + +## 0.2.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-test-utils@0.1.29-next.0 + - @backstage/backend-common@0.15.2-next.0 + - @backstage/backend-tasks@0.3.6-next.0 + - @backstage/config@1.0.3-next.0 + - @backstage/errors@1.1.2-next.0 + ## 0.2.2 ### Patch Changes diff --git a/plugins/vault-backend/package.json b/plugins/vault-backend/package.json index e4f0774b45..27a6718c10 100644 --- a/plugins/vault-backend/package.json +++ b/plugins/vault-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-vault-backend", "description": "A Backstage backend plugin that integrates towards Vault", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -46,7 +46,7 @@ "helmet": "^6.0.0", "node-fetch": "^2.6.7", "p-limit": "^3.1.0", - "winston": "^3.7.2", + "winston": "^3.2.1", "yn": "^5.0.0" }, "devDependencies": { diff --git a/plugins/vault-backend/src/service/VaultBuilder.tsx b/plugins/vault-backend/src/service/VaultBuilder.ts similarity index 100% rename from plugins/vault-backend/src/service/VaultBuilder.tsx rename to plugins/vault-backend/src/service/VaultBuilder.ts diff --git a/plugins/vault/CHANGELOG.md b/plugins/vault/CHANGELOG.md index 5f646bd441..610fe46f12 100644 --- a/plugins/vault/CHANGELOG.md +++ b/plugins/vault/CHANGELOG.md @@ -1,5 +1,53 @@ # @backstage/plugin-vault +## 0.1.4 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2 + - @backstage/plugin-catalog-react@1.2.0 + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.1.4-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.2 + - @backstage/catalog-model@1.1.2-next.2 + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## 0.1.4-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.2.0-next.1 + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/catalog-model@1.1.2-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/catalog-model@1.1.2-next.0 + - @backstage/core-components@0.11.2-next.0 + - @backstage/plugin-catalog-react@1.1.5-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + ## 0.1.3 ### Patch Changes diff --git a/plugins/vault/package.json b/plugins/vault/package.json index 843434be41..182b091743 100644 --- a/plugins/vault/package.json +++ b/plugins/vault/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-vault", "description": "A Backstage plugin that integrates towards Vault", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/plugins/xcmetrics/CHANGELOG.md b/plugins/xcmetrics/CHANGELOG.md index 62726efa29..1461d15b9d 100644 --- a/plugins/xcmetrics/CHANGELOG.md +++ b/plugins/xcmetrics/CHANGELOG.md @@ -1,5 +1,45 @@ # @backstage/plugin-xcmetrics +## 0.2.30 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2 + - @backstage/core-plugin-api@1.0.7 + - @backstage/errors@1.1.2 + - @backstage/theme@0.2.16 + +## 0.2.30-next.2 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.2 + - @backstage/core-plugin-api@1.0.7-next.2 + - @backstage/errors@1.1.2-next.2 + - @backstage/theme@0.2.16 + +## 0.2.30-next.1 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.1 + - @backstage/core-plugin-api@1.0.7-next.1 + - @backstage/errors@1.1.2-next.1 + - @backstage/theme@0.2.16 + +## 0.2.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.11.2-next.0 + - @backstage/core-plugin-api@1.0.7-next.0 + - @backstage/errors@1.1.2-next.0 + - @backstage/theme@0.2.16 + ## 0.2.29 ### Patch Changes diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index afed40f10d..c9082c9802 100644 --- a/plugins/xcmetrics/package.json +++ b/plugins/xcmetrics/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-xcmetrics", "description": "A Backstage plugin that shows XCode build metrics for your components", - "version": "0.2.29", + "version": "0.2.30", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", diff --git a/storybook/yarn.lock b/storybook/yarn.lock index 4a5d57c5b2..1567968127 100644 --- a/storybook/yarn.lock +++ b/storybook/yarn.lock @@ -2977,126 +2977,126 @@ __metadata: languageName: node linkType: hard -"@swc/core-android-arm-eabi@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-android-arm-eabi@npm:1.3.2" +"@swc/core-android-arm-eabi@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-android-arm-eabi@npm:1.3.5" dependencies: "@swc/wasm": 1.2.122 conditions: os=android & cpu=arm languageName: node linkType: hard -"@swc/core-android-arm64@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-android-arm64@npm:1.3.2" +"@swc/core-android-arm64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-android-arm64@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-darwin-arm64@npm:1.3.2" +"@swc/core-darwin-arm64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-darwin-arm64@npm:1.3.5" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-darwin-x64@npm:1.3.2" +"@swc/core-darwin-x64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-darwin-x64@npm:1.3.5" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-freebsd-x64@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-freebsd-x64@npm:1.3.2" +"@swc/core-freebsd-x64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-freebsd-x64@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.2" +"@swc/core-linux-arm-gnueabihf@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.2" +"@swc/core-linux-arm64-gnu@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.5" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.2" +"@swc/core-linux-arm64-musl@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.5" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.2" +"@swc/core-linux-x64-gnu@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.5" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-linux-x64-musl@npm:1.3.2" +"@swc/core-linux-x64-musl@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-x64-musl@npm:1.3.5" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.2" +"@swc/core-win32-arm64-msvc@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.2" +"@swc/core-win32-ia32-msvc@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.2" +"@swc/core-win32-x64-msvc@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.5" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.2.239": - version: 1.3.2 - resolution: "@swc/core@npm:1.3.2" + version: 1.3.5 + resolution: "@swc/core@npm:1.3.5" dependencies: - "@swc/core-android-arm-eabi": 1.3.2 - "@swc/core-android-arm64": 1.3.2 - "@swc/core-darwin-arm64": 1.3.2 - "@swc/core-darwin-x64": 1.3.2 - "@swc/core-freebsd-x64": 1.3.2 - "@swc/core-linux-arm-gnueabihf": 1.3.2 - "@swc/core-linux-arm64-gnu": 1.3.2 - "@swc/core-linux-arm64-musl": 1.3.2 - "@swc/core-linux-x64-gnu": 1.3.2 - "@swc/core-linux-x64-musl": 1.3.2 - "@swc/core-win32-arm64-msvc": 1.3.2 - "@swc/core-win32-ia32-msvc": 1.3.2 - "@swc/core-win32-x64-msvc": 1.3.2 + "@swc/core-android-arm-eabi": 1.3.5 + "@swc/core-android-arm64": 1.3.5 + "@swc/core-darwin-arm64": 1.3.5 + "@swc/core-darwin-x64": 1.3.5 + "@swc/core-freebsd-x64": 1.3.5 + "@swc/core-linux-arm-gnueabihf": 1.3.5 + "@swc/core-linux-arm64-gnu": 1.3.5 + "@swc/core-linux-arm64-musl": 1.3.5 + "@swc/core-linux-x64-gnu": 1.3.5 + "@swc/core-linux-x64-musl": 1.3.5 + "@swc/core-win32-arm64-msvc": 1.3.5 + "@swc/core-win32-ia32-msvc": 1.3.5 + "@swc/core-win32-x64-msvc": 1.3.5 dependenciesMeta: "@swc/core-android-arm-eabi": optional: true @@ -3126,7 +3126,7 @@ __metadata: optional: true bin: swcx: run_swcx.js - checksum: c2c83d0e6b4c56d65fb3723aa0be5e777823a6efe5b12702d4d44827a6c6dd8fac3c5dec7ff45222c7b22b4084bffc846f8497d697e61a1706817977256a65dc + checksum: f0bcd0aa1d1a0b7d48ee5c416f8a384e19e4c0eed1e0be940d8893bf3981d98bf00f27d03bebf9cdedd34bf316d68c2db68e7bb7d1940d7b6062541e6e4cc738 languageName: node linkType: hard diff --git a/yarn.lock b/yarn.lock index a039eaa65c..f86858396f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -110,9 +110,9 @@ __metadata: languageName: node linkType: hard -"@apollo/protobufjs@npm:1.2.4": - version: 1.2.4 - resolution: "@apollo/protobufjs@npm:1.2.4" +"@apollo/protobufjs@npm:1.2.6": + version: 1.2.6 + resolution: "@apollo/protobufjs@npm:1.2.6" dependencies: "@protobufjs/aspromise": ^1.1.2 "@protobufjs/base64": ^1.1.2 @@ -130,7 +130,7 @@ __metadata: bin: apollo-pbjs: bin/pbjs apollo-pbts: bin/pbts - checksum: 261d48dbeb4f8958fce707a131d2c44cd4d24efaf6c8d6c4c05e91f673c301015d5dc267caf9c17df32de7ef3ea70b247360f476adee8eb3e96204d61c5b2a64 + checksum: 1410e94f87f7ce202491f2c45633ddea0590f5279bd3eaaeb25aa114345d09a5041f15d79b75e5915b23813dc71ec52e71fc1fda858fee21dead890dcb768416 languageName: node linkType: hard @@ -286,12 +286,12 @@ __metadata: languageName: node linkType: hard -"@asyncapi/parser@npm:^1.15.0": - version: 1.15.0 - resolution: "@asyncapi/parser@npm:1.15.0" +"@asyncapi/parser@npm:^1.17.0": + version: 1.17.0 + resolution: "@asyncapi/parser@npm:1.17.0" dependencies: "@apidevtools/json-schema-ref-parser": ^9.0.6 - "@asyncapi/specs": ^2.14.0 + "@asyncapi/specs": ^3.2.0 "@fmvilas/pseudo-yaml-ast": ^0.3.1 ajv: ^6.10.1 js-yaml: ^3.13.1 @@ -299,17 +299,17 @@ __metadata: lodash.clonedeep: ^4.5.0 node-fetch: ^2.6.0 tiny-merge-patch: ^0.1.2 - checksum: e9435265b4671f6a4c2451e054751ea637dd466b1f6b6848622781fbf6fa9fc93c292ed8791bf38816f63cd9c352f09727ab2d34f76d62f8f21c084b0125445b + checksum: 6ac0bd3c9c90abf331cfdfb054dab047cf0324ee803633c2abc6dad96b12c64d033e4fb94907906adfb521ba16e9b23083b2a6acc7719ccd22154a7867046a95 languageName: node linkType: hard -"@asyncapi/react-component@npm:1.0.0-next.42": - version: 1.0.0-next.42 - resolution: "@asyncapi/react-component@npm:1.0.0-next.42" +"@asyncapi/react-component@npm:1.0.0-next.43": + version: 1.0.0-next.43 + resolution: "@asyncapi/react-component@npm:1.0.0-next.43" dependencies: "@asyncapi/avro-schema-parser": ^0.3.0 "@asyncapi/openapi-schema-parser": ^2.0.0 - "@asyncapi/parser": ^1.15.0 + "@asyncapi/parser": ^1.17.0 highlight.js: ^10.7.2 isomorphic-dompurify: ^0.13.0 marked: ^4.0.14 @@ -319,14 +319,14 @@ __metadata: peerDependencies: react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: eba4aaf344aabc1889085df7f94d14d51e2449db06cabc70c60b9d5b0b2fb9ccec6aa89cbedf9d45c28deca99e1e4ab2d4399b0a2756e9969d4691f304088a77 + checksum: d19adc2b6e674633457da28d38636df4cf9891f4038827b52f2737699d064bcf0d3be707e9f3284852d1bb507f3f36df9ec3bb02fd6f901cfda7a1f49ea9c5da languageName: node linkType: hard -"@asyncapi/specs@npm:^2.14.0": - version: 2.14.0 - resolution: "@asyncapi/specs@npm:2.14.0" - checksum: 066c23c493df54c44c319433bdcf8482a3acd584e32c0073e6a9f5b167d61bde23a252621be2b28bbaf1466636f6cafaab570795de403f0c671358784d4b12ed +"@asyncapi/specs@npm:^3.2.0": + version: 3.2.0 + resolution: "@asyncapi/specs@npm:3.2.0" + checksum: 09971262aefc8844ab3e7c0c3652711862ac562dd5d614f23b496185690430a81df8e50eddba657f4141e0fd9548ef622fe6c20f4e3dec8054be23f774798335 languageName: node linkType: hard @@ -339,6 +339,33 @@ __metadata: languageName: node linkType: hard +"@azure/arm-appservice@npm:^13.0.1": + version: 13.0.2 + resolution: "@azure/arm-appservice@npm:13.0.2" + dependencies: + "@azure/abort-controller": ^1.0.0 + "@azure/core-auth": ^1.3.0 + "@azure/core-client": ^1.5.0 + "@azure/core-lro": ^2.2.0 + "@azure/core-paging": ^1.2.0 + "@azure/core-rest-pipeline": ^1.8.0 + tslib: ^2.2.0 + checksum: 6d604c492a70a6cdd4d3ad3f634a74228dc081ed4f49128873e76bfa5e65ec8cce18b7b19319fd2b1b4edbd390dbd66c9ac48646f0ed80cae09cd2f1d05fb279 + languageName: node + linkType: hard + +"@azure/arm-resourcegraph@npm:^4.2.1": + version: 4.2.1 + resolution: "@azure/arm-resourcegraph@npm:4.2.1" + dependencies: + "@azure/core-auth": ^1.1.4 + "@azure/ms-rest-azure-js": ^2.1.0 + "@azure/ms-rest-js": ^2.2.0 + tslib: ^1.10.0 + checksum: a5afea9faef84ed4576fda430301039524aefe6976a54204fe4453e672aa3d13edc7ef5c996ad6293e9afa3a0833549174aaac18ce236c4ec1242698e2c22bd2 + languageName: node + linkType: hard + "@azure/core-asynciterator-polyfill@npm:^1.0.0": version: 1.0.0 resolution: "@azure/core-asynciterator-polyfill@npm:1.0.0" @@ -346,6 +373,16 @@ __metadata: languageName: node linkType: hard +"@azure/core-auth@npm:^1.1.4, @azure/core-auth@npm:^1.4.0": + version: 1.4.0 + resolution: "@azure/core-auth@npm:1.4.0" + dependencies: + "@azure/abort-controller": ^1.0.0 + tslib: ^2.2.0 + checksum: 1c76c296fe911ad39fc780b033c25a92c41c5a15f011b816d42c13584f627a4dd153dfb4334900ec93eb5b006e14bdda37e8412a7697c5a9636a0abaccffad39 + languageName: node + linkType: hard + "@azure/core-auth@npm:^1.3.0": version: 1.3.2 resolution: "@azure/core-auth@npm:1.3.2" @@ -371,6 +408,21 @@ __metadata: languageName: node linkType: hard +"@azure/core-client@npm:^1.5.0": + version: 1.6.1 + resolution: "@azure/core-client@npm:1.6.1" + dependencies: + "@azure/abort-controller": ^1.0.0 + "@azure/core-auth": ^1.4.0 + "@azure/core-rest-pipeline": ^1.9.1 + "@azure/core-tracing": ^1.0.0 + "@azure/core-util": ^1.0.0 + "@azure/logger": ^1.0.0 + tslib: ^2.2.0 + checksum: 400890a9b5f0c8801ff92005c3cba7bb5124634e321735406731bef33688a79f23d28b49fccdfab90814dade41a13f3d3cb99f80151a2bff17628416e811afb6 + languageName: node + linkType: hard + "@azure/core-http@npm:^2.0.0": version: 2.2.4 resolution: "@azure/core-http@npm:2.2.4" @@ -415,6 +467,15 @@ __metadata: languageName: node linkType: hard +"@azure/core-paging@npm:^1.2.0": + version: 1.3.0 + resolution: "@azure/core-paging@npm:1.3.0" + dependencies: + tslib: ^2.2.0 + checksum: 3fbf3d6474e2346f7c3ea8344a41bf41e053789efbbd29df78365e9c9ca66e143da3e5407d8c9dd5ddc82a65680185cd6f0ec851c48635776d18a6a605a98e78 + languageName: node + linkType: hard + "@azure/core-rest-pipeline@npm:^1.1.0, @azure/core-rest-pipeline@npm:^1.5.0": version: 1.5.0 resolution: "@azure/core-rest-pipeline@npm:1.5.0" @@ -432,6 +493,24 @@ __metadata: languageName: node linkType: hard +"@azure/core-rest-pipeline@npm:^1.8.0, @azure/core-rest-pipeline@npm:^1.9.1": + version: 1.9.2 + resolution: "@azure/core-rest-pipeline@npm:1.9.2" + dependencies: + "@azure/abort-controller": ^1.0.0 + "@azure/core-auth": ^1.4.0 + "@azure/core-tracing": ^1.0.1 + "@azure/core-util": ^1.0.0 + "@azure/logger": ^1.0.0 + form-data: ^4.0.0 + http-proxy-agent: ^5.0.0 + https-proxy-agent: ^5.0.0 + tslib: ^2.2.0 + uuid: ^8.3.0 + checksum: cdfdaf8bdb0778f40ecfeb8b5da0b4f8643d641b75bb5869485afd38f7c3127ab088ac2fe2980df03d28fb6715928e04560343ee6416246aebe75443c4dd2906 + languageName: node + linkType: hard + "@azure/core-tracing@npm:1.0.0-preview.13": version: 1.0.0-preview.13 resolution: "@azure/core-tracing@npm:1.0.0-preview.13" @@ -442,7 +521,7 @@ __metadata: languageName: node linkType: hard -"@azure/core-tracing@npm:^1.0.0": +"@azure/core-tracing@npm:^1.0.0, @azure/core-tracing@npm:^1.0.1": version: 1.0.1 resolution: "@azure/core-tracing@npm:1.0.1" dependencies: @@ -493,6 +572,34 @@ __metadata: languageName: node linkType: hard +"@azure/ms-rest-azure-js@npm:^2.1.0": + version: 2.1.0 + resolution: "@azure/ms-rest-azure-js@npm:2.1.0" + dependencies: + "@azure/core-auth": ^1.1.4 + "@azure/ms-rest-js": ^2.2.0 + tslib: ^1.10.0 + checksum: 0e6d6700f4ee0ad14383642b02fe86b33f4034aaff38499374ed5be59154e869ee231790d20b7557622ebcdfa9eed45dc2559391a79dee51c849a8ced0c86fed + languageName: node + linkType: hard + +"@azure/ms-rest-js@npm:^2.2.0": + version: 2.6.2 + resolution: "@azure/ms-rest-js@npm:2.6.2" + dependencies: + "@azure/core-auth": ^1.1.4 + abort-controller: ^3.0.0 + form-data: ^2.5.0 + node-fetch: ^2.6.7 + tough-cookie: ^3.0.1 + tslib: ^1.10.0 + tunnel: 0.0.6 + uuid: ^8.3.2 + xml2js: ^0.4.19 + checksum: de650dfc3928a2036923fb3bf50e0324f9f36f5d320ca6d128005b1345ad80b39ab3410073ac35fb137dec4c7d799c602b1cafdf9573b3432b6b8eee8a1e39f8 + languageName: node + linkType: hard + "@azure/msal-browser@npm:^2.26.0": version: 2.27.0 resolution: "@azure/msal-browser@npm:2.27.0" @@ -521,8 +628,8 @@ __metadata: linkType: hard "@azure/storage-blob@npm:^12.5.0": - version: 12.11.0 - resolution: "@azure/storage-blob@npm:12.11.0" + version: 12.12.0 + resolution: "@azure/storage-blob@npm:12.12.0" dependencies: "@azure/abort-controller": ^1.0.0 "@azure/core-http": ^2.0.0 @@ -532,7 +639,7 @@ __metadata: "@azure/logger": ^1.0.0 events: ^3.0.0 tslib: ^2.2.0 - checksum: f43501d66f10fb3f8e63b0e1e6e90827240e0ff41d722181c52b3abb13fa1b1b89dd985d0b6e488f6ffeee030e4afc1b6a9b6e2f18078ff6472dc494c5b199cb + checksum: bb7f6514a251f8ec337718088b8c9ba80e27b34c786b7fb380fdca19eb3735a0da917dcfcdacef27d0eb256d007a37f89f3d6b08c55405cbcc04685f2a51da3b languageName: node linkType: hard @@ -705,6 +812,17 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.18.13": + version: 7.19.3 + resolution: "@babel/generator@npm:7.19.3" + dependencies: + "@babel/types": ^7.19.3 + "@jridgewell/gen-mapping": ^0.3.2 + jsesc: ^2.5.1 + checksum: b1585e398f6c37f442a2fdac964a326b348fbc8fb99a6aaf4f72bbe993adb0ca792bc0a9c65e59930b2a2e55eb5aa3aab360ceb678d3d40692eb0cda2b7b6aa6 + languageName: node + linkType: hard + "@babel/generator@npm:^7.18.9": version: 7.18.9 resolution: "@babel/generator@npm:7.18.9" @@ -1219,6 +1337,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.19.1": + version: 7.19.1 + resolution: "@babel/helper-validator-identifier@npm:7.19.1" + checksum: 0eca5e86a729162af569b46c6c41a63e18b43dbe09fda1d2a3c8924f7d617116af39cac5e4cd5d431bb760b4dca3c0970e0c444789b1db42bcf1fa41fbad0a3a + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.16.7": version: 7.16.7 resolution: "@babel/helper-validator-option@npm:7.16.7" @@ -2936,6 +3061,17 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.18.13, @babel/types@npm:^7.19.3": + version: 7.19.3 + resolution: "@babel/types@npm:7.19.3" + dependencies: + "@babel/helper-string-parser": ^7.18.10 + "@babel/helper-validator-identifier": ^7.19.1 + to-fast-properties: ^2.0.0 + checksum: 34a5b3db3b99a1a80ec2a784c2bb0e48769a38f1526dc377a5753a3ac5e5704663c405a393117ecc7a9df9da07b01625be7c4c3fee43ae46aba23b0c40928d77 + languageName: node + linkType: hard + "@babel/types@npm:^7.18.4, @babel/types@npm:^7.18.6, @babel/types@npm:^7.18.9": version: 7.18.9 resolution: "@babel/types@npm:7.18.9" @@ -3165,7 +3301,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/catalog-model@^1.0.0, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": +"@backstage/catalog-model@^1.1.0-next.2, @backstage/catalog-model@^1.1.1, @backstage/catalog-model@workspace:^, @backstage/catalog-model@workspace:packages/catalog-model": version: 0.0.0-use.local resolution: "@backstage/catalog-model@workspace:packages/catalog-model" dependencies: @@ -3379,7 +3515,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/config@workspace:^, @backstage/config@workspace:packages/config": +"@backstage/config@^1.0.1, @backstage/config@workspace:^, @backstage/config@workspace:packages/config": version: 0.0.0-use.local resolution: "@backstage/config@workspace:packages/config" dependencies: @@ -3425,7 +3561,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-components@^0.11.0, @backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": +"@backstage/core-components@^0.11.0, @backstage/core-components@^0.11.1, @backstage/core-components@workspace:^, @backstage/core-components@workspace:packages/core-components": version: 0.0.0-use.local resolution: "@backstage/core-components@workspace:packages/core-components" dependencies: @@ -3497,7 +3633,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/core-plugin-api@^1.0.0, @backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": +"@backstage/core-plugin-api@^1.0.3, @backstage/core-plugin-api@^1.0.6, @backstage/core-plugin-api@workspace:^, @backstage/core-plugin-api@workspace:packages/core-plugin-api": version: 0.0.0-use.local resolution: "@backstage/core-plugin-api@workspace:packages/core-plugin-api" dependencies: @@ -3532,11 +3668,13 @@ __metadata: dependencies: "@backstage/cli": "workspace:^" "@backstage/cli-common": "workspace:^" + "@types/command-exists": ^1.2.0 "@types/fs-extra": ^9.0.1 "@types/inquirer": ^8.1.3 "@types/node": ^16.11.26 "@types/recursive-readdir": ^2.2.0 chalk: ^4.0.0 + command-exists: ^1.2.9 commander: ^9.1.0 fs-extra: 10.1.0 handlebars: ^4.7.3 @@ -3593,7 +3731,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/integration-react@^1.0.0, @backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": +"@backstage/integration-react@^1.1.4, @backstage/integration-react@workspace:^, @backstage/integration-react@workspace:packages/integration-react": version: 0.0.0-use.local resolution: "@backstage/integration-react@workspace:packages/integration-react" dependencies: @@ -3865,7 +4003,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-api-docs@workspace:plugins/api-docs" dependencies: - "@asyncapi/react-component": 1.0.0-next.42 + "@asyncapi/react-component": 1.0.0-next.43 "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/core-app-api": "workspace:^" @@ -3932,7 +4070,9 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-app-backend@workspace:plugins/app-backend" dependencies: + "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -3943,6 +4083,7 @@ __metadata: express: ^4.17.1 express-promise-router: ^4.1.0 fs-extra: 10.1.0 + get-port: ^6.1.2 globby: ^11.0.0 helmet: ^6.0.0 knex: ^2.0.0 @@ -3950,6 +4091,7 @@ __metadata: luxon: ^3.0.0 mock-fs: ^5.1.0 msw: ^0.47.0 + node-fetch: ^2.6.7 supertest: ^6.1.3 winston: ^3.2.1 yn: ^4.0.0 @@ -4103,6 +4245,66 @@ __metadata: languageName: unknown linkType: soft +"@backstage/plugin-azure-sites-backend@workspace:^, @backstage/plugin-azure-sites-backend@workspace:plugins/azure-sites-backend": + version: 0.0.0-use.local + resolution: "@backstage/plugin-azure-sites-backend@workspace:plugins/azure-sites-backend" + dependencies: + "@azure/arm-appservice": ^13.0.1 + "@azure/arm-resourcegraph": ^4.2.1 + "@azure/identity": ^2.1.0 + "@backstage/backend-common": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/config": "workspace:^" + "@backstage/plugin-azure-sites-common": "workspace:^" + "@types/express": ^4.17.6 + "@types/supertest": ^2.0.8 + express: ^4.17.1 + express-promise-router: ^4.1.0 + msw: ^0.47.0 + node-fetch: ^2.6.7 + winston: ^3.2.1 + yn: ^4.0.0 + languageName: unknown + linkType: soft + +"@backstage/plugin-azure-sites-common@workspace:^, @backstage/plugin-azure-sites-common@workspace:plugins/azure-sites-common": + version: 0.0.0-use.local + resolution: "@backstage/plugin-azure-sites-common@workspace:plugins/azure-sites-common" + dependencies: + "@backstage/cli": "workspace:^" + languageName: unknown + linkType: soft + +"@backstage/plugin-azure-sites@workspace:^, @backstage/plugin-azure-sites@workspace:plugins/azure-sites": + version: 0.0.0-use.local + resolution: "@backstage/plugin-azure-sites@workspace:plugins/azure-sites" + dependencies: + "@backstage/catalog-model": "workspace:^" + "@backstage/cli": "workspace:^" + "@backstage/core-app-api": "workspace:^" + "@backstage/core-components": "workspace:^" + "@backstage/core-plugin-api": "workspace:^" + "@backstage/dev-utils": "workspace:^" + "@backstage/plugin-azure-sites-common": "workspace:^" + "@backstage/plugin-catalog-react": "workspace:^" + "@backstage/test-utils": "workspace:^" + "@backstage/theme": "workspace:^" + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.9.1 + "@material-ui/lab": 4.0.0-alpha.57 + "@testing-library/jest-dom": ^5.10.1 + "@testing-library/react": ^12.1.3 + "@testing-library/user-event": ^14.0.0 + "@types/node": ^16.11.26 + cross-fetch: ^3.1.5 + luxon: ^3.0.0 + msw: ^0.47.0 + react-use: ^17.2.4 + peerDependencies: + react: ^16.13.1 || ^17.0.0 + languageName: unknown + linkType: soft + "@backstage/plugin-badges-backend@workspace:^, @backstage/plugin-badges-backend@workspace:plugins/badges-backend": version: 0.0.0-use.local resolution: "@backstage/plugin-badges-backend@workspace:plugins/badges-backend" @@ -4163,6 +4365,8 @@ __metadata: "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" + "@backstage/plugin-auth-node": "workspace:^" "@types/express": ^4.17.6 express: ^4.17.1 express-promise-router: ^4.1.0 @@ -4182,6 +4386,7 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@date-io/luxon": 1.x @@ -4251,18 +4456,22 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-aws@workspace:plugins/catalog-backend-module-aws" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" + "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" "@types/lodash": ^4.14.151 aws-sdk: ^2.840.0 aws-sdk-mock: ^5.2.1 lodash: ^4.17.21 + luxon: ^3.0.0 p-limit: ^3.0.2 uuid: ^8.0.0 winston: ^3.2.1 @@ -4275,6 +4484,7 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-azure@workspace:plugins/catalog-backend-module-azure" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -4283,9 +4493,11 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" "@types/lodash": ^4.14.151 lodash: ^4.17.21 + luxon: ^3.0.0 msw: ^0.47.0 node-fetch: ^2.6.7 uuid: ^8.0.0 @@ -4298,6 +4510,7 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-bitbucket-cloud@workspace:plugins/catalog-backend-module-bitbucket-cloud" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/cli": "workspace:^" @@ -4305,6 +4518,8 @@ __metadata: "@backstage/integration": "workspace:^" "@backstage/plugin-bitbucket-cloud-common": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" + luxon: ^3.0.0 msw: ^0.47.0 uuid: ^8.0.0 winston: ^3.2.1 @@ -4316,6 +4531,7 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-bitbucket-server@workspace:plugins/catalog-backend-module-bitbucket-server" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -4324,7 +4540,9 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@types/node-fetch": ^2.5.12 + luxon: ^3.0.0 msw: ^0.47.0 node-fetch: ^2.6.7 uuid: ^8.0.0 @@ -4359,6 +4577,7 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-gerrit@workspace:plugins/catalog-backend-module-gerrit" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -4367,8 +4586,10 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@types/fs-extra": ^9.0.1 fs-extra: 10.1.0 + luxon: ^3.0.0 msw: ^0.47.0 node-fetch: ^2.6.7 uuid: ^8.0.0 @@ -4384,6 +4605,7 @@ __metadata: "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" + "@backstage/catalog-client": "workspace:^" "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" @@ -4393,8 +4615,11 @@ __metadata: "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" "@octokit/graphql": ^5.0.0 + "@octokit/rest": ^19.0.3 "@types/lodash": ^4.14.151 + git-url-parse: ^13.0.0 lodash: ^4.17.21 + luxon: ^3.0.0 msw: ^0.47.0 node-fetch: ^2.6.7 uuid: ^8.0.0 @@ -4407,6 +4632,7 @@ __metadata: resolution: "@backstage/plugin-catalog-backend-module-gitlab@workspace:plugins/catalog-backend-module-gitlab" dependencies: "@backstage/backend-common": "workspace:^" + "@backstage/backend-plugin-api": "workspace:^" "@backstage/backend-tasks": "workspace:^" "@backstage/backend-test-utils": "workspace:^" "@backstage/catalog-model": "workspace:^" @@ -4415,10 +4641,12 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" + "@backstage/plugin-catalog-node": "workspace:^" "@backstage/types": "workspace:^" "@types/lodash": ^4.14.151 "@types/uuid": ^8.0.0 lodash: ^4.17.21 + luxon: ^3.0.0 msw: ^0.47.0 node-fetch: ^2.6.7 uuid: ^8.0.0 @@ -4544,10 +4772,11 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": +"@backstage/plugin-catalog-common@^1.0.4-next.0, @backstage/plugin-catalog-common@workspace:^, @backstage/plugin-catalog-common@workspace:plugins/catalog-common": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-common@workspace:plugins/catalog-common" dependencies: + "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/plugin-permission-common": "workspace:^" "@backstage/plugin-search-common": "workspace:^" @@ -4629,6 +4858,7 @@ __metadata: "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/integration-react": "workspace:^" + "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/test-utils": "workspace:^" "@material-ui/core": ^4.12.2 @@ -4665,11 +4895,12 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/plugin-catalog-common": "workspace:^" "@backstage/types": "workspace:^" languageName: unknown linkType: soft -"@backstage/plugin-catalog-react@^1.0.0, @backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": +"@backstage/plugin-catalog-react@^1.1.2-next.2, @backstage/plugin-catalog-react@^1.1.4, @backstage/plugin-catalog-react@workspace:^, @backstage/plugin-catalog-react@workspace:plugins/catalog-react": version: 0.0.0-use.local resolution: "@backstage/plugin-catalog-react@workspace:plugins/catalog-react" dependencies: @@ -5128,6 +5359,7 @@ __metadata: "@backstage/core-components": "workspace:^" "@backstage/core-plugin-api": "workspace:^" "@backstage/dev-utils": "workspace:^" + "@backstage/plugin-catalog": "workspace:^" "@backstage/plugin-catalog-react": "workspace:^" "@backstage/plugin-explore-react": "workspace:^" "@backstage/test-utils": "workspace:^" @@ -5399,7 +5631,7 @@ __metadata: "@types/node": "*" "@types/react": ^16.13.1 || ^17.0.0 cross-fetch: ^3.1.5 - luxon: ^2.4.0 + luxon: ^3.0.0 msw: ^0.47.0 octokit: ^2.0.4 react-use: ^17.4.0 @@ -5431,7 +5663,7 @@ __metadata: "@types/node": ^16.11.26 "@types/react": ^16.13.1 || ^17.0.0 cross-fetch: ^3.1.5 - moment: ^2.29.1 + luxon: ^3.0.0 msw: ^0.47.0 react-use: ^17.2.4 peerDependencies: @@ -5558,7 +5790,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/plugin-home@^0.4.19, @backstage/plugin-home@workspace:^, @backstage/plugin-home@workspace:plugins/home": +"@backstage/plugin-home@^0.4.25, @backstage/plugin-home@workspace:^, @backstage/plugin-home@workspace:plugins/home": version: 0.0.0-use.local resolution: "@backstage/plugin-home@workspace:plugins/home" dependencies: @@ -6067,6 +6299,7 @@ __metadata: "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" "@backstage/errors": "workspace:^" + "@backstage/types": "workspace:^" cross-fetch: ^3.1.5 msw: ^0.47.0 uuid: ^8.0.0 @@ -6092,6 +6325,7 @@ __metadata: msw: ^0.47.0 supertest: ^6.1.3 zod: ^3.11.6 + zod-to-json-schema: ^3.18.1 languageName: unknown linkType: soft @@ -6142,6 +6376,7 @@ __metadata: uuid: ^8.2.0 winston: ^3.2.1 yn: ^4.0.0 + zod: ^3.11.6 languageName: unknown linkType: soft @@ -6447,7 +6682,11 @@ __metadata: "@material-ui/lab": 4.0.0-alpha.57 "@react-hookz/web": ^15.0.0 "@rjsf/core": ^3.2.1 + "@rjsf/core-v5": "npm:@rjsf/core@^5.0.0-beta.10" "@rjsf/material-ui": ^3.2.1 + "@rjsf/material-ui-v5": "npm:@rjsf/material-ui@^5.0.0-beta.10" + "@rjsf/utils": ^5.0.0-beta.10 + "@rjsf/validator-ajv8": ^5.0.0-beta.10 "@testing-library/jest-dom": ^5.10.1 "@testing-library/react": ^12.1.3 "@testing-library/react-hooks": ^8.0.0 @@ -6729,7 +6968,7 @@ __metadata: msw: ^0.47.0 node-fetch: ^2.6.7 supertest: ^6.2.4 - winston: ^3.8.1 + winston: ^3.2.1 yn: ^5.0.0 languageName: unknown linkType: soft @@ -6942,6 +7181,7 @@ __metadata: "@types/node": ^16.11.26 cross-fetch: ^3.1.5 msw: ^0.47.0 + qs: ^6.9.4 react-use: ^17.2.4 peerDependencies: "@types/react": ^16.13.1 || ^17.0.0 @@ -7327,7 +7567,7 @@ __metadata: node-fetch: ^2.6.7 p-limit: ^3.1.0 supertest: ^6.1.6 - winston: ^3.7.2 + winston: ^3.2.1 yn: ^5.0.0 languageName: unknown linkType: soft @@ -7434,7 +7674,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/theme@^0.2.6, @backstage/theme@^0.2.7, @backstage/theme@^0.2.9, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": +"@backstage/theme@^0.2.16, @backstage/theme@^0.2.16-next.1, @backstage/theme@workspace:^, @backstage/theme@workspace:packages/theme": version: 0.0.0-use.local resolution: "@backstage/theme@workspace:packages/theme" dependencies: @@ -7443,7 +7683,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/types@workspace:^, @backstage/types@workspace:packages/types": +"@backstage/types@^1.0.0, @backstage/types@workspace:^, @backstage/types@workspace:packages/types": version: 0.0.0-use.local resolution: "@backstage/types@workspace:packages/types" dependencies: @@ -7453,7 +7693,7 @@ __metadata: languageName: unknown linkType: soft -"@backstage/version-bridge@workspace:^, @backstage/version-bridge@workspace:packages/version-bridge": +"@backstage/version-bridge@^1.0.1, @backstage/version-bridge@workspace:^, @backstage/version-bridge@workspace:packages/version-bridge": version: 0.0.0-use.local resolution: "@backstage/version-bridge@workspace:packages/version-bridge" dependencies: @@ -7724,8 +7964,8 @@ __metadata: linkType: hard "@codemirror/autocomplete@npm:^6.0.0": - version: 6.0.2 - resolution: "@codemirror/autocomplete@npm:6.0.2" + version: 6.3.0 + resolution: "@codemirror/autocomplete@npm:6.3.0" dependencies: "@codemirror/language": ^6.0.0 "@codemirror/state": ^6.0.0 @@ -7736,19 +7976,19 @@ __metadata: "@codemirror/state": ^6.0.0 "@codemirror/view": ^6.0.0 "@lezer/common": ^1.0.0 - checksum: f843572333ae8d0ac772952d3f261e53ebed16a4914ec7089d0de5496089daa24f45c3c4609e1817328a081447bbe0bc40aa82d8c42f29071ad6179020a59dac + checksum: 903b24732230ce3713e5be2bea615ef11f42b6253859274b37206a7158b8f7aed1700c9013d9330665c16d6f9576fd71efb686b4def4434e29b5c1e4e15520dc languageName: node linkType: hard -"@codemirror/commands@npm:^6.0.0": - version: 6.0.0 - resolution: "@codemirror/commands@npm:6.0.0" +"@codemirror/commands@npm:^6.0.0, @codemirror/commands@npm:^6.1.0": + version: 6.1.0 + resolution: "@codemirror/commands@npm:6.1.0" dependencies: "@codemirror/language": ^6.0.0 "@codemirror/state": ^6.0.0 "@codemirror/view": ^6.0.0 "@lezer/common": ^1.0.0 - checksum: 5b3bac420635316f9fb324e38b76a281a4d1425f80e6a5e66a34cf1508414e5d182f494e3c30936b27f86aa67403c1af4137bca5df0f0e53b2a4baa49a25caf2 + checksum: 0b95ed5b657590adda3171ef8cb1b662078ed1c02109b414bbd71503ae8cac3d5d56a56a159020178a4e8f1e7168bd3c5f5782eb2d90a6e25f067fc8c39dbc87 languageName: node linkType: hard @@ -7787,43 +8027,43 @@ __metadata: linkType: hard "@codemirror/search@npm:^6.0.0": - version: 6.0.0 - resolution: "@codemirror/search@npm:6.0.0" + version: 6.2.1 + resolution: "@codemirror/search@npm:6.2.1" dependencies: "@codemirror/state": ^6.0.0 "@codemirror/view": ^6.0.0 crelt: ^1.0.5 - checksum: e40b55f1ff1287a8b5b321f633bff638c9d4a411ada524b36b275344a13f9f4a66b7c5176d8a142a6a7761d1591d18dc535c88897f30b9885508a2890162fc72 + checksum: fdd352c6df6972863ea7e07a0dbad8872d4bd7e3ada01417d562a2cde4cf2b4fdfde47ed801c66e18342bb695c00b1cecd9918bd2e1dab74eaedab7b2dde5feb languageName: node linkType: hard -"@codemirror/state@npm:^6.0.0": - version: 6.0.1 - resolution: "@codemirror/state@npm:6.0.1" - checksum: fcb5aa5e1ce455ed1261616beb6cdb9e855f9cf11618a1ef7f61142622a0aba4551605356d381d2fa96f021f932b71323808369bf8edadfa69d745252b7e6ccd +"@codemirror/state@npm:^6.0.0, @codemirror/state@npm:^6.1.1": + version: 6.1.2 + resolution: "@codemirror/state@npm:6.1.2" + checksum: 7eda4b3eb6a777fecaa5a62e95a162aa163ccff01913d21a88d1157247390d9b57e6bcf54738be0b98aff8ba3a6ec3cb7aeb3b9714b019cb6c05242da9d9fdfe languageName: node linkType: hard "@codemirror/theme-one-dark@npm:^6.0.0": - version: 6.0.0 - resolution: "@codemirror/theme-one-dark@npm:6.0.0" + version: 6.1.0 + resolution: "@codemirror/theme-one-dark@npm:6.1.0" dependencies: "@codemirror/language": ^6.0.0 "@codemirror/state": ^6.0.0 "@codemirror/view": ^6.0.0 "@lezer/highlight": ^1.0.0 - checksum: 916f23e3ce0e003224e4bcee209ebcae57eb620c942adb180d7e0a1a0f2091bb042723d07cbd855c7016cb86fae66aa374de115001dcbe3d033f86c1624c3f9b + checksum: 7454742006f0ea23ed36b1c8e232d55bd1cdf6cd3c9b5ad7f7212a4b5fe7907eecaeb1d908fed2022c2ca5d667176c93e9f071c939d54449cfe740dc028a16d8 languageName: node linkType: hard "@codemirror/view@npm:^6.0.0": - version: 6.2.2 - resolution: "@codemirror/view@npm:6.2.2" + version: 6.3.0 + resolution: "@codemirror/view@npm:6.3.0" dependencies: "@codemirror/state": ^6.0.0 style-mod: ^4.0.0 w3c-keyname: ^2.2.4 - checksum: 6983c51362367d3885961fa233d302e75dfb103cd83ec78e6a044c2420e61466b5e94cfb73a9e840b04765577e8bbb269eb1ad45fa21abd28efda4ae780791db + checksum: 8fe39a1c47799b9bca8b815d7fd5c33bcde06cc8b98c26c87729ccca6d5afbc005671c8b00d8678209bcfc079f6894f4e61f26618d8b8133a79befa0828c789f languageName: node linkType: hard @@ -8112,23 +8352,23 @@ __metadata: linkType: hard "@google-cloud/container@npm:^4.0.0": - version: 4.1.3 - resolution: "@google-cloud/container@npm:4.1.3" + version: 4.3.0 + resolution: "@google-cloud/container@npm:4.3.0" dependencies: google-gax: ^3.3.0 - checksum: d9554ed344e065ec16b9d281eeecd2796bf5c90149358e724596c6e2bc97c6b9ea16a9dbcf4805e29003e4bcaf3aaa85c70609b7b9b1db0fbae6c9018e55b6d8 + checksum: cc1dc2ca7f6b47bac0d374013e78b05372f25dce7a3f84d799899af274d26fa22f23d2554a66ca1db528c3bbbec1cfbac86dfa9fc72cc78f175bcb737900b3ed languageName: node linkType: hard "@google-cloud/firestore@npm:^6.0.0": - version: 6.2.0 - resolution: "@google-cloud/firestore@npm:6.2.0" + version: 6.4.0 + resolution: "@google-cloud/firestore@npm:6.4.0" dependencies: fast-deep-equal: ^3.1.1 functional-red-black-tree: ^1.0.1 google-gax: ^3.5.1 protobufjs: ^7.0.0 - checksum: fce0d729d63b5722df0dcedb78c8b494b2876de128128de8c8dc7d9add1ff7e00a8df1d04e7e2210a71331e623877e67149593341f714d2b0f92b6fef73dfcbd + checksum: 7503e56f85c20aad4fa01b5de0b97e5108fe44c87db3423cb53bcdb083e053c6458c7a690820dc7db9ead384ce28da66142c1781d8cc406d4e7056374b29d23d languageName: node linkType: hard @@ -8157,14 +8397,13 @@ __metadata: linkType: hard "@google-cloud/storage@npm:^6.0.0": - version: 6.5.0 - resolution: "@google-cloud/storage@npm:6.5.0" + version: 6.5.3 + resolution: "@google-cloud/storage@npm:6.5.3" dependencies: "@google-cloud/paginator": ^3.0.7 "@google-cloud/projectify": ^3.0.0 "@google-cloud/promisify": ^3.0.0 abort-controller: ^3.0.0 - arrify: ^2.0.0 async-retry: ^1.3.3 compressible: ^2.0.12 duplexify: ^4.0.0 @@ -8178,7 +8417,7 @@ __metadata: retry-request: ^5.0.0 teeny-request: ^8.0.0 uuid: ^8.0.0 - checksum: b97dc19814e4154ef0920202e79feaf14c3d7de2ea9aa295bc2e2784b3b0ba4e7feabdd4b499d0f2e996accc4fa965b974174bdb60d5f51408967a268bdc5b9f + checksum: 9b88a5bff4e8401dbd18223fe570882df632cbb0e515e7f3eca33dbea16568168b2c0397b02ad8030e6d114b6d73109283d17a49762f1833f077424bf90a676d languageName: node linkType: hard @@ -8216,11 +8455,14 @@ __metadata: linkType: hard "@graphql-codegen/cli@npm:^2.3.1": - version: 2.12.1 - resolution: "@graphql-codegen/cli@npm:2.12.1" + version: 2.13.7 + resolution: "@graphql-codegen/cli@npm:2.13.7" dependencies: + "@babel/generator": ^7.18.13 + "@babel/template": ^7.18.10 + "@babel/types": ^7.18.13 "@graphql-codegen/core": 2.6.2 - "@graphql-codegen/plugin-helpers": ^2.7.1 + "@graphql-codegen/plugin-helpers": ^2.6.2 "@graphql-tools/apollo-engine-loader": ^7.3.6 "@graphql-tools/code-file-loader": ^7.3.1 "@graphql-tools/git-loader": ^7.2.1 @@ -8231,21 +8473,22 @@ __metadata: "@graphql-tools/prisma-loader": ^7.2.7 "@graphql-tools/url-loader": ^7.13.2 "@graphql-tools/utils": ^8.9.0 - "@whatwg-node/fetch": ^0.4.0 + "@whatwg-node/fetch": ^0.3.0 ansi-escapes: ^4.3.1 chalk: ^4.1.0 chokidar: ^3.5.2 cosmiconfig: ^7.0.0 - cosmiconfig-typescript-loader: ^4.0.0 + cosmiconfig-typescript-loader: 4.1.1 debounce: ^1.2.0 detect-indent: ^6.0.0 - graphql-config: ^4.3.5 + graphql-config: 4.3.6 inquirer: ^8.0.0 is-glob: ^4.0.1 json-to-pretty-yaml: ^1.2.2 listr2: ^4.0.5 log-symbols: ^4.0.0 mkdirp: ^1.0.4 + shell-quote: ^1.7.3 string-env-interpolation: ^1.0.1 ts-log: ^2.2.3 tslib: ^2.4.0 @@ -8258,7 +8501,7 @@ __metadata: graphql-code-generator: cjs/bin.js graphql-codegen: cjs/bin.js graphql-codegen-esm: esm/bin.js - checksum: c3e3772f1bd7d6a35ecc42a0fcb96d7e417e184170254ef89286f029a31eba688465179d572897f52197199757edf16e5a7442e4e7fb2612cd79530ea33572a3 + checksum: c025e311c072c1d992451f27625c863eef1236e5f01e2aeb6f00e67a60e99c7812a13481481a8fc6ea2967fffd77433d582f10365244d677608fdc7aa13bbe29 languageName: node linkType: hard @@ -8277,18 +8520,18 @@ __metadata: linkType: hard "@graphql-codegen/graphql-modules-preset@npm:^2.3.2": - version: 2.5.1 - resolution: "@graphql-codegen/graphql-modules-preset@npm:2.5.1" + version: 2.5.3 + resolution: "@graphql-codegen/graphql-modules-preset@npm:2.5.3" dependencies: "@graphql-codegen/plugin-helpers": ^2.6.2 - "@graphql-codegen/visitor-plugin-common": 2.12.1 + "@graphql-codegen/visitor-plugin-common": 2.12.2 "@graphql-tools/utils": ^8.8.0 change-case-all: 1.0.14 parse-filepath: ^1.0.2 tslib: ~2.4.0 peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: 185bd63081cdeba9ee332cd22fb1f640853f0f7bf60a70d4af864047465641b7ed79287413840202e317726ffad5bdd61382a4578ba88efd0b46a2d9b3acd32d + checksum: 3808a92ccb27e42f667f21929adfb7d13cdfaea20d716c63b699016fbf1b7c1b704a9fd7f1d670bf36e64588403a843d6cbba7190cd0464e03da3f485a5486fe languageName: node linkType: hard @@ -8308,22 +8551,6 @@ __metadata: languageName: node linkType: hard -"@graphql-codegen/plugin-helpers@npm:^2.7.1": - version: 2.7.1 - resolution: "@graphql-codegen/plugin-helpers@npm:2.7.1" - dependencies: - "@graphql-tools/utils": ^8.8.0 - change-case-all: 1.0.14 - common-tags: 1.8.2 - import-from: 4.0.0 - lodash: ~4.17.0 - tslib: ~2.4.0 - peerDependencies: - graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: fffb801ccccee36d729c134caa79cc5eb6a1b3717253646cd1759e2dd0e754bccb6b0b6b5341a649fd18794f8b0b970776b71907d5a7b15048521ea9eb283180 - languageName: node - linkType: hard - "@graphql-codegen/schema-ast@npm:^2.5.1": version: 2.5.1 resolution: "@graphql-codegen/schema-ast@npm:2.5.1" @@ -8338,39 +8565,39 @@ __metadata: linkType: hard "@graphql-codegen/typescript-resolvers@npm:^2.4.3": - version: 2.7.3 - resolution: "@graphql-codegen/typescript-resolvers@npm:2.7.3" + version: 2.7.4 + resolution: "@graphql-codegen/typescript-resolvers@npm:2.7.4" dependencies: "@graphql-codegen/plugin-helpers": ^2.6.2 - "@graphql-codegen/typescript": ^2.7.3 - "@graphql-codegen/visitor-plugin-common": 2.12.1 + "@graphql-codegen/typescript": ^2.7.4 + "@graphql-codegen/visitor-plugin-common": 2.12.2 "@graphql-tools/utils": ^8.8.0 auto-bind: ~4.0.0 tslib: ~2.4.0 peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: 28672b86e62ad7d0bcf244eebce7a4ee3b4dcd0055570d872a135167880030f27434c512e6bcd9d9144e5a6e4cd757268677dd61c7fb43aea2981290a5de9b2e + checksum: e54e96cbcc78ae8c8311b781cd33f4bacec15b34fff831870bae1f482d59fd55790f2c1f92c7b7f251c2b13aaad25e21fa7e7904fc67f0ed5c13abaa47b20c27 languageName: node linkType: hard -"@graphql-codegen/typescript@npm:^2.4.2, @graphql-codegen/typescript@npm:^2.7.3": - version: 2.7.3 - resolution: "@graphql-codegen/typescript@npm:2.7.3" +"@graphql-codegen/typescript@npm:^2.4.2, @graphql-codegen/typescript@npm:^2.7.4": + version: 2.7.4 + resolution: "@graphql-codegen/typescript@npm:2.7.4" dependencies: "@graphql-codegen/plugin-helpers": ^2.6.2 "@graphql-codegen/schema-ast": ^2.5.1 - "@graphql-codegen/visitor-plugin-common": 2.12.1 + "@graphql-codegen/visitor-plugin-common": 2.12.2 auto-bind: ~4.0.0 tslib: ~2.4.0 peerDependencies: graphql: ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: fa08ad7a379cfc05cf80d71509d1f8154919b8a0e89e8bf2c95a41856e0b42e3788b9225faf084a94cf900dab8893e42aa296bdb4725d5d20ce00c9e2e0bd707 + checksum: 91ce69d19eead417d4ba07a59f4be19fd50a28c4bd3800f8cd69efa7e2afb05cea6e15b8d58086b67c366970a6c58f0c5179a46a9f07c7f135f642a9d6af3583 languageName: node linkType: hard -"@graphql-codegen/visitor-plugin-common@npm:2.12.1": - version: 2.12.1 - resolution: "@graphql-codegen/visitor-plugin-common@npm:2.12.1" +"@graphql-codegen/visitor-plugin-common@npm:2.12.2": + version: 2.12.2 + resolution: "@graphql-codegen/visitor-plugin-common@npm:2.12.2" dependencies: "@graphql-codegen/plugin-helpers": ^2.6.2 "@graphql-tools/optimize": ^1.3.0 @@ -8384,7 +8611,7 @@ __metadata: tslib: ~2.4.0 peerDependencies: graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: 846d1c698b12863a25d36da761ff7c1904405e6a8ead450f95658db407bc007582eb9328f4ba57fe33129ff3e6ebce2b55347125c0bf980370480ffd47299d2e + checksum: 325b6a2bbd297c7beff00963f4f375d2990956287d4e4976864875340728abf1bdaa9891bbd9fc2625d783689f2655befd212a03c521d3661e16dcea0e0a6de4 languageName: node linkType: hard @@ -10457,9 +10684,9 @@ __metadata: linkType: hard "@microsoft/microsoft-graph-types@npm:^2.6.0": - version: 2.24.0 - resolution: "@microsoft/microsoft-graph-types@npm:2.24.0" - checksum: 94dfcf83905d4e416b8ddc52a338d583cbdc15bdc4bad0aff2212a0692d81870c55f9a01165dd3878442d2de512de57f02c575ebb77f1d393ac87449fb8e5c51 + version: 2.25.0 + resolution: "@microsoft/microsoft-graph-types@npm:2.25.0" + checksum: b311385e0a51f827082af5a016f35b1d6cd4d861121e8767b3279ab1a17475977cddbbdcc208abdddf0f953fd6ce4634d3c302e317cf50af4cb6111385347b4f languageName: node linkType: hard @@ -10852,20 +11079,20 @@ __metadata: linkType: hard "@octokit/auth-app@npm:^4.0.0": - version: 4.0.5 - resolution: "@octokit/auth-app@npm:4.0.5" + version: 4.0.7 + resolution: "@octokit/auth-app@npm:4.0.7" dependencies: "@octokit/auth-oauth-app": ^5.0.0 "@octokit/auth-oauth-user": ^2.0.0 "@octokit/request": ^6.0.0 "@octokit/request-error": ^3.0.0 - "@octokit/types": ^7.0.0 + "@octokit/types": ^8.0.0 "@types/lru-cache": ^5.1.0 deprecation: ^2.3.1 lru-cache: ^6.0.0 universal-github-app-jwt: ^1.0.1 universal-user-agent: ^6.0.0 - checksum: 26da7c1b279be68be17572897652091257a5ef7666fa5ac4c79dd42f2717aed560b117089c44ad766b4f90a87348572356ac3571c417fef385fdf31794281023 + checksum: 880de7341f47c5a48822612f50ae453fb0990a188b29afdb2f80a1f7e703c88e068d05a4edc1aa9a8204c730b6f70cf2772fbd5da032fee4986a5266ebdda4c7 languageName: node linkType: hard @@ -11120,6 +11347,13 @@ __metadata: languageName: node linkType: hard +"@octokit/openapi-types@npm:^14.0.0": + version: 14.0.0 + resolution: "@octokit/openapi-types@npm:14.0.0" + checksum: 0a1f8f3be998cd82c5a640e9166d43fd183b33d5d36f5e1a9b81608e94d0da87c01ec46c9988f69cd26585d4e2ffc4d3ec99ee4f75e5fe997fc86dad0aa8293c + languageName: node + linkType: hard + "@octokit/openapi-types@npm:^7.3.2": version: 7.4.0 resolution: "@octokit/openapi-types@npm:7.4.0" @@ -11261,16 +11495,16 @@ __metadata: linkType: hard "@octokit/request@npm:^6.0.0": - version: 6.2.1 - resolution: "@octokit/request@npm:6.2.1" + version: 6.2.2 + resolution: "@octokit/request@npm:6.2.2" dependencies: "@octokit/endpoint": ^7.0.0 "@octokit/request-error": ^3.0.0 - "@octokit/types": ^7.0.0 + "@octokit/types": ^8.0.0 is-plain-object: ^5.0.0 node-fetch: ^2.6.7 universal-user-agent: ^6.0.0 - checksum: f0a3e878de8c2e6930da5af835d9a3750800eff9ba66af02400dc75238475a9b9c2c5473047792c0f37c2c371095a36485c0729c419873bdccb6058bb8637685 + checksum: adbeb38807c60b53d32d9b69be0c1f861c26698bc6f5f3f7e05d26972290dc4867827dd333bdd801818c347e5723efd049a2b9848c6c8bf74a2032968dede0ff languageName: node linkType: hard @@ -11343,6 +11577,15 @@ __metadata: languageName: node linkType: hard +"@octokit/types@npm:^8.0.0": + version: 8.0.0 + resolution: "@octokit/types@npm:8.0.0" + dependencies: + "@octokit/openapi-types": ^14.0.0 + checksum: 1a0197b2c4c522ac90f145e02b3f8cb048a47f71c2c6bdbf021a03db7dd30ca92a899c0186acb401337f218efe44e60d33cc1cc68715b622bb75bc1a4e79515d + languageName: node + linkType: hard + "@octokit/webhooks-methods@npm:^3.0.0": version: 3.0.0 resolution: "@octokit/webhooks-methods@npm:3.0.0" @@ -11350,22 +11593,22 @@ __metadata: languageName: node linkType: hard -"@octokit/webhooks-types@npm:6.3.6": - version: 6.3.6 - resolution: "@octokit/webhooks-types@npm:6.3.6" - checksum: 974db34258cbdb0d02f3bed202595822c1799804cbd46e3375571537e0ba879350f30a3e27b156480a0be4eaa924bbb353a578ae86cb58e495c524478f052f6b +"@octokit/webhooks-types@npm:6.5.0": + version: 6.5.0 + resolution: "@octokit/webhooks-types@npm:6.5.0" + checksum: 747e4d277061ddced1b215d9fb3c3fb279e41069d90e703b4f9df6feb723769ba899c107f62aaa2db4cc7ac988de807e4455ab3b6049d8b853164e64c723a680 languageName: node linkType: hard "@octokit/webhooks@npm:^10.0.0": - version: 10.1.5 - resolution: "@octokit/webhooks@npm:10.1.5" + version: 10.3.0 + resolution: "@octokit/webhooks@npm:10.3.0" dependencies: "@octokit/request-error": ^3.0.0 "@octokit/webhooks-methods": ^3.0.0 - "@octokit/webhooks-types": 6.3.6 + "@octokit/webhooks-types": 6.5.0 aggregate-error: ^3.1.0 - checksum: ea34d0e9431550d94cc2421b7afee56515745d8df3a61592cfeba3087ad7541c5b3bd64dba77fe0da554dc34e4e5438061cc94b80ba7e57ee65b74024929792e + checksum: 23c61cc139f1db145f74deab637efcb47f213b45ce85f107b9b9a80d96c9a566e5f763dfc9f3d75594e31ca3408c1b5ced48e735bb367886948135f3bb490210 languageName: node linkType: hard @@ -11430,6 +11673,30 @@ __metadata: languageName: node linkType: hard +"@oriflame/backstage-plugin-score-card@npm:^0.5.1": + version: 0.5.3 + resolution: "@oriflame/backstage-plugin-score-card@npm:0.5.3" + dependencies: + "@backstage/catalog-model": ^1.1.0-next.2 + "@backstage/config": ^1.0.1 + "@backstage/core-components": ^0.11.0 + "@backstage/core-plugin-api": ^1.0.3 + "@backstage/plugin-catalog-common": ^1.0.4-next.0 + "@backstage/plugin-catalog-react": ^1.1.2-next.2 + "@backstage/theme": ^0.2.16-next.1 + "@backstage/types": ^1.0.0 + "@backstage/version-bridge": ^1.0.1 + "@material-ui/core": ^4.12.2 + "@material-ui/icons": ^4.11.2 + "@material-ui/lab": ^4.0.0-alpha.57 + react-use: ^17.2.4 + peerDependencies: + react: ^16.13.1 || ^17.0.0 + react-dom: ^16.13.1 || ^17.0.0 + checksum: 2f567d8838ecd938215899ce92e5569438bad8cdc521a938864a565497d4133f41f3710516abf3e375aaba881808de68ac41b249672943bda76fa54aa0352b06 + languageName: node + linkType: hard + "@peculiar/asn1-schema@npm:^2.1.6": version: 2.2.0 resolution: "@peculiar/asn1-schema@npm:2.2.0" @@ -11464,12 +11731,12 @@ __metadata: linkType: hard "@pmmmwh/react-refresh-webpack-plugin@npm:^0.5.7": - version: 0.5.7 - resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.7" + version: 0.5.8 + resolution: "@pmmmwh/react-refresh-webpack-plugin@npm:0.5.8" dependencies: ansi-html-community: ^0.0.8 common-path-prefix: ^3.0.0 - core-js-pure: ^3.8.1 + core-js-pure: ^3.23.3 error-stack-parser: ^2.0.6 find-up: ^5.0.0 html-entities: ^2.1.0 @@ -11480,7 +11747,7 @@ __metadata: "@types/webpack": 4.x || 5.x react-refresh: ">=0.10.0 <1.0.0" sockjs-client: ^1.4.0 - type-fest: ">=0.17.0 <3.0.0" + type-fest: ">=0.17.0 <4.0.0" webpack: ">=4.43.0 <6.0.0" webpack-dev-server: 3.x || 4.x webpack-hot-middleware: 2.x @@ -11498,7 +11765,7 @@ __metadata: optional: true webpack-plugin-serve: optional: true - checksum: 3490649181878cc8808fb91f3870ef095e5a1fb9647b3ac83740df07379c9d1cf540f24bf2b09d5f26a3a8c805b2c6b9c5be7192bdb9317d0ffffa67426e9f66 + checksum: 48d8b2813dfba7d482e58a2b0161b79e3a5d608603f1a3c34d709ecc2e6e08f8b14f79934c57849d06f153eb327f18e3d8e1539f978e40ca91539c342f27b8ae languageName: node linkType: hard @@ -11598,6 +11865,21 @@ __metadata: languageName: node linkType: hard +"@rjsf/core-v5@npm:@rjsf/core@^5.0.0-beta.10": + version: 5.0.0-beta.10 + resolution: "@rjsf/core@npm:5.0.0-beta.10" + dependencies: + lodash: ^4.17.15 + lodash-es: ^4.17.15 + nanoid: ^3.3.4 + prop-types: ^15.7.2 + peerDependencies: + "@rjsf/utils": ^5.0.0-beta.1 + react: ^16.14.0 || >=17 + checksum: c3e8852c8fedc6ae23da610033e0bded1bd198cebf7f54a5cdcf3b42d22a3b3864da9c735300cadc7d756f26055a7e62408d51a5ebdb906be9b1bf85bf3712ed + languageName: node + linkType: hard + "@rjsf/core@npm:^3.2.1": version: 3.2.1 resolution: "@rjsf/core@npm:3.2.1" @@ -11617,6 +11899,19 @@ __metadata: languageName: node linkType: hard +"@rjsf/material-ui-v5@npm:@rjsf/material-ui@^5.0.0-beta.10": + version: 5.0.0-beta.10 + resolution: "@rjsf/material-ui@npm:5.0.0-beta.10" + peerDependencies: + "@material-ui/core": ^4.12.3 + "@material-ui/icons": ^4.11.2 + "@rjsf/core": ^5.0.0-beta.1 + "@rjsf/utils": ^5.0.0-beta.1 + react: ^16.14.0 || >=17 + checksum: c6e602fc331ec37524837123f42e605389c8ea1a9207ed3e9052de380b91fb90365aa962b0020197f0fb2bcbd87baac34bef7dbbb31ea4b57b6587d0fcd81a09 + languageName: node + linkType: hard + "@rjsf/material-ui@npm:^3.2.1": version: 3.2.1 resolution: "@rjsf/material-ui@npm:3.2.1" @@ -11629,15 +11924,44 @@ __metadata: languageName: node linkType: hard -"@roadiehq/backstage-plugin-buildkite@npm:^2.0.8": - version: 2.0.8 - resolution: "@roadiehq/backstage-plugin-buildkite@npm:2.0.8" +"@rjsf/utils@npm:^5.0.0-beta.10": + version: 5.0.0-beta.11 + resolution: "@rjsf/utils@npm:5.0.0-beta.11" dependencies: - "@backstage/catalog-model": ^1.0.0 - "@backstage/core-components": ^0.11.0 - "@backstage/core-plugin-api": ^1.0.0 - "@backstage/plugin-catalog-react": ^1.0.0 - "@backstage/theme": ^0.2.6 + json-schema-merge-allof: ^0.8.1 + jsonpointer: ^5.0.1 + lodash: ^4.17.15 + lodash-es: ^4.17.15 + react-is: ^18.2.0 + peerDependencies: + react: ^16.14.0 || >=17 + checksum: 4e77616023b5cf193e1631a4d5fb8d54122d9f48e30ce2fcb1b22f057e2dcf37aae174f25f1afddb664b0114e461f9d738fa282995b07e9e3eb30f7469d77145 + languageName: node + linkType: hard + +"@rjsf/validator-ajv8@npm:^5.0.0-beta.10": + version: 5.0.0-beta.11 + resolution: "@rjsf/validator-ajv8@npm:5.0.0-beta.11" + dependencies: + ajv: ^8.11.0 + ajv-formats: ^2.1.1 + lodash: ^4.17.15 + lodash-es: ^4.17.15 + peerDependencies: + "@rjsf/utils": ^5.0.0-beta.1 + checksum: e9aec2e77c69ee55fd35cc82b2c3fe0c78cf021145500bc86f7e0a5bae794163dfae198df0543ce635e22cd4235880dcbb7c73487ae0baae6b03e1bdea2955c4 + languageName: node + linkType: hard + +"@roadiehq/backstage-plugin-buildkite@npm:^2.0.8": + version: 2.0.9 + resolution: "@roadiehq/backstage-plugin-buildkite@npm:2.0.9" + dependencies: + "@backstage/catalog-model": ^1.1.1 + "@backstage/core-components": ^0.11.1 + "@backstage/core-plugin-api": ^1.0.6 + "@backstage/plugin-catalog-react": ^1.1.4 + "@backstage/theme": ^0.2.16 "@material-ui/core": ^4.12.1 "@material-ui/icons": ^4.11.2 "@material-ui/lab": 4.0.0-alpha.57 @@ -11649,20 +11973,20 @@ __metadata: react-dom: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: 589f89b53ff166ca9099ea8d2a5b6f96b87a3daf2e0cb115e94c8880639f69bc25a04cab2926cba0c99dc9bb70a159528c6f1fc10a9fba67e4fb3abe40bce0bf + checksum: 45a7f04969be1896919709311e38218f642d754cf0ae32c608b87d8965147e048d1537854278d97b5bcd777f84861bd65386ffcb5a8831d004cbdc2f5f16dbed languageName: node linkType: hard "@roadiehq/backstage-plugin-github-insights@npm:^2.0.5": - version: 2.0.5 - resolution: "@roadiehq/backstage-plugin-github-insights@npm:2.0.5" + version: 2.0.6 + resolution: "@roadiehq/backstage-plugin-github-insights@npm:2.0.6" dependencies: - "@backstage/catalog-model": ^1.0.0 - "@backstage/core-components": ^0.11.0 - "@backstage/core-plugin-api": ^1.0.0 - "@backstage/integration-react": ^1.0.0 - "@backstage/plugin-catalog-react": ^1.0.0 - "@backstage/theme": ^0.2.7 + "@backstage/catalog-model": ^1.1.1 + "@backstage/core-components": ^0.11.1 + "@backstage/core-plugin-api": ^1.0.6 + "@backstage/integration-react": ^1.1.4 + "@backstage/plugin-catalog-react": ^1.1.4 + "@backstage/theme": ^0.2.16 "@date-io/core": 2.10.7 "@material-ui/core": ^4.11.0 "@material-ui/icons": ^4.9.1 @@ -11677,19 +12001,19 @@ __metadata: react: ^16.13.1 || ^17.0.0 react-dom: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 - checksum: 37f99d0478dee6acbbafaf334a2eb1f75c251e96f8b10cead0d6f6dc203667dca51ded935ca704900251c01493b6287a66814f79245410a79ad66c843a3d4803 + checksum: ff9701f4e904c44c1031616f59837f9a3bca1448f846791e6b6c2b1eb35ae78fbfc2e7161d233a620d227fc076ba71663af6380eaede9e8cda8930fa008d92ec languageName: node linkType: hard "@roadiehq/backstage-plugin-github-pull-requests@npm:^2.2.7": - version: 2.2.7 - resolution: "@roadiehq/backstage-plugin-github-pull-requests@npm:2.2.7" + version: 2.2.8 + resolution: "@roadiehq/backstage-plugin-github-pull-requests@npm:2.2.8" dependencies: - "@backstage/catalog-model": ^1.0.0 - "@backstage/core-components": ^0.11.0 - "@backstage/core-plugin-api": ^1.0.0 - "@backstage/plugin-catalog-react": ^1.0.0 - "@backstage/plugin-home": ^0.4.19 + "@backstage/catalog-model": ^1.1.1 + "@backstage/core-components": ^0.11.1 + "@backstage/core-plugin-api": ^1.0.6 + "@backstage/plugin-catalog-react": ^1.1.4 + "@backstage/plugin-home": ^0.4.25 "@material-ui/core": ^4.11.0 "@material-ui/icons": ^4.9.1 "@material-ui/lab": ^4.0.0-alpha.60 @@ -11706,19 +12030,19 @@ __metadata: react: ^16.13.1 || ^17.0.0 react-dom: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 - checksum: 2453f72c4b853e4d6f43cb27aaf27cbce5d122fbcdc71625b1e1493486acde551798524cc84f792d986397011ab458ba9b7e56eec34cde55660ccd69ea488cb0 + checksum: d334641a6eb1a464a930ef81ff3fd418d7ac96e15ff2aa9c9542d724dceac394d505335b53c77f4ef859791c347a8a70964e36e1e9b094ab289074e2c5b70bcf languageName: node linkType: hard "@roadiehq/backstage-plugin-travis-ci@npm:^2.0.5": - version: 2.0.5 - resolution: "@roadiehq/backstage-plugin-travis-ci@npm:2.0.5" + version: 2.0.6 + resolution: "@roadiehq/backstage-plugin-travis-ci@npm:2.0.6" dependencies: - "@backstage/catalog-model": ^1.0.0 - "@backstage/core-components": ^0.11.0 - "@backstage/core-plugin-api": ^1.0.0 - "@backstage/plugin-catalog-react": ^1.0.0 - "@backstage/theme": ^0.2.9 + "@backstage/catalog-model": ^1.1.1 + "@backstage/core-components": ^0.11.1 + "@backstage/core-plugin-api": ^1.0.6 + "@backstage/plugin-catalog-react": ^1.1.4 + "@backstage/theme": ^0.2.16 "@material-ui/core": ^4.11.3 "@material-ui/icons": ^4.11.2 "@material-ui/lab": 4.0.0-alpha.57 @@ -11732,7 +12056,7 @@ __metadata: react-dom: ^16.13.1 || ^17.0.0 react-router: 6.0.0-beta.0 || ^6.3.0 react-router-dom: 6.0.0-beta.0 || ^6.3.0 - checksum: bafcef69ca1b36585dbafcf8964d897942bd026f74dad81f372aed9ee1ce496a4821570f462b62b1b8d0cb86013615ad39e103827163bde8cf32037143b05c7a + checksum: dfa6645c58640eabd60c5ea520c5e4cf9bba1001d40342bde87ad583b129a4a0c1f9ae1497aa52d3e975a76352251f716cbab927b29ae0cb63d3800129367234 languageName: node linkType: hard @@ -12181,126 +12505,126 @@ __metadata: languageName: node linkType: hard -"@swc/core-android-arm-eabi@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-android-arm-eabi@npm:1.3.2" +"@swc/core-android-arm-eabi@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-android-arm-eabi@npm:1.3.5" dependencies: "@swc/wasm": 1.2.122 conditions: os=android & cpu=arm languageName: node linkType: hard -"@swc/core-android-arm64@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-android-arm64@npm:1.3.2" +"@swc/core-android-arm64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-android-arm64@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=android & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-arm64@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-darwin-arm64@npm:1.3.2" +"@swc/core-darwin-arm64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-darwin-arm64@npm:1.3.5" conditions: os=darwin & cpu=arm64 languageName: node linkType: hard -"@swc/core-darwin-x64@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-darwin-x64@npm:1.3.2" +"@swc/core-darwin-x64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-darwin-x64@npm:1.3.5" conditions: os=darwin & cpu=x64 languageName: node linkType: hard -"@swc/core-freebsd-x64@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-freebsd-x64@npm:1.3.2" +"@swc/core-freebsd-x64@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-freebsd-x64@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=freebsd & cpu=x64 languageName: node linkType: hard -"@swc/core-linux-arm-gnueabihf@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.2" +"@swc/core-linux-arm-gnueabihf@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-arm-gnueabihf@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=linux & cpu=arm languageName: node linkType: hard -"@swc/core-linux-arm64-gnu@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-linux-arm64-gnu@npm:1.3.2" +"@swc/core-linux-arm64-gnu@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-arm64-gnu@npm:1.3.5" conditions: os=linux & cpu=arm64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-arm64-musl@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-linux-arm64-musl@npm:1.3.2" +"@swc/core-linux-arm64-musl@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-arm64-musl@npm:1.3.5" conditions: os=linux & cpu=arm64 & libc=musl languageName: node linkType: hard -"@swc/core-linux-x64-gnu@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-linux-x64-gnu@npm:1.3.2" +"@swc/core-linux-x64-gnu@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-x64-gnu@npm:1.3.5" conditions: os=linux & cpu=x64 & libc=glibc languageName: node linkType: hard -"@swc/core-linux-x64-musl@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-linux-x64-musl@npm:1.3.2" +"@swc/core-linux-x64-musl@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-linux-x64-musl@npm:1.3.5" conditions: os=linux & cpu=x64 & libc=musl languageName: node linkType: hard -"@swc/core-win32-arm64-msvc@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-win32-arm64-msvc@npm:1.3.2" +"@swc/core-win32-arm64-msvc@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-win32-arm64-msvc@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=win32 & cpu=arm64 languageName: node linkType: hard -"@swc/core-win32-ia32-msvc@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-win32-ia32-msvc@npm:1.3.2" +"@swc/core-win32-ia32-msvc@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-win32-ia32-msvc@npm:1.3.5" dependencies: "@swc/wasm": 1.2.130 conditions: os=win32 & cpu=ia32 languageName: node linkType: hard -"@swc/core-win32-x64-msvc@npm:1.3.2": - version: 1.3.2 - resolution: "@swc/core-win32-x64-msvc@npm:1.3.2" +"@swc/core-win32-x64-msvc@npm:1.3.5": + version: 1.3.5 + resolution: "@swc/core-win32-x64-msvc@npm:1.3.5" conditions: os=win32 & cpu=x64 languageName: node linkType: hard "@swc/core@npm:^1.2.239": - version: 1.3.2 - resolution: "@swc/core@npm:1.3.2" + version: 1.3.5 + resolution: "@swc/core@npm:1.3.5" dependencies: - "@swc/core-android-arm-eabi": 1.3.2 - "@swc/core-android-arm64": 1.3.2 - "@swc/core-darwin-arm64": 1.3.2 - "@swc/core-darwin-x64": 1.3.2 - "@swc/core-freebsd-x64": 1.3.2 - "@swc/core-linux-arm-gnueabihf": 1.3.2 - "@swc/core-linux-arm64-gnu": 1.3.2 - "@swc/core-linux-arm64-musl": 1.3.2 - "@swc/core-linux-x64-gnu": 1.3.2 - "@swc/core-linux-x64-musl": 1.3.2 - "@swc/core-win32-arm64-msvc": 1.3.2 - "@swc/core-win32-ia32-msvc": 1.3.2 - "@swc/core-win32-x64-msvc": 1.3.2 + "@swc/core-android-arm-eabi": 1.3.5 + "@swc/core-android-arm64": 1.3.5 + "@swc/core-darwin-arm64": 1.3.5 + "@swc/core-darwin-x64": 1.3.5 + "@swc/core-freebsd-x64": 1.3.5 + "@swc/core-linux-arm-gnueabihf": 1.3.5 + "@swc/core-linux-arm64-gnu": 1.3.5 + "@swc/core-linux-arm64-musl": 1.3.5 + "@swc/core-linux-x64-gnu": 1.3.5 + "@swc/core-linux-x64-musl": 1.3.5 + "@swc/core-win32-arm64-msvc": 1.3.5 + "@swc/core-win32-ia32-msvc": 1.3.5 + "@swc/core-win32-x64-msvc": 1.3.5 dependenciesMeta: "@swc/core-android-arm-eabi": optional: true @@ -12330,27 +12654,28 @@ __metadata: optional: true bin: swcx: run_swcx.js - checksum: c2c83d0e6b4c56d65fb3723aa0be5e777823a6efe5b12702d4d44827a6c6dd8fac3c5dec7ff45222c7b22b4084bffc846f8497d697e61a1706817977256a65dc + checksum: f0bcd0aa1d1a0b7d48ee5c416f8a384e19e4c0eed1e0be940d8893bf3981d98bf00f27d03bebf9cdedd34bf316d68c2db68e7bb7d1940d7b6062541e6e4cc738 languageName: node linkType: hard "@swc/helpers@npm:^0.4.7": - version: 0.4.11 - resolution: "@swc/helpers@npm:0.4.11" + version: 0.4.12 + resolution: "@swc/helpers@npm:0.4.12" dependencies: tslib: ^2.4.0 - checksum: 736857d524b41a8a4db81094e9b027f554004e0fa3e86325d85bdb38f7e6459ce022db079edb6c61ba0f46fe8583b3e663e95f7acbd13e51b8da6c34e45bba2e + checksum: 3f9112f37d87815b6d4270137fc78d22bb98c75138e9b0eac7cac203ec2cf2bffbf13b20a713067c292affd5e9e70a724eb245b8daf0963e7fe528b901771c28 languageName: node linkType: hard "@swc/jest@npm:^0.2.22": - version: 0.2.22 - resolution: "@swc/jest@npm:0.2.22" + version: 0.2.23 + resolution: "@swc/jest@npm:0.2.23" dependencies: "@jest/create-cache-key-function": ^27.4.2 + jsonc-parser: ^3.2.0 peerDependencies: "@swc/core": "*" - checksum: c624cfcc9325a719fc7d811536011c4bde0c63c808827b47d7bbcc57621323146cb3891d8702fd01c1bebfc54a9148692f772af4db8e839eb2c6ccd7850fa5f5 + checksum: 1c7db1f6995916ad77369311be078e9d33f2c6a586be9c87927f6a36d124dcd49c29d8c596758cd9dbf4e388ec30f41989e70e574eb59bef3fb41d3131629763 languageName: node linkType: hard @@ -12377,18 +12702,18 @@ __metadata: languageName: node linkType: hard -"@tanstack/query-core@npm:4.3.8": - version: 4.3.8 - resolution: "@tanstack/query-core@npm:4.3.8" - checksum: 25ea16db7632f5acc7c80e60b9f1410cf9df430240ec5077a1607c3ffea14db2c78db800f7bc70efafd7ce26f7216cda767824fa279049cc9c7e40ac1818f38e +"@tanstack/query-core@npm:4.12.0": + version: 4.12.0 + resolution: "@tanstack/query-core@npm:4.12.0" + checksum: 535719c2079c18bb83e459b968858c424b3404b34d662e853389067dae3b2dc61b5f92b28a93237830cac3de7d35ef61793d7ddb1df6b45bc7fe31ae7ddba246 languageName: node linkType: hard "@tanstack/react-query@npm:^4.1.3": - version: 4.3.9 - resolution: "@tanstack/react-query@npm:4.3.9" + version: 4.12.0 + resolution: "@tanstack/react-query@npm:4.12.0" dependencies: - "@tanstack/query-core": 4.3.8 + "@tanstack/query-core": 4.12.0 use-sync-external-store: ^1.2.0 peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 @@ -12399,7 +12724,7 @@ __metadata: optional: true react-native: optional: true - checksum: 1f77aadda55722519458185b4db5378d05d95bcbe9f7c4fda0ef82121a975223ede1a56729db6030984b336e533c04b1c7aa8fed344e5c52ec73a0486b65f8e7 + checksum: 7c3688735bafa58e6993e1161fd53ec9d4f065de8cb04c4ec696e7e53fe86aef333df88553c8cecad278154c66ebe18c2c7072b0a0bbdce03c7112aabca81cda languageName: node linkType: hard @@ -13056,7 +13381,17 @@ __metadata: languageName: node linkType: hard -"@types/dockerode@npm:^3.3.0, @types/dockerode@npm:^3.3.8": +"@types/dockerode@npm:^3.3.0": + version: 3.3.10 + resolution: "@types/dockerode@npm:3.3.10" + dependencies: + "@types/docker-modem": "*" + "@types/node": "*" + checksum: ef2231adebbdc1876a00fd16a51963ed2ea05a2305031467420c753f77a27a6f2a47617e2f4a42702be84c88046a5a5f6dfd471e3579f11f21166f539a3d2373 + languageName: node + linkType: hard + +"@types/dockerode@npm:^3.3.8": version: 3.3.9 resolution: "@types/dockerode@npm:3.3.9" dependencies: @@ -13134,18 +13469,7 @@ __metadata: languageName: node linkType: hard -"@types/express-serve-static-core@npm:4.17.30": - version: 4.17.30 - resolution: "@types/express-serve-static-core@npm:4.17.30" - dependencies: - "@types/node": "*" - "@types/qs": "*" - "@types/range-parser": "*" - checksum: c40d9027884ab9e97fa29d9d41d1b75a5966109312e26594cf03c61b278b5bf8e095f53589e47899b34a2e224291a44043617695c3e8bd22284f988e48582ee6 - languageName: node - linkType: hard - -"@types/express-serve-static-core@npm:^4.17.5": +"@types/express-serve-static-core@npm:4.17.31, @types/express-serve-static-core@npm:^4.17.5": version: 4.17.31 resolution: "@types/express-serve-static-core@npm:4.17.31" dependencies: @@ -13175,15 +13499,15 @@ __metadata: languageName: node linkType: hard -"@types/express@npm:*, @types/express@npm:4.17.13, @types/express@npm:^4.17.13, @types/express@npm:^4.17.6": - version: 4.17.13 - resolution: "@types/express@npm:4.17.13" +"@types/express@npm:*, @types/express@npm:4.17.14, @types/express@npm:^4.17.13, @types/express@npm:^4.17.6": + version: 4.17.14 + resolution: "@types/express@npm:4.17.14" dependencies: "@types/body-parser": "*" "@types/express-serve-static-core": ^4.17.18 "@types/qs": "*" "@types/serve-static": "*" - checksum: 12a2a0e6c4b993fc0854bec665906788aea0d8ee4392389d7a98a5de1eefdd33c9e1e40a91f3afd274011119c506f7b4126acb97fae62ae20b654974d44cba12 + checksum: 15c1af46d02de834e4a225eccaa9d85c0370fdbb3ed4e1bc2d323d24872309961542b993ae236335aeb3e278630224a6ea002078d39e651d78a3b0356b1eaa79 languageName: node linkType: hard @@ -13313,11 +13637,11 @@ __metadata: linkType: hard "@types/inquirer@npm:^8.1.3": - version: 8.2.3 - resolution: "@types/inquirer@npm:8.2.3" + version: 8.2.4 + resolution: "@types/inquirer@npm:8.2.4" dependencies: "@types/through": "*" - checksum: 2978e749fbda061b85a56331a267f6c7f40380c17bce1b0f8b0bd1d86c88820e3bd7df4985c633647652b571f7ffd6b97b929cabe8b7d61259d354229e4c6c5b + checksum: 3a46231faac1e5df4863a2d501617b3f25e9481ea8a966a9b866bafef01fcc151606d9f25cb66f90492dd0d1d7af7c675a2314f50db85a7f2aeed265d93eb412 languageName: node linkType: hard @@ -13509,11 +13833,11 @@ __metadata: linkType: hard "@types/ldapjs@npm:^2.2.0": - version: 2.2.2 - resolution: "@types/ldapjs@npm:2.2.2" + version: 2.2.4 + resolution: "@types/ldapjs@npm:2.2.4" dependencies: "@types/node": "*" - checksum: e5da993f06240251dad1e095564c722124ed08d915781fb3046f7933cde94b5d2681030ab9f48d4aa5d72b655b501bfd6f2ad4ea41d557d0e1a08d574ed058bf + checksum: 3f240809927e1292380e5977579d7edffb6e5e41b35510c7091b51da19e7387d4ebff0f254c0b145f3cf5d5485b6bb39c77335d857d092e6f4e9ce5a6682a872 languageName: node linkType: hard @@ -13525,9 +13849,9 @@ __metadata: linkType: hard "@types/lodash@npm:^4.14.151, @types/lodash@npm:^4.14.173, @types/lodash@npm:^4.14.175": - version: 4.14.184 - resolution: "@types/lodash@npm:4.14.184" - checksum: 6d9a4d67f7f9d0ec3fd21174f3dd3d00629dc1227eb469450eace53adbc1f7e2330699c28d0fe093e5f0fef0f0e763098be1f779268857213224af082b62be21 + version: 4.14.185 + resolution: "@types/lodash@npm:4.14.185" + checksum: f81d13da5ecab110ca9c5c7cc2bedc3c9802a6acf668576aecd1b8f4b134ed81d06c15f1e600fb08f05975098280a0d97d30cddfc2cb39ec1c6b56e971ca53b3 languageName: node linkType: hard @@ -13552,10 +13876,17 @@ __metadata: languageName: node linkType: hard -"@types/luxon@npm:*, @types/luxon@npm:^3.0.0": - version: 3.0.0 - resolution: "@types/luxon@npm:3.0.0" - checksum: 7738d3f4b91097a8139eca966ab53c6b40bb417b2cc2014c3bbd0aee033d6ff54af14c62046c2eb042434923b6bf796874f24b2af90e07ef422e6d841463d4b2 +"@types/luxon@npm:*": + version: 3.0.1 + resolution: "@types/luxon@npm:3.0.1" + checksum: a81444f9b474ea9b3063ab4cc68b917a2634e38b4e229f86c78c35023a32bf5e8d1044d7c229c011291662c976cb6c4cf109dc3d2077c571790a31779a554178 + languageName: node + linkType: hard + +"@types/luxon@npm:^3.0.0": + version: 3.0.2 + resolution: "@types/luxon@npm:3.0.2" + checksum: e47e199c5d70ce16e4eff54b21fdf385065f1134774109b40c36dba8b5fd29a732b763125e93863d6070dd22ba6a3f18dbdd368cf3748aae9f24bb335c5c210d languageName: node linkType: hard @@ -13904,11 +14235,11 @@ __metadata: linkType: hard "@types/puppeteer@npm:^5.4.4": - version: 5.4.6 - resolution: "@types/puppeteer@npm:5.4.6" + version: 5.4.7 + resolution: "@types/puppeteer@npm:5.4.7" dependencies: "@types/node": "*" - checksum: b969230812e95fcc5eee83151731e51b287cc934169f552034d8eea58ce9b5beb4ba09b9ce05b75d9510241a85d244f28e8b0a0a4b9f7dffcad8c37a39288260 + checksum: 491c0ca2d879b72e869f5152a9ea7de1356a66f44ef6210fdd5c5036d893b4ee2956212e2f3353c7045b9e56d1a4cea408f41a26d7cf3708774c4d6c19a7c654 languageName: node linkType: hard @@ -14311,12 +14642,12 @@ __metadata: linkType: hard "@types/tar@npm:^6.1.1": - version: 6.1.2 - resolution: "@types/tar@npm:6.1.2" + version: 6.1.3 + resolution: "@types/tar@npm:6.1.3" dependencies: "@types/node": "*" minipass: ^3.3.5 - checksum: 57e625e2db29e3b9c6d8d06774758cf6e4caa2096f4144f7c0fdb2760e1150146d2a86f0eb80b4d2e1ba0ecf07f06303775054c4f4d22bea5d51e8b54cdd0fd8 + checksum: 3a221f74adfcef8555b9c4cc951907dfd567630744ffe5211da1b44caf2a4c3b02297b73c9fb02d171e93a7a7c74fb15c1826e3f0438f0e5e8f4c790db59ddcf languageName: node linkType: hard @@ -14766,9 +15097,9 @@ __metadata: languageName: node linkType: hard -"@uiw/codemirror-extensions-basic-setup@npm:4.11.6": - version: 4.11.6 - resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.11.6" +"@uiw/codemirror-extensions-basic-setup@npm:4.12.4": + version: 4.12.4 + resolution: "@uiw/codemirror-extensions-basic-setup@npm:4.12.4" dependencies: "@codemirror/autocomplete": ^6.0.0 "@codemirror/commands": ^6.0.0 @@ -14785,26 +15116,29 @@ __metadata: "@codemirror/search": ">=6.0.0" "@codemirror/state": ">=6.0.0" "@codemirror/view": ">=6.0.0" - checksum: 706a64a098380c404499a228ba3e28f472d39b20088ed794c586005d227340e77c5320a09b9574e8ec51f7d13ca1b98816606de0e7f3c9407761493c41c9801f + checksum: 27c65e051383cedf0ea63b3c923caf10aa45dba7cec01b3a767d81d1d0114ba221665bf0d8166f8de67946806fde7348768cf4a7683a70907e9f2eef9c99dbea languageName: node linkType: hard "@uiw/react-codemirror@npm:^4.9.3": - version: 4.11.6 - resolution: "@uiw/react-codemirror@npm:4.11.6" + version: 4.12.4 + resolution: "@uiw/react-codemirror@npm:4.12.4" dependencies: "@babel/runtime": ^7.18.6 + "@codemirror/commands": ^6.1.0 + "@codemirror/state": ^6.1.1 "@codemirror/theme-one-dark": ^6.0.0 - "@uiw/codemirror-extensions-basic-setup": 4.11.6 + "@uiw/codemirror-extensions-basic-setup": 4.12.4 codemirror: ^6.0.0 peerDependencies: "@babel/runtime": ">=7.11.0" + "@codemirror/state": ">=6.0.0" "@codemirror/theme-one-dark": ">=6.0.0" "@codemirror/view": ">=6.0.0" codemirror: ">=6.0.0" react: ">=16.8.0" react-dom: ">=16.8.0" - checksum: f630400ef57ff1928962244854805acb37f259606bc21967861c9ffaf80168d7c6c5052058299e96f7694635e218c6497ef4c5f3086109b35669b7cf5e2ee297 + checksum: e7228beb95e918929e99e819c4f3b3920a16c5aca2477775c41bd16166af5bb7b5cefe240bd75b80fd9f61cde82086f93f8cd2dc1e2a4ecf02ff6d756cfd6f42 languageName: node linkType: hard @@ -14976,19 +15310,20 @@ __metadata: languageName: node linkType: hard -"@whatwg-node/fetch@npm:^0.4.0": - version: 0.4.4 - resolution: "@whatwg-node/fetch@npm:0.4.4" +"@whatwg-node/fetch@npm:^0.3.0": + version: 0.3.2 + resolution: "@whatwg-node/fetch@npm:0.3.2" dependencies: "@peculiar/webcrypto": ^1.4.0 abort-controller: ^3.0.0 busboy: ^1.6.0 + event-target-polyfill: ^0.0.3 form-data-encoder: ^1.7.1 formdata-node: ^4.3.1 node-fetch: ^2.6.7 undici: ^5.8.0 web-streams-polyfill: ^3.2.0 - checksum: f91ebad3c07f1244dec0c57b1bdc4d70e8e2b0eccd237407f649439eb091e1e8a23dfb8e13159468c727537c5ebf6f66f20f5659ca682495cd1a0752de2c2fad + checksum: d9cb1b1293694edf0d61889512e5b5a0b8b69db2cf8c4cca4acdbbe652f899742456d10954312ef96a8f7257a898d6275b50df03cc481e5a97740cb301930892 languageName: node linkType: hard @@ -15028,12 +15363,12 @@ __metadata: linkType: hard "@yarnpkg/parsers@npm:^3.0.0-rc.4": - version: 3.0.0-rc.20 - resolution: "@yarnpkg/parsers@npm:3.0.0-rc.20" + version: 3.0.0-rc.21 + resolution: "@yarnpkg/parsers@npm:3.0.0-rc.21" dependencies: js-yaml: ^3.10.0 tslib: ^2.4.0 - checksum: a0dfc2d4d824322ae779363870ad7705b42161ad5e90c992404bf5dc0454a046a6f04144303c9bb047239e480b882b4105272127f2ba080b865180276c46deda + checksum: c0741ef01089a7d452dfb75b8c24a82ddcc3e218dac22b794a06f2e50c7070378c460f63f63fb7ed0f62f634114e448e3d85a75ccd4db84e1526242ae9b4a7d5 languageName: node linkType: hard @@ -15280,7 +15615,7 @@ __metadata: languageName: node linkType: hard -"ajv@npm:^8.0.0, ajv@npm:^8.10.0, ajv@npm:^8.6.3, ajv@npm:^8.8.0": +"ajv@npm:^8.0.0, ajv@npm:^8.10.0, ajv@npm:^8.11.0, ajv@npm:^8.6.3, ajv@npm:^8.8.0": version: 8.11.0 resolution: "ajv@npm:8.11.0" dependencies: @@ -15443,18 +15778,18 @@ __metadata: languageName: node linkType: hard -"apollo-reporting-protobuf@npm:^3.3.2": - version: 3.3.2 - resolution: "apollo-reporting-protobuf@npm:3.3.2" +"apollo-reporting-protobuf@npm:^3.3.3": + version: 3.3.3 + resolution: "apollo-reporting-protobuf@npm:3.3.3" dependencies: - "@apollo/protobufjs": 1.2.4 - checksum: fcd7312991ac3510bf4b78fe67b81d9803179c019ef5741317f06c7a182b9826247e186a9074ca2baaa8cc53b00993ddf1c69e9b38a8f65ab4c9d698a51aa2a5 + "@apollo/protobufjs": 1.2.6 + checksum: 6900db30476ef2e888ecef4c291e26579eba9695dc874ca8b3d1f93064bea307689172790b9d574849e5bdb371953ea38f2caddc5abb51e1ca197197f3d56d28 languageName: node linkType: hard -"apollo-server-core@npm:^3.10.2": - version: 3.10.2 - resolution: "apollo-server-core@npm:3.10.2" +"apollo-server-core@npm:^3.10.3": + version: 3.10.3 + resolution: "apollo-server-core@npm:3.10.3" dependencies: "@apollo/utils.keyvaluecache": ^1.0.1 "@apollo/utils.logger": ^1.0.0 @@ -15465,22 +15800,22 @@ __metadata: "@graphql-tools/schema": ^8.0.0 "@josephg/resolvable": ^1.0.0 apollo-datasource: ^3.3.2 - apollo-reporting-protobuf: ^3.3.2 + apollo-reporting-protobuf: ^3.3.3 apollo-server-env: ^4.2.1 apollo-server-errors: ^3.3.1 - apollo-server-plugin-base: ^3.6.2 - apollo-server-types: ^3.6.2 + apollo-server-plugin-base: ^3.6.3 + apollo-server-types: ^3.6.3 async-retry: ^1.2.1 fast-json-stable-stringify: ^2.1.0 graphql-tag: ^2.11.0 loglevel: ^1.6.8 lru-cache: ^6.0.0 sha.js: ^2.4.11 - uuid: ^8.0.0 + uuid: ^9.0.0 whatwg-mimetype: ^3.0.0 peerDependencies: graphql: ^15.3.0 || ^16.0.0 - checksum: 14b8ac9ef442993b5ef9354d6457cfeeaa1af09c8ece6041bfbf061e7c5d9983f20971ebf6ae2c9b8b8447ad5273943ea548a139ecf06c3559d380ed5c077bcb + checksum: 1a9c3ca29c1e664970737e83d76999d43ceb938e39026a329682fcc80fa17a564a69cb7b38110b1928d3d512ae77eeeb8ed6e160043521052c0360b0b00f0ada languageName: node linkType: hard @@ -15502,64 +15837,64 @@ __metadata: languageName: node linkType: hard -"apollo-server-express@npm:^3.0.0, apollo-server-express@npm:^3.10.2": - version: 3.10.2 - resolution: "apollo-server-express@npm:3.10.2" +"apollo-server-express@npm:^3.0.0, apollo-server-express@npm:^3.10.3": + version: 3.10.3 + resolution: "apollo-server-express@npm:3.10.3" dependencies: "@types/accepts": ^1.3.5 "@types/body-parser": 1.19.2 "@types/cors": 2.8.12 - "@types/express": 4.17.13 - "@types/express-serve-static-core": 4.17.30 + "@types/express": 4.17.14 + "@types/express-serve-static-core": 4.17.31 accepts: ^1.3.5 - apollo-server-core: ^3.10.2 - apollo-server-types: ^3.6.2 + apollo-server-core: ^3.10.3 + apollo-server-types: ^3.6.3 body-parser: ^1.19.0 cors: ^2.8.5 parseurl: ^1.3.3 peerDependencies: express: ^4.17.1 graphql: ^15.3.0 || ^16.0.0 - checksum: 8e0159c00b7b2dbcb043542cf37d62bcd1dfd958dc88fa4e3dd6b273632524cde01d26ded3438e04dd2f77783f29c5075ec2b5fa78df534ee2d87478bf20d602 + checksum: c6dcfd8b61a558bd479d93d1c9cc4f436145b31e7f5e53d6c655d919c79f0b4b785788f0d16fc1d94c1929b85b034ae84a7332ce4e20603b67e0a4709e48b3a6 languageName: node linkType: hard -"apollo-server-plugin-base@npm:^3.6.2": - version: 3.6.2 - resolution: "apollo-server-plugin-base@npm:3.6.2" +"apollo-server-plugin-base@npm:^3.6.3": + version: 3.6.3 + resolution: "apollo-server-plugin-base@npm:3.6.3" dependencies: - apollo-server-types: ^3.6.2 + apollo-server-types: ^3.6.3 peerDependencies: graphql: ^15.3.0 || ^16.0.0 - checksum: 8430aa65619d96cd9e15e46b7352bca5abda3f7e4b19d3a289cf337263cdd5bc0d808d2ef110ff76358fea9cf63e62c63d0a8502f1555ee7bb8c784c64b52b6d + checksum: 50e690cf4c5047957c4546676f11d82f8c0bee2c0340573322f23188af27175ac49859d463c6123ebef500b3c32a0932e3e3f9fa8671e2d1a410d2e89c8608e3 languageName: node linkType: hard -"apollo-server-types@npm:^3.6.2": - version: 3.6.2 - resolution: "apollo-server-types@npm:3.6.2" +"apollo-server-types@npm:^3.6.3": + version: 3.6.3 + resolution: "apollo-server-types@npm:3.6.3" dependencies: "@apollo/utils.keyvaluecache": ^1.0.1 "@apollo/utils.logger": ^1.0.0 - apollo-reporting-protobuf: ^3.3.2 + apollo-reporting-protobuf: ^3.3.3 apollo-server-env: ^4.2.1 peerDependencies: graphql: ^15.3.0 || ^16.0.0 - checksum: 410d7c0e6cfcc68dbc5f0182e07008235c6ecf5ab7d01d334a2af3c4deed3921cff12e32f54206d16aad88d5213f67f5f5302762ac92a6883b778800334e2016 + checksum: ebd218e6fa8b756bb2d6954b454676907d99b8f140923b439cc1bbb200251f9df7228a9d83c5c91bb571a448cc80c2d86bc0a0eae491a4ce028e86a724cde68a languageName: node linkType: hard "apollo-server@npm:^3.0.0": - version: 3.10.2 - resolution: "apollo-server@npm:3.10.2" + version: 3.10.3 + resolution: "apollo-server@npm:3.10.3" dependencies: - "@types/express": 4.17.13 - apollo-server-core: ^3.10.2 - apollo-server-express: ^3.10.2 + "@types/express": 4.17.14 + apollo-server-core: ^3.10.3 + apollo-server-express: ^3.10.3 express: ^4.17.1 peerDependencies: graphql: ^15.3.0 || ^16.0.0 - checksum: 9f3c22ed1d4275a8bd5559b276dadab26758c3b4ae90b454d86aeccfa1a0da90d1201f627676e36ec84b8d581f767f24037d682a58615247b393930aca850324 + checksum: eec2423bbd30256683c0e7873cd1e3f616ff2a3c90cf7bba241880027f5e1374d20d13bf7ce4b09543b16753d0d7a4627f77d64de77b5b241c410f0f246bdbd7 languageName: node linkType: hard @@ -16061,8 +16396,8 @@ __metadata: linkType: hard "aws-sdk@npm:^2.1122.0, aws-sdk@npm:^2.814.0, aws-sdk@npm:^2.840.0, aws-sdk@npm:^2.948.0": - version: 2.1209.0 - resolution: "aws-sdk@npm:2.1209.0" + version: 2.1224.0 + resolution: "aws-sdk@npm:2.1224.0" dependencies: buffer: 4.9.2 events: 1.1.1 @@ -16074,7 +16409,7 @@ __metadata: util: ^0.12.4 uuid: 8.0.0 xml2js: 0.4.19 - checksum: da4cae7da28b218dfa67398e5a63acb894b1636337e2842f6e0800bad6eb5fc692d0b703dc84f67fdc9f1d59ee9f82f4ccc9d71fe8fb6a49a8292697ab77b92c + checksum: 990b4db8b72462432cfd75b4fe2c71ff8b24abed2b0d6b6441d51211167afd00b002ff91d7c3ff02379b34f6e5dcae72bdf9e31f55a9c105f50ad96cfc8f08da languageName: node linkType: hard @@ -18028,7 +18363,7 @@ __metadata: languageName: node linkType: hard -"commander@npm:*, commander@npm:^9.1.0, commander@npm:^9.3.0": +"commander@npm:*, commander@npm:^9.3.0": version: 9.4.0 resolution: "commander@npm:9.4.0" checksum: a322de584a6ccd1ea83c24f6a660e52d16ffbe2613fcfbb8d2cc68bc9dec637492456d754fe8bb5b039ad843ed8e04fb0b107e581a75f62cde9e1a0ab1546e09 @@ -18084,6 +18419,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^9.1.0": + version: 9.4.1 + resolution: "commander@npm:9.4.1" + checksum: bfb18e325a5bdf772763c2213d5c7d9e77144d944124e988bcd8e5e65fb6d45d5d4e86b09155d0f2556c9a59c31e428720e57968bcd050b2306e910a0bf3cf13 + languageName: node + linkType: hard + "common-ancestor-path@npm:^1.0.1": version: 1.0.1 resolution: "common-ancestor-path@npm:1.0.1" @@ -18440,13 +18782,27 @@ __metadata: languageName: node linkType: hard -"core-js-pure@npm:^3.20.2, core-js-pure@npm:^3.6.5, core-js-pure@npm:^3.8.1": +"core-js-pure@npm:^3.20.2": version: 3.21.1 resolution: "core-js-pure@npm:3.21.1" checksum: 00a5dff599b7fb0b30746a638b9d0edbdc0df24ed1580ca56be595fbe3c78c375d37fc4e1bff23627109229702c9ee8ea2587a66b8280eb33b85160aa4e401e9 languageName: node linkType: hard +"core-js-pure@npm:^3.23.3": + version: 3.25.5 + resolution: "core-js-pure@npm:3.25.5" + checksum: e48799a8ab28f00ef3db18377142ff2c578574ab2b18ebddde6cbf12823e0181a57c80e3caa6046ce2a2e439d603a252be767583ddc54248e3d1060bc5012127 + languageName: node + linkType: hard + +"core-js-pure@npm:^3.6.5": + version: 3.25.3 + resolution: "core-js-pure@npm:3.25.3" + checksum: be3097b8e2b541977a08004e433b68adb4951cc7694229e695ae407f296d370c1f360e5fd70fc6910c4427e46e48e087ac455d68cebdf4314370907d2e03eef1 + languageName: node + linkType: hard + "core-js@npm:^2.4.0, core-js@npm:^2.5.0, core-js@npm:^2.6.10": version: 2.6.12 resolution: "core-js@npm:2.6.12" @@ -18462,9 +18818,9 @@ __metadata: linkType: hard "core-js@npm:^3.6.5": - version: 3.25.2 - resolution: "core-js@npm:3.25.2" - checksum: e93c6c645d44d98973efb07750975552ad405f080f5a563a99972ff6b2c5c6ee25705f55accd363f5dccd51e9e5f56be25e2be6c14a7294da65763e0e5659c02 + version: 3.25.5 + resolution: "core-js@npm:3.25.5" + checksum: 208b308c49bc022f90d4349d4c99802a73c9d55053976b3c529f10014c1e37845926defad8c519f2c7f71ea0acf18d2b323ab6aaee34dc85b4c4b3ced0623f3f languageName: node linkType: hard @@ -18494,6 +18850,18 @@ __metadata: languageName: node linkType: hard +"cosmiconfig-typescript-loader@npm:4.1.1": + version: 4.1.1 + resolution: "cosmiconfig-typescript-loader@npm:4.1.1" + peerDependencies: + "@types/node": "*" + cosmiconfig: ">=7" + ts-node: ">=10" + typescript: ">=3" + checksum: a774961868f0406d0fd75e448c2e1a0ddb95de27d477fa325a37369a2cbd892cf4d639a109082cd886840dea7707ea9bed5c38a468b52de18400c4f1d495d35a + languageName: node + linkType: hard + "cosmiconfig-typescript-loader@npm:^4.0.0": version: 4.0.0 resolution: "cosmiconfig-typescript-loader@npm:4.0.0" @@ -21330,8 +21698,8 @@ __metadata: linkType: hard "eslint-plugin-react@npm:^7.28.0": - version: 7.31.8 - resolution: "eslint-plugin-react@npm:7.31.8" + version: 7.31.10 + resolution: "eslint-plugin-react@npm:7.31.10" dependencies: array-includes: ^3.1.5 array.prototype.flatmap: ^1.3.0 @@ -21349,7 +21717,7 @@ __metadata: string.prototype.matchall: ^4.0.7 peerDependencies: eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 - checksum: 0683e2a624a4df6f08264a3f6bc614a81e8f961c83173bdf2d8d3523f84ed5d234cddc976dbc6815913e007c5984df742ba61be0c0592b27c3daabe0f68165a3 + checksum: f013669c296483559a760648fa06425f161b1aff93c668f14c4561c933d22a7836b745b88a795c53cab929c71513d5fd1f2ffdddff915709f01b77ac25f5b71b languageName: node linkType: hard @@ -21669,6 +22037,7 @@ __metadata: "@backstage/plugin-apache-airflow": "workspace:^" "@backstage/plugin-api-docs": "workspace:^" "@backstage/plugin-azure-devops": "workspace:^" + "@backstage/plugin-azure-sites": "workspace:^" "@backstage/plugin-badges": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" "@backstage/plugin-catalog-graph": "workspace:^" @@ -21718,7 +22087,7 @@ __metadata: "@material-ui/icons": ^4.9.1 "@material-ui/lab": 4.0.0-alpha.57 "@octokit/rest": ^19.0.3 - "@rjsf/core": ^3.2.1 + "@oriflame/backstage-plugin-score-card": ^0.5.1 "@roadiehq/backstage-plugin-buildkite": ^2.0.8 "@roadiehq/backstage-plugin-github-insights": ^2.0.5 "@roadiehq/backstage-plugin-github-pull-requests": ^2.2.7 @@ -21753,6 +22122,7 @@ __metadata: dependencies: "@backstage/backend-defaults": "workspace:^" "@backstage/cli": "workspace:^" + "@backstage/plugin-app-backend": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" "@backstage/plugin-scaffolder-backend": "workspace:^" languageName: unknown @@ -21773,6 +22143,7 @@ __metadata: "@backstage/plugin-auth-backend": "workspace:^" "@backstage/plugin-auth-node": "workspace:^" "@backstage/plugin-azure-devops-backend": "workspace:^" + "@backstage/plugin-azure-sites-backend": "workspace:^" "@backstage/plugin-badges-backend": "workspace:^" "@backstage/plugin-catalog-backend": "workspace:^" "@backstage/plugin-code-coverage-backend": "workspace:^" @@ -23133,6 +23504,13 @@ __metadata: languageName: node linkType: hard +"get-port@npm:^6.1.2": + version: 6.1.2 + resolution: "get-port@npm:6.1.2" + checksum: e3c3d591492a11393455ef220f24c812a28f7da56ec3e4a2512d931a1f196d42850b50ac6138349a44622eda6dc3c0ccd8495cd91376d968e2d9e6f6f849e0a9 + languageName: node + linkType: hard + "get-stdin@npm:^8.0.0": version: 8.0.0 resolution: "get-stdin@npm:8.0.0" @@ -23411,8 +23789,8 @@ __metadata: linkType: hard "google-auth-library@npm:^8.0.0": - version: 8.5.1 - resolution: "google-auth-library@npm:8.5.1" + version: 8.5.2 + resolution: "google-auth-library@npm:8.5.2" dependencies: arrify: ^2.0.0 base64-js: ^1.3.0 @@ -23423,7 +23801,7 @@ __metadata: gtoken: ^6.1.0 jws: ^4.0.0 lru-cache: ^6.0.0 - checksum: 0d80d7e136e672055e44fdb44f9e13843a554cc531a01deb1b0da6b78427778785faa7377ed85d840208bb61878cd7d69902172886476bd49ea67e645729570a + checksum: 5ab2904f5da3c119a7c241a1d5a11640468a7da58dfcec8a9cad181cc2723e6662b3c65997906069852d9fa066ba3e4b7f14e11cbb80d541e320b66ead6777dc languageName: node linkType: hard @@ -23577,9 +23955,9 @@ __metadata: languageName: node linkType: hard -"graphql-config@npm:^4.3.5": - version: 4.3.5 - resolution: "graphql-config@npm:4.3.5" +"graphql-config@npm:4.3.6": + version: 4.3.6 + resolution: "graphql-config@npm:4.3.6" dependencies: "@graphql-tools/graphql-file-loader": ^7.3.7 "@graphql-tools/json-file-loader": ^7.3.7 @@ -23596,7 +23974,7 @@ __metadata: tslib: ^2.4.0 peerDependencies: graphql: ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 - checksum: 0b8ca47263b12b228db72d9abb663ab8e02e01c677d9ffe08f7be651617fdc93ff2ff6c342465544df43dc4d507293a66b8fc2b8237c2628ebdbc5c74311b1d8 + checksum: 528f69c2c3620b51fe3d609e65d13861bdc11ccfdfcb75d476f758dc6e3047c78117a260f69b5e7efa5f0b66da1980c352a8b4053532ffb3018377f784331e21 languageName: node linkType: hard @@ -23671,11 +24049,11 @@ __metadata: linkType: hard "graphql-ws@npm:^5.4.1, graphql-ws@npm:^5.9.0": - version: 5.10.1 - resolution: "graphql-ws@npm:5.10.1" + version: 5.11.2 + resolution: "graphql-ws@npm:5.11.2" peerDependencies: graphql: ">=0.11 <=16" - checksum: ad8eb8a6823a32a7bd9bc6ec73a0b244f503e50318ef5db1312f1d09c5f0b0e4505ad75b08f06e4c40c6935cf5170444b19347b7b83222b4e1af01401418cb91 + checksum: 2c94b06c1919217dc15a0556474673de7aabcc7179a2982a87ded51856c105e4f4ee6d54a6c135a0a7f55d85a5997a6a15cff514959258885814adec6a61ff00 languageName: node linkType: hard @@ -24857,6 +25235,13 @@ __metadata: languageName: node linkType: hard +"ip-regex@npm:^2.1.0": + version: 2.1.0 + resolution: "ip-regex@npm:2.1.0" + checksum: 331d95052aa53ce245745ea0fc3a6a1e2e3c8d6da65fa8ea52bf73768c1b22a9ac50629d1d2b08c04e7b3ac4c21b536693c149ce2c2615ee4796030e5b3e3cba + languageName: node + linkType: hard + "ip@npm:^1.1.5": version: 1.1.5 resolution: "ip@npm:1.1.5" @@ -25712,8 +26097,8 @@ __metadata: linkType: hard "isomorphic-git@npm:^1.8.0": - version: 1.20.0 - resolution: "isomorphic-git@npm:1.20.0" + version: 1.21.0 + resolution: "isomorphic-git@npm:1.21.0" dependencies: async-lock: ^1.1.0 clean-git-ref: ^2.0.1 @@ -25728,7 +26113,7 @@ __metadata: simple-get: ^4.0.1 bin: isogit: cli.cjs - checksum: 4f5af69b02795d656c171c2347186ac0b04269db23ab4ec3fb0204cda5cb89cceaacae8eb44ee3e1870fcbc930518c63a6e82a9fc7428719e08be5c3d6d88396 + checksum: afbdf98000c586fdcb96f84c054d6ada392b0d2422a7b7baad89e5f5881a79426f8555bdadeebe25202be18bb8eaee3881f8c04b6a289d04b916790bdd5dd5df languageName: node linkType: hard @@ -26932,6 +27317,13 @@ __metadata: languageName: node linkType: hard +"jsonc-parser@npm:^3.2.0": + version: 3.2.0 + resolution: "jsonc-parser@npm:3.2.0" + checksum: 946dd9a5f326b745aa326d48a7257e3f4a4b62c5e98ec8e49fa2bdd8d96cef7e6febf1399f5c7016114fd1f68a1c62c6138826d5d90bc650448e3cf0951c53c7 + languageName: node + linkType: hard + "jsonfile@npm:^4.0.0": version: 4.0.0 resolution: "jsonfile@npm:4.0.0" @@ -26985,10 +27377,10 @@ __metadata: languageName: node linkType: hard -"jsonpointer@npm:^5.0.0": - version: 5.0.0 - resolution: "jsonpointer@npm:5.0.0" - checksum: c7ec0b6bb596b81de687bc12945586bbcdc80dfb54919656d2690d76334f796a936270067ee9f1b5bbc2d9ecc551afb366ac35e6685aa61f07b5b68d1e5e857d +"jsonpointer@npm:^5.0.0, jsonpointer@npm:^5.0.1": + version: 5.0.1 + resolution: "jsonpointer@npm:5.0.1" + checksum: 0b40f712900ad0c846681ea2db23b6684b9d5eedf55807b4708c656f5894b63507d0e28ae10aa1bddbea551241035afe62b6df0800fc94c2e2806a7f3adecd7c languageName: node linkType: hard @@ -27423,8 +27815,8 @@ __metadata: linkType: hard "ldapjs@npm:^2.2.0": - version: 2.3.2 - resolution: "ldapjs@npm:2.3.2" + version: 2.3.3 + resolution: "ldapjs@npm:2.3.3" dependencies: abstract-logging: ^2.0.0 asn1: ^0.2.4 @@ -27434,7 +27826,7 @@ __metadata: once: ^1.4.0 vasync: ^2.2.0 verror: ^1.8.1 - checksum: 4eb7b18839d8fe1d8acc0ba5d2aa71aefcb25ecb6f4f9582299d73faf546984326590c44366f8b148d3a4276707006b1aaffffdc548b3c4ca753e720403da4a5 + checksum: 791cf22812805db3888dfc8aa8e4db8337e67387b318522c9965d40e678ecdb64950a6ea5930083590ec3377038620a3e63f90e985173ae6aca40e7f485290e0 languageName: node linkType: hard @@ -27723,7 +28115,7 @@ __metadata: languageName: node linkType: hard -"lodash-es@npm:^4.17.21": +"lodash-es@npm:^4.17.15, lodash-es@npm:^4.17.21": version: 4.17.21 resolution: "lodash-es@npm:4.17.21" checksum: 05cbffad6e2adbb331a4e16fbd826e7faee403a1a04873b82b42c0f22090f280839f85b95393f487c1303c8a3d2a010048bf06151a6cbe03eee4d388fb0a12d2 @@ -28133,17 +28525,10 @@ __metadata: languageName: node linkType: hard -"luxon@npm:^2.4.0": - version: 2.5.0 - resolution: "luxon@npm:2.5.0" - checksum: 2fccce6bbdfc8f13c5a8c148ff045ab3b10f4f80cac28dd92575588fffce9b2d7197096d7fedcc61a6245b59f4233507797f530e63f22b9ae4c425dff2909ae3 - languageName: node - linkType: hard - "luxon@npm:^3.0.0": - version: 3.0.1 - resolution: "luxon@npm:3.0.1" - checksum: aa966eb919bf95b1bd819cda784d1f6f66e3fb65bd9ec7bf68b6a978eeb4e3e14f7e2275021b473f93b15b6b7ba2e5a30471e53add3929a7e695fcfd6dd40ec8 + version: 3.0.4 + resolution: "luxon@npm:3.0.4" + checksum: d0908c3951da2a10ccf23040210ead23b0da5366a9d0954e7d5db3560189a7bd703d8af1e00084f197effc9cd7158d1bddf32886d98a70d59ce9bc3fe88bbce0 languageName: node linkType: hard @@ -28165,12 +28550,12 @@ __metadata: languageName: node linkType: hard -"magic-string@npm:^0.26.1": - version: 0.26.1 - resolution: "magic-string@npm:0.26.1" +"magic-string@npm:^0.26.6": + version: 0.26.7 + resolution: "magic-string@npm:0.26.7" dependencies: sourcemap-codec: ^1.4.8 - checksum: 23f21f5734346ddfbabd7b9834e3ecda3521e3e1db81166c1513b45b729489bbed1eafa8cd052c7db7fdc7c68ebc5c03bc00dd5a23697edda15dbecaf8c98397 + checksum: 89b0d60cbb32bbf3d1e23c46ea93db082d18a8230b972027aecb10a40bba51be519ecce0674f995571e3affe917b76b09f59d8dbc9a1b2c9c4102a2b6e8a2b01 languageName: node linkType: hard @@ -28350,7 +28735,7 @@ __metadata: languageName: node linkType: hard -"marked@npm:^4.0.10, marked@npm:^4.0.14": +"marked@npm:^4.0.10": version: 4.1.0 resolution: "marked@npm:4.1.0" bin: @@ -28359,6 +28744,15 @@ __metadata: languageName: node linkType: hard +"marked@npm:^4.0.14": + version: 4.1.1 + resolution: "marked@npm:4.1.1" + bin: + marked: bin/marked.js + checksum: 717e3357952ee53de831bf0eb110ed075bebca2376c58bcdf7ee523ef540d45308ad6d51b2c933da0968832ea4386f31c142ca65443e77c098e84f6cce73e418 + languageName: node + linkType: hard + "matcher@npm:^3.0.0": version: 3.0.0 resolution: "matcher@npm:3.0.0" @@ -29268,13 +29662,20 @@ __metadata: languageName: node linkType: hard -"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.5, minimist@npm:^1.2.6": +"minimist@npm:^1.2.0, minimist@npm:^1.2.3, minimist@npm:^1.2.6": version: 1.2.6 resolution: "minimist@npm:1.2.6" checksum: d15428cd1e11eb14e1233bcfb88ae07ed7a147de251441d61158619dfb32c4d7e9061d09cab4825fdee18ecd6fce323228c8c47b5ba7cd20af378ca4048fb3fb languageName: node linkType: hard +"minimist@npm:^1.2.5": + version: 1.2.7 + resolution: "minimist@npm:1.2.7" + checksum: 7346574a1038ca23c32e02252f603801f09384dd1d78b69a943a4e8c2c28730b80e96193882d3d3b22a063445f460e48316b29b8a25addca2d7e5e8f75478bec + languageName: node + linkType: hard + "minimisted@npm:^2.0.0": version: 2.0.1 resolution: "minimisted@npm:2.0.1" @@ -29558,8 +29959,8 @@ __metadata: linkType: hard "msw@npm:^0.47.0": - version: 0.47.3 - resolution: "msw@npm:0.47.3" + version: 0.47.4 + resolution: "msw@npm:0.47.4" dependencies: "@mswjs/cookies": ^0.2.2 "@mswjs/interceptors": ^0.17.5 @@ -29578,7 +29979,7 @@ __metadata: outvariant: ^1.3.0 path-to-regexp: ^6.2.0 statuses: ^2.0.0 - strict-event-emitter: ^0.2.0 + strict-event-emitter: ^0.2.6 type-fest: ^2.19.0 yargs: ^17.3.1 peerDependencies: @@ -29588,7 +29989,7 @@ __metadata: optional: true bin: msw: cli/index.js - checksum: 1be018c7b2eff982409967cccb5c604e45f65710ee9698bab57fbe794f8426d1a4d33e52b75ef395c6d226948c799241c7c2c7748ec4f5b741e7f25bcbafbd1e + checksum: 10ff632641d40384d6622abf4df6399e4ae649db0f676b5d1ee2d0a515ec96f33abe9d4fecba08cdba4b2e43255af419da9eefc020d40a7e10669d0906457197 languageName: node linkType: hard @@ -29695,16 +30096,7 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.1.23": - version: 3.3.1 - resolution: "nanoid@npm:3.3.1" - bin: - nanoid: bin/nanoid.cjs - checksum: 4ef0969e1bbe866fc223eb32276cbccb0961900bfe79104fa5abe34361979dead8d0e061410a5c03bc3d47455685adf32c09d6f27790f4a6898fb51f7df7ec86 - languageName: node - linkType: hard - -"nanoid@npm:^3.3.4": +"nanoid@npm:^3.1.23, nanoid@npm:^3.3.4": version: 3.3.4 resolution: "nanoid@npm:3.3.4" bin: @@ -30524,7 +30916,19 @@ __metadata: languageName: node linkType: hard -"openid-client@npm:^5.1.3, openid-client@npm:^5.1.6": +"openid-client@npm:^5.1.3": + version: 5.1.10 + resolution: "openid-client@npm:5.1.10" + dependencies: + jose: ^4.1.4 + lru-cache: ^6.0.0 + object-hash: ^2.0.1 + oidc-token-hash: ^5.0.1 + checksum: 38a4bf08ea4ee4576043968307cf53f0369df224bd025c1bc348b295152df36c47d2a836dfe1505d15c6b79c05f86aadefad798bd3ea3ccbe90834be01f2245c + languageName: node + linkType: hard + +"openid-client@npm:^5.1.6": version: 5.1.9 resolution: "openid-client@npm:5.1.9" dependencies: @@ -31183,8 +31587,8 @@ __metadata: linkType: hard "passport-saml@npm:^3.1.2": - version: 3.2.1 - resolution: "passport-saml@npm:3.2.1" + version: 3.2.3 + resolution: "passport-saml@npm:3.2.3" dependencies: "@xmldom/xmldom": ^0.7.5 debug: ^4.3.2 @@ -31193,7 +31597,7 @@ __metadata: xml-encryption: ^2.0.0 xml2js: ^0.4.23 xmlbuilder: ^15.1.1 - checksum: 7236c82f2b16240acc6e026cbe171e130e703d0e85c7f83733be0137218a62f61dee72d34cea00ea02a0db4b823497e574ef4b6585a316097b926eb760f90bb8 + checksum: ccddfa37557ef39219b447285d9b87ad08309b3cd2537aab5df344fd3910d9b506eb36e4f24f2eec35047465f00fbea53b320674de9ae68ea18cbbce17de79f4 languageName: node linkType: hard @@ -32153,7 +32557,18 @@ __metadata: languageName: node linkType: hard -"postcss@npm:^8.1.0, postcss@npm:^8.4.7": +"postcss@npm:^8.1.0": + version: 8.4.17 + resolution: "postcss@npm:8.4.17" + dependencies: + nanoid: ^3.3.4 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: a6d9096dd711e17f7b1d18ff5dcb4fdedf3941d5a3dc8b0e4ea873b8f31972d57f73d6da9a8aed7ff389eb52190ed34f6a94f299a7f5ddc68b08a24a48f77eb9 + languageName: node + linkType: hard + +"postcss@npm:^8.4.7": version: 8.4.16 resolution: "postcss@npm:8.4.16" dependencies: @@ -33210,6 +33625,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^18.2.0": + version: 18.2.0 + resolution: "react-is@npm:18.2.0" + checksum: e72d0ba81b5922759e4aff17e0252bd29988f9642ed817f56b25a3e217e13eea8a7f2322af99a06edb779da12d5d636e9fda473d620df9a3da0df2a74141d53e + languageName: node + linkType: hard + "react-lifecycles-compat@npm:^3.0.4": version: 3.0.4 resolution: "react-lifecycles-compat@npm:3.0.4" @@ -34580,18 +35002,18 @@ __metadata: linkType: hard "rollup-plugin-dts@npm:^4.0.1": - version: 4.2.2 - resolution: "rollup-plugin-dts@npm:4.2.2" + version: 4.2.3 + resolution: "rollup-plugin-dts@npm:4.2.3" dependencies: - "@babel/code-frame": ^7.16.7 - magic-string: ^0.26.1 + "@babel/code-frame": ^7.18.6 + magic-string: ^0.26.6 peerDependencies: rollup: ^2.55 typescript: ^4.1 dependenciesMeta: "@babel/code-frame": optional: true - checksum: cf4b45f6cca442a5f44af0f0fb567c8fc540ecb792c763571d1bcda9bf495803bcc8d4eaef451a2dd32f7f391eb822e2b96cc6b86b096db54a4d3935236fd8da + checksum: b1de94202d0574e7c12105bf0d013e7142c1b9b74d6b83d194d870dcdc281e90cff45ed47a0ab1c62280cc25e75f522e1278ec0ba89c8f75b8bcb56dc98c2c63 languageName: node linkType: hard @@ -34703,6 +35125,7 @@ __metadata: "@backstage/cli": "workspace:*" "@backstage/codemods": "workspace:*" "@backstage/create-app": "workspace:*" + "@backstage/errors": "workspace:^" "@changesets/cli": ^2.14.0 "@manypkg/get-packages": ^1.1.3 "@microsoft/api-documenter": ^7.17.11 @@ -36165,6 +36588,15 @@ __metadata: languageName: node linkType: hard +"strict-event-emitter@npm:^0.2.6": + version: 0.2.7 + resolution: "strict-event-emitter@npm:0.2.7" + dependencies: + events: ^3.3.0 + checksum: 111691e7d3fce0810586ccd8e8234af883ad3b121ef69091c7e260c32299d1ba085a95238ad09b43478bc5e9e80370f2fcb8114716e343be6f44bfc08fab4142 + languageName: node + linkType: hard + "strict-uri-encode@npm:^2.0.0": version: 2.0.0 resolution: "strict-uri-encode@npm:2.0.0" @@ -36648,8 +37080,8 @@ __metadata: linkType: hard "swagger-ui-react@npm:^4.11.1": - version: 4.14.0 - resolution: "swagger-ui-react@npm:4.14.0" + version: 4.14.3 + resolution: "swagger-ui-react@npm:4.14.3" dependencies: "@babel/runtime-corejs3": ^7.18.9 "@braintree/sanitize-url": =6.0.0 @@ -36687,7 +37119,7 @@ __metadata: peerDependencies: react: ">=17.0.0" react-dom: ">=17.0.0" - checksum: dbd6ce54bd8d2665aa8cd8b983c7cfd4407a710a6fc662ef61e611d54383abc082f15e55730f70ae214481d8e0c0ddeba100091747315edd5d455657c9fdf346 + checksum: ae8e932ff7eb0b4be71fd1ad7f00ef3eb461636a83985bd36ea28647f60769e6234a9921ff2f84aa33c370f30b8fa1c30a6ca7cf3c4a6ded963aaf9a337aa197 languageName: node linkType: hard @@ -37274,6 +37706,17 @@ __metadata: languageName: node linkType: hard +"tough-cookie@npm:^3.0.1": + version: 3.0.1 + resolution: "tough-cookie@npm:3.0.1" + dependencies: + ip-regex: ^2.1.0 + psl: ^1.1.28 + punycode: ^2.1.1 + checksum: 796f6239bce5674a1267b19f41972a2602a2a23715817237b5922b0dc2343512512eea7d41d29210a4ec545f8ef32173bbbf01277dd8ec3ae3841b19cbe69f67 + languageName: node + linkType: hard + "tough-cookie@npm:^4.0.0": version: 4.0.0 resolution: "tough-cookie@npm:4.0.0" @@ -37475,7 +37918,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": +"tslib@npm:^1.10.0, tslib@npm:^1.8.1, tslib@npm:^1.9.0, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: dbe628ef87f66691d5d2959b3e41b9ca0045c3ee3c7c7b906cc1e328b39f199bb1ad9e671c39025bd56122ac57dfbf7385a94843b1cc07c60a4db74795829acd @@ -38348,6 +38791,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^9.0.0": + version: 9.0.0 + resolution: "uuid@npm:9.0.0" + bin: + uuid: dist/bin/uuid + checksum: 8dd2c83c43ddc7e1c71e36b60aea40030a6505139af6bee0f382ebcd1a56f6cd3028f7f06ffb07f8cf6ced320b76aea275284b224b002b289f89fe89c389b028 + languageName: node + linkType: hard + "uvu@npm:^0.5.0": version: 0.5.3 resolution: "uvu@npm:0.5.3" @@ -39041,7 +39493,7 @@ __metadata: languageName: node linkType: hard -"winston@npm:^3.2.1, winston@npm:^3.7.2, winston@npm:^3.8.1": +"winston@npm:^3.2.1": version: 3.8.2 resolution: "winston@npm:3.8.2" dependencies: @@ -39735,6 +40187,15 @@ __metadata: languageName: node linkType: hard +"zod-to-json-schema@npm:^3.18.1": + version: 3.18.1 + resolution: "zod-to-json-schema@npm:3.18.1" + peerDependencies: + zod: ^3.18.0 + checksum: e55d0de83b50fbd1caa7541d037858815964477b52a9e6495496e447107386cf16e2c08b007fcfbffd7fbe069ca2c19018425a53eaee36aff5dda942d3db71f4 + languageName: node + linkType: hard + "zod@npm:^3.11.6, zod@npm:^3.9.5": version: 3.18.0 resolution: "zod@npm:3.18.0"