From 68b442c1723b26d5d2af23b0e4586ef8d97d40fe Mon Sep 17 00:00:00 2001 From: Guilherme Oenning Date: Wed, 4 May 2022 12:23:51 +0100 Subject: [PATCH 001/149] add azure auth provider Signed-off-by: Guilherme Oenning --- .changeset/big-teachers-dress.md | 6 +++ docs/features/kubernetes/configuration.md | 3 +- plugins/kubernetes-backend/api-report.md | 5 +++ plugins/kubernetes-backend/package.json | 1 + .../cluster-locator/ConfigClusterLocator.ts | 3 ++ .../AzureIdentityKubernetesAuthTranslator.ts | 41 +++++++++++++++++++ .../KubernetesAuthTranslatorGenerator.ts | 4 ++ plugins/kubernetes-backend/src/types/types.ts | 1 + .../AzureKubernetesAuthProvider.ts | 27 ++++++++++++ .../KubernetesAuthProviders.ts | 2 + 10 files changed, 92 insertions(+), 1 deletion(-) create mode 100644 .changeset/big-teachers-dress.md create mode 100644 plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts create mode 100644 plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts diff --git a/.changeset/big-teachers-dress.md b/.changeset/big-teachers-dress.md new file mode 100644 index 0000000000..4545939c0a --- /dev/null +++ b/.changeset/big-teachers-dress.md @@ -0,0 +1,6 @@ +--- +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-kubernetes-backend': patch +--- + +add Azure Identity auth provider diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index cb30451c7d..d880aaf6cd 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -90,7 +90,8 @@ cluster. Valid values are: | `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 | +| `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 | ##### `clusters.\*.skipTLSVerify` diff --git a/plugins/kubernetes-backend/api-report.md b/plugins/kubernetes-backend/api-report.md index 9eca295f96..03682807e2 100644 --- a/plugins/kubernetes-backend/api-report.md +++ b/plugins/kubernetes-backend/api-report.md @@ -24,6 +24,11 @@ export interface AWSClusterDetails extends ClusterDetails { externalId?: string; } +// Warning: (ae-missing-release-tag) "AzureClusterDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface AzureClusterDetails extends ClusterDetails {} + // Warning: (ae-missing-release-tag) "ClusterDetails" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 125f03322e..4d4cd8dfd0 100644 --- a/plugins/kubernetes-backend/package.json +++ b/plugins/kubernetes-backend/package.json @@ -35,6 +35,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { + "@azure/identity": "^2.0.4", "@backstage/backend-common": "^0.13.3-next.0", "@backstage/catalog-model": "^1.0.1", "@backstage/config": "^1.0.0", diff --git a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts index 1bde1226dd..5598740873 100644 --- a/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts +++ b/plugins/kubernetes-backend/src/cluster-locator/ConfigClusterLocator.ts @@ -61,6 +61,9 @@ export class ConfigClusterLocator implements KubernetesClustersSupplier { return { assumeRole, externalId, ...clusterDetails }; } + case 'azure': { + return clusterDetails; + } case 'serviceAccount': { return clusterDetails; } diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts new file mode 100644 index 0000000000..ab6469b4e4 --- /dev/null +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts @@ -0,0 +1,41 @@ +/* + * 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 { KubernetesAuthTranslator } from './types'; +import { AzureClusterDetails } from '../types/types'; +import { DefaultAzureCredential } from '@azure/identity'; + +const aksScope = "6dae42f8-4368-4678-94ff-3960e28e3630/.default" // This scope is the same for all Azure Managed Kubernetes + +export class AzureIdentityKubernetesAuthTranslator + implements KubernetesAuthTranslator +{ + async decorateClusterDetailsWithAuth( + clusterDetails: AzureClusterDetails, + ): Promise { + const clusterDetailsWithAuthToken: AzureClusterDetails = Object.assign( + {}, + clusterDetails, + ); + + const credentials = new DefaultAzureCredential(); + + // TODO: can we cache this? It's inneficiant to get a new token every time + const accessToken = await credentials.getToken(aksScope); + clusterDetailsWithAuthToken.serviceAccountToken = accessToken.token + return clusterDetailsWithAuthToken; + } +} diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts index 337ae899dc..e9a8a00ae9 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts @@ -19,6 +19,7 @@ import { GoogleKubernetesAuthTranslator } from './GoogleKubernetesAuthTranslator import { ServiceAccountKubernetesAuthTranslator } from './ServiceAccountKubernetesAuthTranslator'; import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator'; import { GoogleServiceAccountAuthTranslator } from './GoogleServiceAccountAuthProvider'; +import { AzureIdentityKubernetesAuthTranslator } from './AzureIdentityKubernetesAuthTranslator'; export class KubernetesAuthTranslatorGenerator { static getKubernetesAuthTranslatorInstance( @@ -31,6 +32,9 @@ export class KubernetesAuthTranslatorGenerator { case 'aws': { return new AwsIamKubernetesAuthTranslator(); } + case 'azure': { + return new AzureIdentityKubernetesAuthTranslator(); + } case 'serviceAccount': { return new ServiceAccountKubernetesAuthTranslator(); } diff --git a/plugins/kubernetes-backend/src/types/types.ts b/plugins/kubernetes-backend/src/types/types.ts index 12c4d2fb48..706582f439 100644 --- a/plugins/kubernetes-backend/src/types/types.ts +++ b/plugins/kubernetes-backend/src/types/types.ts @@ -147,6 +147,7 @@ export interface ClusterDetails { } export interface GKEClusterDetails extends ClusterDetails {} +export interface AzureClusterDetails extends ClusterDetails {} export interface ServiceAccountClusterDetails extends ClusterDetails {} export interface AWSClusterDetails extends ClusterDetails { assumeRole?: string; diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts new file mode 100644 index 0000000000..59dd0321b3 --- /dev/null +++ b/plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.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. + */ + +import { KubernetesAuthProvider } from './types'; +import { KubernetesRequestBody } from '@backstage/plugin-kubernetes-common'; + +export class AzureKubernetesAuthProvider implements KubernetesAuthProvider { + async decorateRequestBodyForAuth( + requestBody: KubernetesRequestBody, + ): Promise { + // No-op, with aws auth, server's Azire credentials are used for access + return requestBody; + } +} diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts index be6712f103..5bccccc43d 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts @@ -21,6 +21,7 @@ import { ServiceAccountKubernetesAuthProvider } from './ServiceAccountKubernetes import { AwsKubernetesAuthProvider } from './AwsKubernetesAuthProvider'; import { OAuthApi } from '@backstage/core-plugin-api'; import { GoogleServiceAccountAuthProvider } from './GoogleServiceAccountAuthProvider'; +import { AzureKubernetesAuthProvider } from './AzureKubernetesAuthProvider'; export class KubernetesAuthProviders implements KubernetesAuthProvidersApi { private readonly kubernetesAuthProviderMap: Map< @@ -43,6 +44,7 @@ export class KubernetesAuthProviders implements KubernetesAuthProvidersApi { new GoogleServiceAccountAuthProvider(), ); this.kubernetesAuthProviderMap.set('aws', new AwsKubernetesAuthProvider()); + this.kubernetesAuthProviderMap.set('azure', new AzureKubernetesAuthProvider()); } async decorateRequestBodyForAuth( From 8bc3cd1dce565a95e518034e81db6abd14890f49 Mon Sep 17 00:00:00 2001 From: Guilherme Oenning Date: Wed, 4 May 2022 14:32:33 +0100 Subject: [PATCH 002/149] fix prettier-S Signed-off-by: Guilherme Oenning --- docs/features/kubernetes/configuration.md | 4 ++-- .../AzureIdentityKubernetesAuthTranslator.ts | 4 ++-- .../src/kubernetes-auth-provider/KubernetesAuthProviders.ts | 5 ++++- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index d880aaf6cd..5b60316fd0 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -90,8 +90,8 @@ cluster. Valid values are: | `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 | +| `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 | ##### `clusters.\*.skipTLSVerify` diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts index ab6469b4e4..20b519c269 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts @@ -18,7 +18,7 @@ import { KubernetesAuthTranslator } from './types'; import { AzureClusterDetails } from '../types/types'; import { DefaultAzureCredential } from '@azure/identity'; -const aksScope = "6dae42f8-4368-4678-94ff-3960e28e3630/.default" // This scope is the same for all Azure Managed Kubernetes +const aksScope = '6dae42f8-4368-4678-94ff-3960e28e3630/.default'; // This scope is the same for all Azure Managed Kubernetes export class AzureIdentityKubernetesAuthTranslator implements KubernetesAuthTranslator @@ -35,7 +35,7 @@ export class AzureIdentityKubernetesAuthTranslator // TODO: can we cache this? It's inneficiant to get a new token every time const accessToken = await credentials.getToken(aksScope); - clusterDetailsWithAuthToken.serviceAccountToken = accessToken.token + clusterDetailsWithAuthToken.serviceAccountToken = accessToken.token; return clusterDetailsWithAuthToken; } } diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts index 5bccccc43d..00d5a17f72 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/KubernetesAuthProviders.ts @@ -44,7 +44,10 @@ export class KubernetesAuthProviders implements KubernetesAuthProvidersApi { new GoogleServiceAccountAuthProvider(), ); this.kubernetesAuthProviderMap.set('aws', new AwsKubernetesAuthProvider()); - this.kubernetesAuthProviderMap.set('azure', new AzureKubernetesAuthProvider()); + this.kubernetesAuthProviderMap.set( + 'azure', + new AzureKubernetesAuthProvider(), + ); } async decorateRequestBodyForAuth( From ce1e8df8a583e7c1ade76cfa11a45aeec468c29d Mon Sep 17 00:00:00 2001 From: Guilherme Oenning Date: Wed, 4 May 2022 15:08:35 +0100 Subject: [PATCH 003/149] add aks dashboard formatter Signed-off-by: Guilherme Oenning --- .../utils/clusterLinks/formatters/aks.test.ts | 89 ++++++++++++++++++- .../src/utils/clusterLinks/formatters/aks.ts | 37 +++++++- 2 files changed, 121 insertions(+), 5 deletions(-) diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts index 320c06e54c..6f90ab1f1d 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.test.ts @@ -16,10 +16,9 @@ import { aksFormatter } from './aks'; describe('clusterLinks - AKS formatter', () => { - it('should return an url on the workloads when there is a namespace only', () => { + it('should provide a dashboardParameters in the options', () => { expect(() => aksFormatter({ - dashboardUrl: new URL('https://k8s.foo.com'), object: { metadata: { name: 'foobar', @@ -28,6 +27,90 @@ describe('clusterLinks - AKS formatter', () => { }, kind: 'Deployment', }), - ).toThrowError('AKS formatter is not yet implemented. Please, contribute!'); + ).toThrowError('AKS dashboard requires a dashboardParameters option'); + }); + it('should provide a subscriptionId in the dashboardParameters options', () => { + expect(() => + aksFormatter({ + dashboardParameters: { + resourceGroup: 'rg-1', + clusterName: 'cluster-1', + }, + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }), + ).toThrowError( + 'AKS dashboard requires a "subscriptionId" of type string in the dashboardParameters option', + ); + }); + it('should provide a resourceGroup in the dashboardParameters options', () => { + expect(() => + aksFormatter({ + dashboardParameters: { + subscriptionId: '1234-GUID-5678', + clusterName: 'cluster-1', + }, + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }), + ).toThrowError( + 'AKS dashboard requires a "resourceGroup" of type string in the dashboardParameters option', + ); + }); + it('should provide a clusterName in the dashboardParameters options', () => { + expect(() => + aksFormatter({ + dashboardParameters: { + subscriptionId: '1234-GUID-5678', + resourceGroup: 'us-east1-c', + }, + object: { + metadata: { + name: 'foobar', + namespace: 'bar', + }, + }, + kind: 'Deployment', + }), + ).toThrowError( + 'AKS dashboard requires a "clusterName" of type string in the dashboardParameters option', + ); + }); + it('should return an url on the cluster with object details', () => { + const url = aksFormatter({ + dashboardParameters: { + subscriptionId: '1234-GUID-5678', + resourceGroup: 'rg-1', + clusterName: 'cluster-1', + }, + object: { + metadata: { + name: 'my-deployment', + namespace: 'my-namespace', + uid: '111-GUID-222', + }, + spec: { + selector: { + matchLabels: { + app: 'foo', + }, + }, + }, + }, + kind: 'Deployment', + }); + expect(url.href).toBe( + 'https://portal.azure.com/#blade/Microsoft_Azure_ContainerService/AksK8ResourceMenuBlade/overview-Deployment/aksClusterId/%2Fsubscriptions%2F1234-GUID-5678%2FresourceGroups%2Frg-1%2Fproviders%2FMicrosoft.ContainerService%2FmanagedClusters%2Fcluster-1/resource/%7B%22kind%22%3A%22Deployment%22%2C%22metadata%22%3A%7B%22name%22%3A%22my-deployment%22%2C%22namespace%22%3A%22my-namespace%22%2C%22uid%22%3A%22111-GUID-222%22%7D%2C%22spec%22%3A%7B%22selector%22%3A%7B%22matchLabels%22%3A%7B%22app%22%3A%22foo%22%7D%7D%7D%7D', + ); }); }); diff --git a/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.ts b/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.ts index d6f39ab72c..71fffb00e6 100644 --- a/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.ts +++ b/plugins/kubernetes/src/utils/clusterLinks/formatters/aks.ts @@ -15,6 +15,39 @@ */ import { ClusterLinksFormatterOptions } from '../../../types/types'; -export function aksFormatter(_options: ClusterLinksFormatterOptions): URL { - throw new Error('AKS formatter is not yet implemented. Please, contribute!'); +const basePath = + 'https://portal.azure.com/#blade/Microsoft_Azure_ContainerService/AksK8ResourceMenuBlade/overview-Deployment/aksClusterId'; + +const requiredParams = ['subscriptionId', 'resourceGroup', 'clusterName']; + +export function aksFormatter(options: ClusterLinksFormatterOptions): URL { + if (!options.dashboardParameters) { + throw new Error('AKS dashboard requires a dashboardParameters option'); + } + const args = options.dashboardParameters; + for (const param of requiredParams) { + if (typeof args[param] !== 'string') { + throw new Error( + `AKS dashboard requires a "${param}" of type string in the dashboardParameters option`, + ); + } + } + + const path = `/subscriptions/${args.subscriptionId}/resourceGroups/${args.resourceGroup}/providers/Microsoft.ContainerService/managedClusters/${args.clusterName}`; + + const { name, namespace, uid } = options.object.metadata; + const { selector } = options.object.spec; + const params = { + kind: options.kind, + metadata: { name, namespace, uid }, + spec: { + selector, + }, + }; + + return new URL( + `${basePath}/${encodeURIComponent(path)}/resource/${encodeURIComponent( + JSON.stringify(params), + )}`, + ); } From df35b3b30d452ce80192c5a0074ce5305cb654b4 Mon Sep 17 00:00:00 2001 From: Guilherme Oenning Date: Wed, 4 May 2022 15:10:40 +0100 Subject: [PATCH 004/149] ammend patch notes Signed-off-by: Guilherme Oenning --- .changeset/big-teachers-dress.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/big-teachers-dress.md b/.changeset/big-teachers-dress.md index 4545939c0a..6522efa056 100644 --- a/.changeset/big-teachers-dress.md +++ b/.changeset/big-teachers-dress.md @@ -3,4 +3,4 @@ '@backstage/plugin-kubernetes-backend': patch --- -add Azure Identity auth provider +add Azure Identity auth provider and AKS dashboard formatter From 0a336bf640e2df8c4cb14e5f6a28ad3f764ed03c Mon Sep 17 00:00:00 2001 From: Guilherme Oenning Date: Wed, 4 May 2022 15:11:17 +0100 Subject: [PATCH 005/149] typo Signed-off-by: Guilherme Oenning --- .../src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts index 59dd0321b3..ee184b3f57 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts @@ -21,7 +21,7 @@ export class AzureKubernetesAuthProvider implements KubernetesAuthProvider { async decorateRequestBodyForAuth( requestBody: KubernetesRequestBody, ): Promise { - // No-op, with aws auth, server's Azire credentials are used for access + // No-op, with aws auth, server's Azure credentials are used for access return requestBody; } } From 403e5de2630beb8367291a34fd04bdb0619de857 Mon Sep 17 00:00:00 2001 From: goenning Date: Thu, 5 May 2022 10:34:21 +0100 Subject: [PATCH 006/149] update enum to include azure Signed-off-by: goenning --- plugins/kubernetes-backend/schema.d.ts | 2 +- plugins/kubernetes-common/api-report.md | 2 +- plugins/kubernetes-common/src/types.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/kubernetes-backend/schema.d.ts b/plugins/kubernetes-backend/schema.d.ts index 7f5183e337..c2ac017713 100644 --- a/plugins/kubernetes-backend/schema.d.ts +++ b/plugins/kubernetes-backend/schema.d.ts @@ -52,7 +52,7 @@ export interface Config { /** @visibility secret */ serviceAccountToken?: string; /** @visibility frontend */ - authProvider: 'aws' | 'google' | 'serviceAccount'; + authProvider: 'aws' | 'google' | 'serviceAccount' | 'azure'; /** @visibility frontend */ skipTLSVerify?: boolean; }>; diff --git a/plugins/kubernetes-common/api-report.md b/plugins/kubernetes-common/api-report.md index 15019d71c3..639c3f63c2 100644 --- a/plugins/kubernetes-common/api-report.md +++ b/plugins/kubernetes-common/api-report.md @@ -18,7 +18,7 @@ import { V1Service } from '@kubernetes/client-node'; // Warning: (ae-missing-release-tag) "AuthProviderType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export type AuthProviderType = 'google' | 'serviceAccount' | 'aws'; +export type AuthProviderType = 'google' | 'serviceAccount' | 'aws' | 'azure'; // Warning: (ae-missing-release-tag) "ClientContainerStatus" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/plugins/kubernetes-common/src/types.ts b/plugins/kubernetes-common/src/types.ts index afda9b5299..cb388bad0a 100644 --- a/plugins/kubernetes-common/src/types.ts +++ b/plugins/kubernetes-common/src/types.ts @@ -84,7 +84,7 @@ export interface ObjectsByEntityResponse { items: ClusterObjects[]; } -export type AuthProviderType = 'google' | 'serviceAccount' | 'aws'; +export type AuthProviderType = 'google' | 'serviceAccount' | 'aws' | 'azure'; export type FetchResponse = | PodFetchResponse From 88c539a2db6c40f6b1bfbbb9005d7bb2e559159b Mon Sep 17 00:00:00 2001 From: Guilherme Oenning Date: Fri, 6 May 2022 15:18:06 +0100 Subject: [PATCH 007/149] fix typo Signed-off-by: goenning --- .../src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts b/plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts index ee184b3f57..60401bbe4d 100644 --- a/plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts +++ b/plugins/kubernetes/src/kubernetes-auth-provider/AzureKubernetesAuthProvider.ts @@ -21,7 +21,7 @@ export class AzureKubernetesAuthProvider implements KubernetesAuthProvider { async decorateRequestBodyForAuth( requestBody: KubernetesRequestBody, ): Promise { - // No-op, with aws auth, server's Azure credentials are used for access + // No-op, with azure auth, server's Azure credentials are used for access return requestBody; } } From 31d1b31392fd0d9706ef73546d3fbcd4b88a469b Mon Sep 17 00:00:00 2001 From: goenning Date: Fri, 6 May 2022 15:21:08 +0100 Subject: [PATCH 008/149] add plugin to changeset Signed-off-by: goenning --- .changeset/big-teachers-dress.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.changeset/big-teachers-dress.md b/.changeset/big-teachers-dress.md index 6522efa056..418b6a2f3f 100644 --- a/.changeset/big-teachers-dress.md +++ b/.changeset/big-teachers-dress.md @@ -1,6 +1,7 @@ --- '@backstage/plugin-kubernetes': patch '@backstage/plugin-kubernetes-backend': patch +'@backstage/plugin-kubernetes-common': patch --- add Azure Identity auth provider and AKS dashboard formatter From 0c70cd8e1d8f9a1960788e701d1babc3f67d788c Mon Sep 17 00:00:00 2001 From: goenning Date: Thu, 12 May 2022 13:44:48 +0100 Subject: [PATCH 009/149] cache azure token for kubernetes Signed-off-by: goenning --- .changeset/healthy-pets-mix.md | 5 ++ ...reIdentityKubernetesAuthTranslator.test.ts | 84 +++++++++++++++++++ .../AzureIdentityKubernetesAuthTranslator.ts | 33 ++++++-- .../src/service/KubernetesFanOutHandler.ts | 25 ++++-- 4 files changed, 134 insertions(+), 13 deletions(-) create mode 100644 .changeset/healthy-pets-mix.md create mode 100644 plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts diff --git a/.changeset/healthy-pets-mix.md b/.changeset/healthy-pets-mix.md new file mode 100644 index 0000000000..057cd5f775 --- /dev/null +++ b/.changeset/healthy-pets-mix.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-kubernetes-backend': patch +--- + +cache and refresh Azure tokens to avoid excesive calls to Azure Identity diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts new file mode 100644 index 0000000000..b9c8df2431 --- /dev/null +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts @@ -0,0 +1,84 @@ +/* + * 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 { AccessToken, TokenCredential } from '@azure/identity'; +import { AzureIdentityKubernetesAuthTranslator } from './AzureIdentityKubernetesAuthTranslator'; + +class StaticTokenCredential implements TokenCredential { + private count: number = 0; + + constructor(private expiryInMs: number) {} + + getToken(): Promise { + this.count++; + + return Promise.resolve({ + token: `MY_TOKEN_${this.count}`, + expiresOnTimestamp: Date.now() + this.expiryInMs, + }); + } +} + +describe('AzureIdentityKubernetesAuthTranslator tests', () => { + const cd = { + authProvider: 'Azure', + name: 'My Cluster', + url: 'mycluster.privatelink.westeurope.azmk8s.io', + }; + + it('should decorate cluster with Azure token', async () => { + const authTranslator = new AzureIdentityKubernetesAuthTranslator( + new StaticTokenCredential(5 * 60 * 1000), + ); + + const response = await authTranslator.decorateClusterDetailsWithAuth(cd); + expect(response.serviceAccountToken).toEqual('MY_TOKEN_1'); + }); + + it('should re-use token before expiry', async () => { + const authTranslator = new AzureIdentityKubernetesAuthTranslator( + new StaticTokenCredential(5 * 60 * 1000), + ); + + const response = await authTranslator.decorateClusterDetailsWithAuth(cd); + expect(response.serviceAccountToken).toEqual('MY_TOKEN_1'); + + const response2 = await authTranslator.decorateClusterDetailsWithAuth(cd); + expect(response2.serviceAccountToken).toEqual('MY_TOKEN_1'); + }); + + it('should reissue new token 2 minutes befory expiry', async () => { + const authTranslator = new AzureIdentityKubernetesAuthTranslator( + new StaticTokenCredential(3 * 60 * 1000), // token expires in 3m + ); + + const response = await authTranslator.decorateClusterDetailsWithAuth({ + authProvider: 'Azure', + name: 'My Cluster', + url: 'mycluster.privatelink.westeurope.azmk8s.io', + }); + expect(response.serviceAccountToken).toEqual('MY_TOKEN_1'); + + jest.useFakeTimers().setSystemTime(Date.now() + 1 * 60 * 1000); // advance time by 1min + + const response2 = await authTranslator.decorateClusterDetailsWithAuth({ + authProvider: 'Azure', + name: 'My Cluster', + url: 'mycluster.privatelink.westeurope.azmk8s.io', + }); + expect(response2.serviceAccountToken).toEqual('MY_TOKEN_2'); + }); +}); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts index 20b519c269..d33ce5fe5a 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts @@ -16,13 +16,24 @@ import { KubernetesAuthTranslator } from './types'; import { AzureClusterDetails } from '../types/types'; -import { DefaultAzureCredential } from '@azure/identity'; +import { + AccessToken, + DefaultAzureCredential, + TokenCredential, +} from '@azure/identity'; const aksScope = '6dae42f8-4368-4678-94ff-3960e28e3630/.default'; // This scope is the same for all Azure Managed Kubernetes export class AzureIdentityKubernetesAuthTranslator implements KubernetesAuthTranslator { + private tokenCredential: TokenCredential; + private accessToken: AccessToken | null = null; + + constructor(tokenCredential?: TokenCredential) { + this.tokenCredential = tokenCredential || new DefaultAzureCredential(); + } + async decorateClusterDetailsWithAuth( clusterDetails: AzureClusterDetails, ): Promise { @@ -31,11 +42,23 @@ export class AzureIdentityKubernetesAuthTranslator clusterDetails, ); - const credentials = new DefaultAzureCredential(); + if (!this.accessToken || this.tokenExpired()) { + this.accessToken = await this.tokenCredential.getToken(aksScope); - // TODO: can we cache this? It's inneficiant to get a new token every time - const accessToken = await credentials.getToken(aksScope); - clusterDetailsWithAuthToken.serviceAccountToken = accessToken.token; + if (!this.accessToken) { + throw new Error('Unable to retrieve Azure token'); + } + } + + clusterDetailsWithAuthToken.serviceAccountToken = this.accessToken.token; return clusterDetailsWithAuthToken; } + + private tokenExpired(): boolean { + if (!this.accessToken) return true; + + // Set tokens to expire 2 minutes before its actual expiry time + const expiresOn = this.accessToken.expiresOnTimestamp - 2 * 60 * 1000; + return Date.now() >= expiresOn; + } } diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index 00090e57ac..7a20db3b94 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -155,6 +155,7 @@ export class KubernetesFanOutHandler { private readonly serviceLocator: KubernetesServiceLocator; private readonly customResources: CustomResource[]; private readonly objectTypesToFetch: Set; + private readonly authTranslators: Record; constructor({ logger, @@ -168,6 +169,7 @@ export class KubernetesFanOutHandler { this.serviceLocator = serviceLocator; this.customResources = customResources; this.objectTypesToFetch = new Set(objectTypesToFetch); + this.authTranslators = {}; } async getKubernetesObjectsByEntity( @@ -183,14 +185,9 @@ export class KubernetesFanOutHandler { // Execute all of these async actions simultaneously/without blocking sequentially as no common object is modified by them const promises: Promise[] = clusterDetails.map(cd => { - const kubernetesAuthTranslator: KubernetesAuthTranslator = - KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance( - cd.authProvider, - ); - return kubernetesAuthTranslator.decorateClusterDetailsWithAuth( - cd, - requestBody, - ); + return this.getAuthTranslator( + cd.authProvider, + ).decorateClusterDetailsWithAuth(cd, requestBody); }); const clusterDetailsDecoratedForAuth: ClusterDetails[] = await Promise.all( promises, @@ -288,4 +285,16 @@ export class KubernetesFanOutHandler { return Promise.all([result, Promise.all(podMetrics)]); } + + private getAuthTranslator(provider: string): KubernetesAuthTranslator { + if (this.authTranslators[provider]) { + return this.authTranslators[provider]; + } + + this.authTranslators[provider] = + KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance( + provider, + ); + return this.authTranslators[provider]; + } } From 6e495a1c650d158d0880e1d1b0a77cb800ec4d44 Mon Sep 17 00:00:00 2001 From: goenning Date: Thu, 12 May 2022 13:56:56 +0100 Subject: [PATCH 010/149] typo Signed-off-by: goenning --- .changeset/healthy-pets-mix.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/healthy-pets-mix.md b/.changeset/healthy-pets-mix.md index 057cd5f775..44075b6a76 100644 --- a/.changeset/healthy-pets-mix.md +++ b/.changeset/healthy-pets-mix.md @@ -2,4 +2,4 @@ '@backstage/plugin-kubernetes-backend': patch --- -cache and refresh Azure tokens to avoid excesive calls to Azure Identity +cache and refresh Azure tokens to avoid excessive calls to Azure Identity From 81b8a9ded30479caa82b1a6dbc6f2e7665ae47a7 Mon Sep 17 00:00:00 2001 From: goenning Date: Thu, 12 May 2022 14:05:43 +0100 Subject: [PATCH 011/149] simplify test Signed-off-by: goenning --- .../AzureIdentityKubernetesAuthTranslator.test.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts index b9c8df2431..8a2387723c 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts @@ -60,25 +60,17 @@ describe('AzureIdentityKubernetesAuthTranslator tests', () => { expect(response2.serviceAccountToken).toEqual('MY_TOKEN_1'); }); - it('should reissue new token 2 minutes befory expiry', async () => { + it('should issue new token 2 minutes befory expiry', async () => { const authTranslator = new AzureIdentityKubernetesAuthTranslator( new StaticTokenCredential(3 * 60 * 1000), // token expires in 3m ); - const response = await authTranslator.decorateClusterDetailsWithAuth({ - authProvider: 'Azure', - name: 'My Cluster', - url: 'mycluster.privatelink.westeurope.azmk8s.io', - }); + const response = await authTranslator.decorateClusterDetailsWithAuth(cd); expect(response.serviceAccountToken).toEqual('MY_TOKEN_1'); jest.useFakeTimers().setSystemTime(Date.now() + 1 * 60 * 1000); // advance time by 1min - const response2 = await authTranslator.decorateClusterDetailsWithAuth({ - authProvider: 'Azure', - name: 'My Cluster', - url: 'mycluster.privatelink.westeurope.azmk8s.io', - }); + const response2 = await authTranslator.decorateClusterDetailsWithAuth(cd); expect(response2.serviceAccountToken).toEqual('MY_TOKEN_2'); }); }); From 72dfcbc8bff6afe7481e1f1ba53893313338eb3e Mon Sep 17 00:00:00 2001 From: Niklas Aronsson Date: Tue, 10 May 2022 14:28:02 +0200 Subject: [PATCH 012/149] Added scaffolder support for publishing to Gerrit This patch enables support for publishing the workspace content to new project in Gerrit. This can be broken down to three things: * "resolveUrl" for the Gerrit integration have been updated to handle absolute paths correctly. * "RepoUrlPicker" has been updated to handle gerrit hosts. * A new scaffolder action has been added that will publish the workspace content to a newly created Gerrit project. Signed-off-by: Niklas Aronsson --- .changeset/gold-tables-matter.md | 5 + .changeset/poor-years-develop.md | 5 + .changeset/ten-rocks-smile.md | 5 + .../src/gerrit/GerritIntegration.test.ts | 18 ++ .../src/gerrit/GerritIntegration.ts | 5 + packages/integration/src/gerrit/core.test.ts | 15 ++ packages/integration/src/gerrit/core.ts | 20 ++ plugins/scaffolder-backend/api-report.md | 13 ++ .../actions/builtin/createBuiltinActions.ts | 5 + .../actions/builtin/publish/gerrit.test.ts | 143 ++++++++++++ .../actions/builtin/publish/gerrit.ts | 216 ++++++++++++++++++ .../actions/builtin/publish/index.ts | 1 + plugins/scaffolder/api-report.md | 1 + plugins/scaffolder/src/api.ts | 1 + .../RepoUrlPicker/GerritRepoPicker.test.tsx | 78 +++++++ .../fields/RepoUrlPicker/GerritRepoPicker.tsx | 75 ++++++ .../fields/RepoUrlPicker/RepoUrlPicker.tsx | 9 + 17 files changed, 615 insertions(+) create mode 100644 .changeset/gold-tables-matter.md create mode 100644 .changeset/poor-years-develop.md create mode 100644 .changeset/ten-rocks-smile.md create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.test.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.ts create mode 100644 plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx create mode 100644 plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx diff --git a/.changeset/gold-tables-matter.md b/.changeset/gold-tables-matter.md new file mode 100644 index 0000000000..ee5f4e5af3 --- /dev/null +++ b/.changeset/gold-tables-matter.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': minor +--- + +A new scaffolder action has been added: `gerrit:publish` diff --git a/.changeset/poor-years-develop.md b/.changeset/poor-years-develop.md new file mode 100644 index 0000000000..aee42bfc48 --- /dev/null +++ b/.changeset/poor-years-develop.md @@ -0,0 +1,5 @@ +--- +'@backstage/integration': patch +--- + +Gerrit Integration: Handle absolute paths in `resolveUrl` properly. diff --git a/.changeset/ten-rocks-smile.md b/.changeset/ten-rocks-smile.md new file mode 100644 index 0000000000..af178f559e --- /dev/null +++ b/.changeset/ten-rocks-smile.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder': minor +--- + +Gerrit Integration: Implemented a `RepoUrlPicker` for Gerrit. diff --git a/packages/integration/src/gerrit/GerritIntegration.test.ts b/packages/integration/src/gerrit/GerritIntegration.test.ts index 6b5e228bb6..1a7bac5be6 100644 --- a/packages/integration/src/gerrit/GerritIntegration.test.ts +++ b/packages/integration/src/gerrit/GerritIntegration.test.ts @@ -97,6 +97,24 @@ describe('GerritIntegration', () => { }); }); + describe('resolves with an absolute url', () => { + it('works for valid urls', () => { + const integration = new GerritIntegration({ + host: 'gerrit-review.example.com', + gitilesBaseUrl: 'https://gerrit-review.example.com/gitiles', + } as any); + + expect( + integration.resolveUrl({ + url: '/catalog-info.yaml', + base: 'https://gerrit-review.example.com/gitiles/repo/+/refs/heads/master/', + }), + ).toBe( + 'https://gerrit-review.example.com/gitiles/repo/+/refs/heads/master/catalog-info.yaml', + ); + }); + }); + it('resolve edit URL', () => { const integration = new GerritIntegration({ host: 'gerrit-review.example.com', diff --git a/packages/integration/src/gerrit/GerritIntegration.ts b/packages/integration/src/gerrit/GerritIntegration.ts index 97eb9372c0..d57b54df8e 100644 --- a/packages/integration/src/gerrit/GerritIntegration.ts +++ b/packages/integration/src/gerrit/GerritIntegration.ts @@ -20,6 +20,7 @@ import { GerritIntegrationConfig, readGerritIntegrationConfigs, } from './config'; +import { parseGerritGitilesUrl, builldGerritGitilesUrl } from './core'; /** * A Gerrit based integration. @@ -58,6 +59,10 @@ export class GerritIntegration implements ScmIntegration { }): string { const { url, base, lineNumber } = options; let updated; + if (url.startsWith('/')) { + const { branch, project } = parseGerritGitilesUrl(this.config, base); + return builldGerritGitilesUrl(this.config, project, branch, url); + } if (url) { updated = new URL(url, base); } else { diff --git a/packages/integration/src/gerrit/core.test.ts b/packages/integration/src/gerrit/core.test.ts index 84bc1c0c19..351e7ce58e 100644 --- a/packages/integration/src/gerrit/core.test.ts +++ b/packages/integration/src/gerrit/core.test.ts @@ -20,6 +20,7 @@ import fetch from 'cross-fetch'; import { setupRequestMockHandlers } from '@backstage/test-utils'; import { GerritIntegrationConfig } from './config'; import { + builldGerritGitilesUrl, getGerritBranchApiUrl, getGerritCloneRepoUrl, getGerritRequestOptions, @@ -32,6 +33,20 @@ describe('gerrit core', () => { const worker = setupServer(); setupRequestMockHandlers(worker); + describe('builldGerritGitilesUrl', () => { + it('can create an url from arguments', () => { + const config: GerritIntegrationConfig = { + host: 'gerrit.com', + gitilesBaseUrl: 'https://gerrit.com/gitiles', + }; + expect( + builldGerritGitilesUrl(config, 'repo', 'dev', 'catalog-info.yaml'), + ).toEqual( + 'https://gerrit.com/gitiles/repo/+/refs/heads/dev/catalog-info.yaml', + ); + }); + }); + describe('getGerritRequestOptions', () => { it('adds headers when a password is specified', () => { const authRequest: GerritIntegrationConfig = { diff --git a/packages/integration/src/gerrit/core.ts b/packages/integration/src/gerrit/core.ts index eb413a2608..cd2b919e09 100644 --- a/packages/integration/src/gerrit/core.ts +++ b/packages/integration/src/gerrit/core.ts @@ -70,6 +70,26 @@ export function parseGerritGitilesUrl( }; } +/** + * Build a Gerrit Gitiles url that targets a specific path. + * + * @param config - A Gerrit provider config. + * @param project - The name of the git project + * @param branch - The branch we will target. + * @param filePath - The absolute file path. + * @public + */ +export function builldGerritGitilesUrl( + config: GerritIntegrationConfig, + project: string, + branch: string, + filePath: string, +): string { + return `${ + config.gitilesBaseUrl + }/${project}/+/refs/heads/${branch}/${trimStart(filePath, '/')}`; +} + /** * Return the authentication prefix. * diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 7cf26aa473..84293a8538 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -247,6 +247,19 @@ export function createPublishFileAction(): TemplateAction<{ path: string; }>; +// @public +export function createPublishGerritAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; +}): TemplateAction<{ + repoUrl: string; + description: string; + defaultBranch?: string | undefined; + gitCommitMessage?: string | undefined; + gitAuthorName?: string | undefined; + gitAuthorEmail?: string | undefined; +}>; + // @public export function createPublishGithubAction(options: { integrations: ScmIntegrationRegistry; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index 0d269019df..2ffbc09ce5 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -39,6 +39,7 @@ import { createPublishBitbucketAction, createPublishBitbucketCloudAction, createPublishBitbucketServerAction, + createPublishGerritAction, createPublishGithubAction, createPublishGithubPullRequestAction, createPublishGitlabAction, @@ -111,6 +112,10 @@ export const createBuiltinActions = ( reader, additionalTemplateFilters, }), + createPublishGerritAction({ + integrations, + config, + }), createPublishGithubAction({ integrations, config, diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.test.ts new file mode 100644 index 0000000000..0b1ffe0de6 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.test.ts @@ -0,0 +1,143 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +jest.mock('../helpers'); + +import { createPublishGerritAction } from './gerrit'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import { getVoidLogger } from '@backstage/backend-common'; +import { PassThrough } from 'stream'; +import { initRepoAndPush } from '../helpers'; + +describe('publish:gerrit', () => { + const config = new ConfigReader({ + integrations: { + gerrit: [ + { + host: 'gerrithost.org', + username: 'gerrituser', + password: 'usertoken', + }, + ], + }, + }); + + const description = 'for the lols'; + const integrations = ScmIntegrations.fromConfig(config); + const action = createPublishGerritAction({ integrations, config }); + const mockContext = { + input: { + repoUrl: + 'gerrithost.org?owner=owner&workspace=parent&project=project&repo=repo', + description, + }, + workspacePath: 'lol', + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + const server = setupServer(); + setupRequestMockHandlers(server); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should throw an error when the repoUrl is not well formed', async () => { + await expect( + action.handler({ + ...mockContext, + input: { repoUrl: 'gerrithost.org?workspace=w&repo=repo', description }, + }), + ).rejects.toThrow(/missing owner/); + + await expect( + action.handler({ + ...mockContext, + input: { repoUrl: 'gerrithost.org?workspace=w&owner=o', description }, + }), + ).rejects.toThrow(/missing repo/); + }); + + it('should throw if there is no integration config provided', async () => { + await expect( + action.handler({ + ...mockContext, + input: { + repoUrl: 'missing.com?workspace=w&owner=o&repo=repo', + description, + }, + }), + ).rejects.toThrow(/No matching integration configuration/); + }); + + it('can correctly create a new project', async () => { + expect.assertions(5); + server.use( + rest.put('https://gerrithost.org/a/projects/repo', (req, res, ctx) => { + expect(req.headers.get('Authorization')).toBe( + 'Basic Z2Vycml0dXNlcjp1c2VydG9rZW4=', + ); + expect(req.body).toEqual({ + create_empty_commit: false, + owners: ['owner'], + description, + parent: 'workspace', + }); + return res( + ctx.status(201), + ctx.set('Content-Type', 'application/json'), + ctx.json({}), + ); + }), + ); + + await action.handler({ + ...mockContext, + input: { + ...mockContext.input, + repoUrl: 'gerrithost.org?workspace=workspace&owner=owner&repo=repo', + }, + }); + + expect(initRepoAndPush).toHaveBeenCalledWith({ + dir: mockContext.workspacePath, + remoteUrl: 'https://gerrithost.org/a/repo', + defaultBranch: 'master', + auth: { username: 'gerrituser', password: 'usertoken' }, + logger: mockContext.logger, + commitMessage: expect.stringContaining('initial commit\n\nChange-Id:'), + gitAuthorInfo: {}, + }); + + expect(mockContext.output).toHaveBeenCalledWith( + 'remoteUrl', + 'https://gerrithost.org/a/repo', + ); + expect(mockContext.output).toHaveBeenCalledWith( + 'repoContentsUrl', + 'https://gerrithost.org/repo/+/refs/heads/master', + ); + }); + afterEach(() => { + jest.resetAllMocks(); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.ts new file mode 100644 index 0000000000..51355ec357 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gerrit.ts @@ -0,0 +1,216 @@ +/* + * 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 crypto from 'crypto'; +import { InputError } from '@backstage/errors'; +import { Config } from '@backstage/config'; +import { + GerritIntegrationConfig, + getGerritRequestOptions, + ScmIntegrationRegistry, +} from '@backstage/integration'; +import { createTemplateAction } from '../../createTemplateAction'; +import { getRepoSourceDirectory, parseRepoUrl } from './util'; +import fetch, { Response, RequestInit } from 'node-fetch'; +import { initRepoAndPush } from '../helpers'; + +const createGerritProject = async ( + config: GerritIntegrationConfig, + options: { + projectName: string; + parent: string; + owner: string; + description: string; + }, +): Promise => { + const { projectName, parent, owner, description } = options; + + const fetchOptions: RequestInit = { + method: 'PUT', + body: JSON.stringify({ + parent, + description, + owners: [owner], + create_empty_commit: false, + }), + headers: { + ...getGerritRequestOptions(config).headers, + 'Content-Type': 'application/json', + }, + }; + const response: Response = await fetch( + `${config.baseUrl}/a/projects/${encodeURIComponent(projectName)}`, + fetchOptions, + ); + if (response.status !== 201) { + throw new Error( + `Unable to create repository, ${response.status} ${ + response.statusText + }, ${await response.text()}`, + ); + } +}; + +const generateCommitMessage = ( + config: Config, + commitSubject?: string, +): string => { + const changeId = crypto.randomBytes(20).toString('hex'); + const msg = `${ + config.getOptionalString('scaffolder.defaultCommitMessage') || commitSubject + }\n\nChange-Id: I${changeId}`; + return msg; +}; + +/** + * Creates a new action that initializes a git repository of the content in the workspace + * and publishes it to a Gerrit instance. + * @public + */ +export function createPublishGerritAction(options: { + integrations: ScmIntegrationRegistry; + config: Config; +}) { + const { integrations, config } = options; + + return createTemplateAction<{ + repoUrl: string; + description: string; + defaultBranch?: string; + gitCommitMessage?: string; + gitAuthorName?: string; + gitAuthorEmail?: string; + }>({ + id: 'publish:gerrit', + description: + 'Initializes a git repository of the content in the workspace, and publishes it to Gerrit.', + schema: { + input: { + type: 'object', + required: ['repoUrl'], + properties: { + repoUrl: { + title: 'Repository Location', + type: 'string', + }, + description: { + title: 'Repository Description', + type: 'string', + }, + defaultBranch: { + title: 'Default Branch', + type: 'string', + description: `Sets the default branch on the repository. The default value is 'master'`, + }, + gitCommitMessage: { + title: 'Git Commit Message', + type: 'string', + description: `Sets the commit message on the repository. The default value is 'initial commit'`, + }, + gitAuthorName: { + title: 'Default Author Name', + type: 'string', + description: `Sets the default author name for the commit. The default value is 'Scaffolder'`, + }, + gitAuthorEmail: { + title: 'Default Author Email', + type: 'string', + description: `Sets the default author email for the commit.`, + }, + }, + }, + output: { + type: 'object', + properties: { + remoteUrl: { + title: 'A URL to the repository with the provider', + type: 'string', + }, + repoContentsUrl: { + title: 'A URL to the root of the repository', + type: 'string', + }, + }, + }, + }, + async handler(ctx) { + const { + repoUrl, + description, + defaultBranch = 'master', + gitAuthorName, + gitAuthorEmail, + gitCommitMessage = 'initial commit', + } = ctx.input; + const { repo, host, owner, workspace } = parseRepoUrl( + repoUrl, + integrations, + ); + + const integrationConfig = integrations.gerrit.byHost(host); + + if (!integrationConfig) { + throw new InputError( + `No matching integration configuration for host ${host}, please check your integrations config`, + ); + } + + if (!owner) { + throw new InputError( + `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing owner`, + ); + } + if (!workspace) { + throw new InputError( + `Invalid URL provider was included in the repo URL to create ${ctx.input.repoUrl}, missing workspace`, + ); + } + + await createGerritProject(integrationConfig.config, { + description, + owner: owner, + projectName: repo, + parent: workspace, + }); + const auth = { + username: integrationConfig.config.username!, + password: integrationConfig.config.password!, + }; + const gitAuthorInfo = { + name: gitAuthorName + ? gitAuthorName + : config.getOptionalString('scaffolder.defaultAuthor.name'), + email: gitAuthorEmail + ? gitAuthorEmail + : config.getOptionalString('scaffolder.defaultAuthor.email'), + }; + + const remoteUrl = `${integrationConfig.config.cloneUrl}/a/${repo}`; + await initRepoAndPush({ + dir: getRepoSourceDirectory(ctx.workspacePath, undefined), + remoteUrl, + auth, + defaultBranch, + logger: ctx.logger, + commitMessage: generateCommitMessage(config, gitCommitMessage), + gitAuthorInfo, + }); + + const repoContentsUrl = `${integrationConfig.config.gitilesBaseUrl}/${repo}/+/refs/heads/${defaultBranch}`; + ctx.output('remoteUrl', remoteUrl); + ctx.output('repoContentsUrl', repoContentsUrl); + }, + }); +} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts index 02266b6a06..11ecd2b127 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/index.ts @@ -19,6 +19,7 @@ export { createPublishBitbucketAction } from './bitbucket'; export { createPublishBitbucketCloudAction } from './bitbucketCloud'; export { createPublishBitbucketServerAction } from './bitbucketServer'; export { createPublishFileAction } from './file'; +export { createPublishGerritAction } from './gerrit'; export { createPublishGithubAction } from './github'; export { createPublishGithubPullRequestAction } from './githubPullRequest'; export type { diff --git a/plugins/scaffolder/api-report.md b/plugins/scaffolder/api-report.md index 20945d08a6..229737eaf6 100644 --- a/plugins/scaffolder/api-report.md +++ b/plugins/scaffolder/api-report.md @@ -194,6 +194,7 @@ export interface RepoUrlPickerUiOptions { requestUserCredentials?: { secretsKey: string; additionalScopes?: { + gerrit?: string[]; github?: string[]; gitlab?: string[]; bitbucket?: string[]; diff --git a/plugins/scaffolder/src/api.ts b/plugins/scaffolder/src/api.ts index 6fc456e1fe..04103b9f8a 100644 --- a/plugins/scaffolder/src/api.ts +++ b/plugins/scaffolder/src/api.ts @@ -84,6 +84,7 @@ export class ScaffolderClient implements ScaffolderApi { ), ...this.scmIntegrationsApi.bitbucketCloud.list(), ...this.scmIntegrationsApi.bitbucketServer.list(), + ...this.scmIntegrationsApi.gerrit.list(), ...this.scmIntegrationsApi.github.list(), ...this.scmIntegrationsApi.gitlab.list(), ] diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx new file mode 100644 index 0000000000..edc6732588 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.test.tsx @@ -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 React from 'react'; +import { GerritRepoPicker } from './GerritRepoPicker'; +import { render, fireEvent } from '@testing-library/react'; + +describe('BitbucketRepoPicker', () => { + describe('owner input field', () => { + it('calls onChange when the owner input changes', () => { + const onChange = jest.fn(); + const { getAllByRole } = render( + , + ); + + const ownerInput = getAllByRole('textbox')[0]; + + fireEvent.change(ownerInput, { target: { value: 'test-owner' } }); + + expect(onChange).toHaveBeenCalledWith({ owner: 'test-owner' }); + }); + }); + + describe('parent field', () => { + it('calls onChange when the parent changes', () => { + const onChange = jest.fn(); + const { getAllByRole } = render( + , + ); + + const parentInput = getAllByRole('textbox')[1]; + + fireEvent.change(parentInput, { target: { value: 'test-parent' } }); + + expect(onChange).toHaveBeenCalledWith({ workspace: 'test-parent' }); + }); + }); + + describe('repoName field', () => { + it('calls onChange when the repoName changes', () => { + const onChange = jest.fn(); + const { getAllByRole } = render( + , + ); + + const repoNameInput = getAllByRole('textbox')[2]; + + fireEvent.change(repoNameInput, { target: { value: 'test-repo' } }); + + expect(onChange).toHaveBeenCalledWith({ repoName: 'test-repo' }); + }); + }); +}); diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx new file mode 100644 index 0000000000..4cbc0859e8 --- /dev/null +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/GerritRepoPicker.tsx @@ -0,0 +1,75 @@ +/* + * 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 FormControl from '@material-ui/core/FormControl'; +import FormHelperText from '@material-ui/core/FormHelperText'; +import Input from '@material-ui/core/Input'; +import InputLabel from '@material-ui/core/InputLabel'; +import { RepoUrlPickerState } from './types'; + +export const GerritRepoPicker = (props: { + onChange: (state: RepoUrlPickerState) => void; + state: RepoUrlPickerState; + rawErrors: string[]; +}) => { + const { onChange, rawErrors, state } = props; + const { workspace, repoName, owner } = state; + return ( + <> + 0 && !workspace} + > + Owner + onChange({ owner: e.target.value })} + value={owner} + /> + The owner of the project + + 0 && !workspace} + > + Parent + onChange({ workspace: e.target.value })} + value={workspace} + /> + + The project parent that the repo will belong to + + + 0 && !repoName} + > + Repository + onChange({ repoName: e.target.value })} + value={repoName} + /> + The name of the repository + + + ); +}; diff --git a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx index fbb7bcd821..6f762933b6 100644 --- a/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx +++ b/plugins/scaffolder/src/components/fields/RepoUrlPicker/RepoUrlPicker.tsx @@ -23,6 +23,7 @@ import { GithubRepoPicker } from './GithubRepoPicker'; import { GitlabRepoPicker } from './GitlabRepoPicker'; import { AzureRepoPicker } from './AzureRepoPicker'; import { BitbucketRepoPicker } from './BitbucketRepoPicker'; +import { GerritRepoPicker } from './GerritRepoPicker'; import { FieldExtensionComponentProps } from '../../../extensions'; import { RepoUrlPickerHost } from './RepoUrlPickerHost'; import { parseRepoPickerUrl, serializeRepoPickerUrl } from './utils'; @@ -42,6 +43,7 @@ export interface RepoUrlPickerUiOptions { requestUserCredentials?: { secretsKey: string; additionalScopes?: { + gerrit?: string[]; github?: string[]; gitlab?: string[]; bitbucket?: string[]; @@ -170,6 +172,13 @@ export const RepoUrlPicker = ( onChange={updateLocalState} /> )} + {hostType === 'gerrit' && ( + + )} ); }; From 4fee8f59e33b8928b3cb8dbfddf2286948b7ed55 Mon Sep 17 00:00:00 2001 From: Milos Protic Date: Mon, 16 May 2022 15:13:57 +0200 Subject: [PATCH 013/149] fixed tech-insights API endpoint to return the proper latest value Signed-off-by: Milos Protic --- .changeset/beige-apricots-enjoy.md | 5 ++ .../persistence/TechInsightsDatabase.test.ts | 77 +++++++++++++++++++ .../persistence/TechInsightsDatabase.ts | 8 +- 3 files changed, 87 insertions(+), 3 deletions(-) create mode 100644 .changeset/beige-apricots-enjoy.md diff --git a/.changeset/beige-apricots-enjoy.md b/.changeset/beige-apricots-enjoy.md new file mode 100644 index 0000000000..bd1f932454 --- /dev/null +++ b/.changeset/beige-apricots-enjoy.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-tech-insights-backend': patch +--- + +Updated tech-insights fetch/latest endpoint to return the actual latest row based on the timestamp diff --git a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.test.ts b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.test.ts index c69432d070..e7e8819c66 100644 --- a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.test.ts +++ b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.test.ts @@ -115,6 +115,56 @@ const additionalFacts = [ }, ]; +const sameFactsDiffDateSchema = { + id: 'same-fact-diff-date-test', + version: '0.0.1-test', + entityFilter: JSON.stringify([{ kind: 'service' }]), + schema: JSON.stringify({ + testStringFact: { + type: 'string', + description: 'Test fact with a string type', + }, + }), +}; + +const sameFactsDiffDateNow = DateTime.now().toISO(); +const sameFactsDiffDateNearFuture = DateTime.now() + .plus(Duration.fromMillis(555)) + .toISO(); +const sameFactsDiffDateFuture = DateTime.now() + .plus(Duration.fromMillis(1000)) + .toISO(); + +const multipleSameFacts = [ + { + timestamp: sameFactsDiffDateNow, + id: sameFactsDiffDateSchema.id, + version: '0.0.1-test', + entity: 'a:a/a', + facts: JSON.stringify({ + testNumberFact: 1, + }), + }, + { + timestamp: sameFactsDiffDateNearFuture, + id: sameFactsDiffDateSchema.id, + version: '0.0.1-test', + entity: 'a:a/a', + facts: JSON.stringify({ + testNumberFact: 2, + }), + }, + { + timestamp: sameFactsDiffDateFuture, + id: 'multiple-same-facts', + version: '0.0.1-test', + entity: 'a:a/a', + facts: JSON.stringify({ + testNumberFact: 3, + }), + }, +]; + describe('Tech Insights database', () => { const databases = TestDatabases.create(); let store: TechInsightsStore; @@ -215,6 +265,33 @@ describe('Tech Insights database', () => { expect(returnedFact['test-fact']).toMatchObject(baseAssertionFact); }); + it('should return latest fact with multiple entries', async () => { + await testDbClient.batchInsert('fact_schemas', [sameFactsDiffDateSchema]); + await testDbClient.batchInsert( + 'facts', + multipleSameFacts.map(fact => ({ + ...fact, + id: sameFactsDiffDateSchema.id, + })), + ); + + const returnedFacts = await store.getLatestFactsByIds( + ['test-fact', sameFactsDiffDateSchema.id], + 'a:a/a', + ); + + expect(returnedFacts['test-fact']).toMatchObject({ + ...baseAssertionFact, + }); + + expect(returnedFacts[sameFactsDiffDateSchema.id]).toMatchObject({ + ...baseAssertionFact, + id: sameFactsDiffDateSchema.id, + timestamp: DateTime.fromISO(sameFactsDiffDateFuture), + facts: { testNumberFact: 3 }, + }); + }); + it('should return latest facts for multiple ids', async () => { await testDbClient.batchInsert('fact_schemas', [secondSchema]); await testDbClient.batchInsert( diff --git a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts index 3507e280a1..c6751e864a 100644 --- a/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts +++ b/plugins/tech-insights-backend/src/service/persistence/TechInsightsDatabase.ts @@ -131,14 +131,16 @@ export class TechInsightsDatabase implements TechInsightsStore { .and.whereIn('id', ids) .join( this.db('facts') - .max('timestamp') + .max('timestamp as maxTimestamp') .column('id as subId') .where({ entity: entityTriplet }) .and.whereIn('id', ids) .groupBy('id') .as('subQ'), - 'facts.id', - 'subQ.subId', + { + 'facts.id': 'subQ.subId', + 'facts.timestamp': 'subQ.maxTimestamp', + }, ); return this.dbFactRowsToTechInsightFacts(results); } From 6e284e2a3aa3d11f451c7805fb3adef40b471c0c Mon Sep 17 00:00:00 2001 From: goenning Date: Mon, 16 May 2022 15:51:54 +0100 Subject: [PATCH 014/149] code review Signed-off-by: goenning --- .../AzureIdentityKubernetesAuthTranslator.test.ts | 6 +++--- .../AzureIdentityKubernetesAuthTranslator.ts | 15 +++++++-------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts index 8a2387723c..c5183f8a22 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts @@ -50,7 +50,7 @@ describe('AzureIdentityKubernetesAuthTranslator tests', () => { it('should re-use token before expiry', async () => { const authTranslator = new AzureIdentityKubernetesAuthTranslator( - new StaticTokenCredential(5 * 60 * 1000), + new StaticTokenCredential(20 * 60 * 1000), ); const response = await authTranslator.decorateClusterDetailsWithAuth(cd); @@ -60,9 +60,9 @@ describe('AzureIdentityKubernetesAuthTranslator tests', () => { expect(response2.serviceAccountToken).toEqual('MY_TOKEN_1'); }); - it('should issue new token 2 minutes befory expiry', async () => { + it('should issue new token 15 minutes befory expiry', async () => { const authTranslator = new AzureIdentityKubernetesAuthTranslator( - new StaticTokenCredential(3 * 60 * 1000), // token expires in 3m + new StaticTokenCredential(16 * 60 * 1000), // token expires in 11m ); const response = await authTranslator.decorateClusterDetailsWithAuth(cd); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts index d33ce5fe5a..027bf03bb6 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts @@ -27,12 +27,11 @@ const aksScope = '6dae42f8-4368-4678-94ff-3960e28e3630/.default'; // This scope export class AzureIdentityKubernetesAuthTranslator implements KubernetesAuthTranslator { - private tokenCredential: TokenCredential; private accessToken: AccessToken | null = null; - constructor(tokenCredential?: TokenCredential) { - this.tokenCredential = tokenCredential || new DefaultAzureCredential(); - } + constructor( + private readonly tokenCredential: TokenCredential = new DefaultAzureCredential(), + ) {} async decorateClusterDetailsWithAuth( clusterDetails: AzureClusterDetails, @@ -42,7 +41,7 @@ export class AzureIdentityKubernetesAuthTranslator clusterDetails, ); - if (!this.accessToken || this.tokenExpired()) { + if (this.tokenExpired()) { this.accessToken = await this.tokenCredential.getToken(aksScope); if (!this.accessToken) { @@ -50,15 +49,15 @@ export class AzureIdentityKubernetesAuthTranslator } } - clusterDetailsWithAuthToken.serviceAccountToken = this.accessToken.token; + clusterDetailsWithAuthToken.serviceAccountToken = this.accessToken!.token; return clusterDetailsWithAuthToken; } private tokenExpired(): boolean { if (!this.accessToken) return true; - // Set tokens to expire 2 minutes before its actual expiry time - const expiresOn = this.accessToken.expiresOnTimestamp - 2 * 60 * 1000; + // Set tokens to expire 15 minutes before its actual expiry time + const expiresOn = this.accessToken.expiresOnTimestamp - 15 * 60 * 1000; return Date.now() >= expiresOn; } } From 1f83f0bc84a60dce1730b60595ce5d044d13d6a6 Mon Sep 17 00:00:00 2001 From: Antonio Musolino Date: Wed, 18 May 2022 10:47:28 +0200 Subject: [PATCH 015/149] feat(LDAP): added tls configuration Signed-off-by: Antonio Musolino --- .changeset/wicked-teachers-hide.md | 5 ++++ .../catalog-backend-module-ldap/api-report.md | 7 ++++++ .../catalog-backend-module-ldap/config.d.ts | 16 +++++++++++++ .../src/ldap/client.ts | 8 +++++-- .../src/ldap/config.test.ts | 2 ++ .../src/ldap/config.ts | 24 +++++++++++++++++++ .../src/ldap/index.ts | 1 + .../src/processors/LdapOrgEntityProvider.ts | 1 + .../src/processors/LdapOrgReaderProcessor.ts | 1 + 9 files changed, 63 insertions(+), 2 deletions(-) create mode 100644 .changeset/wicked-teachers-hide.md diff --git a/.changeset/wicked-teachers-hide.md b/.changeset/wicked-teachers-hide.md new file mode 100644 index 0000000000..c93b9e0848 --- /dev/null +++ b/.changeset/wicked-teachers-hide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-ldap': minor +--- + +Added the possibility to pass TLS configuration to ldap connection diff --git a/plugins/catalog-backend-module-ldap/api-report.md b/plugins/catalog-backend-module-ldap/api-report.md index 3c6cd4849b..90e58b3fb2 100644 --- a/plugins/catalog-backend-module-ldap/api-report.md +++ b/plugins/catalog-backend-module-ldap/api-report.md @@ -82,6 +82,7 @@ export class LdapClient { logger: Logger, target: string, bind?: BindConfig, + tls?: TLSConfig, ): Promise; getRootDSE(): Promise; getVendor(): Promise; @@ -154,6 +155,7 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { // @public export type LdapProviderConfig = { target: string; + tls?: TLSConfig; bind?: BindConfig; users: UserConfig; groups: GroupConfig; @@ -192,6 +194,11 @@ export function readLdapOrg( groups: GroupEntity[]; }>; +// @public +export type TLSConfig = { + rejectUnauthorized?: boolean; +}; + // @public export type UserConfig = { dn: string; diff --git a/plugins/catalog-backend-module-ldap/config.d.ts b/plugins/catalog-backend-module-ldap/config.d.ts index 8ae3145811..eb9564f20d 100644 --- a/plugins/catalog-backend-module-ldap/config.d.ts +++ b/plugins/catalog-backend-module-ldap/config.d.ts @@ -50,6 +50,14 @@ export interface Config { secret: string; }; + /** + * TLS settings + */ + tls?: { + // Node TLS rejectUnauthorized + rejectUnauthorized?: boolean; + }; + /** * The settings that govern the reading and interpretation of users. */ @@ -273,6 +281,14 @@ export interface Config { secret: string; }; + /** + * TLS settings + */ + tls?: { + // Node TLS rejectUnauthorized + rejectUnauthorized?: boolean; + }; + /** * The settings that govern the reading and interpretation of users. */ diff --git a/plugins/catalog-backend-module-ldap/src/ldap/client.ts b/plugins/catalog-backend-module-ldap/src/ldap/client.ts index e778b0cfe4..a9283ecb94 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/client.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/client.ts @@ -18,7 +18,7 @@ import { ForwardedError } from '@backstage/errors'; import ldap, { Client, SearchEntry, SearchOptions } from 'ldapjs'; import { cloneDeep } from 'lodash'; import { Logger } from 'winston'; -import { BindConfig } from './config'; +import { BindConfig, TLSConfig } from './config'; import { errorString } from './util'; import { ActiveDirectoryVendor, @@ -40,8 +40,12 @@ export class LdapClient { logger: Logger, target: string, bind?: BindConfig, + tls?: TLSConfig, ): Promise { - const client = ldap.createClient({ url: target }); + const client = ldap.createClient({ + url: target, + tlsOptions: tls, + }); // We want to have a catch-all error handler at the top, since the default // behavior of the client is to blow up the entire process when it fails, diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts index 292e9a218e..28c842294f 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.test.ts @@ -80,6 +80,7 @@ describe('readLdapConfig', () => { { target: 'target', bind: { dn: 'bdn', secret: 's' }, + tls: { rejectUnauthorized: false }, users: { dn: 'udn', options: { @@ -139,6 +140,7 @@ describe('readLdapConfig', () => { { target: 'target', bind: { dn: 'bdn', secret: 's' }, + tls: { rejectUnauthorized: false }, users: { dn: 'udn', options: { diff --git a/plugins/catalog-backend-module-ldap/src/ldap/config.ts b/plugins/catalog-backend-module-ldap/src/ldap/config.ts index 370c9491b3..9ae4b9ed8d 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/config.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/config.ts @@ -30,6 +30,8 @@ export type LdapProviderConfig = { // The prefix of the target that this matches on, e.g. // "ldaps://ds.example.net", with no trailing slash. target: string; + // TLS settings + tls?: TLSConfig; // The settings to use for the bind command. If none are specified, the bind // command is not issued. bind?: BindConfig; @@ -39,6 +41,16 @@ export type LdapProviderConfig = { groups: GroupConfig; }; +/** + * TLS settings + * + * @public + */ +export type TLSConfig = { + // Node TLS rejectUnauthorized + rejectUnauthorized?: boolean; +}; + /** * The settings to use for the a command. * @@ -185,6 +197,17 @@ export function readLdapConfig(config: Config): LdapProviderConfig[] { }); } + function readTlsConfig( + c: Config | undefined, + ): LdapProviderConfig['tls'] | undefined { + if (!c) { + return undefined; + } + return { + rejectUnauthorized: c.getOptionalBoolean('rejectUnauthorized'), + }; + } + function readBindConfig( c: Config | undefined, ): LdapProviderConfig['bind'] | undefined { @@ -312,6 +335,7 @@ export function readLdapConfig(config: Config): LdapProviderConfig[] { return providerConfigs.map(c => { const newConfig = { target: trimEnd(c.getString('target'), '/'), + tls: readTlsConfig(c.getOptionalConfig('tls')), bind: readBindConfig(c.getOptionalConfig('bind')), users: readUserConfig(c.getConfig('users')), groups: readGroupConfig(c.getConfig('groups')), diff --git a/plugins/catalog-backend-module-ldap/src/ldap/index.ts b/plugins/catalog-backend-module-ldap/src/ldap/index.ts index c3aace492f..6d7800fbfd 100644 --- a/plugins/catalog-backend-module-ldap/src/ldap/index.ts +++ b/plugins/catalog-backend-module-ldap/src/ldap/index.ts @@ -22,6 +22,7 @@ export type { GroupConfig, UserConfig, BindConfig, + TLSConfig, } from './config'; export type { LdapVendor } from './vendors'; export { diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts index b31b7ae266..b38ea51cc0 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgEntityProvider.ts @@ -179,6 +179,7 @@ export class LdapOrgEntityProvider implements EntityProvider { this.options.logger, this.options.provider.target, this.options.provider.bind, + this.options.provider.tls, ); const { users, groups } = await readLdapOrg( diff --git a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts index 74a7e153b9..fcfdb43eb1 100644 --- a/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-ldap/src/processors/LdapOrgReaderProcessor.ts @@ -103,6 +103,7 @@ export class LdapOrgReaderProcessor implements CatalogProcessor { this.logger, provider.target, provider.bind, + provider.tls, ); const { users, groups } = await readLdapOrg( client, From 3ac4522537266ec26d20e093ac0ca5ae25cf31dc Mon Sep 17 00:00:00 2001 From: qu Date: Wed, 11 May 2022 17:36:30 +0500 Subject: [PATCH 016/149] feat(catalog-backend-module-gitlab): create url location only if file exists (#12) Signed-off-by: Ruslan.Nasyrov --- .changeset/real-beers-type.md | 5 + .../src/GitLabDiscoveryProcessor.test.ts | 142 +++++++++++++++--- .../src/GitLabDiscoveryProcessor.ts | 22 ++- .../src/lib/client.ts | 30 ++++ 4 files changed, 175 insertions(+), 24 deletions(-) create mode 100644 .changeset/real-beers-type.md diff --git a/.changeset/real-beers-type.md b/.changeset/real-beers-type.md new file mode 100644 index 0000000000..106a97bffb --- /dev/null +++ b/.changeset/real-beers-type.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-gitlab': patch +--- + +do not creating url location object if file with component definition do not exists in project, that decrease count of request to gitlab with 404 status code diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts index dc4ffb7251..3f4534ad48 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts @@ -24,26 +24,36 @@ import { GitLabProject } from './lib'; const server = setupServer(); -const PROJECTS_URL = 'https://gitlab.fake/api/v4/projects'; -const GROUP_PROJECTS_URL = - 'https://gitlab.fake/api/v4/groups/group%2Fsubgroup/projects'; +const DOMAIN = 'gitlab.fake'; +const SERVER_URL = `https://${DOMAIN}`; +const API_URL = `${SERVER_URL}/api/v4`; +const PROJECTS_URL = `${API_URL}/projects`; +const GROUP_PROJECTS_URL = `${API_URL}/groups/group%2Fsubgroup/projects`; const PROJECT_LOCATION: LocationSpec = { type: 'gitlab-discovery', - target: 'https://gitlab.fake/blob/*/catalog-info.yaml', + target: `${SERVER_URL}/blob/*/catalog-info.yaml`, }; const PROJECT_LOCATION_MASTER_BRANCH: LocationSpec = { type: 'gitlab-discovery', - target: 'https://gitlab.fake/blob/master/catalog-info.yaml', + target: `${SERVER_URL}/blob/master/catalog-info.yaml`, }; const GROUP_LOCATION: LocationSpec = { type: 'gitlab-discovery', - target: 'https://gitlab.fake/group/subgroup/blob/*/catalog-info.yaml', + target: `${SERVER_URL}/group/subgroup/blob/*/catalog-info.yaml`, +}; + +const GROUP_LOCATION_CUSTOM_BRANCH: LocationSpec = { + type: 'gitlab-discovery', + target: `${SERVER_URL}/group/subgroup/blob/test/catalog-info.yaml`, }; function setupFakeServer( url: string, - callback: (request: { page: number; include_subgroups: boolean }) => { + list_projects_callback: (request: { + page: number; + include_subgroups: boolean; + }) => { data: GitLabProject[]; nextPage?: number; }, @@ -55,7 +65,7 @@ function setupFakeServer( } const page = req.url.searchParams.get('page'); const include_subgroups = req.url.searchParams.get('include_subgroups'); - const response = callback({ + const response = list_projects_callback({ page: parseInt(page!, 10), include_subgroups: include_subgroups === 'true', }); @@ -75,6 +85,20 @@ function setupFakeServer( ctx.json(filteredData), ); }), + rest.head( + `${API_URL}/projects/:project_path/repository/files/:file_path`, + (req, res, ctx) => { + if (req.headers.get('private-token') !== 'test-token') { + return res(ctx.status(401), ctx.json({})); + } + const ref = req.url.searchParams.get('ref'); + + if (ref === 'main' || ref === 'master') { + return res(ctx.status(200)); + } + return res(ctx.status(404)); + }, + ), ); } @@ -86,8 +110,8 @@ function getConfig(): any { integrations: { gitlab: [ { - host: 'gitlab.fake', - apiBaseUrl: 'https://gitlab.fake/api/v4', + host: DOMAIN, + apiBaseUrl: API_URL, token: 'test-token', }, ], @@ -95,11 +119,18 @@ function getConfig(): any { }; } -function getProcessor(config?: any): GitLabDiscoveryProcessor { +function getProcessor({ + config, + options, +}: { + config?: any; + options?: Partial[1]>; +} = {}): GitLabDiscoveryProcessor { return GitLabDiscoveryProcessor.fromConfig( new ConfigReader(config || getConfig()), { logger: getVoidLogger(), + ...options, }, ); } @@ -163,6 +194,15 @@ describe('GitlabDiscoveryProcessor', () => { default_branch: 'main', last_activity_at: '2021-08-05T11:03:05.774Z', web_url: 'https://gitlab.fake/1', + path_with_namespace: '1', + }, + { + id: 2, + archived: false, + default_branch: 'main', + last_activity_at: '2021-08-05T11:03:05.774Z', + web_url: 'https://gitlab.fake/g/2', + path_with_namespace: 'g/2', }, ], nextPage: 2, @@ -170,26 +210,29 @@ describe('GitlabDiscoveryProcessor', () => { case 2: return { data: [ - { - id: 2, - archived: false, - default_branch: 'master', - last_activity_at: '2021-08-05T11:03:05.774Z', - web_url: 'https://gitlab.fake/2', - }, { id: 3, - archived: true, // ARCHIVED + archived: false, default_branch: 'master', last_activity_at: '2021-08-05T11:03:05.774Z', web_url: 'https://gitlab.fake/3', + path_with_namespace: '3', }, { id: 4, + archived: true, // ARCHIVED + default_branch: 'master', + last_activity_at: '2021-08-05T11:03:05.774Z', + web_url: 'https://gitlab.fake/4', + path_with_namespace: '4', + }, + { + id: 5, archived: false, default_branch: undefined, // MISSING DEFAULT BRANCH last_activity_at: '2021-08-05T11:03:05.774Z', - web_url: 'https://gitlab.fake/4', + web_url: 'https://gitlab.fake/g/5', + path_with_namespace: 'g/5', }, ], }; @@ -215,7 +258,15 @@ describe('GitlabDiscoveryProcessor', () => { type: 'location', location: { type: 'url', - target: 'https://gitlab.fake/2/-/blob/master/catalog-info.yaml', + target: 'https://gitlab.fake/g/2/-/blob/main/catalog-info.yaml', + presence: 'optional', + }, + }, + { + type: 'location', + location: { + type: 'url', + target: 'https://gitlab.fake/3/-/blob/master/catalog-info.yaml', presence: 'optional', }, }, @@ -235,6 +286,7 @@ describe('GitlabDiscoveryProcessor', () => { default_branch: 'main', last_activity_at: '2021-08-05T11:03:05.774Z', web_url: 'https://gitlab.fake/1', + path_with_namespace: '1', }, ], }; @@ -275,6 +327,7 @@ describe('GitlabDiscoveryProcessor', () => { default_branch: 'main', last_activity_at: '2021-08-05T11:03:05.774Z', web_url: 'https://gitlab.fake/1', + path_with_namespace: '1', }, ], }; @@ -291,6 +344,47 @@ describe('GitlabDiscoveryProcessor', () => { expect(result).toHaveLength(1); }); + it('can filter based on file existing', async () => { + const processor = getProcessor({ options: { checkFileExistence: true } }); + setupFakeServer(GROUP_PROJECTS_URL, request => { + if (!request.include_subgroups) { + throw new Error('include_subgroups should be set'); + } + switch (request.page) { + case 1: + return { + data: [ + { + id: 1, + archived: false, + default_branch: 'main', + last_activity_at: '2021-08-05T11:03:05.774Z', + web_url: 'https://gitlab.fake/1', + path_with_namespace: '1', + }, + { + id: 1, + archived: false, + default_branch: 'main', + last_activity_at: '2021-08-05T11:03:05.774Z', + web_url: 'https://gitlab.fake/g/2', + path_with_namespace: 'g/2', + }, + ], + }; + default: + throw new Error('Invalid request'); + } + }); + + const result: any[] = []; + await processor.readLocation(GROUP_LOCATION_CUSTOM_BRANCH, false, e => { + result.push(e); + }); + // If everything was set up correctly, we should have received the fake repo specified above + expect(result).toHaveLength(0); + }); + it('uses the previous scan timestamp to filter', async () => { const processor = getProcessor(); setupFakeServer(PROJECTS_URL, request => { @@ -304,6 +398,7 @@ describe('GitlabDiscoveryProcessor', () => { default_branch: 'main', last_activity_at: '2000-01-01T00:00:00Z', web_url: 'https://gitlab.fake/1', + path_with_namespace: '1', }, { id: 2, @@ -311,6 +406,7 @@ describe('GitlabDiscoveryProcessor', () => { default_branch: 'main', last_activity_at: '2002-01-01T00:00:00Z', web_url: 'https://gitlab.fake/2', + path_with_namespace: '2', }, ], }; @@ -349,7 +445,7 @@ describe('GitlabDiscoveryProcessor', () => { const config = getConfig(); config.integrations.gitlab[0].token = 'invalid'; await expect( - getProcessor(config).readLocation(PROJECT_LOCATION, false, _ => {}), + getProcessor({ config }).readLocation(PROJECT_LOCATION, false, _ => {}), ).rejects.toThrow(/Unauthorized/); }); @@ -357,7 +453,7 @@ describe('GitlabDiscoveryProcessor', () => { const config = getConfig(); delete config.integrations; await expect( - getProcessor(config).readLocation(PROJECT_LOCATION, false, _ => {}), + getProcessor({ config }).readLocation(PROJECT_LOCATION, false, _ => {}), ).rejects.toThrow(/no GitLab integration/); }); diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts index 01de1f12a0..e5cb7b15db 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts @@ -41,8 +41,12 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrationRegistry; private readonly logger: Logger; private readonly cache: CacheClient; + private readonly checkFileExistence: boolean; - static fromConfig(config: Config, options: { logger: Logger }) { + static fromConfig( + config: Config, + options: { logger: Logger; checkFileExistence?: boolean }, + ): GitLabDiscoveryProcessor { const integrations = ScmIntegrations.fromConfig(config); const pluginCache = CacheManager.fromConfig(config).forPlugin('gitlab-discovery'); @@ -58,10 +62,12 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { integrations: ScmIntegrationRegistry; pluginCache: PluginCacheManager; logger: Logger; + checkFileExistence?: boolean; }) { this.integrations = options.integrations; this.cache = options.pluginCache.getClient(); this.logger = options.logger; + this.checkFileExistence = options.checkFileExistence || false; } getProcessorName(): string { @@ -114,6 +120,20 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { continue; } + if (this.checkFileExistence) { + const project_branch = branch === '*' ? project.default_branch : branch; + + const projectHasFile: boolean = await client.hasFile( + project.path_with_namespace, + project_branch, + catalogPath, + ); + + if (!projectHasFile) { + continue; + } + } + res.matches.push(project); } diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.ts index 4d42f5e068..aa8eb22c19 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/client.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/client.ts @@ -63,6 +63,36 @@ export class GitLabClient { return this.pagedRequest(`/projects`, options); } + async hasFile( + projectPath: string, + branch: string, + filePath: string, + ): Promise { + const endpoint: string = `/projects/${encodeURIComponent( + projectPath, + )}/repository/files/${encodeURIComponent(filePath)}`; + const request = new URL(`${this.config.apiBaseUrl}${endpoint}`); + request.searchParams.append('ref', branch); + + this.logger.debug(`Fetching: ${request.toString()}`); + + const response = await fetch(request.toString(), { + headers: getGitLabRequestOptions(this.config).headers, + method: 'HEAD', + }); + + if (!response.ok) { + this.logger.debug( + `Unexpected response when fetching ${request.toString()}. Expected 200 but got ${ + response.status + } - ${response.statusText}`, + ); + return false; + } + + return true; + } + /** * Performs a request against a given paginated GitLab endpoint. * From ba95c81bff16e2fc506148ddbf0858d7c30dd074 Mon Sep 17 00:00:00 2001 From: "Ruslan.Nasyrov" Date: Fri, 13 May 2022 14:55:05 +0500 Subject: [PATCH 017/149] feat/catalog-backend-module-gitlab: create url location only if file exists (fixes #1) Signed-off-by: Ruslan.Nasyrov --- .changeset/real-beers-type.md | 11 +++++++++- .../src/GitLabDiscoveryProcessor.test.ts | 20 +++++++++++++------ .../src/GitLabDiscoveryProcessor.ts | 11 +++++----- .../src/lib/client.ts | 7 ------- 4 files changed, 30 insertions(+), 19 deletions(-) diff --git a/.changeset/real-beers-type.md b/.changeset/real-beers-type.md index 106a97bffb..e3ab67cfca 100644 --- a/.changeset/real-beers-type.md +++ b/.changeset/real-beers-type.md @@ -2,4 +2,13 @@ '@backstage/plugin-catalog-backend-module-gitlab': patch --- -do not creating url location object if file with component definition do not exists in project, that decrease count of request to gitlab with 404 status code +do not create url location object if file with component definition do not exists in project, that decrease count of request to gitlab with 404 status code. Now we can create processor with new flag to enable this logic: + +```ts +const processor = GitLabDiscoveryProcessor.fromConfig(config, { + logger, + skipReposWithoutExactFileMatch: true, +}); +``` + +**WARNING:** This new functionality does not support globs in the repo filepath diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts index 3f4534ad48..278efe8036 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.test.ts @@ -29,6 +29,7 @@ const SERVER_URL = `https://${DOMAIN}`; const API_URL = `${SERVER_URL}/api/v4`; const PROJECTS_URL = `${API_URL}/projects`; const GROUP_PROJECTS_URL = `${API_URL}/groups/group%2Fsubgroup/projects`; +const EXISTING_PROJECT_PATH = 'exist'; const PROJECT_LOCATION: LocationSpec = { type: 'gitlab-discovery', @@ -50,7 +51,7 @@ const GROUP_LOCATION_CUSTOM_BRANCH: LocationSpec = { function setupFakeServer( url: string, - list_projects_callback: (request: { + listProjectsCallback: (request: { page: number; include_subgroups: boolean; }) => { @@ -65,7 +66,7 @@ function setupFakeServer( } const page = req.url.searchParams.get('page'); const include_subgroups = req.url.searchParams.get('include_subgroups'); - const response = list_projects_callback({ + const response = listProjectsCallback({ page: parseInt(page!, 10), include_subgroups: include_subgroups === 'true', }); @@ -96,6 +97,11 @@ function setupFakeServer( if (ref === 'main' || ref === 'master') { return res(ctx.status(200)); } + + if (EXISTING_PROJECT_PATH === req.params.project_path) { + return res(ctx.status(200)); + } + return res(ctx.status(404)); }, ), @@ -345,7 +351,9 @@ describe('GitlabDiscoveryProcessor', () => { }); it('can filter based on file existing', async () => { - const processor = getProcessor({ options: { checkFileExistence: true } }); + const processor = getProcessor({ + options: { skipReposWithoutExactFileMatch: true }, + }); setupFakeServer(GROUP_PROJECTS_URL, request => { if (!request.include_subgroups) { throw new Error('include_subgroups should be set'); @@ -367,8 +375,8 @@ describe('GitlabDiscoveryProcessor', () => { archived: false, default_branch: 'main', last_activity_at: '2021-08-05T11:03:05.774Z', - web_url: 'https://gitlab.fake/g/2', - path_with_namespace: 'g/2', + web_url: `https://gitlab.fake/${EXISTING_PROJECT_PATH}`, + path_with_namespace: EXISTING_PROJECT_PATH, }, ], }; @@ -382,7 +390,7 @@ describe('GitlabDiscoveryProcessor', () => { result.push(e); }); // If everything was set up correctly, we should have received the fake repo specified above - expect(result).toHaveLength(0); + expect(result).toHaveLength(1); }); it('uses the previous scan timestamp to filter', async () => { diff --git a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts index e5cb7b15db..73b8c52358 100644 --- a/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts +++ b/plugins/catalog-backend-module-gitlab/src/GitLabDiscoveryProcessor.ts @@ -41,11 +41,11 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { private readonly integrations: ScmIntegrationRegistry; private readonly logger: Logger; private readonly cache: CacheClient; - private readonly checkFileExistence: boolean; + private readonly skipReposWithoutExactFileMatch: boolean; static fromConfig( config: Config, - options: { logger: Logger; checkFileExistence?: boolean }, + options: { logger: Logger; skipReposWithoutExactFileMatch?: boolean }, ): GitLabDiscoveryProcessor { const integrations = ScmIntegrations.fromConfig(config); const pluginCache = @@ -62,12 +62,13 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { integrations: ScmIntegrationRegistry; pluginCache: PluginCacheManager; logger: Logger; - checkFileExistence?: boolean; + skipReposWithoutExactFileMatch?: boolean; }) { this.integrations = options.integrations; this.cache = options.pluginCache.getClient(); this.logger = options.logger; - this.checkFileExistence = options.checkFileExistence || false; + this.skipReposWithoutExactFileMatch = + options.skipReposWithoutExactFileMatch || false; } getProcessorName(): string { @@ -120,7 +121,7 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { continue; } - if (this.checkFileExistence) { + if (this.skipReposWithoutExactFileMatch) { const project_branch = branch === '*' ? project.default_branch : branch; const projectHasFile: boolean = await client.hasFile( diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.ts index aa8eb22c19..5c43db6e68 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/client.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/client.ts @@ -74,19 +74,12 @@ export class GitLabClient { const request = new URL(`${this.config.apiBaseUrl}${endpoint}`); request.searchParams.append('ref', branch); - this.logger.debug(`Fetching: ${request.toString()}`); - const response = await fetch(request.toString(), { headers: getGitLabRequestOptions(this.config).headers, method: 'HEAD', }); if (!response.ok) { - this.logger.debug( - `Unexpected response when fetching ${request.toString()}. Expected 200 but got ${ - response.status - } - ${response.statusText}`, - ); return false; } From cf864e9e25489b5cdad46ab7849c68d67eff8a9f Mon Sep 17 00:00:00 2001 From: "Ruslan.Nasyrov" Date: Tue, 17 May 2022 13:43:00 +0500 Subject: [PATCH 018/149] feat/catalog-backend-module-gitlab: create url location only if file exists (fixes #2) Signed-off-by: Ruslan.Nasyrov --- .changeset/real-beers-type.md | 4 ++-- docs/integrations/gitlab/discovery.md | 5 ++++- plugins/catalog-backend-module-gitlab/api-report.md | 1 + 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.changeset/real-beers-type.md b/.changeset/real-beers-type.md index e3ab67cfca..f09aafe007 100644 --- a/.changeset/real-beers-type.md +++ b/.changeset/real-beers-type.md @@ -2,7 +2,7 @@ '@backstage/plugin-catalog-backend-module-gitlab': patch --- -do not create url location object if file with component definition do not exists in project, that decrease count of request to gitlab with 404 status code. Now we can create processor with new flag to enable this logic: +do not create location object if file with component definition do not exists in project, that decrease count of request to gitlab with 404 status code. Now we can create processor with new flag to enable this logic: ```ts const processor = GitLabDiscoveryProcessor.fromConfig(config, { @@ -11,4 +11,4 @@ const processor = GitLabDiscoveryProcessor.fromConfig(config, { }); ``` -**WARNING:** This new functionality does not support globs in the repo filepath +**WARNING:** This new functionality does not support globs in the repo file path diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index 8d805fa362..2ce4b59ca6 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -47,6 +47,9 @@ of your backend. ): Promise { const builder = await CatalogBuilder.create(env); + builder.addProcessor( -+ GitLabDiscoveryProcessor.fromConfig(env.config, { logger: env.logger }) ++ GitLabDiscoveryProcessor.fromConfig(env.config, { ++ logger: env.logger, ++ skipReposWithoutExactFileMatch: true, ++ }) + ); ``` diff --git a/plugins/catalog-backend-module-gitlab/api-report.md b/plugins/catalog-backend-module-gitlab/api-report.md index ec10f80939..63e6fe7ccb 100644 --- a/plugins/catalog-backend-module-gitlab/api-report.md +++ b/plugins/catalog-backend-module-gitlab/api-report.md @@ -16,6 +16,7 @@ export class GitLabDiscoveryProcessor implements CatalogProcessor { config: Config, options: { logger: Logger; + skipReposWithoutExactFileMatch?: boolean; }, ): GitLabDiscoveryProcessor; // (undocumented) From 6f8dfe10713f089214651324f5460b9d2ca9f32a Mon Sep 17 00:00:00 2001 From: "Ruslan.Nasyrov" Date: Wed, 18 May 2022 15:04:19 +0500 Subject: [PATCH 019/149] feat/catalog-backend-module-gitlab: create url location only if file exists (fixes #3) Signed-off-by: Ruslan.Nasyrov --- docs/integrations/gitlab/discovery.md | 2 +- plugins/catalog-backend-module-gitlab/src/lib/client.ts | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index 2ce4b59ca6..5a5fdb1d92 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -49,7 +49,7 @@ of your backend. + builder.addProcessor( + GitLabDiscoveryProcessor.fromConfig(env.config, { + logger: env.logger, -+ skipReposWithoutExactFileMatch: true, ++ skipReposWithoutExactFileMatch: false, + }) + ); ``` diff --git a/plugins/catalog-backend-module-gitlab/src/lib/client.ts b/plugins/catalog-backend-module-gitlab/src/lib/client.ts index 5c43db6e68..27214f3be8 100644 --- a/plugins/catalog-backend-module-gitlab/src/lib/client.ts +++ b/plugins/catalog-backend-module-gitlab/src/lib/client.ts @@ -80,6 +80,13 @@ export class GitLabClient { }); if (!response.ok) { + if (response.status >= 500) { + this.logger.debug( + `Unexpected response when fetching ${request.toString()}. Expected 200 but got ${ + response.status + } - ${response.statusText}`, + ); + } return false; } From d08496cd94efce04920018e79b792b8e51864e5a Mon Sep 17 00:00:00 2001 From: Jack Palmer Date: Wed, 18 May 2022 15:18:27 +0200 Subject: [PATCH 020/149] feat: Add console warning when app.baseUrl and backend.baseUrl are identical Signed-off-by: Jack Palmer --- .../cli/src/commands/start/startFrontend.ts | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/commands/start/startFrontend.ts b/packages/cli/src/commands/start/startFrontend.ts index e5500c310b..4c08ff6d5f 100644 --- a/packages/cli/src/commands/start/startFrontend.ts +++ b/packages/cli/src/commands/start/startFrontend.ts @@ -62,14 +62,30 @@ export async function startFrontend(options: StartAppOptions) { } const { name } = await fs.readJson(paths.resolveTarget('package.json')); + const config = await loadCliConfig({ + args: options.configPaths, + fromPackage: name, + withFilteredKeys: true, + }); + + const appBaseUrl = config.frontendConfig.getString('app.baseUrl'); + const backendBaseUrl = config.frontendConfig.getString('backend.baseUrl'); + if (appBaseUrl === backendBaseUrl) { + console.log( + chalk.yellow( + `⚠️ Conflict between app baseUrl and backend baseUrl: + + app.baseUrl: ${appBaseUrl} + backend.baseUrl: ${appBaseUrl} +`, + ), + ); + } + const waitForExit = await serveBundle({ entry: options.entry, checksEnabled: options.checksEnabled, - ...(await loadCliConfig({ - args: options.configPaths, - fromPackage: name, - withFilteredKeys: true, - })), + ...config, }); await waitForExit(); From db27a98afa3246f9e3fecfbb3d0cf59a814767a5 Mon Sep 17 00:00:00 2001 From: Jack Palmer Date: Wed, 18 May 2022 15:25:51 +0200 Subject: [PATCH 021/149] changeset: added Signed-off-by: Jack Palmer --- .changeset/fuzzy-colts-enjoy.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fuzzy-colts-enjoy.md diff --git a/.changeset/fuzzy-colts-enjoy.md b/.changeset/fuzzy-colts-enjoy.md new file mode 100644 index 0000000000..5ebccd59a3 --- /dev/null +++ b/.changeset/fuzzy-colts-enjoy.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': minor +--- + +Added console warning to frontend start when the `app.baseUrl` and `backend.baseUrl` are identical From 6de866ea74a56183c64b270f8d48c1be1d86ac9e Mon Sep 17 00:00:00 2001 From: Jack Palmer Date: Thu, 19 May 2022 09:25:41 +0100 Subject: [PATCH 022/149] fix: Change changeset to patch Signed-off-by: Jack Palmer --- .changeset/{fuzzy-colts-enjoy.md => shiny-clocks-joke.md} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .changeset/{fuzzy-colts-enjoy.md => shiny-clocks-joke.md} (81%) diff --git a/.changeset/fuzzy-colts-enjoy.md b/.changeset/shiny-clocks-joke.md similarity index 81% rename from .changeset/fuzzy-colts-enjoy.md rename to .changeset/shiny-clocks-joke.md index 5ebccd59a3..3784cb1b7d 100644 --- a/.changeset/fuzzy-colts-enjoy.md +++ b/.changeset/shiny-clocks-joke.md @@ -1,5 +1,5 @@ --- -'@backstage/cli': minor +'@backstage/cli': patch --- Added console warning to frontend start when the `app.baseUrl` and `backend.baseUrl` are identical From 76f490ee0f6e57414ad12fae185a470d12899a5b Mon Sep 17 00:00:00 2001 From: Jack Palmer Date: Thu, 19 May 2022 11:17:17 +0100 Subject: [PATCH 023/149] fix: Add resolution steps to warning Signed-off-by: Jack Palmer --- packages/cli/src/commands/start/startFrontend.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/cli/src/commands/start/startFrontend.ts b/packages/cli/src/commands/start/startFrontend.ts index 4c08ff6d5f..f6923632c8 100644 --- a/packages/cli/src/commands/start/startFrontend.ts +++ b/packages/cli/src/commands/start/startFrontend.ts @@ -44,7 +44,7 @@ export async function startFrontend(options: StartAppOptions) { if (problemPackages.length > 1) { console.log( chalk.yellow( - `⚠️ Some of the following packages may be outdated or have duplicate installations: + `⚠️ Some of the following packages may be outdated or have duplicate installations: ${uniq(problemPackages).join(', ')} `, @@ -52,7 +52,7 @@ export async function startFrontend(options: StartAppOptions) { ); console.log( chalk.yellow( - `⚠️ This can be resolved using the following command: + `⚠️ This can be resolved using the following command: yarn backstage-cli versions:check --fix `, @@ -73,10 +73,14 @@ export async function startFrontend(options: StartAppOptions) { if (appBaseUrl === backendBaseUrl) { console.log( chalk.yellow( - `⚠️ Conflict between app baseUrl and backend baseUrl: + `⚠️ Conflict between app baseUrl and backend baseUrl: - app.baseUrl: ${appBaseUrl} - backend.baseUrl: ${appBaseUrl} + app.baseUrl: ${appBaseUrl} + backend.baseUrl: ${appBaseUrl} + + Must have unique hostname and/or ports. + + This can be resolved by changing app.baseUrl and backend.baseUrl to point to their respective local development ports. `, ), ); From 838474ff16fdc4e4e32e4560e30f84e40940a554 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 19 May 2022 17:19:39 +0200 Subject: [PATCH 024/149] ADOPTERS.md: add 4 new adopters Signed-off-by: Patrik Oldsberg --- ADOPTERS.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/ADOPTERS.md b/ADOPTERS.md index 3915aee949..8dd2213158 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -126,4 +126,7 @@ _If you're using Backstage in your organization, please try to add your company | [Avalia Systems](https://avalia.io) | [Olivier Liechti](https://github.com/wasadigi), [Fabio Velloso](https://github.com/fabiovelloso) | Innersource, software analytics, knowledge base for 360 software assessments, collaborative applications, hub for tracking and sharing IP assets. | | [Albert Heijn](https://ah.technology) | [Joost Hofman](https://github.com/joosthofman), [Reindrich Geerman](https://github.com/reinst) | Single point of entry for all our engineers (Developer portal), Tech radar, catalog, templates (paved roads) and tech documentation. | | [Wise, formerly TransferWise](https://wise.com) | [Andrew Beveridge](https://github.com/beveradb) | It's early days for us, we're trying to start small with catalog, tech docs and unified search. Future ambitious vision includes scaffolder for one-click component addition, building out integrations with CI/CD tooling, kubernetes clusters, monitoring/alerting tooling etc. and aiming for a frictionless "golden path" for engineers! 🚀 | - +|[Happy Money](http://happymoney.com/)|[Akshit Lomash](mailto:alomash@happymoney.com)|We are moving from a monolith to microservices-based architecture. We are developing a developer portal based on Backstage to create a service catalog for our new services. All the services created are onboarded Backstage and engineering teams are using a cookie-cutter-based template from backstage to initiate a new service. +|[Lightspeed](http://lightspeedhq.com/)|[Marcus Crane](mailto:marcus.crane@lightspeedhq.com)|We use it within our X-Series division (https://vendhq.com) to catalog ~100+ systems and ~350 components! +|[Siemens](https://www.siemens.com/global/en.html)|[Nizar Chaouch](mailto:nizar.chaouch@siemens.com)|We are using Backstage as our Developer portal +|[The Warehouse Group](https://www.thewarehouse.co.nz)|[Matt Law](mailto:matt.law@thewarehouse.co.nz)|Backstage enables us to bootstrap our middleware environment of new services for our Dev teams in a matter of seconds. CI, CD, testing, logging, deployments are all taken care of to get them up and running in less than 60 seconds. From afb4db792c1f35744c3832ac63612a05536511de Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Thu, 19 May 2022 16:03:32 +0000 Subject: [PATCH 025/149] fix(deps): update dependency @keyv/redis to v2.3.6 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9311ad5a3b..c384ad49cd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3063,9 +3063,9 @@ integrity sha512-zMM9Ds+SawiUkakS7y94Ymqx+S0ORzpG3frZirN3l+UlXUmSUR7hF4wxCVqW+ei94JzV5kt0uXBcoOEAuiydrw== "@keyv/redis@^2.2.3": - version "2.3.5" - resolved "https://registry.npmjs.org/@keyv/redis/-/redis-2.3.5.tgz#8ff4fecf9b5520cac2a346e6216db59474df3385" - integrity sha512-dO9sn1HEPVkh1b7Cn/jcLyi7sDFKmnGQIaXw4wTIhJzkwcev24CffvEU26B+KRUNBu/2PgNVNRfFmEpy1smb4w== + version "2.3.6" + resolved "https://registry.npmjs.org/@keyv/redis/-/redis-2.3.6.tgz#462991af60b5b35af4e9ac8d05ad99a08ae2cfc2" + integrity sha512-MKRAeWaKI7zpGb+lSgAAlznQi725NEN18I/Uj2CJysk1o6hBqyXyxCvyI+b9qKwoFAkzxoogdvONIpstzI7t/Q== dependencies: ioredis "^5.0.4" From 65840b17be0c935c8f4f0c32c45ebec009d75cfb Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Thu, 19 May 2022 17:00:50 -0400 Subject: [PATCH 026/149] fix(sidebarItem): only add arrow icon to items with a submenu Signed-off-by: Phil Kuang --- .changeset/mean-turtles-reply.md | 5 +++++ packages/core-components/src/layout/Sidebar/Items.tsx | 7 +++++-- 2 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 .changeset/mean-turtles-reply.md diff --git a/.changeset/mean-turtles-reply.md b/.changeset/mean-turtles-reply.md new file mode 100644 index 0000000000..f3b21989d1 --- /dev/null +++ b/.changeset/mean-turtles-reply.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Fix issue where right arrow icon was incorrectly added to side bar items without a sub-menu diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index c004f6904c..fde3c85c96 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -259,6 +259,7 @@ type SidebarItemBaseProps = { icon: IconComponent; text?: string; hasNotifications?: boolean; + hasSubmenu?: boolean; disableHighlight?: boolean; className?: string; }; @@ -356,6 +357,7 @@ const SidebarItemBase = forwardRef((props, ref) => { icon: Icon, text, hasNotifications = false, + hasSubmenu = false, disableHighlight = false, onClick, children, @@ -370,12 +372,12 @@ const SidebarItemBase = forwardRef((props, ref) => { const { isOpen } = useContext(SidebarContext); const divStyle = - !isOpen && children ? { display: 'flex', marginLeft: '24px' } : {}; + !isOpen && hasSubmenu ? { display: 'flex', marginLeft: '24px' } : {}; const displayItemIcon = (
- {!isOpen && children ? : <>} + {!isOpen && hasSubmenu ? : <>}
); @@ -492,6 +494,7 @@ const SidebarItemWithSubmenu = ({ className={classnames(isHoveredOn && classes.highlighted)} > From 05be42097133f5d26415b170ab102b0cf515f0e9 Mon Sep 17 00:00:00 2001 From: Matt Ng Date: Thu, 19 May 2022 18:06:42 -0400 Subject: [PATCH 027/149] Header Hierarchy Jump on Catalog Import Page Signed-off-by: Matt Ng --- .changeset/violet-apples-repair.md | 5 +++++ .../src/components/ImportInfoCard/ImportInfoCard.tsx | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) create mode 100644 .changeset/violet-apples-repair.md diff --git a/.changeset/violet-apples-repair.md b/.changeset/violet-apples-repair.md new file mode 100644 index 0000000000..8bf6a6c1c9 --- /dev/null +++ b/.changeset/violet-apples-repair.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-import': minor +--- + +Updated catalog import page text so they go in the correct hierarchy order diff --git a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx index c879cbe998..339d71b476 100644 --- a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx +++ b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx @@ -54,6 +54,7 @@ export const ImportInfoCard = (props: ImportInfoCardProps) => { return ( { Enter the URL to your source code repository to add it to {appTitle}. - Link to an existing entity file + Link to an existing entity file Example: {exampleLocationUrl} @@ -72,7 +73,7 @@ export const ImportInfoCard = (props: ImportInfoCardProps) => { {hasGithubIntegration && ( <> - + Link to a repository{' '} From 30824042f103aee5ec4cdd2473d375a847ae71cf Mon Sep 17 00:00:00 2001 From: "Ruslan.Nasyrov" Date: Thu, 19 May 2022 11:11:02 +0500 Subject: [PATCH 028/149] feat/catalog-backend-module-gitlab: create url location only if file exists (fixes #4) Signed-off-by: Ruslan.Nasyrov --- docs/integrations/gitlab/discovery.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/integrations/gitlab/discovery.md b/docs/integrations/gitlab/discovery.md index 5a5fdb1d92..3715b89920 100644 --- a/docs/integrations/gitlab/discovery.md +++ b/docs/integrations/gitlab/discovery.md @@ -47,9 +47,8 @@ of your backend. ): Promise { const builder = await CatalogBuilder.create(env); + builder.addProcessor( -+ GitLabDiscoveryProcessor.fromConfig(env.config, { -+ logger: env.logger, -+ skipReposWithoutExactFileMatch: false, -+ }) ++ GitLabDiscoveryProcessor.fromConfig(env.config, { logger: env.logger }) + ); ``` + +If you don't want create location object if file with component definition do not exists in project, you can set the `skipReposWithoutExactFileMatch` option. That can reduce count of request to gitlab with 404 status code. From 443accb4bb2e26f029b0902a4e7d5b36e53b4931 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 19 May 2022 14:23:52 +0200 Subject: [PATCH 029/149] fix(techdocs): use object routes on entity docs pages Signed-off-by: Camila Belo --- .../app/src/components/catalog/EntityPage.tsx | 27 ++++++++++++++++--- plugins/techdocs/api-report.md | 6 +++-- plugins/techdocs/src/Router.tsx | 24 +++++++++++------ 3 files changed, 44 insertions(+), 13 deletions(-) diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 22d6cc3ab1..c0edbc6ed0 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -140,6 +140,12 @@ import { EntityGoCdContent, isGoCdAvailable } from '@backstage/plugin-gocd'; import React, { ReactNode, useMemo, useState } from 'react'; +import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; +import { + TextSize, + ReportIssue, +} from '@backstage/plugin-techdocs-module-addons-contrib'; + const customEntityFilterKind = ['Component', 'API', 'System']; const EntityLayoutWrapper = (props: { children?: ReactNode }) => { @@ -398,7 +404,12 @@ const serviceEntityPage = ( - + + + + + + - + + + + + + - + + + + + + diff --git a/plugins/techdocs/api-report.md b/plugins/techdocs/api-report.md index a8204bc9af..b2090bc879 100644 --- a/plugins/techdocs/api-report.md +++ b/plugins/techdocs/api-report.md @@ -111,7 +111,9 @@ export type DocsTableRow = { }; // @public -export const EmbeddedDocsRouter: (props: PropsWithChildren<{}>) => JSX.Element; +export const EmbeddedDocsRouter: ( + props: PropsWithChildren<{}>, +) => JSX.Element | null; // @public export const EntityListDocsGrid: () => JSX.Element; @@ -153,7 +155,7 @@ export type EntityListDocsTableProps = { // @public export const EntityTechdocsContent: (props: { children?: ReactNode; -}) => JSX.Element; +}) => JSX.Element | null; // @public export const isTechDocsAvailable: (entity: Entity) => boolean; diff --git a/plugins/techdocs/src/Router.tsx b/plugins/techdocs/src/Router.tsx index de63838bc8..12b3ddf611 100644 --- a/plugins/techdocs/src/Router.tsx +++ b/plugins/techdocs/src/Router.tsx @@ -15,7 +15,7 @@ */ import React, { PropsWithChildren } from 'react'; -import { Route, Routes } from 'react-router-dom'; +import { Route, Routes, useRoutes } from 'react-router-dom'; import { Entity } from '@backstage/catalog-model'; import { useEntity } from '@backstage/plugin-catalog-react'; @@ -61,17 +61,25 @@ export const EmbeddedDocsRouter = (props: PropsWithChildren<{}>) => { const { children } = props; const { entity } = useEntity(); + // Using objects instead of elements, otherwise "outlet" will be null on sub-pages and add-ons won't render + const element = useRoutes([ + { + path: '/*', + element: , + children: [ + { + path: '/*', + element: children, + }, + ], + }, + ]); + const projectId = entity.metadata.annotations?.[TECHDOCS_ANNOTATION]; if (!projectId) { return ; } - return ( - - }> - {children} - - - ); + return element; }; From 89e78851cc7b0f250edbd8f3e98e6613f5607476 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 19 May 2022 16:37:19 +0200 Subject: [PATCH 030/149] feat(create-app): add docs addons on entity page Signed-off-by: Camila Belo --- .../app/src/components/catalog/EntityPage.tsx | 23 ++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx index 84d094410b..2695f4033e 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx @@ -68,6 +68,11 @@ import { RELATION_PROVIDES_API, } from '@backstage/catalog-model'; +import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; +import { + ReportIssue, +} from '@backstage/plugin-techdocs-module-addons-contrib'; + const cicdContent = ( // This is an example of how you can implement your company's logic in entity page. // You can for example enforce that all components of type 'service' should use GitHubActions @@ -167,7 +172,11 @@ const serviceEntityPage = ( - + + + + + ); @@ -194,7 +203,11 @@ const websiteEntityPage = ( - + + + + + ); @@ -213,7 +226,11 @@ const defaultEntityPage = ( - + + + + + ); From 9bf2f0f476c54eb47ee30c8f39117b1df410ab16 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 19 May 2022 16:40:29 +0200 Subject: [PATCH 031/149] test(cypress): docs addons on catalog sub-pages Signed-off-by: Camila Belo --- .../src/integration/plugins/catalog.spec.ts | 113 ++++++++++++++++++ .../src/integration/plugins/techdocs.spec.ts | 39 +++++- 2 files changed, 151 insertions(+), 1 deletion(-) diff --git a/cypress/src/integration/plugins/catalog.spec.ts b/cypress/src/integration/plugins/catalog.spec.ts index adf9b9a384..50ef3145e5 100644 --- a/cypress/src/integration/plugins/catalog.spec.ts +++ b/cypress/src/integration/plugins/catalog.spec.ts @@ -82,5 +82,118 @@ describe('Catalog', () => { .contains('Sub-page 1') .should('be.visible'); }); + + it('Should render addons on docs tab homepage', () => { + cy.loginAsGuest(); + + cy.visit('/catalog'); + + cy.contains('techdocs-e2e-fixture').click(); + + cy.location().should(loc => { + expect(loc.pathname).to.eq( + '/catalog/default/component/techdocs-e2e-fixture', + ); + }); + + cy.getCatalogDocsTab().click(); + + cy.wait(300); + + cy.getTechDocsShadowRoot() + .find('h1') + .contains('Home page') + .should('be.visible'); + + // highlight a snippet of text + cy.getTechDocsShadowRoot() + .find('article > p') + .then($el => { + const el = $el[0]; + const document = el.ownerDocument; + const range = document.createRange(); + range.selectNodeContents(el); + document?.getSelection()?.removeAllRanges(); + document?.getSelection()?.addRange(range); + }); + + cy.document().trigger('selectionchange'); + + // wait for new issue default debounce time + cy.wait(600); + + // assert that the new issue button has a right url + cy.getTechDocsShadowRoot() + .contains('Open new Github issue') + .should( + 'have.attr', + 'href', + 'https://github.com/backstage/backstage/issues/new?title=Documentation%20feedback%3A%20This%20is%20a%20basic%20documentation%20used%20for%20end-to-end%20tests.&body=%23%23%20Documentation%20Feedback%20%F0%9F%93%9D%0A%0A%20%23%23%23%23%20The%20highlighted%20text%3A%20%0A%0A%20%3E%20This%20is%20a%20basic%20documentation%20used%20for%20end-to-end%20tests.%0A%0A%20%23%23%23%23%20The%20comment%20on%20the%20text%3A%20%0A%20_%3Ereplace%20this%20line%20with%20your%20comment%3C_%0A%0A%20___%0ABackstage%20URL%3A%20%3Chttp%3A%2F%2Flocalhost%3A7007%2Fcatalog%2Fdefault%2Fcomponent%2Ftechdocs-e2e-fixture%2Fdocs%3E%20%0AMarkdown%20URL%3A%20%3Chttps%3A%2F%2Fgithub.com%2Fbackstage%2Fbackstage%2Fblob%2Fmaster%2Fcypress%2Ffixtures%2Fdocs%2Findex.md%3E', + ); + }); + + it('Should render addons on docs tab sup-page', () => { + cy.loginAsGuest(); + + cy.visit('/catalog'); + + cy.contains('techdocs-e2e-fixture').click(); + + cy.location().should(loc => { + expect(loc.pathname).to.eq( + '/catalog/default/component/techdocs-e2e-fixture', + ); + }); + + cy.getCatalogDocsTab().click(); + + cy.wait(300); + + cy.getTechDocsShadowRoot() + .find('h1') + .contains('Home page') + .should('be.visible'); + + cy.getTechDocsShadowRoot().within(() => { + cy.getTechDocsNavigation().find('a').contains('Sub-page 1').click(); + }); + + cy.location().should(loc => { + expect(loc.pathname).to.eq( + '/catalog/default/component/techdocs-e2e-fixture/docs/sub-page-one/', + ); + }); + + cy.getTechDocsShadowRoot() + .find('h1') + .contains('Sub-page 1') + .should('be.visible'); + + // highlight a snippet of text + cy.getTechDocsShadowRoot() + .find('#section-11') + .then($el => { + const el = $el[0]; + const document = el.ownerDocument; + const range = document.createRange(); + range.selectNodeContents(el); + document?.getSelection()?.removeAllRanges(); + document?.getSelection()?.addRange(range); + }); + + cy.document().trigger('selectionchange'); + + // wait for new issue default debounce time + cy.wait(600); + + // assert that the new issue button has a right url + cy.getTechDocsShadowRoot() + .contains('Open new Github issue') + .should( + 'have.attr', + 'href', + 'https://github.com/backstage/backstage/issues/new?title=Documentation%20feedback%3A%20Section%201.1%C2%B6&body=%23%23%20Documentation%20Feedback%20%F0%9F%93%9D%0A%0A%20%23%23%23%23%20The%20highlighted%20text%3A%20%0A%0A%20%3E%20Section%201.1%C2%B6%0A%0A%20%23%23%23%23%20The%20comment%20on%20the%20text%3A%20%0A%20_%3Ereplace%20this%20line%20with%20your%20comment%3C_%0A%0A%20___%0ABackstage%20URL%3A%20%3Chttp%3A%2F%2Flocalhost%3A7007%2Fcatalog%2Fdefault%2Fcomponent%2Ftechdocs-e2e-fixture%2Fdocs%2Fsub-page-one%2F%3E%20%0AMarkdown%20URL%3A%20%3Chttps%3A%2F%2Fgithub.com%2Fbackstage%2Fbackstage%2Fblob%2Fmaster%2Fcypress%2Ffixtures%2Fdocs%2Fsub-page-one.md%3E', + ); + }); }); }); diff --git a/cypress/src/integration/plugins/techdocs.spec.ts b/cypress/src/integration/plugins/techdocs.spec.ts index 1e69ba46ad..31094cb128 100644 --- a/cypress/src/integration/plugins/techdocs.spec.ts +++ b/cypress/src/integration/plugins/techdocs.spec.ts @@ -99,7 +99,7 @@ describe('TechDocs', () => { }); describe('Rendering TechDocs Addons', () => { - it('should render a content addon', () => { + it('should render a content addon in homepage', () => { cy.visit('/docs/default/Component/techdocs-e2e-fixture'); cy.contains('e2e Fixture Documentation'); @@ -130,6 +130,43 @@ describe('TechDocs', () => { 'https://github.com/backstage/backstage/issues/new?title=Documentation%20feedback%3A%20This%20is%20a%20basic%20documentation%20used%20for%20end-to-end%20tests.&body=%23%23%20Documentation%20Feedback%20%F0%9F%93%9D%0A%0A%20%23%23%23%23%20The%20highlighted%20text%3A%20%0A%0A%20%3E%20This%20is%20a%20basic%20documentation%20used%20for%20end-to-end%20tests.%0A%0A%20%23%23%23%23%20The%20comment%20on%20the%20text%3A%20%0A%20_%3Ereplace%20this%20line%20with%20your%20comment%3C_%0A%0A%20___%0ABackstage%20URL%3A%20%3Chttp%3A%2F%2Flocalhost%3A7007%2Fdocs%2Fdefault%2FComponent%2Ftechdocs-e2e-fixture%3E%20%0AMarkdown%20URL%3A%20%3Chttps%3A%2F%2Fgithub.com%2Fbackstage%2Fbackstage%2Fblob%2Fmaster%2Fcypress%2Ffixtures%2Fdocs%2Findex.md%3E', ); }); + + it('should render a content addon in sub-pages', () => { + cy.visit('/docs/default/Component/techdocs-e2e-fixture'); + + cy.contains('e2e Fixture Documentation'); + + // open sub-page + cy.getTechDocsShadowRoot().within(() => { + cy.getTechDocsNavigation().find('a').contains('Sub-page 1').click(); + }); + + // highlight a snippet of text + cy.getTechDocsShadowRoot() + .find('#section-11') + .then($el => { + const el = $el[0]; + const document = el.ownerDocument; + const range = document.createRange(); + range.selectNodeContents(el); + document?.getSelection()?.removeAllRanges(); + document?.getSelection()?.addRange(range); + }); + + cy.document().trigger('selectionchange'); + + // wait for new issue default debounce time + cy.wait(600); + + // assert that the new issue button has a right url + cy.getTechDocsShadowRoot() + .contains('Open new Github issue') + .should( + 'have.attr', + 'href', + 'https://github.com/backstage/backstage/issues/new?title=Documentation%20feedback%3A%20Section%201.1%C2%B6&body=%23%23%20Documentation%20Feedback%20%F0%9F%93%9D%0A%0A%20%23%23%23%23%20The%20highlighted%20text%3A%20%0A%0A%20%3E%20Section%201.1%C2%B6%0A%0A%20%23%23%23%23%20The%20comment%20on%20the%20text%3A%20%0A%20_%3Ereplace%20this%20line%20with%20your%20comment%3C_%0A%0A%20___%0ABackstage%20URL%3A%20%3Chttp%3A%2F%2Flocalhost%3A7007%2Fdocs%2Fdefault%2FComponent%2Ftechdocs-e2e-fixture%2Fsub-page-one%2F%3E%20%0AMarkdown%20URL%3A%20%3Chttps%3A%2F%2Fgithub.com%2Fbackstage%2Fbackstage%2Fblob%2Fmaster%2Fcypress%2Ffixtures%2Fdocs%2Fsub-page-one.md%3E', + ); + }); }); describe('Navigating within TechDocs', () => { From 881fbd7e8dea79b4da27b96b54234dc70680be06 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 19 May 2022 14:41:09 +0200 Subject: [PATCH 032/149] chore: add changeset files Signed-off-by: Camila Belo --- .changeset/fair-grapes-joke.md | 5 +++++ .changeset/techdocs-buttons-film.md | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/fair-grapes-joke.md create mode 100644 .changeset/techdocs-buttons-film.md diff --git a/.changeset/fair-grapes-joke.md b/.changeset/fair-grapes-joke.md new file mode 100644 index 0000000000..5abfa0bb50 --- /dev/null +++ b/.changeset/fair-grapes-joke.md @@ -0,0 +1,5 @@ +--- +'@backstage/create-app': patch +--- + +Register the `TechDocs` addons on the catalog page and also test their rendering in `EntityDocs` sub pages. diff --git a/.changeset/techdocs-buttons-film.md b/.changeset/techdocs-buttons-film.md new file mode 100644 index 0000000000..46895ff7ce --- /dev/null +++ b/.changeset/techdocs-buttons-film.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Fix `EntityDocs` component to use objects instead of `` elements, otherwise "outlet" will be null on sub-pages and add-ons won't render. From 629ce5ce3fab74c93684eb634fd0c6480c7dbca1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 20 May 2022 10:13:50 +0200 Subject: [PATCH 033/149] backend-common: add patch release to changelog Signed-off-by: Patrik Oldsberg --- .changeset/itchy-avocados-hug.md | 2 +- packages/backend-common/CHANGELOG.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.changeset/itchy-avocados-hug.md b/.changeset/itchy-avocados-hug.md index c97016a711..1ce61d405e 100644 --- a/.changeset/itchy-avocados-hug.md +++ b/.changeset/itchy-avocados-hug.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -Fixed potential crash by bumping the `luxon` dependency to `^2.3.1`. +Applied the `luxon` dependency fix from the `0.13.4` patch release. diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index 6787a3e12f..ed4282299c 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/backend-common +## 0.13.4 + +### Patch Changes + +- 739be2b079: Fixed potential crash by bumping the `luxon` dependency to `^2.3.1`. + ## 0.13.3 ### Patch Changes From fd57c935685a99ed25ce0a2159575929b616ee56 Mon Sep 17 00:00:00 2001 From: Sebastian Olsson Date: Fri, 20 May 2022 09:21:50 +0200 Subject: [PATCH 034/149] Update ADOPTERS.md Signed-off-by: Sebastian Olsson --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 8dd2213158..3762fddbea 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -130,3 +130,4 @@ _If you're using Backstage in your organization, please try to add your company |[Lightspeed](http://lightspeedhq.com/)|[Marcus Crane](mailto:marcus.crane@lightspeedhq.com)|We use it within our X-Series division (https://vendhq.com) to catalog ~100+ systems and ~350 components! |[Siemens](https://www.siemens.com/global/en.html)|[Nizar Chaouch](mailto:nizar.chaouch@siemens.com)|We are using Backstage as our Developer portal |[The Warehouse Group](https://www.thewarehouse.co.nz)|[Matt Law](mailto:matt.law@thewarehouse.co.nz)|Backstage enables us to bootstrap our middleware environment of new services for our Dev teams in a matter of seconds. CI, CD, testing, logging, deployments are all taken care of to get them up and running in less than 60 seconds. +| [Tink](https://tink.com/) | [Sebastian Olsson](https://github.com/Sebelino), [Błażej Szum](https://github.com/blazejszumtink), [Anders Eurenius Runvald](https://github.com/anders-er-at-tink) | Internal developer portal which provides templates for creating new Java or Go microservices seamlessly. Also includes a tech radar and a visualization of our CD pipeline. | From 9199bc5945350c5e933c06dba6a38689000bf028 Mon Sep 17 00:00:00 2001 From: Gary Niemen <65337273+garyniemen@users.noreply.github.com> Date: Wed, 18 May 2022 17:56:40 +0200 Subject: [PATCH 035/149] roadmap-updates Signed-off-by: <65337273+garyniemen@users.noreply.github.com> Signed-off-by: Johan Haals --- docs/features/search/README.md | 149 +++++++++++++++++-------------- docs/features/techdocs/README.md | 128 ++++++++++++++------------ docs/overview/roadmap.md | 21 +++-- 3 files changed, 163 insertions(+), 135 deletions(-) diff --git a/docs/features/search/README.md b/docs/features/search/README.md index a151da1736..f5209e4627 100644 --- a/docs/features/search/README.md +++ b/docs/features/search/README.md @@ -10,76 +10,23 @@ description: Backstage Search lets you find the right information you are lookin ## What is it? -Backstage Search lets you find the right information you are looking for in the -Backstage ecosystem. +Backstage Search lets you find the right information you are looking for in the Backstage ecosystem. ## Features -- A federated, faceted search, searching across all entities registered in your - Backstage instance. +- A federated, faceted search, searching across all entities registered in your Backstage instance. - A search that lets you plug in your own search engine of choice. -- A standardized search API where you can choose to index other plugins data. +- A standardized search API where you can choose to index data from other plugins. ## Project roadmap -| Version | Description | -| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| Backstage Search Pre-Alpha ✅ | Search Frontend letting you search through the entities of the software catalog. [See Pre-Alpha Use Cases.](#backstage-search-pre-alpha) | -| Backstage Search Alpha ✅ | Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. [See Alpha Use Cases](#backstage-search-alpha). | -| Backstage Search Beta ✅ | At least one production-ready search engine that supports the same use-cases as in the alpha. [See Beta Use Cases](#backstage-search-beta). | -| [Backstage Search 1.0 ⌛] | A stable Search API for plugin developers to add search to their plugins, and app integrators to expose that to their users. [See 1.0 Use Cases](#backstage-search-1.0). | +### Now -## Use Cases +**Backstage Search 1.0** -#### Backstage Search Pre-Alpha - -The pre-alpha is intended to solve for the following user stories, but will get -there by means of a front-end only, non-extensible MVP. - -- As a software engineer I should be able to navigate to a search page and - search for entities registered in the Software Catalog. -- As a software engineer I should be able to use the search input field in the - sidebar to search for entities registered in the Software Catalog. -- As a software engineer I should be able to see the number of results my search - returned. -- As a software engineer I should be able to filter on metadata (kind, - lifecycle) when I’ve performed a search. -- As a software engineer I should be able to hide the filters if I don’t need to - use them. - -#### Backstage Search Alpha - -We will consider Backstage Search to be in alpha when the above use-cases are -met, but built on top of a flexible, extensible platform. - -- As an integrator, I should be able to provide all of the pre-alpha experiences - to my users if I choose, but also be able to customize the experience using a - composable set of components. -- As a plugin developer, I should have a standard way to expose my plugin's data - to Backstage Search. -- As an integrator, I should still be able to expose everything in the Software - Catalog in search, but it should be possible to customize what is searchable. -- As an integrator, although I should be able to customize all of the above, it - should be possible to have the pre-alpha user experiences covered without - having to set up and configure a search engine. - -#### Backstage Search Beta - -We will consider Backstage Search to be in a beta phase when the above use-cases -are met, and can be deployed using a production-ready search engine. - -- As an integrator, I should be able to power my Backstage Search experience - (including querying and indexing) using a production-ready search engine like - ElasticSearch. -- As an integrator, I should be able to configure the connection to my search - engine in `app_config.yaml`. -- As an integrator, I should be able to tune the queries sent to my chosen - search engine according to my organization's needs, but a sensible default - query should be in place so that I am not required to do so. - -#### Backstage Search 1.0 +A stable Search API for plugin developers to add search to their plugins, and app integrators to expose that to their users. We will consider Backstage Search to be 1.0 when the above use-cases are met, and an ecosystem of search-enabled plugins are available and @@ -92,9 +39,24 @@ stable. how to customize and extend search in my Backstage instance to meet my organization's needs. -more to come... +### Next -## Search Engines Supported +*Not specified* + +### Someday/Maybe + +*Not specified* + +### Done + +See [Done](#done) below for a list of completed roadmap items. + + +## Supported + +The following sections show the search engines and plugins currently supported by Backstage Search. + +### Search engines See [Backstage Search Architecture](architecture.md) to get an overview of how the search engines are used. @@ -108,11 +70,11 @@ the search engines are used. [Reach out to us](#get-involved) if you want to chat about support for more search engines. -## Plugins Integrated with Search +### Plugins integrated with Backstage Search -| Plugin | Support Status | +| Plugin | Support Status | | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | -| Catalog | ✅ | +| Software Catalog | ✅ | | [TechDocs](./how-to-guides.md#how-to-index-techdocs-documents) | ✅ | | [Stack Overflow](https://github.com/backstage/backstage/blob/master/plugins/stack-overflow-backend/README.md#index-stack-overflow-questions-to-search) | ✅ | @@ -131,8 +93,63 @@ plugins integrated to search. | Backend Plugin Module | @backstage/plugin-search-backend-module-elasticsearch | | Backend Plugin Module | @backstage/plugin-search-backend-module-pg | -## Get Involved +## Get involved For any questions, feedback, or to help move search forward, reach out to us in -the `#search` channel of our +the **#search** channel of our [Discord chatroom](https://github.com/backstage/backstage#community). + +## Done + +**Backstage Search Pre-Alpha** + +Search Frontend letting you search through the entities of the software catalog. + +The pre-alpha is intended to solve for the following user stories, but will get +there by means of a front-end only, non-extensible MVP. + +- As a software engineer I should be able to navigate to a search page and + search for entities registered in the Software Catalog. +- As a software engineer I should be able to use the search input field in the + sidebar to search for entities registered in the Software Catalog. +- As a software engineer I should be able to see the number of results my search + returned. +- As a software engineer I should be able to filter on metadata (kind, + lifecycle) when I’ve performed a search. +- As a software engineer I should be able to hide the filters if I don’t need to + use them. + +**Backstage Search Alpha** + +Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. + +We will consider Backstage Search to be in alpha when the above use-cases are +met, but built on top of a flexible, extensible platform. + +- As an integrator, I should be able to provide all of the pre-alpha experiences + to my users if I choose, but also be able to customize the experience using a + composable set of components. +- As a plugin developer, I should have a standard way to expose my plugin's data + to Backstage Search. +- As an integrator, I should still be able to expose everything in the Software + Catalog in search, but it should be possible to customize what is searchable. +- As an integrator, although I should be able to customize all of the above, it + should be possible to have the pre-alpha user experiences covered without + having to set up and configure a search engine. + +**Backstage Search Beta** + +At least one production-ready search engine that supports the same use-cases as in the alpha. + +We will consider Backstage Search to be in a beta phase when the above use-cases +are met, and can be deployed using a production-ready search engine. + +- As an integrator, I should be able to power my Backstage Search experience + (including querying and indexing) using a production-ready search engine like + ElasticSearch. +- As an integrator, I should be able to configure the connection to my search + engine in **app_config.yaml**. +- As an integrator, I should be able to tune the queries sent to my chosen + search engine according to my organization's needs, but a sensible default + query should be in place so that I am not required to do so. + diff --git a/docs/features/techdocs/README.md b/docs/features/techdocs/README.md index 5ac275b8b4..f0b77728b6 100644 --- a/docs/features/techdocs/README.md +++ b/docs/features/techdocs/README.md @@ -21,14 +21,44 @@ Today, it is one of the core products in Spotify’s developer experience offeri - Deploy TechDocs no matter how your software environment is set up. - Discover your Service's technical documentation from the Service's page in Backstage Catalog. - Create documentation-only sites for any purpose by just writing Markdown. +- Take advantage of the [TechDocs Addon Framework](addons.md) to add features on top of the base docs-like-code experience. - Explore and take advantage of the large ecosystem of [MkDocs plugins](https://www.mkdocs.org/user-guide/plugins/) to create a rich reading experience. - Search for and find docs. -## Platforms supported +## Project roadmap -See [TechDocs Architecture](architecture.md) to get an overview of where these -providers are used. +### Now + +With the Backstage 1.2 release, we have introduced the [TechDocs Addon Framework](https://backstage.io/blog/2022/05/13/techdocs-addon-framework) for augmenting the TechDocs experience at read-time. + +In addition to the framework itself, we have open sourced a **ReportIssue** Addon, helping you to create a feedback loop that drives up documentation quality and foster a documentation culture at your organization. + +### Next + +What can we do in TechDocs to help drive up documentation quality? We have many ideas, for example, a Trust Card with associated Trust Score and automatic triggering of documentation maintenance notifications. + +### Someday/Maybe + +- Contribute to and deploy from a marketplace of TechDocs Addons +- Addon: MDX (allows you to use JSX in your Markdown content) +- Can we go static site generator agnostic? +- Better integration with + [Scaffolder V2](https://github.com/backstage/backstage/issues/2771) (e.g. easy to choose and apply documentation template with Software Templates) +- Possible to configure several aspects about TechDocs (e.g. URL, homepage, + theme) + +### Done + +See [Done](#done) below for a list of completed roadmap items. + +## Supported + +The following sections show the source code hosting providers and file storage providers that are currently supported by TechDocs. + +See [TechDocs Architecture](architecture.md) to get an overview of where the below providers are used. + +### Source code hosting providers | Source Code Hosting Provider | Support Status | | ---------------------------- | -------------- | @@ -39,6 +69,8 @@ providers are used. | GitLab | Yes ✅ | | GitLab Enterprise | Yes ✅ | +### File storage providers + | File Storage Provider | Support Status | | --------------------------------- | -------------- | | Local Filesystem of Backstage app | Yes ✅ | @@ -47,64 +79,17 @@ providers are used. | Azure Blob Storage | Yes ✅ | | OpenStack Swift | Community ✅ | -[Reach out to us](#feedback) if you want to request more platforms. - -## Project roadmap - -### **Published versions** - -**Alpha release** ✅ - -[Milestone](https://github.com/backstage/backstage/milestone/16) - -- Alpha of TechDocs that you can use end to end - and contribute to. - -**Beta release** ✅ - -[Milestone](https://github.com/backstage/backstage/milestone/29) - -- TechDocs' recommended setup supports most environments (CI systems, cloud - storage solutions, source control systems). -- [Instructions for upgrading from Alpha to Beta](how-to-guides.md#how-to-migrate-from-techdocs-alpha-to-beta) - -**v1** ✅ - -TechDocs packages: - -- '@backstage/plugin-techdocs' -- '@backstage/plugin-techdocs-backend' -- '@backstage/plugin-techdocs-node' -- '@techdocs/cli' - -TechDocs promoted to v1.0! To understand how this change affects the package, please check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). - -**v1.2** 🚧 - -With the Backstage 1.2 release, we plan to introduce the [TechDocs Addon Framework](https://github.com/backstage/backstage/issues/9636) for augmenting the TechDocs experience at read-time. - -In addition to the framework itself, we'll be open sourcing a `` addon, helping you to create a feedback loop that drives up documentation quality and fosters a documentation culture at your organization. - -### **Next** - -- What can we do in TechDocs to drive up documentation quality? - -### **Someday/Maybe** - -- Contribute to and deploy from a marketplace of TechDocs Addons -- Addon: MDX (allows you to use JSX in your Markdown content) -- Can we go static site generator agnostic? -- Better integration with - [Scaffolder V2](https://github.com/backstage/backstage/issues/2771) (e.g. easy to choose and apply documentation template with Software Templates) -- Possible to configure several aspects about TechDocs (e.g. URL, homepage, - theme) +[Reach out to us](#get-involved) if you want to request more providers. ## Tech stack | Stack | Location | | ----------------------------------------------- | --------------------------------------------------------------- | -| Frontend Plugin | [`@backstage/plugin-techdocs`][techdocs/frontend] | -| Frontend Plugin Library | [`@backstage/plugin-techdocs-react`][techdocs/frontend-library] | -| Backend Plugin | [`@backstage/plugin-techdocs-backend`][techdocs/backend] | -| CLI (for local development and generating docs) | [`@techdocs/cli`][techdocs/cli] | -| Docker Container (for generating docs) | [`techdocs-container`][techdocs/container] | +| Frontend Plugin | [@backstage/plugin-techdocs][techdocs/frontend] | +| Frontend Plugin Library | [@backstage/plugin-techdocs-react][techdocs/frontend-library] | +| Backend Plugin | [@backstage/plugin-techdocs-backend][techdocs/backend] | +| CLI (for local development and generating docs) | [@techdocs/cli][techdocs/cli] | +| Docker Container (for generating docs) | [techdocs-container][techdocs/container] | [techdocs/frontend]: https://github.com/backstage/backstage/blob/master/plugins/techdocs [techdocs/frontend-library]: https://github.com/backstage/backstage/blob/master/plugins/techdocs-react @@ -112,7 +97,34 @@ In addition to the framework itself, we'll be open sourcing a `` [techdocs/container]: https://github.com/backstage/techdocs-container [techdocs/cli]: https://github.com/backstage/techdocs-cli -## Contact us +## Get involved -Reach out to us in the `#docs-like-code` channel of our +Reach out to us in the **#docs-like-code** channel of our [Discord chatroom](https://github.com/backstage/backstage#community). + +## Done + +**Alpha release** + +[Milestone](https://github.com/backstage/backstage/milestone/16) + +- Alpha of TechDocs that you can use end to end - and contribute to. + +**Beta release** + +[Milestone](https://github.com/backstage/backstage/milestone/29) + +- TechDocs' recommended setup supports most environments (CI systems, cloud + storage solutions, source control systems). +- [Instructions for upgrading from Alpha to Beta](how-to-guides.md#how-to-migrate-from-techdocs-alpha-to-beta) + +**v1.0** + +TechDocs promoted to v1.0! To understand how this change affects the package, check out our [versioning policy](https://backstage.io/docs/overview/versioning-policy). + +TechDocs packages: + +- @backstage/plugin-techdocs +- @backstage/plugin-techdocs-backend +- @backstage/plugin-techdocs-node +- @techdocs/cli diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index 106bf56f79..4aadb06aac 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -12,12 +12,12 @@ and the broader Backstage community. The Backstage roadmap lays out both [“what's next”](#whats-next) and ["future work"](#future-work). With "next" we mean features planned for release within -the ongoing quarter from January through March 2022. With "future" we mean +the ongoing quarter from April through June 2022. With "future" we mean features on the radar, but not yet scheduled. | [What's next](#whats-next) | [Future work](#future-work) | | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | -| [Ease of onboarding](#ease-of-onboarding)
[Search 1.0](#search-1.0)
[TechDocs Addon framework](#techdocs-addon-framework)
[Backend Services (initial)](#backend-services-initial)
[Backstage Security Audit](#backstage-security-audit)
[SIGs for contributors](#sigs-for-contributors) | Security Plan (and Strategy)
Composable Homepage 1.0
GraphQL
Telemetry
Improved UX design | +| [Ease of onboarding](#ease-of-onboarding)
[Backstage Search 1.0](#search-1.0)
[TechDocs Addon Framework](#techdocs-addon-framework)
[Backend Services (initial)](#backend-services-initial)
[Backstage Security Audit](#backstage-security-audit)
[SIGs for contributors](#sigs-for-contributors) | Security Plan (and Strategy)
Composable Homepage 1.0
GraphQL
Telemetry
Improved UX design | The long-term roadmap (12 - 36 months) is not detailed in the public roadmap. Third-party contributions are also not currently included in the roadmap. Let us @@ -46,18 +46,17 @@ More iterations will be required in the following quarters, but this will be a good improvement in the onboarding experience, especially for the benefit of new adopters. -### Search 1.0 +### Backstage Search 1.0 -Fix the few remaining issues to get Backstage Search platform up to 1.0 -([here](https://github.com/backstage/backstage/milestone/27) and -[here](https://github.com/backstage/backstage/milestone/28)). +Fix the few remaining issues to get Backstage Search platform up to 1.0. For more information, see the [Backstage Search documentation and roadmap page](https://backstage.io/docs/features/search/search-overview). -### TechDocs Addon framework +### TechDocs Addon Framework -Addons are TechDocs features that are added on top of the base docs-like-code -experience. An example would be a feature that showed comments on the page. We -plan to add an Addon framework and open source a selection of the Addons that we -use internally at Spotify. Further Addons can then be added by the Community. +Addons are TechDocs features that are added on top of the base docs-like-code experience. An example would be a feature that showed comments on the page. We plan to add an Addon framework and open source a selection of the Addons that we use internally at Spotify. We encourage the Backstage community to add further Addons. + +For more information about the TechDocs Addon Framework, see the documentation page [here](https://backstage.io/docs/features/techdocs/addons) + +For general information about TechDocs including roadmap, see [here](https://backstage.io/docs/features/techdocs/techdocs-overview). ### Backend Services (initial) From 75d8a17466643c3a2fe82c39fdd1e7ec273abb21 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 20 May 2022 10:46:08 +0200 Subject: [PATCH 036/149] format with prettier Signed-off-by: Johan Haals --- docs/features/search/README.md | 14 ++++++-------- docs/features/techdocs/README.md | 4 ++-- docs/overview/roadmap.md | 4 ++-- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/docs/features/search/README.md b/docs/features/search/README.md index f5209e4627..f1744d4d80 100644 --- a/docs/features/search/README.md +++ b/docs/features/search/README.md @@ -41,17 +41,16 @@ stable. ### Next -*Not specified* +_Not specified_ ### Someday/Maybe -*Not specified* +_Not specified_ ### Done See [Done](#done) below for a list of completed roadmap items. - ## Supported The following sections show the search engines and plugins currently supported by Backstage Search. @@ -72,9 +71,9 @@ search engines. ### Plugins integrated with Backstage Search -| Plugin | Support Status | +| Plugin | Support Status | | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | -| Software Catalog | ✅ | +| Software Catalog | ✅ | | [TechDocs](./how-to-guides.md#how-to-index-techdocs-documents) | ✅ | | [Stack Overflow](https://github.com/backstage/backstage/blob/master/plugins/stack-overflow-backend/README.md#index-stack-overflow-questions-to-search) | ✅ | @@ -118,7 +117,7 @@ there by means of a front-end only, non-extensible MVP. lifecycle) when I’ve performed a search. - As a software engineer I should be able to hide the filters if I don’t need to use them. - + **Backstage Search Alpha** Basic “out-of-the-box” in-memory indexing process of entities, and their metadata, registered to the Software Catalog. @@ -138,7 +137,7 @@ met, but built on top of a flexible, extensible platform. having to set up and configure a search engine. **Backstage Search Beta** - + At least one production-ready search engine that supports the same use-cases as in the alpha. We will consider Backstage Search to be in a beta phase when the above use-cases @@ -152,4 +151,3 @@ are met, and can be deployed using a production-ready search engine. - As an integrator, I should be able to tune the queries sent to my chosen search engine according to my organization's needs, but a sensible default query should be in place so that I am not required to do so. - diff --git a/docs/features/techdocs/README.md b/docs/features/techdocs/README.md index f0b77728b6..48c469def7 100644 --- a/docs/features/techdocs/README.md +++ b/docs/features/techdocs/README.md @@ -83,8 +83,8 @@ See [TechDocs Architecture](architecture.md) to get an overview of where the bel ## Tech stack -| Stack | Location | -| ----------------------------------------------- | --------------------------------------------------------------- | +| Stack | Location | +| ----------------------------------------------- | ------------------------------------------------------------- | | Frontend Plugin | [@backstage/plugin-techdocs][techdocs/frontend] | | Frontend Plugin Library | [@backstage/plugin-techdocs-react][techdocs/frontend-library] | | Backend Plugin | [@backstage/plugin-techdocs-backend][techdocs/backend] | diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index 4aadb06aac..727000728f 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -15,8 +15,8 @@ work"](#future-work). With "next" we mean features planned for release within the ongoing quarter from April through June 2022. With "future" we mean features on the radar, but not yet scheduled. -| [What's next](#whats-next) | [Future work](#future-work) | -| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| [What's next](#whats-next) | [Future work](#future-work) | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | [Ease of onboarding](#ease-of-onboarding)
[Backstage Search 1.0](#search-1.0)
[TechDocs Addon Framework](#techdocs-addon-framework)
[Backend Services (initial)](#backend-services-initial)
[Backstage Security Audit](#backstage-security-audit)
[SIGs for contributors](#sigs-for-contributors) | Security Plan (and Strategy)
Composable Homepage 1.0
GraphQL
Telemetry
Improved UX design | The long-term roadmap (12 - 36 months) is not detailed in the public roadmap. From 1e5d66f7018b92554601848fa4ce97980cc911e8 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Thu, 19 May 2022 17:32:23 +0200 Subject: [PATCH 037/149] refactor: apply review suggestions Signed-off-by: Camila Belo --- .changeset/fair-grapes-joke.md | 43 ++++++++++++++++++- .changeset/techdocs-buttons-film.md | 2 +- .../app/src/components/catalog/EntityPage.tsx | 31 ++++++------- .../app/src/components/catalog/EntityPage.tsx | 30 ++++++------- 4 files changed, 68 insertions(+), 38 deletions(-) diff --git a/.changeset/fair-grapes-joke.md b/.changeset/fair-grapes-joke.md index 5abfa0bb50..8ab6516833 100644 --- a/.changeset/fair-grapes-joke.md +++ b/.changeset/fair-grapes-joke.md @@ -2,4 +2,45 @@ '@backstage/create-app': patch --- -Register the `TechDocs` addons on the catalog page and also test their rendering in `EntityDocs` sub pages. +Register `TechDocs` addons on catalog entity pages, follow the steps below to add them manually: + +```diff +// packages/app/src/components/catalog/EntityPage.tsx + ++ import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; ++ import { ++ ReportIssue, ++ } from '@backstage/plugin-techdocs-module-addons-contrib'; + ++ const techdocsContent = ( ++ ++ ++ ++ ++ ++ ); + +const defaultEntityPage = ( + ... + ++ {techdocsContent} + + ... +); + +const serviceEntityPage = ( + ... + ++ {techdocsContent} + + ... +); + +const websiteEntityPage = ( + ... + ++ {techdocsContent} + + ... +); +``` diff --git a/.changeset/techdocs-buttons-film.md b/.changeset/techdocs-buttons-film.md index 46895ff7ce..1af1ead4c7 100644 --- a/.changeset/techdocs-buttons-film.md +++ b/.changeset/techdocs-buttons-film.md @@ -2,4 +2,4 @@ '@backstage/plugin-techdocs': patch --- -Fix `EntityDocs` component to use objects instead of `` elements, otherwise "outlet" will be null on sub-pages and add-ons won't render. +Fix `EntityTechdocsContent` component to use objects instead of `` elements, otherwise "outlet" will be null on sub-pages and add-ons won't render. diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index c0edbc6ed0..799fe3e4d9 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -174,6 +174,15 @@ const EntityLayoutWrapper = (props: { children?: ReactNode }) => { ); }; +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`, @@ -404,12 +413,7 @@ const serviceEntityPage = ( - - - - - - + {techdocsContent} - - - - - - + {techdocsContent} + - - - - - - + {techdocsContent} diff --git a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx index 2695f4033e..6ec4da055d 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/catalog/EntityPage.tsx @@ -69,9 +69,15 @@ import { } from '@backstage/catalog-model'; import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; -import { - ReportIssue, -} from '@backstage/plugin-techdocs-module-addons-contrib'; +import { ReportIssue } from '@backstage/plugin-techdocs-module-addons-contrib'; + +const techdocsContent = ( + + + + + +); const cicdContent = ( // This is an example of how you can implement your company's logic in entity page. @@ -172,11 +178,7 @@ const serviceEntityPage = ( - - - - - + {techdocsContent} ); @@ -203,11 +205,7 @@ const websiteEntityPage = ( - - - - - + {techdocsContent} ); @@ -226,11 +224,7 @@ const defaultEntityPage = ( - - - - - + {techdocsContent} ); From ed7608e878cbe5fd84b0428c0dc06a02f413a7aa Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 20 May 2022 13:24:00 +0200 Subject: [PATCH 038/149] fix alert after triggering an incident Signed-off-by: Samira Mokaram --- .../pagerduty/src/components/TriggerDialog/TriggerDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx index 171238cdd3..4dc4f04699 100644 --- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx +++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.tsx @@ -60,7 +60,7 @@ export const TriggerDialog = ({ defaultKind: 'User', defaultNamespace: DEFAULT_NAMESPACE, }); - await api.triggerAlarm({ + return await api.triggerAlarm({ integrationKey: integrationKey as string, source: window.location.toString(), description: descriptions, From 76bf6400fef128e4f0304e9eccc75d8f36315af3 Mon Sep 17 00:00:00 2001 From: Samira Mokaram Date: Fri, 20 May 2022 13:50:40 +0200 Subject: [PATCH 039/149] add changeset Signed-off-by: Samira Mokaram --- .changeset/hungry-brooms-wash.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/hungry-brooms-wash.md diff --git a/.changeset/hungry-brooms-wash.md b/.changeset/hungry-brooms-wash.md new file mode 100644 index 0000000000..307b91ce15 --- /dev/null +++ b/.changeset/hungry-brooms-wash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-pagerduty': patch +--- + +Fix alert that was not showing after creating an incident. From 0e91c38450d1190fba36dcd355b53221ae8b238d Mon Sep 17 00:00:00 2001 From: Martina Iglesias Fernandez Date: Fri, 20 May 2022 10:17:15 +0200 Subject: [PATCH 040/149] Revert "chore(deps): update helm release postgresql to v11" This reverts commit d0b04813ab991e0701495e1015c3b386be14ced9. I am reverting this because this was a breaking change and needs more work and testing. Signed-off-by: Martina Iglesias Fernandez --- contrib/chart/backstage/Chart.lock | 6 +++--- contrib/chart/backstage/Chart.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/chart/backstage/Chart.lock b/contrib/chart/backstage/Chart.lock index b06f1ea1cb..6220a481dd 100644 --- a/contrib/chart/backstage/Chart.lock +++ b/contrib/chart/backstage/Chart.lock @@ -1,6 +1,6 @@ dependencies: - name: postgresql repository: https://charts.bitnami.com/bitnami - version: 11.2.4 -digest: sha256:782c8593a80e332b19f736d48f4635e424bbf0575ed9d587c0a301cccfcedce8 -generated: "2022-05-19T10:46:51.441725486Z" + version: 9.8.12 +digest: sha256:549b9a0cdf7b2e0ad949ebad853a467bf320928970a946fb0ef7e13e9bdb7a10 +generated: "2022-05-20T08:15:48.301491565Z" diff --git a/contrib/chart/backstage/Chart.yaml b/contrib/chart/backstage/Chart.yaml index 3776f4a41c..9770ef665c 100644 --- a/contrib/chart/backstage/Chart.yaml +++ b/contrib/chart/backstage/Chart.yaml @@ -18,7 +18,7 @@ sources: dependencies: - name: postgresql condition: postgresql.enabled - version: 11.2.4 + version: 9.8.12 repository: https://charts.bitnami.com/bitnami maintainers: From 1b173f8b5dc0543ac228b88e301972e5528252a4 Mon Sep 17 00:00:00 2001 From: goenning Date: Fri, 20 May 2022 14:21:46 +0100 Subject: [PATCH 041/149] refactor null check and race condition Signed-off-by: goenning --- ...reIdentityKubernetesAuthTranslator.test.ts | 39 +++++++++++++- .../AzureIdentityKubernetesAuthTranslator.ts | 51 ++++++++++++++----- .../KubernetesAuthTranslatorGenerator.test.ts | 15 ++++-- .../KubernetesAuthTranslatorGenerator.ts | 4 +- .../src/service/KubernetesFanOutHandler.ts | 1 + 5 files changed, 89 insertions(+), 21 deletions(-) diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts index c5183f8a22..574b5838f9 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts @@ -15,8 +15,11 @@ */ import { AccessToken, TokenCredential } from '@azure/identity'; +import { getVoidLogger } from '@backstage/backend-common'; import { AzureIdentityKubernetesAuthTranslator } from './AzureIdentityKubernetesAuthTranslator'; +const logger = getVoidLogger(); + class StaticTokenCredential implements TokenCredential { private count: number = 0; @@ -25,6 +28,10 @@ class StaticTokenCredential implements TokenCredential { getToken(): Promise { this.count++; + if (this.count === 3) { + return Promise.reject(new Error('Third time never works.')); + } + return Promise.resolve({ token: `MY_TOKEN_${this.count}`, expiresOnTimestamp: Date.now() + this.expiryInMs, @@ -41,6 +48,7 @@ describe('AzureIdentityKubernetesAuthTranslator tests', () => { it('should decorate cluster with Azure token', async () => { const authTranslator = new AzureIdentityKubernetesAuthTranslator( + logger, new StaticTokenCredential(5 * 60 * 1000), ); @@ -50,6 +58,7 @@ describe('AzureIdentityKubernetesAuthTranslator tests', () => { it('should re-use token before expiry', async () => { const authTranslator = new AzureIdentityKubernetesAuthTranslator( + logger, new StaticTokenCredential(20 * 60 * 1000), ); @@ -62,15 +71,41 @@ describe('AzureIdentityKubernetesAuthTranslator tests', () => { it('should issue new token 15 minutes befory expiry', async () => { const authTranslator = new AzureIdentityKubernetesAuthTranslator( - new StaticTokenCredential(16 * 60 * 1000), // token expires in 11m + logger, + new StaticTokenCredential(16 * 60 * 1000), // token expires in 16m ); const response = await authTranslator.decorateClusterDetailsWithAuth(cd); expect(response.serviceAccountToken).toEqual('MY_TOKEN_1'); - jest.useFakeTimers().setSystemTime(Date.now() + 1 * 60 * 1000); // advance time by 1min + jest.useFakeTimers().setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2mins const response2 = await authTranslator.decorateClusterDetailsWithAuth(cd); expect(response2.serviceAccountToken).toEqual('MY_TOKEN_2'); }); + + it('should re-use existing token if there is afailure', async () => { + const authTranslator = new AzureIdentityKubernetesAuthTranslator( + logger, + new StaticTokenCredential(16 * 60 * 1000), // new tokens expires in 16m + ); + + const response = await authTranslator.decorateClusterDetailsWithAuth(cd); + expect(response.serviceAccountToken).toEqual('MY_TOKEN_1'); + + jest.useFakeTimers().setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2mins + + const response2 = await authTranslator.decorateClusterDetailsWithAuth(cd); + expect(response2.serviceAccountToken).toEqual('MY_TOKEN_2'); + + jest.useFakeTimers().setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2mins + + const response3 = await authTranslator.decorateClusterDetailsWithAuth(cd); + expect(response3.serviceAccountToken).toEqual('MY_TOKEN_2'); + + jest.useFakeTimers().setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2mins + + const response4 = await authTranslator.decorateClusterDetailsWithAuth(cd); + expect(response4.serviceAccountToken).toEqual('MY_TOKEN_4'); + }); }); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts index 027bf03bb6..00c67f1328 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { Logger } from 'winston'; import { KubernetesAuthTranslator } from './types'; import { AzureClusterDetails } from '../types/types'; import { @@ -27,9 +28,11 @@ const aksScope = '6dae42f8-4368-4678-94ff-3960e28e3630/.default'; // This scope export class AzureIdentityKubernetesAuthTranslator implements KubernetesAuthTranslator { - private accessToken: AccessToken | null = null; + private accessToken: AccessToken = { token: '', expiresOnTimestamp: 0 }; + private newToken: Promise | undefined; constructor( + private readonly logger: Logger, private readonly tokenCredential: TokenCredential = new DefaultAzureCredential(), ) {} @@ -41,23 +44,45 @@ export class AzureIdentityKubernetesAuthTranslator clusterDetails, ); - if (this.tokenExpired()) { - this.accessToken = await this.tokenCredential.getToken(aksScope); - - if (!this.accessToken) { - throw new Error('Unable to retrieve Azure token'); - } - } - - clusterDetailsWithAuthToken.serviceAccountToken = this.accessToken!.token; + clusterDetailsWithAuthToken.serviceAccountToken = await this.getToken(); return clusterDetailsWithAuthToken; } - private tokenExpired(): boolean { - if (!this.accessToken) return true; + private async getToken(): Promise { + if (this.isTokenValid()) { + return this.accessToken.token; + } + if (!this.newToken) { + this.newToken = this.fetchNewToken(); + } + + return this.newToken; + } + + private async fetchNewToken(): Promise { + try { + this.logger.info('Fetching new Azure token for AKS'); + + const newAccessToken = await this.tokenCredential.getToken(aksScope, { + requestOptions: { timeout: 10_000 }, // 10 seconds + }); + if (!newAccessToken) { + throw new Error('AccessToken is null'); + } + + this.accessToken = newAccessToken; + } catch (err) { + this.logger.error('Unable to fetch Azure token', err); + } + + this.newToken = undefined; + return this.accessToken.token; + } + + private isTokenValid(): boolean { // Set tokens to expire 15 minutes before its actual expiry time const expiresOn = this.accessToken.expiresOnTimestamp - 15 * 60 * 1000; - return Date.now() >= expiresOn; + return expiresOn >= Date.now(); } } diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts index b31443cdd1..dc0d589148 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts @@ -20,25 +20,28 @@ import { KubernetesAuthTranslatorGenerator } from './KubernetesAuthTranslatorGen import { ServiceAccountKubernetesAuthTranslator } from './ServiceAccountKubernetesAuthTranslator'; import { AwsIamKubernetesAuthTranslator } from './AwsIamKubernetesAuthTranslator'; import { OidcKubernetesAuthTranslator } from './OidcKubernetesAuthTranslator'; +import { getVoidLogger } from '@backstage/backend-common'; + +const logger = getVoidLogger(); describe('getKubernetesAuthTranslatorInstance', () => { const sut = KubernetesAuthTranslatorGenerator; it('can return an auth translator for google auth', () => { const authTranslator: KubernetesAuthTranslator = - sut.getKubernetesAuthTranslatorInstance('google'); + sut.getKubernetesAuthTranslatorInstance(logger, 'google'); expect(authTranslator instanceof GoogleKubernetesAuthTranslator).toBe(true); }); it('can return an auth translator for aws auth', () => { const authTranslator: KubernetesAuthTranslator = - sut.getKubernetesAuthTranslatorInstance('aws'); + sut.getKubernetesAuthTranslatorInstance(logger, 'aws'); expect(authTranslator instanceof AwsIamKubernetesAuthTranslator).toBe(true); }); it('can return an auth translator for serviceAccount auth', () => { const authTranslator: KubernetesAuthTranslator = - sut.getKubernetesAuthTranslatorInstance('serviceAccount'); + sut.getKubernetesAuthTranslatorInstance(logger, 'serviceAccount'); expect( authTranslator instanceof ServiceAccountKubernetesAuthTranslator, ).toBe(true); @@ -46,12 +49,14 @@ describe('getKubernetesAuthTranslatorInstance', () => { it('can return an auth translator for oidc auth', () => { const authTranslator: KubernetesAuthTranslator = - sut.getKubernetesAuthTranslatorInstance('oidc'); + sut.getKubernetesAuthTranslatorInstance(logger, 'oidc'); expect(authTranslator instanceof OidcKubernetesAuthTranslator).toBe(true); }); it('throws an error when asked for an auth translator for an unsupported auth type', () => { - expect(() => sut.getKubernetesAuthTranslatorInstance('linode')).toThrow( + expect(() => + sut.getKubernetesAuthTranslatorInstance(logger, 'linode'), + ).toThrow( 'authProvider "linode" has no KubernetesAuthTranslator associated with it', ); }); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts index 4b17653359..92c267cec9 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { Logger } from 'winston'; import { KubernetesAuthTranslator } from './types'; import { GoogleKubernetesAuthTranslator } from './GoogleKubernetesAuthTranslator'; import { ServiceAccountKubernetesAuthTranslator } from './ServiceAccountKubernetesAuthTranslator'; @@ -24,6 +25,7 @@ import { OidcKubernetesAuthTranslator } from './OidcKubernetesAuthTranslator'; export class KubernetesAuthTranslatorGenerator { static getKubernetesAuthTranslatorInstance( + logger: Logger, authProvider: string, ): KubernetesAuthTranslator { switch (authProvider) { @@ -34,7 +36,7 @@ export class KubernetesAuthTranslatorGenerator { return new AwsIamKubernetesAuthTranslator(); } case 'azure': { - return new AzureIdentityKubernetesAuthTranslator(); + return new AzureIdentityKubernetesAuthTranslator(logger); } case 'serviceAccount': { return new ServiceAccountKubernetesAuthTranslator(); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index 7a20db3b94..e65754a8cb 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -293,6 +293,7 @@ export class KubernetesFanOutHandler { this.authTranslators[provider] = KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance( + this.logger, provider, ); return this.authTranslators[provider]; From f3b42c2cdbe95ea8c67f3cb78c3e917fe6c991a4 Mon Sep 17 00:00:00 2001 From: goenning Date: Fri, 20 May 2022 14:28:33 +0100 Subject: [PATCH 042/149] add explanation Signed-off-by: goenning --- .../AzureIdentityKubernetesAuthTranslator.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts index 00c67f1328..9b913cccff 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts @@ -74,6 +74,7 @@ export class AzureIdentityKubernetesAuthTranslator this.accessToken = newAccessToken; } catch (err) { this.logger.error('Unable to fetch Azure token', err); + // don't throw the error, so the existing token will be re-used until we're able to fetch a new token } this.newToken = undefined; From 5b22a8c97f5bafb88a806efe781c9da6740039b0 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 20 May 2022 15:36:04 +0200 Subject: [PATCH 043/149] backend-common: fix stuck s3 reading Signed-off-by: Patrik Oldsberg --- .changeset/odd-baboons-buy.md | 5 + .../src/reading/AwsS3UrlReader.test.ts | 245 ++++++++---------- .../src/reading/AwsS3UrlReader.ts | 34 ++- 3 files changed, 146 insertions(+), 138 deletions(-) create mode 100644 .changeset/odd-baboons-buy.md diff --git a/.changeset/odd-baboons-buy.md b/.changeset/odd-baboons-buy.md new file mode 100644 index 0000000000..6fe3826985 --- /dev/null +++ b/.changeset/odd-baboons-buy.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-common': patch +--- + +Fix a bug in the URL Reading towards AWS S3 where it would hang indefinitely. diff --git a/packages/backend-common/src/reading/AwsS3UrlReader.test.ts b/packages/backend-common/src/reading/AwsS3UrlReader.test.ts index ea969b43a9..0ae64e9404 100644 --- a/packages/backend-common/src/reading/AwsS3UrlReader.test.ts +++ b/packages/backend-common/src/reading/AwsS3UrlReader.test.ts @@ -18,6 +18,9 @@ import { ConfigReader } from '@backstage/config'; import { JsonObject } from '@backstage/types'; import { getVoidLogger } from '../logging'; import { DefaultReadTreeResponseFactory } from './tree'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { setupRequestMockHandlers } from '@backstage/backend-test-utils'; import { AwsS3UrlReader, parseUrl } from './AwsS3UrlReader'; import { AwsS3Integration, @@ -111,6 +114,9 @@ describe('parseUrl', () => { }); describe('AwsS3UrlReader', () => { + const worker = setupServer(); + setupRequestMockHandlers(worker); + const createReader = (config: JsonObject): UrlReaderPredicateTuple[] => { return AwsS3UrlReader.factory({ config: new ConfigReader(config), @@ -119,10 +125,6 @@ describe('AwsS3UrlReader', () => { }); }; - afterEach(() => { - AWSMock.restore(); - }); - it('creates a dummy reader without the awsS3 field', () => { const entries = createReader({ integrations: {}, @@ -209,40 +211,34 @@ describe('AwsS3UrlReader', () => { }); describe('read', () => { - let awsS3UrlReader: AwsS3UrlReader; + const [{ reader }] = createReader({ + integrations: { + awsS3: [ + { + host: 'amazonaws.com', + accessKeyId: 'fake-access-key', + secretAccessKey: 'fake-secret-key', + }, + ], + }, + }); - beforeAll(() => { - AWSMock.setSDKInstance(aws); - AWSMock.mock( - 'S3', - 'getObject', - Buffer.from( - require('fs').readFileSync( - path.resolve( - __dirname, - '__fixtures__/awsS3/awsS3-mock-object.yaml', + beforeEach(() => { + worker.use( + rest.get( + 'https://test-bucket.s3.amazonaws.com/awsS3-mock-object.yaml', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('ETag', '123abc'), + ctx.body('site_name: Test'), ), - ), ), ); - - const s3 = new aws.S3(); - awsS3UrlReader = new AwsS3UrlReader( - new AwsS3Integration( - readAwsS3IntegrationConfig( - new ConfigReader({ - host: 'amazonaws.com', - accessKeyId: 'fake-access-key', - secretAccessKey: 'fake-secret-key', - }), - ), - ), - { s3, treeResponseFactory }, - ); }); it('returns contents of an object in a bucket', async () => { - const response = await awsS3UrlReader.read( + const response = await reader.read( 'https://test-bucket.s3.us-east-2.amazonaws.com/awsS3-mock-object.yaml', ); expect(response.toString().trim()).toBe('site_name: Test'); @@ -250,7 +246,7 @@ describe('AwsS3UrlReader', () => { it('rejects unknown targets', async () => { await expect( - awsS3UrlReader.read( + reader.read( 'https://test-bucket.s3.us-east-2.NOTamazonaws.com/file.yaml', ), ).rejects.toThrow( @@ -262,59 +258,53 @@ describe('AwsS3UrlReader', () => { }); describe('readUrl', () => { - let awsS3UrlReader: AwsS3UrlReader; + const [{ reader }] = createReader({ + integrations: { + awsS3: [ + { + host: 'amazonaws.com', + accessKeyId: 'fake-access-key', + secretAccessKey: 'fake-secret-key', + }, + ], + }, + }); beforeEach(() => { - AWSMock.setSDKInstance(aws); - - AWSMock.mock( - 'S3', - 'getObject', - Buffer.from( - require('fs').readFileSync( - path.resolve( - __dirname, - '__fixtures__/awsS3/awsS3-mock-object.yaml', + worker.use( + rest.get( + 'https://test-bucket.s3.amazonaws.com/awsS3-mock-object.yaml', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('ETag', '123abc'), + ctx.body('site_name: Test'), ), - ), ), ); - - const s3 = new aws.S3(); - - awsS3UrlReader = new AwsS3UrlReader( - new AwsS3Integration( - readAwsS3IntegrationConfig( - new ConfigReader({ - host: 'amazonaws.com', - accessKeyId: 'fake-access-key', - secretAccessKey: 'fake-secret-key', - }), - ), - ), - { s3, treeResponseFactory }, - ); }); it('returns contents of an object in a bucket via buffer', async () => { - const response = await awsS3UrlReader.readUrl( + const response = await reader.readUrl!( 'https://test-bucket.s3.us-east-2.amazonaws.com/awsS3-mock-object.yaml', ); + expect(response.etag).toBe('123abc'); const buffer = await response.buffer(); expect(buffer.toString().trim()).toBe('site_name: Test'); }); it('returns contents of an object in a bucket via stream', async () => { - const response = await awsS3UrlReader.readUrl( + const response = await reader.readUrl!( 'https://test-bucket.s3.us-east-2.amazonaws.com/awsS3-mock-object.yaml', ); + expect(response.etag).toBe('123abc'); const fromStream = await getRawBody(response.stream!()); expect(fromStream.toString().trim()).toBe('site_name: Test'); }); it('rejects unknown targets', async () => { await expect( - awsS3UrlReader.readUrl( + reader.readUrl!( 'https://test-bucket.s3.us-east-2.NOTamazonaws.com/file.yaml', ), ).rejects.toThrow( @@ -325,45 +315,73 @@ describe('AwsS3UrlReader', () => { }); }); - describe('readUrl with etag', () => { - let awsS3UrlReader: AwsS3UrlReader; + describe('readUrl towards custom host', () => { + const [{ reader }] = createReader({ + integrations: { + awsS3: [ + { + host: 'localhost:4566', + accessKeyId: 'fake-access-key', + secretAccessKey: 'fake-secret-key', + endpoint: 'http://localhost:4566', + s3ForcePathStyle: true, + }, + ], + }, + }); - beforeAll(() => { - AWSMock.setSDKInstance(aws); - - AWSMock.mock('S3', 'getObject', (_, callback) => { - const error: aws.AWSError = { - code: 'NotModified', - message: 'Not Modified', - statusCode: 304, - name: 'oops', - time: new Date('2019-01-01T00:00:00.000Z'), - }; - callback(error, undefined); - }); - - const s3 = new aws.S3(); - - awsS3UrlReader = new AwsS3UrlReader( - new AwsS3Integration( - readAwsS3IntegrationConfig( - new ConfigReader({ - host: 'amazonaws.com', - accessKeyId: 'fake-access-key', - secretAccessKey: 'fake-secret-key', - }), - ), + beforeEach(() => { + worker.use( + rest.get( + 'http://localhost:4566/test-bucket/awsS3-mock-object.yaml', + (_, res, ctx) => + res( + ctx.status(200), + ctx.set('ETag', '123abc'), + ctx.body('site_name: Test'), + ), + ), + ); + }); + + it('returns contents of an object in a bucket via buffer', async () => { + const response = await reader.readUrl!( + 'http://localhost:4566/test-bucket/awsS3-mock-object.yaml', + ); + expect(response.etag).toBe('123abc'); + const buffer = await response.buffer(); + expect(buffer.toString().trim()).toBe('site_name: Test'); + }); + }); + + describe('readUrl with etag', () => { + const [{ reader }] = createReader({ + integrations: { + awsS3: [ + { + host: 'amazonaws.com', + accessKeyId: 'fake-access-key', + secretAccessKey: 'fake-secret-key', + }, + ], + }, + }); + + beforeEach(() => { + worker.use( + rest.get( + 'https://test-bucket.s3.amazonaws.com/awsS3-mock-object.yaml', + (_, res, ctx) => res(ctx.status(304)), ), - { s3, treeResponseFactory }, ); }); it('returns contents of an object in a bucket', async () => { await expect( - awsS3UrlReader.readUrl( + reader.readUrl!( 'https://test-bucket.s3.us-east-2.amazonaws.com/awsS3-mock-object.yaml', { - etag: 'abc123', + etag: '123abc', }, ), ).rejects.toThrow(NotModifiedError); @@ -424,47 +442,4 @@ describe('AwsS3UrlReader', () => { expect(body.toString().trim()).toBe('site_name: Test'); }); }); - - describe('readNonAwsHost', () => { - let awsS3UrlReader: AwsS3UrlReader; - - beforeAll(() => { - AWSMock.setSDKInstance(aws); - AWSMock.mock( - 'S3', - 'getObject', - Buffer.from( - require('fs').readFileSync( - path.resolve( - __dirname, - '__fixtures__/awsS3/awsS3-mock-object.yaml', - ), - ), - ), - ); - - const s3 = new aws.S3(); - awsS3UrlReader = new AwsS3UrlReader( - new AwsS3Integration( - readAwsS3IntegrationConfig( - new ConfigReader({ - host: 'localhost:4566', - accessKeyId: 'fake-access-key', - secretAccessKey: 'fake-secret-key', - endpoint: 'http://localhost:4566', - s3ForcePathStyle: true, - }), - ), - ), - { s3, treeResponseFactory }, - ); - }); - - it('returns contents of an object in a bucket', async () => { - const response = await awsS3UrlReader.read( - 'http://localhost:4566/test-bucket/awsS3-mock-object.yaml', - ); - expect(response.toString().trim()).toBe('site_name: Test'); - }); - }); }); diff --git a/packages/backend-common/src/reading/AwsS3UrlReader.ts b/packages/backend-common/src/reading/AwsS3UrlReader.ts index dbd2c66a62..b0ac81676a 100644 --- a/packages/backend-common/src/reading/AwsS3UrlReader.ts +++ b/packages/backend-common/src/reading/AwsS3UrlReader.ts @@ -214,10 +214,38 @@ export class AwsS3UrlReader implements UrlReader { const request = this.deps.s3.getObject(params); options?.signal?.addEventListener('abort', () => request.abort()); - const etag = (await request.promise()).ETag; - return ReadUrlResponseFactory.fromReadable(request.createReadStream(), { - etag, + // Since we're consuming the read stream we need to consume headers and errors via events. + const etagPromise = new Promise((resolve, reject) => { + request.on('httpHeaders', (status, headers) => { + if (status < 400) { + if (status === 200) { + resolve(headers.etag); + } else if (status !== 304 /* not modified */) { + reject( + new Error( + `S3 readUrl request received unexpected status '${status}' in response`, + ), + ); + } + } + }); + request.on('error', error => reject(error)); + request.on('complete', () => + reject( + new Error('S3 readUrl request completed without receiving headers'), + ), + ); + }); + + const stream = request.createReadStream(); + stream.on('error', () => { + // The AWS SDK forwards request errors to the stream, so we need to + // ignore those errors here or the process will crash. + }); + + return ReadUrlResponseFactory.fromReadable(stream, { + etag: await etagPromise, }); } catch (e) { if (e.statusCode === 304) { From 464c33f93252bbe66f5bce86c381611bc51ec8e7 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 20 May 2022 16:55:24 +0200 Subject: [PATCH 044/149] graphiql: fix headers not being included in requests Signed-off-by: Patrik Oldsberg --- .changeset/sixty-poems-drum.md | 5 +++++ plugins/graphiql/src/lib/api/GraphQLEndpoints.ts | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/sixty-poems-drum.md diff --git a/.changeset/sixty-poems-drum.md b/.changeset/sixty-poems-drum.md new file mode 100644 index 0000000000..82c5f6c499 --- /dev/null +++ b/.changeset/sixty-poems-drum.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-graphiql': patch +--- + +Fix for custom headers not being included in requests. diff --git a/plugins/graphiql/src/lib/api/GraphQLEndpoints.ts b/plugins/graphiql/src/lib/api/GraphQLEndpoints.ts index 7287eb579b..7f9c0ca4ee 100644 --- a/plugins/graphiql/src/lib/api/GraphQLEndpoints.ts +++ b/plugins/graphiql/src/lib/api/GraphQLEndpoints.ts @@ -59,11 +59,12 @@ export class GraphQLEndpoints implements GraphQLBrowseApi { return { id, title, - fetcher: async (params: any) => { + fetcher: async (params: any, options: any = {}) => { const body = JSON.stringify(params); const headers = { 'Content-Type': 'application/json', ...config.headers, + ...options.headers, }; const res = await fetch(url, { method, @@ -96,7 +97,7 @@ export class GraphQLEndpoints implements GraphQLBrowseApi { return { id, title, - fetcher: async (params: any) => { + fetcher: async (params: any, options: any = {}) => { let retried = false; const doRequest = async (): Promise => { @@ -105,6 +106,7 @@ export class GraphQLEndpoints implements GraphQLBrowseApi { headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${await githubAuthApi.getAccessToken()}`, + ...options.headers, }, body: JSON.stringify(params), }); From 63aa03cc82f277457933f129c41bd48e4ccc6686 Mon Sep 17 00:00:00 2001 From: goenning Date: Fri, 20 May 2022 16:13:31 +0100 Subject: [PATCH 045/149] code review Signed-off-by: goenning --- ...reIdentityKubernetesAuthTranslator.test.ts | 31 +++++++++++++++---- .../AzureIdentityKubernetesAuthTranslator.ts | 26 ++++++++++------ .../KubernetesAuthTranslatorGenerator.test.ts | 10 +++--- .../KubernetesAuthTranslatorGenerator.ts | 6 ++-- .../src/service/KubernetesFanOutHandler.ts | 4 ++- 5 files changed, 54 insertions(+), 23 deletions(-) diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts index 574b5838f9..a28f825434 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.test.ts @@ -72,7 +72,7 @@ describe('AzureIdentityKubernetesAuthTranslator tests', () => { it('should issue new token 15 minutes befory expiry', async () => { const authTranslator = new AzureIdentityKubernetesAuthTranslator( logger, - new StaticTokenCredential(16 * 60 * 1000), // token expires in 16m + new StaticTokenCredential(16 * 60 * 1000), // token expires in 16min ); const response = await authTranslator.decorateClusterDetailsWithAuth(cd); @@ -87,25 +87,44 @@ describe('AzureIdentityKubernetesAuthTranslator tests', () => { it('should re-use existing token if there is afailure', async () => { const authTranslator = new AzureIdentityKubernetesAuthTranslator( logger, - new StaticTokenCredential(16 * 60 * 1000), // new tokens expires in 16m + new StaticTokenCredential(16 * 60 * 1000), // new tokens expires in 16min ); const response = await authTranslator.decorateClusterDetailsWithAuth(cd); expect(response.serviceAccountToken).toEqual('MY_TOKEN_1'); - jest.useFakeTimers().setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2mins + jest.useFakeTimers().setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2min const response2 = await authTranslator.decorateClusterDetailsWithAuth(cd); expect(response2.serviceAccountToken).toEqual('MY_TOKEN_2'); - jest.useFakeTimers().setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2mins + jest.useFakeTimers().setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2min const response3 = await authTranslator.decorateClusterDetailsWithAuth(cd); expect(response3.serviceAccountToken).toEqual('MY_TOKEN_2'); - jest.useFakeTimers().setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2mins - const response4 = await authTranslator.decorateClusterDetailsWithAuth(cd); expect(response4.serviceAccountToken).toEqual('MY_TOKEN_4'); }); + + it('should throw if existing token expired and failed to fetch a new one', async () => { + const authTranslator = new AzureIdentityKubernetesAuthTranslator( + logger, + new StaticTokenCredential(16 * 60 * 1000), // new tokens expires in 16min + ); + + const response = await authTranslator.decorateClusterDetailsWithAuth(cd); + expect(response.serviceAccountToken).toEqual('MY_TOKEN_1'); + + jest.useFakeTimers().setSystemTime(Date.now() + 2 * 60 * 1000); // advance time by 2min + + const response2 = await authTranslator.decorateClusterDetailsWithAuth(cd); + expect(response2.serviceAccountToken).toEqual('MY_TOKEN_2'); + + jest.useFakeTimers().setSystemTime(Date.now() + 17 * 60 * 1000); // advance time by 17min + + await expect( + authTranslator.decorateClusterDetailsWithAuth(cd), + ).rejects.toThrow(); + }); }); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts index 9b913cccff..5b8a709270 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/AzureIdentityKubernetesAuthTranslator.ts @@ -29,7 +29,7 @@ export class AzureIdentityKubernetesAuthTranslator implements KubernetesAuthTranslator { private accessToken: AccessToken = { token: '', expiresOnTimestamp: 0 }; - private newToken: Promise | undefined; + private newTokenPromise: Promise | undefined; constructor( private readonly logger: Logger, @@ -49,15 +49,15 @@ export class AzureIdentityKubernetesAuthTranslator } private async getToken(): Promise { - if (this.isTokenValid()) { + if (!this.tokenRequiresRefresh()) { return this.accessToken.token; } - if (!this.newToken) { - this.newToken = this.fetchNewToken(); + if (!this.newTokenPromise) { + this.newTokenPromise = this.fetchNewToken(); } - return this.newToken; + return this.newTokenPromise; } private async fetchNewToken(): Promise { @@ -74,16 +74,24 @@ export class AzureIdentityKubernetesAuthTranslator this.accessToken = newAccessToken; } catch (err) { this.logger.error('Unable to fetch Azure token', err); - // don't throw the error, so the existing token will be re-used until we're able to fetch a new token + + // only throw the error if the token has already expired, otherwise re-use existing until we're able to fetch a new token + if (this.tokenExpired()) { + throw err; + } } - this.newToken = undefined; + this.newTokenPromise = undefined; return this.accessToken.token; } - private isTokenValid(): boolean { + private tokenRequiresRefresh(): boolean { // Set tokens to expire 15 minutes before its actual expiry time const expiresOn = this.accessToken.expiresOnTimestamp - 15 * 60 * 1000; - return expiresOn >= Date.now(); + return Date.now() >= expiresOn; + } + + private tokenExpired(): boolean { + return Date.now() >= this.accessToken.expiresOnTimestamp; } } diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts index dc0d589148..123b05456c 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.test.ts @@ -29,19 +29,19 @@ describe('getKubernetesAuthTranslatorInstance', () => { it('can return an auth translator for google auth', () => { const authTranslator: KubernetesAuthTranslator = - sut.getKubernetesAuthTranslatorInstance(logger, 'google'); + sut.getKubernetesAuthTranslatorInstance('google', { logger }); expect(authTranslator instanceof GoogleKubernetesAuthTranslator).toBe(true); }); it('can return an auth translator for aws auth', () => { const authTranslator: KubernetesAuthTranslator = - sut.getKubernetesAuthTranslatorInstance(logger, 'aws'); + sut.getKubernetesAuthTranslatorInstance('aws', { logger }); expect(authTranslator instanceof AwsIamKubernetesAuthTranslator).toBe(true); }); it('can return an auth translator for serviceAccount auth', () => { const authTranslator: KubernetesAuthTranslator = - sut.getKubernetesAuthTranslatorInstance(logger, 'serviceAccount'); + sut.getKubernetesAuthTranslatorInstance('serviceAccount', { logger }); expect( authTranslator instanceof ServiceAccountKubernetesAuthTranslator, ).toBe(true); @@ -49,13 +49,13 @@ describe('getKubernetesAuthTranslatorInstance', () => { it('can return an auth translator for oidc auth', () => { const authTranslator: KubernetesAuthTranslator = - sut.getKubernetesAuthTranslatorInstance(logger, 'oidc'); + sut.getKubernetesAuthTranslatorInstance('oidc', { logger }); expect(authTranslator instanceof OidcKubernetesAuthTranslator).toBe(true); }); it('throws an error when asked for an auth translator for an unsupported auth type', () => { expect(() => - sut.getKubernetesAuthTranslatorInstance(logger, 'linode'), + sut.getKubernetesAuthTranslatorInstance('linode', { logger }), ).toThrow( 'authProvider "linode" has no KubernetesAuthTranslator associated with it', ); diff --git a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts index 92c267cec9..fa69ad42b5 100644 --- a/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts +++ b/plugins/kubernetes-backend/src/kubernetes-auth-translator/KubernetesAuthTranslatorGenerator.ts @@ -25,8 +25,10 @@ import { OidcKubernetesAuthTranslator } from './OidcKubernetesAuthTranslator'; export class KubernetesAuthTranslatorGenerator { static getKubernetesAuthTranslatorInstance( - logger: Logger, authProvider: string, + options: { + logger: Logger; + }, ): KubernetesAuthTranslator { switch (authProvider) { case 'google': { @@ -36,7 +38,7 @@ export class KubernetesAuthTranslatorGenerator { return new AwsIamKubernetesAuthTranslator(); } case 'azure': { - return new AzureIdentityKubernetesAuthTranslator(logger); + return new AzureIdentityKubernetesAuthTranslator(options.logger); } case 'serviceAccount': { return new ServiceAccountKubernetesAuthTranslator(); diff --git a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts index e65754a8cb..275347ff82 100644 --- a/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts +++ b/plugins/kubernetes-backend/src/service/KubernetesFanOutHandler.ts @@ -293,8 +293,10 @@ export class KubernetesFanOutHandler { this.authTranslators[provider] = KubernetesAuthTranslatorGenerator.getKubernetesAuthTranslatorInstance( - this.logger, provider, + { + logger: this.logger, + }, ); return this.authTranslators[provider]; } From 68a63cf2e696fd57f4fd7e8afc96369f5966ec1b Mon Sep 17 00:00:00 2001 From: Matt Ng Date: Fri, 20 May 2022 13:28:39 -0400 Subject: [PATCH 046/149] Changing the header hierarchy while keeping the original styling Signed-off-by: Matt Ng --- .../src/components/ImportInfoCard/ImportInfoCard.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx index 339d71b476..7fa6b13a82 100644 --- a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx +++ b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.tsx @@ -63,7 +63,9 @@ export const ImportInfoCard = (props: ImportInfoCardProps) => { Enter the URL to your source code repository to add it to {appTitle}. - Link to an existing entity file + + Link to an existing entity file + Example: {exampleLocationUrl} @@ -73,7 +75,7 @@ export const ImportInfoCard = (props: ImportInfoCardProps) => {
{hasGithubIntegration && ( <> - + Link to a repository{' '} From cd8ad6d6003477a5cca02b0cba04b5aadd2ab7aa Mon Sep 17 00:00:00 2001 From: Matt Ng Date: Fri, 20 May 2022 13:30:16 -0400 Subject: [PATCH 047/149] updating to call this a patch Signed-off-by: Matt Ng --- .changeset/violet-apples-repair.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/violet-apples-repair.md b/.changeset/violet-apples-repair.md index 8bf6a6c1c9..a6be2797f6 100644 --- a/.changeset/violet-apples-repair.md +++ b/.changeset/violet-apples-repair.md @@ -1,5 +1,5 @@ --- -'@backstage/plugin-catalog-import': minor +'@backstage/plugin-catalog-import': patch --- Updated catalog import page text so they go in the correct hierarchy order From 8571448bcd32c9207eea469bb09eb417bf4cfff8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 20 May 2022 20:41:21 +0000 Subject: [PATCH 048/149] chore(deps): Bump dset from 3.1.0 to 3.1.2 Bumps [dset](https://github.com/lukeed/dset) from 3.1.0 to 3.1.2. - [Release notes](https://github.com/lukeed/dset/releases) - [Commits](https://github.com/lukeed/dset/compare/v3.1.0...v3.1.2) --- updated-dependencies: - dependency-name: dset dependency-type: indirect ... Signed-off-by: dependabot[bot] --- yarn.lock | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index 09207cd6bc..bd85eebbdc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10954,15 +10954,10 @@ drange@^1.0.2: resolved "https://registry.npmjs.org/drange/-/drange-1.1.1.tgz#b2aecec2aab82fcef11dbbd7b9e32b83f8f6c0b8" integrity sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA== -dset@^3.1.0: - version "3.1.0" - resolved "https://registry.npmjs.org/dset/-/dset-3.1.0.tgz#23feb6df93816ea452566308b1374d6e869b0d7b" - integrity sha512-7xTQ5DzyE59Nn+7ZgXDXjKAGSGmXZHqttMVVz1r4QNfmGpyj+cm2YtI3II0c/+4zS4a9yq2mBhgdeq2QnpcYlw== - -dset@^3.1.1: - version "3.1.1" - resolved "https://registry.npmjs.org/dset/-/dset-3.1.1.tgz#07de5af7a8d03eab337ad1a8ba77fe17bba61a8c" - integrity sha512-hYf+jZNNqJBD2GiMYb+5mqOIX4R4RRHXU3qWMWYN+rqcR2/YpRL2bUHr8C8fU+5DNvqYjJ8YvMGSLuVPWU1cNg== +dset@^3.1.0, dset@^3.1.1: + version "3.1.2" + resolved "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz#89c436ca6450398396dc6538ea00abc0c54cd45a" + integrity sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q== duplexer2@~0.1.4: version "0.1.4" From be89d6e7f375909348936018c709c6acd7dadd80 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 21 May 2022 10:48:34 +0200 Subject: [PATCH 049/149] fix references to search-backend-node Signed-off-by: Patrik Oldsberg --- .changeset/reject-failed-index-tasks.md | 2 +- plugins/catalog-backend/CHANGELOG.md | 2 +- plugins/catalog-backend/src/search/DefaultCatalogCollator.ts | 2 +- plugins/search-backend-module-elasticsearch/CHANGELOG.md | 4 ++-- plugins/search-backend-module-pg/CHANGELOG.md | 4 ++-- plugins/search-common/src/types.ts | 2 +- plugins/techdocs-backend/CHANGELOG.md | 2 +- .../techdocs-backend/src/search/DefaultTechDocsCollator.ts | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.changeset/reject-failed-index-tasks.md b/.changeset/reject-failed-index-tasks.md index 3c669ce545..b2f1f5ad6a 100644 --- a/.changeset/reject-failed-index-tasks.md +++ b/.changeset/reject-failed-index-tasks.md @@ -1,5 +1,5 @@ --- -'@backstage/search-backend-node': patch +'@backstage/plugin-search-backend-node': patch --- propagate indexing errors so they don't appear successful to the task scheduler diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index c4679de20a..44e470750d 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -495,7 +495,7 @@ - 022507c860: A `DefaultCatalogCollatorFactory`, which works with the new stream-based search indexing subsystem, is now available. The `DefaultCatalogCollator` will continue to be available for those unable to upgrade to the stream-based - `@backstage/search-backend-node` (and related packages), however it is now + `@backstage/plugin-search-backend-node` (and related packages), however it is now marked as deprecated and will be removed in a future version. To upgrade this plugin and the search indexing subsystem in one go, check diff --git a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts index bb44670443..f895ded85e 100644 --- a/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts +++ b/plugins/catalog-backend/src/search/DefaultCatalogCollator.ts @@ -37,7 +37,7 @@ import { Permission } from '@backstage/plugin-permission-common'; /** * @public - * @deprecated Upgrade to a more recent `@backstage/search-backend-node` and + * @deprecated Upgrade to a more recent `@backstage/plugin-search-backend-node` and * use `DefaultCatalogCollatorFactory` instead. */ export class DefaultCatalogCollator { diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index b5c8adaea7..f2344d430c 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -91,10 +91,10 @@ - 022507c860: **BREAKING** The `ElasticSearchSearchEngine` implements the new stream-based indexing - process expected by the latest `@backstage/search-backend-node`. + process expected by the latest `@backstage/plugin-search-backend-node`. When updating to this version, you must also update to the latest version of - `@backstage/search-backend-node`. Check [this upgrade guide](https://backstage.io/docs/features/search/how-to-guides#how-to-migrate-from-search-alpha-to-beta) + `@backstage/plugin-search-backend-node`. Check [this upgrade guide](https://backstage.io/docs/features/search/how-to-guides#how-to-migrate-from-search-alpha-to-beta) for further details. ### Patch Changes diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index b4823f448d..af48846754 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -83,10 +83,10 @@ - 022507c860: **BREAKING** The `PgSearchEngine` implements the new stream-based indexing process expected - by the latest `@backstage/search-backend-node`. + by the latest `@backstage/plugin-search-backend-node`. When updating to this version, you must also update to the latest version of - `@backstage/search-backend-node`. Check [this upgrade guide](https://backstage.io/docs/features/search/how-to-guides#how-to-migrate-from-search-alpha-to-beta) + `@backstage/plugin-search-backend-node`. Check [this upgrade guide](https://backstage.io/docs/features/search/how-to-guides#how-to-migrate-from-search-alpha-to-beta) for further details. ### Patch Changes diff --git a/plugins/search-common/src/types.ts b/plugins/search-common/src/types.ts index 220ac977e5..f77e819364 100644 --- a/plugins/search-common/src/types.ts +++ b/plugins/search-common/src/types.ts @@ -134,7 +134,7 @@ export type IndexableDocument = SearchDocument & { /** * Information about a specific document type. Intended to be used in the - * {@link @backstage/search-backend-node#IndexBuilder} to collect information + * {@link @backstage/plugin-search-backend-node#IndexBuilder} to collect information * about the types stored in the index. * @beta */ diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 1ecc97e1aa..897d308c6e 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -190,7 +190,7 @@ - 022507c860: A `DefaultTechDocsCollatorFactory`, which works with the new stream-based search indexing subsystem, is now available. The `DefaultTechDocsCollator` will continue to be available for those unable to upgrade to the stream-based - `@backstage/search-backend-node` (and related packages), however it is now + `@backstage/plugin-search-backend-node` (and related packages), however it is now marked as deprecated and will be removed in a future version. To upgrade this plugin and the search indexing subsystem in one go, check diff --git a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts index bf647322cf..a76ea66729 100644 --- a/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts +++ b/plugins/techdocs-backend/src/search/DefaultTechDocsCollator.ts @@ -69,7 +69,7 @@ type EntityInfo = { * A search collator responsible for gathering and transforming TechDocs documents. * * @public - * @deprecated Upgrade to a more recent `@backstage/search-backend-node` and + * @deprecated Upgrade to a more recent `@backstage/plugin-search-backend-node` and * use `DefaultTechDocsCollatorFactory` instead. */ export class DefaultTechDocsCollator { From cfec39f1a77239bd2b269e45e981a86273a93f13 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 21 May 2022 10:54:57 +0200 Subject: [PATCH 050/149] backend-common: changelog updates for patch release Signed-off-by: Patrik Oldsberg --- .changeset/odd-baboons-buy.md | 2 +- packages/backend-common/CHANGELOG.md | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/.changeset/odd-baboons-buy.md b/.changeset/odd-baboons-buy.md index 6fe3826985..3a7f5584b4 100644 --- a/.changeset/odd-baboons-buy.md +++ b/.changeset/odd-baboons-buy.md @@ -2,4 +2,4 @@ '@backstage/backend-common': patch --- -Fix a bug in the URL Reading towards AWS S3 where it would hang indefinitely. +Applied the AWS S3 reading patch from the `0.13.5` patch release. diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index ed4282299c..d26ea09e04 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/backend-common +## 0.13.5 + +### Patch Changes + +- 667d2ed6f8: Fix a bug in the URL Reading towards AWS S3 where it would hang indefinitely. + ## 0.13.4 ### Patch Changes From 6dcc5f1d3e6bcae63dc0a6870af8307527c42a98 Mon Sep 17 00:00:00 2001 From: Manuel Scurti Date: Sat, 21 May 2022 12:49:39 +0200 Subject: [PATCH 051/149] fixed migration script for signing_keys Signed-off-by: Manuel Scurti --- .../auth-backend/migrations/20200619125845_init.js | 12 +++++++++++- plugins/auth-backend/src/identity/TokenFactory.ts | 3 +++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/plugins/auth-backend/migrations/20200619125845_init.js b/plugins/auth-backend/migrations/20200619125845_init.js index d5fc08ce48..6697d168c0 100644 --- a/plugins/auth-backend/migrations/20200619125845_init.js +++ b/plugins/auth-backend/migrations/20200619125845_init.js @@ -20,6 +20,13 @@ * @param {import('knex').Knex} knex */ exports.up = async function up(knex) { + /** + * key field length. must be enough for the chosen JWT signing algorithm. + * the default value is set to be enough for all supported algorithms of the + * `jose` library. + */ + const SIGNING_KEY_MAX_LENGTH = 512; + return knex.schema.createTable('signing_keys', table => { table.comment( 'Signing keys that are currently in use or have recently been used to issue tokens', @@ -34,7 +41,10 @@ exports.up = async function up(knex) { .notNullable() .defaultTo(knex.fn.now()) .comment('The creation time of the key'); - table.string('key').notNullable().comment('The serialized signing key'); + table + .string('key', SIGNING_KEY_MAX_LENGTH) + .notNullable() + .comment('The serialized signing key'); }); }; diff --git a/plugins/auth-backend/src/identity/TokenFactory.ts b/plugins/auth-backend/src/identity/TokenFactory.ts index fe9d2a1b4f..fdc7210650 100644 --- a/plugins/auth-backend/src/identity/TokenFactory.ts +++ b/plugins/auth-backend/src/identity/TokenFactory.ts @@ -34,6 +34,9 @@ type Options = { keyDurationSeconds: number; /** JWS "alg" (Algorithm) Header Parameter value. Defaults to ES256. * Must match one of the algorithms defined for IdentityClient. + * When setting a different algorithm, check if the `key` field + * of the `signing_keys` table can fit the length of the generated keys. + * If not, modify the migration file in the migrations folder. * More info on supported algorithms: https://github.com/panva/jose */ algorithm?: string; }; From 5e055079f09dab315d37a58dac799673af8bccb9 Mon Sep 17 00:00:00 2001 From: Manuel Scurti Date: Sat, 21 May 2022 13:00:40 +0200 Subject: [PATCH 052/149] added changeset Signed-off-by: Manuel Scurti --- .changeset/fifty-planes-dream.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/fifty-planes-dream.md diff --git a/.changeset/fifty-planes-dream.md b/.changeset/fifty-planes-dream.md new file mode 100644 index 0000000000..655cd42dc1 --- /dev/null +++ b/.changeset/fifty-planes-dream.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Increased key field size for signing_keys table to account for larger signature keys From 416014b7b810f5d58f3bf7a111efec4a2b9662ef Mon Sep 17 00:00:00 2001 From: Manuel Scurti Date: Sun, 22 May 2022 19:30:40 +0200 Subject: [PATCH 053/149] added migration file Signed-off-by: Manuel Scurti --- .../migrations/20200619125845_init.js | 12 +---- .../20220522100910_key_field_size.js | 49 +++++++++++++++++++ .../auth-backend/src/identity/TokenFactory.ts | 2 +- 3 files changed, 51 insertions(+), 12 deletions(-) create mode 100644 plugins/auth-backend/migrations/20220522100910_key_field_size.js diff --git a/plugins/auth-backend/migrations/20200619125845_init.js b/plugins/auth-backend/migrations/20200619125845_init.js index 6697d168c0..d5fc08ce48 100644 --- a/plugins/auth-backend/migrations/20200619125845_init.js +++ b/plugins/auth-backend/migrations/20200619125845_init.js @@ -20,13 +20,6 @@ * @param {import('knex').Knex} knex */ exports.up = async function up(knex) { - /** - * key field length. must be enough for the chosen JWT signing algorithm. - * the default value is set to be enough for all supported algorithms of the - * `jose` library. - */ - const SIGNING_KEY_MAX_LENGTH = 512; - return knex.schema.createTable('signing_keys', table => { table.comment( 'Signing keys that are currently in use or have recently been used to issue tokens', @@ -41,10 +34,7 @@ exports.up = async function up(knex) { .notNullable() .defaultTo(knex.fn.now()) .comment('The creation time of the key'); - table - .string('key', SIGNING_KEY_MAX_LENGTH) - .notNullable() - .comment('The serialized signing key'); + table.string('key').notNullable().comment('The serialized signing key'); }); }; diff --git a/plugins/auth-backend/migrations/20220522100910_key_field_size.js b/plugins/auth-backend/migrations/20220522100910_key_field_size.js new file mode 100644 index 0000000000..c6637525f3 --- /dev/null +++ b/plugins/auth-backend/migrations/20220522100910_key_field_size.js @@ -0,0 +1,49 @@ +/* + * 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. + */ + +// @ts-check + +/** + * @param {import('knex').Knex} knex + */ +exports.up = async function up(knex) { + // Sqlite does not support alter column. + if (!knex.client.config.client.includes('sqlite3')) { + await knex.schema.alterTable('signing_keys', table => { + table + .text('key') + .notNullable() + .comment('The serialized signing key') + .alter({ alterType: true }); + }); + } +}; + +/** + * @param {import('knex').Knex} knex + */ +exports.down = async function down(knex) { + // Sqlite does not support alter column. + if (!knex.client.config.client.includes('sqlite3')) { + await knex.schema.alterTable('signing_keys', table => { + table + .string('key') + .notNullable() + .comment('The serialized signing key') + .alter({ alterType: true }); + }); + } +}; diff --git a/plugins/auth-backend/src/identity/TokenFactory.ts b/plugins/auth-backend/src/identity/TokenFactory.ts index fdc7210650..44cfebfb71 100644 --- a/plugins/auth-backend/src/identity/TokenFactory.ts +++ b/plugins/auth-backend/src/identity/TokenFactory.ts @@ -36,7 +36,7 @@ type Options = { * Must match one of the algorithms defined for IdentityClient. * When setting a different algorithm, check if the `key` field * of the `signing_keys` table can fit the length of the generated keys. - * If not, modify the migration file in the migrations folder. + * If not, add a knex migration file in the migrations folder. * More info on supported algorithms: https://github.com/panva/jose */ algorithm?: string; }; From d4a00ed16762d1236bbb407e16d4692d0b862d8a Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Sun, 22 May 2022 08:53:52 +0200 Subject: [PATCH 054/149] refactor(techdocs): extract links sanitizer hook Signed-off-by: Camila Belo --- .../reader/transformers/html/hooks/links.ts | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/html/hooks/links.ts diff --git a/plugins/techdocs/src/reader/transformers/html/hooks/links.ts b/plugins/techdocs/src/reader/transformers/html/hooks/links.ts new file mode 100644 index 0000000000..38f775cfbe --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/html/hooks/links.ts @@ -0,0 +1,51 @@ +/* + * 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 MKDOCS_CSS = /main\.[A-Fa-f0-9]{8}\.min\.css$/; +const GOOGLE_FONTS = /^https:\/\/fonts\.googleapis\.com/; +const GSTATIC_FONTS = /^https:\/\/fonts\.gstatic\.com/; + +/** + * Checks whether a node is link or not. + * @param node - can be any element. + * @returns true when node is link. + */ +const isLink = (node: Element) => node.nodeName === 'LINK'; + +/** + * Checks whether a link is safe or not. + * @param node - is an link element. + * @returns true when link is mkdocs css, google fonts or gstatic fonts. + */ +const isSafe = (node: Element) => { + const href = node?.getAttribute('href') || ''; + const isMkdocsCss = href.match(MKDOCS_CSS); + const isGoogleFonts = href.match(GOOGLE_FONTS); + const isGstaticFonts = href.match(GSTATIC_FONTS); + return isMkdocsCss || isGoogleFonts || isGstaticFonts; +}; + +/** + * Function that removes unsafe link nodes. + * @param node - can be any element. + * @param hosts - list of allowed hosts. + */ +export const removeUnsafeLinks = (node: Element) => { + if (isLink(node) && !isSafe(node)) { + node.remove(); + } + return node; +}; From 4a70a448af4f6f8102949198451117ad131f0fe6 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 23 May 2022 08:31:27 +0200 Subject: [PATCH 055/149] refactor(techdocs): extract iframes sanitizer hook Signed-off-by: Camila Belo --- .../reader/transformers/html/hooks/iframes.ts | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/html/hooks/iframes.ts diff --git a/plugins/techdocs/src/reader/transformers/html/hooks/iframes.ts b/plugins/techdocs/src/reader/transformers/html/hooks/iframes.ts new file mode 100644 index 0000000000..25259dbc43 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/html/hooks/iframes.ts @@ -0,0 +1,50 @@ +/* + * 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. + */ + +/** + * Checks whether a node is iframe or not. + * @param node - can be any element. + * @returns true when node is iframe. + */ +const isIframe = (node: Element) => node.nodeName === 'IFRAME'; + +/** + * Checks whether a iframe is safe or not. + * @param node - is an iframe element. + * @param hosts - list of allowed hosts. + * @returns true when iframe is included in hosts. + */ +const isSafe = (node: Element, hosts: string[]) => { + const src = node.getAttribute('src') || ''; + try { + const { host } = new URL(src); + return hosts.includes(host); + } catch { + return false; + } +}; + +/** + * Returns a function that removes unsafe iframe nodes. + * @param node - can be any element. + * @param hosts - list of allowed hosts. + */ +export const removeUnsafeIframes = (hosts: string[]) => (node: Element) => { + if (isIframe(node) && !isSafe(node, hosts)) { + node.remove(); + } + return node; +}; From b2b19f170ec6c9a18c6174bb56bb8834436b8200 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 23 May 2022 08:32:09 +0200 Subject: [PATCH 056/149] refactor(techdocs): export html sanitizer hooks Signed-off-by: Camila Belo --- .../reader/transformers/html/hooks/hooks.ts | 18 ++++++++++++++++++ .../reader/transformers/html/hooks/index.ts | 17 +++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/html/hooks/hooks.ts create mode 100644 plugins/techdocs/src/reader/transformers/html/hooks/index.ts diff --git a/plugins/techdocs/src/reader/transformers/html/hooks/hooks.ts b/plugins/techdocs/src/reader/transformers/html/hooks/hooks.ts new file mode 100644 index 0000000000..a4356db2e9 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/html/hooks/hooks.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 { removeUnsafeLinks } from './links'; +export { removeUnsafeIframes } from './iframes'; diff --git a/plugins/techdocs/src/reader/transformers/html/hooks/index.ts b/plugins/techdocs/src/reader/transformers/html/hooks/index.ts new file mode 100644 index 0000000000..445136beee --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/html/hooks/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 './hooks'; From beba2615d3dd4f591a12fc49d07212ea617bbe51 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 23 May 2022 08:32:39 +0200 Subject: [PATCH 057/149] refactor(techdocs): create html sanitizer transformer Signed-off-by: Camila Belo --- .../reader/transformers/html/transformer.ts | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/html/transformer.ts diff --git a/plugins/techdocs/src/reader/transformers/html/transformer.ts b/plugins/techdocs/src/reader/transformers/html/transformer.ts new file mode 100644 index 0000000000..0c28d3cc49 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/html/transformer.ts @@ -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 DOMPurify from 'dompurify'; +import { useMemo, useCallback } from 'react'; + +import { useApi, configApiRef } from '@backstage/core-plugin-api'; + +import { Transformer } from '..'; +import { removeUnsafeLinks, removeUnsafeIframes } from './hooks'; + +/** + * Returns html sanitizer configuration + */ +const useSanitizerConfig = () => { + const configApi = useApi(configApiRef); + + return useMemo(() => { + return configApi.getOptionalConfig('techdocs.sanitizer'); + }, [configApi]); +}; + +/** + * Returns a transformer that sanitizes the dom's internal html. + */ +export const useHtmlTransformer = (): Transformer => { + const config = useSanitizerConfig(); + + return useCallback( + async (dom: Element) => { + const hosts = config?.getOptionalStringArray('allowedIframeHosts'); + + DOMPurify.addHook('beforeSanitizeElements', removeUnsafeLinks); + const tags = ['link']; + + if (hosts) { + tags.push('iframe'); + DOMPurify.addHook('beforeSanitizeElements', removeUnsafeIframes(hosts)); + } + + return DOMPurify.sanitize(dom.innerHTML, { + ADD_TAGS: tags, + FORBID_TAGS: ['style'], + WHOLE_DOCUMENT: true, + RETURN_DOM: true, + }); + }, + [config], + ); +}; From 131a99e909c0ab3834f52afab158ebf321da5bae Mon Sep 17 00:00:00 2001 From: Christian Probst Date: Wed, 18 May 2022 08:27:10 +0200 Subject: [PATCH 058/149] Align common.schema.json with Entity type Added the field `targetRef` to match the Typescript type Signed-off-by: Christian Probst --- .changeset/shaggy-crabs-return.md | 5 +++++ packages/catalog-model/src/schema/shared/common.schema.json | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changeset/shaggy-crabs-return.md diff --git a/.changeset/shaggy-crabs-return.md b/.changeset/shaggy-crabs-return.md new file mode 100644 index 0000000000..afd1f74ef5 --- /dev/null +++ b/.changeset/shaggy-crabs-return.md @@ -0,0 +1,5 @@ +--- +'@backstage/catalog-model': patch +--- + +Added targetRef to common.schema.json to match the Typescript type diff --git a/packages/catalog-model/src/schema/shared/common.schema.json b/packages/catalog-model/src/schema/shared/common.schema.json index a9082d53e4..5a5eb97bb7 100644 --- a/packages/catalog-model/src/schema/shared/common.schema.json +++ b/packages/catalog-model/src/schema/shared/common.schema.json @@ -43,6 +43,11 @@ }, "target": { "$ref": "#reference" + }, + "targetRef": { + "type": "string", + "minLength": 1, + "description": "The entity ref of the target of this relation." } } }, From 6cd30abd0477dc71354ef034ee1970a8a46665a2 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 23 May 2022 08:33:51 +0200 Subject: [PATCH 059/149] test(techdocs): cover html sanitizer hook Signed-off-by: Camila Belo --- .../transformers/html/transformer.test.tsx | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/html/transformer.test.tsx diff --git a/plugins/techdocs/src/reader/transformers/html/transformer.test.tsx b/plugins/techdocs/src/reader/transformers/html/transformer.test.tsx new file mode 100644 index 0000000000..ab64eccaa4 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/html/transformer.test.tsx @@ -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 React, { FC } from 'react'; +import { renderHook } from '@testing-library/react-hooks'; + +import { ConfigReader } from '@backstage/core-app-api'; +import { ConfigApi, configApiRef } from '@backstage/core-plugin-api'; +import { TestApiProvider } from '@backstage/test-utils'; + +import { useHtmlTransformer } from './transformer'; + +const configApiMock: ConfigApi = new ConfigReader({ + techdocs: { + sanitizer: { + allowedIframeHosts: ['docs.google.com'], + }, + }, +}); + +const wrapper: FC = ({ children }) => ( + + {children} + +); + +describe('Transformers > Html', () => { + it('should return a function that removes unsafe links from a given dom element', async () => { + const { result } = renderHook(() => useHtmlTransformer(), { wrapper }); + + const dirtyDom = document.createElement('html'); + dirtyDom.innerHTML = ` + + + + + + + `; + const clearDom = await result.current(dirtyDom); // calling html transformer + + const links = Array.from( + clearDom.querySelectorAll('head > link'), + ); + expect(links).toHaveLength(3); + expect(links[0].href).toMatch('assets/stylesheets/main.50e68009.min.css'); + expect(links[1].href).toMatch('https://fonts.googleapis.com'); + expect(links[2].href).toMatch('https://fonts.gstatic.com'); + }); + + it('should return a function that removes unsafe iframes from a given dom element', async () => { + const { result } = renderHook(() => useHtmlTransformer(), { wrapper }); + + const dirtyDom = document.createElement('html'); + dirtyDom.innerHTML = ` + + + + + + `; + const clearDom = await result.current(dirtyDom); // calling html transformer + + const iframes = Array.from( + clearDom.querySelectorAll('body > iframe'), + ); + + expect(iframes).toHaveLength(1); + expect(iframes[0].src).toMatch('docs.google.com'); + }); +}); From 0a5e6ffcf603286d5d5122f1ec3f7fb7a6a7edb5 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 23 May 2022 08:34:32 +0200 Subject: [PATCH 060/149] refactor(techdocs): export html sanitizer transformer Signed-off-by: Camila Belo --- .../src/reader/transformers/html/index.ts | 17 +++++++++++++++++ .../techdocs/src/reader/transformers/index.ts | 1 + 2 files changed, 18 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/html/index.ts diff --git a/plugins/techdocs/src/reader/transformers/html/index.ts b/plugins/techdocs/src/reader/transformers/html/index.ts new file mode 100644 index 0000000000..ef2708dc8e --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/html/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 { useHtmlTransformer } from './transformer'; diff --git a/plugins/techdocs/src/reader/transformers/index.ts b/plugins/techdocs/src/reader/transformers/index.ts index dcd79180a7..748d702fdb 100644 --- a/plugins/techdocs/src/reader/transformers/index.ts +++ b/plugins/techdocs/src/reader/transformers/index.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +export * from './html'; export * from './addBaseUrl'; export * from './addGitFeedbackLink'; export * from './addSidebarToggle'; From 427ecd4e7c69ccf2c4ffd45cff5bca30e88ac937 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 23 May 2022 08:35:05 +0200 Subject: [PATCH 061/149] refactor(techdocs): use html sanitizer transformer Signed-off-by: Camila Belo --- .../components/TechDocsReaderPageContent/dom.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx index 87331c2e46..6d70623a50 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx @@ -22,7 +22,7 @@ import { lighten, alpha } from '@material-ui/core/styles'; import { BackstageTheme } from '@backstage/theme'; import { CompoundEntityRef } from '@backstage/catalog-model'; -import { useApi, configApiRef } from '@backstage/core-plugin-api'; +import { useApi } from '@backstage/core-plugin-api'; import { SidebarPinStateContext } from '@backstage/core-components'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; @@ -42,11 +42,11 @@ import { onCssReady, removeMkdocsHeader, rewriteDocLinks, - sanitizeDOM, simplifyMkdocsFooter, scrollIntoAnchor, transform as transformer, copyToClipboard, + useHtmlTransformer, } from '../../transformers'; const MOBILE_MEDIA_QUERY = 'screen and (max-width: 76.1875em)'; @@ -77,8 +77,8 @@ export const useTechDocsReaderDom = ( const sidebar = useSidebar(); const theme = useTheme(); const isMobileMedia = useMediaQuery(MOBILE_MEDIA_QUERY); + const htmlTransformer = useHtmlTransformer(); - const configApi = useApi(configApiRef); const techdocsStorageApi = useApi(techdocsStorageApiRef); const scmIntegrationsApi = useApi(scmIntegrationsApiRef); @@ -146,7 +146,7 @@ export const useTechDocsReaderDom = ( const preRender = useCallback( (rawContent: string, contentPath: string) => transformer(rawContent, [ - sanitizeDOM(configApi.getOptionalConfig('techdocs.sanitizer')), + htmlTransformer, addBaseUrl({ techdocsStorageApi, entityId: entityRef, @@ -696,9 +696,9 @@ export const useTechDocsReaderDom = ( entityRef, theme, sidebar, - configApi, scmIntegrationsApi, techdocsStorageApi, + htmlTransformer, ], ); From f63b6754045dd574ac175c79a3597055c4bd2cec Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 23 May 2022 08:35:47 +0200 Subject: [PATCH 062/149] refactor(techdocs): delete sanitize dom transformer Signed-off-by: Camila Belo --- .../techdocs/src/reader/transformers/index.ts | 1 - .../reader/transformers/sanitizeDOM.test.ts | 254 ------------------ .../src/reader/transformers/sanitizeDOM.ts | 87 ------ 3 files changed, 342 deletions(-) delete mode 100644 plugins/techdocs/src/reader/transformers/sanitizeDOM.test.ts delete mode 100644 plugins/techdocs/src/reader/transformers/sanitizeDOM.ts diff --git a/plugins/techdocs/src/reader/transformers/index.ts b/plugins/techdocs/src/reader/transformers/index.ts index 748d702fdb..bc72f2d78f 100644 --- a/plugins/techdocs/src/reader/transformers/index.ts +++ b/plugins/techdocs/src/reader/transformers/index.ts @@ -24,7 +24,6 @@ export * from './copyToClipboard'; export * from './removeMkdocsHeader'; export * from './simplifyMkdocsFooter'; export * from './onCssReady'; -export * from './sanitizeDOM'; export * from './injectCss'; export * from './scrollIntoAnchor'; export * from './transformer'; diff --git a/plugins/techdocs/src/reader/transformers/sanitizeDOM.test.ts b/plugins/techdocs/src/reader/transformers/sanitizeDOM.test.ts deleted file mode 100644 index 697b01259c..0000000000 --- a/plugins/techdocs/src/reader/transformers/sanitizeDOM.test.ts +++ /dev/null @@ -1,254 +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 { ConfigReader } from '@backstage/config'; -import { createTestShadowDom, FIXTURES } from '../../test-utils'; -import { Transformer } from './index'; -import { sanitizeDOM } from './sanitizeDOM'; - -const injectMaliciousLink = (): Transformer => dom => { - const link = document.createElement('a'); - link.setAttribute('id', 'test-malicious-link'); - link.setAttribute('onclick', 'alert("Hello world");'); - dom.querySelector('body')?.appendChild(link); - return dom; -}; - -describe('sanitizeDOM', () => { - it('contains a script tag', async () => { - const shadowDom = await createTestShadowDom(FIXTURES.FIXTURE_STANDARD_PAGE); - - expect(shadowDom.querySelectorAll('script').length).toBeGreaterThan(0); - }); - - it('does not contain a script tag', async () => { - const shadowDom = await createTestShadowDom( - FIXTURES.FIXTURE_STANDARD_PAGE, - { - preTransformers: [sanitizeDOM()], - postTransformers: [], - }, - ); - - expect(shadowDom.querySelectorAll('script').length).toBe(0); - }); - - it('contains link with a onClick attribute', async () => { - const shadowDom = await createTestShadowDom( - FIXTURES.FIXTURE_STANDARD_PAGE, - { - preTransformers: [injectMaliciousLink()], - postTransformers: [], - }, - ); - - expect( - shadowDom.querySelector('#test-malicious-link')?.hasAttribute('onclick'), - ).toBeTruthy(); - }); - - it('does not contain link with a onClick attribute', async () => { - const shadowDom = await createTestShadowDom( - FIXTURES.FIXTURE_STANDARD_PAGE, - { - preTransformers: [sanitizeDOM()], - postTransformers: [], - }, - ); - - expect( - shadowDom.querySelector('#test-malicious-link')?.hasAttribute('onclick'), - ).toBeFalsy(); - }); - - it('removes style tags', async () => { - const html = ` - - - - - - - - `; - - const shadowDom = await createTestShadowDom(html, { - preTransformers: [sanitizeDOM()], - postTransformers: [], - }); - - expect(shadowDom.querySelectorAll('style').length).toEqual(0); - }); - - it('does not remove link tags', async () => { - const html = ` - - - - - - - - `; - - const shadowDom = await createTestShadowDom(html, { - preTransformers: [sanitizeDOM()], - postTransformers: [], - }); - - expect(shadowDom.querySelectorAll('link').length).toEqual(1); - }); - - it('render iframe where src host is in allowedIframeHosts', async () => { - const html = ` - - - - - - - - - - `; - const config = new ConfigReader({ - allowedIframeHosts: ['example.com'], - }); - const shadowDom = await createTestShadowDom(html, { - preTransformers: [sanitizeDOM(config)], - postTransformers: [], - }); - expect(shadowDom.querySelectorAll('link').length).toEqual(1); - expect(shadowDom.querySelectorAll('iframe').length).toEqual(1); - expect(shadowDom.querySelectorAll('iframe')[0].getAttribute('src')).toBe( - 'https://example.com?test=1', - ); - }); - - it('should remove all iframes without allowedIframeHosts', async () => { - const html = ` - - - - - - - - - - `; - const config = new ConfigReader({}); - const shadowDom = await createTestShadowDom(html, { - preTransformers: [sanitizeDOM(config)], - postTransformers: [], - }); - expect(shadowDom.querySelectorAll('link').length).toEqual(1); - expect(shadowDom.querySelectorAll('iframe').length).toEqual(0); - }); - - it('should remove iframe with invalid url in src', async () => { - const html = ` - - - - - - - - - `; - const config = new ConfigReader({ - allowedIframeHosts: ['example.com'], - }); - const shadowDom = await createTestShadowDom(html, { - preTransformers: [sanitizeDOM(config)], - postTransformers: [], - }); - expect(shadowDom.querySelectorAll('link').length).toEqual(1); - expect(shadowDom.querySelectorAll('iframe').length).toEqual(0); - }); - - test.each([ - { key: 'allow', value: '"camera \'none\'"', allowed: false }, - { key: 'allowfullscreen', value: true, allowed: false }, - { key: 'allowpaymentrequest', value: true, allowed: false }, - { key: 'height', value: true, allowed: true }, - { key: 'loading', value: "'lazy'", allowed: true }, - { key: 'name', value: "'example'", allowed: true }, - { key: 'referrerpolicy', value: "'no-referrer'", allowed: false }, - { key: 'sandbox', value: "'allow-forms'", allowed: false }, - { key: 'srcdoc', value: "'

Hello world!

'", allowed: false }, - { key: 'onload', value: "'alert(1)'", allowed: false }, - ])('check if the iframe has the attribute %p', async attr => { - const html = ` - - - - - - - - - `; - const config = new ConfigReader({ - allowedIframeHosts: ['example.com'], - }); - const shadowDom = await createTestShadowDom(html, { - preTransformers: [sanitizeDOM(config)], - postTransformers: [], - }); - expect(shadowDom.querySelectorAll('link').length).toEqual(1); - expect(shadowDom.querySelectorAll('iframe').length).toEqual(1); - expect(shadowDom.querySelectorAll('iframe')[0].hasAttribute(attr.key)).toBe( - attr.allowed, - ); - }); - - describe('safe head links', () => { - let shadowDom: ShadowRoot; - - beforeEach(async () => { - shadowDom = await createTestShadowDom(FIXTURES.FIXTURE_STANDARD_PAGE, { - preTransformers: [sanitizeDOM()], - postTransformers: [], - }); - }); - - it('should not sanitize the techdocs css', async () => { - const techdocsCss = shadowDom.querySelector( - 'link[href$="main.fe0cca5b.min.css"]', - ); - const rel = techdocsCss!.getAttribute('rel'); - expect(rel).toBe('stylesheet'); - }); - - it('should not sanitize google fonts', async () => { - const googleFonts = shadowDom.querySelector( - 'link[href^="https://fonts.googleapis.com"]', - ); - const rel = googleFonts!.getAttribute('rel'); - expect(rel).toBe('stylesheet'); - }); - - it('should not sanitize gstatic fonts', async () => { - const gstaticFonts = shadowDom.querySelector( - 'link[href^="https://fonts.gstatic.com"]', - ); - const rel = gstaticFonts!.getAttribute('rel'); - expect(rel).toBe('preconnect'); - }); - }); -}); diff --git a/plugins/techdocs/src/reader/transformers/sanitizeDOM.ts b/plugins/techdocs/src/reader/transformers/sanitizeDOM.ts deleted file mode 100644 index da4bd793be..0000000000 --- a/plugins/techdocs/src/reader/transformers/sanitizeDOM.ts +++ /dev/null @@ -1,87 +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. - */ - -const TECHDOCS_CSS = /main\.[A-Fa-f0-9]{8}\.min\.css$/; -const GOOGLE_FONTS = /^https:\/\/fonts\.googleapis\.com/; -const GSTATIC_FONTS = /^https:\/\/fonts\.gstatic\.com/; - -export const safeLinksHook = (node: Element) => { - if (node.nodeName && node.nodeName === 'LINK') { - const href = node.getAttribute('href') || ''; - if (href.match(TECHDOCS_CSS)) { - node.setAttribute('rel', 'stylesheet'); - } - if (href.match(GOOGLE_FONTS)) { - node.setAttribute('rel', 'stylesheet'); - } - if (href.match(GSTATIC_FONTS)) { - node.setAttribute('rel', 'preconnect'); - } - } - return node; -}; - -const filterIframeHook = (allowedIframeHosts: string[]) => (node: Element) => { - if (node.nodeName === 'IFRAME') { - const src = node.getAttribute('src'); - if (!src) { - node.remove(); - return node; - } - - try { - const srcUrl = new URL(src); - const isMatch = allowedIframeHosts.some(host => srcUrl.host === host); - if (!isMatch) { - node.remove(); - } - } catch (error) { - // eslint-disable-next-line no-console - console.warn(`Invalid iframe src, ${error}`); - node.remove(); - } - } - return node; -}; - -import { Config } from '@backstage/config'; -import DOMPurify from 'dompurify'; -import type { Transformer } from './transformer'; - -export const sanitizeDOM = (config?: Config): Transformer => { - const allowedIframeHosts = - config?.getOptionalStringArray('allowedIframeHosts') || []; - - return dom => { - DOMPurify.addHook('afterSanitizeAttributes', safeLinksHook); - const addTags = ['link']; - - if (allowedIframeHosts.length > 0) { - DOMPurify.addHook( - 'beforeSanitizeElements', - filterIframeHook(allowedIframeHosts), - ); - addTags.push('iframe'); - } - - return DOMPurify.sanitize(dom.innerHTML, { - ADD_TAGS: addTags, - FORBID_TAGS: ['style'], - WHOLE_DOCUMENT: true, - RETURN_DOM: true, - }); - }; -}; From 816f7475ec9ff420160360779e5ba54961d03dec Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 23 May 2022 08:43:03 +0200 Subject: [PATCH 063/149] chore: add changeset file Signed-off-by: Camila Belo --- .changeset/techdocs-vans-run.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/techdocs-vans-run.md diff --git a/.changeset/techdocs-vans-run.md b/.changeset/techdocs-vans-run.md new file mode 100644 index 0000000000..2e59da8ad5 --- /dev/null +++ b/.changeset/techdocs-vans-run.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Convert `sanitizeDOM` transformer to hook as part of code readability improvements in dom file. From bf8219d0fec100047ccb1a7d82fe11ca0931f7a6 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 23 May 2022 13:03:29 +0200 Subject: [PATCH 064/149] refator(techdocs): apply review suggestions Signed-off-by: Camila Belo --- .../TechDocsReaderPageContent/dom.tsx | 8 ++++---- .../reader/transformers/html/hooks/hooks.ts | 18 ------------------ .../reader/transformers/html/hooks/index.ts | 3 ++- .../src/reader/transformers/html/index.ts | 2 +- .../transformers/html/transformer.test.tsx | 6 +++--- .../reader/transformers/html/transformer.ts | 4 ++-- 6 files changed, 12 insertions(+), 29 deletions(-) delete mode 100644 plugins/techdocs/src/reader/transformers/html/hooks/hooks.ts diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx index 6d70623a50..6a825a4788 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx @@ -46,7 +46,7 @@ import { scrollIntoAnchor, transform as transformer, copyToClipboard, - useHtmlTransformer, + useSanitizerTransformer, } from '../../transformers'; const MOBILE_MEDIA_QUERY = 'screen and (max-width: 76.1875em)'; @@ -77,7 +77,7 @@ export const useTechDocsReaderDom = ( const sidebar = useSidebar(); const theme = useTheme(); const isMobileMedia = useMediaQuery(MOBILE_MEDIA_QUERY); - const htmlTransformer = useHtmlTransformer(); + const sanitizerTransformer = useSanitizerTransformer(); const techdocsStorageApi = useApi(techdocsStorageApiRef); const scmIntegrationsApi = useApi(scmIntegrationsApiRef); @@ -146,7 +146,7 @@ export const useTechDocsReaderDom = ( const preRender = useCallback( (rawContent: string, contentPath: string) => transformer(rawContent, [ - htmlTransformer, + sanitizerTransformer, addBaseUrl({ techdocsStorageApi, entityId: entityRef, @@ -698,7 +698,7 @@ export const useTechDocsReaderDom = ( sidebar, scmIntegrationsApi, techdocsStorageApi, - htmlTransformer, + sanitizerTransformer, ], ); diff --git a/plugins/techdocs/src/reader/transformers/html/hooks/hooks.ts b/plugins/techdocs/src/reader/transformers/html/hooks/hooks.ts deleted file mode 100644 index a4356db2e9..0000000000 --- a/plugins/techdocs/src/reader/transformers/html/hooks/hooks.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2022 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -export { removeUnsafeLinks } from './links'; -export { removeUnsafeIframes } from './iframes'; diff --git a/plugins/techdocs/src/reader/transformers/html/hooks/index.ts b/plugins/techdocs/src/reader/transformers/html/hooks/index.ts index 445136beee..a4356db2e9 100644 --- a/plugins/techdocs/src/reader/transformers/html/hooks/index.ts +++ b/plugins/techdocs/src/reader/transformers/html/hooks/index.ts @@ -14,4 +14,5 @@ * limitations under the License. */ -export * from './hooks'; +export { removeUnsafeLinks } from './links'; +export { removeUnsafeIframes } from './iframes'; diff --git a/plugins/techdocs/src/reader/transformers/html/index.ts b/plugins/techdocs/src/reader/transformers/html/index.ts index ef2708dc8e..4b8bd06798 100644 --- a/plugins/techdocs/src/reader/transformers/html/index.ts +++ b/plugins/techdocs/src/reader/transformers/html/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { useHtmlTransformer } from './transformer'; +export { useSanitizerTransformer } from './transformer'; diff --git a/plugins/techdocs/src/reader/transformers/html/transformer.test.tsx b/plugins/techdocs/src/reader/transformers/html/transformer.test.tsx index ab64eccaa4..dc4eeb85be 100644 --- a/plugins/techdocs/src/reader/transformers/html/transformer.test.tsx +++ b/plugins/techdocs/src/reader/transformers/html/transformer.test.tsx @@ -21,7 +21,7 @@ import { ConfigReader } from '@backstage/core-app-api'; import { ConfigApi, configApiRef } from '@backstage/core-plugin-api'; import { TestApiProvider } from '@backstage/test-utils'; -import { useHtmlTransformer } from './transformer'; +import { useSanitizerTransformer } from './transformer'; const configApiMock: ConfigApi = new ConfigReader({ techdocs: { @@ -39,7 +39,7 @@ const wrapper: FC = ({ children }) => ( describe('Transformers > Html', () => { it('should return a function that removes unsafe links from a given dom element', async () => { - const { result } = renderHook(() => useHtmlTransformer(), { wrapper }); + const { result } = renderHook(() => useSanitizerTransformer(), { wrapper }); const dirtyDom = document.createElement('html'); dirtyDom.innerHTML = ` @@ -62,7 +62,7 @@ describe('Transformers > Html', () => { }); it('should return a function that removes unsafe iframes from a given dom element', async () => { - const { result } = renderHook(() => useHtmlTransformer(), { wrapper }); + const { result } = renderHook(() => useSanitizerTransformer(), { wrapper }); const dirtyDom = document.createElement('html'); dirtyDom.innerHTML = ` diff --git a/plugins/techdocs/src/reader/transformers/html/transformer.ts b/plugins/techdocs/src/reader/transformers/html/transformer.ts index 0c28d3cc49..a197a2d4b7 100644 --- a/plugins/techdocs/src/reader/transformers/html/transformer.ts +++ b/plugins/techdocs/src/reader/transformers/html/transformer.ts @@ -19,7 +19,7 @@ import { useMemo, useCallback } from 'react'; import { useApi, configApiRef } from '@backstage/core-plugin-api'; -import { Transformer } from '..'; +import { Transformer } from '../transformer'; import { removeUnsafeLinks, removeUnsafeIframes } from './hooks'; /** @@ -36,7 +36,7 @@ const useSanitizerConfig = () => { /** * Returns a transformer that sanitizes the dom's internal html. */ -export const useHtmlTransformer = (): Transformer => { +export const useSanitizerTransformer = (): Transformer => { const config = useSanitizerConfig(); return useCallback( From c63b34fe859f9a98655bd63e5aa98c871d7b6620 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 15:36:02 +0200 Subject: [PATCH 065/149] feat(techdocs): define style rule options Signed-off-by: Camila Belo --- .../reader/transformers/styles/rules/types.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/styles/rules/types.ts diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/types.ts b/plugins/techdocs/src/reader/transformers/styles/rules/types.ts new file mode 100644 index 0000000000..6419ef8e39 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/styles/rules/types.ts @@ -0,0 +1,39 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BackstageTheme } from '@backstage/theme'; + +/** + * A Backstage sidebar object that contains properties such as its pin state. + */ +type BackstageSidebar = { + /** Tracks whether the user pinned the sidebar or not. */ + isPinned: boolean; +}; + +/** + * A dependencies object injected into rules by the style processor. + */ +export type RuleOptions = { + /** + * A Backstage theme object that contains the application's design tokens. + */ + theme: BackstageTheme; + /** + * A Backstage sidebar, see {@link BackstageSidebar} for more details. + */ + sidebar: BackstageSidebar; +}; From 97a7e8bb0f7c66c44613b6b8fb7489ffd4a933e2 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 15:37:29 +0200 Subject: [PATCH 066/149] feat(techdocs): extract variables rules Signed-off-by: Camila Belo --- .../transformers/styles/rules/variables.ts | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/styles/rules/variables.ts diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts b/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts new file mode 100644 index 0000000000..908cb39faf --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/styles/rules/variables.ts @@ -0,0 +1,168 @@ +/* + * 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 { RuleOptions } from './types'; +import { alpha, lighten } from '@material-ui/core'; + +export default ({ theme }: RuleOptions) => ` +/*================== Variables ==================*/ +/* + As the MkDocs output is rendered in shadow DOM, the CSS variable definitions on the root selector are not applied. Instead, they have to be applied on :host. + As there is no way to transform the served main*.css yet (for example in the backend), we have to copy from main*.css and modify them. +*/ + +:host { + /* FONT */ + --md-default-fg-color: ${theme.palette.text.primary}; + --md-default-fg-color--light: ${theme.palette.text.secondary}; + --md-default-fg-color--lighter: ${lighten(theme.palette.text.secondary, 0.7)}; + --md-default-fg-color--lightest: ${lighten( + theme.palette.text.secondary, + 0.3, + )}; + + /* BACKGROUND */ + --md-default-bg-color:${theme.palette.background.default}; + --md-default-bg-color--light: ${theme.palette.background.paper}; + --md-default-bg-color--lighter: ${lighten( + theme.palette.background.paper, + 0.7, + )}; + --md-default-bg-color--lightest: ${lighten( + theme.palette.background.paper, + 0.3, + )}; + + /* PRIMARY */ + --md-primary-fg-color: ${theme.palette.primary.main}; + --md-primary-fg-color--light: ${theme.palette.primary.light}; + --md-primary-fg-color--dark: ${theme.palette.primary.dark}; + --md-primary-bg-color: ${theme.palette.primary.contrastText}; + --md-primary-bg-color--light: ${lighten( + theme.palette.primary.contrastText, + 0.7, + )}; + + /* ACCENT */ + --md-accent-fg-color: var(--md-primary-fg-color); + + /* SHADOW */ + --md-shadow-z1: ${theme.shadows[1]}; + --md-shadow-z2: ${theme.shadows[2]}; + --md-shadow-z3: ${theme.shadows[3]}; + + /* EXTENSIONS */ + --md-admonition-fg-color: var(--md-default-fg-color); + --md-admonition-bg-color: var(--md-default-bg-color); + /* Admonitions and others are using SVG masks to define icons. These masks are defined as CSS variables. */ + --md-admonition-icon--note: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--abstract: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--info: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--tip: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--success: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--question: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--warning: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--failure: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--danger: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--bug: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--example: url('data:image/svg+xml;charset=utf-8,'); + --md-admonition-icon--quote: url('data:image/svg+xml;charset=utf-8,'); + --md-footnotes-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-details-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-tasklist-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-tasklist-icon--checked: url('data:image/svg+xml;charset=utf-8,'); + --md-nav-icon--prev: url('data:image/svg+xml;charset=utf-8,'); + --md-nav-icon--next: url('data:image/svg+xml;charset=utf-8,'); + --md-toc-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-clipboard-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-search-result-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-source-forks-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-source-repositories-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-source-stars-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-source-version-icon: url('data:image/svg+xml;charset=utf-8,'); + --md-version-icon: url('data:image/svg+xml;charset=utf-8,'); +} + +:host > * { + /* CODE */ + --md-code-fg-color: ${theme.palette.text.primary}; + --md-code-bg-color: ${theme.palette.background.paper}; + --md-code-hl-color: ${alpha(theme.palette.warning.main, 0.5)}; + --md-code-hl-keyword-color: ${ + theme.palette.type === 'dark' + ? theme.palette.primary.light + : theme.palette.primary.dark + }; + --md-code-hl-function-color: ${ + theme.palette.type === 'dark' + ? theme.palette.secondary.light + : theme.palette.secondary.dark + }; + --md-code-hl-string-color: ${ + theme.palette.type === 'dark' + ? theme.palette.success.light + : theme.palette.success.dark + }; + --md-code-hl-number-color: ${ + theme.palette.type === 'dark' + ? theme.palette.error.light + : theme.palette.error.dark + }; + --md-code-hl-constant-color: var(--md-code-hl-function-color); + --md-code-hl-special-color: var(--md-code-hl-function-color); + --md-code-hl-name-color: var(--md-code-fg-color); + --md-code-hl-comment-color: var(--md-default-fg-color--light); + --md-code-hl-generic-color: var(--md-default-fg-color--light); + --md-code-hl-variable-color: var(--md-default-fg-color--light); + --md-code-hl-operator-color: var(--md-default-fg-color--light); + --md-code-hl-punctuation-color: var(--md-default-fg-color--light); + + /* TYPESET */ + --md-typeset-font-size: 1rem; + --md-typeset-color: var(--md-default-fg-color); + --md-typeset-a-color: var(--md-accent-fg-color); + --md-typeset-table-color: ${theme.palette.text.primary}; + --md-typeset-del-color: ${ + theme.palette.type === 'dark' + ? alpha(theme.palette.error.dark, 0.5) + : alpha(theme.palette.error.light, 0.5) + }; + --md-typeset-ins-color: ${ + theme.palette.type === 'dark' + ? alpha(theme.palette.success.dark, 0.5) + : alpha(theme.palette.success.light, 0.5) + }; + --md-typeset-mark-color: ${ + theme.palette.type === 'dark' + ? alpha(theme.palette.warning.dark, 0.5) + : alpha(theme.palette.warning.light, 0.5) + }; +} + +@media screen and (max-width: 76.1875em) { + :host > * { + /* TYPESET */ + --md-typeset-font-size: .9rem; + } +} + +@media screen and (max-width: 600px) { + :host > * { + /* TYPESET */ + --md-typeset-font-size: .7rem; + } +} +`; From eeba7f240c8521b148c1fd12ce2777f5b5272cf1 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 15:37:49 +0200 Subject: [PATCH 067/149] feat(techdocs): extract reset rules Signed-off-by: Camila Belo --- .../reader/transformers/styles/rules/reset.ts | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/styles/rules/reset.ts diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/reset.ts b/plugins/techdocs/src/reader/transformers/styles/rules/reset.ts new file mode 100644 index 0000000000..16868460c3 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/styles/rules/reset.ts @@ -0,0 +1,29 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { RuleOptions } from './types'; + +export default ({ theme }: RuleOptions) => ` +/*================== Reset ==================*/ + +body { + --md-text-color: var(--md-default-fg-color); + --md-text-link-color: var(--md-accent-fg-color); + --md-text-font-family: ${theme.typography.fontFamily}; + font-family: var(--md-text-font-family); + background-color: unset; +} +`; From c8df6bfd7e28664cf34bed322935b3b3c3a74f14 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 15:38:24 +0200 Subject: [PATCH 068/149] feat(techdocs): extract layout rules Signed-off-by: Camila Belo --- .../transformers/styles/rules/layout.ts | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/styles/rules/layout.ts diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/layout.ts b/plugins/techdocs/src/reader/transformers/styles/rules/layout.ts new file mode 100644 index 0000000000..2488504dd9 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/styles/rules/layout.ts @@ -0,0 +1,210 @@ +/* + * 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 { RuleOptions } from './types'; + +export default ({ theme, sidebar }: RuleOptions) => ` +/*================== Layout ==================*/ + +.md-grid { + max-width: 100%; + margin: 0; +} + +.md-nav { + font-size: calc(var(--md-typeset-font-size) * 0.9); +} +.md-nav__link { + display: flex; + align-items: center; + justify-content: space-between; +} +.md-nav__icon { + height: 20px !important; + width: 20px !important; + margin-left:${theme.spacing(1)}px; +} +.md-nav__icon svg { + margin: 0; + width: 20px !important; + height: 20px !important; +} +.md-nav__icon:after { + width: 20px !important; + height: 20px !important; +} + +.md-main__inner { + margin-top: 0; +} + +.md-sidebar { + bottom: 75px; + position: fixed; + width: 16rem; + overflow-y: auto; + overflow-x: hidden; + scrollbar-color: rgb(193, 193, 193) #eee; + scrollbar-width: thin; +} +.md-sidebar .md-sidebar__scrollwrap { + width: calc(16rem - 10px); +} +.md-sidebar--secondary { + right: ${theme.spacing(3)}px; +} +.md-sidebar::-webkit-scrollbar { + width: 5px; +} +.md-sidebar::-webkit-scrollbar-button { + width: 5px; + height: 5px; +} +.md-sidebar::-webkit-scrollbar-track { + background: #eee; + border: 1 px solid rgb(250, 250, 250); + box-shadow: 0px 0px 3px #dfdfdf inset; + border-radius: 3px; +} +.md-sidebar::-webkit-scrollbar-thumb { + width: 5px; + background: rgb(193, 193, 193); + border: transparent; + border-radius: 3px; +} +.md-sidebar::-webkit-scrollbar-thumb:hover { + background: rgb(125, 125, 125); +} + +.md-content { + max-width: calc(100% - 16rem * 2); + margin-left: 16rem; + margin-bottom: 50px; +} + +.md-footer { + position: fixed; + bottom: 0px; +} +.md-footer__title { + background-color: unset; +} +.md-footer-nav__link { + width: 16rem; +} + +.md-dialog { + background-color: unset; +} + +@media screen and (min-width: 76.25em) { + .md-sidebar { + height: auto; + } +} + +@media screen and (max-width: 76.1875em) { + .md-nav { + transition: none !important; + background-color: var(--md-default-bg-color) + } + .md-nav--primary .md-nav__title { + cursor: auto; + color: var(--md-default-fg-color); + font-weight: 700; + white-space: normal; + line-height: 1rem; + height: auto; + display: flex; + flex-flow: column; + row-gap: 1.6rem; + padding: 1.2rem .8rem .8rem; + background-color: var(--md-default-bg-color); + } + .md-nav--primary .md-nav__title~.md-nav__list { + box-shadow: none; + } + .md-nav--primary .md-nav__title ~ .md-nav__list > :first-child { + border-top: none; + } + .md-nav--primary .md-nav__title .md-nav__button { + display: none; + } + .md-nav--primary .md-nav__title .md-nav__icon { + color: var(--md-default-fg-color); + position: static; + height: auto; + margin: 0 0 0 -0.2rem; + } + .md-nav--primary > .md-nav__title [for="none"] { + padding-top: 0; + } + .md-nav--primary .md-nav__item { + border-top: none; + } + .md-nav--primary :is(.md-nav__title,.md-nav__item) { + font-size : var(--md-typeset-font-size); + } + .md-nav .md-source { + display: none; + } + + .md-sidebar { + height: 100%; + } + .md-sidebar--primary { + width: 12.1rem !important; + z-index: 200; + left: ${ + sidebar.isPinned ? 'calc(-12.1rem + 242px)' : 'calc(-12.1rem + 72px)' + } !important; + } + .md-sidebar--secondary:not([hidden]) { + display: none; + } + + .md-content { + max-width: 100%; + margin-left: 0; + } + + .md-header__button { + margin: 0.4rem 0; + margin-left: 0.4rem; + padding: 0; + } + + .md-overlay { + left: 0; + } + + .md-footer { + position: static; + padding-left: 0; + } + .md-footer-nav__link { + /* footer links begin to overlap at small sizes without setting width */ + width: 50%; + } +} + +@media screen and (max-width: 600px) { + .md-sidebar--primary { + left: -12.1rem !important; + width: 12.1rem; + } +} +`; From 432ed4ea1d02755a5d9c273882a8728ea94b9527 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 15:38:47 +0200 Subject: [PATCH 069/149] feat(techdocs): extract typeset rules Signed-off-by: Camila Belo --- .../transformers/styles/rules/typeset.ts | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts b/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts new file mode 100644 index 0000000000..e24b61da83 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/styles/rules/typeset.ts @@ -0,0 +1,109 @@ +/* + * 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 { RuleOptions } from './types'; + +type TypographyHeadings = Pick< + RuleOptions['theme']['typography'], + 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' +>; + +type TypographyHeadingsKeys = keyof TypographyHeadings; + +const headings: TypographyHeadingsKeys[] = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']; + +export default ({ theme }: RuleOptions) => ` +/*================== Typeset ==================*/ + +.md-typeset { + font-size: var(--md-typeset-font-size); +} + +${headings.reduce((style, heading) => { + const styles = theme.typography[heading]; + const { lineHeight, fontFamily, fontWeight, fontSize } = styles; + const calculate = (value: typeof fontSize) => { + let factor: number | string = 1; + if (typeof value === 'number') { + // 60% of the size defined because it is too big + factor = (value / 16) * 0.6; + } + if (typeof value === 'string') { + factor = value.replace('rem', ''); + } + return `calc(${factor} * var(--md-typeset-font-size))`; + }; + return style.concat(` + .md-typeset ${heading} { + color: var(--md-default-fg-color); + line-height: ${lineHeight}; + font-family: ${fontFamily}; + font-weight: ${fontWeight}; + font-size: ${calculate(fontSize)}; + } + `); +}, '')} + +.md-typeset .md-content__button { + color: var(--md-default-fg-color); +} + +.md-typeset hr { + border-bottom: 0.05rem dotted ${theme.palette.divider}; +} + +.md-typeset details { + font-size: var(--md-typeset-font-size) !important; +} +.md-typeset details summary { + padding-left: 2.5rem !important; +} +.md-typeset details summary:before, +.md-typeset details summary:after { + top: 50% !important; + width: 20px !important; + height: 20px !important; + transform: rotate(0deg) translateY(-50%) !important; +} +.md-typeset details[open] > summary:after { + transform: rotate(90deg) translateX(-50%) !important; +} + +.md-typeset blockquote { + color: var(--md-default-fg-color--light); + border-left: 0.2rem solid var(--md-default-fg-color--light); +} + +.md-typeset table:not([class]) { + font-size: var(--md-typeset-font-size); + border: 1px solid var(--md-default-fg-color); + border-bottom: none; + border-collapse: collapse; +} +.md-typeset table:not([class]) th { + font-weight: bold; +} +.md-typeset table:not([class]) td, .md-typeset table:not([class]) th { + border-bottom: 1px solid var(--md-default-fg-color); +} + +.md-typeset pre > code::-webkit-scrollbar-thumb { + background-color: hsla(0, 0%, 0%, 0.32); +} +.md-typeset pre > code::-webkit-scrollbar-thumb:hover { + background-color: hsla(0, 0%, 0%, 0.87); +} +`; From 49fe071b65eb45fb3d4deb215cfaadc4ad6b34c0 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 15:39:12 +0200 Subject: [PATCH 070/149] feat(techdocs): extract animations rules Signed-off-by: Camila Belo --- .../transformers/styles/rules/animations.ts | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/styles/rules/animations.ts diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/animations.ts b/plugins/techdocs/src/reader/transformers/styles/rules/animations.ts new file mode 100644 index 0000000000..0c50fb0633 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/styles/rules/animations.ts @@ -0,0 +1,26 @@ +/* + * 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 default () => ` +/*================== Animations ==================*/ +/* + Disable CSS animations on link colors as they lead to issues in dark mode. + The dark mode color theme is applied later and theirfore there is always an animation from light to dark mode when navigation between pages. +*/ +.md-dialog, .md-nav__link, .md-footer__link, .md-typeset a, .md-typeset a::before, .md-typeset .headerlink { + transition: none; +} +`; From 906568604ec9125072a4f71598ed52e7e0d0c5c6 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 15:39:43 +0200 Subject: [PATCH 071/149] feat(techdocs): extract extensions rules Signed-off-by: Camila Belo --- .../transformers/styles/rules/extensions.ts | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/styles/rules/extensions.ts diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/extensions.ts b/plugins/techdocs/src/reader/transformers/styles/rules/extensions.ts new file mode 100644 index 0000000000..403858339e --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/styles/rules/extensions.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 { RuleOptions } from './types'; + +export default ({ theme }: RuleOptions) => ` +/*================== Extensions ==================*/ + +/* HIGHLIGHT */ +.highlight .md-clipboard:after { + content: unset; +} + +.highlight .nx { + color: ${theme.palette.type === 'dark' ? '#ff53a3' : '#ec407a'}; +} + +/* CODE HILITE */ +.codehilite .gd { + background-color: ${ + theme.palette.type === 'dark' ? 'rgba(248,81,73,0.65)' : '#fdd' + }; +} + +.codehilite .gi { + background-color: ${ + theme.palette.type === 'dark' ? 'rgba(46,160,67,0.65)' : '#dfd' + }; +} + +/* TABBED */ +.tabbed-set>input:nth-child(1):checked~.tabbed-labels>:nth-child(1), +.tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2), +.tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3), +.tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4), +.tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5), +.tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6), +.tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7), +.tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8), +.tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9), +.tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10), +.tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11), +.tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12), +.tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13), +.tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14), +.tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15), +.tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16), +.tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17), +.tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18), +.tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19), +.tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20) { + color: var(--md-accent-fg-color); + border-color: var(--md-accent-fg-color); +} + +/* TASK-LIST */ +.task-list-control .task-list-indicator::before { + background-color: ${theme.palette.action.disabledBackground}; +} +.task-list-control [type="checkbox"]:checked + .task-list-indicator:before { + background-color: ${theme.palette.success.main}; +} + +/* ADMONITION */ +.admonition { + font-size: var(--md-typeset-font-size) !important; +} +.admonition .admonition-title { + padding-left: 2.5rem !important; +} + +.admonition .admonition-title:before { + top: 50% !important; + width: 20px !important; + height: 20px !important; + transform: translateY(-50%) !important; +} +`; From cfa8b5be752d5b94578453033a8c09913fe6aedb Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 15:40:31 +0200 Subject: [PATCH 072/149] feat(techdocs): sort style rules Signed-off-by: Camila Belo --- .../reader/transformers/styles/rules/rules.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/styles/rules/rules.ts diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/rules.ts b/plugins/techdocs/src/reader/transformers/styles/rules/rules.ts new file mode 100644 index 0000000000..917793733e --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/styles/rules/rules.ts @@ -0,0 +1,38 @@ +/* + * 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 { default as variables } from './variables'; +import { default as reset } from './reset'; +import { default as layout } from './layout'; +import { default as typeset } from './typeset'; +import { default as animations } from './animations'; +import { default as extensions } from './extensions'; + +/** + * A list of style rules that will be applied to an element in the order they were added. + * + * @remarks + * The order of items is important, which means that a rule can override any other rule previously added to the list, + * i.e. the rules will be applied from the first added to the last added. + */ +export const rules = [ + variables, + reset, + layout, + typeset, + animations, + extensions, +]; From 0d969cc4dfe00a99ac3035402aa7f86923ce50fa Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 15:40:56 +0200 Subject: [PATCH 073/149] feat(techdocs): export style rules Signed-off-by: Camila Belo --- .../reader/transformers/styles/rules/index.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/styles/rules/index.ts diff --git a/plugins/techdocs/src/reader/transformers/styles/rules/index.ts b/plugins/techdocs/src/reader/transformers/styles/rules/index.ts new file mode 100644 index 0000000000..b41da508f0 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/styles/rules/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 './rules'; From b20cdd0efa3badccdd786f752b6f3a409c08a660 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 15:41:37 +0200 Subject: [PATCH 074/149] feat(techdocs): create styles transformer Signed-off-by: Camila Belo --- .../reader/transformers/styles/transformer.ts | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/styles/transformer.ts diff --git a/plugins/techdocs/src/reader/transformers/styles/transformer.ts b/plugins/techdocs/src/reader/transformers/styles/transformer.ts new file mode 100644 index 0000000000..f16ee951c6 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/styles/transformer.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 { useCallback, useContext, useMemo } from 'react'; + +import { useTheme } from '@material-ui/core'; + +import { SidebarPinStateContext } from '@backstage/core-components'; +import { BackstageTheme } from '@backstage/theme'; + +import { Transformer } from '..'; +import { rules } from './rules'; + +/** + * Sidebar pinned state to be used in computing style injections. + */ +const useSidebar = () => useContext(SidebarPinStateContext); + +/** + * Process all rules and concatenate their definitions into a single style. + * @returns a string containing all processed style definitions. + */ +const useRuleStyles = () => { + const sidebar = useSidebar(); + const theme = useTheme(); + + return useMemo(() => { + const options = { theme, sidebar }; + return rules.reduce((styles, rule) => styles + rule(options), ''); + }, [theme, sidebar]); +}; + +/** + * Returns a transformer that inserts all style rules into the given element's head tag. + */ +export const useStylesTransformer = (): Transformer => { + const styles = useRuleStyles(); + + return useCallback( + (dom: Element) => { + dom + .getElementsByTagName('head')[0] + .insertAdjacentHTML('beforeend', ``); + return dom; + }, + [styles], + ); +}; From c7e4026c948fd54a4902c8e114c408fa2a3bee74 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 15:42:24 +0200 Subject: [PATCH 075/149] test(techdocs): cover styles transformer Signed-off-by: Camila Belo --- .../transformers/styles/transformer.test.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/styles/transformer.test.ts diff --git a/plugins/techdocs/src/reader/transformers/styles/transformer.test.ts b/plugins/techdocs/src/reader/transformers/styles/transformer.test.ts new file mode 100644 index 0000000000..84ba16d6e6 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/styles/transformer.test.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. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { useStylesTransformer } from './transformer'; + +describe('Transformers > Styles', () => { + it('should return a function that injects all styles into a given dom element', () => { + const { result } = renderHook(() => useStylesTransformer()); + + const dom = document.createElement('html'); + dom.innerHTML = ''; + result.current(dom); // calling styles transformer + + const style = dom.querySelector('head > style'); + expect(style).toHaveTextContent( + '/*================== Variables ==================*/', + ); + expect(style).toHaveTextContent( + '/*================== Reset ==================*/', + ); + expect(style).toHaveTextContent( + '/*================== Layout ==================*/', + ); + expect(style).toHaveTextContent( + '/*================== Typeset ==================*/', + ); + expect(style).toHaveTextContent( + '/*================== Animations ==================*/', + ); + expect(style).toHaveTextContent( + '/*================== Extensions ==================*/', + ); + }); +}); From 4e42ccad35ad642d16f876a02192a9734c45dc2f Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 15:42:47 +0200 Subject: [PATCH 076/149] feat(techdocs): export styles transformer Signed-off-by: Camila Belo --- .../techdocs/src/reader/transformers/index.ts | 1 + .../src/reader/transformers/styles/index.ts | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 plugins/techdocs/src/reader/transformers/styles/index.ts diff --git a/plugins/techdocs/src/reader/transformers/index.ts b/plugins/techdocs/src/reader/transformers/index.ts index bc72f2d78f..56846a51cd 100644 --- a/plugins/techdocs/src/reader/transformers/index.ts +++ b/plugins/techdocs/src/reader/transformers/index.ts @@ -15,6 +15,7 @@ */ export * from './html'; +export * from './styles'; export * from './addBaseUrl'; export * from './addGitFeedbackLink'; export * from './addSidebarToggle'; diff --git a/plugins/techdocs/src/reader/transformers/styles/index.ts b/plugins/techdocs/src/reader/transformers/styles/index.ts new file mode 100644 index 0000000000..50d5a6a368 --- /dev/null +++ b/plugins/techdocs/src/reader/transformers/styles/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 { useStylesTransformer } from './transformer'; From 307581d8b60bf53c7aa7bac2baca9b29495b50cb Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 15:45:18 +0200 Subject: [PATCH 077/149] feat(techdocs): use styles transformer Signed-off-by: Camila Belo --- .../TechDocsReaderPageContent/dom.tsx | 561 +----------------- 1 file changed, 6 insertions(+), 555 deletions(-) diff --git a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx index 6a825a4788..9f561bfcd3 100644 --- a/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx +++ b/plugins/techdocs/src/reader/components/TechDocsReaderPageContent/dom.tsx @@ -14,16 +14,14 @@ * limitations under the License. */ -import { useContext, useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import { Theme, useTheme, useMediaQuery } from '@material-ui/core'; -import { lighten, alpha } from '@material-ui/core/styles'; +import { useTheme, useMediaQuery } from '@material-ui/core'; import { BackstageTheme } from '@backstage/theme'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { useApi } from '@backstage/core-plugin-api'; -import { SidebarPinStateContext } from '@backstage/core-components'; import { scmIntegrationsApiRef } from '@backstage/integration-react'; import { @@ -38,7 +36,6 @@ import { addGitFeedbackLink, addLinkClickListener, addSidebarToggle, - injectCss, onCssReady, removeMkdocsHeader, rewriteDocLinks, @@ -47,24 +44,11 @@ import { transform as transformer, copyToClipboard, useSanitizerTransformer, + useStylesTransformer, } from '../../transformers'; const MOBILE_MEDIA_QUERY = 'screen and (max-width: 76.1875em)'; -type TypographyHeadings = Pick< - Theme['typography'], - 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' ->; - -type TypographyHeadingsKeys = keyof TypographyHeadings; - -const headings: TypographyHeadingsKeys[] = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']; - -/** - * Sidebar pinned status to be used in computing CSS style injections - */ -const useSidebar = () => useContext(SidebarPinStateContext); - /** * Hook that encapsulates the behavior of getting raw HTML and applying * transforms to it in order to make it function at a basic level in the @@ -74,10 +58,10 @@ export const useTechDocsReaderDom = ( entityRef: CompoundEntityRef, ): Element | null => { const navigate = useNavigate(); - const sidebar = useSidebar(); const theme = useTheme(); const isMobileMedia = useMediaQuery(MOBILE_MEDIA_QUERY); const sanitizerTransformer = useSanitizerTransformer(); + const stylesTransformer = useStylesTransformer(); const techdocsStorageApi = useApi(techdocsStorageApiRef); const scmIntegrationsApi = useApi(scmIntegrationsApiRef); @@ -157,548 +141,15 @@ export const useTechDocsReaderDom = ( removeMkdocsHeader(), simplifyMkdocsFooter(), addGitFeedbackLink(scmIntegrationsApi), - injectCss({ - // Variables - css: ` - /* - As the MkDocs output is rendered in shadow DOM, the CSS variable definitions on the root selector are not applied. Instead, they have to be applied on :host. - As there is no way to transform the served main*.css yet (for example in the backend), we have to copy from main*.css and modify them. - */ - :host { - /* FONT */ - --md-default-fg-color: ${theme.palette.text.primary}; - --md-default-fg-color--light: ${theme.palette.text.secondary}; - --md-default-fg-color--lighter: ${lighten( - theme.palette.text.secondary, - 0.7, - )}; - --md-default-fg-color--lightest: ${lighten( - theme.palette.text.secondary, - 0.3, - )}; - - /* BACKGROUND */ - --md-default-bg-color:${theme.palette.background.default}; - --md-default-bg-color--light: ${theme.palette.background.paper}; - --md-default-bg-color--lighter: ${lighten( - theme.palette.background.paper, - 0.7, - )}; - --md-default-bg-color--lightest: ${lighten( - theme.palette.background.paper, - 0.3, - )}; - - /* PRIMARY */ - --md-primary-fg-color: ${theme.palette.primary.main}; - --md-primary-fg-color--light: ${theme.palette.primary.light}; - --md-primary-fg-color--dark: ${theme.palette.primary.dark}; - --md-primary-bg-color: ${theme.palette.primary.contrastText}; - --md-primary-bg-color--light: ${lighten( - theme.palette.primary.contrastText, - 0.7, - )}; - - /* ACCENT */ - --md-accent-fg-color: var(--md-primary-fg-color); - - /* SHADOW */ - --md-shadow-z1: ${theme.shadows[1]}; - --md-shadow-z2: ${theme.shadows[2]}; - --md-shadow-z3: ${theme.shadows[3]}; - - /* EXTENSIONS */ - --md-admonition-fg-color: var(--md-default-fg-color); - --md-admonition-bg-color: var(--md-default-bg-color); - /* Admonitions and others are using SVG masks to define icons. These masks are defined as CSS variables. */ - --md-admonition-icon--note: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--abstract: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--info: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--tip: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--success: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--question: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--warning: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--failure: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--danger: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--bug: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--example: url('data:image/svg+xml;charset=utf-8,'); - --md-admonition-icon--quote: url('data:image/svg+xml;charset=utf-8,'); - --md-footnotes-icon: url('data:image/svg+xml;charset=utf-8,'); - --md-details-icon: url('data:image/svg+xml;charset=utf-8,'); - --md-tasklist-icon: url('data:image/svg+xml;charset=utf-8,'); - --md-tasklist-icon--checked: url('data:image/svg+xml;charset=utf-8,'); - --md-nav-icon--prev: url('data:image/svg+xml;charset=utf-8,'); - --md-nav-icon--next: url('data:image/svg+xml;charset=utf-8,'); - --md-toc-icon: url('data:image/svg+xml;charset=utf-8,'); - --md-clipboard-icon: url('data:image/svg+xml;charset=utf-8,'); - --md-search-result-icon: url('data:image/svg+xml;charset=utf-8,'); - --md-source-forks-icon: url('data:image/svg+xml;charset=utf-8,'); - --md-source-repositories-icon: url('data:image/svg+xml;charset=utf-8,'); - --md-source-stars-icon: url('data:image/svg+xml;charset=utf-8,'); - --md-source-version-icon: url('data:image/svg+xml;charset=utf-8,'); - --md-version-icon: url('data:image/svg+xml;charset=utf-8,'); - } - - :host > * { - /* CODE */ - --md-code-fg-color: ${theme.palette.text.primary}; - --md-code-bg-color: ${theme.palette.background.paper}; - --md-code-hl-color: ${alpha(theme.palette.warning.main, 0.5)}; - --md-code-hl-keyword-color: ${ - theme.palette.type === 'dark' - ? theme.palette.primary.light - : theme.palette.primary.dark - }; - --md-code-hl-function-color: ${ - theme.palette.type === 'dark' - ? theme.palette.secondary.light - : theme.palette.secondary.dark - }; - --md-code-hl-string-color: ${ - theme.palette.type === 'dark' - ? theme.palette.success.light - : theme.palette.success.dark - }; - --md-code-hl-number-color: ${ - theme.palette.type === 'dark' - ? theme.palette.error.light - : theme.palette.error.dark - }; - --md-code-hl-constant-color: var(--md-code-hl-function-color); - --md-code-hl-special-color: var(--md-code-hl-function-color); - --md-code-hl-name-color: var(--md-code-fg-color); - --md-code-hl-comment-color: var(--md-default-fg-color--light); - --md-code-hl-generic-color: var(--md-default-fg-color--light); - --md-code-hl-variable-color: var(--md-default-fg-color--light); - --md-code-hl-operator-color: var(--md-default-fg-color--light); - --md-code-hl-punctuation-color: var(--md-default-fg-color--light); - - /* TYPESET */ - --md-typeset-font-size: 1rem; - --md-typeset-color: var(--md-default-fg-color); - --md-typeset-a-color: var(--md-accent-fg-color); - --md-typeset-table-color: ${theme.palette.text.primary}; - --md-typeset-del-color: ${ - theme.palette.type === 'dark' - ? alpha(theme.palette.error.dark, 0.5) - : alpha(theme.palette.error.light, 0.5) - }; - --md-typeset-ins-color: ${ - theme.palette.type === 'dark' - ? alpha(theme.palette.success.dark, 0.5) - : alpha(theme.palette.success.light, 0.5) - }; - --md-typeset-mark-color: ${ - theme.palette.type === 'dark' - ? alpha(theme.palette.warning.dark, 0.5) - : alpha(theme.palette.warning.light, 0.5) - }; - } - - @media screen and (max-width: 76.1875em) { - :host > * { - /* TYPESET */ - --md-typeset-font-size: .9rem; - } - } - - @media screen and (max-width: 600px) { - :host > * { - /* TYPESET */ - --md-typeset-font-size: .7rem; - } - } - `, - }), - injectCss({ - // Reset - css: ` - body { - --md-text-color: var(--md-default-fg-color); - --md-text-link-color: var(--md-accent-fg-color); - --md-text-font-family: ${theme.typography.fontFamily}; - font-family: var(--md-text-font-family); - background-color: unset; - } - `, - }), - injectCss({ - // Layout - css: ` - .md-grid { - max-width: 100%; - margin: 0; - } - - .md-nav { - font-size: calc(var(--md-typeset-font-size) * 0.9); - } - .md-nav__link { - display: flex; - align-items: center; - justify-content: space-between; - } - .md-nav__icon { - height: 20px !important; - width: 20px !important; - margin-left:${theme.spacing(1)}px; - } - .md-nav__icon svg { - margin: 0; - width: 20px !important; - height: 20px !important; - } - .md-nav__icon:after { - width: 20px !important; - height: 20px !important; - } - - .md-main__inner { - margin-top: 0; - } - - .md-sidebar { - bottom: 75px; - position: fixed; - width: 16rem; - overflow-y: auto; - overflow-x: hidden; - scrollbar-color: rgb(193, 193, 193) #eee; - scrollbar-width: thin; - } - .md-sidebar .md-sidebar__scrollwrap { - width: calc(16rem - 10px); - } - .md-sidebar--secondary { - right: ${theme.spacing(3)}px; - } - .md-sidebar::-webkit-scrollbar { - width: 5px; - } - .md-sidebar::-webkit-scrollbar-button { - width: 5px; - height: 5px; - } - .md-sidebar::-webkit-scrollbar-track { - background: #eee; - border: 1 px solid rgb(250, 250, 250); - box-shadow: 0px 0px 3px #dfdfdf inset; - border-radius: 3px; - } - .md-sidebar::-webkit-scrollbar-thumb { - width: 5px; - background: rgb(193, 193, 193); - border: transparent; - border-radius: 3px; - } - .md-sidebar::-webkit-scrollbar-thumb:hover { - background: rgb(125, 125, 125); - } - - .md-content { - max-width: calc(100% - 16rem * 2); - margin-left: 16rem; - margin-bottom: 50px; - } - - .md-footer { - position: fixed; - bottom: 0px; - } - .md-footer__title { - background-color: unset; - } - .md-footer-nav__link { - width: 16rem; - } - - .md-dialog { - background-color: unset; - } - - @media screen and (min-width: 76.25em) { - .md-sidebar { - height: auto; - } - } - - @media screen and (max-width: 76.1875em) { - .md-nav { - transition: none !important; - background-color: var(--md-default-bg-color) - } - .md-nav--primary .md-nav__title { - cursor: auto; - color: var(--md-default-fg-color); - font-weight: 700; - white-space: normal; - line-height: 1rem; - height: auto; - display: flex; - flex-flow: column; - row-gap: 1.6rem; - padding: 1.2rem .8rem .8rem; - background-color: var(--md-default-bg-color); - } - .md-nav--primary .md-nav__title~.md-nav__list { - box-shadow: none; - } - .md-nav--primary .md-nav__title ~ .md-nav__list > :first-child { - border-top: none; - } - .md-nav--primary .md-nav__title .md-nav__button { - display: none; - } - .md-nav--primary .md-nav__title .md-nav__icon { - color: var(--md-default-fg-color); - position: static; - height: auto; - margin: 0 0 0 -0.2rem; - } - .md-nav--primary > .md-nav__title [for="none"] { - padding-top: 0; - } - .md-nav--primary .md-nav__item { - border-top: none; - } - .md-nav--primary :is(.md-nav__title,.md-nav__item) { - font-size : var(--md-typeset-font-size); - } - .md-nav .md-source { - display: none; - } - - .md-sidebar { - height: 100%; - } - .md-sidebar--primary { - width: 12.1rem !important; - z-index: 200; - left: ${ - sidebar.isPinned - ? 'calc(-12.1rem + 242px)' - : 'calc(-12.1rem + 72px)' - } !important; - } - .md-sidebar--secondary:not([hidden]) { - display: none; - } - - .md-content { - max-width: 100%; - margin-left: 0; - } - - .md-header__button { - margin: 0.4rem 0; - margin-left: 0.4rem; - padding: 0; - } - - .md-overlay { - left: 0; - } - - .md-footer { - position: static; - padding-left: 0; - } - .md-footer-nav__link { - /* footer links begin to overlap at small sizes without setting width */ - width: 50%; - } - } - - @media screen and (max-width: 600px) { - .md-sidebar--primary { - left: -12.1rem !important; - width: 12.1rem; - } - } - `, - }), - injectCss({ - // Typeset - css: ` - .md-typeset { - font-size: var(--md-typeset-font-size); - } - - ${headings.reduce((style, heading) => { - const styles = theme.typography[heading]; - const { lineHeight, fontFamily, fontWeight, fontSize } = styles; - const calculate = (value: typeof fontSize) => { - let factor: number | string = 1; - if (typeof value === 'number') { - // 60% of the size defined because it is too big - factor = (value / 16) * 0.6; - } - if (typeof value === 'string') { - factor = value.replace('rem', ''); - } - return `calc(${factor} * var(--md-typeset-font-size))`; - }; - return style.concat(` - .md-typeset ${heading} { - color: var(--md-default-fg-color); - line-height: ${lineHeight}; - font-family: ${fontFamily}; - font-weight: ${fontWeight}; - font-size: ${calculate(fontSize)}; - } - `); - }, '')} - - .md-typeset .md-content__button { - color: var(--md-default-fg-color); - } - - .md-typeset hr { - border-bottom: 0.05rem dotted ${theme.palette.divider}; - } - - .md-typeset details { - font-size: var(--md-typeset-font-size) !important; - } - .md-typeset details summary { - padding-left: 2.5rem !important; - } - .md-typeset details summary:before, - .md-typeset details summary:after { - top: 50% !important; - width: 20px !important; - height: 20px !important; - transform: rotate(0deg) translateY(-50%) !important; - } - .md-typeset details[open] > summary:after { - transform: rotate(90deg) translateX(-50%) !important; - } - - .md-typeset blockquote { - color: var(--md-default-fg-color--light); - border-left: 0.2rem solid var(--md-default-fg-color--light); - } - - .md-typeset table:not([class]) { - font-size: var(--md-typeset-font-size); - border: 1px solid var(--md-default-fg-color); - border-bottom: none; - border-collapse: collapse; - } - .md-typeset table:not([class]) th { - font-weight: bold; - } - .md-typeset table:not([class]) td, .md-typeset table:not([class]) th { - border-bottom: 1px solid var(--md-default-fg-color); - } - - .md-typeset pre > code::-webkit-scrollbar-thumb { - background-color: hsla(0, 0%, 0%, 0.32); - } - .md-typeset pre > code::-webkit-scrollbar-thumb:hover { - background-color: hsla(0, 0%, 0%, 0.87); - } - `, - }), - injectCss({ - // Animations - css: ` - /* - Disable CSS animations on link colors as they lead to issues in dark mode. - The dark mode color theme is applied later and theirfore there is always an animation from light to dark mode when navigation between pages. - */ - .md-dialog, .md-nav__link, .md-footer__link, .md-typeset a, .md-typeset a::before, .md-typeset .headerlink { - transition: none; - } - `, - }), - injectCss({ - // Extensions - css: ` - /* HIGHLIGHT */ - .highlight .md-clipboard:after { - content: unset; - } - - .highlight .nx { - color: ${theme.palette.type === 'dark' ? '#ff53a3' : '#ec407a'}; - } - - /* CODE HILITE */ - .codehilite .gd { - background-color: ${ - theme.palette.type === 'dark' - ? 'rgba(248,81,73,0.65)' - : '#fdd' - }; - } - - .codehilite .gi { - background-color: ${ - theme.palette.type === 'dark' - ? 'rgba(46,160,67,0.65)' - : '#dfd' - }; - } - - /* TABBED */ - .tabbed-set>input:nth-child(1):checked~.tabbed-labels>:nth-child(1), - .tabbed-set>input:nth-child(2):checked~.tabbed-labels>:nth-child(2), - .tabbed-set>input:nth-child(3):checked~.tabbed-labels>:nth-child(3), - .tabbed-set>input:nth-child(4):checked~.tabbed-labels>:nth-child(4), - .tabbed-set>input:nth-child(5):checked~.tabbed-labels>:nth-child(5), - .tabbed-set>input:nth-child(6):checked~.tabbed-labels>:nth-child(6), - .tabbed-set>input:nth-child(7):checked~.tabbed-labels>:nth-child(7), - .tabbed-set>input:nth-child(8):checked~.tabbed-labels>:nth-child(8), - .tabbed-set>input:nth-child(9):checked~.tabbed-labels>:nth-child(9), - .tabbed-set>input:nth-child(10):checked~.tabbed-labels>:nth-child(10), - .tabbed-set>input:nth-child(11):checked~.tabbed-labels>:nth-child(11), - .tabbed-set>input:nth-child(12):checked~.tabbed-labels>:nth-child(12), - .tabbed-set>input:nth-child(13):checked~.tabbed-labels>:nth-child(13), - .tabbed-set>input:nth-child(14):checked~.tabbed-labels>:nth-child(14), - .tabbed-set>input:nth-child(15):checked~.tabbed-labels>:nth-child(15), - .tabbed-set>input:nth-child(16):checked~.tabbed-labels>:nth-child(16), - .tabbed-set>input:nth-child(17):checked~.tabbed-labels>:nth-child(17), - .tabbed-set>input:nth-child(18):checked~.tabbed-labels>:nth-child(18), - .tabbed-set>input:nth-child(19):checked~.tabbed-labels>:nth-child(19), - .tabbed-set>input:nth-child(20):checked~.tabbed-labels>:nth-child(20) { - color: var(--md-accent-fg-color); - border-color: var(--md-accent-fg-color); - } - - /* TASK-LIST */ - .task-list-control .task-list-indicator::before { - background-color: ${theme.palette.action.disabledBackground}; - } - .task-list-control [type="checkbox"]:checked + .task-list-indicator:before { - background-color: ${theme.palette.success.main}; - } - - /* ADMONITION */ - .admonition { - font-size: var(--md-typeset-font-size) !important; - } - .admonition .admonition-title { - padding-left: 2.5rem !important; - } - - .admonition .admonition-title:before { - top: 50% !important; - width: 20px !important; - height: 20px !important; - transform: translateY(-50%) !important; - } - `, - }), + stylesTransformer, ]), [ // only add dependencies that are in state or memorized variables to avoid unnecessary calls between re-renders entityRef, - theme, - sidebar, scmIntegrationsApi, techdocsStorageApi, sanitizerTransformer, + stylesTransformer, ], ); From a805d841af1f23be1b94461d7db3410010159a46 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 15:45:52 +0200 Subject: [PATCH 078/149] refactor(techdocs): delete inject css Signed-off-by: Camila Belo --- .../techdocs/src/reader/transformers/index.ts | 1 - .../src/reader/transformers/injectCss.test.ts | 40 ------------------- .../src/reader/transformers/injectCss.ts | 31 -------------- 3 files changed, 72 deletions(-) delete mode 100644 plugins/techdocs/src/reader/transformers/injectCss.test.ts delete mode 100644 plugins/techdocs/src/reader/transformers/injectCss.ts diff --git a/plugins/techdocs/src/reader/transformers/index.ts b/plugins/techdocs/src/reader/transformers/index.ts index 56846a51cd..dc3c43a584 100644 --- a/plugins/techdocs/src/reader/transformers/index.ts +++ b/plugins/techdocs/src/reader/transformers/index.ts @@ -25,6 +25,5 @@ export * from './copyToClipboard'; export * from './removeMkdocsHeader'; export * from './simplifyMkdocsFooter'; export * from './onCssReady'; -export * from './injectCss'; export * from './scrollIntoAnchor'; export * from './transformer'; diff --git a/plugins/techdocs/src/reader/transformers/injectCss.test.ts b/plugins/techdocs/src/reader/transformers/injectCss.test.ts deleted file mode 100644 index 6d0eb8daa9..0000000000 --- a/plugins/techdocs/src/reader/transformers/injectCss.test.ts +++ /dev/null @@ -1,40 +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 { createTestShadowDom } from '../../test-utils'; -import { injectCss } from './injectCss'; - -describe('injectCss', () => { - it('should inject style with passed css in head', async () => { - const html = ` - - - - - `; - const injectedCss = '* {background-color: #fff}'; - - const shadowDom = await createTestShadowDom(html, { - preTransformers: [injectCss({ css: injectedCss })], - postTransformers: [], - }); - - const styleElement = shadowDom.querySelector('head > style'); - - expect(styleElement).toBeTruthy(); - expect(styleElement!.innerHTML).toEqual(injectedCss); - }); -}); diff --git a/plugins/techdocs/src/reader/transformers/injectCss.ts b/plugins/techdocs/src/reader/transformers/injectCss.ts deleted file mode 100644 index c847d3e1d8..0000000000 --- a/plugins/techdocs/src/reader/transformers/injectCss.ts +++ /dev/null @@ -1,31 +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 type { Transformer } from './transformer'; - -type InjectCssOptions = { - css: string; -}; - -export const injectCss = ({ css }: InjectCssOptions): Transformer => { - return dom => { - dom - .getElementsByTagName('head')[0] - .insertAdjacentHTML('beforeend', ``); - - return dom; - }; -}; From 17c059dfd0ff93168955d1b43b28e59bf08dc04a Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Fri, 20 May 2022 16:13:01 +0200 Subject: [PATCH 079/149] chore: add changeset file Signed-off-by: Camila Belo --- .changeset/techdocs-crabs-retire.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .changeset/techdocs-crabs-retire.md diff --git a/.changeset/techdocs-crabs-retire.md b/.changeset/techdocs-crabs-retire.md new file mode 100644 index 0000000000..b15c983bc4 --- /dev/null +++ b/.changeset/techdocs-crabs-retire.md @@ -0,0 +1,9 @@ +--- +'@backstage/plugin-techdocs': patch +--- + +Restructures reader style transformations to improve code readability: + +- Extracts the style rules to separate files; +- Creates a hook that processes each rule; +- And creates another hook that returns a transformer responsible for injecting them into the head tag of a given element. From b862e974cbdcbab2de6c6d947f6a4709fad19b27 Mon Sep 17 00:00:00 2001 From: Camila Belo Date: Mon, 23 May 2022 14:53:35 +0200 Subject: [PATCH 080/149] refactor(techdocs): apply review suggestions Signed-off-by: Camila Belo --- plugins/techdocs/src/reader/transformers/styles/transformer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/techdocs/src/reader/transformers/styles/transformer.ts b/plugins/techdocs/src/reader/transformers/styles/transformer.ts index f16ee951c6..26ab4ceb26 100644 --- a/plugins/techdocs/src/reader/transformers/styles/transformer.ts +++ b/plugins/techdocs/src/reader/transformers/styles/transformer.ts @@ -21,7 +21,7 @@ import { useTheme } from '@material-ui/core'; import { SidebarPinStateContext } from '@backstage/core-components'; import { BackstageTheme } from '@backstage/theme'; -import { Transformer } from '..'; +import { Transformer } from '../transformer'; import { rules } from './rules'; /** From 3533075dfd48a02ce386f69e35391baeb180eb83 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 20 May 2022 13:00:51 +0200 Subject: [PATCH 081/149] Replace SidebarContext with versioned provider and hook. Signed-off-by: Eric Peterson --- packages/app/src/components/Root/Root.tsx | 6 +- packages/core-components/api-report.md | 16 ++-- packages/core-components/package.json | 1 + .../src/layout/Sidebar/Bar.tsx | 6 +- .../src/layout/Sidebar/Intro.tsx | 4 +- .../src/layout/Sidebar/Items.tsx | 6 +- .../src/layout/Sidebar/MobileSidebar.tsx | 7 +- .../layout/Sidebar/SidebarContext.test.tsx | 68 ++++++++++++++++ .../src/layout/Sidebar/SidebarContext.tsx | 78 +++++++++++++++++++ .../src/layout/Sidebar/SidebarSubmenu.tsx | 4 +- .../src/layout/Sidebar/config.ts | 16 ---- .../src/layout/Sidebar/index.ts | 14 +--- .../packages/app/src/components/Root/Root.tsx | 6 +- .../src/components/Root/Root.tsx | 6 +- plugins/shortcuts/src/ShortcutItem.test.tsx | 6 +- plugins/shortcuts/src/Shortcuts.test.tsx | 6 +- 16 files changed, 190 insertions(+), 60 deletions(-) create mode 100644 packages/core-components/src/layout/Sidebar/SidebarContext.test.tsx create mode 100644 packages/core-components/src/layout/Sidebar/SidebarContext.tsx diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index ac62c81899..e90fe29b54 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { useContext, PropsWithChildren } from 'react'; +import React, { PropsWithChildren } from 'react'; import { Link, makeStyles } from '@material-ui/core'; import HomeIcon from '@material-ui/icons/Home'; import ExtensionIcon from '@material-ui/icons/Extension'; @@ -39,13 +39,13 @@ import { Shortcuts } from '@backstage/plugin-shortcuts'; import { Sidebar, sidebarConfig, - SidebarContext, SidebarDivider, SidebarGroup, SidebarItem, SidebarPage, SidebarScrollWrapper, SidebarSpace, + useSidebar, } from '@backstage/core-components'; import { MyGroupsSidebarItem } from '@backstage/plugin-org'; import GroupIcon from '@material-ui/icons/People'; @@ -68,7 +68,7 @@ const useSidebarLogoStyles = makeStyles({ const SidebarLogo = () => { const classes = useSidebarLogoStyles(); - const { isOpen } = useContext(SidebarContext); + const { isOpen } = useSidebar(); return (
diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 727a50926c..e7ccc8e72b 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -16,7 +16,6 @@ import { CardHeaderProps } from '@material-ui/core/CardHeader'; import { Column } from '@material-table/core'; import { ComponentClass } from 'react'; import { ComponentProps } from 'react'; -import { Context } from 'react'; import { default as CSS_2 } from 'csstype'; import { CSSProperties } from 'react'; import { ElementType } from 'react'; @@ -903,13 +902,15 @@ export const sidebarConfig: { mobileSidebarHeight: number; }; -// Warning: (ae-missing-release-tag) "SidebarContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public -export const SidebarContext: Context; +export const SidebarContextProvider: ({ + children, + value, +}: { + children: ReactNode; + value: SidebarContextType; +}) => JSX.Element; -// Warning: (ae-missing-release-tag) "SidebarContextType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// // @public export type SidebarContextType = { isOpen: boolean; @@ -1447,6 +1448,9 @@ export class UserIdentity implements IdentityApi { signOut(): Promise; } +// @public +export const useSidebar: () => SidebarContextType; + // Warning: (ae-missing-release-tag) "useSupportConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/packages/core-components/package.json b/packages/core-components/package.json index a988d8213e..6c7424af6e 100644 --- a/packages/core-components/package.json +++ b/packages/core-components/package.json @@ -37,6 +37,7 @@ "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", "@backstage/theme": "^0.2.15", + "@backstage/version-bridge": "^1.0.1", "@material-table/core": "^3.1.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/packages/core-components/src/layout/Sidebar/Bar.tsx b/packages/core-components/src/layout/Sidebar/Bar.tsx index e78706bd62..2fdb2012dc 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.tsx @@ -25,7 +25,6 @@ import { makeSidebarConfig, makeSidebarSubmenuConfig, SidebarConfig, - SidebarContext, SidebarConfigContext, SubmenuConfig, SidebarOptions, @@ -33,6 +32,7 @@ import { } from './config'; import { BackstageTheme } from '@backstage/theme'; import { SidebarPinStateContext, useContent } from './Page'; +import { SidebarContextProvider } from './SidebarContext'; import { MobileSidebar } from './MobileSidebar'; /** @public */ @@ -191,7 +191,7 @@ const DesktopSidebar = (props: DesktopSidebarProps) => { return (
- + ); }; diff --git a/packages/core-components/src/layout/Sidebar/Intro.tsx b/packages/core-components/src/layout/Sidebar/Intro.tsx index d1d8f4b2a2..51d453b22d 100644 --- a/packages/core-components/src/layout/Sidebar/Intro.tsx +++ b/packages/core-components/src/layout/Sidebar/Intro.tsx @@ -25,10 +25,10 @@ import { useLocalStorageValue } from '@react-hookz/web'; import { SidebarConfigContext, SidebarConfig, - SidebarContext, SIDEBAR_INTRO_LOCAL_STORAGE, } from './config'; import { SidebarDivider } from './Items'; +import { useSidebar } from './SidebarContext'; /** @public */ export type SidebarIntroClassKey = @@ -151,7 +151,7 @@ const recentlyViewedIntroText = 'And your recently viewed plugins will pop up here!'; export function SidebarIntro(_props: {}) { - const { isOpen } = useContext(SidebarContext); + const { isOpen } = useSidebar(); const defaultValue = { starredItemsDismissed: false, recentlyViewedItemsDismissed: false, diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index fde3c85c96..3d294a54a8 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -48,7 +48,6 @@ import { useResolvedPath, } from 'react-router-dom'; import { - SidebarContext, SidebarConfigContext, SidebarItemWithSubmenuContext, SidebarConfig, @@ -62,6 +61,7 @@ import DoubleArrowLeft from './icons/DoubleArrowLeft'; import DoubleArrowRight from './icons/DoubleArrowRight'; import { isLocationMatch } from './utils'; import { Location } from 'history'; +import { useSidebar } from './SidebarContext'; /** @public */ export type SidebarItemClassKey = @@ -369,7 +369,7 @@ const SidebarItemBase = forwardRef((props, ref) => { // XXX (@koroeskohr): unsure this is optimal. But I just really didn't want to have the item component // depend on the current location, and at least have it being optionally forced to selected. // Still waiting on a Q answered to fine tune the implementation - const { isOpen } = useContext(SidebarContext); + const { isOpen } = useSidebar(); const divStyle = !isOpen && hasSubmenu ? { display: 'flex', marginLeft: '24px' } : {}; @@ -671,7 +671,7 @@ export const SidebarScrollWrapper = styled('div')(({ theme }) => { export const SidebarExpandButton = () => { const { sidebarConfig } = useContext(SidebarConfigContext); const classes = useMemoStyles(sidebarConfig); - const { isOpen, setOpen } = useContext(SidebarContext); + const { isOpen, setOpen } = useSidebar(); const isSmallScreen = useMediaQuery( theme => theme.breakpoints.down('md'), { noSsr: true }, diff --git a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx index 111bf38314..46f38d9a2c 100644 --- a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx +++ b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx @@ -27,8 +27,9 @@ import MenuIcon from '@material-ui/icons/Menu'; import { orderBy } from 'lodash'; import React, { createContext, useEffect, useState, useContext } from 'react'; import { useLocation } from 'react-router'; +import { SidebarContextProvider } from './SidebarContext'; import { SidebarGroup } from './SidebarGroup'; -import { SidebarConfigContext, SidebarContext, SidebarConfig } from './config'; +import { SidebarConfigContext, SidebarConfig } from './config'; /** * Type of `MobileSidebarContext` @@ -207,7 +208,7 @@ export const MobileSidebar = (props: MobileSidebarProps) => { !sidebarGroups[selectedMenuItemIndex].props.to; return ( - {} }}> + {} }}> @@ -231,6 +232,6 @@ export const MobileSidebar = (props: MobileSidebarProps) => { {sidebarGroups} - + ); }; diff --git a/packages/core-components/src/layout/Sidebar/SidebarContext.test.tsx b/packages/core-components/src/layout/Sidebar/SidebarContext.test.tsx new file mode 100644 index 0000000000..d14e035433 --- /dev/null +++ b/packages/core-components/src/layout/Sidebar/SidebarContext.test.tsx @@ -0,0 +1,68 @@ +/* + * 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 } from 'react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { screen, waitFor } from '@testing-library/react'; +import { renderHook, act } from '@testing-library/react-hooks'; +import { SidebarContextProvider, useSidebar } from './SidebarContext'; + +describe('SidebarContext', () => { + describe('SidebarContextProvider', () => { + it('should render children', async () => { + await renderInTestApp( + {} }}> + Child + , + ); + expect(await screen.findByText('Child')).toBeInTheDocument(); + }); + }); + + describe('useSidebar', () => { + it('does not need to be invoked within provider', () => { + const { result } = renderHook(() => useSidebar()); + expect(result.current.isOpen).toBe(false); + expect(typeof result.current.setOpen).toBe('function'); + }); + + it('should read and update state', async () => { + let actualValue = true; + const wrapper = ({ children }: { children: ReactNode }) => ( + { + actualValue = value; + }, + }} + > + {children} + + ); + const { result } = renderHook(() => useSidebar(), { wrapper }); + + expect(result.current.isOpen).toBe(true); + + act(() => { + result.current.setOpen(false); + }); + + waitFor(() => { + expect(result.current.isOpen).toBe(false); + }); + }); + }); +}); diff --git a/packages/core-components/src/layout/Sidebar/SidebarContext.tsx b/packages/core-components/src/layout/Sidebar/SidebarContext.tsx new file mode 100644 index 0000000000..c91be85928 --- /dev/null +++ b/packages/core-components/src/layout/Sidebar/SidebarContext.tsx @@ -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 React, { ReactNode, useContext } from 'react'; +import { + createVersionedContext, + createVersionedValueMap, +} from '@backstage/version-bridge'; + +/** + * Types for the `SidebarContext` + * + * @public + */ +export type SidebarContextType = { + isOpen: boolean; + setOpen: (open: boolean) => void; +}; + +const VersionedSidebarContext = createVersionedContext<{ + 1: SidebarContextType; +}>('sidebar-context'); + +/** + * Provides context for reading and updating sidebar state. + * + * @public + */ +export const SidebarContextProvider = ({ + children, + value, +}: { + children: ReactNode; + value: SidebarContextType; +}) => ( + + {children} + +); + +/** + * Hook to read and update sidebar state. + * + * @public + */ +export const useSidebar = (): SidebarContextType => { + const versionedSidebarContext = useContext(VersionedSidebarContext); + + // Invoked from outside a SidbarContextProvider, return a default value. + if (versionedSidebarContext === undefined) { + return { + isOpen: false, + setOpen: () => {}, + }; + } + + const sidebarContext = versionedSidebarContext.atVersion(1); + if (sidebarContext === undefined) { + throw new Error('No context found for version 1.'); + } + + return sidebarContext; +}; diff --git a/packages/core-components/src/layout/Sidebar/SidebarSubmenu.tsx b/packages/core-components/src/layout/Sidebar/SidebarSubmenu.tsx index d1b79263eb..d8a87c98d9 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarSubmenu.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarSubmenu.tsx @@ -19,10 +19,10 @@ import classnames from 'classnames'; import React, { ReactNode, useContext, useEffect, useState } from 'react'; import { SidebarItemWithSubmenuContext, - SidebarContext, SidebarConfigContext, SubmenuConfig, } from './config'; +import { useSidebar } from './SidebarContext'; import { BackstageTheme } from '@backstage/theme'; const useStyles = makeStyles< @@ -105,7 +105,7 @@ export type SidebarSubmenuProps = { * @public */ export const SidebarSubmenu = (props: SidebarSubmenuProps) => { - const { isOpen } = useContext(SidebarContext); + const { isOpen } = useSidebar(); const { sidebarConfig, submenuConfig } = useContext(SidebarConfigContext); const left = isOpen ? sidebarConfig.drawerWidthOpen diff --git a/packages/core-components/src/layout/Sidebar/config.ts b/packages/core-components/src/layout/Sidebar/config.ts index 08d469d5a3..bb0ff207fb 100644 --- a/packages/core-components/src/layout/Sidebar/config.ts +++ b/packages/core-components/src/layout/Sidebar/config.ts @@ -101,22 +101,6 @@ export const makeSidebarSubmenuConfig = ( export const SIDEBAR_INTRO_LOCAL_STORAGE = '@backstage/core/sidebar-intro-dismissed'; -/** - * Types for the `SidebarContext` - */ -export type SidebarContextType = { - isOpen: boolean; - setOpen: (open: boolean) => void; -}; - -/** - * Context whether the `Sidebar` is open - */ -export const SidebarContext = createContext({ - isOpen: false, - setOpen: () => {}, -}); - export type SidebarConfigContextType = { sidebarConfig: SidebarConfig; submenuConfig: SubmenuConfig; diff --git a/packages/core-components/src/layout/Sidebar/index.ts b/packages/core-components/src/layout/Sidebar/index.ts index ecc573fbda..a852b35bf8 100644 --- a/packages/core-components/src/layout/Sidebar/index.ts +++ b/packages/core-components/src/layout/Sidebar/index.ts @@ -54,13 +54,7 @@ export type { } from './Items'; export { IntroCard, SidebarIntro } from './Intro'; export type { SidebarIntroClassKey } from './Intro'; -export { - SIDEBAR_INTRO_LOCAL_STORAGE, - SidebarContext, - sidebarConfig, -} from './config'; -export type { - SidebarContextType, - SidebarOptions, - SubmenuOptions, -} from './config'; +export { SIDEBAR_INTRO_LOCAL_STORAGE, sidebarConfig } from './config'; +export type { SidebarOptions, SubmenuOptions } from './config'; +export { SidebarContextProvider, useSidebar } from './SidebarContext'; +export type { SidebarContextType } from './SidebarContext'; diff --git a/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx b/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx index d10eccf03a..05e11458ce 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { useContext, PropsWithChildren } from 'react'; +import React, { PropsWithChildren } from 'react'; import { Link, makeStyles } from '@material-ui/core'; import HomeIcon from '@material-ui/icons/Home'; import ExtensionIcon from '@material-ui/icons/Extension'; @@ -32,13 +32,13 @@ import { SidebarSearchModal } from '@backstage/plugin-search'; import { Sidebar, sidebarConfig, - SidebarContext, SidebarDivider, SidebarGroup, SidebarItem, SidebarPage, SidebarScrollWrapper, SidebarSpace, + useSidebar, } from '@backstage/core-components'; import MenuIcon from '@material-ui/icons/Menu'; import SearchIcon from '@material-ui/icons/Search'; @@ -60,7 +60,7 @@ const useSidebarLogoStyles = makeStyles({ const SidebarLogo = () => { const classes = useSidebarLogoStyles(); - const { isOpen } = useContext(SidebarContext); + const { isOpen } = useSidebar(); return (
diff --git a/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx b/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx index 249394fdc4..6613e7c0ea 100644 --- a/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx +++ b/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { PropsWithChildren, useContext } from 'react'; +import React, { PropsWithChildren } from 'react'; import { Link, makeStyles } from '@material-ui/core'; import LibraryBooks from '@material-ui/icons/LibraryBooks'; @@ -27,7 +27,7 @@ import { SidebarPage, sidebarConfig, SidebarDivider, - SidebarContext, + useSidebar, } from '@backstage/core-components'; import { NavLink } from 'react-router-dom'; @@ -48,7 +48,7 @@ const useSidebarLogoStyles = makeStyles({ const SidebarLogo = () => { const classes = useSidebarLogoStyles(); - const { isOpen } = useContext(SidebarContext); + const { isOpen } = useSidebar(); return (
diff --git a/plugins/shortcuts/src/ShortcutItem.test.tsx b/plugins/shortcuts/src/ShortcutItem.test.tsx index 90b8ff06e5..d1a1018513 100644 --- a/plugins/shortcuts/src/ShortcutItem.test.tsx +++ b/plugins/shortcuts/src/ShortcutItem.test.tsx @@ -20,7 +20,7 @@ import { ShortcutItem } from './ShortcutItem'; import { Shortcut } from './types'; import { LocalStoredShortcuts } from './api'; import { MockStorageApi, renderInTestApp } from '@backstage/test-utils'; -import { SidebarContext } from '@backstage/core-components'; +import { SidebarContextProvider } from '@backstage/core-components'; describe('ShortcutItem', () => { const shortcut: Shortcut = { @@ -32,9 +32,9 @@ describe('ShortcutItem', () => { it('displays the shortcut', async () => { await renderInTestApp( - {} }}> + {} }}> - , + , ); expect(screen.getByText('ST')).toBeInTheDocument(); expect(screen.getByText('some title')).toBeInTheDocument(); diff --git a/plugins/shortcuts/src/Shortcuts.test.tsx b/plugins/shortcuts/src/Shortcuts.test.tsx index 8cee9a6622..813dbb8def 100644 --- a/plugins/shortcuts/src/Shortcuts.test.tsx +++ b/plugins/shortcuts/src/Shortcuts.test.tsx @@ -24,12 +24,12 @@ import { screen, waitFor } from '@testing-library/react'; import { Shortcuts } from './Shortcuts'; import { LocalStoredShortcuts, shortcutsApiRef } from './api'; -import { SidebarContext } from '@backstage/core-components'; +import { SidebarContextProvider } from '@backstage/core-components'; describe('Shortcuts', () => { it('displays an add button', async () => { await renderInTestApp( - {} }}> + {} }}> { > - , + , ); await waitFor(() => !screen.queryByTestId('progress')); expect(screen.getByText('Add Shortcuts')).toBeInTheDocument(); From da72da5daee9ac5cd54e43bbc9c2db13b51b6f53 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 20 May 2022 15:23:39 +0200 Subject: [PATCH 082/149] Replace SidebarPinStateContext with versioned provider and hook. Signed-off-by: Eric Peterson --- .../app/src/components/search/SearchPage.tsx | 6 +- packages/core-components/api-report.md | 11 ++- .../core-components/src/layout/Page/Page.tsx | 6 +- .../src/layout/Sidebar/Bar.test.tsx | 6 +- .../src/layout/Sidebar/Bar.tsx | 9 +-- .../src/layout/Sidebar/Page.tsx | 29 +------ .../src/layout/Sidebar/SidebarGroup.tsx | 4 +- .../Sidebar/SidebarPinStateContext.test.tsx | 79 +++++++++++++++++++ .../layout/Sidebar/SidebarPinStateContext.tsx | 79 +++++++++++++++++++ .../src/layout/Sidebar/index.ts | 17 ++-- .../reader/transformers/styles/transformer.ts | 6 +- .../General/UserSettingsAppearanceCard.tsx | 6 +- .../General/UserSettingsPinToggle.test.tsx | 6 +- .../General/UserSettingsPinToggle.tsx | 8 +- .../src/components/SettingsPage.tsx | 6 +- 15 files changed, 208 insertions(+), 70 deletions(-) create mode 100644 packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx create mode 100644 packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx diff --git a/packages/app/src/components/search/SearchPage.tsx b/packages/app/src/components/search/SearchPage.tsx index d1b56bbaf0..d23ad957d7 100644 --- a/packages/app/src/components/search/SearchPage.tsx +++ b/packages/app/src/components/search/SearchPage.tsx @@ -21,7 +21,7 @@ import { Header, Lifecycle, Page, - SidebarPinStateContext, + useSidebarPinState, } from '@backstage/core-components'; import { useApi } from '@backstage/core-plugin-api'; import { CatalogSearchResultListItem } from '@backstage/plugin-catalog'; @@ -40,7 +40,7 @@ import { import { useSearch } from '@backstage/plugin-search-react'; import { TechDocsSearchResultListItem } from '@backstage/plugin-techdocs'; import { Grid, List, makeStyles, Paper, Theme } from '@material-ui/core'; -import React, { useContext } from 'react'; +import React from 'react'; const useStyles = makeStyles((theme: Theme) => ({ bar: { @@ -59,7 +59,7 @@ const useStyles = makeStyles((theme: Theme) => ({ const SearchPage = () => { const classes = useStyles(); - const { isMobile } = useContext(SidebarPinStateContext); + const { isMobile } = useSidebarPinState(); const { types } = useSearch(); const catalogApi = useApi(catalogApiRef); diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index e7ccc8e72b..d162e7a714 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -1006,7 +1006,13 @@ export type SidebarPageProps = { }; // @public -export const SidebarPinStateContext: React_2.Context; +export const SidebarPinStateContextProvider: ({ + children, + value, +}: { + children: ReactNode; + value: SidebarPinStateContextType; +}) => JSX.Element; // @public export type SidebarPinStateContextType = { @@ -1451,6 +1457,9 @@ export class UserIdentity implements IdentityApi { // @public export const useSidebar: () => SidebarContextType; +// @public +export const useSidebarPinState: () => SidebarPinStateContextType; + // Warning: (ae-missing-release-tag) "useSupportConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) diff --git a/packages/core-components/src/layout/Page/Page.tsx b/packages/core-components/src/layout/Page/Page.tsx index 9bfc16fea3..1abb8da93b 100644 --- a/packages/core-components/src/layout/Page/Page.tsx +++ b/packages/core-components/src/layout/Page/Page.tsx @@ -14,10 +14,10 @@ * limitations under the License. */ -import React, { useContext } from 'react'; +import React from 'react'; import { BackstageTheme } from '@backstage/theme'; import { makeStyles, ThemeProvider } from '@material-ui/core/styles'; -import { SidebarPinStateContext } from '../Sidebar/Page'; +import { useSidebarPinState } from '../Sidebar/SidebarPinStateContext'; export type PageClassKey = 'root'; @@ -43,7 +43,7 @@ type Props = { export function Page(props: Props) { const { themeId, children } = props; - const { isMobile } = useContext(SidebarPinStateContext); + const { isMobile } = useSidebarPinState(); const classes = useStyles({ isMobile }); return ( - , + , ); } diff --git a/packages/core-components/src/layout/Sidebar/Bar.tsx b/packages/core-components/src/layout/Sidebar/Bar.tsx index 2fdb2012dc..a315406b58 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.tsx @@ -31,8 +31,9 @@ import { SubmenuOptions, } from './config'; import { BackstageTheme } from '@backstage/theme'; -import { SidebarPinStateContext, useContent } from './Page'; +import { useContent } from './Page'; import { SidebarContextProvider } from './SidebarContext'; +import { useSidebarPinState } from './SidebarPinStateContext'; import { MobileSidebar } from './MobileSidebar'; /** @public */ @@ -133,9 +134,7 @@ const DesktopSidebar = (props: DesktopSidebarProps) => { ); const [state, setState] = useState(State.Closed); const hoverTimerRef = useRef(); - const { isPinned, toggleSidebarPinState } = useContext( - SidebarPinStateContext, - ); + const { isPinned, toggleSidebarPinState } = useSidebarPinState(); const handleOpen = () => { if (isPinned || disableExpandOnHover) { @@ -226,7 +225,7 @@ export const Sidebar = (props: SidebarProps) => { props.submenuOptions ?? {}, ); const { children, disableExpandOnHover, openDelayMs, closeDelayMs } = props; - const { isMobile } = useContext(SidebarPinStateContext); + const { isMobile } = useSidebarPinState(); return isMobile ? ( {children} diff --git a/packages/core-components/src/layout/Sidebar/Page.tsx b/packages/core-components/src/layout/Sidebar/Page.tsx index 9bf8105f81..17cd5ddee1 100644 --- a/packages/core-components/src/layout/Sidebar/Page.tsx +++ b/packages/core-components/src/layout/Sidebar/Page.tsx @@ -29,6 +29,7 @@ import { SidebarConfigContext, SidebarConfig } from './config'; import { BackstageTheme } from '@backstage/theme'; import { LocalStorage } from './localStorage'; import useMediaQuery from '@material-ui/core/useMediaQuery'; +import { SidebarPinStateContextProvider } from './SidebarPinStateContext'; export type SidebarPageClassKey = 'root'; @@ -62,17 +63,6 @@ const useStyles = makeStyles< { name: 'BackstageSidebarPage' }, ); -/** - * Type of `SidebarPinStateContext` - * - * @public - */ -export type SidebarPinStateContextType = { - isPinned: boolean; - toggleSidebarPinState: () => any; - isMobile?: boolean; -}; - /** * Props for SidebarPage * @@ -82,19 +72,6 @@ export type SidebarPageProps = { children?: React.ReactNode; }; -/** - * Contains the state on how the `Sidebar` is rendered - * - * @public - */ -export const SidebarPinStateContext = createContext( - { - isPinned: true, - toggleSidebarPinState: () => {}, - isMobile: false, - }, -); - type PageContextType = { content: { contentRef?: React.MutableRefObject; @@ -137,7 +114,7 @@ export function SidebarPage(props: SidebarPageProps) { const classes = useStyles({ isPinned, sidebarConfig }); return ( -
{props.children}
-
+ ); } diff --git a/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx b/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx index 55b5e770b0..1158cc14df 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarGroup.tsx @@ -22,7 +22,7 @@ import BottomNavigationAction, { import { makeStyles } from '@material-ui/core/styles'; import React, { useContext } from 'react'; import { useLocation } from 'react-router-dom'; -import { SidebarPinStateContext } from '.'; +import { useSidebarPinState } from '.'; import { Link } from '../../components'; import { SidebarConfigContext, SidebarConfig } from './config'; import { MobileSidebarContext } from './MobileSidebar'; @@ -122,7 +122,7 @@ const MobileSidebarGroup = (props: SidebarGroupProps) => { */ export const SidebarGroup = (props: SidebarGroupProps) => { const { children, to, label, icon, value } = props; - const { isMobile } = useContext(SidebarPinStateContext); + const { isMobile } = useSidebarPinState(); return isMobile ? ( diff --git a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx new file mode 100644 index 0000000000..cc83a846e3 --- /dev/null +++ b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx @@ -0,0 +1,79 @@ +/* + * 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 } from 'react'; +import { renderInTestApp } from '@backstage/test-utils'; +import { screen, waitFor } from '@testing-library/react'; +import { renderHook, act } from '@testing-library/react-hooks'; +import { + SidebarPinStateContextProvider, + useSidebarPinState, +} from './SidebarPinStateContext'; + +describe('SidebarContext', () => { + describe('SidebarContextProvider', () => { + it('should render children', async () => { + await renderInTestApp( + {}, + }} + > + Child + , + ); + expect(await screen.findByText('Child')).toBeInTheDocument(); + }); + }); + + describe('useSidebar', () => { + it('does not need to be invoked within provider', () => { + const { result } = renderHook(() => useSidebarPinState()); + expect(result.current.isPinned).toBe(true); + expect(result.current.isMobile).toBe(false); + expect(typeof result.current.toggleSidebarPinState).toBe('function'); + }); + + it('should read and update state', async () => { + let actualValue = true; + const wrapper = ({ children }: { children: ReactNode }) => ( + { + actualValue = !actualValue; + }, + }} + > + {children} + + ); + const { result } = renderHook(() => useSidebarPinState(), { wrapper }); + + expect(result.current.isPinned).toBe(true); + + act(() => { + result.current.toggleSidebarPinState(); + }); + + waitFor(() => { + expect(result.current.isPinned).toBe(false); + }); + }); + }); +}); diff --git a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx new file mode 100644 index 0000000000..709973e064 --- /dev/null +++ b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx @@ -0,0 +1,79 @@ +/* + * 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 { + createVersionedContext, + createVersionedValueMap, +} from '@backstage/version-bridge'; +import React, { ReactNode, useContext } from 'react'; + +/** + * Type of `SidebarPinStateContext` + * + * @public + */ +export type SidebarPinStateContextType = { + isPinned: boolean; + toggleSidebarPinState: () => any; + isMobile?: boolean; +}; + +const VersionedSidebarPinStateContext = createVersionedContext<{ + 1: SidebarPinStateContextType; +}>('sidebar-pin-state-context'); + +/** + * Provides state for how the `Sidebar` is rendered + * + * @public + */ +export const SidebarPinStateContextProvider = ({ + children, + value, +}: { + children: ReactNode; + value: SidebarPinStateContextType; +}) => ( + + {children} + +); + +/** + * Hook to read and update sidebar pin state. + * + * @public + */ +export const useSidebarPinState = (): SidebarPinStateContextType => { + const versionedSidebarContext = useContext(VersionedSidebarPinStateContext); + + // Invoked from outside a SidebarPinStateContextProvider: default value. + if (versionedSidebarContext === undefined) { + return { + isPinned: true, + toggleSidebarPinState: () => {}, + isMobile: false, + }; + } + + const sidebarContext = versionedSidebarContext.atVersion(1); + if (sidebarContext === undefined) { + throw new Error('No context found for version 1.'); + } + + return sidebarContext; +}; diff --git a/packages/core-components/src/layout/Sidebar/index.ts b/packages/core-components/src/layout/Sidebar/index.ts index a852b35bf8..e041ecc730 100644 --- a/packages/core-components/src/layout/Sidebar/index.ts +++ b/packages/core-components/src/layout/Sidebar/index.ts @@ -27,16 +27,8 @@ export type { SidebarSubmenuItemDropdownItem, } from './SidebarSubmenuItem'; export type { SidebarClassKey, SidebarProps } from './Bar'; -export { - SidebarPage, - SidebarPinStateContext as SidebarPinStateContext, - useContent, -} from './Page'; -export type { - SidebarPinStateContextType as SidebarPinStateContextType, - SidebarPageClassKey, - SidebarPageProps, -} from './Page'; +export { SidebarPage, useContent } from './Page'; +export type { SidebarPageClassKey, SidebarPageProps } from './Page'; export { SidebarDivider, SidebarItem, @@ -58,3 +50,8 @@ export { SIDEBAR_INTRO_LOCAL_STORAGE, sidebarConfig } from './config'; export type { SidebarOptions, SubmenuOptions } from './config'; export { SidebarContextProvider, useSidebar } from './SidebarContext'; export type { SidebarContextType } from './SidebarContext'; +export { + SidebarPinStateContextProvider, + useSidebarPinState, +} from './SidebarPinStateContext'; +export type { SidebarPinStateContextType } from './SidebarPinStateContext'; diff --git a/plugins/techdocs/src/reader/transformers/styles/transformer.ts b/plugins/techdocs/src/reader/transformers/styles/transformer.ts index 26ab4ceb26..04a6d476af 100644 --- a/plugins/techdocs/src/reader/transformers/styles/transformer.ts +++ b/plugins/techdocs/src/reader/transformers/styles/transformer.ts @@ -14,11 +14,11 @@ * limitations under the License. */ -import { useCallback, useContext, useMemo } from 'react'; +import { useCallback, useMemo } from 'react'; import { useTheme } from '@material-ui/core'; -import { SidebarPinStateContext } from '@backstage/core-components'; +import { useSidebarPinState } from '@backstage/core-components'; import { BackstageTheme } from '@backstage/theme'; import { Transformer } from '../transformer'; @@ -27,7 +27,7 @@ import { rules } from './rules'; /** * Sidebar pinned state to be used in computing style injections. */ -const useSidebar = () => useContext(SidebarPinStateContext); +const useSidebar = () => useSidebarPinState(); /** * Process all rules and concatenate their definitions into a single style. diff --git a/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx b/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx index 15ab93c6d0..cd55218db5 100644 --- a/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsAppearanceCard.tsx @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { InfoCard, SidebarPinStateContext } from '@backstage/core-components'; +import { InfoCard, useSidebarPinState } from '@backstage/core-components'; import { List } from '@material-ui/core'; -import React, { useContext } from 'react'; +import React from 'react'; import { UserSettingsPinToggle } from './UserSettingsPinToggle'; import { UserSettingsThemeToggle } from './UserSettingsThemeToggle'; export const UserSettingsAppearanceCard = () => { - const { isMobile } = useContext(SidebarPinStateContext); + const { isMobile } = useSidebarPinState(); return ( diff --git a/plugins/user-settings/src/components/General/UserSettingsPinToggle.test.tsx b/plugins/user-settings/src/components/General/UserSettingsPinToggle.test.tsx index b63ac85b10..a41792768a 100644 --- a/plugins/user-settings/src/components/General/UserSettingsPinToggle.test.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsPinToggle.test.tsx @@ -18,14 +18,14 @@ import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent } from '@testing-library/react'; import React from 'react'; import { UserSettingsPinToggle } from './UserSettingsPinToggle'; -import { SidebarPinStateContext } from '@backstage/core-components'; +import { SidebarPinStateContextProvider } from '@backstage/core-components'; describe('', () => { it('toggles the pin sidebar button', async () => { const mockToggleFn = jest.fn(); const rendered = await renderWithEffects( wrapInTestApp( - ', () => { }} > - , + , ), ); expect(rendered.getByText('Pin Sidebar')).toBeInTheDocument(); diff --git a/plugins/user-settings/src/components/General/UserSettingsPinToggle.tsx b/plugins/user-settings/src/components/General/UserSettingsPinToggle.tsx index 4d71df8113..d218787c05 100644 --- a/plugins/user-settings/src/components/General/UserSettingsPinToggle.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsPinToggle.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { useContext } from 'react'; +import React from 'react'; import { ListItem, ListItemSecondaryAction, @@ -22,12 +22,10 @@ import { Switch, Tooltip, } from '@material-ui/core'; -import { SidebarPinStateContext } from '@backstage/core-components'; +import { useSidebarPinState } from '@backstage/core-components'; export const UserSettingsPinToggle = () => { - const { isPinned, toggleSidebarPinState } = useContext( - SidebarPinStateContext, - ); + const { isPinned, toggleSidebarPinState } = useSidebarPinState(); return ( diff --git a/plugins/user-settings/src/components/SettingsPage.tsx b/plugins/user-settings/src/components/SettingsPage.tsx index 8942235fda..dcaf62d435 100644 --- a/plugins/user-settings/src/components/SettingsPage.tsx +++ b/plugins/user-settings/src/components/SettingsPage.tsx @@ -17,10 +17,10 @@ import { Header, Page, - SidebarPinStateContext, TabbedLayout, + useSidebarPinState, } from '@backstage/core-components'; -import React, { useContext } from 'react'; +import React from 'react'; import { useOutlet } from 'react-router'; import { useElementFilter } from '@backstage/core-plugin-api'; import { UserSettingsAuthProviders } from './AuthProviders'; @@ -33,7 +33,7 @@ type Props = { }; export const SettingsPage = ({ providerSettings }: Props) => { - const { isMobile } = useContext(SidebarPinStateContext); + const { isMobile } = useSidebarPinState(); const outlet = useOutlet(); const tabs = useElementFilter(outlet, elements => From bff65e6958de04600bfcfe4bcd022cc6e0443b8f Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 20 May 2022 16:51:32 +0200 Subject: [PATCH 083/149] Changesets for affected packages. Signed-off-by: Eric Peterson --- .changeset/give-that-wolf-a-banana.md | 30 +++++++++++++++++++++++ .changeset/right-one-at-the-wrong-time.md | 7 ++++++ .changeset/up-in-space-man.md | 7 ++++++ 3 files changed, 44 insertions(+) create mode 100644 .changeset/give-that-wolf-a-banana.md create mode 100644 .changeset/right-one-at-the-wrong-time.md create mode 100644 .changeset/up-in-space-man.md diff --git a/.changeset/give-that-wolf-a-banana.md b/.changeset/give-that-wolf-a-banana.md new file mode 100644 index 0000000000..685ef7400a --- /dev/null +++ b/.changeset/give-that-wolf-a-banana.md @@ -0,0 +1,30 @@ +--- +'@backstage/create-app': patch +--- + +Use of `SidebarContext` has been deprecated and will be removed in a future release. Instead, `useSidebar()` should be used to consume the context and `` should be used to provide it. + +To prepare your app, update `packages/app/src/components/Root/Root.tsx` as follows: + +```diff +import { + Sidebar, + sidebarConfig, +- SidebarContext + SidebarDivider, + // ... + SidebarSpace, ++ useSidebar, +} from '@backstage/core-components'; + +// ... + + +const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); +- const { isOpen } = useContext(SidebarContext); ++ const { isOpen } = useSidebar(); + + // ... +}; +``` diff --git a/.changeset/right-one-at-the-wrong-time.md b/.changeset/right-one-at-the-wrong-time.md new file mode 100644 index 0000000000..c7fee94a91 --- /dev/null +++ b/.changeset/right-one-at-the-wrong-time.md @@ -0,0 +1,7 @@ +--- +'@backstage/plugin-techdocs': patch +'@backstage/plugin-user-settings': patch +'@techdocs/cli': patch +--- + +Updated sidebar-related logic to use `` + `useSidebarPinState()` and/or `` + `useSidebar()` from `@backstage/core-components`. diff --git a/.changeset/up-in-space-man.md b/.changeset/up-in-space-man.md new file mode 100644 index 0000000000..f3aadfece8 --- /dev/null +++ b/.changeset/up-in-space-man.md @@ -0,0 +1,7 @@ +--- +'@backstage/core-components': patch +--- + +The `SidebarPinStateContext` and `SidebarContext` have been deprecated and will be removed in a future release. Instead, use `` + `useSidebarPinState()` and/or `` + `useSidebar()`. + +This was done to ensure that sidebar state can be shared successfully across components exported by different packages, regardless of what version of this package is resolved and installed for each individual package. From a907d620fa771fc4a4c8f71e4adabdb2839a3446 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 20 May 2022 17:28:14 +0200 Subject: [PATCH 084/149] Provide a deprecation path for affected sidebar contexts. Signed-off-by: Eric Peterson --- packages/core-components/api-report.md | 6 +++ .../layout/Sidebar/SidebarContext.test.tsx | 41 +++++++++++++---- .../src/layout/Sidebar/SidebarContext.tsx | 34 +++++++++----- .../Sidebar/SidebarPinStateContext.test.tsx | 45 ++++++++++++++----- .../layout/Sidebar/SidebarPinStateContext.tsx | 35 ++++++++++----- .../src/layout/Sidebar/index.ts | 7 ++- 6 files changed, 128 insertions(+), 40 deletions(-) diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index d162e7a714..33db173fcf 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -902,6 +902,9 @@ export const sidebarConfig: { mobileSidebarHeight: number; }; +// @public @deprecated +export const SidebarContext: React_2.Context; + // @public export const SidebarContextProvider: ({ children, @@ -1005,6 +1008,9 @@ export type SidebarPageProps = { children?: React_2.ReactNode; }; +// @public @deprecated +export const SidebarPinStateContext: React_2.Context; + // @public export const SidebarPinStateContextProvider: ({ children, diff --git a/packages/core-components/src/layout/Sidebar/SidebarContext.test.tsx b/packages/core-components/src/layout/Sidebar/SidebarContext.test.tsx index d14e035433..43a18851d7 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarContext.test.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarContext.test.tsx @@ -13,21 +13,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { ReactNode } from 'react'; -import { renderInTestApp } from '@backstage/test-utils'; -import { screen, waitFor } from '@testing-library/react'; +import React, { ReactNode, useContext } from 'react'; +import { renderWithEffects } from '@backstage/test-utils'; +import { waitFor } from '@testing-library/react'; import { renderHook, act } from '@testing-library/react-hooks'; -import { SidebarContextProvider, useSidebar } from './SidebarContext'; +import { + LegacySidebarContext, + SidebarContextProvider, + useSidebar, +} from './SidebarContext'; describe('SidebarContext', () => { describe('SidebarContextProvider', () => { it('should render children', async () => { - await renderInTestApp( + const { findByText } = await renderWithEffects( {} }}> Child , ); - expect(await screen.findByText('Child')).toBeInTheDocument(); + expect(await findByText('Child')).toBeInTheDocument(); + }); + + it('should provide the legacy context as well, for now', async () => { + const LegacyContextSpy = () => { + const { isOpen } = useContext(LegacySidebarContext); + return <>{String(isOpen)}; + }; + + const { findByText } = await renderWithEffects( + {}, + }} + > + + , + ); + + expect(await findByText('true')).toBeInTheDocument(); }); }); @@ -52,15 +76,16 @@ describe('SidebarContext', () => { {children} ); - const { result } = renderHook(() => useSidebar(), { wrapper }); + const { result, rerender } = renderHook(() => useSidebar(), { wrapper }); expect(result.current.isOpen).toBe(true); act(() => { result.current.setOpen(false); + rerender(); }); - waitFor(() => { + await waitFor(() => { expect(result.current.isOpen).toBe(false); }); }); diff --git a/packages/core-components/src/layout/Sidebar/SidebarContext.tsx b/packages/core-components/src/layout/Sidebar/SidebarContext.tsx index c91be85928..4e47b6040a 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarContext.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarContext.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import React, { ReactNode, useContext } from 'react'; +import React, { createContext, ReactNode, useContext } from 'react'; import { createVersionedContext, createVersionedValueMap, @@ -30,6 +30,21 @@ export type SidebarContextType = { setOpen: (open: boolean) => void; }; +const defaultSidebarContext = { + isOpen: false, + setOpen: () => {}, +}; + +/** + * Context whether the `Sidebar` is open + * + * @public @deprecated + * Use `` + `useSidebar()` instead. + */ +export const LegacySidebarContext = createContext( + defaultSidebarContext, +); + const VersionedSidebarContext = createVersionedContext<{ 1: SidebarContextType; }>('sidebar-context'); @@ -46,11 +61,13 @@ export const SidebarContextProvider = ({ children: ReactNode; value: SidebarContextType; }) => ( - - {children} - + + + {children} + + ); /** @@ -63,10 +80,7 @@ export const useSidebar = (): SidebarContextType => { // Invoked from outside a SidbarContextProvider, return a default value. if (versionedSidebarContext === undefined) { - return { - isOpen: false, - setOpen: () => {}, - }; + return defaultSidebarContext; } const sidebarContext = versionedSidebarContext.atVersion(1); diff --git a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx index cc83a846e3..d8bc054e4a 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx @@ -13,19 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { ReactNode } from 'react'; -import { renderInTestApp } from '@backstage/test-utils'; -import { screen, waitFor } from '@testing-library/react'; +import React, { ReactNode, useContext } from 'react'; +import { renderWithEffects } from '@backstage/test-utils'; +import { waitFor } from '@testing-library/react'; import { renderHook, act } from '@testing-library/react-hooks'; import { + LegacySidebarPinStateContext, SidebarPinStateContextProvider, useSidebarPinState, } from './SidebarPinStateContext'; -describe('SidebarContext', () => { - describe('SidebarContextProvider', () => { +describe('SidebarPinStateContext', () => { + describe('SidebarPinStateContextProvider', () => { it('should render children', async () => { - await renderInTestApp( + const { findByText } = await renderWithEffects( { Child , ); - expect(await screen.findByText('Child')).toBeInTheDocument(); + expect(await findByText('Child')).toBeInTheDocument(); + }); + + it('should provide the legacy context as well, for now', async () => { + const LegacyContextSpy = () => { + const { isMobile } = useContext(LegacySidebarPinStateContext); + return <>{String(isMobile)}; + }; + + const { findByText } = await renderWithEffects( + {}, + }} + > + + , + ); + + expect(await findByText('true')).toBeInTheDocument(); }); }); - describe('useSidebar', () => { + describe('useSidebarPinState', () => { it('does not need to be invoked within provider', () => { const { result } = renderHook(() => useSidebarPinState()); expect(result.current.isPinned).toBe(true); @@ -63,15 +85,18 @@ describe('SidebarContext', () => { {children} ); - const { result } = renderHook(() => useSidebarPinState(), { wrapper }); + const { result, rerender } = renderHook(() => useSidebarPinState(), { + wrapper, + }); expect(result.current.isPinned).toBe(true); act(() => { result.current.toggleSidebarPinState(); + rerender(); }); - waitFor(() => { + await waitFor(() => { expect(result.current.isPinned).toBe(false); }); }); diff --git a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx index 709973e064..ec38884890 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx @@ -17,7 +17,7 @@ import { createVersionedContext, createVersionedValueMap, } from '@backstage/version-bridge'; -import React, { ReactNode, useContext } from 'react'; +import React, { createContext, ReactNode, useContext } from 'react'; /** * Type of `SidebarPinStateContext` @@ -30,6 +30,21 @@ export type SidebarPinStateContextType = { isMobile?: boolean; }; +const defaultSidebarPinStateContext = { + isPinned: true, + toggleSidebarPinState: () => {}, + isMobile: false, +}; + +/** + * Contains the state on how the `Sidebar` is rendered + * + * @public @deprecated + * Use `` + `useSidebarPinState()` instead. + */ +export const LegacySidebarPinStateContext = + createContext(defaultSidebarPinStateContext); + const VersionedSidebarPinStateContext = createVersionedContext<{ 1: SidebarPinStateContextType; }>('sidebar-pin-state-context'); @@ -46,11 +61,13 @@ export const SidebarPinStateContextProvider = ({ children: ReactNode; value: SidebarPinStateContextType; }) => ( - - {children} - + + + {children} + + ); /** @@ -63,11 +80,7 @@ export const useSidebarPinState = (): SidebarPinStateContextType => { // Invoked from outside a SidebarPinStateContextProvider: default value. if (versionedSidebarContext === undefined) { - return { - isPinned: true, - toggleSidebarPinState: () => {}, - isMobile: false, - }; + return defaultSidebarPinStateContext; } const sidebarContext = versionedSidebarContext.atVersion(1); diff --git a/packages/core-components/src/layout/Sidebar/index.ts b/packages/core-components/src/layout/Sidebar/index.ts index e041ecc730..06681ded52 100644 --- a/packages/core-components/src/layout/Sidebar/index.ts +++ b/packages/core-components/src/layout/Sidebar/index.ts @@ -48,9 +48,14 @@ export { IntroCard, SidebarIntro } from './Intro'; export type { SidebarIntroClassKey } from './Intro'; export { SIDEBAR_INTRO_LOCAL_STORAGE, sidebarConfig } from './config'; export type { SidebarOptions, SubmenuOptions } from './config'; -export { SidebarContextProvider, useSidebar } from './SidebarContext'; +export { + LegacySidebarContext as SidebarContext, + SidebarContextProvider, + useSidebar, +} from './SidebarContext'; export type { SidebarContextType } from './SidebarContext'; export { + LegacySidebarPinStateContext as SidebarPinStateContext, SidebarPinStateContextProvider, useSidebarPinState, } from './SidebarPinStateContext'; From 37c8f8444c46b5ca442bd09f7f8685d4932244a3 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Fri, 20 May 2022 17:51:11 +0200 Subject: [PATCH 085/149] Better naming of providers, hooks, and types. Signed-off-by: Eric Peterson --- .changeset/give-that-wolf-a-banana.md | 7 ++- .changeset/right-one-at-the-wrong-time.md | 2 +- .changeset/up-in-space-man.md | 2 +- packages/app/src/components/Root/Root.tsx | 4 +- packages/core-components/api-report.md | 53 ++++++++++++------- .../src/layout/Sidebar/Bar.test.tsx | 6 +-- .../src/layout/Sidebar/Bar.tsx | 6 +-- .../src/layout/Sidebar/Intro.tsx | 4 +- .../src/layout/Sidebar/Items.tsx | 6 +-- .../src/layout/Sidebar/MobileSidebar.tsx | 6 +-- .../src/layout/Sidebar/Page.tsx | 6 +-- ...t.tsx => SidebarOpenStateContext.test.tsx} | 30 ++++++----- ...ontext.tsx => SidebarOpenStateContext.tsx} | 27 +++++++--- .../Sidebar/SidebarPinStateContext.test.tsx | 16 +++--- .../layout/Sidebar/SidebarPinStateContext.tsx | 22 ++++++-- .../src/layout/Sidebar/SidebarSubmenu.tsx | 4 +- .../src/layout/Sidebar/index.ts | 18 ++++--- .../packages/app/src/components/Root/Root.tsx | 4 +- .../src/components/Root/Root.tsx | 4 +- plugins/shortcuts/src/ShortcutItem.test.tsx | 6 +-- plugins/shortcuts/src/Shortcuts.test.tsx | 6 +-- .../General/UserSettingsPinToggle.test.tsx | 6 +-- 22 files changed, 144 insertions(+), 101 deletions(-) rename packages/core-components/src/layout/Sidebar/{SidebarContext.test.tsx => SidebarOpenStateContext.test.tsx} (78%) rename packages/core-components/src/layout/Sidebar/{SidebarContext.tsx => SidebarOpenStateContext.tsx} (79%) diff --git a/.changeset/give-that-wolf-a-banana.md b/.changeset/give-that-wolf-a-banana.md index 685ef7400a..a9e99782ad 100644 --- a/.changeset/give-that-wolf-a-banana.md +++ b/.changeset/give-that-wolf-a-banana.md @@ -2,7 +2,7 @@ '@backstage/create-app': patch --- -Use of `SidebarContext` has been deprecated and will be removed in a future release. Instead, `useSidebar()` should be used to consume the context and `` should be used to provide it. +Use of `SidebarContext` has been deprecated and will be removed in a future release. Instead, `useSidebarOpenState()` should be used to consume the context and `` should be used to provide it. To prepare your app, update `packages/app/src/components/Root/Root.tsx` as follows: @@ -14,16 +14,15 @@ import { SidebarDivider, // ... SidebarSpace, -+ useSidebar, ++ useSidebarOpenState, } from '@backstage/core-components'; // ... - const SidebarLogo = () => { const classes = useSidebarLogoStyles(); - const { isOpen } = useContext(SidebarContext); -+ const { isOpen } = useSidebar(); ++ const { isOpen } = useSidebarOpenState(); // ... }; diff --git a/.changeset/right-one-at-the-wrong-time.md b/.changeset/right-one-at-the-wrong-time.md index c7fee94a91..5ef8e42555 100644 --- a/.changeset/right-one-at-the-wrong-time.md +++ b/.changeset/right-one-at-the-wrong-time.md @@ -4,4 +4,4 @@ '@techdocs/cli': patch --- -Updated sidebar-related logic to use `` + `useSidebarPinState()` and/or `` + `useSidebar()` from `@backstage/core-components`. +Updated sidebar-related logic to use `` + `useSidebarPinState()` and/or `` + `useSidebarOpenState()` from `@backstage/core-components`. diff --git a/.changeset/up-in-space-man.md b/.changeset/up-in-space-man.md index f3aadfece8..3a4dc6c5d3 100644 --- a/.changeset/up-in-space-man.md +++ b/.changeset/up-in-space-man.md @@ -2,6 +2,6 @@ '@backstage/core-components': patch --- -The `SidebarPinStateContext` and `SidebarContext` have been deprecated and will be removed in a future release. Instead, use `` + `useSidebarPinState()` and/or `` + `useSidebar()`. +The `SidebarPinStateContext` and `SidebarContext` have been deprecated and will be removed in a future release. Instead, use `` + `useSidebarPinState()` and/or `` + `useSidebarOpenState()`. This was done to ensure that sidebar state can be shared successfully across components exported by different packages, regardless of what version of this package is resolved and installed for each individual package. diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index e90fe29b54..30da01841c 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -45,7 +45,7 @@ import { SidebarPage, SidebarScrollWrapper, SidebarSpace, - useSidebar, + useSidebarOpenState, } from '@backstage/core-components'; import { MyGroupsSidebarItem } from '@backstage/plugin-org'; import GroupIcon from '@material-ui/icons/People'; @@ -68,7 +68,7 @@ const useSidebarLogoStyles = makeStyles({ const SidebarLogo = () => { const classes = useSidebarLogoStyles(); - const { isOpen } = useSidebar(); + const { isOpen } = useSidebarOpenState(); return (
diff --git a/packages/core-components/api-report.md b/packages/core-components/api-report.md index 33db173fcf..911ef36867 100644 --- a/packages/core-components/api-report.md +++ b/packages/core-components/api-report.md @@ -905,16 +905,7 @@ export const sidebarConfig: { // @public @deprecated export const SidebarContext: React_2.Context; -// @public -export const SidebarContextProvider: ({ - children, - value, -}: { - children: ReactNode; - value: SidebarContextType; -}) => JSX.Element; - -// @public +// @public @deprecated export type SidebarContextType = { isOpen: boolean; setOpen: (open: boolean) => void; @@ -987,6 +978,21 @@ export type SidebarItemClassKey = | 'arrows' | 'selected'; +// @public +export type SidebarOpenState = { + isOpen: boolean; + setOpen: (open: boolean) => void; +}; + +// @public +export const SidebarOpenStateProvider: ({ + children, + value, +}: { + children: ReactNode; + value: SidebarOpenState; +}) => JSX.Element; + // @public (undocumented) export type SidebarOptions = { drawerWidthClosed?: number; @@ -1008,11 +1014,25 @@ export type SidebarPageProps = { children?: React_2.ReactNode; }; +// @public +export type SidebarPinState = { + isPinned: boolean; + toggleSidebarPinState: () => any; + isMobile?: boolean; +}; + // @public @deprecated export const SidebarPinStateContext: React_2.Context; +// @public @deprecated +export type SidebarPinStateContextType = { + isPinned: boolean; + toggleSidebarPinState: () => any; + isMobile?: boolean; +}; + // @public -export const SidebarPinStateContextProvider: ({ +export const SidebarPinStateProvider: ({ children, value, }: { @@ -1020,13 +1040,6 @@ export const SidebarPinStateContextProvider: ({ value: SidebarPinStateContextType; }) => JSX.Element; -// @public -export type SidebarPinStateContextType = { - isPinned: boolean; - toggleSidebarPinState: () => any; - isMobile?: boolean; -}; - // @public (undocumented) export type SidebarProps = { openDelayMs?: number; @@ -1461,10 +1474,10 @@ export class UserIdentity implements IdentityApi { } // @public -export const useSidebar: () => SidebarContextType; +export const useSidebarOpenState: () => SidebarOpenState; // @public -export const useSidebarPinState: () => SidebarPinStateContextType; +export const useSidebarPinState: () => SidebarPinState; // Warning: (ae-missing-release-tag) "useSupportConfig" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // diff --git a/packages/core-components/src/layout/Sidebar/Bar.test.tsx b/packages/core-components/src/layout/Sidebar/Bar.test.tsx index ec9c08a1eb..87e62f15e0 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.test.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.test.tsx @@ -27,14 +27,14 @@ import { SidebarExpandButton, SidebarItem, SidebarSearchField, - SidebarPinStateContextProvider, + SidebarPinStateProvider, SidebarSubmenu, SidebarSubmenuItem, } from '.'; async function renderScalableSidebar() { await renderInTestApp( - - , + , ); } diff --git a/packages/core-components/src/layout/Sidebar/Bar.tsx b/packages/core-components/src/layout/Sidebar/Bar.tsx index a315406b58..c86f60a0a2 100644 --- a/packages/core-components/src/layout/Sidebar/Bar.tsx +++ b/packages/core-components/src/layout/Sidebar/Bar.tsx @@ -32,7 +32,7 @@ import { } from './config'; import { BackstageTheme } from '@backstage/theme'; import { useContent } from './Page'; -import { SidebarContextProvider } from './SidebarContext'; +import { SidebarOpenStateProvider } from './SidebarOpenStateContext'; import { useSidebarPinState } from './SidebarPinStateContext'; import { MobileSidebar } from './MobileSidebar'; @@ -190,7 +190,7 @@ const DesktopSidebar = (props: DesktopSidebarProps) => { return (
-
+
); }; diff --git a/packages/core-components/src/layout/Sidebar/Intro.tsx b/packages/core-components/src/layout/Sidebar/Intro.tsx index 51d453b22d..05d580c960 100644 --- a/packages/core-components/src/layout/Sidebar/Intro.tsx +++ b/packages/core-components/src/layout/Sidebar/Intro.tsx @@ -28,7 +28,7 @@ import { SIDEBAR_INTRO_LOCAL_STORAGE, } from './config'; import { SidebarDivider } from './Items'; -import { useSidebar } from './SidebarContext'; +import { useSidebarOpenState } from './SidebarOpenStateContext'; /** @public */ export type SidebarIntroClassKey = @@ -151,7 +151,7 @@ const recentlyViewedIntroText = 'And your recently viewed plugins will pop up here!'; export function SidebarIntro(_props: {}) { - const { isOpen } = useSidebar(); + const { isOpen } = useSidebarOpenState(); const defaultValue = { starredItemsDismissed: false, recentlyViewedItemsDismissed: false, diff --git a/packages/core-components/src/layout/Sidebar/Items.tsx b/packages/core-components/src/layout/Sidebar/Items.tsx index 3d294a54a8..0413864084 100644 --- a/packages/core-components/src/layout/Sidebar/Items.tsx +++ b/packages/core-components/src/layout/Sidebar/Items.tsx @@ -61,7 +61,7 @@ import DoubleArrowLeft from './icons/DoubleArrowLeft'; import DoubleArrowRight from './icons/DoubleArrowRight'; import { isLocationMatch } from './utils'; import { Location } from 'history'; -import { useSidebar } from './SidebarContext'; +import { useSidebarOpenState } from './SidebarOpenStateContext'; /** @public */ export type SidebarItemClassKey = @@ -369,7 +369,7 @@ const SidebarItemBase = forwardRef((props, ref) => { // XXX (@koroeskohr): unsure this is optimal. But I just really didn't want to have the item component // depend on the current location, and at least have it being optionally forced to selected. // Still waiting on a Q answered to fine tune the implementation - const { isOpen } = useSidebar(); + const { isOpen } = useSidebarOpenState(); const divStyle = !isOpen && hasSubmenu ? { display: 'flex', marginLeft: '24px' } : {}; @@ -671,7 +671,7 @@ export const SidebarScrollWrapper = styled('div')(({ theme }) => { export const SidebarExpandButton = () => { const { sidebarConfig } = useContext(SidebarConfigContext); const classes = useMemoStyles(sidebarConfig); - const { isOpen, setOpen } = useSidebar(); + const { isOpen, setOpen } = useSidebarOpenState(); const isSmallScreen = useMediaQuery( theme => theme.breakpoints.down('md'), { noSsr: true }, diff --git a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx index 46f38d9a2c..b51e3cbc0d 100644 --- a/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx +++ b/packages/core-components/src/layout/Sidebar/MobileSidebar.tsx @@ -27,7 +27,7 @@ import MenuIcon from '@material-ui/icons/Menu'; import { orderBy } from 'lodash'; import React, { createContext, useEffect, useState, useContext } from 'react'; import { useLocation } from 'react-router'; -import { SidebarContextProvider } from './SidebarContext'; +import { SidebarOpenStateProvider } from './SidebarOpenStateContext'; import { SidebarGroup } from './SidebarGroup'; import { SidebarConfigContext, SidebarConfig } from './config'; @@ -208,7 +208,7 @@ export const MobileSidebar = (props: MobileSidebarProps) => { !sidebarGroups[selectedMenuItemIndex].props.to; return ( - {} }}> + {} }}> @@ -232,6 +232,6 @@ export const MobileSidebar = (props: MobileSidebarProps) => { {sidebarGroups} - +
); }; diff --git a/packages/core-components/src/layout/Sidebar/Page.tsx b/packages/core-components/src/layout/Sidebar/Page.tsx index 17cd5ddee1..bdf41b5dd7 100644 --- a/packages/core-components/src/layout/Sidebar/Page.tsx +++ b/packages/core-components/src/layout/Sidebar/Page.tsx @@ -29,7 +29,7 @@ import { SidebarConfigContext, SidebarConfig } from './config'; import { BackstageTheme } from '@backstage/theme'; import { LocalStorage } from './localStorage'; import useMediaQuery from '@material-ui/core/useMediaQuery'; -import { SidebarPinStateContextProvider } from './SidebarPinStateContext'; +import { SidebarPinStateProvider } from './SidebarPinStateContext'; export type SidebarPageClassKey = 'root'; @@ -114,7 +114,7 @@ export function SidebarPage(props: SidebarPageProps) { const classes = useStyles({ isPinned, sidebarConfig }); return ( -
{props.children}
-
+ ); } diff --git a/packages/core-components/src/layout/Sidebar/SidebarContext.test.tsx b/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.test.tsx similarity index 78% rename from packages/core-components/src/layout/Sidebar/SidebarContext.test.tsx rename to packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.test.tsx index 43a18851d7..4953e1341b 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarContext.test.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.test.tsx @@ -19,17 +19,17 @@ import { waitFor } from '@testing-library/react'; import { renderHook, act } from '@testing-library/react-hooks'; import { LegacySidebarContext, - SidebarContextProvider, - useSidebar, -} from './SidebarContext'; + SidebarOpenStateProvider, + useSidebarOpenState, +} from './SidebarOpenStateContext'; -describe('SidebarContext', () => { - describe('SidebarContextProvider', () => { +describe('SidebarOpenStateContext', () => { + describe('SidebarOpenStateProvider', () => { it('should render children', async () => { const { findByText } = await renderWithEffects( - {} }}> + {} }}> Child - , + , ); expect(await findByText('Child')).toBeInTheDocument(); }); @@ -41,23 +41,23 @@ describe('SidebarContext', () => { }; const { findByText } = await renderWithEffects( - {}, }} > - , + , ); expect(await findByText('true')).toBeInTheDocument(); }); }); - describe('useSidebar', () => { + describe('useSidebarOpenState', () => { it('does not need to be invoked within provider', () => { - const { result } = renderHook(() => useSidebar()); + const { result } = renderHook(() => useSidebarOpenState()); expect(result.current.isOpen).toBe(false); expect(typeof result.current.setOpen).toBe('function'); }); @@ -65,7 +65,7 @@ describe('SidebarContext', () => { it('should read and update state', async () => { let actualValue = true; const wrapper = ({ children }: { children: ReactNode }) => ( - { @@ -74,9 +74,11 @@ describe('SidebarContext', () => { }} > {children} - + ); - const { result, rerender } = renderHook(() => useSidebar(), { wrapper }); + const { result, rerender } = renderHook(() => useSidebarOpenState(), { + wrapper, + }); expect(result.current.isOpen).toBe(true); diff --git a/packages/core-components/src/layout/Sidebar/SidebarContext.tsx b/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.tsx similarity index 79% rename from packages/core-components/src/layout/Sidebar/SidebarContext.tsx rename to packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.tsx index 4e47b6040a..1ceb6d95cf 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarContext.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.tsx @@ -23,13 +23,24 @@ import { /** * Types for the `SidebarContext` * - * @public + * @public @deprecated + * Use `SidebarOpenState` instead. */ export type SidebarContextType = { isOpen: boolean; setOpen: (open: boolean) => void; }; +/** + * The open state of the sidebar. + * + * @public + */ +export type SidebarOpenState = { + isOpen: boolean; + setOpen: (open: boolean) => void; +}; + const defaultSidebarContext = { isOpen: false, setOpen: () => {}, @@ -46,20 +57,20 @@ export const LegacySidebarContext = createContext( ); const VersionedSidebarContext = createVersionedContext<{ - 1: SidebarContextType; -}>('sidebar-context'); + 1: SidebarOpenState; +}>('sidebar-open-state-context'); /** * Provides context for reading and updating sidebar state. * * @public */ -export const SidebarContextProvider = ({ +export const SidebarOpenStateProvider = ({ children, value, }: { children: ReactNode; - value: SidebarContextType; + value: SidebarOpenState; }) => ( { +export const useSidebarOpenState = (): SidebarOpenState => { const versionedSidebarContext = useContext(VersionedSidebarContext); - // Invoked from outside a SidbarContextProvider, return a default value. + // Invoked from outside a SidebarOpenStateProvider, return a default value. if (versionedSidebarContext === undefined) { return defaultSidebarContext; } diff --git a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx index d8bc054e4a..1a86ec612e 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx @@ -19,15 +19,15 @@ import { waitFor } from '@testing-library/react'; import { renderHook, act } from '@testing-library/react-hooks'; import { LegacySidebarPinStateContext, - SidebarPinStateContextProvider, + SidebarPinStateProvider, useSidebarPinState, } from './SidebarPinStateContext'; describe('SidebarPinStateContext', () => { - describe('SidebarPinStateContextProvider', () => { + describe('SidebarPinStateProvider', () => { it('should render children', async () => { const { findByText } = await renderWithEffects( - { }} > Child - , + , ); expect(await findByText('Child')).toBeInTheDocument(); }); @@ -47,7 +47,7 @@ describe('SidebarPinStateContext', () => { }; const { findByText } = await renderWithEffects( - { }} > - , + , ); expect(await findByText('true')).toBeInTheDocument(); @@ -73,7 +73,7 @@ describe('SidebarPinStateContext', () => { it('should read and update state', async () => { let actualValue = true; const wrapper = ({ children }: { children: ReactNode }) => ( - { }} > {children} - + ); const { result, rerender } = renderHook(() => useSidebarPinState(), { wrapper, diff --git a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx index ec38884890..0eb59dd119 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx @@ -22,7 +22,8 @@ import React, { createContext, ReactNode, useContext } from 'react'; /** * Type of `SidebarPinStateContext` * - * @public + * @public @deprecated + * Use `SidebarPinState` instead. */ export type SidebarPinStateContextType = { isPinned: boolean; @@ -30,6 +31,17 @@ export type SidebarPinStateContextType = { isMobile?: boolean; }; +/** + * The pin state of the sidebar. + * + * @public + */ +export type SidebarPinState = { + isPinned: boolean; + toggleSidebarPinState: () => any; + isMobile?: boolean; +}; + const defaultSidebarPinStateContext = { isPinned: true, toggleSidebarPinState: () => {}, @@ -46,7 +58,7 @@ export const LegacySidebarPinStateContext = createContext(defaultSidebarPinStateContext); const VersionedSidebarPinStateContext = createVersionedContext<{ - 1: SidebarPinStateContextType; + 1: SidebarPinState; }>('sidebar-pin-state-context'); /** @@ -54,7 +66,7 @@ const VersionedSidebarPinStateContext = createVersionedContext<{ * * @public */ -export const SidebarPinStateContextProvider = ({ +export const SidebarPinStateProvider = ({ children, value, }: { @@ -75,10 +87,10 @@ export const SidebarPinStateContextProvider = ({ * * @public */ -export const useSidebarPinState = (): SidebarPinStateContextType => { +export const useSidebarPinState = (): SidebarPinState => { const versionedSidebarContext = useContext(VersionedSidebarPinStateContext); - // Invoked from outside a SidebarPinStateContextProvider: default value. + // Invoked from outside a SidebarPinStateProvider: default value. if (versionedSidebarContext === undefined) { return defaultSidebarPinStateContext; } diff --git a/packages/core-components/src/layout/Sidebar/SidebarSubmenu.tsx b/packages/core-components/src/layout/Sidebar/SidebarSubmenu.tsx index d8a87c98d9..438e580ef4 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarSubmenu.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarSubmenu.tsx @@ -22,7 +22,7 @@ import { SidebarConfigContext, SubmenuConfig, } from './config'; -import { useSidebar } from './SidebarContext'; +import { useSidebarOpenState } from './SidebarOpenStateContext'; import { BackstageTheme } from '@backstage/theme'; const useStyles = makeStyles< @@ -105,7 +105,7 @@ export type SidebarSubmenuProps = { * @public */ export const SidebarSubmenu = (props: SidebarSubmenuProps) => { - const { isOpen } = useSidebar(); + const { isOpen } = useSidebarOpenState(); const { sidebarConfig, submenuConfig } = useContext(SidebarConfigContext); const left = isOpen ? sidebarConfig.drawerWidthOpen diff --git a/packages/core-components/src/layout/Sidebar/index.ts b/packages/core-components/src/layout/Sidebar/index.ts index 06681ded52..4c91ea6a8f 100644 --- a/packages/core-components/src/layout/Sidebar/index.ts +++ b/packages/core-components/src/layout/Sidebar/index.ts @@ -50,13 +50,19 @@ export { SIDEBAR_INTRO_LOCAL_STORAGE, sidebarConfig } from './config'; export type { SidebarOptions, SubmenuOptions } from './config'; export { LegacySidebarContext as SidebarContext, - SidebarContextProvider, - useSidebar, -} from './SidebarContext'; -export type { SidebarContextType } from './SidebarContext'; + SidebarOpenStateProvider, + useSidebarOpenState, +} from './SidebarOpenStateContext'; +export type { + SidebarContextType, + SidebarOpenState, +} from './SidebarOpenStateContext'; export { LegacySidebarPinStateContext as SidebarPinStateContext, - SidebarPinStateContextProvider, + SidebarPinStateProvider, useSidebarPinState, } from './SidebarPinStateContext'; -export type { SidebarPinStateContextType } from './SidebarPinStateContext'; +export type { + SidebarPinStateContextType, + SidebarPinState, +} from './SidebarPinStateContext'; diff --git a/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx b/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx index 05e11458ce..b1164a32f0 100644 --- a/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx +++ b/packages/create-app/templates/default-app/packages/app/src/components/Root/Root.tsx @@ -38,7 +38,7 @@ import { SidebarPage, SidebarScrollWrapper, SidebarSpace, - useSidebar, + useSidebarOpenState, } from '@backstage/core-components'; import MenuIcon from '@material-ui/icons/Menu'; import SearchIcon from '@material-ui/icons/Search'; @@ -60,7 +60,7 @@ const useSidebarLogoStyles = makeStyles({ const SidebarLogo = () => { const classes = useSidebarLogoStyles(); - const { isOpen } = useSidebar(); + const { isOpen } = useSidebarOpenState(); return (
diff --git a/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx b/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx index 6613e7c0ea..cd8b5d35ed 100644 --- a/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx +++ b/packages/techdocs-cli-embedded-app/src/components/Root/Root.tsx @@ -27,7 +27,7 @@ import { SidebarPage, sidebarConfig, SidebarDivider, - useSidebar, + useSidebarOpenState, } from '@backstage/core-components'; import { NavLink } from 'react-router-dom'; @@ -48,7 +48,7 @@ const useSidebarLogoStyles = makeStyles({ const SidebarLogo = () => { const classes = useSidebarLogoStyles(); - const { isOpen } = useSidebar(); + const { isOpen } = useSidebarOpenState(); return (
diff --git a/plugins/shortcuts/src/ShortcutItem.test.tsx b/plugins/shortcuts/src/ShortcutItem.test.tsx index d1a1018513..b98dd2dc0b 100644 --- a/plugins/shortcuts/src/ShortcutItem.test.tsx +++ b/plugins/shortcuts/src/ShortcutItem.test.tsx @@ -20,7 +20,7 @@ import { ShortcutItem } from './ShortcutItem'; import { Shortcut } from './types'; import { LocalStoredShortcuts } from './api'; import { MockStorageApi, renderInTestApp } from '@backstage/test-utils'; -import { SidebarContextProvider } from '@backstage/core-components'; +import { SidebarOpenStateProvider } from '@backstage/core-components'; describe('ShortcutItem', () => { const shortcut: Shortcut = { @@ -32,9 +32,9 @@ describe('ShortcutItem', () => { it('displays the shortcut', async () => { await renderInTestApp( - {} }}> + {} }}> - , + , ); expect(screen.getByText('ST')).toBeInTheDocument(); expect(screen.getByText('some title')).toBeInTheDocument(); diff --git a/plugins/shortcuts/src/Shortcuts.test.tsx b/plugins/shortcuts/src/Shortcuts.test.tsx index 813dbb8def..02b351d3a4 100644 --- a/plugins/shortcuts/src/Shortcuts.test.tsx +++ b/plugins/shortcuts/src/Shortcuts.test.tsx @@ -24,12 +24,12 @@ import { screen, waitFor } from '@testing-library/react'; import { Shortcuts } from './Shortcuts'; import { LocalStoredShortcuts, shortcutsApiRef } from './api'; -import { SidebarContextProvider } from '@backstage/core-components'; +import { SidebarOpenStateProvider } from '@backstage/core-components'; describe('Shortcuts', () => { it('displays an add button', async () => { await renderInTestApp( - {} }}> + {} }}> { > - , + , ); await waitFor(() => !screen.queryByTestId('progress')); expect(screen.getByText('Add Shortcuts')).toBeInTheDocument(); diff --git a/plugins/user-settings/src/components/General/UserSettingsPinToggle.test.tsx b/plugins/user-settings/src/components/General/UserSettingsPinToggle.test.tsx index a41792768a..ef8046a54e 100644 --- a/plugins/user-settings/src/components/General/UserSettingsPinToggle.test.tsx +++ b/plugins/user-settings/src/components/General/UserSettingsPinToggle.test.tsx @@ -18,14 +18,14 @@ import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils'; import { fireEvent } from '@testing-library/react'; import React from 'react'; import { UserSettingsPinToggle } from './UserSettingsPinToggle'; -import { SidebarPinStateContextProvider } from '@backstage/core-components'; +import { SidebarPinStateProvider } from '@backstage/core-components'; describe('', () => { it('toggles the pin sidebar button', async () => { const mockToggleFn = jest.fn(); const rendered = await renderWithEffects( wrapInTestApp( - ', () => { }} > - , + , ), ); expect(rendered.getByText('Pin Sidebar')).toBeInTheDocument(); From 58a957f4fc6faafc47e8f890e7aaad3b0ea1ff73 Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 23 May 2022 14:37:39 +0200 Subject: [PATCH 086/149] Better document hooks, props, etc. Signed-off-by: Eric Peterson --- .../layout/Sidebar/SidebarOpenStateContext.tsx | 12 +++++++++++- .../layout/Sidebar/SidebarPinStateContext.tsx | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.tsx b/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.tsx index 1ceb6d95cf..be15785f29 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.tsx @@ -37,7 +37,16 @@ export type SidebarContextType = { * @public */ export type SidebarOpenState = { + /** + * Whether or not the sidebar is open and full-width. When `false`, the + * sidebar is "closed" and typically only shows icons with no text. + */ isOpen: boolean; + + /** + * A function to set whether or not the sidebar is open. Pass `true` to open + * the sidebar. Pass `false` to close it. + */ setOpen: (open: boolean) => void; }; @@ -82,7 +91,8 @@ export const SidebarOpenStateProvider = ({ ); /** - * Hook to read and update the sidebar's open state. + * Hook to read and update the sidebar's open state, which controls whether or + * not the sidebar is open and full-width, or closed and only displaying icons. * * @public */ diff --git a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx index 0eb59dd119..a85929ab70 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx @@ -37,8 +37,22 @@ export type SidebarPinStateContextType = { * @public */ export type SidebarPinState = { + /** + * Whether or not the sidebar is pinned to the `open` state. When `isPinned` + * is `false`, the sidebar opens and closes on hover. When `true`, the + * sidebar is permanently opened, regardless of user interaction. + */ isPinned: boolean; + + /** + * A function to toggle the pin state of the sidebar. + */ toggleSidebarPinState: () => any; + + /** + * Whether or not the sidebar is or should be rendered in a mobile-optimized + * way. + */ isMobile?: boolean; }; @@ -83,7 +97,8 @@ export const SidebarPinStateProvider = ({ ); /** - * Hook to read and update sidebar pin state. + * Hook to read and update sidebar pin state, which controls whether or not the + * sidebar is pinned open. * * @public */ From bd58365d094660d35474408bb254eef72f7301be Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 24 May 2022 04:02:24 +0000 Subject: [PATCH 087/149] fix(deps): update dependency run-script-webpack-plugin to ^0.0.14 Signed-off-by: Renovate Bot --- .changeset/renovate-126b147.md | 5 +++++ packages/cli/package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 .changeset/renovate-126b147.md diff --git a/.changeset/renovate-126b147.md b/.changeset/renovate-126b147.md new file mode 100644 index 0000000000..c087911b84 --- /dev/null +++ b/.changeset/renovate-126b147.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Updated dependency `run-script-webpack-plugin` to `^0.0.14`. diff --git a/packages/cli/package.json b/packages/cli/package.json index e36a59b8e2..2d92caa558 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -110,7 +110,7 @@ "rollup-plugin-esbuild": "^4.7.2", "rollup-plugin-postcss": "^4.0.0", "rollup-pluginutils": "^2.8.2", - "run-script-webpack-plugin": "^0.0.11", + "run-script-webpack-plugin": "^0.0.14", "semver": "^7.3.2", "style-loader": "^3.3.1", "sucrase": "^3.20.2", diff --git a/yarn.lock b/yarn.lock index 09207cd6bc..d2ba7151bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -21848,10 +21848,10 @@ run-parallel@^1.1.9: resolved "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz#c9dd3a7cf9f4b2c4b6244e173a6ed866e61dd679" integrity sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q== -run-script-webpack-plugin@^0.0.11: - version "0.0.11" - resolved "https://registry.npmjs.org/run-script-webpack-plugin/-/run-script-webpack-plugin-0.0.11.tgz#04c510bed06b907fa2285e75feece71a25691171" - integrity sha512-QmuBhiqBPmhQLpO5vMBHVTAGyoPBnrCM5gQ3IzgieiImBXiBbXcIv4kysCT1gilFNFxQk22oKQfiIhWbT/zXCw== +run-script-webpack-plugin@^0.0.14: + version "0.0.14" + resolved "https://registry.npmjs.org/run-script-webpack-plugin/-/run-script-webpack-plugin-0.0.14.tgz#fe2362b32c1dab7a8af7a6f1246fc043690cedd7" + integrity sha512-DXe6lzzEVXjBr/74zd4m4yOfmz5P6GMjzhQxDDsViOmwG7cap8UCE6RgD5rT7zf4wM83a+ToHnpB3v4efUv5IA== rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.6.0, rxjs@^6.6.3: version "6.6.7" From 99d2a0f3ad4ef2c2e9da37af2bdc1f8580e143ac Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 24 May 2022 11:29:44 +0200 Subject: [PATCH 088/149] yarn.lock: bump gRPC deps Signed-off-by: Patrik Oldsberg --- yarn.lock | 58 +++++++++++++++++++++++++++---------------------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/yarn.lock b/yarn.lock index 09207cd6bc..3788fddf65 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2419,24 +2419,24 @@ "@repeaterjs/repeater" "^3.0.4" tslib "^2.3.1" -"@grpc/grpc-js@~1.4.0": - version "1.4.6" - resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.4.6.tgz#8108d7ab7c0c21b38c538c1a48583edbbf2c2412" - integrity sha512-Byau4xiXfIixb1PnW30V/P9mkrZ05lknyNqiK+cVY9J5hj3gecxd/anwaUbAM8j834zg1x78NvAbwGnMfWEu7A== +"@grpc/grpc-js@~1.6.0": + version "1.6.7" + resolved "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.6.7.tgz#4c4fa998ff719fe859ac19fe977fdef097bb99aa" + integrity sha512-eBM03pu9hd3VqDQG+kHahiG1x80RGkkqqRb1Pchcwqej/KkAH95gAvKs6laqaHCycYaPK+TKuNQnOz9UXYA8qw== dependencies: "@grpc/proto-loader" "^0.6.4" "@types/node" ">=12.12.47" -"@grpc/proto-loader@^0.6.1", "@grpc/proto-loader@^0.6.4": - version "0.6.7" - resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.7.tgz#e62a202f4cf5897bdd0e244dec1dbc80d84bdfa1" - integrity sha512-QzTPIyJxU0u+r2qGe8VMl3j/W2ryhEvBv7hc42OjYfthSj370fUrb7na65rG6w3YLZS/fb8p89iTBobfWGDgdw== +"@grpc/proto-loader@^0.6.12", "@grpc/proto-loader@^0.6.4": + version "0.6.12" + resolved "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.6.12.tgz#459b619b8b9b67794bf0d1cb819653a38c63e164" + integrity sha512-filTVbETFnxb9CyRX98zN18ilChTuf/C5scZ2xyaOTp0EHGq0/ufX8rjqXUcSb1Gpv7eZq4M2jDvbh9BogKnrg== dependencies: "@types/long" "^4.0.1" lodash.camelcase "^4.3.0" long "^4.0.0" protobufjs "^6.10.0" - yargs "^16.1.1" + yargs "^16.2.0" "@hapi/hoek@^9.0.0": version "9.0.4" @@ -13113,7 +13113,7 @@ globby@^7.1.1: pify "^3.0.0" slash "^1.0.0" -google-auth-library@^7.14.1, google-auth-library@^7.6.1: +google-auth-library@^7.14.0, google-auth-library@^7.14.1, google-auth-library@^7.6.1: version "7.14.1" resolved "https://registry.npmjs.org/google-auth-library/-/google-auth-library-7.14.1.tgz#e3483034162f24cc71b95c8a55a210008826213c" integrity sha512-5Rk7iLNDFhFeBYc3s8l1CqzbEBcdhwR193RlD4vSNFajIcINKI8W8P0JLmBpwymHqqWbX34pJDQu39cSy/6RsA== @@ -13129,22 +13129,22 @@ google-auth-library@^7.14.1, google-auth-library@^7.6.1: lru-cache "^6.0.0" google-gax@^2.24.1: - version "2.28.1" - resolved "https://registry.npmjs.org/google-gax/-/google-gax-2.28.1.tgz#99bc234b5769d901d70959d40bd1651729eb4a34" - integrity sha512-2Xjd3FrjlVd6Cmw2B2Aicpc/q92SwTpIOvxPUlnRg9w+Do8nu7UR+eQrgoKlo2FIUcUuDTvppvcx8toND0pK9g== + version "2.30.5" + resolved "https://registry.npmjs.org/google-gax/-/google-gax-2.30.5.tgz#e836f984f3228900a8336f608c83d75f9cb73eff" + integrity sha512-Jey13YrAN2hfpozHzbtrwEfEHdStJh1GwaQ2+Akh1k0Tv/EuNVSuBtHZoKSBm5wBMvNsxTsEIZ/152NrYyZgxQ== dependencies: - "@grpc/grpc-js" "~1.4.0" - "@grpc/proto-loader" "^0.6.1" + "@grpc/grpc-js" "~1.6.0" + "@grpc/proto-loader" "^0.6.12" "@types/long" "^4.0.0" abort-controller "^3.0.0" duplexify "^4.0.0" fast-text-encoding "^1.0.3" - google-auth-library "^7.6.1" + google-auth-library "^7.14.0" is-stream-ended "^0.1.4" node-fetch "^2.6.1" - object-hash "^2.1.1" - proto3-json-serializer "^0.1.5" - protobufjs "6.11.2" + object-hash "^3.0.0" + proto3-json-serializer "^0.1.8" + protobufjs "6.11.3" retry-request "^4.0.0" google-p12-pem@^3.0.3: @@ -18529,7 +18529,7 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-hash@^2.0.1, object-hash@^2.1.1: +object-hash@^2.0.1: version "2.2.0" resolved "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== @@ -20273,17 +20273,17 @@ proto-list@~1.2.1: resolved "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk= -proto3-json-serializer@^0.1.5: - version "0.1.6" - resolved "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.6.tgz#67cf3b8d5f4c8bebfc410698ad3b1ed64da39c7b" - integrity sha512-tGbV6m6Kad8NqxMh5hw87euPS0YoZSAOIfvR01zYkQV8Gpx1V/8yU/0gCKCvfCkhAJsjvzzhnnsdQxA1w7PSog== +proto3-json-serializer@^0.1.8: + version "0.1.9" + resolved "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-0.1.9.tgz#705ddb41b009dd3e6fcd8123edd72926abf65a34" + integrity sha512-A60IisqvnuI45qNRygJjrnNjX2TMdQGMY+57tR3nul3ZgO2zXkR9OGR8AXxJhkqx84g0FTnrfi3D5fWMSdANdQ== dependencies: protobufjs "^6.11.2" -protobufjs@6.11.2, protobufjs@^6.10.0, protobufjs@^6.11.2, protobufjs@^6.8.6: - version "6.11.2" - resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.2.tgz#de39fabd4ed32beaa08e9bb1e30d08544c1edf8b" - integrity sha512-4BQJoPooKJl2G9j3XftkIXjoC9C0Av2NOrWmbLWT1vH32GcSUHjM0Arra6UfTsVyfMAuFzaLucXn1sadxJydAw== +protobufjs@6.11.3, protobufjs@^6.10.0, protobufjs@^6.11.2, protobufjs@^6.8.6: + version "6.11.3" + resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz#637a527205a35caa4f3e2a9a4a13ddffe0e7af74" + integrity sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg== dependencies: "@protobufjs/aspromise" "^1.1.2" "@protobufjs/base64" "^1.1.2" @@ -25449,7 +25449,7 @@ yargs@^15.1.0, yargs@^15.3.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^16.1.1, yargs@^16.2.0: +yargs@^16.2.0: version "16.2.0" resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== From bcc563abe97aa9b3ffa4e4bcdd578fe98917f4dd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 24 May 2022 11:05:52 +0000 Subject: [PATCH 089/149] Version Packages (next) --- .changeset/pre.json | 45 +- docs/releases/v1.3.0-next.0-changelog.md | 1297 +++++++++++++++++ package.json | 2 +- packages/app-defaults/CHANGELOG.md | 7 + packages/app-defaults/package.json | 6 +- packages/app/CHANGELOG.md | 51 + packages/app/package.json | 92 +- packages/backend-common/CHANGELOG.md | 9 + packages/backend-common/package.json | 8 +- packages/backend-tasks/CHANGELOG.md | 8 + packages/backend-tasks/package.json | 8 +- packages/backend-test-utils/CHANGELOG.md | 8 + packages/backend-test-utils/package.json | 8 +- packages/backend/CHANGELOG.md | 36 + packages/backend/package.json | 62 +- packages/catalog-client/package.json | 2 +- packages/catalog-model/package.json | 2 +- packages/cli/CHANGELOG.md | 7 + packages/cli/package.json | 8 +- packages/core-app-api/package.json | 2 +- packages/core-components/CHANGELOG.md | 11 + packages/core-components/package.json | 4 +- packages/core-plugin-api/package.json | 2 +- packages/create-app/CHANGELOG.md | 75 + packages/create-app/package.json | 2 +- packages/dev-utils/CHANGELOG.md | 10 + packages/dev-utils/package.json | 12 +- packages/errors/package.json | 2 +- packages/integration-react/CHANGELOG.md | 8 + packages/integration-react/package.json | 10 +- packages/integration/CHANGELOG.md | 6 + packages/integration/package.json | 4 +- .../techdocs-cli-embedded-app/CHANGELOG.md | 13 + .../techdocs-cli-embedded-app/package.json | 18 +- packages/techdocs-cli/CHANGELOG.md | 8 + packages/techdocs-cli/package.json | 8 +- packages/techdocs-common/CHANGELOG.md | 7 + packages/techdocs-common/package.json | 4 +- packages/test-utils/package.json | 2 +- packages/theme/package.json | 2 +- packages/types/package.json | 2 +- packages/version-bridge/package.json | 2 +- plugins/adr-backend/CHANGELOG.md | 9 + plugins/adr-backend/package.json | 10 +- plugins/adr-common/CHANGELOG.md | 7 + plugins/adr-common/package.json | 6 +- plugins/adr/CHANGELOG.md | 11 + plugins/adr/package.json | 14 +- plugins/airbrake-backend/CHANGELOG.md | 7 + plugins/airbrake-backend/package.json | 6 +- plugins/airbrake/CHANGELOG.md | 9 + plugins/airbrake/package.json | 14 +- plugins/allure/CHANGELOG.md | 9 + plugins/allure/package.json | 10 +- plugins/analytics-module-ga/CHANGELOG.md | 7 + plugins/analytics-module-ga/package.json | 8 +- plugins/apache-airflow/CHANGELOG.md | 7 + plugins/apache-airflow/package.json | 8 +- plugins/api-docs/CHANGELOG.md | 9 + plugins/api-docs/package.json | 12 +- plugins/app-backend/CHANGELOG.md | 7 + plugins/app-backend/package.json | 8 +- plugins/auth-backend/CHANGELOG.md | 9 + plugins/auth-backend/package.json | 10 +- plugins/auth-node/CHANGELOG.md | 8 + plugins/auth-node/package.json | 6 +- plugins/azure-devops-backend/CHANGELOG.md | 7 + plugins/azure-devops-backend/package.json | 6 +- plugins/azure-devops-common/package.json | 2 +- plugins/azure-devops/CHANGELOG.md | 8 + plugins/azure-devops/package.json | 10 +- plugins/badges-backend/CHANGELOG.md | 7 + plugins/badges-backend/package.json | 6 +- plugins/badges/CHANGELOG.md | 8 + plugins/badges/package.json | 10 +- plugins/bazaar-backend/CHANGELOG.md | 8 + plugins/bazaar-backend/package.json | 8 +- plugins/bazaar/CHANGELOG.md | 10 + plugins/bazaar/package.json | 14 +- plugins/bitrise/CHANGELOG.md | 8 + plugins/bitrise/package.json | 10 +- .../catalog-backend-module-aws/CHANGELOG.md | 11 + .../catalog-backend-module-aws/package.json | 12 +- .../catalog-backend-module-azure/CHANGELOG.md | 9 + .../catalog-backend-module-azure/package.json | 12 +- .../CHANGELOG.md | 9 + .../package.json | 12 +- .../CHANGELOG.md | 11 + .../package.json | 14 +- .../CHANGELOG.md | 10 + .../package.json | 14 +- .../CHANGELOG.md | 20 + .../package.json | 12 +- .../catalog-backend-module-ldap/CHANGELOG.md | 12 + .../catalog-backend-module-ldap/package.json | 8 +- .../CHANGELOG.md | 8 + .../package.json | 12 +- plugins/catalog-backend/CHANGELOG.md | 23 + plugins/catalog-backend/package.json | 14 +- plugins/catalog-common/package.json | 2 +- plugins/catalog-graph/CHANGELOG.md | 8 + plugins/catalog-graph/package.json | 12 +- plugins/catalog-graphql/package.json | 2 +- plugins/catalog-import/CHANGELOG.md | 10 + plugins/catalog-import/package.json | 14 +- plugins/catalog-react/CHANGELOG.md | 13 + plugins/catalog-react/package.json | 8 +- plugins/catalog/CHANGELOG.md | 14 + plugins/catalog/package.json | 12 +- .../CHANGELOG.md | 7 + .../package.json | 6 +- plugins/cicd-statistics/CHANGELOG.md | 7 + plugins/cicd-statistics/package.json | 4 +- plugins/circleci/CHANGELOG.md | 8 + plugins/circleci/package.json | 10 +- plugins/cloudbuild/CHANGELOG.md | 8 + plugins/cloudbuild/package.json | 10 +- plugins/code-climate/CHANGELOG.md | 8 + plugins/code-climate/package.json | 10 +- plugins/code-coverage-backend/CHANGELOG.md | 8 + plugins/code-coverage-backend/package.json | 8 +- plugins/code-coverage/CHANGELOG.md | 8 + plugins/code-coverage/package.json | 10 +- plugins/codescene/CHANGELOG.md | 7 + plugins/codescene/package.json | 8 +- plugins/config-schema/CHANGELOG.md | 7 + plugins/config-schema/package.json | 8 +- plugins/cost-insights/CHANGELOG.md | 8 + plugins/cost-insights/package.json | 8 +- .../example-todo-list-backend/CHANGELOG.md | 8 + .../example-todo-list-backend/package.json | 8 +- plugins/example-todo-list-common/package.json | 4 +- plugins/example-todo-list/CHANGELOG.md | 7 + plugins/example-todo-list/package.json | 8 +- plugins/explore-react/package.json | 4 +- plugins/explore/CHANGELOG.md | 8 + plugins/explore/package.json | 10 +- plugins/firehydrant/CHANGELOG.md | 8 + plugins/firehydrant/package.json | 10 +- plugins/fossa/CHANGELOG.md | 8 + plugins/fossa/package.json | 10 +- plugins/gcalendar/CHANGELOG.md | 7 + plugins/gcalendar/package.json | 8 +- plugins/gcp-projects/CHANGELOG.md | 8 + plugins/gcp-projects/package.json | 8 +- plugins/git-release-manager/CHANGELOG.md | 8 + plugins/git-release-manager/package.json | 10 +- plugins/github-actions/CHANGELOG.md | 9 + plugins/github-actions/package.json | 12 +- plugins/github-deployments/CHANGELOG.md | 10 + plugins/github-deployments/package.json | 14 +- plugins/gitops-profiles/CHANGELOG.md | 7 + plugins/gitops-profiles/package.json | 8 +- plugins/gocd/CHANGELOG.md | 8 + plugins/gocd/package.json | 10 +- plugins/graphiql/CHANGELOG.md | 7 + plugins/graphiql/package.json | 8 +- plugins/graphql-backend/CHANGELOG.md | 7 + plugins/graphql-backend/package.json | 6 +- plugins/home/CHANGELOG.md | 9 + plugins/home/package.json | 12 +- plugins/ilert/CHANGELOG.md | 8 + plugins/ilert/package.json | 10 +- plugins/jenkins-backend/CHANGELOG.md | 8 + plugins/jenkins-backend/package.json | 8 +- plugins/jenkins-common/package.json | 2 +- plugins/jenkins/CHANGELOG.md | 8 + plugins/jenkins/package.json | 10 +- plugins/kafka-backend/CHANGELOG.md | 7 + plugins/kafka-backend/package.json | 6 +- plugins/kafka/CHANGELOG.md | 8 + plugins/kafka/package.json | 10 +- plugins/kubernetes-backend/CHANGELOG.md | 13 + plugins/kubernetes-backend/package.json | 8 +- plugins/kubernetes-common/CHANGELOG.md | 6 + plugins/kubernetes-common/package.json | 4 +- plugins/kubernetes/CHANGELOG.md | 11 + plugins/kubernetes/package.json | 12 +- plugins/lighthouse/CHANGELOG.md | 8 + plugins/lighthouse/package.json | 10 +- plugins/newrelic-dashboard/CHANGELOG.md | 8 + plugins/newrelic-dashboard/package.json | 10 +- plugins/newrelic/CHANGELOG.md | 7 + plugins/newrelic/package.json | 8 +- plugins/org/CHANGELOG.md | 8 + plugins/org/package.json | 10 +- plugins/pagerduty/CHANGELOG.md | 9 + plugins/pagerduty/package.json | 10 +- plugins/periskop-backend/CHANGELOG.md | 7 + plugins/periskop-backend/package.json | 6 +- plugins/periskop/CHANGELOG.md | 8 + plugins/periskop/package.json | 10 +- plugins/permission-backend/CHANGELOG.md | 9 + plugins/permission-backend/package.json | 10 +- plugins/permission-common/package.json | 2 +- plugins/permission-node/CHANGELOG.md | 8 + plugins/permission-node/package.json | 8 +- plugins/permission-react/package.json | 2 +- plugins/proxy-backend/CHANGELOG.md | 7 + plugins/proxy-backend/package.json | 6 +- plugins/rollbar-backend/CHANGELOG.md | 7 + plugins/rollbar-backend/package.json | 8 +- plugins/rollbar/CHANGELOG.md | 8 + plugins/rollbar/package.json | 10 +- .../CHANGELOG.md | 9 + .../package.json | 10 +- .../CHANGELOG.md | 9 + .../package.json | 10 +- .../CHANGELOG.md | 7 + .../package.json | 6 +- plugins/scaffolder-backend/CHANGELOG.md | 14 + plugins/scaffolder-backend/package.json | 12 +- plugins/scaffolder-common/package.json | 2 +- plugins/scaffolder/CHANGELOG.md | 32 + plugins/scaffolder/package.json | 16 +- .../CHANGELOG.md | 7 + .../package.json | 8 +- plugins/search-backend-module-pg/CHANGELOG.md | 8 + plugins/search-backend-module-pg/package.json | 10 +- plugins/search-backend-node/CHANGELOG.md | 8 + plugins/search-backend-node/package.json | 8 +- plugins/search-backend/CHANGELOG.md | 10 + plugins/search-backend/package.json | 12 +- plugins/search-common/package.json | 2 +- plugins/search/CHANGELOG.md | 8 + plugins/search/package.json | 10 +- plugins/sentry/CHANGELOG.md | 8 + plugins/sentry/package.json | 10 +- plugins/shortcuts/CHANGELOG.md | 7 + plugins/shortcuts/package.json | 8 +- plugins/sonarqube/CHANGELOG.md | 8 + plugins/sonarqube/package.json | 10 +- plugins/splunk-on-call/CHANGELOG.md | 8 + plugins/splunk-on-call/package.json | 10 +- plugins/stack-overflow/CHANGELOG.md | 8 + plugins/stack-overflow/package.json | 10 +- .../CHANGELOG.md | 8 + .../package.json | 8 +- plugins/tech-insights-backend/CHANGELOG.md | 9 + plugins/tech-insights-backend/package.json | 12 +- plugins/tech-insights-common/package.json | 2 +- plugins/tech-insights-node/CHANGELOG.md | 7 + plugins/tech-insights-node/package.json | 6 +- plugins/tech-insights/CHANGELOG.md | 8 + plugins/tech-insights/package.json | 10 +- plugins/tech-radar/CHANGELOG.md | 7 + plugins/tech-radar/package.json | 8 +- .../techdocs-addons-test-utils/CHANGELOG.md | 11 + .../techdocs-addons-test-utils/package.json | 16 +- plugins/techdocs-backend/CHANGELOG.md | 70 + plugins/techdocs-backend/package.json | 14 +- .../CHANGELOG.md | 11 + .../package.json | 16 +- plugins/techdocs-node/CHANGELOG.md | 8 + plugins/techdocs-node/package.json | 8 +- plugins/techdocs-react/CHANGELOG.md | 12 + plugins/techdocs-react/package.json | 4 +- plugins/techdocs/CHANGELOG.md | 26 + plugins/techdocs/package.json | 16 +- plugins/todo-backend/CHANGELOG.md | 8 + plugins/todo-backend/package.json | 8 +- plugins/todo/CHANGELOG.md | 8 + plugins/todo/package.json | 10 +- plugins/user-settings/CHANGELOG.md | 7 + plugins/user-settings/package.json | 8 +- plugins/xcmetrics/CHANGELOG.md | 7 + plugins/xcmetrics/package.json | 8 +- yarn.lock | 257 +++- 268 files changed, 3502 insertions(+), 722 deletions(-) create mode 100644 docs/releases/v1.3.0-next.0-changelog.md diff --git a/.changeset/pre.json b/.changeset/pre.json index aff34f172d..9856189d3e 100644 --- a/.changeset/pre.json +++ b/.changeset/pre.json @@ -154,5 +154,48 @@ "@backstage/plugin-user-settings": "0.4.4", "@backstage/plugin-xcmetrics": "0.2.25" }, - "changesets": [] + "changesets": [ + "afraid-mangos-sip", + "beige-deers-remember", + "blue-roses-give", + "fair-grapes-joke", + "fluffy-candles-learn", + "fresh-items-punch", + "funny-suns-pay", + "gold-tables-matter", + "healthy-pets-mix", + "hungry-brooms-wash", + "itchy-avocados-hug", + "loud-jars-kick", + "loud-walls-itch", + "mean-turtles-reply", + "nervous-gorillas-approve", + "odd-baboons-buy", + "olive-rats-rest", + "plenty-garlics-shop", + "polite-spiders-pay", + "poor-years-develop", + "quick-ladybugs-try", + "real-beers-type", + "reject-failed-index-tasks", + "renovate-ad175cc", + "scaffolder-form-context", + "shiny-clocks-joke", + "short-jokes-applaud", + "silly-wombats-flash", + "sixty-plums-kick", + "slimy-elephants-attend", + "spotty-goats-look", + "tasty-snails-boil", + "techdocs-buttons-film", + "techdocs-crabs-retire", + "techdocs-paws-study", + "techdocs-swans-check", + "techdocs-vans-run", + "techdocs-ways-type", + "techdocs-wolves-carry", + "ten-rocks-smile", + "unlucky-lies-pretend", + "wicked-teachers-hide" + ] } diff --git a/docs/releases/v1.3.0-next.0-changelog.md b/docs/releases/v1.3.0-next.0-changelog.md new file mode 100644 index 0000000000..412af14841 --- /dev/null +++ b/docs/releases/v1.3.0-next.0-changelog.md @@ -0,0 +1,1297 @@ +# Release v1.3.0-next.0 + +## @backstage/plugin-catalog-backend@1.2.0-next.0 + +### Minor Changes + +- b594679ae3: Allow array as non-spread arguments at the `CatalogBuilder`. + + ```typescript + builder.addEntityProvider(...getArrayOfProviders()); + ``` + + can be simplified to + + ```typescript + builder.addEntityProvider(getArrayOfProviders()); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-permission-node@0.6.2-next.0 + +## @backstage/plugin-catalog-backend-module-ldap@0.5.0-next.0 + +### Minor Changes + +- 1f83f0bc84: Added the possibility to pass TLS configuration to ldap connection + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + +## @backstage/plugin-kubernetes-backend@0.6.0-next.0 + +### Minor Changes + +- 4328737af6: Add support to fetch data for Stateful Sets from Kubernetes + +### Patch Changes + +- 0c70cd8e1d: cache and refresh Azure tokens to avoid excessive calls to Azure Identity +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-kubernetes-common@0.3.0-next.0 + +## @backstage/plugin-kubernetes-common@0.3.0-next.0 + +### Minor Changes + +- 4328737af6: Add support to fetch data for Stateful Sets + +## @backstage/plugin-scaffolder@1.3.0-next.0 + +### Minor Changes + +- 86a4a0f72d: Get data of other fields in Form from a custom field in template Scaffolder. + following: + + ```tsx + const CustomFieldExtensionComponent = (props: FieldExtensionComponentProps) => { + const { formData } = props.formContext; + ... + }; + + const CustomFieldExtension = scaffolderPlugin.provide( + createScaffolderFieldExtension({ + name: ..., + component: CustomFieldExtensionComponent, + validation: ... + }) + ); + ``` + +- 72dfcbc8bf: Gerrit Integration: Implemented a `RepoUrlPicker` for Gerrit. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/integration-react@1.1.1-next.0 + +## @backstage/plugin-scaffolder-backend@1.3.0-next.0 + +### Minor Changes + +- 72dfcbc8bf: A new scaffolder action has been added: `gerrit:publish` + +### Patch Changes + +- 6901f6be4a: Adds more of an explanation when the `publish:github` scaffolder action fails to create a repository. +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + +## @backstage/app-defaults@1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/backend-common@0.13.6-next.0 + +### Patch Changes + +- f72a6b8c62: Applied the `luxon` dependency fix from the `0.13.4` patch release. +- 5b22a8c97f: Applied the AWS S3 reading patch from the `0.13.5` patch release. +- Updated dependencies + - @backstage/integration@1.2.1-next.0 + +## @backstage/backend-tasks@0.3.2-next.0 + +### Patch Changes + +- fde10d24f6: Allow tasks that fail to retry on a loop emitting a warning log every time it fails with the amount of attempts it has +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + +## @backstage/backend-test-utils@0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.17.2-next.0 + - @backstage/backend-common@0.13.6-next.0 + +## @backstage/cli@0.17.2-next.0 + +### Patch Changes + +- 4f73352608: Updated Lockfile to support new versions of yarn as well as the legacy 1 version +- 6de866ea74: Added console warning to frontend start when the `app.baseUrl` and `backend.baseUrl` are identical + +## @backstage/core-components@0.9.5-next.0 + +### Patch Changes + +- 65840b17be: Fix issue where right arrow icon was incorrectly added to side bar items without a sub-menu +- 6968b65ba1: Updated dependency `@react-hookz/web` to `^14.0.0`. +- 96d1e01641: Accessibility updates: + + - Added `aria-label` to the `Select` component + - Changed heading level used in the header of `Table` component + +## @backstage/create-app@0.4.28-next.0 + +### Patch Changes + +- 881fbd7e8d: Register `TechDocs` addons on catalog entity pages, follow the steps below to add them manually: + + ```diff + // packages/app/src/components/catalog/EntityPage.tsx + + + import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; + + import { + + ReportIssue, + + } from '@backstage/plugin-techdocs-module-addons-contrib'; + + + const techdocsContent = ( + + + + + + + + + + + + ); + + const defaultEntityPage = ( + ... + + + {techdocsContent} + + ... + ); + + const serviceEntityPage = ( + ... + + + {techdocsContent} + + ... + ); + + const websiteEntityPage = ( + ... + + + {techdocsContent} + + ... + ); + ``` + +- 935d8515da: Updated the `--version` flag to output the version of the current backstage release instead of the version of create-app. + +- 1f70704580: Accessibility updates: + + - Added `aria-label` to the sidebar Logo link. To enable this for an existing app, please make the following changes: + + `packages/app/src/components/Root/Root.tsx` + + ```diff + const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); + const { isOpen } = useContext(SidebarContext); + + return ( +
+ + {isOpen ? : } + +
+ ); + }; + ``` + +## @backstage/dev-utils@1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/app-defaults@1.0.3-next.0 + - @backstage/integration-react@1.1.1-next.0 + +## @backstage/integration@1.2.1-next.0 + +### Patch Changes + +- 72dfcbc8bf: Gerrit Integration: Handle absolute paths in `resolveUrl` properly. + +## @backstage/integration-react@1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + +## @techdocs/cli@1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-techdocs-node@1.1.2-next.0 + +## @backstage/techdocs-common@0.11.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.1.2-next.0 + +## @backstage/plugin-adr@0.1.1-next.0 + +### Patch Changes + +- a6458a120b: Adding term highlighting support to `AdrSearchResultListItem` +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration-react@1.1.1-next.0 + - @backstage/plugin-adr-common@0.1.1-next.0 + +## @backstage/plugin-adr-backend@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-adr-common@0.1.1-next.0 + +## @backstage/plugin-adr-common@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.2.1-next.0 + +## @backstage/plugin-airbrake@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/dev-utils@1.0.3-next.0 + +## @backstage/plugin-airbrake-backend@0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + +## @backstage/plugin-allure@0.1.22-next.0 + +### Patch Changes + +- 6387b7a98a: Add export for `isAllureReportAvailable` and `ALLURE_PROJECT_ID_ANNOTATION` so it can be used outside of plugin +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-analytics-module-ga@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-apache-airflow@0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-api-docs@0.8.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/plugin-catalog@1.2.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-app-backend@0.3.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + +## @backstage/plugin-auth-backend@0.14.1-next.0 + +### Patch Changes + +- f6aae90e4e: Added configurable algorithm field for TokenFactory +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + +## @backstage/plugin-auth-node@0.2.2-next.0 + +### Patch Changes + +- 9079a78078: Added configurable algorithms array for IdentityClient +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + +## @backstage/plugin-azure-devops@0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-azure-devops-backend@0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + +## @backstage/plugin-badges@0.2.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-badges-backend@0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + +## @backstage/plugin-bazaar@0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/plugin-catalog@1.2.1-next.0 + - @backstage/cli@0.17.2-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-bazaar-backend@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/backend-test-utils@0.1.25-next.0 + +## @backstage/plugin-bitrise@0.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-catalog@1.2.1-next.0 + +### Patch Changes + +- 449dcef98e: Updates the `isKind`, `ìsComponentType`, and `isNamespace` to allow an array of possible values + +- 1f70704580: Accessibility updates: + + - Added screen reader elements to describe default table `Action` buttons + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration-react@1.1.1-next.0 + +## @backstage/plugin-catalog-backend-module-aws@0.1.6-next.0 + +### Patch Changes + +- eb2544b21b: Inline config interfaces +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + +## @backstage/plugin-catalog-backend-module-azure@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + +## @backstage/plugin-catalog-backend-module-bitbucket@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + +## @backstage/plugin-catalog-backend-module-gerrit@0.1.1-next.0 + +### Patch Changes + +- eb2544b21b: Inline config interfaces +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + +## @backstage/plugin-catalog-backend-module-github@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + +## @backstage/plugin-catalog-backend-module-gitlab@0.1.4-next.0 + +### Patch Changes + +- 3ac4522537: do not create location object if file with component definition do not exists in project, that decrease count of request to gitlab with 404 status code. Now we can create processor with new flag to enable this logic: + + ```ts + const processor = GitLabDiscoveryProcessor.fromConfig(config, { + logger, + skipReposWithoutExactFileMatch: true, + }); + ``` + + **WARNING:** This new functionality does not support globs in the repo file path + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + +## @backstage/plugin-catalog-backend-module-msgraph@0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + +## @backstage/plugin-catalog-graph@0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-catalog-import@0.8.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/integration-react@1.1.1-next.0 + +## @backstage/plugin-catalog-react@1.1.1-next.0 + +### Patch Changes + +- 1f70704580: Accessibility updates: + + - Wrapped the `EntityLifecyclePicker`, `EntityOwnerPicker`, `EntityTagPicker`, in `label` elements + - Changed group name `Typography` component to `span` (from default `h6`), added `aria-label` to the `List` component, and `role` of `menuitem` to the container of the `MenuItem` component + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + +## @backstage/plugin-cicd-statistics@0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + +## @backstage/plugin-cicd-statistics-module-gitlab@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-cicd-statistics@0.1.8-next.0 + +## @backstage/plugin-circleci@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-cloudbuild@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-code-climate@0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-code-coverage@0.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-code-coverage-backend@0.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + +## @backstage/plugin-codescene@0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-config-schema@0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-cost-insights@0.11.28-next.0 + +### Patch Changes + +- eb2544b21b: Add missing `export` in configuration schema. +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-explore@0.3.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-firehydrant@0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-fossa@0.2.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-gcalendar@0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-gcp-projects@0.3.25-next.0 + +### Patch Changes + +- 6968b65ba1: Updated dependency `@react-hookz/web` to `^14.0.0`. +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-git-release-manager@0.3.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + +## @backstage/plugin-github-actions@0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + +## @backstage/plugin-github-deployments@0.1.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/integration-react@1.1.1-next.0 + +## @backstage/plugin-gitops-profiles@0.3.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-gocd@0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-graphiql@0.2.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-graphql-backend@0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + +## @backstage/plugin-home@0.4.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/plugin-stack-overflow@0.1.2-next.0 + +## @backstage/plugin-ilert@0.1.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-jenkins@0.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-jenkins-backend@0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + +## @backstage/plugin-kafka@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-kafka-backend@0.2.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + +## @backstage/plugin-kubernetes@0.6.6-next.0 + +### Patch Changes + +- 4328737af6: Add support to fetch data for Stateful Sets and display an accordion in the same way as with Deployments +- 81304e3e91: Fix for HPA matching when deploying same HPA in multiple namespaces +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/plugin-kubernetes-common@0.3.0-next.0 + +## @backstage/plugin-lighthouse@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-newrelic@0.3.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-newrelic-dashboard@0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-org@0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-pagerduty@0.3.33-next.0 + +### Patch Changes + +- 76bf6400fe: Fix alert that was not showing after creating an incident. +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-periskop@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-periskop-backend@0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + +## @backstage/plugin-permission-backend@0.5.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + - @backstage/plugin-permission-node@0.6.2-next.0 + +## @backstage/plugin-permission-node@0.6.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + +## @backstage/plugin-proxy-backend@0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + +## @backstage/plugin-rollbar@0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-rollbar-backend@0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + +## @backstage/plugin-scaffolder-backend-module-cookiecutter@0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.3.0-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + +## @backstage/plugin-scaffolder-backend-module-rails@0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.3.0-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + +## @backstage/plugin-scaffolder-backend-module-yeoman@0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.3.0-next.0 + +## @backstage/plugin-search@0.8.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-search-backend@0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-search-backend-node@0.6.2-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + - @backstage/plugin-permission-node@0.6.2-next.0 + +## @backstage/plugin-search-backend-module-elasticsearch@0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@0.6.2-next.0 + +## @backstage/plugin-search-backend-module-pg@0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-search-backend-node@0.6.2-next.0 + +## @backstage/plugin-search-backend-node@0.6.2-next.0 + +### Patch Changes + +- e7794a0aaa: propagate indexing errors so they don't appear successful to the task scheduler +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + +## @backstage/plugin-sentry@0.3.44-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-shortcuts@0.2.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-sonarqube@0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-splunk-on-call@0.3.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-stack-overflow@0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + - @backstage/plugin-home@0.4.22-next.0 + +## @backstage/plugin-tech-insights@0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-tech-insights-backend@0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-tech-insights-node@0.3.1-next.0 + +## @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-tech-insights-node@0.3.1-next.0 + +## @backstage/plugin-tech-insights-node@0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + +## @backstage/plugin-tech-radar@0.5.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-techdocs@1.1.2-next.0 + +### Patch Changes + +- 881fbd7e8d: Fix `EntityTechdocsContent` component to use objects instead of `` elements, otherwise "outlet" will be null on sub-pages and add-ons won't render. + +- 17c059dfd0: Restructures reader style transformations to improve code readability: + + - Extracts the style rules to separate files; + - Creates a hook that processes each rule; + - And creates another hook that returns a transformer responsible for injecting them into the head tag of a given element. + +- 3b45ad701f: Packages a set of tweaks to the TechDocs addons rendering process: + + - Prevents displaying sidebars until page styles are loaded and the sidebar position is updated; + - Prevents new sidebar locations from being created every time the reader page is rendered if these locations already exist; + - Centers the styles loaded event to avoid having multiple locations setting the opacity style in Shadow Dom causing the screen to flash multiple times. + +- 816f7475ec: Convert `sanitizeDOM` transformer to hook as part of code readability improvements in dom file. + +- 50ff56a80f: Change the `EntityDocsPage` path to be more specific and also add integration tests for `sub-routes` on this page. + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.1-next.0 + - @backstage/integration-react@1.1.1-next.0 + +## @backstage/plugin-techdocs-addons-test-utils@1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.2.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/plugin-techdocs@1.1.2-next.0 + - @backstage/plugin-techdocs-react@1.0.1-next.0 + - @backstage/integration-react@1.1.1-next.0 + +## @backstage/plugin-techdocs-backend@1.1.2-next.0 + +### Patch Changes + +- 5d66d4ff67: Output logs from a TechDocs build to a logging transport in addition to existing + frontend event stream, for capturing these logs to other sources. + + This allows users to capture debugging information around why tech docs fail to build + without needing to rely on end users capturing information from their web browser. + + The most common use case is to log to the same place as the rest of the backend + application logs. + + Sample usage: + + import { DockerContainerRunner } from '@backstage/backend-common'; + import { + createRouter, + Generators, + Preparers, + Publisher, + } from '@backstage/plugin-techdocs-backend'; + import Docker from 'dockerode'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + const preparers = await Preparers.fromConfig(env.config, { + logger: env.logger, + reader: env.reader, + }); + + const dockerClient = new Docker(); + const containerRunner = new DockerContainerRunner({ dockerClient }); + + const generators = await Generators.fromConfig(env.config, { + logger: env.logger, + containerRunner, + }); + + const publisher = await Publisher.fromConfig(env.config, { + logger: env.logger, + discovery: env.discovery, + }); + + await publisher.getReadiness(); + + return await createRouter({ + preparers, + generators, + publisher, + logger: env.logger, + // Passing a buildLogTransport as a parameter in createRouter will enable + // capturing build logs to a backend log stream + buildLogTransport: env.logger, + config: env.config, + discovery: env.discovery, + cache: env.cache, + }); + } + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-techdocs-node@1.1.2-next.0 + +## @backstage/plugin-techdocs-module-addons-contrib@1.0.1-next.0 + +### Patch Changes + +- 6968b65ba1: Updated dependency `@react-hookz/web` to `^14.0.0`. +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.1-next.0 + - @backstage/integration-react@1.1.1-next.0 + +## @backstage/plugin-techdocs-node@1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + +## @backstage/plugin-techdocs-react@1.0.1-next.0 + +### Patch Changes + +- 3b45ad701f: Creates a `TechDocsShadowDom` component that takes a tree of elements and an `onAppend` handler: + + - Calls the `onAppend` handler when appending the element tree to the shadow root; + - Also dispatches an event when styles are loaded to let transformers know that the computed styles are ready to be consumed. + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-todo@0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-todo-backend@0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + +## @backstage/plugin-user-settings@0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @backstage/plugin-xcmetrics@0.2.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## example-app@0.2.72-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes@0.6.6-next.0 + - @backstage/plugin-cost-insights@0.11.28-next.0 + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/plugin-catalog@1.2.1-next.0 + - @backstage/cli@0.17.2-next.0 + - @backstage/plugin-pagerduty@0.3.33-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/plugin-gcp-projects@0.3.25-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.1-next.0 + - @backstage/plugin-scaffolder@1.3.0-next.0 + - @backstage/plugin-techdocs@1.1.2-next.0 + - @backstage/plugin-techdocs-react@1.0.1-next.0 + - @backstage/plugin-airbrake@0.3.6-next.0 + - @backstage/plugin-api-docs@0.8.6-next.0 + - @backstage/plugin-azure-devops@0.1.22-next.0 + - @backstage/plugin-badges@0.2.30-next.0 + - @backstage/plugin-catalog-graph@0.2.18-next.0 + - @backstage/plugin-catalog-import@0.8.9-next.0 + - @backstage/plugin-circleci@0.3.6-next.0 + - @backstage/plugin-cloudbuild@0.3.6-next.0 + - @backstage/plugin-code-coverage@0.1.33-next.0 + - @backstage/plugin-explore@0.3.37-next.0 + - @backstage/plugin-github-actions@0.5.6-next.0 + - @backstage/plugin-gocd@0.1.12-next.0 + - @backstage/plugin-home@0.4.22-next.0 + - @backstage/plugin-jenkins@0.7.5-next.0 + - @backstage/plugin-kafka@0.3.6-next.0 + - @backstage/plugin-lighthouse@0.3.6-next.0 + - @backstage/plugin-newrelic-dashboard@0.1.14-next.0 + - @backstage/plugin-org@0.5.6-next.0 + - @backstage/plugin-rollbar@0.4.6-next.0 + - @backstage/plugin-search@0.8.2-next.0 + - @backstage/plugin-sentry@0.3.44-next.0 + - @backstage/plugin-tech-insights@0.2.2-next.0 + - @backstage/plugin-todo@0.2.8-next.0 + - @backstage/app-defaults@1.0.3-next.0 + - @backstage/integration-react@1.1.1-next.0 + - @backstage/plugin-apache-airflow@0.1.14-next.0 + - @backstage/plugin-gcalendar@0.3.2-next.0 + - @backstage/plugin-graphiql@0.2.38-next.0 + - @backstage/plugin-newrelic@0.3.24-next.0 + - @backstage/plugin-shortcuts@0.2.7-next.0 + - @backstage/plugin-stack-overflow@0.1.2-next.0 + - @backstage/plugin-tech-radar@0.5.13-next.0 + - @backstage/plugin-user-settings@0.4.5-next.0 + +## example-backend@0.2.72-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/plugin-scaffolder-backend@1.3.0-next.0 + - @backstage/plugin-kubernetes-backend@0.6.0-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-backend@0.14.1-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-search-backend-node@0.6.2-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + - @backstage/plugin-techdocs-backend@1.1.2-next.0 + - example-app@0.2.72-next.0 + - @backstage/plugin-app-backend@0.3.33-next.0 + - @backstage/plugin-azure-devops-backend@0.3.12-next.0 + - @backstage/plugin-badges-backend@0.1.27-next.0 + - @backstage/plugin-code-coverage-backend@0.1.31-next.0 + - @backstage/plugin-graphql-backend@0.1.23-next.0 + - @backstage/plugin-jenkins-backend@0.1.23-next.0 + - @backstage/plugin-kafka-backend@0.2.26-next.0 + - @backstage/plugin-permission-backend@0.5.8-next.0 + - @backstage/plugin-permission-node@0.6.2-next.0 + - @backstage/plugin-proxy-backend@0.2.27-next.0 + - @backstage/plugin-rollbar-backend@0.1.30-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.1-next.0 + - @backstage/plugin-search-backend@0.5.3-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.5-next.0 + - @backstage/plugin-search-backend-module-pg@0.3.4-next.0 + - @backstage/plugin-tech-insights-backend@0.4.1-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.17-next.0 + - @backstage/plugin-tech-insights-node@0.3.1-next.0 + - @backstage/plugin-todo-backend@0.1.30-next.0 + +## techdocs-cli-embedded-app@0.2.71-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.2.1-next.0 + - @backstage/cli@0.17.2-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/plugin-techdocs@1.1.2-next.0 + - @backstage/plugin-techdocs-react@1.0.1-next.0 + - @backstage/app-defaults@1.0.3-next.0 + - @backstage/integration-react@1.1.1-next.0 + +## @internal/plugin-todo-list@1.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + +## @internal/plugin-todo-list-backend@1.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 diff --git a/package.json b/package.json index 7752322535..cfc585b9df 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@types/react": "^17", "@types/react-dom": "^17" }, - "version": "1.2.0", + "version": "1.3.0-next.0", "dependencies": { "@manypkg/get-packages": "^1.1.3", "@microsoft/api-documenter": "^7.17.11", diff --git a/packages/app-defaults/CHANGELOG.md b/packages/app-defaults/CHANGELOG.md index b0534e7d84..a117b77f2b 100644 --- a/packages/app-defaults/CHANGELOG.md +++ b/packages/app-defaults/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/app-defaults +## 1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 1.0.2 ### Patch Changes diff --git a/packages/app-defaults/package.json b/packages/app-defaults/package.json index 4d931d9560..d43af430a4 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.2", + "version": "1.0.3-next.0", "private": false, "publishConfig": { "access": "public", @@ -33,7 +33,7 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-app-api": "^1.0.2", "@backstage/core-plugin-api": "^1.0.2", "@backstage/plugin-permission-react": "^0.4.1", @@ -46,7 +46,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index 9d30db9dbc..7843693d46 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,56 @@ # example-app +## 0.2.72-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-kubernetes@0.6.6-next.0 + - @backstage/plugin-cost-insights@0.11.28-next.0 + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/plugin-catalog@1.2.1-next.0 + - @backstage/cli@0.17.2-next.0 + - @backstage/plugin-pagerduty@0.3.33-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/plugin-gcp-projects@0.3.25-next.0 + - @backstage/plugin-techdocs-module-addons-contrib@1.0.1-next.0 + - @backstage/plugin-scaffolder@1.3.0-next.0 + - @backstage/plugin-techdocs@1.1.2-next.0 + - @backstage/plugin-techdocs-react@1.0.1-next.0 + - @backstage/plugin-airbrake@0.3.6-next.0 + - @backstage/plugin-api-docs@0.8.6-next.0 + - @backstage/plugin-azure-devops@0.1.22-next.0 + - @backstage/plugin-badges@0.2.30-next.0 + - @backstage/plugin-catalog-graph@0.2.18-next.0 + - @backstage/plugin-catalog-import@0.8.9-next.0 + - @backstage/plugin-circleci@0.3.6-next.0 + - @backstage/plugin-cloudbuild@0.3.6-next.0 + - @backstage/plugin-code-coverage@0.1.33-next.0 + - @backstage/plugin-explore@0.3.37-next.0 + - @backstage/plugin-github-actions@0.5.6-next.0 + - @backstage/plugin-gocd@0.1.12-next.0 + - @backstage/plugin-home@0.4.22-next.0 + - @backstage/plugin-jenkins@0.7.5-next.0 + - @backstage/plugin-kafka@0.3.6-next.0 + - @backstage/plugin-lighthouse@0.3.6-next.0 + - @backstage/plugin-newrelic-dashboard@0.1.14-next.0 + - @backstage/plugin-org@0.5.6-next.0 + - @backstage/plugin-rollbar@0.4.6-next.0 + - @backstage/plugin-search@0.8.2-next.0 + - @backstage/plugin-sentry@0.3.44-next.0 + - @backstage/plugin-tech-insights@0.2.2-next.0 + - @backstage/plugin-todo@0.2.8-next.0 + - @backstage/app-defaults@1.0.3-next.0 + - @backstage/integration-react@1.1.1-next.0 + - @backstage/plugin-apache-airflow@0.1.14-next.0 + - @backstage/plugin-gcalendar@0.3.2-next.0 + - @backstage/plugin-graphiql@0.2.38-next.0 + - @backstage/plugin-newrelic@0.3.24-next.0 + - @backstage/plugin-shortcuts@0.2.7-next.0 + - @backstage/plugin-stack-overflow@0.1.2-next.0 + - @backstage/plugin-tech-radar@0.5.13-next.0 + - @backstage/plugin-user-settings@0.4.5-next.0 + ## 0.2.71 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index b94b4eb6d4..b14f0a502e 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,65 +1,65 @@ { "name": "example-app", - "version": "0.2.71", + "version": "0.2.72-next.0", "private": true, "backstage": { "role": "frontend" }, "bundled": true, "dependencies": { - "@backstage/app-defaults": "^1.0.2", + "@backstage/app-defaults": "^1.0.3-next.0", "@backstage/catalog-model": "^1.0.2", - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/config": "^1.0.1", "@backstage/core-app-api": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/integration-react": "^1.1.0", - "@backstage/plugin-airbrake": "^0.3.5", - "@backstage/plugin-api-docs": "^0.8.5", - "@backstage/plugin-azure-devops": "^0.1.21", - "@backstage/plugin-apache-airflow": "^0.1.13", - "@backstage/plugin-badges": "^0.2.29", - "@backstage/plugin-catalog": "^1.2.0", + "@backstage/integration-react": "^1.1.1-next.0", + "@backstage/plugin-airbrake": "^0.3.6-next.0", + "@backstage/plugin-api-docs": "^0.8.6-next.0", + "@backstage/plugin-azure-devops": "^0.1.22-next.0", + "@backstage/plugin-apache-airflow": "^0.1.14-next.0", + "@backstage/plugin-badges": "^0.2.30-next.0", + "@backstage/plugin-catalog": "^1.2.1-next.0", "@backstage/plugin-catalog-common": "^1.0.2", - "@backstage/plugin-catalog-graph": "^0.2.17", - "@backstage/plugin-catalog-import": "^0.8.8", - "@backstage/plugin-catalog-react": "^1.1.0", - "@backstage/plugin-circleci": "^0.3.5", - "@backstage/plugin-cloudbuild": "^0.3.5", - "@backstage/plugin-code-coverage": "^0.1.32", - "@backstage/plugin-cost-insights": "^0.11.27", - "@backstage/plugin-explore": "^0.3.36", - "@backstage/plugin-gcalendar": "^0.3.1", - "@backstage/plugin-gcp-projects": "^0.3.24", - "@backstage/plugin-github-actions": "^0.5.5", - "@backstage/plugin-gocd": "^0.1.11", - "@backstage/plugin-graphiql": "^0.2.37", - "@backstage/plugin-home": "^0.4.21", - "@backstage/plugin-jenkins": "^0.7.4", - "@backstage/plugin-kafka": "^0.3.5", - "@backstage/plugin-kubernetes": "^0.6.5", - "@backstage/plugin-lighthouse": "^0.3.5", - "@backstage/plugin-newrelic": "^0.3.23", - "@backstage/plugin-newrelic-dashboard": "^0.1.13", - "@backstage/plugin-org": "^0.5.5", - "@backstage/plugin-pagerduty": "0.3.32", + "@backstage/plugin-catalog-graph": "^0.2.18-next.0", + "@backstage/plugin-catalog-import": "^0.8.9-next.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", + "@backstage/plugin-circleci": "^0.3.6-next.0", + "@backstage/plugin-cloudbuild": "^0.3.6-next.0", + "@backstage/plugin-code-coverage": "^0.1.33-next.0", + "@backstage/plugin-cost-insights": "^0.11.28-next.0", + "@backstage/plugin-explore": "^0.3.37-next.0", + "@backstage/plugin-gcalendar": "^0.3.2-next.0", + "@backstage/plugin-gcp-projects": "^0.3.25-next.0", + "@backstage/plugin-github-actions": "^0.5.6-next.0", + "@backstage/plugin-gocd": "^0.1.12-next.0", + "@backstage/plugin-graphiql": "^0.2.38-next.0", + "@backstage/plugin-home": "^0.4.22-next.0", + "@backstage/plugin-jenkins": "^0.7.5-next.0", + "@backstage/plugin-kafka": "^0.3.6-next.0", + "@backstage/plugin-kubernetes": "^0.6.6-next.0", + "@backstage/plugin-lighthouse": "^0.3.6-next.0", + "@backstage/plugin-newrelic": "^0.3.24-next.0", + "@backstage/plugin-newrelic-dashboard": "^0.1.14-next.0", + "@backstage/plugin-org": "^0.5.6-next.0", + "@backstage/plugin-pagerduty": "0.3.33-next.0", "@backstage/plugin-permission-react": "^0.4.1", - "@backstage/plugin-rollbar": "^0.4.5", - "@backstage/plugin-scaffolder": "^1.2.0", - "@backstage/plugin-search": "^0.8.1", + "@backstage/plugin-rollbar": "^0.4.6-next.0", + "@backstage/plugin-scaffolder": "^1.3.0-next.0", + "@backstage/plugin-search": "^0.8.2-next.0", "@backstage/plugin-search-react": "^0.2.0", "@backstage/plugin-search-common": "^0.3.4", - "@backstage/plugin-sentry": "^0.3.43", - "@backstage/plugin-shortcuts": "^0.2.6", - "@backstage/plugin-stack-overflow": "^0.1.1", - "@backstage/plugin-tech-radar": "^0.5.12", - "@backstage/plugin-techdocs": "^1.1.1", - "@backstage/plugin-techdocs-react": "^1.0.0", - "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.0", - "@backstage/plugin-todo": "^0.2.7", - "@backstage/plugin-user-settings": "^0.4.4", - "@backstage/plugin-tech-insights": "^0.2.1", + "@backstage/plugin-sentry": "^0.3.44-next.0", + "@backstage/plugin-shortcuts": "^0.2.7-next.0", + "@backstage/plugin-stack-overflow": "^0.1.2-next.0", + "@backstage/plugin-tech-radar": "^0.5.13-next.0", + "@backstage/plugin-techdocs": "^1.1.2-next.0", + "@backstage/plugin-techdocs-react": "^1.0.1-next.0", + "@backstage/plugin-techdocs-module-addons-contrib": "^1.0.1-next.0", + "@backstage/plugin-todo": "^0.2.8-next.0", + "@backstage/plugin-user-settings": "^0.4.5-next.0", + "@backstage/plugin-tech-insights": "^0.2.2-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", diff --git a/packages/backend-common/CHANGELOG.md b/packages/backend-common/CHANGELOG.md index d26ea09e04..0581962383 100644 --- a/packages/backend-common/CHANGELOG.md +++ b/packages/backend-common/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/backend-common +## 0.13.6-next.0 + +### Patch Changes + +- f72a6b8c62: Applied the `luxon` dependency fix from the `0.13.4` patch release. +- 5b22a8c97f: Applied the AWS S3 reading patch from the `0.13.5` patch release. +- Updated dependencies + - @backstage/integration@1.2.1-next.0 + ## 0.13.5 ### Patch Changes diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 988335ec05..6114505127 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.13.3", + "version": "0.13.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -38,7 +38,7 @@ "@backstage/config": "^1.0.1", "@backstage/config-loader": "^1.1.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", + "@backstage/integration": "^1.2.1-next.0", "@backstage/types": "^1.0.0", "@google-cloud/storage": "^5.8.0", "@manypkg/get-packages": "^1.1.3", @@ -91,8 +91,8 @@ } }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", "@types/archiver": "^5.1.0", "@types/base64-stream": "^1.0.2", "@types/compression": "^1.7.0", diff --git a/packages/backend-tasks/CHANGELOG.md b/packages/backend-tasks/CHANGELOG.md index 0a74dc0a1d..e7bbe8ba9e 100644 --- a/packages/backend-tasks/CHANGELOG.md +++ b/packages/backend-tasks/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/backend-tasks +## 0.3.2-next.0 + +### Patch Changes + +- fde10d24f6: Allow tasks that fail to retry on a loop emitting a warning log every time it fails with the amount of attempts it has +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + ## 0.3.1 ### Patch Changes diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index 6a4ea9d70d..108ce59f15 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.1", + "version": "0.3.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -33,7 +33,7 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", "@backstage/types": "^1.0.0", @@ -48,8 +48,8 @@ "zod": "^3.9.5" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", "@types/cron": "^1.7.3", "wait-for-expect": "^3.0.2" }, diff --git a/packages/backend-test-utils/CHANGELOG.md b/packages/backend-test-utils/CHANGELOG.md index b84b877774..ec8b360731 100644 --- a/packages/backend-test-utils/CHANGELOG.md +++ b/packages/backend-test-utils/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/backend-test-utils +## 0.1.25-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/cli@0.17.2-next.0 + - @backstage/backend-common@0.13.6-next.0 + ## 0.1.24 ### Patch Changes diff --git a/packages/backend-test-utils/package.json b/packages/backend-test-utils/package.json index c60b860b6b..80bab74f52 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.24", + "version": "0.1.25-next.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -34,8 +34,8 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", - "@backstage/cli": "^0.17.1", + "@backstage/backend-common": "^0.13.6-next.0", + "@backstage/cli": "^0.17.2-next.0", "@backstage/config": "^1.0.1", "better-sqlite3": "^7.5.0", "knex": "^1.0.2", @@ -46,7 +46,7 @@ "uuid": "^8.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1" + "@backstage/cli": "^0.17.2-next.0" }, "files": [ "dist" diff --git a/packages/backend/CHANGELOG.md b/packages/backend/CHANGELOG.md index ae6beff70a..418f11ecfd 100644 --- a/packages/backend/CHANGELOG.md +++ b/packages/backend/CHANGELOG.md @@ -1,5 +1,41 @@ # example-backend +## 0.2.72-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/plugin-scaffolder-backend@1.3.0-next.0 + - @backstage/plugin-kubernetes-backend@0.6.0-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-backend@0.14.1-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-search-backend-node@0.6.2-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + - @backstage/plugin-techdocs-backend@1.1.2-next.0 + - example-app@0.2.72-next.0 + - @backstage/plugin-app-backend@0.3.33-next.0 + - @backstage/plugin-azure-devops-backend@0.3.12-next.0 + - @backstage/plugin-badges-backend@0.1.27-next.0 + - @backstage/plugin-code-coverage-backend@0.1.31-next.0 + - @backstage/plugin-graphql-backend@0.1.23-next.0 + - @backstage/plugin-jenkins-backend@0.1.23-next.0 + - @backstage/plugin-kafka-backend@0.2.26-next.0 + - @backstage/plugin-permission-backend@0.5.8-next.0 + - @backstage/plugin-permission-node@0.6.2-next.0 + - @backstage/plugin-proxy-backend@0.2.27-next.0 + - @backstage/plugin-rollbar-backend@0.1.30-next.0 + - @backstage/plugin-scaffolder-backend-module-rails@0.4.1-next.0 + - @backstage/plugin-search-backend@0.5.3-next.0 + - @backstage/plugin-search-backend-module-elasticsearch@0.1.5-next.0 + - @backstage/plugin-search-backend-module-pg@0.3.4-next.0 + - @backstage/plugin-tech-insights-backend@0.4.1-next.0 + - @backstage/plugin-tech-insights-backend-module-jsonfc@0.1.17-next.0 + - @backstage/plugin-tech-insights-node@0.3.1-next.0 + - @backstage/plugin-todo-backend@0.1.30-next.0 + ## 0.2.71 ### Patch Changes diff --git a/packages/backend/package.json b/packages/backend/package.json index d3c42db1c9..f68a30d7c8 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -1,6 +1,6 @@ { "name": "example-backend", - "version": "0.2.71", + "version": "0.2.72-next.0", "main": "dist/index.cjs.js", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,39 +26,39 @@ "build-image": "docker build ../.. -f Dockerfile --tag example-backend" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", - "@backstage/backend-tasks": "^0.3.1", + "@backstage/backend-common": "^0.13.6-next.0", + "@backstage/backend-tasks": "^0.3.2-next.0", "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", - "@backstage/integration": "^1.2.0", - "@backstage/plugin-app-backend": "^0.3.32", - "@backstage/plugin-auth-backend": "^0.14.0", - "@backstage/plugin-auth-node": "^0.2.1", - "@backstage/plugin-azure-devops-backend": "^0.3.11", - "@backstage/plugin-badges-backend": "^0.1.26", - "@backstage/plugin-catalog-backend": "^1.1.2", - "@backstage/plugin-code-coverage-backend": "^0.1.30", - "@backstage/plugin-graphql-backend": "^0.1.22", - "@backstage/plugin-jenkins-backend": "^0.1.22", - "@backstage/plugin-kubernetes-backend": "^0.5.1", - "@backstage/plugin-kafka-backend": "^0.2.25", - "@backstage/plugin-permission-backend": "^0.5.7", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/plugin-app-backend": "^0.3.33-next.0", + "@backstage/plugin-auth-backend": "^0.14.1-next.0", + "@backstage/plugin-auth-node": "^0.2.2-next.0", + "@backstage/plugin-azure-devops-backend": "^0.3.12-next.0", + "@backstage/plugin-badges-backend": "^0.1.27-next.0", + "@backstage/plugin-catalog-backend": "^1.2.0-next.0", + "@backstage/plugin-code-coverage-backend": "^0.1.31-next.0", + "@backstage/plugin-graphql-backend": "^0.1.23-next.0", + "@backstage/plugin-jenkins-backend": "^0.1.23-next.0", + "@backstage/plugin-kubernetes-backend": "^0.6.0-next.0", + "@backstage/plugin-kafka-backend": "^0.2.26-next.0", + "@backstage/plugin-permission-backend": "^0.5.8-next.0", "@backstage/plugin-permission-common": "^0.6.1", - "@backstage/plugin-permission-node": "^0.6.1", - "@backstage/plugin-proxy-backend": "^0.2.26", - "@backstage/plugin-rollbar-backend": "^0.1.29", - "@backstage/plugin-scaffolder-backend": "^1.2.0", - "@backstage/plugin-scaffolder-backend-module-rails": "^0.4.0", - "@backstage/plugin-search-backend": "^0.5.2", - "@backstage/plugin-search-backend-node": "^0.6.1", - "@backstage/plugin-search-backend-module-elasticsearch": "^0.1.4", - "@backstage/plugin-search-backend-module-pg": "^0.3.3", - "@backstage/plugin-techdocs-backend": "^1.1.1", - "@backstage/plugin-tech-insights-backend": "^0.4.0", - "@backstage/plugin-tech-insights-node": "^0.3.0", - "@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.16", - "@backstage/plugin-todo-backend": "^0.1.29", + "@backstage/plugin-permission-node": "^0.6.2-next.0", + "@backstage/plugin-proxy-backend": "^0.2.27-next.0", + "@backstage/plugin-rollbar-backend": "^0.1.30-next.0", + "@backstage/plugin-scaffolder-backend": "^1.3.0-next.0", + "@backstage/plugin-scaffolder-backend-module-rails": "^0.4.1-next.0", + "@backstage/plugin-search-backend": "^0.5.3-next.0", + "@backstage/plugin-search-backend-node": "^0.6.2-next.0", + "@backstage/plugin-search-backend-module-elasticsearch": "^0.1.5-next.0", + "@backstage/plugin-search-backend-module-pg": "^0.3.4-next.0", + "@backstage/plugin-techdocs-backend": "^1.1.2-next.0", + "@backstage/plugin-tech-insights-backend": "^0.4.1-next.0", + "@backstage/plugin-tech-insights-node": "^0.3.1-next.0", + "@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.17-next.0", + "@backstage/plugin-todo-backend": "^0.1.30-next.0", "@gitbeaker/node": "^35.1.0", "@octokit/rest": "^18.5.3", "better-sqlite3": "^7.5.0", @@ -75,7 +75,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/dockerode": "^3.3.0", "@types/express": "^4.17.6", "@types/express-serve-static-core": "^4.17.5", diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 1ccd49ab6c..0aa0f3a220 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -38,7 +38,7 @@ "cross-fetch": "^3.1.5" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/jest": "^26.0.7", "msw": "^0.35.0" }, diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 41f6f15851..93c86e2f4a 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -43,7 +43,7 @@ "uuid": "^8.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/jest": "^26.0.7", "@types/json-schema": "^7.0.5", "@types/lodash": "^4.14.151", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 5ad3803a25..817bf2f745 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/cli +## 0.17.2-next.0 + +### Patch Changes + +- 4f73352608: Updated Lockfile to support new versions of yarn as well as the legacy 1 version +- 6de866ea74: Added console warning to frontend start when the `app.baseUrl` and `backend.baseUrl` are identical + ## 0.17.1 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index e36a59b8e2..7914b6fb80 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.17.1", + "version": "0.17.2-next.0", "private": false, "publishConfig": { "access": "public" @@ -126,12 +126,12 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@backstage/core-app-api": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@backstage/theme": "^0.2.15", "@types/diff": "^5.0.0", diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 28baeab4df..c9f12431e9 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -49,7 +49,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/packages/core-components/CHANGELOG.md b/packages/core-components/CHANGELOG.md index 167076a74e..53a1d92dde 100644 --- a/packages/core-components/CHANGELOG.md +++ b/packages/core-components/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/core-components +## 0.9.5-next.0 + +### Patch Changes + +- 65840b17be: Fix issue where right arrow icon was incorrectly added to side bar items without a sub-menu +- 6968b65ba1: Updated dependency `@react-hookz/web` to `^14.0.0`. +- 96d1e01641: Accessibility updates: + + - Added `aria-label` to the `Select` component + - Changed heading level used in the header of `Table` component + ## 0.9.4 ### Patch Changes diff --git a/packages/core-components/package.json b/packages/core-components/package.json index a988d8213e..0e5cc05243 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.9.4", + "version": "0.9.5-next.0", "private": false, "publishConfig": { "access": "public", @@ -79,7 +79,7 @@ }, "devDependencies": { "@backstage/core-app-api": "^1.0.2", - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 4377761d89..e0ea4b425a 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -46,7 +46,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 6993b4631e..4988f5aa6b 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,80 @@ # @backstage/create-app +## 0.4.28-next.0 + +### Patch Changes + +- 881fbd7e8d: Register `TechDocs` addons on catalog entity pages, follow the steps below to add them manually: + + ```diff + // packages/app/src/components/catalog/EntityPage.tsx + + + import { TechDocsAddons } from '@backstage/plugin-techdocs-react'; + + import { + + ReportIssue, + + } from '@backstage/plugin-techdocs-module-addons-contrib'; + + + const techdocsContent = ( + + + + + + + + + + + + ); + + const defaultEntityPage = ( + ... + + + {techdocsContent} + + ... + ); + + const serviceEntityPage = ( + ... + + + {techdocsContent} + + ... + ); + + const websiteEntityPage = ( + ... + + + {techdocsContent} + + ... + ); + ``` + +- 935d8515da: Updated the `--version` flag to output the version of the current backstage release instead of the version of create-app. +- 1f70704580: Accessibility updates: + + - Added `aria-label` to the sidebar Logo link. To enable this for an existing app, please make the following changes: + + `packages/app/src/components/Root/Root.tsx` + + ```diff + const SidebarLogo = () => { + const classes = useSidebarLogoStyles(); + const { isOpen } = useContext(SidebarContext); + + return ( +
+ + {isOpen ? : } + +
+ ); + }; + ``` + ## 0.4.27 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 2fe8398309..e936483f53 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.27", + "version": "0.4.28-next.0", "private": false, "publishConfig": { "access": "public" diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 3463c378fe..a3ef76a5af 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/dev-utils +## 1.0.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/app-defaults@1.0.3-next.0 + - @backstage/integration-react@1.1.1-next.0 + ## 1.0.2 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 8ced35c07c..e91c4351fe 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.2", + "version": "1.0.3-next.0", "private": false, "publishConfig": { "access": "public", @@ -33,13 +33,13 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/app-defaults": "^1.0.2", + "@backstage/app-defaults": "^1.0.3-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/catalog-model": "^1.0.2", - "@backstage/integration-react": "^1.1.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/integration-react": "^1.1.1-next.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/test-utils": "^1.1.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -59,7 +59,7 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/jest": "^26.0.7", "@types/node": "^16.0.0" }, diff --git a/packages/errors/package.json b/packages/errors/package.json index f37c103f37..8327246224 100644 --- a/packages/errors/package.json +++ b/packages/errors/package.json @@ -38,7 +38,7 @@ "serialize-error": "^8.0.1" }, "devDependencies": { - "@backstage/cli": "^0.17.1-next.0", + "@backstage/cli": "^0.17.2-next.0", "@types/jest": "^26.0.7" }, "files": [ diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index baa0318529..c943a4a011 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/integration-react +## 1.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + ## 1.1.0 ### Minor Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index 34790174d0..ff0646dbcf 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.0", + "version": "1.1.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,9 +25,9 @@ }, "dependencies": { "@backstage/config": "^1.0.1", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/integration": "^1.2.0", + "@backstage/integration": "^1.2.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -38,8 +38,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", - "@backstage/dev-utils": "^1.0.2", + "@backstage/cli": "^0.17.2-next.0", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/packages/integration/CHANGELOG.md b/packages/integration/CHANGELOG.md index fa4999f57c..8217b3dbd4 100644 --- a/packages/integration/CHANGELOG.md +++ b/packages/integration/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/integration +## 1.2.1-next.0 + +### Patch Changes + +- 72dfcbc8bf: Gerrit Integration: Handle absolute paths in `resolveUrl` properly. + ## 1.2.0 ### Minor Changes diff --git a/packages/integration/package.json b/packages/integration/package.json index ae91c4ca2f..a27e07d7a9 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.2.0", + "version": "1.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -43,7 +43,7 @@ "lodash": "^4.17.21" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/config-loader": "^1.1.1", "@backstage/test-utils": "^1.1.0", "@types/jest": "^26.0.7", diff --git a/packages/techdocs-cli-embedded-app/CHANGELOG.md b/packages/techdocs-cli-embedded-app/CHANGELOG.md index 9f749dc2ab..aca2197870 100644 --- a/packages/techdocs-cli-embedded-app/CHANGELOG.md +++ b/packages/techdocs-cli-embedded-app/CHANGELOG.md @@ -1,5 +1,18 @@ # techdocs-cli-embedded-app +## 0.2.71-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.2.1-next.0 + - @backstage/cli@0.17.2-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/plugin-techdocs@1.1.2-next.0 + - @backstage/plugin-techdocs-react@1.0.1-next.0 + - @backstage/app-defaults@1.0.3-next.0 + - @backstage/integration-react@1.1.1-next.0 + ## 0.2.70 ### Patch Changes diff --git a/packages/techdocs-cli-embedded-app/package.json b/packages/techdocs-cli-embedded-app/package.json index d174cd0525..1d25509a87 100644 --- a/packages/techdocs-cli-embedded-app/package.json +++ b/packages/techdocs-cli-embedded-app/package.json @@ -1,23 +1,23 @@ { "name": "techdocs-cli-embedded-app", - "version": "0.2.70", + "version": "0.2.71-next.0", "private": true, "backstage": { "role": "frontend" }, "bundled": true, "dependencies": { - "@backstage/app-defaults": "^1.0.2", + "@backstage/app-defaults": "^1.0.3-next.0", "@backstage/catalog-model": "^1.0.2", - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/config": "^1.0.1", "@backstage/core-app-api": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/integration-react": "^1.1.0", - "@backstage/plugin-catalog": "^1.2.0", - "@backstage/plugin-techdocs": "^1.1.1", - "@backstage/plugin-techdocs-react": "^1.0.0", + "@backstage/integration-react": "^1.1.1-next.0", + "@backstage/plugin-catalog": "^1.2.1-next.0", + "@backstage/plugin-techdocs": "^1.1.2-next.0", + "@backstage/plugin-techdocs-react": "^1.0.1-next.0", "@backstage/test-utils": "^1.1.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.11.0", @@ -30,7 +30,7 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^14.0.0", diff --git a/packages/techdocs-cli/CHANGELOG.md b/packages/techdocs-cli/CHANGELOG.md index 44c8b9a1d1..97e14fe1f7 100644 --- a/packages/techdocs-cli/CHANGELOG.md +++ b/packages/techdocs-cli/CHANGELOG.md @@ -1,5 +1,13 @@ # @techdocs/cli +## 1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-techdocs-node@1.1.2-next.0 + ## 1.1.1 ### Patch Changes diff --git a/packages/techdocs-cli/package.json b/packages/techdocs-cli/package.json index 2e8107aa35..07995b2990 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.1.1", + "version": "1.1.2-next.0", "private": false, "publishConfig": { "access": "public" @@ -37,7 +37,7 @@ "techdocs-cli": "bin/techdocs-cli" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/commander": "^2.12.2", "@types/fs-extra": "^9.0.6", "@types/http-proxy": "^1.17.4", @@ -62,11 +62,11 @@ "ext": "ts" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-model": "^1.0.2", "@backstage/cli-common": "^0.1.9", "@backstage/config": "^1.0.1", - "@backstage/plugin-techdocs-node": "^1.1.1", + "@backstage/plugin-techdocs-node": "^1.1.2-next.0", "@types/dockerode": "^3.3.0", "commander": "^9.1.0", "dockerode": "^3.3.1", diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md index 5d930469fa..f97e216428 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/techdocs-common +## 0.11.16-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-techdocs-node@1.1.2-next.0 + ## 0.11.15 ### Patch Changes diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index fbc7052f9c..998eb4e222 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/techdocs-common", "description": "No longer maintained. Use @backstage/plugin-techdocs-node instead.", - "version": "0.11.15", + "version": "0.11.16-next.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -36,7 +36,7 @@ "url": "https://github.com/backstage/backstage/issues" }, "dependencies": { - "@backstage/plugin-techdocs-node": "1.1.1" + "@backstage/plugin-techdocs-node": "1.1.2-next.0" }, "devDependencies": {}, "jest": { diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 870882463f..a532b78ccb 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -55,7 +55,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "msw": "^0.35.0" diff --git a/packages/theme/package.json b/packages/theme/package.json index 537b6bab96..d10999fefb 100644 --- a/packages/theme/package.json +++ b/packages/theme/package.json @@ -36,7 +36,7 @@ "@material-ui/core": "^4.12.2" }, "devDependencies": { - "@backstage/cli": "^0.17.1-next.0" + "@backstage/cli": "^0.17.2-next.0" }, "files": [ "dist" diff --git a/packages/types/package.json b/packages/types/package.json index 112247fefe..5ea10709ec 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -34,7 +34,7 @@ }, "dependencies": {}, "devDependencies": { - "@backstage/cli": "^0.17.1-next.0", + "@backstage/cli": "^0.17.2-next.0", "@types/zen-observable": "^0.8.0", "zen-observable": "^0.8.15" }, diff --git a/packages/version-bridge/package.json b/packages/version-bridge/package.json index c7fdf46951..81ec8017a1 100644 --- a/packages/version-bridge/package.json +++ b/packages/version-bridge/package.json @@ -37,7 +37,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1-next.0", + "@backstage/cli": "^0.17.2-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/react-hooks": "^8.0.0" diff --git a/plugins/adr-backend/CHANGELOG.md b/plugins/adr-backend/CHANGELOG.md index 20b2cf507c..3512ef037c 100644 --- a/plugins/adr-backend/CHANGELOG.md +++ b/plugins/adr-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-adr-backend +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-adr-common@0.1.1-next.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/adr-backend/package.json b/plugins/adr-backend/package.json index bc7526754e..ecdd9dce29 100644 --- a/plugins/adr-backend/package.json +++ b/plugins/adr-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-adr-backend", - "version": "0.1.0", + "version": "0.1.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,13 +29,13 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", - "@backstage/plugin-adr-common": "^0.1.0", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/plugin-adr-common": "^0.1.1-next.0", "@backstage/plugin-search-common": "^0.3.4", "luxon": "^2.0.2", "marked": "^4.0.14", @@ -44,7 +44,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/marked": "^4.0.0", "@types/supertest": "^2.0.8", "supertest": "^6.1.3", diff --git a/plugins/adr-common/CHANGELOG.md b/plugins/adr-common/CHANGELOG.md index 506d2b3d3d..919cedf687 100644 --- a/plugins/adr-common/CHANGELOG.md +++ b/plugins/adr-common/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-adr-common +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/integration@1.2.1-next.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/adr-common/package.json b/plugins/adr-common/package.json index 69774b4f9a..5cd2bd24ca 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.1.0", + "version": "0.1.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,11 +31,11 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/integration": "^1.2.0", + "@backstage/integration": "^1.2.1-next.0", "@backstage/plugin-search-common": "^0.3.4" }, "devDependencies": { - "@backstage/cli": "^0.17.1" + "@backstage/cli": "^0.17.2-next.0" }, "files": [ "dist" diff --git a/plugins/adr/CHANGELOG.md b/plugins/adr/CHANGELOG.md index 735f333b2f..6e1c25cd2d 100644 --- a/plugins/adr/CHANGELOG.md +++ b/plugins/adr/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-adr +## 0.1.1-next.0 + +### Patch Changes + +- a6458a120b: Adding term highlighting support to `AdrSearchResultListItem` +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration-react@1.1.1-next.0 + - @backstage/plugin-adr-common@0.1.1-next.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/adr/package.json b/plugins/adr/package.json index 2274bdebd4..b63f5818ed 100644 --- a/plugins/adr/package.json +++ b/plugins/adr/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-adr", - "version": "0.1.0", + "version": "0.1.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,11 +22,11 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/integration-react": "^1.1.0", - "@backstage/plugin-adr-common": "^0.1.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/integration-react": "^1.1.1-next.0", + "@backstage/plugin-adr-common": "^0.1.1-next.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/plugin-search-common": "^0.3.4", "@backstage/plugin-search-react": "^0.2.0", "@backstage/theme": "^0.2.15", @@ -44,9 +44,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/airbrake-backend/CHANGELOG.md b/plugins/airbrake-backend/CHANGELOG.md index 2e03a33bee..4ca71b2871 100644 --- a/plugins/airbrake-backend/CHANGELOG.md +++ b/plugins/airbrake-backend/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-airbrake-backend +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + ## 0.2.5 ### Patch Changes diff --git a/plugins/airbrake-backend/package.json b/plugins/airbrake-backend/package.json index c5f118d7f5..5b788ecfff 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.5", + "version": "0.2.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,7 +22,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@types/express": "*", "express": "^4.17.1", @@ -33,7 +33,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", diff --git a/plugins/airbrake/CHANGELOG.md b/plugins/airbrake/CHANGELOG.md index 8ded2c050d..5c0915e8f7 100644 --- a/plugins/airbrake/CHANGELOG.md +++ b/plugins/airbrake/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-airbrake +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/dev-utils@1.0.3-next.0 + ## 0.3.5 ### Patch Changes diff --git a/plugins/airbrake/package.json b/plugins/airbrake/package.json index 0751a66003..b2e8d21eca 100644 --- a/plugins/airbrake/package.json +++ b/plugins/airbrake/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-airbrake", - "version": "0.3.5", + "version": "0.3.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,10 +24,10 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/dev-utils": "^1.0.3-next.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/test-utils": "^1.1.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -40,9 +40,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/app-defaults": "^1.0.2", - "@backstage/cli": "^0.17.1", - "@backstage/dev-utils": "^1.0.2", + "@backstage/app-defaults": "^1.0.3-next.0", + "@backstage/cli": "^0.17.2-next.0", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/allure/CHANGELOG.md b/plugins/allure/CHANGELOG.md index 5f93922e09..525b1978dd 100644 --- a/plugins/allure/CHANGELOG.md +++ b/plugins/allure/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-allure +## 0.1.22-next.0 + +### Patch Changes + +- 6387b7a98a: Add export for `isAllureReportAvailable` and `ALLURE_PROJECT_ID_ANNOTATION` so it can be used outside of plugin +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.1.21 ### Patch Changes diff --git a/plugins/allure/package.json b/plugins/allure/package.json index bc9ef8ce87..60a27654c1 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.21", + "version": "0.1.22-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,9 +26,9 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -40,9 +40,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/analytics-module-ga/CHANGELOG.md b/plugins/analytics-module-ga/CHANGELOG.md index fdb73801a4..488b27d75e 100644 --- a/plugins/analytics-module-ga/CHANGELOG.md +++ b/plugins/analytics-module-ga/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-analytics-module-ga +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 0.1.16 ### Patch Changes diff --git a/plugins/analytics-module-ga/package.json b/plugins/analytics-module-ga/package.json index fbe2d0a15e..2c764e740c 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.16", + "version": "0.1.17-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,7 +25,7 @@ }, "dependencies": { "@backstage/config": "^1.0.1", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -38,9 +38,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/apache-airflow/CHANGELOG.md b/plugins/apache-airflow/CHANGELOG.md index e23ede0cde..3c2baafd17 100644 --- a/plugins/apache-airflow/CHANGELOG.md +++ b/plugins/apache-airflow/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-apache-airflow +## 0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 0.1.13 ### Patch Changes diff --git a/plugins/apache-airflow/package.json b/plugins/apache-airflow/package.json index 80403da6e4..32bc6e21dd 100644 --- a/plugins/apache-airflow/package.json +++ b/plugins/apache-airflow/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-apache-airflow", - "version": "0.1.13", + "version": "0.1.14-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,7 +23,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -36,9 +36,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index 053c7d2421..9a630b5534 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-api-docs +## 0.8.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/plugin-catalog@1.2.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.8.5 ### Patch Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 5ac5949179..dcace08715 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.5", + "version": "0.8.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,10 +35,10 @@ "dependencies": { "@asyncapi/react-component": "1.0.0-next.38", "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog": "^1.2.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog": "^1.2.1-next.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -57,9 +57,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/app-backend/CHANGELOG.md b/plugins/app-backend/CHANGELOG.md index 464ad88629..f781c0b0d1 100644 --- a/plugins/app-backend/CHANGELOG.md +++ b/plugins/app-backend/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-app-backend +## 0.3.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + ## 0.3.32 ### Patch Changes diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 3d98c1ba5a..68aa3301a4 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-app-backend", "description": "A Backstage backend plugin that serves the Backstage frontend app", - "version": "0.3.32", + "version": "0.3.33-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,7 +33,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config-loader": "^1.1.1", "@backstage/config": "^1.0.1", "@backstage/types": "^1.0.0", @@ -50,8 +50,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", "@backstage/types": "^1.0.0", "@types/supertest": "^2.0.8", "mock-fs": "^5.1.0", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index 6c1ea681a1..a5ead31405 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-auth-backend +## 0.14.1-next.0 + +### Patch Changes + +- f6aae90e4e: Added configurable algorithm field for TokenFactory +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + ## 0.14.0 ### Minor Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index fbb5729843..99ce0ff6bd 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.14.0", + "version": "0.14.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,8 +33,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/plugin-auth-node": "^0.2.1", - "@backstage/backend-common": "^0.13.3", + "@backstage/plugin-auth-node": "^0.2.2-next.0", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", @@ -76,8 +76,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/auth-node/CHANGELOG.md b/plugins/auth-node/CHANGELOG.md index 34deb4db30..a4148a807f 100644 --- a/plugins/auth-node/CHANGELOG.md +++ b/plugins/auth-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-auth-node +## 0.2.2-next.0 + +### Patch Changes + +- 9079a78078: Added configurable algorithms array for IdentityClient +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + ## 0.2.1 ### Patch Changes diff --git a/plugins/auth-node/package.json b/plugins/auth-node/package.json index 301843239b..27d0575417 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.1", + "version": "0.2.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,7 +23,7 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", "jose": "^4.6.0", @@ -31,7 +31,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "lodash": "^4.17.21", "msw": "^0.35.0", "uuid": "^8.0.0" diff --git a/plugins/azure-devops-backend/CHANGELOG.md b/plugins/azure-devops-backend/CHANGELOG.md index 2755a273a5..8986381478 100644 --- a/plugins/azure-devops-backend/CHANGELOG.md +++ b/plugins/azure-devops-backend/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-azure-devops-backend +## 0.3.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + ## 0.3.11 ### Patch Changes diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index 852e9bf03a..03c91c6394 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.11", + "version": "0.3.12-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,7 +23,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@backstage/plugin-azure-devops-common": "^0.2.3", "@types/express": "^4.17.6", @@ -35,7 +35,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", "msw": "^0.35.0" diff --git a/plugins/azure-devops-common/package.json b/plugins/azure-devops-common/package.json index 50fe55aa34..0f52082db6 100644 --- a/plugins/azure-devops-common/package.json +++ b/plugins/azure-devops-common/package.json @@ -32,7 +32,7 @@ "clean": "backstage-cli package clean" }, "devDependencies": { - "@backstage/cli": "^0.17.1" + "@backstage/cli": "^0.17.2-next.0" }, "files": [ "dist" diff --git a/plugins/azure-devops/CHANGELOG.md b/plugins/azure-devops/CHANGELOG.md index 3c5466611e..dd20dafa5b 100644 --- a/plugins/azure-devops/CHANGELOG.md +++ b/plugins/azure-devops/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-azure-devops +## 0.1.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.1.21 ### Patch Changes diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json index 9ddf73f5e4..5990a9501a 100644 --- a/plugins/azure-devops/package.json +++ b/plugins/azure-devops/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-azure-devops", - "version": "0.1.21", + "version": "0.1.22-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,11 +31,11 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", "@backstage/plugin-azure-devops-common": "^0.2.3", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -49,9 +49,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/badges-backend/CHANGELOG.md b/plugins/badges-backend/CHANGELOG.md index 0a40812f64..fb19813272 100644 --- a/plugins/badges-backend/CHANGELOG.md +++ b/plugins/badges-backend/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-badges-backend +## 0.1.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + ## 0.1.26 ### Patch Changes diff --git a/plugins/badges-backend/package.json b/plugins/badges-backend/package.json index cae6a418fd..6b118e0152 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.26", + "version": "0.1.27-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,7 +34,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", @@ -48,7 +48,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md index 33a7b76a73..b49838ec3f 100644 --- a/plugins/badges/CHANGELOG.md +++ b/plugins/badges/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-badges +## 0.2.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.2.29 ### Patch Changes diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 618e6ae7f4..b7be771e19 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.29", + "version": "0.2.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -46,9 +46,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/bazaar-backend/CHANGELOG.md b/plugins/bazaar-backend/CHANGELOG.md index 31fd97df3d..90163bd7c0 100644 --- a/plugins/bazaar-backend/CHANGELOG.md +++ b/plugins/bazaar-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-bazaar-backend +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/backend-test-utils@0.1.25-next.0 + ## 0.1.16 ### Patch Changes diff --git a/plugins/bazaar-backend/package.json b/plugins/bazaar-backend/package.json index 8a940e5045..7296a73f82 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.16", + "version": "0.1.17-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,8 +23,8 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", - "@backstage/backend-test-utils": "^0.1.24", + "@backstage/backend-common": "^0.13.6-next.0", + "@backstage/backend-test-utils": "^0.1.25-next.0", "@backstage/config": "^1.0.1", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -34,7 +34,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1" + "@backstage/cli": "^0.17.2-next.0" }, "files": [ "dist", diff --git a/plugins/bazaar/CHANGELOG.md b/plugins/bazaar/CHANGELOG.md index 3ae149ecfb..6a7c6874cf 100644 --- a/plugins/bazaar/CHANGELOG.md +++ b/plugins/bazaar/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-bazaar +## 0.1.21-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/plugin-catalog@1.2.1-next.0 + - @backstage/cli@0.17.2-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.1.20 ### Patch Changes diff --git a/plugins/bazaar/package.json b/plugins/bazaar/package.json index ca26e3c800..440309d9f3 100644 --- a/plugins/bazaar/package.json +++ b/plugins/bazaar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bazaar", - "version": "0.1.20", + "version": "0.1.21-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,11 +26,11 @@ "dependencies": { "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", - "@backstage/cli": "^0.17.1", - "@backstage/core-components": "^0.9.4", + "@backstage/cli": "^0.17.2-next.0", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog": "^1.2.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog": "^1.2.1-next.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@date-io/luxon": "1.x", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -47,8 +47,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", - "@backstage/dev-utils": "^1.0.2", + "@backstage/cli": "^0.17.2-next.0", + "@backstage/dev-utils": "^1.0.3-next.0", "@testing-library/jest-dom": "^5.10.1", "cross-fetch": "^3.1.5" }, diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md index 5da5158f2a..7dc7b7830e 100644 --- a/plugins/bitrise/CHANGELOG.md +++ b/plugins/bitrise/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-bitrise +## 0.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.1.32 ### Patch Changes diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index 26a652d2a0..81bd158b7d 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.32", + "version": "0.1.33-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,9 +25,9 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -43,9 +43,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/catalog-backend-module-aws/CHANGELOG.md b/plugins/catalog-backend-module-aws/CHANGELOG.md index 58c5b25492..ee96ae5a1c 100644 --- a/plugins/catalog-backend-module-aws/CHANGELOG.md +++ b/plugins/catalog-backend-module-aws/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-aws +## 0.1.6-next.0 + +### Patch Changes + +- eb2544b21b: Inline config interfaces +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + ## 0.1.5 ### Patch Changes diff --git a/plugins/catalog-backend-module-aws/package.json b/plugins/catalog-backend-module-aws/package.json index 8f340f3f8a..4d904bb58b 100644 --- a/plugins/catalog-backend-module-aws/package.json +++ b/plugins/catalog-backend-module-aws/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-aws", "description": "A Backstage catalog backend module that helps integrate towards AWS", - "version": "0.1.5", + "version": "0.1.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,13 +33,13 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", - "@backstage/backend-tasks": "^0.3.1", + "@backstage/backend-common": "^0.13.6-next.0", + "@backstage/backend-tasks": "^0.3.2-next.0", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", - "@backstage/plugin-catalog-backend": "^1.1.2", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@backstage/types": "^1.0.0", "aws-sdk": "^2.840.0", "lodash": "^4.17.21", @@ -48,7 +48,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/lodash": "^4.14.151", "aws-sdk-mock": "^5.2.1", "yaml": "^1.9.2" diff --git a/plugins/catalog-backend-module-azure/CHANGELOG.md b/plugins/catalog-backend-module-azure/CHANGELOG.md index c01db4760c..8b3644ba3e 100644 --- a/plugins/catalog-backend-module-azure/CHANGELOG.md +++ b/plugins/catalog-backend-module-azure/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-azure +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-azure/package.json b/plugins/catalog-backend-module-azure/package.json index 9d5d56ac73..2fd95ef97b 100644 --- a/plugins/catalog-backend-module-azure/package.json +++ b/plugins/catalog-backend-module-azure/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-azure", "description": "A Backstage catalog backend module that helps integrate towards Azure", - "version": "0.1.3", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", - "@backstage/plugin-catalog-backend": "^1.1.2", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@backstage/types": "^1.0.0", "lodash": "^4.17.21", "msw": "^0.35.0", @@ -46,8 +46,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md index 868d27b750..5a430eeef3 100644 --- a/plugins/catalog-backend-module-bitbucket/CHANGELOG.md +++ b/plugins/catalog-backend-module-bitbucket/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-catalog-backend-module-bitbucket +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json index e433cdda15..51f5a6e603 100644 --- a/plugins/catalog-backend-module-bitbucket/package.json +++ b/plugins/catalog-backend-module-bitbucket/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-bitbucket", "description": "A Backstage catalog backend module that helps integrate towards Bitbucket", - "version": "0.1.3", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", - "@backstage/plugin-catalog-backend": "^1.1.2", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@backstage/types": "^1.0.0", "lodash": "^4.17.21", "msw": "^0.35.0", @@ -46,8 +46,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-gerrit/CHANGELOG.md b/plugins/catalog-backend-module-gerrit/CHANGELOG.md index 971e71820a..829049e574 100644 --- a/plugins/catalog-backend-module-gerrit/CHANGELOG.md +++ b/plugins/catalog-backend-module-gerrit/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-backend-module-gerrit +## 0.1.1-next.0 + +### Patch Changes + +- eb2544b21b: Inline config interfaces +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/catalog-backend-module-gerrit/package.json b/plugins/catalog-backend-module-gerrit/package.json index d2bb7ab8e6..3d8c51a9c3 100644 --- a/plugins/catalog-backend-module-gerrit/package.json +++ b/plugins/catalog-backend-module-gerrit/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-gerrit", - "version": "0.1.0", + "version": "0.1.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,13 +28,13 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", - "@backstage/backend-tasks": "^0.3.1", + "@backstage/backend-common": "^0.13.6-next.0", + "@backstage/backend-tasks": "^0.3.2-next.0", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", - "@backstage/plugin-catalog-backend": "^1.1.2", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "fs-extra": "10.1.0", "msw": "^0.35.0", "node-fetch": "^2.6.7", @@ -42,8 +42,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", "@types/fs-extra": "^9.0.1" }, "files": [ diff --git a/plugins/catalog-backend-module-github/CHANGELOG.md b/plugins/catalog-backend-module-github/CHANGELOG.md index f3b676f218..cafb596aad 100644 --- a/plugins/catalog-backend-module-github/CHANGELOG.md +++ b/plugins/catalog-backend-module-github/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-backend-module-github +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-github/package.json b/plugins/catalog-backend-module-github/package.json index b0e29952de..12dfaf8e4e 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.3", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,13 +33,13 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", - "@backstage/backend-tasks": "^0.3.1", + "@backstage/backend-common": "^0.13.6-next.0", + "@backstage/backend-tasks": "^0.3.2-next.0", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", - "@backstage/plugin-catalog-backend": "^1.1.2", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@backstage/types": "^1.0.0", "@octokit/graphql": "^4.5.8", "lodash": "^4.17.21", @@ -49,8 +49,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-gitlab/CHANGELOG.md b/plugins/catalog-backend-module-gitlab/CHANGELOG.md index bd5e58522f..bbe917876e 100644 --- a/plugins/catalog-backend-module-gitlab/CHANGELOG.md +++ b/plugins/catalog-backend-module-gitlab/CHANGELOG.md @@ -1,5 +1,25 @@ # @backstage/plugin-catalog-backend-module-gitlab +## 0.1.4-next.0 + +### Patch Changes + +- 3ac4522537: do not create location object if file with component definition do not exists in project, that decrease count of request to gitlab with 404 status code. Now we can create processor with new flag to enable this logic: + + ```ts + const processor = GitLabDiscoveryProcessor.fromConfig(config, { + logger, + skipReposWithoutExactFileMatch: true, + }); + ``` + + **WARNING:** This new functionality does not support globs in the repo file path + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-gitlab/package.json b/plugins/catalog-backend-module-gitlab/package.json index ff285efe69..a273f936cf 100644 --- a/plugins/catalog-backend-module-gitlab/package.json +++ b/plugins/catalog-backend-module-gitlab/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-backend-module-gitlab", "description": "A Backstage catalog backend module that helps integrate towards GitLab", - "version": "0.1.3", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,12 +33,12 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", - "@backstage/plugin-catalog-backend": "^1.1.2", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@backstage/types": "^1.0.0", "lodash": "^4.17.21", "msw": "^0.35.0", @@ -46,8 +46,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-ldap/CHANGELOG.md b/plugins/catalog-backend-module-ldap/CHANGELOG.md index 072d858124..f6e9f490aa 100644 --- a/plugins/catalog-backend-module-ldap/CHANGELOG.md +++ b/plugins/catalog-backend-module-ldap/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-backend-module-ldap +## 0.5.0-next.0 + +### Minor Changes + +- 1f83f0bc84: Added the possibility to pass TLS configuration to ldap connection + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + ## 0.4.3 ### Patch Changes diff --git a/plugins/catalog-backend-module-ldap/package.json b/plugins/catalog-backend-module-ldap/package.json index 7bde21862d..57de531d68 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.4.3", + "version": "0.5.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,11 +33,11 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-tasks": "^0.3.1", + "@backstage/backend-tasks": "^0.3.2-next.0", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/plugin-catalog-backend": "^1.1.2", + "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@backstage/types": "^1.0.0", "@types/ldapjs": "^2.2.0", "ldapjs": "^2.2.0", @@ -46,7 +46,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/lodash": "^4.14.151" }, "files": [ diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md index 3a88e1f3e5..62d5cfd507 100644 --- a/plugins/catalog-backend-module-msgraph/CHANGELOG.md +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-backend-module-msgraph +## 0.3.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + ## 0.3.2 ### Patch Changes diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index d9f8b9753a..c9c9b75962 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.3.2", + "version": "0.3.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,10 +34,10 @@ }, "dependencies": { "@azure/msal-node": "^1.1.0", - "@backstage/backend-tasks": "^0.3.1", + "@backstage/backend-tasks": "^0.3.2-next.0", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", - "@backstage/plugin-catalog-backend": "^1.1.2", + "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@microsoft/microsoft-graph-types": "^2.6.0", "@types/node-fetch": "^2.5.12", "lodash": "^4.17.21", @@ -48,9 +48,9 @@ "qs": "^6.9.4" }, "devDependencies": { - "@backstage/backend-common": "^0.13.3", - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", + "@backstage/backend-common": "^0.13.6-next.0", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", "@types/lodash": "^4.14.151", "msw": "^0.35.0" }, diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 44e470750d..684355bd0f 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,28 @@ # @backstage/plugin-catalog-backend +## 1.2.0-next.0 + +### Minor Changes + +- b594679ae3: Allow array as non-spread arguments at the `CatalogBuilder`. + + ```typescript + builder.addEntityProvider(...getArrayOfProviders()); + ``` + + can be simplified to + + ```typescript + builder.addEntityProvider(getArrayOfProviders()); + ``` + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-permission-node@0.6.2-next.0 + ## 1.1.2 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index c9781c1b99..e35590838f 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.1.2", + "version": "1.2.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,15 +34,15 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", + "@backstage/integration": "^1.2.1-next.0", "@backstage/plugin-catalog-common": "^1.0.2", "@backstage/plugin-permission-common": "^0.6.1", - "@backstage/plugin-permission-node": "^0.6.1", + "@backstage/plugin-permission-node": "^0.6.2-next.0", "@backstage/plugin-scaffolder-common": "^1.1.0", "@backstage/plugin-search-common": "^0.3.4", "@backstage/types": "^1.0.0", @@ -68,10 +68,10 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", "@backstage/plugin-permission-common": "^0.6.1", - "@backstage/plugin-search-backend-node": "0.6.1", + "@backstage/plugin-search-backend-node": "0.6.2-next.0", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", "@types/lodash": "^4.14.151", diff --git a/plugins/catalog-common/package.json b/plugins/catalog-common/package.json index 41c9c1d3de..e41f9a457c 100644 --- a/plugins/catalog-common/package.json +++ b/plugins/catalog-common/package.json @@ -38,7 +38,7 @@ "@backstage/search-common": "^0.3.4" }, "devDependencies": { - "@backstage/cli": "^0.17.1" + "@backstage/cli": "^0.17.2-next.0" }, "files": [ "dist", diff --git a/plugins/catalog-graph/CHANGELOG.md b/plugins/catalog-graph/CHANGELOG.md index a6b285f65c..e8b13a6dae 100644 --- a/plugins/catalog-graph/CHANGELOG.md +++ b/plugins/catalog-graph/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-catalog-graph +## 0.2.18-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.2.17 ### Patch Changes diff --git a/plugins/catalog-graph/package.json b/plugins/catalog-graph/package.json index 55458a005b..8bc30c3a03 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.17", + "version": "0.2.18-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,9 +26,9 @@ "dependencies": { "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -45,10 +45,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", - "@backstage/plugin-catalog": "^1.2.0", + "@backstage/cli": "^0.17.2-next.0", + "@backstage/plugin-catalog": "^1.2.1-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@backstage/types": "^1.0.0", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index efbd6502cd..848037bf53 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -46,7 +46,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/test-utils": "^1.1.0", "@graphql-codegen/cli": "^2.3.1", "@graphql-codegen/graphql-modules-preset": "^2.3.2", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index d0e07747e2..35304326a5 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-catalog-import +## 0.8.9-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/integration-react@1.1.1-next.0 + ## 0.8.8 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 6b9defebd8..b15faada11 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-import", "description": "A Backstage plugin the helps you import entities into your catalog", - "version": "0.8.8", + "version": "0.8.9-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,12 +37,12 @@ "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", - "@backstage/integration-react": "^1.1.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/integration-react": "^1.1.1-next.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", @@ -60,9 +60,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index acc6123d20..cd3ec488b8 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-catalog-react +## 1.1.1-next.0 + +### Patch Changes + +- 1f70704580: Accessibility updates: + + - Wrapped the `EntityLifecyclePicker`, `EntityOwnerPicker`, `EntityTagPicker`, in `label` elements + - Changed group name `Typography` component to `span` (from default `h6`), added `aria-label` to the `List` component, and `role` of `menuitem` to the container of the `MenuItem` component + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + ## 1.1.0 ### Minor Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index f661f363d0..4749275a44 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-catalog-react", "description": "A frontend library that helps other Backstage plugins interact with the catalog", - "version": "1.1.0", + "version": "1.1.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,10 +36,10 @@ "dependencies": { "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", + "@backstage/integration": "^1.2.1-next.0", "@backstage/plugin-catalog-common": "^1.0.2", "@backstage/plugin-permission-common": "^0.6.1", "@backstage/plugin-permission-react": "^0.4.1", @@ -63,7 +63,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", "@backstage/plugin-catalog-common": "^1.0.2", "@backstage/plugin-scaffolder-common": "^1.1.0", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index 4ca3cb0a29..5334d0156b 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-catalog +## 1.2.1-next.0 + +### Patch Changes + +- 449dcef98e: Updates the `isKind`, `ìsComponentType`, and `isNamespace` to allow an array of possible values +- 1f70704580: Accessibility updates: + + - Added screen reader elements to describe default table `Action` buttons + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration-react@1.1.1-next.0 + ## 1.2.0 ### Minor Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 5331618829..c7c160931a 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.2.0", + "version": "1.2.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,12 +36,12 @@ "dependencies": { "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/integration-react": "^1.1.0", + "@backstage/integration-react": "^1.1.1-next.0", "@backstage/plugin-catalog-common": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/plugin-search-common": "^0.3.4", "@backstage/plugin-search-react": "^0.2.0", "@backstage/theme": "^0.2.15", @@ -61,9 +61,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/plugin-permission-react": "^0.4.1", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", diff --git a/plugins/cicd-statistics-module-gitlab/CHANGELOG.md b/plugins/cicd-statistics-module-gitlab/CHANGELOG.md index 0f8a185cd2..4f63055238 100644 --- a/plugins/cicd-statistics-module-gitlab/CHANGELOG.md +++ b/plugins/cicd-statistics-module-gitlab/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-cicd-statistics-module-gitlab +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-cicd-statistics@0.1.8-next.0 + ## 0.1.1 ### Patch Changes diff --git a/plugins/cicd-statistics-module-gitlab/package.json b/plugins/cicd-statistics-module-gitlab/package.json index b6fa60eb31..2a2d7f4b71 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.1", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,7 +29,7 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/plugin-cicd-statistics": "^0.1.7", + "@backstage/plugin-cicd-statistics": "^0.1.8-next.0", "@gitbeaker/browser": "^35.6.0", "@gitbeaker/core": "^35.6.0", "luxon": "^2.0.2", @@ -38,7 +38,7 @@ "@backstage/catalog-model": "^1.0.2" }, "devDependencies": { - "@backstage/cli": "^0.17.1" + "@backstage/cli": "^0.17.2-next.0" }, "files": [ "dist" diff --git a/plugins/cicd-statistics/CHANGELOG.md b/plugins/cicd-statistics/CHANGELOG.md index 0f640057b3..be3d651aa3 100644 --- a/plugins/cicd-statistics/CHANGELOG.md +++ b/plugins/cicd-statistics/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-cicd-statistics +## 0.1.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + ## 0.1.7 ### Patch Changes diff --git a/plugins/cicd-statistics/package.json b/plugins/cicd-statistics/package.json index 78599b558d..09910abee7 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.7", + "version": "0.1.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -39,7 +39,7 @@ "dependencies": { "@backstage/catalog-model": "^1.0.2", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@date-io/luxon": "^1.3.13", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.11.2", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index 130a6650d7..dede64a7f6 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-circleci +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.3.5 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 627ae10ff6..ee8ee6d27f 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.5", + "version": "0.3.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,9 +36,9 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -55,9 +55,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index 742eef4f96..f7b6db530f 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-cloudbuild +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.3.5 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 0c316f0f47..924b9ce21e 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.5", + "version": "0.3.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,9 +35,9 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -52,9 +52,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/code-climate/CHANGELOG.md b/plugins/code-climate/CHANGELOG.md index 83cc86ebfb..b160bdcd4c 100644 --- a/plugins/code-climate/CHANGELOG.md +++ b/plugins/code-climate/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-code-climate +## 0.1.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.1.5 ### Patch Changes diff --git a/plugins/code-climate/package.json b/plugins/code-climate/package.json index ef18d12bef..0765c7aec4 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.5", + "version": "0.1.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,9 +24,9 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -40,8 +40,8 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", - "@backstage/dev-utils": "^1.0.2", + "@backstage/cli": "^0.17.2-next.0", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/code-coverage-backend/CHANGELOG.md b/plugins/code-coverage-backend/CHANGELOG.md index ecebc0c82f..51875df915 100644 --- a/plugins/code-coverage-backend/CHANGELOG.md +++ b/plugins/code-coverage-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-code-coverage-backend +## 0.1.31-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + ## 0.1.30 ### Patch Changes diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index 2c3efa027a..f3bd764000 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.1.30", + "version": "0.1.31-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,12 +23,12 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", + "@backstage/integration": "^1.2.1-next.0", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -39,7 +39,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/express-xml-bodyparser": "^0.3.2", "@types/supertest": "^2.0.8", "msw": "^0.35.0", diff --git a/plugins/code-coverage/CHANGELOG.md b/plugins/code-coverage/CHANGELOG.md index 1e1ea8d1c7..a3a9a80347 100644 --- a/plugins/code-coverage/CHANGELOG.md +++ b/plugins/code-coverage/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-code-coverage +## 0.1.33-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.1.32 ### Patch Changes diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index d2163e9a90..b01de562eb 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.1.32", + "version": "0.1.33-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,10 +26,10 @@ "dependencies": { "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -46,9 +46,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/codescene/CHANGELOG.md b/plugins/codescene/CHANGELOG.md index 9e8705c859..40f8a2a3cb 100644 --- a/plugins/codescene/CHANGELOG.md +++ b/plugins/codescene/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-codescene +## 0.1.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 0.1.0 ### Minor Changes diff --git a/plugins/codescene/package.json b/plugins/codescene/package.json index d0acded7e4..670231908b 100644 --- a/plugins/codescene/package.json +++ b/plugins/codescene/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-codescene", - "version": "0.1.0", + "version": "0.1.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,7 +23,7 @@ }, "dependencies": { "@backstage/config": "^1.0.1", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", "@backstage/theme": "^0.2.15", @@ -38,9 +38,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index 026f3a66e0..7f5e8b4064 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-config-schema +## 0.1.29-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 0.1.28 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index 3ea1eacb8a..4c2b64eb2b 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.28", + "version": "0.1.29-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,7 +25,7 @@ }, "dependencies": { "@backstage/config": "^1.0.1", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", "@backstage/theme": "^0.2.15", @@ -41,9 +41,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index 7cc0c60e62..0ac6a460f3 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-cost-insights +## 0.11.28-next.0 + +### Patch Changes + +- eb2544b21b: Add missing `export` in configuration schema. +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 0.11.27 ### Patch Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index f19739436e..46fd3f4bd8 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.27", + "version": "0.11.28-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,7 +36,7 @@ "dependencies": { "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -60,9 +60,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/example-todo-list-backend/CHANGELOG.md b/plugins/example-todo-list-backend/CHANGELOG.md index f5bb0542a3..812fd07a3f 100644 --- a/plugins/example-todo-list-backend/CHANGELOG.md +++ b/plugins/example-todo-list-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @internal/plugin-todo-list-backend +## 1.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + ## 1.0.1 ### Patch Changes diff --git a/plugins/example-todo-list-backend/package.json b/plugins/example-todo-list-backend/package.json index 78600526ad..e9df66e183 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.1", + "version": "1.0.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/plugin-auth-node": "^0.2.1", + "@backstage/plugin-auth-node": "^0.2.2-next.0", "@types/express": "^4.17.6", "cross-fetch": "^3.1.5", "express": "^4.17.1", @@ -33,7 +33,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", "msw": "^0.35.0", diff --git a/plugins/example-todo-list-common/package.json b/plugins/example-todo-list-common/package.json index 8ab1270372..ca91f926ed 100644 --- a/plugins/example-todo-list-common/package.json +++ b/plugins/example-todo-list-common/package.json @@ -26,9 +26,9 @@ "@backstage/plugin-permission-common": "^0.6.1" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@types/node": "^16.11.26", "msw": "^0.35.0", diff --git a/plugins/example-todo-list/CHANGELOG.md b/plugins/example-todo-list/CHANGELOG.md index 8772497922..02dc2bf3ba 100644 --- a/plugins/example-todo-list/CHANGELOG.md +++ b/plugins/example-todo-list/CHANGELOG.md @@ -1,5 +1,12 @@ # @internal/plugin-todo-list +## 1.0.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 1.0.1 ### Patch Changes diff --git a/plugins/example-todo-list/package.json b/plugins/example-todo-list/package.json index 416514f550..36b9a72048 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.1", + "version": "1.0.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,7 +21,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -33,9 +33,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index a563b66a23..c925e024d2 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -36,8 +36,8 @@ "@backstage/core-plugin-api": "^1.0.2" }, "devDependencies": { - "@backstage/cli": "^0.17.1", - "@backstage/dev-utils": "^1.0.2", + "@backstage/cli": "^0.17.2-next.0", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index 37eb6e4a39..768a2f32b4 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-explore +## 0.3.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.3.36 ### Patch Changes diff --git a/plugins/explore/package.json b/plugins/explore/package.json index e0b2704145..a4114d9cee 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.36", + "version": "0.3.37-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,9 +35,9 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/plugin-explore-react": "^0.0.17", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -53,9 +53,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@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 fcfe424e76..90eea36890 100644 --- a/plugins/firehydrant/CHANGELOG.md +++ b/plugins/firehydrant/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-firehydrant +## 0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.1.22 ### Patch Changes diff --git a/plugins/firehydrant/package.json b/plugins/firehydrant/package.json index 9f8cb184e0..6af1ec094c 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.22", + "version": "0.1.23-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,9 +25,9 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -39,9 +39,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index 9218c857c1..a180f8fb3f 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-fossa +## 0.2.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.2.37 ### Patch Changes diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 156c5b4577..6763f32711 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.37", + "version": "0.2.38-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,10 +36,10 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -53,9 +53,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/gcalendar/CHANGELOG.md b/plugins/gcalendar/CHANGELOG.md index c8cdc093ee..5bb61d03ba 100644 --- a/plugins/gcalendar/CHANGELOG.md +++ b/plugins/gcalendar/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-gcalendar +## 0.3.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 0.3.1 ### Patch Changes diff --git a/plugins/gcalendar/package.json b/plugins/gcalendar/package.json index 0dd60a4cee..3bdad9c3b7 100644 --- a/plugins/gcalendar/package.json +++ b/plugins/gcalendar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gcalendar", - "version": "0.3.1", + "version": "0.3.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,7 +22,7 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", "@backstage/theme": "^0.2.15", @@ -43,9 +43,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/gcp-projects/CHANGELOG.md b/plugins/gcp-projects/CHANGELOG.md index 702e62b010..2cf0265e99 100644 --- a/plugins/gcp-projects/CHANGELOG.md +++ b/plugins/gcp-projects/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-gcp-projects +## 0.3.25-next.0 + +### Patch Changes + +- 6968b65ba1: Updated dependency `@react-hookz/web` to `^14.0.0`. +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 0.3.24 ### Patch Changes diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 844c8e3fb3..2edff98b4f 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.24", + "version": "0.3.25-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,7 +34,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -47,9 +47,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/git-release-manager/CHANGELOG.md b/plugins/git-release-manager/CHANGELOG.md index 2e39c9511a..21c88188fd 100644 --- a/plugins/git-release-manager/CHANGELOG.md +++ b/plugins/git-release-manager/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-git-release-manager +## 0.3.19-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + ## 0.3.18 ### Patch Changes diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 85615c1b60..d811a20779 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.18", + "version": "0.3.19-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,9 +24,9 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/integration": "^1.2.0", + "@backstage/integration": "^1.2.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -43,9 +43,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index 6a75b05d3a..50ced2075d 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-github-actions +## 0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + ## 0.5.5 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index 0bf5e604b4..c86dd6b8a0 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.5", + "version": "0.5.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,10 +37,10 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/integration": "^1.2.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -55,9 +55,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/github-deployments/CHANGELOG.md b/plugins/github-deployments/CHANGELOG.md index aacb12d487..1f613038ec 100644 --- a/plugins/github-deployments/CHANGELOG.md +++ b/plugins/github-deployments/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-github-deployments +## 0.1.37-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/integration-react@1.1.1-next.0 + ## 0.1.36 ### Patch Changes diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index d49b55150d..03ce5d840e 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.36", + "version": "0.1.37-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,12 +25,12 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", - "@backstage/integration-react": "^1.1.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/integration-react": "^1.1.1-next.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -43,9 +43,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/gitops-profiles/CHANGELOG.md b/plugins/gitops-profiles/CHANGELOG.md index de00949059..a5f577cebe 100644 --- a/plugins/gitops-profiles/CHANGELOG.md +++ b/plugins/gitops-profiles/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-gitops-profiles +## 0.3.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 0.3.23 ### Patch Changes diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index dbcde5f475..3f35eaee17 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.23", + "version": "0.3.24-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,7 +35,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -48,9 +48,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/gocd/CHANGELOG.md b/plugins/gocd/CHANGELOG.md index 3c55d7e85a..652f7eb851 100644 --- a/plugins/gocd/CHANGELOG.md +++ b/plugins/gocd/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-gocd +## 0.1.12-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.1.11 ### Patch Changes diff --git a/plugins/gocd/package.json b/plugins/gocd/package.json index 07ace204fa..d4dd8e1c87 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.11", + "version": "0.1.12-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,10 +32,10 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -49,9 +49,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/graphiql/CHANGELOG.md b/plugins/graphiql/CHANGELOG.md index 6b23c5ab93..812c6d075e 100644 --- a/plugins/graphiql/CHANGELOG.md +++ b/plugins/graphiql/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-graphiql +## 0.2.38-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 0.2.37 ### Patch Changes diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index d098cae1f3..0a33313b28 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.37", + "version": "0.2.38-next.0", "private": false, "publishConfig": { "access": "public", @@ -34,7 +34,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -49,9 +49,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/graphql-backend/CHANGELOG.md b/plugins/graphql-backend/CHANGELOG.md index bbf46ec5b0..50c88814b7 100644 --- a/plugins/graphql-backend/CHANGELOG.md +++ b/plugins/graphql-backend/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-graphql-backend +## 0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + ## 0.1.22 ### Patch Changes diff --git a/plugins/graphql-backend/package.json b/plugins/graphql-backend/package.json index 67afd2c910..4987de1b23 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.22", + "version": "0.1.23-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,7 +34,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@backstage/plugin-catalog-graphql": "^0.3.9", "@graphql-tools/schema": "^8.3.1", @@ -51,7 +51,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", "msw": "^0.35.0", "supertest": "^6.1.3" diff --git a/plugins/home/CHANGELOG.md b/plugins/home/CHANGELOG.md index d775487e53..808345cd93 100644 --- a/plugins/home/CHANGELOG.md +++ b/plugins/home/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-home +## 0.4.22-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/plugin-stack-overflow@0.1.2-next.0 + ## 0.4.21 ### Patch Changes diff --git a/plugins/home/package.json b/plugins/home/package.json index e18569b974..48b92442db 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.21", + "version": "0.4.22-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,10 +35,10 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", - "@backstage/plugin-stack-overflow": "^0.1.1", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", + "@backstage/plugin-stack-overflow": "^0.1.2-next.0", "@backstage/theme": "^0.2.15", "@backstage/config": "^1.0.1", "@material-ui/core": "^4.12.2", @@ -53,9 +53,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/ilert/CHANGELOG.md b/plugins/ilert/CHANGELOG.md index 8216fa98eb..18268c894b 100644 --- a/plugins/ilert/CHANGELOG.md +++ b/plugins/ilert/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-ilert +## 0.1.32-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.1.31 ### Patch Changes diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index be179bd2b4..b428b2213f 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.31", + "version": "0.1.32-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,10 +25,10 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@date-io/luxon": "1.x", "@material-ui/core": "^4.12.2", @@ -43,9 +43,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/jenkins-backend/CHANGELOG.md b/plugins/jenkins-backend/CHANGELOG.md index be1d788f8d..91a274d267 100644 --- a/plugins/jenkins-backend/CHANGELOG.md +++ b/plugins/jenkins-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-jenkins-backend +## 0.1.23-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + ## 0.1.22 ### Patch Changes diff --git a/plugins/jenkins-backend/package.json b/plugins/jenkins-backend/package.json index d9c28de86a..05a1564cae 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.22", + "version": "0.1.23-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,12 +25,12 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/plugin-auth-node": "^0.2.1", + "@backstage/plugin-auth-node": "^0.2.2-next.0", "@backstage/plugin-jenkins-common": "^0.1.4", "@backstage/plugin-permission-common": "^0.6.1", "@types/express": "^4.17.6", @@ -41,7 +41,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/jenkins": "^0.23.1", "@types/supertest": "^2.0.8", "msw": "^0.35.0", diff --git a/plugins/jenkins-common/package.json b/plugins/jenkins-common/package.json index 45684752be..881da46001 100644 --- a/plugins/jenkins-common/package.json +++ b/plugins/jenkins-common/package.json @@ -26,7 +26,7 @@ "@backstage/plugin-permission-common": "^0.6.1" }, "devDependencies": { - "@backstage/cli": "^0.17.1" + "@backstage/cli": "^0.17.2-next.0" }, "files": [ "dist" diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index 4d44ae6ca4..df4f61370b 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-jenkins +## 0.7.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.7.4 ### Patch Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index c8205a177a..c58eac65af 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.4", + "version": "0.7.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,10 +36,10 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/plugin-jenkins-common": "^0.1.4", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -54,9 +54,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/kafka-backend/CHANGELOG.md b/plugins/kafka-backend/CHANGELOG.md index c844f38af7..d49c7bfe5d 100644 --- a/plugins/kafka-backend/CHANGELOG.md +++ b/plugins/kafka-backend/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-kafka-backend +## 0.2.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + ## 0.2.25 ### Patch Changes diff --git a/plugins/kafka-backend/package.json b/plugins/kafka-backend/package.json index e5821ce600..e3171b20dd 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.25", + "version": "0.2.26-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,7 +35,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", @@ -47,7 +47,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/jest-when": "^3.5.0", "@types/lodash": "^4.14.151", "jest-when": "^3.1.0", diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index 4f0caebf29..b45ffc08d6 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-kafka +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.3.5 ### Patch Changes diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 3dafb4a731..492bbe48d5 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.5", + "version": "0.3.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,9 +25,9 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -39,9 +39,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/kubernetes-backend/CHANGELOG.md b/plugins/kubernetes-backend/CHANGELOG.md index 8fc9affe3a..ade3675f98 100644 --- a/plugins/kubernetes-backend/CHANGELOG.md +++ b/plugins/kubernetes-backend/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-kubernetes-backend +## 0.6.0-next.0 + +### Minor Changes + +- 4328737af6: Add support to fetch data for Stateful Sets from Kubernetes + +### Patch Changes + +- 0c70cd8e1d: cache and refresh Azure tokens to avoid excessive calls to Azure Identity +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-kubernetes-common@0.3.0-next.0 + ## 0.5.1 ### Patch Changes diff --git a/plugins/kubernetes-backend/package.json b/plugins/kubernetes-backend/package.json index 973808a157..ac992a2752 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.5.1", + "version": "0.6.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,11 +36,11 @@ }, "dependencies": { "@azure/identity": "^2.0.4", - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/plugin-kubernetes-common": "^0.2.10", + "@backstage/plugin-kubernetes-common": "^0.3.0-next.0", "@google-cloud/container": "^3.0.0", "@kubernetes/client-node": "^0.16.0", "@types/express": "^4.17.6", @@ -61,7 +61,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/aws4": "^1.5.1", "aws-sdk-mock": "^5.2.1", "supertest": "^6.1.3" diff --git a/plugins/kubernetes-common/CHANGELOG.md b/plugins/kubernetes-common/CHANGELOG.md index 65b5358733..6d688beda9 100644 --- a/plugins/kubernetes-common/CHANGELOG.md +++ b/plugins/kubernetes-common/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-kubernetes-common +## 0.3.0-next.0 + +### Minor Changes + +- 4328737af6: Add support to fetch data for Stateful Sets + ## 0.2.10 ### Patch Changes diff --git a/plugins/kubernetes-common/package.json b/plugins/kubernetes-common/package.json index 44298c0407..900c5ce8e0 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.2.10", + "version": "0.3.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -42,7 +42,7 @@ "@kubernetes/client-node": "^0.16.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1" + "@backstage/cli": "^0.17.2-next.0" }, "jest": { "roots": [ diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index 22060e3415..2cbb24885d 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-kubernetes +## 0.6.6-next.0 + +### Patch Changes + +- 4328737af6: Add support to fetch data for Stateful Sets and display an accordion in the same way as with Deployments +- 81304e3e91: Fix for HPA matching when deploying same HPA in multiple namespaces +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/plugin-kubernetes-common@0.3.0-next.0 + ## 0.6.5 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index b83e961e9c..341a5c086d 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.6.5", + "version": "0.6.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,10 +36,10 @@ "dependencies": { "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", - "@backstage/plugin-kubernetes-common": "^0.2.10", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", + "@backstage/plugin-kubernetes-common": "^0.3.0-next.0", "@backstage/theme": "^0.2.15", "@kubernetes/client-node": "^0.16.0", "@material-ui/core": "^4.12.2", @@ -57,9 +57,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index 2de9ffda82..92695ba053 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-lighthouse +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.3.5 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index f3c7d7621f..1a02356fe2 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.5", + "version": "0.3.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,9 +37,9 @@ "dependencies": { "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -51,9 +51,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/newrelic-dashboard/CHANGELOG.md b/plugins/newrelic-dashboard/CHANGELOG.md index f14da874e3..b81ad6a827 100644 --- a/plugins/newrelic-dashboard/CHANGELOG.md +++ b/plugins/newrelic-dashboard/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-newrelic-dashboard +## 0.1.14-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.1.13 ### Patch Changes diff --git a/plugins/newrelic-dashboard/package.json b/plugins/newrelic-dashboard/package.json index ad65c1538d..3b6429f8f3 100644 --- a/plugins/newrelic-dashboard/package.json +++ b/plugins/newrelic-dashboard/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-newrelic-dashboard", - "version": "0.1.13", + "version": "0.1.14-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,18 +24,18 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.17.1", - "@backstage/dev-utils": "^1.0.2", + "@backstage/cli": "^0.17.2-next.0", + "@backstage/dev-utils": "^1.0.3-next.0", "@testing-library/jest-dom": "^5.10.1", "@types/react": "^16.13.1 || ^17.0.0", "cross-fetch": "^3.1.5" diff --git a/plugins/newrelic/CHANGELOG.md b/plugins/newrelic/CHANGELOG.md index b9ff452035..375112627b 100644 --- a/plugins/newrelic/CHANGELOG.md +++ b/plugins/newrelic/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-newrelic +## 0.3.24-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 0.3.23 ### Patch Changes diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index ff48d7a40a..056447c90b 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.23", + "version": "0.3.24-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,7 +35,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -47,9 +47,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index 142c92b4a3..a9b98fc2a4 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-org +## 0.5.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.5.5 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index b7b907f888..abe9ce76e7 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.5", + "version": "0.5.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,9 +30,9 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -49,9 +49,9 @@ }, "devDependencies": { "@backstage/catalog-client": "^1.0.2", - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index 30e6171682..481dfe6ee2 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-pagerduty +## 0.3.33-next.0 + +### Patch Changes + +- 76bf6400fe: Fix alert that was not showing after creating an incident. +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.3.32 ### Patch Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index ce4df376ac..fa8f15e35e 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.3.32", + "version": "0.3.33-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,9 +35,9 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -52,9 +52,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/periskop-backend/CHANGELOG.md b/plugins/periskop-backend/CHANGELOG.md index 195afe4545..e6f003a90b 100644 --- a/plugins/periskop-backend/CHANGELOG.md +++ b/plugins/periskop-backend/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-periskop-backend +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/periskop-backend/package.json b/plugins/periskop-backend/package.json index fe60ec120c..e569d1cca2 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.3", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,7 +24,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@types/express": "*", "cross-fetch": "^3.0.6", @@ -35,7 +35,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", "msw": "^0.35.0", "supertest": "^6.1.6" diff --git a/plugins/periskop/CHANGELOG.md b/plugins/periskop/CHANGELOG.md index 33265d79c6..317fe0c21e 100644 --- a/plugins/periskop/CHANGELOG.md +++ b/plugins/periskop/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-periskop +## 0.1.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.1.3 ### Patch Changes diff --git a/plugins/periskop/package.json b/plugins/periskop/package.json index 92648392a2..32ed926e56 100644 --- a/plugins/periskop/package.json +++ b/plugins/periskop/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-periskop", - "version": "0.1.3", + "version": "0.1.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,10 +26,10 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -42,9 +42,9 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/permission-backend/CHANGELOG.md b/plugins/permission-backend/CHANGELOG.md index 4837102f27..d753d3d605 100644 --- a/plugins/permission-backend/CHANGELOG.md +++ b/plugins/permission-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-permission-backend +## 0.5.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + - @backstage/plugin-permission-node@0.6.2-next.0 + ## 0.5.7 ### Patch Changes diff --git a/plugins/permission-backend/package.json b/plugins/permission-backend/package.json index b13d2a1eef..1ac11779c4 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.7", + "version": "0.5.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,12 +22,12 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/plugin-auth-node": "^0.2.1", + "@backstage/plugin-auth-node": "^0.2.2-next.0", "@backstage/plugin-permission-common": "^0.6.1", - "@backstage/plugin-permission-node": "^0.6.1", + "@backstage/plugin-permission-node": "^0.6.2-next.0", "@types/express": "*", "dataloader": "^2.0.0", "express": "^4.17.1", @@ -39,7 +39,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/lodash": "^4.14.151", "@types/supertest": "^2.0.8", "supertest": "^6.1.6", diff --git a/plugins/permission-common/package.json b/plugins/permission-common/package.json index a3a54b8b48..19b0742a93 100644 --- a/plugins/permission-common/package.json +++ b/plugins/permission-common/package.json @@ -48,7 +48,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/jest": "^26.0.7", "msw": "^0.35.0" } diff --git a/plugins/permission-node/CHANGELOG.md b/plugins/permission-node/CHANGELOG.md index cee3143091..6a05ad66dd 100644 --- a/plugins/permission-node/CHANGELOG.md +++ b/plugins/permission-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-permission-node +## 0.6.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + ## 0.6.1 ### Patch Changes diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json index 4eba47e0e4..670b79806d 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.1", + "version": "0.6.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,10 +33,10 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/plugin-auth-node": "^0.2.1", + "@backstage/plugin-auth-node": "^0.2.2-next.0", "@backstage/plugin-permission-common": "^0.6.1", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -44,7 +44,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", "msw": "^0.35.0", "supertest": "^6.1.3" diff --git a/plugins/permission-react/package.json b/plugins/permission-react/package.json index a04506ed58..c900268432 100644 --- a/plugins/permission-react/package.json +++ b/plugins/permission-react/package.json @@ -44,7 +44,7 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index a83d570df4..c95d2c1d26 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-proxy-backend +## 0.2.27-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + ## 0.2.26 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index 2e72f6e40a..82a5dbe711 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.26", + "version": "0.2.27-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,7 +32,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@types/express": "^4.17.6", "express": "^4.17.1", @@ -46,7 +46,7 @@ "yup": "^0.32.9" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", diff --git a/plugins/rollbar-backend/CHANGELOG.md b/plugins/rollbar-backend/CHANGELOG.md index 8f74a25eba..3afea006ce 100644 --- a/plugins/rollbar-backend/CHANGELOG.md +++ b/plugins/rollbar-backend/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-rollbar-backend +## 0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + ## 0.1.29 ### Patch Changes diff --git a/plugins/rollbar-backend/package.json b/plugins/rollbar-backend/package.json index 7c22e1e0a6..14fdbc3b78 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.29", + "version": "0.1.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,7 +34,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@types/express": "^4.17.6", "camelcase-keys": "^7.0.1", @@ -50,8 +50,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", "msw": "^0.36.3", "supertest": "^6.1.3" diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index f620ebe9bb..6045712603 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-rollbar +## 0.4.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.4.5 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index c1a45ca31f..06cf4c5f65 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.5", + "version": "0.4.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,9 +36,9 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -53,9 +53,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md index 87c8ed07bb..49c6061f59 100644 --- a/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-cookiecutter/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-cookiecutter +## 0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.3.0-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + ## 0.2.7 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-cookiecutter/package.json b/plugins/scaffolder-backend-module-cookiecutter/package.json index 8dcef72d77..05d4eb4511 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.7", + "version": "0.2.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,10 +23,10 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", - "@backstage/plugin-scaffolder-backend": "^1.2.0", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/plugin-scaffolder-backend": "^1.3.0-next.0", "@backstage/config": "^1.0.1", "@backstage/types": "^1.0.0", "command-exists": "^1.2.9", @@ -35,7 +35,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/fs-extra": "^9.0.1", "@types/mock-fs": "^4.13.0", "@types/jest": "^26.0.7", diff --git a/plugins/scaffolder-backend-module-rails/CHANGELOG.md b/plugins/scaffolder-backend-module-rails/CHANGELOG.md index 80c423941b..1e4a50833b 100644 --- a/plugins/scaffolder-backend-module-rails/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-rails/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-scaffolder-backend-module-rails +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.3.0-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + ## 0.4.0 ### Minor Changes diff --git a/plugins/scaffolder-backend-module-rails/package.json b/plugins/scaffolder-backend-module-rails/package.json index ee8418f546..f25db32f10 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.0", + "version": "0.4.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,17 +24,17 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", - "@backstage/plugin-scaffolder-backend": "^1.2.0", + "@backstage/backend-common": "^0.13.6-next.0", + "@backstage/plugin-scaffolder-backend": "^1.3.0-next.0", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", + "@backstage/integration": "^1.2.1-next.0", "@backstage/types": "^1.0.0", "command-exists": "^1.2.9", "fs-extra": "^10.0.1" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/jest": "^26.0.7", "@types/node": "^16.11.26", "@types/command-exists": "^1.2.0", diff --git a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md index 32607029a7..bbb6185429 100644 --- a/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md +++ b/plugins/scaffolder-backend-module-yeoman/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-scaffolder-backend-module-yeoman +## 0.2.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-scaffolder-backend@1.3.0-next.0 + ## 0.2.5 ### Patch Changes diff --git a/plugins/scaffolder-backend-module-yeoman/package.json b/plugins/scaffolder-backend-module-yeoman/package.json index 2b3f02cf9b..e823ac0ab9 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.5", + "version": "0.2.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,13 +24,13 @@ }, "dependencies": { "@backstage/config": "^1.0.1", - "@backstage/plugin-scaffolder-backend": "^1.2.0", + "@backstage/plugin-scaffolder-backend": "^1.3.0-next.0", "@backstage/types": "^1.0.0", "winston": "^3.2.1", "yeoman-environment": "^3.9.1" }, "devDependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@types/jest": "^26.0.7" }, "files": [ diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index 98ebe6848e..62fb8e507c 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder-backend +## 1.3.0-next.0 + +### Minor Changes + +- 72dfcbc8bf: A new scaffolder action has been added: `gerrit:publish` + +### Patch Changes + +- 6901f6be4a: Adds more of an explanation when the `publish:github` scaffolder action fails to create a repository. +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-catalog-backend@1.2.0-next.0 + ## 1.2.0 ### Minor Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 0ce300638c..b2ba72dc2a 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.2.0", + "version": "1.3.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,13 +34,13 @@ "build:assets": "node scripts/build-nunjucks.js" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", - "@backstage/plugin-catalog-backend": "^1.1.2", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@backstage/plugin-scaffolder-common": "^1.1.0", "@backstage/types": "^1.0.0", "@gitbeaker/core": "^35.6.0", @@ -74,8 +74,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", "@types/command-exists": "^1.2.0", "@types/fs-extra": "^9.0.1", "@types/git-url-parse": "^9.0.0", diff --git a/plugins/scaffolder-common/package.json b/plugins/scaffolder-common/package.json index b0816d7796..77045747fa 100644 --- a/plugins/scaffolder-common/package.json +++ b/plugins/scaffolder-common/package.json @@ -43,6 +43,6 @@ "@backstage/types": "^1.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1" + "@backstage/cli": "^0.17.2-next.0" } } diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 72f9ba67a3..cce30f0b9f 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-scaffolder +## 1.3.0-next.0 + +### Minor Changes + +- 86a4a0f72d: Get data of other fields in Form from a custom field in template Scaffolder. + following: + + ```tsx + const CustomFieldExtensionComponent = (props: FieldExtensionComponentProps) => { + const { formData } = props.formContext; + ... + }; + + const CustomFieldExtension = scaffolderPlugin.provide( + createScaffolderFieldExtension({ + name: ..., + component: CustomFieldExtensionComponent, + validation: ... + }) + ); + ``` + +- 72dfcbc8bf: Gerrit Integration: Implemented a `RepoUrlPicker` for Gerrit. + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/integration-react@1.1.1-next.0 + ## 1.2.0 ### Minor Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 67548584ad..edc9185488 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.2.0", + "version": "1.3.0-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -38,13 +38,13 @@ "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", - "@backstage/integration-react": "^1.1.0", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/integration-react": "^1.1.1-next.0", "@backstage/plugin-catalog-common": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/plugin-permission-react": "^0.4.1", "@backstage/plugin-scaffolder-common": "^1.1.0", "@backstage/theme": "^0.2.15", @@ -79,10 +79,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", - "@backstage/plugin-catalog": "^1.2.0", + "@backstage/dev-utils": "^1.0.3-next.0", + "@backstage/plugin-catalog": "^1.2.1-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/search-backend-module-elasticsearch/CHANGELOG.md b/plugins/search-backend-module-elasticsearch/CHANGELOG.md index f2344d430c..35d7e7672d 100644 --- a/plugins/search-backend-module-elasticsearch/CHANGELOG.md +++ b/plugins/search-backend-module-elasticsearch/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-search-backend-module-elasticsearch +## 0.1.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-search-backend-node@0.6.2-next.0 + ## 0.1.4 ### Patch Changes diff --git a/plugins/search-backend-module-elasticsearch/package.json b/plugins/search-backend-module-elasticsearch/package.json index d06d4a8db4..fb9a82bbd5 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": "0.1.4", + "version": "0.1.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -25,7 +25,7 @@ "dependencies": { "@acuris/aws-es-connection": "^2.2.0", "@backstage/config": "^1.0.1", - "@backstage/plugin-search-backend-node": "^0.6.1", + "@backstage/plugin-search-backend-node": "^0.6.2-next.0", "@backstage/plugin-search-common": "^0.3.4", "@elastic/elasticsearch": "7.13.0", "aws-sdk": "^2.948.0", @@ -35,8 +35,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-common": "^0.13.3", - "@backstage/cli": "^0.17.1", + "@backstage/backend-common": "^0.13.6-next.0", + "@backstage/cli": "^0.17.2-next.0", "@elastic/elasticsearch-mock": "^1.0.0" }, "files": [ diff --git a/plugins/search-backend-module-pg/CHANGELOG.md b/plugins/search-backend-module-pg/CHANGELOG.md index af48846754..5462238e2c 100644 --- a/plugins/search-backend-module-pg/CHANGELOG.md +++ b/plugins/search-backend-module-pg/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search-backend-module-pg +## 0.3.4-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-search-backend-node@0.6.2-next.0 + ## 0.3.3 ### Patch Changes diff --git a/plugins/search-backend-module-pg/package.json b/plugins/search-backend-module-pg/package.json index d570ca6b74..378cdbdcc4 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.3.3", + "version": "0.3.4-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,15 +23,15 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", - "@backstage/plugin-search-backend-node": "^0.6.1", + "@backstage/backend-common": "^0.13.6-next.0", + "@backstage/plugin-search-backend-node": "^0.6.2-next.0", "@backstage/plugin-search-common": "^0.3.4", "lodash": "^4.17.21", "knex": "^1.0.2" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1" + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0" }, "files": [ "dist", diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index 1ec5f9d0e1..4c0800b160 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search-backend-node +## 0.6.2-next.0 + +### Patch Changes + +- e7794a0aaa: propagate indexing errors so they don't appear successful to the task scheduler +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + ## 0.6.1 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index 0981050d29..7a0b0631c6 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": "0.6.1", + "version": "0.6.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,7 +23,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-tasks": "^0.3.1", + "@backstage/backend-tasks": "^0.3.2-next.0", "@backstage/errors": "^1.0.0", "@backstage/plugin-search-common": "^0.3.4", "@types/lunr": "^2.3.3", @@ -34,8 +34,8 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-common": "^0.13.3", - "@backstage/cli": "^0.17.1" + "@backstage/backend-common": "^0.13.6-next.0", + "@backstage/cli": "^0.17.2-next.0" }, "files": [ "dist" diff --git a/plugins/search-backend/CHANGELOG.md b/plugins/search-backend/CHANGELOG.md index 257748f6a6..608084cda4 100644 --- a/plugins/search-backend/CHANGELOG.md +++ b/plugins/search-backend/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-search-backend +## 0.5.3-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-search-backend-node@0.6.2-next.0 + - @backstage/plugin-auth-node@0.2.2-next.0 + - @backstage/plugin-permission-node@0.6.2-next.0 + ## 0.5.2 ### Patch Changes diff --git a/plugins/search-backend/package.json b/plugins/search-backend/package.json index 173f885847..b80a3bf858 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": "0.5.2", + "version": "0.5.3-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -23,13 +23,13 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/plugin-auth-node": "^0.2.1", + "@backstage/plugin-auth-node": "^0.2.2-next.0", "@backstage/plugin-permission-common": "^0.6.1", - "@backstage/plugin-permission-node": "^0.6.1", - "@backstage/plugin-search-backend-node": "^0.6.1", + "@backstage/plugin-permission-node": "^0.6.2-next.0", + "@backstage/plugin-search-backend-node": "^0.6.2-next.0", "@backstage/plugin-search-common": "^0.3.4", "@backstage/types": "^1.0.0", "@types/express": "^4.17.6", @@ -43,7 +43,7 @@ "zod": "^3.11.6" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", "supertest": "^6.1.3" }, diff --git a/plugins/search-common/package.json b/plugins/search-common/package.json index 0e89e6d808..ad2944a8c0 100644 --- a/plugins/search-common/package.json +++ b/plugins/search-common/package.json @@ -43,7 +43,7 @@ "@backstage/plugin-permission-common": "^0.6.1" }, "devDependencies": { - "@backstage/cli": "^0.17.1" + "@backstage/cli": "^0.17.2-next.0" }, "jest": { "roots": [ diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 26bd4a37ba..f1dac81ba0 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-search +## 0.8.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.8.1 ### Patch Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 947b39ebdd..5724c2af0c 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": "0.8.1", + "version": "0.8.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,10 +35,10 @@ "dependencies": { "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/plugin-search-common": "^0.3.4", "@backstage/plugin-search-react": "^0.2.0", "@backstage/theme": "^0.2.15", @@ -57,9 +57,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index e3e437c2c4..d199b01da6 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-sentry +## 0.3.44-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.3.43 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index ed6ce645e7..40566e1f2c 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.3.43", + "version": "0.3.44-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -36,9 +36,9 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-table/core": "^3.1.0", "@material-ui/core": "^4.12.2", @@ -53,9 +53,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/shortcuts/CHANGELOG.md b/plugins/shortcuts/CHANGELOG.md index 919aacaaed..3df379decf 100644 --- a/plugins/shortcuts/CHANGELOG.md +++ b/plugins/shortcuts/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-shortcuts +## 0.2.7-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 0.2.6 ### Patch Changes diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index eb2b6b4a05..987be389ef 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.2.6", + "version": "0.2.7-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,7 +24,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/theme": "^0.2.15", "@backstage/types": "^1.0.0", @@ -42,9 +42,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index 10a4ffcd32..ae68b5ed59 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-sonarqube +## 0.3.6-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.3.5 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 30cc4e9a61..8f872c4e1c 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-sonarqube", "description": "", - "version": "0.3.5", + "version": "0.3.6-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,9 +37,9 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -53,9 +53,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/splunk-on-call/CHANGELOG.md b/plugins/splunk-on-call/CHANGELOG.md index 429bafb23e..a00b6b111a 100644 --- a/plugins/splunk-on-call/CHANGELOG.md +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-splunk-on-call +## 0.3.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.3.29 ### Patch Changes diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 3e79fcee52..2bcd01dc9c 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.29", + "version": "0.3.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -35,9 +35,9 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -51,9 +51,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/stack-overflow/CHANGELOG.md b/plugins/stack-overflow/CHANGELOG.md index bdb144c555..4c3fccd779 100644 --- a/plugins/stack-overflow/CHANGELOG.md +++ b/plugins/stack-overflow/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-stack-overflow +## 0.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + - @backstage/plugin-home@0.4.22-next.0 + ## 0.1.1 ### Patch Changes diff --git a/plugins/stack-overflow/package.json b/plugins/stack-overflow/package.json index 8fee274fb4..2632d113af 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.1", + "version": "0.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,9 +24,9 @@ }, "dependencies": { "@backstage/config": "^1.0.1", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/plugin-home": "^0.4.21", + "@backstage/plugin-home": "^0.4.22-next.0", "@backstage/plugin-search-common": "^0.3.4", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -42,9 +42,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^14.0.0", diff --git a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md index 51f1700554..e063f03eb8 100644 --- a/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md +++ b/plugins/tech-insights-backend-module-jsonfc/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-tech-insights-backend-module-jsonfc +## 0.1.17-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-tech-insights-node@0.3.1-next.0 + ## 0.1.16 ### Patch Changes diff --git a/plugins/tech-insights-backend-module-jsonfc/package.json b/plugins/tech-insights-backend-module-jsonfc/package.json index be527ea7fb..e490cfb2bb 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.16", + "version": "0.1.17-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,11 +34,11 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", "@backstage/plugin-tech-insights-common": "^0.2.4", - "@backstage/plugin-tech-insights-node": "^0.3.0", + "@backstage/plugin-tech-insights-node": "^0.3.1-next.0", "ajv": "^8.10.0", "json-rules-engine": "^6.1.2", "lodash": "^4.17.21", @@ -46,7 +46,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.17.1" + "@backstage/cli": "^0.17.2-next.0" }, "files": [ "dist" diff --git a/plugins/tech-insights-backend/CHANGELOG.md b/plugins/tech-insights-backend/CHANGELOG.md index d8f09deda3..cf309ad762 100644 --- a/plugins/tech-insights-backend/CHANGELOG.md +++ b/plugins/tech-insights-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-tech-insights-backend +## 0.4.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-tasks@0.3.2-next.0 + - @backstage/backend-common@0.13.6-next.0 + - @backstage/plugin-tech-insights-node@0.3.1-next.0 + ## 0.4.0 ### Minor Changes diff --git a/plugins/tech-insights-backend/package.json b/plugins/tech-insights-backend/package.json index 6ea1207009..64694546f0 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.4.0", + "version": "0.4.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,14 +34,14 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", - "@backstage/backend-tasks": "^0.3.1", + "@backstage/backend-common": "^0.13.6-next.0", + "@backstage/backend-tasks": "^0.3.2-next.0", "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", "@backstage/plugin-tech-insights-common": "^0.2.4", - "@backstage/plugin-tech-insights-node": "^0.3.0", + "@backstage/plugin-tech-insights-node": "^0.3.1-next.0", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -54,8 +54,8 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", "@types/semver": "^7.3.8", "supertest": "^6.1.3", diff --git a/plugins/tech-insights-common/package.json b/plugins/tech-insights-common/package.json index f2293f403e..bc9bbcd792 100644 --- a/plugins/tech-insights-common/package.json +++ b/plugins/tech-insights-common/package.json @@ -38,7 +38,7 @@ "@backstage/types": "^1.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1-next.0" + "@backstage/cli": "^0.17.2-next.0" }, "files": [ "dist" diff --git a/plugins/tech-insights-node/CHANGELOG.md b/plugins/tech-insights-node/CHANGELOG.md index 63a22350cb..f6beecb3bb 100644 --- a/plugins/tech-insights-node/CHANGELOG.md +++ b/plugins/tech-insights-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-tech-insights-node +## 0.3.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + ## 0.3.0 ### Minor Changes diff --git a/plugins/tech-insights-node/package.json b/plugins/tech-insights-node/package.json index 6d03e9f142..2ce04e01c6 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.0", + "version": "0.3.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -33,7 +33,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/config": "^1.0.1", "@backstage/plugin-tech-insights-common": "^0.2.4", "@types/luxon": "^2.0.5", @@ -41,7 +41,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.17.1" + "@backstage/cli": "^0.17.2-next.0" }, "files": [ "dist" diff --git a/plugins/tech-insights/CHANGELOG.md b/plugins/tech-insights/CHANGELOG.md index 70a3d2d3ce..53399e6b21 100644 --- a/plugins/tech-insights/CHANGELOG.md +++ b/plugins/tech-insights/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-tech-insights +## 0.2.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.2.1 ### Patch Changes diff --git a/plugins/tech-insights/package.json b/plugins/tech-insights/package.json index ab7727e8c2..461151659c 100644 --- a/plugins/tech-insights/package.json +++ b/plugins/tech-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-insights", - "version": "0.2.1", + "version": "0.2.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,10 +29,10 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/plugin-tech-insights-common": "^0.2.4", "@backstage/theme": "^0.2.15", "@backstage/types": "^1.0.0", @@ -47,9 +47,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index 77411e298e..bfd3bbad11 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-tech-radar +## 0.5.13-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 0.5.12 ### Patch Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 3a57c5bbdf..99975b5542 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.12", + "version": "0.5.13-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,7 +34,7 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -49,9 +49,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/techdocs-addons-test-utils/CHANGELOG.md b/plugins/techdocs-addons-test-utils/CHANGELOG.md index a91a722cdb..86b0ee184f 100644 --- a/plugins/techdocs-addons-test-utils/CHANGELOG.md +++ b/plugins/techdocs-addons-test-utils/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-addons-test-utils +## 1.0.1-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@1.2.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/plugin-techdocs@1.1.2-next.0 + - @backstage/plugin-techdocs-react@1.0.1-next.0 + - @backstage/integration-react@1.1.1-next.0 + ## 1.0.0 ### Major Changes diff --git a/plugins/techdocs-addons-test-utils/package.json b/plugins/techdocs-addons-test-utils/package.json index 8166bdc8f9..81e05d18c2 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.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,14 +32,14 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-app-api": "^1.0.2", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/integration-react": "^1.1.0", - "@backstage/plugin-catalog": "^1.2.0", + "@backstage/integration-react": "^1.1.1-next.0", + "@backstage/plugin-catalog": "^1.2.1-next.0", "@backstage/plugin-search-react": "^0.2.0", - "@backstage/plugin-techdocs": "^1.1.1", - "@backstage/plugin-techdocs-react": "^1.0.0", + "@backstage/plugin-techdocs": "^1.1.2-next.0", + "@backstage/plugin-techdocs-react": "^1.0.1-next.0", "@backstage/test-utils": "^1.1.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.9.13", @@ -56,8 +56,8 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", - "@backstage/dev-utils": "^1.0.2", + "@backstage/cli": "^0.17.2-next.0", + "@backstage/dev-utils": "^1.0.3-next.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", "@testing-library/user-event": "^14.0.0", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 897d308c6e..b1a86d1d1e 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,75 @@ # @backstage/plugin-techdocs-backend +## 1.1.2-next.0 + +### Patch Changes + +- 5d66d4ff67: Output logs from a TechDocs build to a logging transport in addition to existing + frontend event stream, for capturing these logs to other sources. + + This allows users to capture debugging information around why tech docs fail to build + without needing to rely on end users capturing information from their web browser. + + The most common use case is to log to the same place as the rest of the backend + application logs. + + Sample usage: + + ``` + import { DockerContainerRunner } from '@backstage/backend-common'; + import { + createRouter, + Generators, + Preparers, + Publisher, + } from '@backstage/plugin-techdocs-backend'; + import Docker from 'dockerode'; + import { Router } from 'express'; + import { PluginEnvironment } from '../types'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + const preparers = await Preparers.fromConfig(env.config, { + logger: env.logger, + reader: env.reader, + }); + + const dockerClient = new Docker(); + const containerRunner = new DockerContainerRunner({ dockerClient }); + + const generators = await Generators.fromConfig(env.config, { + logger: env.logger, + containerRunner, + }); + + const publisher = await Publisher.fromConfig(env.config, { + logger: env.logger, + discovery: env.discovery, + }); + + await publisher.getReadiness(); + + return await createRouter({ + preparers, + generators, + publisher, + logger: env.logger, + // Passing a buildLogTransport as a parameter in createRouter will enable + // capturing build logs to a backend log stream + buildLogTransport: env.logger, + config: env.config, + discovery: env.discovery, + cache: env.cache, + }); + } + ``` + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-techdocs-node@1.1.2-next.0 + ## 1.1.1 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index a90a26cc9a..9bca7b7a81 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.1.1", + "version": "1.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,16 +34,16 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", + "@backstage/integration": "^1.2.1-next.0", "@backstage/plugin-catalog-common": "^1.0.2", "@backstage/plugin-permission-common": "^0.6.1", "@backstage/plugin-search-common": "^0.3.4", - "@backstage/plugin-techdocs-node": "^1.1.1", + "@backstage/plugin-techdocs-node": "^1.1.2-next.0", "@types/express": "^4.17.6", "dockerode": "^3.3.1", "express": "^4.17.1", @@ -56,9 +56,9 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/backend-test-utils": "^0.1.24", - "@backstage/cli": "^0.17.1", - "@backstage/plugin-search-backend-node": "0.6.1", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", + "@backstage/plugin-search-backend-node": "0.6.2-next.0", "@types/dockerode": "^3.3.0", "msw": "^0.35.0", "supertest": "^6.1.3" diff --git a/plugins/techdocs-module-addons-contrib/CHANGELOG.md b/plugins/techdocs-module-addons-contrib/CHANGELOG.md index 986c31fe75..45463957cd 100644 --- a/plugins/techdocs-module-addons-contrib/CHANGELOG.md +++ b/plugins/techdocs-module-addons-contrib/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-techdocs-module-addons-contrib +## 1.0.1-next.0 + +### Patch Changes + +- 6968b65ba1: Updated dependency `@react-hookz/web` to `^14.0.0`. +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.1-next.0 + - @backstage/integration-react@1.1.1-next.0 + ## 1.0.0 ### Major Changes diff --git a/plugins/techdocs-module-addons-contrib/package.json b/plugins/techdocs-module-addons-contrib/package.json index 272fd48f07..eb6f4c9c2d 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.0", + "version": "1.0.1-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,11 +34,11 @@ "postpack": "backstage-cli package postpack" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/integration": "^1.2.0", - "@backstage/integration-react": "^1.1.0", - "@backstage/plugin-techdocs-react": "^1.0.0", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/integration-react": "^1.1.1-next.0", + "@backstage/plugin-techdocs-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.9.13", "@material-ui/icons": "^4.9.1", @@ -51,10 +51,10 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", - "@backstage/plugin-techdocs-addons-test-utils": "^1.0.0", + "@backstage/dev-utils": "^1.0.3-next.0", + "@backstage/plugin-techdocs-addons-test-utils": "^1.0.1-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/techdocs-node/CHANGELOG.md b/plugins/techdocs-node/CHANGELOG.md index 51b0c9554e..0d38f3e156 100644 --- a/plugins/techdocs-node/CHANGELOG.md +++ b/plugins/techdocs-node/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-techdocs-node +## 1.1.2-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + ## 1.1.1 ### Patch Changes diff --git a/plugins/techdocs-node/package.json b/plugins/techdocs-node/package.json index 5d9d442944..b028979f2c 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.1.1", + "version": "1.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -42,11 +42,11 @@ "dependencies": { "@azure/identity": "^2.0.1", "@azure/storage-blob": "^12.5.0", - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", + "@backstage/integration": "^1.2.1-next.0", "@backstage/plugin-search-common": "^0.3.4", "@google-cloud/storage": "^5.6.0", "@trendyol-js/openstack-swift-sdk": "^0.0.5", @@ -64,7 +64,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/fs-extra": "^9.0.5", "@types/js-yaml": "^4.0.0", "@types/mime-types": "^2.1.0", diff --git a/plugins/techdocs-react/CHANGELOG.md b/plugins/techdocs-react/CHANGELOG.md index 3a91eb1376..f8a2804ab1 100644 --- a/plugins/techdocs-react/CHANGELOG.md +++ b/plugins/techdocs-react/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-techdocs-react +## 1.0.1-next.0 + +### Patch Changes + +- 3b45ad701f: Creates a `TechDocsShadowDom` component that takes a tree of elements and an `onAppend` handler: + + - Calls the `onAppend` handler when appending the element tree to the shadow root; + - Also dispatches an event when styles are loaded to let transformers know that the computed styles are ready to be consumed. + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 1.0.0 ### Major Changes diff --git a/plugins/techdocs-react/package.json b/plugins/techdocs-react/package.json index 2d315b523c..b16753dd29 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.0", + "version": "1.0.1-next.0", "private": false, "publishConfig": { "access": "public", @@ -36,7 +36,7 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/version-bridge": "^1.0.1", "@material-ui/core": "^4.12.2", diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index 42b2108706..f2fb72e1b0 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,31 @@ # @backstage/plugin-techdocs +## 1.1.2-next.0 + +### Patch Changes + +- 881fbd7e8d: Fix `EntityTechdocsContent` component to use objects instead of `` elements, otherwise "outlet" will be null on sub-pages and add-ons won't render. +- 17c059dfd0: Restructures reader style transformations to improve code readability: + + - Extracts the style rules to separate files; + - Creates a hook that processes each rule; + - And creates another hook that returns a transformer responsible for injecting them into the head tag of a given element. + +- 3b45ad701f: Packages a set of tweaks to the TechDocs addons rendering process: + + - Prevents displaying sidebars until page styles are loaded and the sidebar position is updated; + - Prevents new sidebar locations from being created every time the reader page is rendered if these locations already exist; + - Centers the styles loaded event to avoid having multiple locations setting the opacity style in Shadow Dom causing the screen to flash multiple times. + +- 816f7475ec: Convert `sanitizeDOM` transformer to hook as part of code readability improvements in dom file. +- 50ff56a80f: Change the `EntityDocsPage` path to be more specific and also add integration tests for `sub-routes` on this page. +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + - @backstage/integration@1.2.1-next.0 + - @backstage/plugin-techdocs-react@1.0.1-next.0 + - @backstage/integration-react@1.1.1-next.0 + ## 1.1.1 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 58e9537ce9..49dd1a61fb 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.1.1", + "version": "1.1.2-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -37,15 +37,15 @@ "dependencies": { "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", - "@backstage/integration-react": "^1.1.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/integration-react": "^1.1.1-next.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/plugin-search-common": "^0.3.4", "@backstage/plugin-search-react": "^0.2.0", - "@backstage/plugin-techdocs-react": "^1.0.0", + "@backstage/plugin-techdocs-react": "^1.0.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -67,9 +67,9 @@ "react-dom": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/todo-backend/CHANGELOG.md b/plugins/todo-backend/CHANGELOG.md index 09455f37b0..18920bf909 100644 --- a/plugins/todo-backend/CHANGELOG.md +++ b/plugins/todo-backend/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-todo-backend +## 0.1.30-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/backend-common@0.13.6-next.0 + - @backstage/integration@1.2.1-next.0 + ## 0.1.29 ### Patch Changes diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index ef510eea1f..c050bbf6ca 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.29", + "version": "0.1.30-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,12 +29,12 @@ "start": "backstage-cli package start" }, "dependencies": { - "@backstage/backend-common": "^0.13.3", + "@backstage/backend-common": "^0.13.6-next.0", "@backstage/catalog-client": "^1.0.2", "@backstage/catalog-model": "^1.0.2", "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", - "@backstage/integration": "^1.2.0", + "@backstage/integration": "^1.2.1-next.0", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^4.1.0", @@ -43,7 +43,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@types/supertest": "^2.0.8", "msw": "^0.35.0", "supertest": "^6.1.3" diff --git a/plugins/todo/CHANGELOG.md b/plugins/todo/CHANGELOG.md index 42ca99b4e4..4874753345 100644 --- a/plugins/todo/CHANGELOG.md +++ b/plugins/todo/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-todo +## 0.2.8-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog-react@1.1.1-next.0 + - @backstage/core-components@0.9.5-next.0 + ## 0.2.7 ### Patch Changes diff --git a/plugins/todo/package.json b/plugins/todo/package.json index 1a605a0842..8df4c8b31b 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.7", + "version": "0.2.8-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", - "@backstage/plugin-catalog-react": "^1.1.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -45,9 +45,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 4f6ca582fd..00dd14be8e 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-user-settings +## 0.4.5-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 0.4.4 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index adb9e1850b..dae4e4b360 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.4", + "version": "0.4.5-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -34,7 +34,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", @@ -48,9 +48,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/plugins/xcmetrics/CHANGELOG.md b/plugins/xcmetrics/CHANGELOG.md index c631e9c286..3ee913563b 100644 --- a/plugins/xcmetrics/CHANGELOG.md +++ b/plugins/xcmetrics/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-xcmetrics +## 0.2.26-next.0 + +### Patch Changes + +- Updated dependencies + - @backstage/core-components@0.9.5-next.0 + ## 0.2.25 ### Patch Changes diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json index 602493fcc8..da57f71fef 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.25", + "version": "0.2.26-next.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -24,7 +24,7 @@ "clean": "backstage-cli package clean" }, "dependencies": { - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", "@backstage/errors": "^1.0.0", "@backstage/theme": "^0.2.15", @@ -40,9 +40,9 @@ "react": "^16.13.1 || ^17.0.0" }, "devDependencies": { - "@backstage/cli": "^0.17.1", + "@backstage/cli": "^0.17.2-next.0", "@backstage/core-app-api": "^1.0.2", - "@backstage/dev-utils": "^1.0.2", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", diff --git a/yarn.lock b/yarn.lock index 3788fddf65..79492064a9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1428,6 +1428,146 @@ "@babel/helper-validator-identifier" "^7.16.7" to-fast-properties "^2.0.0" +"@backstage/core-components@^0.9.0", "@backstage/core-components@^0.9.4": + version "0.9.4" + resolved "https://registry.npmjs.org/@backstage/core-components/-/core-components-0.9.4.tgz#47e9a305f768a951e0cb0ffa9c1e3c141d06b223" + integrity sha512-zg297mSw1BIc/BENrSClmgMx4kp0so0cK+lQ4FVa22+Dg5PDc2/NXWId2qEN2zD2XV/nm9RFBQM02gd6M6q3jQ== + dependencies: + "@backstage/config" "^1.0.1" + "@backstage/core-plugin-api" "^1.0.2" + "@backstage/errors" "^1.0.0" + "@backstage/theme" "^0.2.15" + "@material-table/core" "^3.1.0" + "@material-ui/core" "^4.12.2" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.57" + "@react-hookz/web" "^13.0.0" + "@types/react-sparklines" "^1.7.0" + "@types/react-text-truncate" "^0.14.0" + ansi-regex "^6.0.1" + classnames "^2.2.6" + d3-selection "^3.0.0" + d3-shape "^3.0.0" + d3-zoom "^3.0.0" + dagre "^0.8.5" + history "^5.0.0" + immer "^9.0.1" + lodash "^4.17.21" + pluralize "^8.0.0" + prop-types "^15.7.2" + qs "^6.9.4" + rc-progress "3.3.2" + react-helmet "6.1.0" + react-hook-form "^7.12.2" + react-markdown "^8.0.0" + react-router "6.0.0-beta.0" + react-router-dom "6.0.0-beta.0" + react-sparklines "^1.7.0" + react-syntax-highlighter "^15.4.5" + react-text-truncate "^0.18.0" + react-use "^17.3.2" + react-virtualized-auto-sizer "^1.0.6" + react-window "^1.8.6" + remark-gfm "^3.0.1" + zen-observable "^0.8.15" + zod "^3.11.6" + +"@backstage/integration-react@^1.0.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@backstage/integration-react/-/integration-react-1.1.0.tgz#9d58838e85647540d2de69e40099533260ba2622" + integrity sha512-eNUYHOkz0daMlnsSMxK6ypDptu1pvlxgq1spJUK8zfU9TbhGs4321Vh+59RKyTmWt9quVksdB3lCboV4NVWx4Q== + dependencies: + "@backstage/config" "^1.0.1" + "@backstage/core-components" "^0.9.4" + "@backstage/core-plugin-api" "^1.0.2" + "@backstage/integration" "^1.2.0" + "@backstage/theme" "^0.2.15" + "@material-ui/core" "^4.12.2" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.57" + react-use "^17.2.4" + +"@backstage/integration@^1.2.0": + version "1.2.0" + resolved "https://registry.npmjs.org/@backstage/integration/-/integration-1.2.0.tgz#89a3fc95c079c541ca6a5cafc613ebefc0463687" + integrity sha512-ZUfaMUUEUlmS2JE4M0Z4fXe269Z+hRGooptgILC21hgXW20hjVrhb7Q6ynH78md0ItcEuHVs2mnCztc7LS8erg== + dependencies: + "@backstage/config" "^1.0.1" + "@backstage/errors" "^1.0.0" + "@octokit/auth-app" "^3.4.0" + "@octokit/rest" "^18.5.3" + cross-fetch "^3.1.5" + git-url-parse "^11.6.0" + lodash "^4.17.21" + luxon "^2.0.2" + +"@backstage/plugin-catalog-react@^1.0.0", "@backstage/plugin-catalog-react@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@backstage/plugin-catalog-react/-/plugin-catalog-react-1.1.0.tgz#aa2c479e12fa8cfa5409b563e0a7e7dc665204e5" + integrity sha512-jOVDIcwTwdTPA7wuAMZ/1UHipeo9EMOmSJ0mtGllFP8/ldjxe0tNNjaYb4UazCQgCdUUSuk4ccwZ/EU5GtUfYg== + dependencies: + "@backstage/catalog-client" "^1.0.2" + "@backstage/catalog-model" "^1.0.2" + "@backstage/core-components" "^0.9.4" + "@backstage/core-plugin-api" "^1.0.2" + "@backstage/errors" "^1.0.0" + "@backstage/integration" "^1.2.0" + "@backstage/plugin-catalog-common" "^1.0.2" + "@backstage/plugin-permission-common" "^0.6.1" + "@backstage/plugin-permission-react" "^0.4.1" + "@backstage/theme" "^0.2.15" + "@backstage/types" "^1.0.0" + "@backstage/version-bridge" "^1.0.1" + "@material-ui/core" "^4.12.2" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.57" + classnames "^2.2.6" + jwt-decode "^3.1.0" + lodash "^4.17.21" + qs "^6.9.4" + react-router "6.0.0-beta.0" + react-use "^17.2.4" + yaml "^1.10.0" + zen-observable "^0.8.15" + +"@backstage/plugin-home@^0.4.19", "@backstage/plugin-home@^0.4.21": + version "0.4.21" + resolved "https://registry.npmjs.org/@backstage/plugin-home/-/plugin-home-0.4.21.tgz#798058d9aeead651ff830641cb0222b6bafb5542" + integrity sha512-mdaxdR+76ZNYJiK1NtAGjnfQQKPIsZ2tVfyskWaZov1C6DxTGsguQ5oL2kiDiFj4rF7FgmtwfIxKZqWfivbR5Q== + dependencies: + "@backstage/catalog-model" "^1.0.2" + "@backstage/config" "^1.0.1" + "@backstage/core-components" "^0.9.4" + "@backstage/core-plugin-api" "^1.0.2" + "@backstage/plugin-catalog-react" "^1.1.0" + "@backstage/plugin-stack-overflow" "^0.1.1" + "@backstage/theme" "^0.2.15" + "@material-ui/core" "^4.12.2" + "@material-ui/icons" "^4.9.1" + "@material-ui/lab" "4.0.0-alpha.57" + lodash "^4.17.21" + react-router "6.0.0-beta.0" + react-use "^17.2.4" + +"@backstage/plugin-stack-overflow@^0.1.1": + version "0.1.1" + resolved "https://registry.npmjs.org/@backstage/plugin-stack-overflow/-/plugin-stack-overflow-0.1.1.tgz#238ae82cb5f5732eb343b4f87aa6fc973bb613d2" + integrity sha512-CUSQfrymZw90gWB6SbtRKt/SwgpoAxlClSeEMuDkC1JI9QjyLBNkXwYfesTpuQyBF3J6tyn/02q6r9tKKiObgQ== + dependencies: + "@backstage/config" "^1.0.1" + "@backstage/core-components" "^0.9.4" + "@backstage/core-plugin-api" "^1.0.2" + "@backstage/plugin-home" "^0.4.21" + "@backstage/plugin-search-common" "^0.3.4" + "@backstage/theme" "^0.2.15" + "@material-ui/core" "^4.12.2" + "@material-ui/icons" "^4.9.1" + "@testing-library/jest-dom" "^5.10.1" + cross-fetch "^3.1.5" + lodash "^4.17.21" + qs "^6.9.4" + react-use "^17.2.4" + "@balena/dockerignore@^1.0.2": version "1.0.2" resolved "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz#9ffe4726915251e8eb69f44ef3547e0da2c03e0d" @@ -4669,11 +4809,18 @@ resolved "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" integrity sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA= -"@react-hookz/deep-equal@^1.0.2": +"@react-hookz/deep-equal@^1.0.1", "@react-hookz/deep-equal@^1.0.2": version "1.0.2" resolved "https://registry.npmjs.org/@react-hookz/deep-equal/-/deep-equal-1.0.2.tgz#4e8bdeda027379dcf8b62a42e5f75f0351b11b35" integrity sha512-cM5kPFb6EFH5q52WzRxfRX9+8g5kq78McWOYs6e1seo+nK6NpfLupT5uOCIJp37jU8ayd4Su8ni3HRFTN2C2kg== +"@react-hookz/web@^13.0.0": + version "13.3.0" + resolved "https://registry.npmjs.org/@react-hookz/web/-/web-13.3.0.tgz#257e31049e92a121912fe1e67bdd01dbec5a203b" + integrity sha512-KswgkmqBVVDo6UnBFfssrojmDisogxC4jGZmd976R8YHoS3zdQJxjqICpOBSRohbRyYhYS9Cprw7BuV/CZcMaw== + dependencies: + "@react-hookz/deep-equal" "^1.0.1" + "@react-hookz/web@^14.0.0": version "14.2.2" resolved "https://registry.npmjs.org/@react-hookz/web/-/web-14.2.2.tgz#eee0085f954e5b62d0a6c5b20d8786c28abfb9ad" @@ -11798,61 +11945,61 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: safe-buffer "^5.1.1" "example-app@link:packages/app": - version "0.2.71" + version "0.2.72-next.0" dependencies: - "@backstage/app-defaults" "^1.0.2" + "@backstage/app-defaults" "^1.0.3-next.0" "@backstage/catalog-model" "^1.0.2" - "@backstage/cli" "^0.17.1" + "@backstage/cli" "^0.17.2-next.0" "@backstage/config" "^1.0.1" "@backstage/core-app-api" "^1.0.2" - "@backstage/core-components" "^0.9.4" + "@backstage/core-components" "^0.9.5-next.0" "@backstage/core-plugin-api" "^1.0.2" - "@backstage/integration-react" "^1.1.0" - "@backstage/plugin-airbrake" "^0.3.5" - "@backstage/plugin-apache-airflow" "^0.1.13" - "@backstage/plugin-api-docs" "^0.8.5" - "@backstage/plugin-azure-devops" "^0.1.21" - "@backstage/plugin-badges" "^0.2.29" - "@backstage/plugin-catalog" "^1.2.0" + "@backstage/integration-react" "^1.1.1-next.0" + "@backstage/plugin-airbrake" "^0.3.6-next.0" + "@backstage/plugin-apache-airflow" "^0.1.14-next.0" + "@backstage/plugin-api-docs" "^0.8.6-next.0" + "@backstage/plugin-azure-devops" "^0.1.22-next.0" + "@backstage/plugin-badges" "^0.2.30-next.0" + "@backstage/plugin-catalog" "^1.2.1-next.0" "@backstage/plugin-catalog-common" "^1.0.2" - "@backstage/plugin-catalog-graph" "^0.2.17" - "@backstage/plugin-catalog-import" "^0.8.8" - "@backstage/plugin-catalog-react" "^1.1.0" - "@backstage/plugin-circleci" "^0.3.5" - "@backstage/plugin-cloudbuild" "^0.3.5" - "@backstage/plugin-code-coverage" "^0.1.32" - "@backstage/plugin-cost-insights" "^0.11.27" - "@backstage/plugin-explore" "^0.3.36" - "@backstage/plugin-gcalendar" "^0.3.1" - "@backstage/plugin-gcp-projects" "^0.3.24" - "@backstage/plugin-github-actions" "^0.5.5" - "@backstage/plugin-gocd" "^0.1.11" - "@backstage/plugin-graphiql" "^0.2.37" - "@backstage/plugin-home" "^0.4.21" - "@backstage/plugin-jenkins" "^0.7.4" - "@backstage/plugin-kafka" "^0.3.5" - "@backstage/plugin-kubernetes" "^0.6.5" - "@backstage/plugin-lighthouse" "^0.3.5" - "@backstage/plugin-newrelic" "^0.3.23" - "@backstage/plugin-newrelic-dashboard" "^0.1.13" - "@backstage/plugin-org" "^0.5.5" - "@backstage/plugin-pagerduty" "0.3.32" + "@backstage/plugin-catalog-graph" "^0.2.18-next.0" + "@backstage/plugin-catalog-import" "^0.8.9-next.0" + "@backstage/plugin-catalog-react" "^1.1.1-next.0" + "@backstage/plugin-circleci" "^0.3.6-next.0" + "@backstage/plugin-cloudbuild" "^0.3.6-next.0" + "@backstage/plugin-code-coverage" "^0.1.33-next.0" + "@backstage/plugin-cost-insights" "^0.11.28-next.0" + "@backstage/plugin-explore" "^0.3.37-next.0" + "@backstage/plugin-gcalendar" "^0.3.2-next.0" + "@backstage/plugin-gcp-projects" "^0.3.25-next.0" + "@backstage/plugin-github-actions" "^0.5.6-next.0" + "@backstage/plugin-gocd" "^0.1.12-next.0" + "@backstage/plugin-graphiql" "^0.2.38-next.0" + "@backstage/plugin-home" "^0.4.22-next.0" + "@backstage/plugin-jenkins" "^0.7.5-next.0" + "@backstage/plugin-kafka" "^0.3.6-next.0" + "@backstage/plugin-kubernetes" "^0.6.6-next.0" + "@backstage/plugin-lighthouse" "^0.3.6-next.0" + "@backstage/plugin-newrelic" "^0.3.24-next.0" + "@backstage/plugin-newrelic-dashboard" "^0.1.14-next.0" + "@backstage/plugin-org" "^0.5.6-next.0" + "@backstage/plugin-pagerduty" "0.3.33-next.0" "@backstage/plugin-permission-react" "^0.4.1" - "@backstage/plugin-rollbar" "^0.4.5" - "@backstage/plugin-scaffolder" "^1.2.0" - "@backstage/plugin-search" "^0.8.1" + "@backstage/plugin-rollbar" "^0.4.6-next.0" + "@backstage/plugin-scaffolder" "^1.3.0-next.0" + "@backstage/plugin-search" "^0.8.2-next.0" "@backstage/plugin-search-common" "^0.3.4" "@backstage/plugin-search-react" "^0.2.0" - "@backstage/plugin-sentry" "^0.3.43" - "@backstage/plugin-shortcuts" "^0.2.6" - "@backstage/plugin-stack-overflow" "^0.1.1" - "@backstage/plugin-tech-insights" "^0.2.1" - "@backstage/plugin-tech-radar" "^0.5.12" - "@backstage/plugin-techdocs" "^1.1.1" - "@backstage/plugin-techdocs-module-addons-contrib" "^1.0.0" - "@backstage/plugin-techdocs-react" "^1.0.0" - "@backstage/plugin-todo" "^0.2.7" - "@backstage/plugin-user-settings" "^0.4.4" + "@backstage/plugin-sentry" "^0.3.44-next.0" + "@backstage/plugin-shortcuts" "^0.2.7-next.0" + "@backstage/plugin-stack-overflow" "^0.1.2-next.0" + "@backstage/plugin-tech-insights" "^0.2.2-next.0" + "@backstage/plugin-tech-radar" "^0.5.13-next.0" + "@backstage/plugin-techdocs" "^1.1.2-next.0" + "@backstage/plugin-techdocs-module-addons-contrib" "^1.0.1-next.0" + "@backstage/plugin-techdocs-react" "^1.0.1-next.0" + "@backstage/plugin-todo" "^0.2.8-next.0" + "@backstage/plugin-user-settings" "^0.4.5-next.0" "@backstage/theme" "^0.2.15" "@material-ui/core" "^4.12.2" "@material-ui/icons" "^4.9.1" @@ -23449,19 +23596,19 @@ tdigest@^0.1.1: bintrees "1.0.1" "techdocs-cli-embedded-app@link:packages/techdocs-cli-embedded-app": - version "0.2.70" + version "0.2.71-next.0" dependencies: - "@backstage/app-defaults" "^1.0.2" + "@backstage/app-defaults" "^1.0.3-next.0" "@backstage/catalog-model" "^1.0.2" - "@backstage/cli" "^0.17.1" + "@backstage/cli" "^0.17.2-next.0" "@backstage/config" "^1.0.1" "@backstage/core-app-api" "^1.0.2" - "@backstage/core-components" "^0.9.4" + "@backstage/core-components" "^0.9.5-next.0" "@backstage/core-plugin-api" "^1.0.2" - "@backstage/integration-react" "^1.1.0" - "@backstage/plugin-catalog" "^1.2.0" - "@backstage/plugin-techdocs" "^1.1.1" - "@backstage/plugin-techdocs-react" "^1.0.0" + "@backstage/integration-react" "^1.1.1-next.0" + "@backstage/plugin-catalog" "^1.2.1-next.0" + "@backstage/plugin-techdocs" "^1.1.2-next.0" + "@backstage/plugin-techdocs-react" "^1.0.1-next.0" "@backstage/test-utils" "^1.1.0" "@backstage/theme" "^0.2.15" "@material-ui/core" "^4.11.0" From f7146b516fcdf8efe3bb2b761d896400839ad0a9 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Tue, 24 May 2022 15:19:18 +0000 Subject: [PATCH 090/149] fix(deps): update dependency cron to v2 Signed-off-by: Renovate Bot --- .changeset/renovate-648a745.md | 6 +++++ packages/backend-tasks/package.json | 4 ++-- yarn.lock | 36 ++++++++++++++--------------- 3 files changed, 25 insertions(+), 21 deletions(-) create mode 100644 .changeset/renovate-648a745.md diff --git a/.changeset/renovate-648a745.md b/.changeset/renovate-648a745.md new file mode 100644 index 0000000000..005c235736 --- /dev/null +++ b/.changeset/renovate-648a745.md @@ -0,0 +1,6 @@ +--- +'@backstage/backend-tasks': patch +--- + +Updated dependency `cron` to `^2.0.0`. +Updated dependency `@types/cron` to `^2.0.0`. diff --git a/packages/backend-tasks/package.json b/packages/backend-tasks/package.json index 108ce59f15..b0f6d2d6df 100644 --- a/packages/backend-tasks/package.json +++ b/packages/backend-tasks/package.json @@ -38,7 +38,7 @@ "@backstage/errors": "^1.0.0", "@backstage/types": "^1.0.0", "@types/luxon": "^2.0.4", - "cron": "^1.8.2", + "cron": "^2.0.0", "knex": "^1.0.2", "lodash": "^4.17.21", "luxon": "^2.0.2", @@ -50,7 +50,7 @@ "devDependencies": { "@backstage/backend-test-utils": "^0.1.25-next.0", "@backstage/cli": "^0.17.2-next.0", - "@types/cron": "^1.7.3", + "@types/cron": "^2.0.0", "wait-for-expect": "^3.0.2" }, "files": [ diff --git a/yarn.lock b/yarn.lock index 79492064a9..5fb2bdcbce 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5607,13 +5607,13 @@ resolved "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz#6b2c510a7ad7039e98e7b8d3d6598f4359e5c080" integrity sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw== -"@types/cron@^1.7.3": - version "1.7.3" - resolved "https://registry.npmjs.org/@types/cron/-/cron-1.7.3.tgz#993db7d54646f61128c851607b64ba4495deae93" - integrity sha512-iPmUXyIJG1Js+ldPYhOQcYU3kCAQ2FWrSkm1FJPoii2eYSn6wEW6onPukNTT0bfiflexNSRPl6KWmAIqS+36YA== +"@types/cron@^2.0.0": + version "2.0.0" + resolved "https://registry.npmjs.org/@types/cron/-/cron-2.0.0.tgz#4fe75f2720a3b69a1f7b80e656749f4c2c96d727" + integrity sha512-xZM08fqvwIXgghtPVkSPKNgC+JoMQ2OHazEvyTKnNf7aWu1aB6/4lBbQFrb03Td2cUGG7ITzMv3mFYnMu6xRaQ== dependencies: + "@types/luxon" "*" "@types/node" "*" - moment ">=2.14.0" "@types/d3-color@*": version "3.0.2" @@ -6092,7 +6092,7 @@ resolved "https://registry.npmjs.org/@types/lunr/-/lunr-2.3.4.tgz#728f445855818fb17776d10ef4678f278072eb03" integrity sha512-j4x4XJwZvorEUbA519VdQ5b9AOU9TSvfi8tvxMAfP8XzNLtFex7A8vFQwqOx3WACbV0KMXbACV3cZl4/gynQ7g== -"@types/luxon@^2.0.4", "@types/luxon@^2.0.5", "@types/luxon@^2.0.9": +"@types/luxon@*", "@types/luxon@^2.0.4", "@types/luxon@^2.0.5", "@types/luxon@^2.0.9": version "2.3.2" resolved "https://registry.npmjs.org/@types/luxon/-/luxon-2.3.2.tgz#8a3f2cdd4858ce698b56cd8597d9243b8e9d3c65" integrity sha512-WOehptuhKIXukSUUkRgGbj2c997Uv/iUgYgII8U7XLJqq9W2oF0kQ6frEznRQbdurioz+L/cdaIm4GutTQfgmA== @@ -9881,12 +9881,12 @@ crelt@^1.0.5: resolved "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz#57c0d52af8c859e354bace1883eb2e1eb182bb94" integrity sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA== -cron@^1.8.2: - version "1.8.2" - resolved "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz#4ac5e3c55ba8c163d84f3407bde94632da8370ce" - integrity sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg== +cron@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/cron/-/cron-2.0.0.tgz#15c6bf37c1cebf6da1d7a688b9ba1c68338bfe6b" + integrity sha512-RPeRunBCFr/WEo7WLp8Jnm45F/ziGJiHVvVQEBSDTSGu6uHW49b2FOP2O14DcXlGJRLhwE7TIoDzHHK4KmlL6g== dependencies: - moment-timezone "^0.5.x" + luxon "^1.23.x" cronstrue@^2.2.0: version "2.5.0" @@ -16907,6 +16907,11 @@ lunr@^2.3.9: resolved "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz#18b123142832337dd6e964df1a5a7707b25d35e1" integrity sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow== +luxon@^1.23.x: + version "1.28.0" + resolved "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" + integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ== + luxon@^2.0.2, luxon@^2.3.0, luxon@^2.3.1: version "2.4.0" resolved "https://registry.npmjs.org/luxon/-/luxon-2.4.0.tgz#9435806545bb32d4234dab766ab8a3d54847a765" @@ -17962,14 +17967,7 @@ modify-values@^1.0.0: resolved "https://registry.npmjs.org/modify-values/-/modify-values-1.0.1.tgz#b3939fa605546474e3e3e3c63d64bd43b4ee6022" integrity sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw== -moment-timezone@^0.5.x: - version "0.5.34" - resolved "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c" - integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg== - dependencies: - moment ">= 2.9.0" - -"moment@>= 2.9.0", moment@>=2.14.0, moment@^2.27.0, moment@^2.29.1: +moment@^2.27.0, moment@^2.29.1: version "2.29.2" resolved "https://registry.npmjs.org/moment/-/moment-2.29.2.tgz#00910c60b20843bcba52d37d58c628b47b1f20e4" integrity sha512-UgzG4rvxYpN15jgCmVJwac49h9ly9NurikMWGPdVxm8GZD6XjkKPxDTjQQ43gtGgnV3X0cAyWDdP2Wexoquifg== From 467facc6eac766e96aaf1e30b4b0da93a854a1b4 Mon Sep 17 00:00:00 2001 From: Jonah Back Date: Tue, 24 May 2022 14:40:18 -0700 Subject: [PATCH 091/149] (fix): bind 'this' properly for getKey function Signed-off-by: Jonah Back --- .changeset/tricky-hounds-cry.md | 5 +++++ plugins/auth-backend/src/providers/aws-alb/provider.ts | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 .changeset/tricky-hounds-cry.md diff --git a/.changeset/tricky-hounds-cry.md b/.changeset/tricky-hounds-cry.md new file mode 100644 index 0000000000..d54d59fd30 --- /dev/null +++ b/.changeset/tricky-hounds-cry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Fix improper binding of 'this' in ALB Auth provider diff --git a/plugins/auth-backend/src/providers/aws-alb/provider.ts b/plugins/auth-backend/src/providers/aws-alb/provider.ts index 78bfb9f0ec..b8d43a11e6 100644 --- a/plugins/auth-backend/src/providers/aws-alb/provider.ts +++ b/plugins/auth-backend/src/providers/aws-alb/provider.ts @@ -189,7 +189,7 @@ export class AwsAlbAuthProvider implements AuthProviderRouteHandlers { }; } - async getKey(header: JWTHeaderParameters): Promise { + getKey = async (header: JWTHeaderParameters): Promise => { if (!header.kid) { throw new AuthenticationError('No key id was specified in header'); } @@ -208,7 +208,7 @@ export class AwsAlbAuthProvider implements AuthProviderRouteHandlers { keyValue.export({ format: 'pem', type: 'spki' }), ); return keyValue; - } + }; } /** From 718c024cf54b63d70b9028b5e88733ec70be35e8 Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Wed, 25 May 2022 10:36:07 +0100 Subject: [PATCH 092/149] add edit url annotation to the github teams entity Signed-off-by: Brian Fletcher --- plugins/catalog-backend-module-github/src/lib/github.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index b3dae26091..a6933421a5 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -52,6 +52,7 @@ export type Team = { name?: string; description?: string; avatarUrl?: string; + editTeamUrl: string; parentTeam?: Team; members: Connection; }; @@ -165,6 +166,7 @@ export async function getOrganizationTeams( name description avatarUrl + editTeamUrl parentTeam { slug } members(first: 100, membership: IMMEDIATE) { pageInfo { hasNextPage } @@ -186,6 +188,7 @@ export async function getOrganizationTeams( name: team.slug, annotations: { 'github.com/team-slug': team.combinedSlug, + 'backstage.io/edit-url': team.editTeamUrl, }, }, spec: { From 8335a6f6f3498a0be11a5500c974c3f2f7c92f7d Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Wed, 25 May 2022 10:39:07 +0100 Subject: [PATCH 093/149] add changeset Signed-off-by: Brian Fletcher --- .changeset/modern-pandas-agree.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/modern-pandas-agree.md diff --git a/.changeset/modern-pandas-agree.md b/.changeset/modern-pandas-agree.md new file mode 100644 index 0000000000..98360cc6d9 --- /dev/null +++ b/.changeset/modern-pandas-agree.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-github': patch +--- + +Adds an edit url to the GitHub Teams Group entities. From be4aff4ac919af0b25f889fdc97e707b04a3264c Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Wed, 25 May 2022 10:45:07 +0100 Subject: [PATCH 094/149] adds tests and handling if edit url is unset Signed-off-by: Brian Fletcher --- .../src/lib/github.test.ts | 6 ++++++ .../src/lib/github.ts | 15 ++++++++++----- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/plugins/catalog-backend-module-github/src/lib/github.test.ts b/plugins/catalog-backend-module-github/src/lib/github.test.ts index fb4caa42c0..40dec0a297 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.test.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.test.ts @@ -86,6 +86,7 @@ describe('github', () => { name: 'Team', description: 'The one and only team', avatarUrl: 'http://example.com/team.jpeg', + editTeamUrl: 'http://example.com/orgs/blah/teams/team/edit', parentTeam: { slug: 'parent', combinedSlug: '', @@ -109,6 +110,11 @@ describe('github', () => { metadata: expect.objectContaining({ name: 'team', description: 'The one and only team', + annotations: { + 'github.com/team-slug': 'blah/team', + 'backstage.io/edit-url': + 'http://example.com/orgs/blah/teams/team/edit', + }, }), spec: { type: 'team', diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index a6933421a5..888df380f8 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -52,7 +52,7 @@ export type Team = { name?: string; description?: string; avatarUrl?: string; - editTeamUrl: string; + editTeamUrl?: string; parentTeam?: Team; members: Connection; }; @@ -181,15 +181,20 @@ export async function getOrganizationTeams( const groupMemberUsers = new Map(); const mapper = async (team: Team) => { + const annotations: { [annotationName: string]: string } = { + 'github.com/team-slug': team.combinedSlug, + }; + + if (team.editTeamUrl) { + annotations['backstage.io/edit-url'] = team.editTeamUrl; + } + const entity: GroupEntity = { apiVersion: 'backstage.io/v1alpha1', kind: 'Group', metadata: { name: team.slug, - annotations: { - 'github.com/team-slug': team.combinedSlug, - 'backstage.io/edit-url': team.editTeamUrl, - }, + annotations, }, spec: { type: 'team', From acb4e2110e12a4bc8f4b0f56da03d508eea435b6 Mon Sep 17 00:00:00 2001 From: Brian Fletcher Date: Wed, 25 May 2022 10:49:55 +0100 Subject: [PATCH 095/149] fix casing of URL in changeset Signed-off-by: Brian Fletcher --- .changeset/modern-pandas-agree.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/modern-pandas-agree.md b/.changeset/modern-pandas-agree.md index 98360cc6d9..9c3f95aef2 100644 --- a/.changeset/modern-pandas-agree.md +++ b/.changeset/modern-pandas-agree.md @@ -2,4 +2,4 @@ '@backstage/plugin-catalog-backend-module-github': patch --- -Adds an edit url to the GitHub Teams Group entities. +Adds an edit URL to the GitHub Teams Group entities. From 30f1b1d0abdc895bc684da08e6e95e0966006150 Mon Sep 17 00:00:00 2001 From: Jakub Cierlik Date: Wed, 25 May 2022 14:30:00 +0200 Subject: [PATCH 096/149] Add Cloudify plugin Signed-off-by: Jakub Cierlik --- microsite/data/plugins/cloudify.yaml | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 microsite/data/plugins/cloudify.yaml diff --git a/microsite/data/plugins/cloudify.yaml b/microsite/data/plugins/cloudify.yaml new file mode 100644 index 0000000000..394e1fea24 --- /dev/null +++ b/microsite/data/plugins/cloudify.yaml @@ -0,0 +1,9 @@ +--- +title: Cloudify +author: Cloudify +authorUrl: https://cloudify.co/ +category: Orchestration +description: Load blueprints from desired Cloudify Manager instance +documentation: https://github.com/Cloudify-PS/backstage-cloudify-plugin#readme +iconUrl: https://avatars.githubusercontent.com/u/6260555?s=200&v=4 +npmPackageName: 'plugin-cloudify' From ca98fe0b299aab7dae28b0d7680e38540cbd5740 Mon Sep 17 00:00:00 2001 From: Joon Park Date: Thu, 26 May 2022 14:58:19 +0100 Subject: [PATCH 097/149] Rename permission docs path to plural (#11648) * Rename permission docs path to plural Signed-off-by: Joon Park * Update todo list plugin READMEs Signed-off-by: Joon Park --- .../disabled-unregister-entity.png | Bin .../permission-framework-overview.drawio.svg | 0 .../permission-todo-list-page.png | Bin docs/{permission => permissions}/concepts.md | 0 .../custom-rules.md | 0 .../getting-started.md | 2 +- docs/{permission => permissions}/overview.md | 2 +- .../plugin-authors/01-setup.md | 2 +- .../02-adding-a-basic-permission-check.md | 0 .../03-adding-a-resource-permission-check.md | 0 ...04-authorizing-access-to-paginated-data.md | 0 .../writing-a-policy.md | 0 microsite/sidebars.json | 18 +++++++++--------- plugins/example-todo-list-backend/README.md | 2 +- plugins/example-todo-list-common/README.md | 2 +- plugins/example-todo-list/README.md | 2 +- 16 files changed, 15 insertions(+), 15 deletions(-) rename docs/assets/{permission => permissions}/disabled-unregister-entity.png (100%) rename docs/assets/{permission => permissions}/permission-framework-overview.drawio.svg (100%) rename docs/assets/{permission => permissions}/permission-todo-list-page.png (100%) rename docs/{permission => permissions}/concepts.md (100%) rename docs/{permission => permissions}/custom-rules.md (100%) rename docs/{permission => permissions}/getting-started.md (99%) rename docs/{permission => permissions}/overview.md (97%) rename docs/{permission => permissions}/plugin-authors/01-setup.md (98%) rename docs/{permission => permissions}/plugin-authors/02-adding-a-basic-permission-check.md (100%) rename docs/{permission => permissions}/plugin-authors/03-adding-a-resource-permission-check.md (100%) rename docs/{permission => permissions}/plugin-authors/04-authorizing-access-to-paginated-data.md (100%) rename docs/{permission => permissions}/writing-a-policy.md (100%) diff --git a/docs/assets/permission/disabled-unregister-entity.png b/docs/assets/permissions/disabled-unregister-entity.png similarity index 100% rename from docs/assets/permission/disabled-unregister-entity.png rename to docs/assets/permissions/disabled-unregister-entity.png diff --git a/docs/assets/permission/permission-framework-overview.drawio.svg b/docs/assets/permissions/permission-framework-overview.drawio.svg similarity index 100% rename from docs/assets/permission/permission-framework-overview.drawio.svg rename to docs/assets/permissions/permission-framework-overview.drawio.svg diff --git a/docs/assets/permission/permission-todo-list-page.png b/docs/assets/permissions/permission-todo-list-page.png similarity index 100% rename from docs/assets/permission/permission-todo-list-page.png rename to docs/assets/permissions/permission-todo-list-page.png diff --git a/docs/permission/concepts.md b/docs/permissions/concepts.md similarity index 100% rename from docs/permission/concepts.md rename to docs/permissions/concepts.md diff --git a/docs/permission/custom-rules.md b/docs/permissions/custom-rules.md similarity index 100% rename from docs/permission/custom-rules.md rename to docs/permissions/custom-rules.md diff --git a/docs/permission/getting-started.md b/docs/permissions/getting-started.md similarity index 99% rename from docs/permission/getting-started.md rename to docs/permissions/getting-started.md index b115b674d6..6daf448cdd 100644 --- a/docs/permission/getting-started.md +++ b/docs/permissions/getting-started.md @@ -152,6 +152,6 @@ permission: 3. Now that you’ve made this change, you should find that the unregister entity menu option on the catalog entity page is disabled. -![Entity detail page showing disabled unregister entity context menu entry](../assets/permission/disabled-unregister-entity.png) +![Entity detail page showing disabled unregister entity context menu entry](../assets/permissions/disabled-unregister-entity.png) Now that the framework is fully configured, you can craft a permission policy that works best for your organization by utilizing a provided authorization method or by [writing your own policy](./writing-a-policy.md)! diff --git a/docs/permission/overview.md b/docs/permissions/overview.md similarity index 97% rename from docs/permission/overview.md rename to docs/permissions/overview.md index 8e87756faa..b972814aeb 100644 --- a/docs/permission/overview.md +++ b/docs/permissions/overview.md @@ -22,7 +22,7 @@ The permission framework was designed with a few key properties in mind: - **Integrators** can author or configure policies that define which users can take certain actions upon which resources. -![](../assets/permission/permission-framework-overview.drawio.svg) +![](../assets/permissions/permission-framework-overview.drawio.svg) 1. The user triggers a request to perform some action. The request specifies the authorization details using the permission specified by the plugin (in this case, a resource read action). diff --git a/docs/permission/plugin-authors/01-setup.md b/docs/permissions/plugin-authors/01-setup.md similarity index 98% rename from docs/permission/plugin-authors/01-setup.md rename to docs/permissions/plugin-authors/01-setup.md index 2f070f603b..d08d1ee79c 100644 --- a/docs/permission/plugin-authors/01-setup.md +++ b/docs/permissions/plugin-authors/01-setup.md @@ -105,7 +105,7 @@ The source code is available here: Now if you start your application you should be able to reach the `/todo-list` page: -![Todo List plugin page](../../assets/permission/permission-todo-list-page.png) +![Todo List plugin page](../../assets/permissions/permission-todo-list-page.png) --- diff --git a/docs/permission/plugin-authors/02-adding-a-basic-permission-check.md b/docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md similarity index 100% rename from docs/permission/plugin-authors/02-adding-a-basic-permission-check.md rename to docs/permissions/plugin-authors/02-adding-a-basic-permission-check.md diff --git a/docs/permission/plugin-authors/03-adding-a-resource-permission-check.md b/docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md similarity index 100% rename from docs/permission/plugin-authors/03-adding-a-resource-permission-check.md rename to docs/permissions/plugin-authors/03-adding-a-resource-permission-check.md diff --git a/docs/permission/plugin-authors/04-authorizing-access-to-paginated-data.md b/docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md similarity index 100% rename from docs/permission/plugin-authors/04-authorizing-access-to-paginated-data.md rename to docs/permissions/plugin-authors/04-authorizing-access-to-paginated-data.md diff --git a/docs/permission/writing-a-policy.md b/docs/permissions/writing-a-policy.md similarity index 100% rename from docs/permission/writing-a-policy.md rename to docs/permissions/writing-a-policy.md diff --git a/microsite/sidebars.json b/microsite/sidebars.json index a8dc4e53ee..7bbe67f58f 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -259,19 +259,19 @@ "auth/glossary" ], "Permissions": [ - "permission/overview", - "permission/concepts", - "permission/getting-started", - "permission/writing-a-policy", - "permission/custom-rules", + "permissions/overview", + "permissions/concepts", + "permissions/getting-started", + "permissions/writing-a-policy", + "permissions/custom-rules", { "type": "subcategory", "label": "Tutorial: using Permissions in your plugin", "ids": [ - "permission/plugin-authors/01-setup", - "permission/plugin-authors/02-adding-a-basic-permission-check", - "permission/plugin-authors/03-adding-a-resource-permission-check", - "permission/plugin-authors/04-authorizing-access-to-paginated-data" + "permissions/plugin-authors/01-setup", + "permissions/plugin-authors/02-adding-a-basic-permission-check", + "permissions/plugin-authors/03-adding-a-resource-permission-check", + "permissions/plugin-authors/04-authorizing-access-to-paginated-data" ] } ], diff --git a/plugins/example-todo-list-backend/README.md b/plugins/example-todo-list-backend/README.md index d5c51b2b57..f1dbd427d2 100644 --- a/plugins/example-todo-list-backend/README.md +++ b/plugins/example-todo-list-backend/README.md @@ -1,3 +1,3 @@ # todo-list-backend -This package provides a starting point to demonstrate how plugin authors can use the Backstage permission framework. Refer to the [documentation](https://backstage.io/docs/permission/plugin-authors/01-setup) to get started. +This package provides a starting point to demonstrate how plugin authors can use the Backstage permission framework. Refer to the [documentation](https://backstage.io/docs/permissions/plugin-authors/01-setup) to get started. diff --git a/plugins/example-todo-list-common/README.md b/plugins/example-todo-list-common/README.md index 021eca1033..4962d61bd0 100644 --- a/plugins/example-todo-list-common/README.md +++ b/plugins/example-todo-list-common/README.md @@ -1,3 +1,3 @@ # todo-list-common -This package provides a starting point to demonstrate how plugin authors can use the Backstage permission framework. Refer to the [documentation](https://backstage.io/docs/permission/plugin-authors/01-setup) to get started. +This package provides a starting point to demonstrate how plugin authors can use the Backstage permission framework. Refer to the [documentation](https://backstage.io/docs/permissions/plugin-authors/01-setup) to get started. diff --git a/plugins/example-todo-list/README.md b/plugins/example-todo-list/README.md index bd77af87e8..8d5daab89b 100644 --- a/plugins/example-todo-list/README.md +++ b/plugins/example-todo-list/README.md @@ -1,3 +1,3 @@ # todo-list -This package provides a starting point to demonstrate how plugin authors can use the Backstage permission framework. Refer to the [documentation](https://backstage.io/docs/permission/plugin-authors/01-setup) to get started. +This package provides a starting point to demonstrate how plugin authors can use the Backstage permission framework. Refer to the [documentation](https://backstage.io/docs/permissions/plugin-authors/01-setup) to get started. From 35780e76e486d5b65b56dea7f58b8718df802125 Mon Sep 17 00:00:00 2001 From: Manuel Scurti Date: Thu, 26 May 2022 17:15:30 +0200 Subject: [PATCH 098/149] fixed copyright year Signed-off-by: Manuel Scurti --- .../auth-backend/migrations/20220522100910_key_field_size.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/migrations/20220522100910_key_field_size.js b/plugins/auth-backend/migrations/20220522100910_key_field_size.js index c6637525f3..c27d038223 100644 --- a/plugins/auth-backend/migrations/20220522100910_key_field_size.js +++ b/plugins/auth-backend/migrations/20220522100910_key_field_size.js @@ -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. From 83f6a64d2c35ee2d33a229a212dd79ce313c62b7 Mon Sep 17 00:00:00 2001 From: Hasan Oezdemir <21654050+nodify-at@users.noreply.github.com> Date: Fri, 27 May 2022 02:46:34 +0200 Subject: [PATCH 099/149] bugfix: provide backstage token for rebuild api call Signed-off-by: Hasan Oezdemir <21654050+nodify-at@users.noreply.github.com> --- .changeset/pretty-wolves-whisper.md | 5 +++++ plugins/jenkins-backend/src/service/router.ts | 7 ++++--- 2 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 .changeset/pretty-wolves-whisper.md diff --git a/.changeset/pretty-wolves-whisper.md b/.changeset/pretty-wolves-whisper.md new file mode 100644 index 0000000000..5ab603de23 --- /dev/null +++ b/.changeset/pretty-wolves-whisper.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-jenkins-backend': patch +--- + +bugfix: provide backstage token for rebuild api call diff --git a/plugins/jenkins-backend/src/service/router.ts b/plugins/jenkins-backend/src/service/router.ts index fa357f8e9f..2c9e268165 100644 --- a/plugins/jenkins-backend/src/service/router.ts +++ b/plugins/jenkins-backend/src/service/router.ts @@ -131,6 +131,9 @@ export async function createRouter( '/v1/entity/:namespace/:kind/:name/job/:jobFullName/:buildNumber::rebuild', async (request, response) => { const { namespace, kind, name, jobFullName } = request.params; + const token = getBearerTokenFromAuthorizationHeader( + request.header('authorization'), + ); const jenkinsInfo = await jenkinsInfoProvider.getInstance({ entityRef: { kind, @@ -138,10 +141,8 @@ export async function createRouter( name, }, jobFullName, + backstageToken: token, }); - const token = getBearerTokenFromAuthorizationHeader( - request.header('authorization'), - ); const resourceRef = stringifyEntityRef({ kind, namespace, name }); await jenkinsApi.buildProject(jenkinsInfo, jobFullName, resourceRef, { From 646ca259f1f9d69af84e351050bc91ba7fcde76e Mon Sep 17 00:00:00 2001 From: Olivier Liechti Date: Fri, 27 May 2022 08:28:15 +0200 Subject: [PATCH 100/149] Add overflow:display to display large tooltips on cost overview chart Signed-off-by: Olivier Liechti --- .../CostOverviewCard/CostOverviewCard.tsx | 2 +- plugins/cost-insights/src/example/client.ts | 3 + .../cost-insights/src/testUtils/testUtils.ts | 132 ++++++++++++++++++ 3 files changed, 136 insertions(+), 1 deletion(-) diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx index 1a4bb4ba6e..3e77858967 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewCard.tsx @@ -106,7 +106,7 @@ export const CostOverviewCard = ({ const showMetricSelect = config.metrics.length && safeTabIndex === 0; return ( - + {dailyCostData.groupedCosts && } diff --git a/plugins/cost-insights/src/example/client.ts b/plugins/cost-insights/src/example/client.ts index a7a2a0e22f..f1abfffe38 100644 --- a/plugins/cost-insights/src/example/client.ts +++ b/plugins/cost-insights/src/example/client.ts @@ -63,6 +63,9 @@ export class ExampleCostInsightsClient implements CostInsightsApi { { id: 'project-a' }, { id: 'project-b' }, { id: 'project-c' }, + { id: 'project-a1' }, + { id: 'project-b1' }, + { id: 'project-c1' }, ]); return projects; diff --git a/plugins/cost-insights/src/testUtils/testUtils.ts b/plugins/cost-insights/src/testUtils/testUtils.ts index e0ed1aa933..b818d31a59 100644 --- a/plugins/cost-insights/src/testUtils/testUtils.ts +++ b/plugins/cost-insights/src/testUtils/testUtils.ts @@ -188,4 +188,136 @@ export const getGroupedProjects = (intervals: string) => [ id: 'project-c', aggregation: aggregationFor(intervals, 1_300), }, + { + id: 'project-a1', + aggregation: aggregationFor(intervals, 1_700), + }, + { + id: 'project-b1', + aggregation: aggregationFor(intervals, 350), + }, + { + id: 'project-c1', + aggregation: aggregationFor(intervals, 1_300), + }, + { + id: 'project-a2', + aggregation: aggregationFor(intervals, 1_700), + }, + { + id: 'project-b2', + aggregation: aggregationFor(intervals, 350), + }, + { + id: 'project-c2', + aggregation: aggregationFor(intervals, 1_300), + }, + { + id: 'project-a3', + aggregation: aggregationFor(intervals, 1_700), + }, + { + id: 'project-b3', + aggregation: aggregationFor(intervals, 350), + }, + { + id: 'project-c3', + aggregation: aggregationFor(intervals, 1_300), + }, + { + id: 'project-a4', + aggregation: aggregationFor(intervals, 1_700), + }, + { + id: 'project-b4', + aggregation: aggregationFor(intervals, 350), + }, + { + id: 'project-c4', + aggregation: aggregationFor(intervals, 1_300), + }, + { + id: 'project-a5', + aggregation: aggregationFor(intervals, 1_700), + }, + { + id: 'project-b5', + aggregation: aggregationFor(intervals, 350), + }, + { + id: 'project-c5', + aggregation: aggregationFor(intervals, 1_300), + }, + { + id: 'project-a6', + aggregation: aggregationFor(intervals, 1_700), + }, + { + id: 'project-b6', + aggregation: aggregationFor(intervals, 350), + }, + { + id: 'project-c6', + aggregation: aggregationFor(intervals, 1_300), + }, + { + id: 'project-a7', + aggregation: aggregationFor(intervals, 1_700), + }, + { + id: 'project-b7', + aggregation: aggregationFor(intervals, 350), + }, + { + id: 'project-c7', + aggregation: aggregationFor(intervals, 1_300), + }, + { + id: 'project-a8', + aggregation: aggregationFor(intervals, 1_700), + }, + { + id: 'project-b8', + aggregation: aggregationFor(intervals, 350), + }, + { + id: 'project-c8', + aggregation: aggregationFor(intervals, 1_300), + }, + { + id: 'project-a10', + aggregation: aggregationFor(intervals, 1_700), + }, + { + id: 'project-b10', + aggregation: aggregationFor(intervals, 350), + }, + { + id: 'project-c10', + aggregation: aggregationFor(intervals, 1_300), + }, + { + id: 'project-a11', + aggregation: aggregationFor(intervals, 1_700), + }, + { + id: 'project-b11', + aggregation: aggregationFor(intervals, 350), + }, + { + id: 'project-c11', + aggregation: aggregationFor(intervals, 1_300), + }, + { + id: 'project-a12', + aggregation: aggregationFor(intervals, 1_700), + }, + { + id: 'project-b12', + aggregation: aggregationFor(intervals, 350), + }, + { + id: 'project-c12', + aggregation: aggregationFor(intervals, 1_300), + }, ]; From f3d095c324f1ac83b57b668d30ea7fce75cac31e Mon Sep 17 00:00:00 2001 From: Olivier Liechti Date: Fri, 27 May 2022 08:36:07 +0200 Subject: [PATCH 101/149] Restore example project data Signed-off-by: Olivier Liechti Remove unrelated fix Signed-off-by: Olivier Liechti --- plugins/cost-insights/src/example/client.ts | 3 - .../cost-insights/src/testUtils/testUtils.ts | 132 ------------------ 2 files changed, 135 deletions(-) diff --git a/plugins/cost-insights/src/example/client.ts b/plugins/cost-insights/src/example/client.ts index f1abfffe38..a7a2a0e22f 100644 --- a/plugins/cost-insights/src/example/client.ts +++ b/plugins/cost-insights/src/example/client.ts @@ -63,9 +63,6 @@ export class ExampleCostInsightsClient implements CostInsightsApi { { id: 'project-a' }, { id: 'project-b' }, { id: 'project-c' }, - { id: 'project-a1' }, - { id: 'project-b1' }, - { id: 'project-c1' }, ]); return projects; diff --git a/plugins/cost-insights/src/testUtils/testUtils.ts b/plugins/cost-insights/src/testUtils/testUtils.ts index b818d31a59..e0ed1aa933 100644 --- a/plugins/cost-insights/src/testUtils/testUtils.ts +++ b/plugins/cost-insights/src/testUtils/testUtils.ts @@ -188,136 +188,4 @@ export const getGroupedProjects = (intervals: string) => [ id: 'project-c', aggregation: aggregationFor(intervals, 1_300), }, - { - id: 'project-a1', - aggregation: aggregationFor(intervals, 1_700), - }, - { - id: 'project-b1', - aggregation: aggregationFor(intervals, 350), - }, - { - id: 'project-c1', - aggregation: aggregationFor(intervals, 1_300), - }, - { - id: 'project-a2', - aggregation: aggregationFor(intervals, 1_700), - }, - { - id: 'project-b2', - aggregation: aggregationFor(intervals, 350), - }, - { - id: 'project-c2', - aggregation: aggregationFor(intervals, 1_300), - }, - { - id: 'project-a3', - aggregation: aggregationFor(intervals, 1_700), - }, - { - id: 'project-b3', - aggregation: aggregationFor(intervals, 350), - }, - { - id: 'project-c3', - aggregation: aggregationFor(intervals, 1_300), - }, - { - id: 'project-a4', - aggregation: aggregationFor(intervals, 1_700), - }, - { - id: 'project-b4', - aggregation: aggregationFor(intervals, 350), - }, - { - id: 'project-c4', - aggregation: aggregationFor(intervals, 1_300), - }, - { - id: 'project-a5', - aggregation: aggregationFor(intervals, 1_700), - }, - { - id: 'project-b5', - aggregation: aggregationFor(intervals, 350), - }, - { - id: 'project-c5', - aggregation: aggregationFor(intervals, 1_300), - }, - { - id: 'project-a6', - aggregation: aggregationFor(intervals, 1_700), - }, - { - id: 'project-b6', - aggregation: aggregationFor(intervals, 350), - }, - { - id: 'project-c6', - aggregation: aggregationFor(intervals, 1_300), - }, - { - id: 'project-a7', - aggregation: aggregationFor(intervals, 1_700), - }, - { - id: 'project-b7', - aggregation: aggregationFor(intervals, 350), - }, - { - id: 'project-c7', - aggregation: aggregationFor(intervals, 1_300), - }, - { - id: 'project-a8', - aggregation: aggregationFor(intervals, 1_700), - }, - { - id: 'project-b8', - aggregation: aggregationFor(intervals, 350), - }, - { - id: 'project-c8', - aggregation: aggregationFor(intervals, 1_300), - }, - { - id: 'project-a10', - aggregation: aggregationFor(intervals, 1_700), - }, - { - id: 'project-b10', - aggregation: aggregationFor(intervals, 350), - }, - { - id: 'project-c10', - aggregation: aggregationFor(intervals, 1_300), - }, - { - id: 'project-a11', - aggregation: aggregationFor(intervals, 1_700), - }, - { - id: 'project-b11', - aggregation: aggregationFor(intervals, 350), - }, - { - id: 'project-c11', - aggregation: aggregationFor(intervals, 1_300), - }, - { - id: 'project-a12', - aggregation: aggregationFor(intervals, 1_700), - }, - { - id: 'project-b12', - aggregation: aggregationFor(intervals, 350), - }, - { - id: 'project-c12', - aggregation: aggregationFor(intervals, 1_300), - }, ]; From 2297510941718521c8f7ba3819b36ba86ddb5988 Mon Sep 17 00:00:00 2001 From: Olivier Liechti Date: Fri, 27 May 2022 10:31:55 +0200 Subject: [PATCH 102/149] Add changeset Signed-off-by: Olivier Liechti --- .changeset/large-monkeys-visit.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/large-monkeys-visit.md diff --git a/.changeset/large-monkeys-visit.md b/.changeset/large-monkeys-visit.md new file mode 100644 index 0000000000..1ecaadb890 --- /dev/null +++ b/.changeset/large-monkeys-visit.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +Fixed css to show large tooltips on cost overview graph From c174dc83125fef9d8589285ef2391c217df878ec Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Fri, 27 May 2022 10:50:44 +0200 Subject: [PATCH 103/149] backend-tasks: update usage of cron Signed-off-by: Patrik Oldsberg --- packages/backend-tasks/src/tasks/LocalTaskWorker.ts | 2 +- packages/backend-tasks/src/tasks/TaskWorker.ts | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/backend-tasks/src/tasks/LocalTaskWorker.ts b/packages/backend-tasks/src/tasks/LocalTaskWorker.ts index b7dd7c96c1..be30988aa8 100644 --- a/packages/backend-tasks/src/tasks/LocalTaskWorker.ts +++ b/packages/backend-tasks/src/tasks/LocalTaskWorker.ts @@ -123,7 +123,7 @@ export class LocalTaskWorker { let dt: number; if (isCron) { - const nextRun = +new CronTime(settings.cadence).sendAt().toDate(); + const nextRun = +new CronTime(settings.cadence).sendAt().toJSDate(); dt = nextRun - Date.now(); } else { dt = diff --git a/packages/backend-tasks/src/tasks/TaskWorker.ts b/packages/backend-tasks/src/tasks/TaskWorker.ts index 07e1cd90ec..db1cf8d01b 100644 --- a/packages/backend-tasks/src/tasks/TaskWorker.ts +++ b/packages/backend-tasks/src/tasks/TaskWorker.ts @@ -174,8 +174,9 @@ export class TaskWorker { } else if (isCron) { const time = new CronTime(settings.cadence) .sendAt() - .add({ seconds: -1 }) // immediately, if "* * * * * *" - .toISOString(); + .minus({ seconds: 1 }) // immediately, if "* * * * * *" + .toUTC() + .toISO(); startAt = this.knex.client.config.client.includes('sqlite3') ? this.knex.raw('datetime(?)', [time]) : this.knex.raw(`?`, [time]); @@ -278,7 +279,7 @@ export class TaskWorker { let nextRun: Knex.Raw; if (isCron) { - const time = new CronTime(settings.cadence).sendAt().toISOString(); + const time = new CronTime(settings.cadence).sendAt().toUTC().toISO(); this.logger.debug(`task: ${this.taskId} will next occur around ${time}`); nextRun = this.knex.client.config.client.includes('sqlite3') ? this.knex.raw('datetime(?)', [time]) From 36fb2461c5902708dcf8168e6bb16633135231ae Mon Sep 17 00:00:00 2001 From: Talita Gregory Nunes Freire Date: Fri, 22 Apr 2022 13:35:13 +0200 Subject: [PATCH 104/149] feat: Adding github-pull-requests-board plugin Signed-off-by: Talita Gregory Nunes Freire --- .../github-pull-requests-board/.eslintrc.js | 3 + plugins/github-pull-requests-board/README.md | 15 +++ .../github-pull-requests-board/package.json | 56 +++++++++ .../src/api/useGetPullRequestDetails.ts | 71 +++++++++++ .../api/useGetPullRequestsFromRepository.ts | 33 +++++ .../src/api/useOctokitGraphQl.ts | 21 ++++ .../src/components/Card/Card.tsx | 48 ++++++++ .../src/components/Card/CardHeader.tsx | 53 ++++++++ .../src/components/Card/index.ts | 1 + .../InfoCardHeader/InfoCardHeader.tsx | 25 ++++ .../src/components/InfoCardHeader/index.ts | 1 + .../PullRequestBoardOptions.tsx | 43 +++++++ .../PullRequestBoardOptions/index.ts | 1 + .../PullRequestCard/PullRequestCard.tsx | 62 ++++++++++ .../src/components/PullRequestCard/index.ts | 1 + .../SmallPullRequestCard.tsx | 74 +++++++++++ .../components/SmallPullRequestCard/index.ts | 1 + .../TeamPullRequestsPage.tsx | 109 ++++++++++++++++ .../components/TeamPullRequestsPage/index.ts | 1 + .../TeamPullRequestsTable.tsx | 116 ++++++++++++++++++ .../components/TeamPullRequestsTable/index.ts | 1 + .../src/components/UserHeader/UserHeader.tsx | 36 ++++++ .../src/components/UserHeader/index.ts | 1 + .../UserHeaderList/UserHeaderList.tsx | 24 ++++ .../src/components/UserHeaderList/index.ts | 1 + .../src/components/Wrapper/Wrapper.tsx | 20 +++ .../src/components/Wrapper/index.ts | 1 + .../src/components/icons/DraftPr/DraftPr.tsx | 10 ++ .../src/components/icons/DraftPr/index.ts | 1 + .../src/hooks/usePullRequestsByTeam.tsx | 70 +++++++++++ .../src/hooks/useUserRepositories.tsx | 34 +++++ .../github-pull-requests-board/src/index.ts | 1 + .../src/plugin.test.ts | 7 ++ .../github-pull-requests-board/src/plugin.ts | 34 +++++ .../github-pull-requests-board/src/routes.ts | 5 + .../src/setupTests.ts | 2 + .../src/utils/constants.ts | 5 + .../src/utils/functions.ts | 77 ++++++++++++ .../src/utils/types.tsx | 69 +++++++++++ 39 files changed, 1134 insertions(+) create mode 100644 plugins/github-pull-requests-board/.eslintrc.js create mode 100644 plugins/github-pull-requests-board/README.md create mode 100644 plugins/github-pull-requests-board/package.json create mode 100644 plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts create mode 100644 plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts create mode 100644 plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts create mode 100644 plugins/github-pull-requests-board/src/components/Card/Card.tsx create mode 100644 plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx create mode 100644 plugins/github-pull-requests-board/src/components/Card/index.ts create mode 100644 plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx create mode 100644 plugins/github-pull-requests-board/src/components/InfoCardHeader/index.ts create mode 100644 plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx create mode 100644 plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/index.ts create mode 100644 plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx create mode 100644 plugins/github-pull-requests-board/src/components/PullRequestCard/index.ts create mode 100644 plugins/github-pull-requests-board/src/components/SmallPullRequestCard/SmallPullRequestCard.tsx create mode 100644 plugins/github-pull-requests-board/src/components/SmallPullRequestCard/index.ts create mode 100644 plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx create mode 100644 plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/index.ts create mode 100644 plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/TeamPullRequestsTable.tsx create mode 100644 plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/index.ts create mode 100644 plugins/github-pull-requests-board/src/components/UserHeader/UserHeader.tsx create mode 100644 plugins/github-pull-requests-board/src/components/UserHeader/index.ts create mode 100644 plugins/github-pull-requests-board/src/components/UserHeaderList/UserHeaderList.tsx create mode 100644 plugins/github-pull-requests-board/src/components/UserHeaderList/index.ts create mode 100644 plugins/github-pull-requests-board/src/components/Wrapper/Wrapper.tsx create mode 100644 plugins/github-pull-requests-board/src/components/Wrapper/index.ts create mode 100644 plugins/github-pull-requests-board/src/components/icons/DraftPr/DraftPr.tsx create mode 100644 plugins/github-pull-requests-board/src/components/icons/DraftPr/index.ts create mode 100644 plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx create mode 100644 plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx create mode 100644 plugins/github-pull-requests-board/src/index.ts create mode 100644 plugins/github-pull-requests-board/src/plugin.test.ts create mode 100644 plugins/github-pull-requests-board/src/plugin.ts create mode 100644 plugins/github-pull-requests-board/src/routes.ts create mode 100644 plugins/github-pull-requests-board/src/setupTests.ts create mode 100644 plugins/github-pull-requests-board/src/utils/constants.ts create mode 100644 plugins/github-pull-requests-board/src/utils/functions.ts create mode 100644 plugins/github-pull-requests-board/src/utils/types.tsx diff --git a/plugins/github-pull-requests-board/.eslintrc.js b/plugins/github-pull-requests-board/.eslintrc.js new file mode 100644 index 0000000000..13573efa9c --- /dev/null +++ b/plugins/github-pull-requests-board/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint')], +}; diff --git a/plugins/github-pull-requests-board/README.md b/plugins/github-pull-requests-board/README.md new file mode 100644 index 0000000000..2beff349a8 --- /dev/null +++ b/plugins/github-pull-requests-board/README.md @@ -0,0 +1,15 @@ +# github-pull-requests-board + +Welcome to the github-pull-requests-board plugin! + +This plugin will help you and your team stay on top of open pull requests, hopefully reducing the time from open to merged. It's particularly useful when your team deals with many repositories. + +## Getting started + +The plugin exports the **TeamPullRequestsTable** component which should be added into the Team page level, so it can consume the backstage **"team"** entity. + +```javascript +import { TeamPullRequestsTable } from '@backstage/plugin-github-pull-requests-board'; + +; +``` diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json new file mode 100644 index 0000000000..dc61fc7cde --- /dev/null +++ b/plugins/github-pull-requests-board/package.json @@ -0,0 +1,56 @@ +{ + "name": "@backstage/plugin-github-pull-requests-board", + "version": "0.1.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" + }, + "scripts": { + "build": "backstage-cli plugin:build", + "start": "backstage-cli plugin:serve", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "diff": "backstage-cli plugin:diff", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean" + }, + "dependencies": { + "@backstage/catalog-model": "^1.0.0", + "@backstage/core-components": "^0.9.2", + "@backstage/core-plugin-api": "^1.0.0", + "@backstage/plugin-catalog-react": "^1.0.0", + "@backstage/theme": "^0.2.15", + "@material-ui/core": "^4.12.2", + "@material-ui/icons": "^4.9.1", + "@material-ui/lab": "4.0.0-alpha.45", + "@octokit/rest": "^18.6.7", + "moment": "^2.29.1", + "react-use": "^17.2.4" + }, + "devDependencies": { + "@backstage/cli": "^0.14.1", + "@backstage/core-app-api": "^0.5.2", + "@backstage/dev-utils": "^0.2.5", + "@backstage/test-utils": "^0.2.0", + "@testing-library/jest-dom": "^5.10.1", + "@testing-library/react": "^11.2.5", + "@testing-library/user-event": "^13.1.8", + "@types/jest": "^26.0.7", + "@types/node": "^14.14.32", + "cross-fetch": "^3.0.6", + "msw": "^0.29.0" + }, + "peerDependencies": { + "@types/react": "^16.13.1 || ^17.0.0", + "react": "^16.13.1 || ^17.0.0", + "react-dom": "^16.13.1 || ^17.0.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts new file mode 100644 index 0000000000..0732f86683 --- /dev/null +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts @@ -0,0 +1,71 @@ +import React from 'react'; + +import { GraphQlPullRequest, PullRequest } from '../utils/types'; +import { useOctokitGraphQl } from './useOctokitGraphQl'; + +export const useGetPullRequestDetails = () => { + const graphql = useOctokitGraphQl>(); + + const fn = React.useRef(async (repo: string, number: number): Promise => { + const [ organisation, repositoryName ] = repo.split('/'); + + const { repository } = await graphql(` + query($name: String!, $owner: String!, $pull_number: Int!) { + repository(name: $name, owner: $owner) { + pullRequest(number: $pull_number) { + id + repository { + name + } + title + url + createdAt + lastEditedAt + latestReviews(first: 10) { + nodes { + author { + login + avatarUrl + ... on User { + id + email + name + login + } + } + state + } + } + mergeable + state + reviewDecision + isDraft + createdAt + author { + ... on User { + id + email + avatarUrl + name + login + } + ... on Bot { + id + avatarUrl + login + } + } + } + } + } + `, { + 'name': repositoryName, + 'owner': organisation, + 'pull_number': number + }); + + return repository.pullRequest + }); + + return fn.current; +}; diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts new file mode 100644 index 0000000000..d03cca43d3 --- /dev/null +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts @@ -0,0 +1,33 @@ +import React from 'react'; + +import { GraphQlPullRequests, PullRequestsNumber } from '../utils/types'; +import { useOctokitGraphQl } from './useOctokitGraphQl'; + +export const useGetPullRequestsFromRepository = () => { + const graphql = useOctokitGraphQl>(); + + const fn = React.useRef(async (repo: string): Promise => { + const [ organisation, repositoryName ] = repo.split('/'); + + const { repository } = await graphql(` + query($name: String!, $owner: String!) { + repository(name: $name, owner: $owner) { + pullRequests(states: OPEN, first: 10) { + edges { + node { + number + } + } + } + } + } + `, { + 'name': repositoryName, + 'owner': organisation, + }); + + return repository.pullRequests.edges + }); + + return fn.current; +}; diff --git a/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts b/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts new file mode 100644 index 0000000000..36e53bcfb9 --- /dev/null +++ b/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts @@ -0,0 +1,21 @@ +import { Octokit } from '@octokit/rest'; +import { useApi, githubAuthApiRef } from '@backstage/core-plugin-api'; + +let octokit: any; + +export const useOctokitGraphQl = () => { + const auth = useApi(githubAuthApiRef); + + return (path: string, options?: any): Promise => + auth.getAccessToken(['repo']) + .then((token: string) => { + if(!octokit) { + octokit = new Octokit({ auth: token }) + } + + return octokit + }) + .then(octokitInstance => { + return octokitInstance.graphql(path, options) + }); +}; diff --git a/plugins/github-pull-requests-board/src/components/Card/Card.tsx b/plugins/github-pull-requests-board/src/components/Card/Card.tsx new file mode 100644 index 0000000000..8e1067aa1d --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/Card/Card.tsx @@ -0,0 +1,48 @@ +import React, { PropsWithChildren, FunctionComponent } from 'react'; +import { Box, Paper, CardActionArea } from '@material-ui/core'; +import CardHeader from './CardHeader'; + +type Props = { + title: string; + createdAt: string; + updatedAt?: string; + prUrl: string; + authorName: string; + authorAvatar?: string; + repositoryName: string; +} + +const Card: FunctionComponent = (props: PropsWithChildren) => { + const { + title, + createdAt, + updatedAt, + prUrl, + authorName, + authorAvatar, + repositoryName, + children + } = props; + + return ( + + + + + + { children } + + + + + ); +}; + +export default Card; diff --git a/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx b/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx new file mode 100644 index 0000000000..79e936f37d --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { Typography, Box } from '@material-ui/core'; +import { getElapsedTime } from '../../utils/functions'; +import { UserHeader } from '../UserHeader'; + +type Props = { + title: string; + createdAt: string; + updatedAt?: string; + authorName: string; + authorAvatar?: string; + repositoryName: string; +} + +const CardHeader = (props: Props) => { + const { + title, + createdAt, + updatedAt, + authorName, + authorAvatar, + repositoryName, + } = props; + + return ( + <> + + + {repositoryName} + + + + + {title} + + + + Created at: {getElapsedTime(createdAt)} + + { + updatedAt && ( + + Last update: {getElapsedTime(updatedAt)} + + ) + } + + + + ); +}; + +export default CardHeader; diff --git a/plugins/github-pull-requests-board/src/components/Card/index.ts b/plugins/github-pull-requests-board/src/components/Card/index.ts new file mode 100644 index 0000000000..06c3388d6c --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/Card/index.ts @@ -0,0 +1 @@ +export { default as Card } from './Card'; diff --git a/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx b/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx new file mode 100644 index 0000000000..24c1393dd2 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx @@ -0,0 +1,25 @@ +import React, { PropsWithChildren } from 'react'; +import { Typography, Box, IconButton } from '@material-ui/core'; +import RefreshIcon from '@material-ui/icons/Refresh'; + +type Props = { + onRefresh: () => void; +} + +const InfoCardHeader = (props: PropsWithChildren) => { + const { children, onRefresh } = props; + + return ( + + + Open pull requests + + + + + {children} + + ); +}; + +export default InfoCardHeader; diff --git a/plugins/github-pull-requests-board/src/components/InfoCardHeader/index.ts b/plugins/github-pull-requests-board/src/components/InfoCardHeader/index.ts new file mode 100644 index 0000000000..ad044f42a8 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/InfoCardHeader/index.ts @@ -0,0 +1 @@ +export { default as InfoCardHeader } from './InfoCardHeader'; diff --git a/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx b/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx new file mode 100644 index 0000000000..98f52c4970 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx @@ -0,0 +1,43 @@ +import React, { ReactNode } from 'react'; +import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab'; +import { Tooltip, Box } from '@material-ui/core'; +import { PRCardFormating } from '../../utils/types'; + +type Option = { + icon: ReactNode; + value: string; + ariaLabel: string; +} + +type Props = { + value: string[]; + onClickOption: (selectedOptions: PRCardFormating[]) => void; + options: Option[]; +} + +const PullRequestBoardOptions = (props: Props) => { + const { value, onClickOption, options } = props; + return ( + onClickOption(selectedOptions)} + aria-label="Pull Request board settings" + > + { + options.map(({ icon, value: toggleValue, ariaLabel }, index) => ( + + + + {icon} + + + + )) + } + + + ); +}; + +export default PullRequestBoardOptions; diff --git a/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/index.ts b/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/index.ts new file mode 100644 index 0000000000..31b4198239 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/index.ts @@ -0,0 +1 @@ +export { default as PullRequestBoardOptions } from './PullRequestBoardOptions'; diff --git a/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx b/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx new file mode 100644 index 0000000000..66d4fbcf29 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx @@ -0,0 +1,62 @@ +import React, { FunctionComponent } from 'react'; +import { getApprovedReviews, getChangeRequests, getCommentedReviews } from '../../utils/functions'; +import { Reviews, Author } from '../../utils/types'; +import { Card } from '../Card'; +import { UserHeaderList } from '../UserHeaderList'; + +type Props = { + title: string; + createdAt: string; + updatedAt?: string; + author: Author; + url: string; + reviews: Reviews; + repositoryName: string; + isDraft: boolean; +} + +const PullRequestCard: FunctionComponent = (props: Props) => { + const { + title, + createdAt, + updatedAt, + author, + url, + reviews, + repositoryName, + isDraft, + } = props; + + const approvedReviews = getApprovedReviews(reviews); + const commentsReviews = getCommentedReviews(reviews); + const changeRequests = getChangeRequests(reviews); + + const cardTitle = isDraft ? `🔧 DRAFT - ${title}` : title; + + return ( + + {!!approvedReviews.length && ( + reviewAuthor)}/> + )} + {!!commentsReviews.length && ( + reviewAuthor)} + /> + )} + {!!changeRequests.length && ( + reviewAuthor)}/> + )} + + ); +}; + +export default PullRequestCard; diff --git a/plugins/github-pull-requests-board/src/components/PullRequestCard/index.ts b/plugins/github-pull-requests-board/src/components/PullRequestCard/index.ts new file mode 100644 index 0000000000..ed77163680 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/PullRequestCard/index.ts @@ -0,0 +1 @@ +export { default as PullRequestCard } from './PullRequestCard'; diff --git a/plugins/github-pull-requests-board/src/components/SmallPullRequestCard/SmallPullRequestCard.tsx b/plugins/github-pull-requests-board/src/components/SmallPullRequestCard/SmallPullRequestCard.tsx new file mode 100644 index 0000000000..3bf53b4487 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/SmallPullRequestCard/SmallPullRequestCard.tsx @@ -0,0 +1,74 @@ +import React, { FunctionComponent } from 'react'; +import { Box, Chip } from '@material-ui/core'; +import { getApprovedReviews, getCommentedReviews } from '../../utils/functions'; +import { Card } from '../Card'; +import { Reviews, Author } from '../../utils/types'; + +type Props = { + title: string; + createdAt: string; + updatedAt?: string; + author: Author; + url: string; + reviews: Reviews; + repositoryName: string; + isDraft: boolean; +} + +const SmallPullRequestCard: FunctionComponent = (props: Props) => { + const { + title, + createdAt, + updatedAt, + author, + url, + reviews, + repositoryName, + isDraft, + } = props; + + const approvedReviews = getApprovedReviews(reviews); + const commentsReviews = getCommentedReviews(reviews); + + const containReviews = !!approvedReviews.length || !!commentsReviews.length; + const cardTitle = isDraft ? `🔧 DRAFT - ${title}` : title; + + return ( + + { + containReviews && ( + + {!!approvedReviews.length && ( + + + + )} + {!!commentsReviews.length && ( + + )} + + ) + } + + ); +}; + +export default SmallPullRequestCard; diff --git a/plugins/github-pull-requests-board/src/components/SmallPullRequestCard/index.ts b/plugins/github-pull-requests-board/src/components/SmallPullRequestCard/index.ts new file mode 100644 index 0000000000..eeffd436d8 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/SmallPullRequestCard/index.ts @@ -0,0 +1 @@ +export { default as SmallPullRequestCard } from './SmallPullRequestCard'; diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx b/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx new file mode 100644 index 0000000000..eccb0b29b3 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx @@ -0,0 +1,109 @@ +import React, { FunctionComponent, useState } from 'react'; +import { Grid, Typography } from '@material-ui/core'; +import ViewModuleIcon from '@material-ui/icons/ViewModule'; +import { Progress, InfoCard } from '@backstage/core-components'; + +import { InfoCardHeader } from '../../components/InfoCardHeader'; +import { PullRequestBoardOptions } from '../../components/PullRequestBoardOptions'; +import { Wrapper } from '../../components/Wrapper'; +import { SmallPullRequestCard } from '../../components/SmallPullRequestCard'; +import { PullRequestCard } from '../../components/PullRequestCard'; +import { usePullRequestsByTeam } from '../../hooks/usePullRequestsByTeam'; +import { PRCardFormating } from '../../utils/types'; +import { DraftPrIcon } from '../../components/icons/DraftPr' +import { useUserRepositories } from '../../hooks/useUserRepositories'; + +const TeamPullRequestsPage: FunctionComponent = () => { + const [infoCardFormat, setInfoCardFormat] = useState([]); + const { repositories } = useUserRepositories(); + const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam(repositories); + + const CardComponent = infoCardFormat.includes('compacted') + ? SmallPullRequestCard + : PullRequestCard; + + const header = ( + + setInfoCardFormat(newFormats)} + value={infoCardFormat} + options={[ + { + icon: , + value: 'compacted', + ariaLabel: 'Cards compacted' + }, + { + icon: , + value: 'draft', + ariaLabel: 'Show draft PRs' + }, + ]} + /> + + ); + + const getContent = () => { + if (loading) { + return ; + } + + return ( + + {pullRequests.length ? ( + pullRequests.map(({ title: columnTitle, content }) => ( + + + {columnTitle} + + {content.map(({ + id, + title, + createdAt, + lastEditedAt, + author, + url, + latestReviews, + repository, + isDraft + }, index) => ( + isDraft ? (infoCardFormat.includes('draft') === isDraft) && + + : + ))} + + )) + ) : ( + No pull requests found + )} + + ); + }; + + return {getContent()}; +}; + +export default TeamPullRequestsPage; diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/index.ts b/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/index.ts new file mode 100644 index 0000000000..b58aed2482 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/index.ts @@ -0,0 +1 @@ +export { default as TeamPullRequestsPage } from './TeamPullRequestsPage'; diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/TeamPullRequestsTable.tsx b/plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/TeamPullRequestsTable.tsx new file mode 100644 index 0000000000..e80bab7837 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/TeamPullRequestsTable.tsx @@ -0,0 +1,116 @@ +import React, { FunctionComponent, useState } from 'react'; +import { Grid, Typography } from '@material-ui/core'; +import ViewModuleIcon from '@material-ui/icons/ViewModule'; +import FullscreenIcon from '@material-ui/icons/Fullscreen'; + +import { Progress, InfoCard } from '@backstage/core-components'; + +import { InfoCardHeader } from '../../components/InfoCardHeader'; +import { PullRequestBoardOptions } from '../../components/PullRequestBoardOptions'; +import { Wrapper } from '../../components/Wrapper'; +import { SmallPullRequestCard } from '../../components/SmallPullRequestCard'; +import { PullRequestCard } from '../../components/PullRequestCard'; +import { usePullRequestsByTeam } from '../../hooks/usePullRequestsByTeam'; +import { PRCardFormating } from '../../utils/types'; +import { DraftPrIcon } from '../../components/icons/DraftPr' +import { useUserRepositories } from '../../hooks/useUserRepositories'; + +const TeamPullRequestsTable: FunctionComponent = () => { + const [infoCardFormat, setInfoCardFormat] = useState([]); + const { repositories } = useUserRepositories(); + const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam(repositories); + + const CardComponent = infoCardFormat.includes('compacted') + ? SmallPullRequestCard + : PullRequestCard; + + const header = ( + + setInfoCardFormat(newFormats)} + value={infoCardFormat} + options={[ + { + icon: , + value: 'draft', + ariaLabel: 'Show draft PRs' + }, + { + icon: , + value: 'compacted', + ariaLabel: 'Cards compacted' + }, + { + icon: , + value: 'fullscreen', + ariaLabel: 'Info card is set to fullscreen' + } + ]} + /> + + ); + + const getContent = () => { + if (loading) { + return ; + } + + return ( + + {pullRequests.length ? ( + pullRequests.map(({ title: columnTitle, content }) => ( + + + {columnTitle} + + {content.map(({ + id, + title, + createdAt, + lastEditedAt, + author, + url, + latestReviews, + repository, + isDraft + }, index) => ( + isDraft ? (infoCardFormat.includes('draft') === isDraft) && + + : + ))} + + )) + ) : ( + No pull requests found + )} + + ); + }; + + return {getContent()}; +}; + +export default TeamPullRequestsTable; diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/index.ts b/plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/index.ts new file mode 100644 index 0000000000..0da871526a --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/index.ts @@ -0,0 +1 @@ +export { default as TeamPullRequestsTable } from './TeamPullRequestsTable'; diff --git a/plugins/github-pull-requests-board/src/components/UserHeader/UserHeader.tsx b/plugins/github-pull-requests-board/src/components/UserHeader/UserHeader.tsx new file mode 100644 index 0000000000..e7f4c1604d --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/UserHeader/UserHeader.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { + Typography, + Box, + Avatar, + makeStyles +} from '@material-ui/core'; + +type Props = { + name: string; + avatar?: string; +} + +const useStyles = makeStyles((theme) => ({ + small: { + width: theme.spacing(4), + height: theme.spacing(4), + marginLeft: theme.spacing(1) + } +})); + +const UserHeader = (props: Props) => { + const { name, avatar } = props; + const classes = useStyles(); + + return ( + + + {name} + + + + ); +}; + +export default UserHeader; diff --git a/plugins/github-pull-requests-board/src/components/UserHeader/index.ts b/plugins/github-pull-requests-board/src/components/UserHeader/index.ts new file mode 100644 index 0000000000..7229897183 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/UserHeader/index.ts @@ -0,0 +1 @@ +export { default as UserHeader } from './UserHeader'; diff --git a/plugins/github-pull-requests-board/src/components/UserHeaderList/UserHeaderList.tsx b/plugins/github-pull-requests-board/src/components/UserHeaderList/UserHeaderList.tsx new file mode 100644 index 0000000000..0f93fbfbc2 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/UserHeaderList/UserHeaderList.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Typography, Box } from '@material-ui/core'; +import { filterSameUser } from '../../utils/functions'; + +import { UserHeader } from '../UserHeader'; +import { Author } from '../../utils/types'; + +type Props = { + label?: string; + users: Author[]; +} + +const UserHeaderList = (props: Props) => { + const { users, label } = props; + + return ( + + {label && {label}} + {filterSameUser(users).map(({ login, avatarUrl }) => )} + + ); +}; + +export default UserHeaderList; diff --git a/plugins/github-pull-requests-board/src/components/UserHeaderList/index.ts b/plugins/github-pull-requests-board/src/components/UserHeaderList/index.ts new file mode 100644 index 0000000000..88fdb2c4e8 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/UserHeaderList/index.ts @@ -0,0 +1 @@ +export { default as UserHeaderList } from './UserHeaderList'; diff --git a/plugins/github-pull-requests-board/src/components/Wrapper/Wrapper.tsx b/plugins/github-pull-requests-board/src/components/Wrapper/Wrapper.tsx new file mode 100644 index 0000000000..2197e15a06 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/Wrapper/Wrapper.tsx @@ -0,0 +1,20 @@ +import React, { PropsWithChildren } from 'react'; +import { Grid, Box } from '@material-ui/core'; + +type Props = { + fullscreen: boolean; +} + +const Wrapper = (props: PropsWithChildren) => { + const { children, fullscreen } = props; + + return ( + + + {children} + + + ) +}; + +export default Wrapper; diff --git a/plugins/github-pull-requests-board/src/components/Wrapper/index.ts b/plugins/github-pull-requests-board/src/components/Wrapper/index.ts new file mode 100644 index 0000000000..50472e028e --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/Wrapper/index.ts @@ -0,0 +1 @@ +export { default as Wrapper } from './Wrapper'; diff --git a/plugins/github-pull-requests-board/src/components/icons/DraftPr/DraftPr.tsx b/plugins/github-pull-requests-board/src/components/icons/DraftPr/DraftPr.tsx new file mode 100644 index 0000000000..22e96f71f3 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/icons/DraftPr/DraftPr.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +const DraftPr = () => ( + +); + +export default DraftPr; diff --git a/plugins/github-pull-requests-board/src/components/icons/DraftPr/index.ts b/plugins/github-pull-requests-board/src/components/icons/DraftPr/index.ts new file mode 100644 index 0000000000..a91e0d1cb7 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/icons/DraftPr/index.ts @@ -0,0 +1 @@ +export { default as DraftPrIcon } from './DraftPr'; diff --git a/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx b/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx new file mode 100644 index 0000000000..42135379b2 --- /dev/null +++ b/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx @@ -0,0 +1,70 @@ +import { useCallback, useEffect, useState } from 'react'; +import { formatPRsByReviewDecision } from '../utils/functions'; +import { PullRequests, PullRequestsColumn } from '../utils/types'; +import { useGetPullRequestsFromRepository } from '../api/useGetPullRequestsFromRepository'; +import { useGetPullRequestDetails } from '../api/useGetPullRequestDetails'; + +export function usePullRequestsByTeam(repositories: string[]) { + const [pullRequests, setPullRequests] = useState([]); + const [loading, setLoading] = useState(true); + const getPullRequests = useGetPullRequestsFromRepository(); + const getPullRequestDetails = useGetPullRequestDetails(); + + const getPRsPerRepository = useCallback(async (repository: string): Promise => { + + const pullRequestsNumbers = await getPullRequests(repository) + + const pullRequestsWithDetails = await Promise.all( + pullRequestsNumbers.map(async ({ node }) => { + const pullRequest = await getPullRequestDetails( + repository, + node.number, + ); + + return pullRequest; + }), + ); + + return pullRequestsWithDetails; + }, [getPullRequests, getPullRequestDetails]); + + const getPRsFromTeam = useCallback( + async (teamRepositories: string[]): Promise => { + + const teamRepositoriesPromises = teamRepositories.map(repository => + getPRsPerRepository(repository), + ); + + const teamPullRequests = await Promise.allSettled(teamRepositoriesPromises) + .then(promises => promises.reduce((acc, curr) => { + if (curr.status === 'fulfilled') { + return [...acc, ...curr.value]; + } + return acc; + },[] as PullRequests) + ); + + return teamPullRequests; + }, + [getPRsPerRepository], + ); + + const getAllPullRequests = useCallback(async () => { + setLoading(true); + + const teamPullRequests = await getPRsFromTeam(repositories); + setPullRequests(formatPRsByReviewDecision(teamPullRequests)); + setLoading(false); + + }, [getPRsFromTeam, repositories]); + + useEffect(() => { + getAllPullRequests() + }, [getAllPullRequests]); + + return { + pullRequests, + loading, + refreshPullRequests: getAllPullRequests, + }; +} diff --git a/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx b/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx new file mode 100644 index 0000000000..a463734a60 --- /dev/null +++ b/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx @@ -0,0 +1,34 @@ +import { useApi } from '@backstage/core-plugin-api'; +import { useEntity, catalogApiRef } from '@backstage/plugin-catalog-react'; +import { useCallback, useEffect, useState } from 'react'; +import { getProjectNameFromEntity } from '../utils/functions'; + +export function useUserRepositories() { + const { entity: teamEntity } = useEntity(); + const catalogApi = useApi(catalogApiRef); + const [repositories, setRepositories] = useState([]); + + const getRepositoriesNames = useCallback(async () => { + const entitiesList = await catalogApi.getEntities({ + filter: { + kind: 'Component', + 'spec.type': 'service', + 'spec.owner': teamEntity?.metadata?.name, + }, + }); + + const entitiesNames: string[] = entitiesList.items.map(componentEntity => + getProjectNameFromEntity(componentEntity) + ); + + setRepositories([...new Set(entitiesNames)]); + }, [catalogApi, teamEntity?.metadata?.name]); + + useEffect(() => { + getRepositoriesNames() + }, [getRepositoriesNames]); + + return { + repositories, + }; +} diff --git a/plugins/github-pull-requests-board/src/index.ts b/plugins/github-pull-requests-board/src/index.ts new file mode 100644 index 0000000000..4c22dc54be --- /dev/null +++ b/plugins/github-pull-requests-board/src/index.ts @@ -0,0 +1 @@ +export { TeamPullRequestsTable, TeamPullRequestsPage } from './plugin'; diff --git a/plugins/github-pull-requests-board/src/plugin.test.ts b/plugins/github-pull-requests-board/src/plugin.test.ts new file mode 100644 index 0000000000..4bef78d8ee --- /dev/null +++ b/plugins/github-pull-requests-board/src/plugin.test.ts @@ -0,0 +1,7 @@ +import { TeamPullRequestsTable } from './plugin'; + +describe('github-pull-requests-board', () => { + it('should export TeamPullRequestsTable', () => { + expect(TeamPullRequestsTable).toBeDefined(); + }); +}); diff --git a/plugins/github-pull-requests-board/src/plugin.ts b/plugins/github-pull-requests-board/src/plugin.ts new file mode 100644 index 0000000000..aef2782579 --- /dev/null +++ b/plugins/github-pull-requests-board/src/plugin.ts @@ -0,0 +1,34 @@ +import { + createPlugin, + createComponentExtension, + createRoutableExtension, +} from '@backstage/core-plugin-api'; +import { rootRouteRef } from './routes'; + +const githubPullRequestsBoardPlugin = createPlugin({ + id: 'github-pull-requests-board', + routes: { + root: rootRouteRef, + }, +}); + +export const TeamPullRequestsTable = githubPullRequestsBoardPlugin.provide( + createComponentExtension({ + name: 'TeamPullRequestsTable', + component: { + lazy: () => + import('./components/TeamPullRequestsTable').then( + m => m.TeamPullRequestsTable, + ), + }, + }), +); + +export const TeamPullRequestsPage = githubPullRequestsBoardPlugin.provide( + createRoutableExtension({ + name: 'PullRequestPage', + component: () => + import('./components/TeamPullRequestsPage').then(m => m.TeamPullRequestsPage), + mountPoint: rootRouteRef, + }), +); diff --git a/plugins/github-pull-requests-board/src/routes.ts b/plugins/github-pull-requests-board/src/routes.ts new file mode 100644 index 0000000000..170bb9fbfe --- /dev/null +++ b/plugins/github-pull-requests-board/src/routes.ts @@ -0,0 +1,5 @@ +import { createRouteRef } from '@backstage/core-plugin-api'; + +export const rootRouteRef = createRouteRef({ + id: 'github-pull-requests-board', +}); diff --git a/plugins/github-pull-requests-board/src/setupTests.ts b/plugins/github-pull-requests-board/src/setupTests.ts new file mode 100644 index 0000000000..48c09b5346 --- /dev/null +++ b/plugins/github-pull-requests-board/src/setupTests.ts @@ -0,0 +1,2 @@ +import '@testing-library/jest-dom'; +import 'cross-fetch/polyfill'; diff --git a/plugins/github-pull-requests-board/src/utils/constants.ts b/plugins/github-pull-requests-board/src/utils/constants.ts new file mode 100644 index 0000000000..05cef50634 --- /dev/null +++ b/plugins/github-pull-requests-board/src/utils/constants.ts @@ -0,0 +1,5 @@ +export const COLUMNS = Object.freeze({ + REVIEW_REQUIRED: '🔍 Review required', + REVIEW_IN_PROGRESS: '📝 Review in progress', + APPROVED: '👍 Approved' +}) diff --git a/plugins/github-pull-requests-board/src/utils/functions.ts b/plugins/github-pull-requests-board/src/utils/functions.ts new file mode 100644 index 0000000000..c005d0ba8e --- /dev/null +++ b/plugins/github-pull-requests-board/src/utils/functions.ts @@ -0,0 +1,77 @@ +import { Entity } from '@backstage/catalog-model'; +import moment from 'moment'; +import { Reviews, PullRequests, ReviewDecision, PullRequestsColumn, Author } from './types'; +import { COLUMNS } from './constants'; + +const GITHUB_PULL_REQUESTS_ANNOTATION = 'github.com/project-slug'; + + +export const getProjectNameFromEntity = (entity: Entity): string => { + return entity?.metadata.annotations?.[GITHUB_PULL_REQUESTS_ANNOTATION] ?? ''; +}; + +export const getApprovedReviews = (reviews: Reviews = []): Reviews => { + return reviews.filter(({ state }) => state === 'APPROVED'); +}; + +export const getCommentedReviews = (reviews: Reviews = []): Reviews => { + return reviews.filter(({ state }) => state === 'COMMENTED'); +}; +export const getChangeRequests = (reviews: Reviews = []): Reviews => { + return reviews.filter(({ state }) => state === 'CHANGES_REQUESTED'); +}; + +export const filterSameUser = (users: Author[]): Author[] => { + return users.reduce((acc, curr) => { + const contaisUser = acc.find(({ login }) => login === curr.login); + + if(!contaisUser) { + return [ ...acc, curr ]; + } + + return acc; + }, [] as Author[]); +} + +export const getElapsedTime = (start: string): string => { + return moment(start).fromNow(); +}; + +export const formatPRsByReviewDecision = (prs: PullRequests): PullRequestsColumn[] => { + const reviewDecisions = prs.reduce((acc, curr) => { + const decision = curr.reviewDecision || 'REVIEW_REQUIRED'; + + if(decision !== 'APPROVED' && curr.latestReviews.nodes.length === 0) { + return { + ...acc, + REVIEW_REQUIRED: [...acc.REVIEW_REQUIRED, curr] + } + } + + if(decision !== 'APPROVED' && curr.latestReviews.nodes.length > 0) { + return { + ...acc, + IN_PROGRESS: [...acc.IN_PROGRESS, curr] + } + } + + if(decision === 'APPROVED') { + return { + ...acc, + APPROVED: [...acc.APPROVED, curr] + } + } + + return acc; + }, { + REVIEW_REQUIRED: [], + IN_PROGRESS: [], + APPROVED: [] + } as Record); + + return [ + { title: COLUMNS.REVIEW_REQUIRED, content: reviewDecisions.REVIEW_REQUIRED }, + { title: COLUMNS.REVIEW_IN_PROGRESS, content: reviewDecisions.IN_PROGRESS }, + { title: COLUMNS.APPROVED, content: reviewDecisions.APPROVED }, + ]; +}; \ No newline at end of file diff --git a/plugins/github-pull-requests-board/src/utils/types.tsx b/plugins/github-pull-requests-board/src/utils/types.tsx new file mode 100644 index 0000000000..789aa8a704 --- /dev/null +++ b/plugins/github-pull-requests-board/src/utils/types.tsx @@ -0,0 +1,69 @@ +export type GraphQlPullRequest = { + repository: { + pullRequest: T + } +} + +export type GraphQlPullRequests = { + repository: { + pullRequests: { + edges: T + } + } +} + +export type PullRequestsNumber = { + node: { + number: number; + } +} + +export type Review = { + state: + | 'PENDING' + | 'COMMENTED' + | 'APPROVED' + | 'CHANGES_REQUESTED' + | 'DISMISSED'; + author: Author; +}; + +export type Reviews = Review[]; + +export type Author = { + login: string; + avatarUrl: string; + id: string; + email: string; + name: string; +}; + +export type PullRequest = { + id: string; + repository: { + name: string; + }; + title: string; + url: string; + lastEditedAt: string; + latestReviews: { + nodes: Reviews; + }; + mergeable: boolean; + state: string; + reviewDecision: ReviewDecision | null; + isDraft: boolean; + createdAt: string; + author: Author +}; + +export type PullRequests = PullRequest[]; + +export type PullRequestsColumn = { + title: string; + content: PullRequests; +}; + +export type PRCardFormating = 'compacted' | 'fullscreen' | 'draft'; + +export type ReviewDecision = 'IN_PROGRESS' | 'APPROVED' | 'REVIEW_REQUIRED' \ No newline at end of file From ed77c0af0d8ee4fa99dc1a9f700ab4c3e33efef7 Mon Sep 17 00:00:00 2001 From: Talita Gregory Nunes Freire Date: Fri, 22 Apr 2022 16:29:03 +0200 Subject: [PATCH 105/149] feat: copyright added in all files Signed-off-by: Talita Gregory Nunes Freire --- .../github-pull-requests-board/package.json | 15 + .../src/api/useGetPullRequestDetails.ts | 15 + .../api/useGetPullRequestsFromRepository.ts | 15 + .../src/api/useOctokitGraphQl.ts | 15 + .../src/components/Card/Card.tsx | 17 +- .../src/components/Card/CardHeader.tsx | 15 + .../src/components/Card/index.ts | 15 + .../InfoCardHeader/InfoCardHeader.tsx | 15 + .../src/components/InfoCardHeader/index.ts | 15 + .../PullRequestBoardOptions.tsx | 15 + .../PullRequestBoardOptions/index.ts | 15 + .../PullRequestCard/PullRequestCard.tsx | 19 +- .../src/components/PullRequestCard/index.ts | 15 + .../SmallPullRequestCard.tsx | 74 -- .../components/SmallPullRequestCard/index.ts | 1 - .../TeamPullRequestsPage.tsx | 30 +- .../TeamPullRequestsTable.tsx | 30 +- .../src/components/UserHeader/UserHeader.tsx | 15 + .../src/components/UserHeader/index.ts | 15 + .../UserHeaderList/UserHeaderList.tsx | 15 + .../src/components/UserHeaderList/index.ts | 15 + .../src/components/Wrapper/Wrapper.tsx | 15 + .../src/components/Wrapper/index.ts | 15 + .../src/components/icons/DraftPr/DraftPr.tsx | 15 + .../src/components/icons/DraftPr/index.ts | 15 + .../src/hooks/usePullRequestsByTeam.tsx | 21 +- .../src/hooks/useUserRepositories.tsx | 15 + .../github-pull-requests-board/src/index.ts | 15 + .../src/plugin.test.ts | 20 +- .../github-pull-requests-board/src/plugin.ts | 15 + .../github-pull-requests-board/src/routes.ts | 15 + .../src/setupTests.ts | 15 + .../src/utils/constants.ts | 15 + .../src/utils/functions.ts | 15 + .../src/utils/types.tsx | 15 + yarn.lock | 942 +++++++++++++++++- 36 files changed, 1419 insertions(+), 140 deletions(-) delete mode 100644 plugins/github-pull-requests-board/src/components/SmallPullRequestCard/SmallPullRequestCard.tsx delete mode 100644 plugins/github-pull-requests-board/src/components/SmallPullRequestCard/index.ts diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json index dc61fc7cde..2ffcd90bd1 100644 --- a/plugins/github-pull-requests-board/package.json +++ b/plugins/github-pull-requests-board/package.json @@ -1,5 +1,6 @@ { "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.0", "main": "src/index.ts", "types": "src/index.ts", @@ -9,6 +10,20 @@ "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/github-pull-requests-board" + }, + "keywords": [ + "backstage", + "github", + "pull requests" + ], "scripts": { "build": "backstage-cli plugin:build", "start": "backstage-cli plugin:serve", diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts index 0732f86683..05bbdc1e75 100644 --- a/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestDetails.ts @@ -1,3 +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 React from 'react'; import { GraphQlPullRequest, PullRequest } from '../utils/types'; diff --git a/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts index d03cca43d3..2dd19d8087 100644 --- a/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts +++ b/plugins/github-pull-requests-board/src/api/useGetPullRequestsFromRepository.ts @@ -1,3 +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 React from 'react'; import { GraphQlPullRequests, PullRequestsNumber } from '../utils/types'; diff --git a/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts b/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts index 36e53bcfb9..2460588987 100644 --- a/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts +++ b/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts @@ -1,3 +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 { Octokit } from '@octokit/rest'; import { useApi, githubAuthApiRef } from '@backstage/core-plugin-api'; diff --git a/plugins/github-pull-requests-board/src/components/Card/Card.tsx b/plugins/github-pull-requests-board/src/components/Card/Card.tsx index 8e1067aa1d..661416edbe 100644 --- a/plugins/github-pull-requests-board/src/components/Card/Card.tsx +++ b/plugins/github-pull-requests-board/src/components/Card/Card.tsx @@ -1,3 +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 React, { PropsWithChildren, FunctionComponent } from 'react'; import { Box, Paper, CardActionArea } from '@material-ui/core'; import CardHeader from './CardHeader'; @@ -37,7 +52,7 @@ const Card: FunctionComponent = (props: PropsWithChildren) => { authorAvatar={authorAvatar} repositoryName={repositoryName} /> - { children } + {children} diff --git a/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx b/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx index 79e936f37d..99eb0f63bf 100644 --- a/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx +++ b/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx @@ -1,3 +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 React from 'react'; import { Typography, Box } from '@material-ui/core'; import { getElapsedTime } from '../../utils/functions'; diff --git a/plugins/github-pull-requests-board/src/components/Card/index.ts b/plugins/github-pull-requests-board/src/components/Card/index.ts index 06c3388d6c..527bd23115 100644 --- a/plugins/github-pull-requests-board/src/components/Card/index.ts +++ b/plugins/github-pull-requests-board/src/components/Card/index.ts @@ -1 +1,16 @@ +/* + * 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 { default as Card } from './Card'; diff --git a/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx b/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx index 24c1393dd2..47bab88693 100644 --- a/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx +++ b/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx @@ -1,3 +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 React, { PropsWithChildren } from 'react'; import { Typography, Box, IconButton } from '@material-ui/core'; import RefreshIcon from '@material-ui/icons/Refresh'; diff --git a/plugins/github-pull-requests-board/src/components/InfoCardHeader/index.ts b/plugins/github-pull-requests-board/src/components/InfoCardHeader/index.ts index ad044f42a8..393a04af55 100644 --- a/plugins/github-pull-requests-board/src/components/InfoCardHeader/index.ts +++ b/plugins/github-pull-requests-board/src/components/InfoCardHeader/index.ts @@ -1 +1,16 @@ +/* + * 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 { default as InfoCardHeader } from './InfoCardHeader'; diff --git a/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx b/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx index 98f52c4970..e914b774b3 100644 --- a/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx +++ b/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx @@ -1,3 +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 React, { ReactNode } from 'react'; import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab'; import { Tooltip, Box } from '@material-ui/core'; diff --git a/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/index.ts b/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/index.ts index 31b4198239..793ae8d0b7 100644 --- a/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/index.ts +++ b/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/index.ts @@ -1 +1,16 @@ +/* + * 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 { default as PullRequestBoardOptions } from './PullRequestBoardOptions'; diff --git a/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx b/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx index 66d4fbcf29..2884f728d3 100644 --- a/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx +++ b/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx @@ -1,3 +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 React, { FunctionComponent } from 'react'; import { getApprovedReviews, getChangeRequests, getCommentedReviews } from '../../utils/functions'; import { Reviews, Author } from '../../utils/types'; @@ -44,7 +59,7 @@ const PullRequestCard: FunctionComponent = (props: Props) => { prUrl={url} > {!!approvedReviews.length && ( - reviewAuthor)}/> + reviewAuthor)} /> )} {!!commentsReviews.length && ( = (props: Props) => { /> )} {!!changeRequests.length && ( - reviewAuthor)}/> + reviewAuthor)} /> )} ); diff --git a/plugins/github-pull-requests-board/src/components/PullRequestCard/index.ts b/plugins/github-pull-requests-board/src/components/PullRequestCard/index.ts index ed77163680..86da8f79af 100644 --- a/plugins/github-pull-requests-board/src/components/PullRequestCard/index.ts +++ b/plugins/github-pull-requests-board/src/components/PullRequestCard/index.ts @@ -1 +1,16 @@ +/* + * 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 { default as PullRequestCard } from './PullRequestCard'; diff --git a/plugins/github-pull-requests-board/src/components/SmallPullRequestCard/SmallPullRequestCard.tsx b/plugins/github-pull-requests-board/src/components/SmallPullRequestCard/SmallPullRequestCard.tsx deleted file mode 100644 index 3bf53b4487..0000000000 --- a/plugins/github-pull-requests-board/src/components/SmallPullRequestCard/SmallPullRequestCard.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { FunctionComponent } from 'react'; -import { Box, Chip } from '@material-ui/core'; -import { getApprovedReviews, getCommentedReviews } from '../../utils/functions'; -import { Card } from '../Card'; -import { Reviews, Author } from '../../utils/types'; - -type Props = { - title: string; - createdAt: string; - updatedAt?: string; - author: Author; - url: string; - reviews: Reviews; - repositoryName: string; - isDraft: boolean; -} - -const SmallPullRequestCard: FunctionComponent = (props: Props) => { - const { - title, - createdAt, - updatedAt, - author, - url, - reviews, - repositoryName, - isDraft, - } = props; - - const approvedReviews = getApprovedReviews(reviews); - const commentsReviews = getCommentedReviews(reviews); - - const containReviews = !!approvedReviews.length || !!commentsReviews.length; - const cardTitle = isDraft ? `🔧 DRAFT - ${title}` : title; - - return ( - - { - containReviews && ( - - {!!approvedReviews.length && ( - - - - )} - {!!commentsReviews.length && ( - - )} - - ) - } - - ); -}; - -export default SmallPullRequestCard; diff --git a/plugins/github-pull-requests-board/src/components/SmallPullRequestCard/index.ts b/plugins/github-pull-requests-board/src/components/SmallPullRequestCard/index.ts deleted file mode 100644 index eeffd436d8..0000000000 --- a/plugins/github-pull-requests-board/src/components/SmallPullRequestCard/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as SmallPullRequestCard } from './SmallPullRequestCard'; diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx b/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx index eccb0b29b3..be388e24e9 100644 --- a/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx +++ b/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx @@ -1,12 +1,25 @@ +/* + * 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, { FunctionComponent, useState } from 'react'; import { Grid, Typography } from '@material-ui/core'; -import ViewModuleIcon from '@material-ui/icons/ViewModule'; import { Progress, InfoCard } from '@backstage/core-components'; import { InfoCardHeader } from '../../components/InfoCardHeader'; import { PullRequestBoardOptions } from '../../components/PullRequestBoardOptions'; import { Wrapper } from '../../components/Wrapper'; -import { SmallPullRequestCard } from '../../components/SmallPullRequestCard'; import { PullRequestCard } from '../../components/PullRequestCard'; import { usePullRequestsByTeam } from '../../hooks/usePullRequestsByTeam'; import { PRCardFormating } from '../../utils/types'; @@ -18,21 +31,12 @@ const TeamPullRequestsPage: FunctionComponent = () => { const { repositories } = useUserRepositories(); const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam(repositories); - const CardComponent = infoCardFormat.includes('compacted') - ? SmallPullRequestCard - : PullRequestCard; - const header = ( setInfoCardFormat(newFormats)} value={infoCardFormat} options={[ - { - icon: , - value: 'compacted', - ariaLabel: 'Cards compacted' - }, { icon: , value: 'draft', @@ -71,7 +75,7 @@ const TeamPullRequestsPage: FunctionComponent = () => { isDraft }, index) => ( isDraft ? (infoCardFormat.includes('draft') === isDraft) && - { repositoryName={repository.name} isDraft={isDraft} /> - : { const { repositories } = useUserRepositories(); const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam(repositories); - const CardComponent = infoCardFormat.includes('compacted') - ? SmallPullRequestCard - : PullRequestCard; - const header = ( { value: 'draft', ariaLabel: 'Show draft PRs' }, - { - icon: , - value: 'compacted', - ariaLabel: 'Cards compacted' - }, { icon: , value: 'fullscreen', @@ -78,7 +82,7 @@ const TeamPullRequestsTable: FunctionComponent = () => { isDraft }, index) => ( isDraft ? (infoCardFormat.includes('draft') === isDraft) && - { repositoryName={repository.name} isDraft={isDraft} /> - : ( diff --git a/plugins/github-pull-requests-board/src/components/icons/DraftPr/index.ts b/plugins/github-pull-requests-board/src/components/icons/DraftPr/index.ts index a91e0d1cb7..f2ef0a43c1 100644 --- a/plugins/github-pull-requests-board/src/components/icons/DraftPr/index.ts +++ b/plugins/github-pull-requests-board/src/components/icons/DraftPr/index.ts @@ -1 +1,16 @@ +/* + * 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 { default as DraftPrIcon } from './DraftPr'; diff --git a/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx b/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx index 42135379b2..ce2636d5a2 100644 --- a/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx +++ b/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx @@ -1,3 +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 { useCallback, useEffect, useState } from 'react'; import { formatPRsByReviewDecision } from '../utils/functions'; import { PullRequests, PullRequestsColumn } from '../utils/types'; @@ -36,13 +51,13 @@ export function usePullRequestsByTeam(repositories: string[]) { ); const teamPullRequests = await Promise.allSettled(teamRepositoriesPromises) - .then(promises => promises.reduce((acc, curr) => { + .then(promises => promises.reduce((acc, curr) => { if (curr.status === 'fulfilled') { return [...acc, ...curr.value]; } return acc; - },[] as PullRequests) - ); + }, [] as PullRequests) + ); return teamPullRequests; }, diff --git a/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx b/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx index a463734a60..f974e7e457 100644 --- a/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx +++ b/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx @@ -1,3 +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 { useApi } from '@backstage/core-plugin-api'; import { useEntity, catalogApiRef } from '@backstage/plugin-catalog-react'; import { useCallback, useEffect, useState } from 'react'; diff --git a/plugins/github-pull-requests-board/src/index.ts b/plugins/github-pull-requests-board/src/index.ts index 4c22dc54be..684b62f8cd 100644 --- a/plugins/github-pull-requests-board/src/index.ts +++ b/plugins/github-pull-requests-board/src/index.ts @@ -1 +1,16 @@ +/* + * 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 { TeamPullRequestsTable, TeamPullRequestsPage } from './plugin'; diff --git a/plugins/github-pull-requests-board/src/plugin.test.ts b/plugins/github-pull-requests-board/src/plugin.test.ts index 4bef78d8ee..c12c8b1f06 100644 --- a/plugins/github-pull-requests-board/src/plugin.test.ts +++ b/plugins/github-pull-requests-board/src/plugin.test.ts @@ -1,7 +1,25 @@ -import { TeamPullRequestsTable } from './plugin'; +/* + * 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 { TeamPullRequestsTable, TeamPullRequestsPage } from './plugin'; describe('github-pull-requests-board', () => { it('should export TeamPullRequestsTable', () => { expect(TeamPullRequestsTable).toBeDefined(); }); + it('should export TeamPullRequestsPage', () => { + expect(TeamPullRequestsPage).toBeDefined(); + }); }); diff --git a/plugins/github-pull-requests-board/src/plugin.ts b/plugins/github-pull-requests-board/src/plugin.ts index aef2782579..38edd7562e 100644 --- a/plugins/github-pull-requests-board/src/plugin.ts +++ b/plugins/github-pull-requests-board/src/plugin.ts @@ -1,3 +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 { createPlugin, createComponentExtension, diff --git a/plugins/github-pull-requests-board/src/routes.ts b/plugins/github-pull-requests-board/src/routes.ts index 170bb9fbfe..13e21ecacf 100644 --- a/plugins/github-pull-requests-board/src/routes.ts +++ b/plugins/github-pull-requests-board/src/routes.ts @@ -1,3 +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 { createRouteRef } from '@backstage/core-plugin-api'; export const rootRouteRef = createRouteRef({ diff --git a/plugins/github-pull-requests-board/src/setupTests.ts b/plugins/github-pull-requests-board/src/setupTests.ts index 48c09b5346..9bb3e72355 100644 --- a/plugins/github-pull-requests-board/src/setupTests.ts +++ b/plugins/github-pull-requests-board/src/setupTests.ts @@ -1,2 +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. + */ import '@testing-library/jest-dom'; import 'cross-fetch/polyfill'; diff --git a/plugins/github-pull-requests-board/src/utils/constants.ts b/plugins/github-pull-requests-board/src/utils/constants.ts index 05cef50634..22b23b259d 100644 --- a/plugins/github-pull-requests-board/src/utils/constants.ts +++ b/plugins/github-pull-requests-board/src/utils/constants.ts @@ -1,3 +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 const COLUMNS = Object.freeze({ REVIEW_REQUIRED: '🔍 Review required', REVIEW_IN_PROGRESS: '📝 Review in progress', diff --git a/plugins/github-pull-requests-board/src/utils/functions.ts b/plugins/github-pull-requests-board/src/utils/functions.ts index c005d0ba8e..55915bdbc2 100644 --- a/plugins/github-pull-requests-board/src/utils/functions.ts +++ b/plugins/github-pull-requests-board/src/utils/functions.ts @@ -1,3 +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 { Entity } from '@backstage/catalog-model'; import moment from 'moment'; import { Reviews, PullRequests, ReviewDecision, PullRequestsColumn, Author } from './types'; diff --git a/plugins/github-pull-requests-board/src/utils/types.tsx b/plugins/github-pull-requests-board/src/utils/types.tsx index 789aa8a704..b8ae1058f0 100644 --- a/plugins/github-pull-requests-board/src/utils/types.tsx +++ b/plugins/github-pull-requests-board/src/utils/types.tsx @@ -1,3 +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 type GraphQlPullRequest = { repository: { pullRequest: T diff --git a/yarn.lock b/yarn.lock index 79492064a9..d456b5e8e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -354,6 +354,27 @@ json5 "^2.1.2" semver "^6.3.0" +"@babel/core@^7.7.5": + version "7.17.9" + resolved "https://registry.npmjs.org/@babel/core/-/core-7.17.9.tgz#6bae81a06d95f4d0dec5bb9d74bbc1f58babdcfe" + integrity sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.9" + "@babel/helper-compilation-targets" "^7.17.7" + "@babel/helper-module-transforms" "^7.17.7" + "@babel/helpers" "^7.17.9" + "@babel/parser" "^7.17.9" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.9" + "@babel/types" "^7.17.0" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + "@babel/generator@^7.14.0", "@babel/generator@^7.16.8", "@babel/generator@^7.7.2": version "7.16.8" resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz#359d44d966b8cd059d543250ce79596f792f2ebe" @@ -372,6 +393,15 @@ jsesc "^2.5.1" source-map "^0.5.0" +"@babel/generator@^7.17.9": + version "7.17.9" + resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.17.9.tgz#f4af9fd38fa8de143c29fce3f71852406fc1e2fc" + integrity sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ== + dependencies: + "@babel/types" "^7.17.0" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/helper-annotate-as-pure@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" @@ -465,6 +495,14 @@ "@babel/template" "^7.16.7" "@babel/types" "^7.16.7" +"@babel/helper-function-name@^7.17.9": + version "7.17.9" + resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" + integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== + dependencies: + "@babel/template" "^7.16.7" + "@babel/types" "^7.17.0" + "@babel/helper-get-function-arity@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" @@ -619,6 +657,15 @@ "@babel/traverse" "^7.17.3" "@babel/types" "^7.17.0" +"@babel/helpers@^7.17.9": + version "7.17.9" + resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.9.tgz#b2af120821bfbe44f9907b1826e168e819375a1a" + integrity sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q== + dependencies: + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.17.9" + "@babel/types" "^7.17.0" + "@babel/highlight@^7.0.0", "@babel/highlight@^7.16.7": version "7.16.10" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" @@ -628,7 +675,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.10", "@babel/parser@^7.16.12", "@babel/parser@^7.16.7", "@babel/parser@^7.16.8", "@babel/parser@^7.17.3", "@babel/parser@^7.17.8": +"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.10", "@babel/parser@^7.16.12", "@babel/parser@^7.16.7", "@babel/parser@^7.16.8", "@babel/parser@^7.17.3", "@babel/parser@^7.17.8", "@babel/parser@^7.17.9": version "7.17.9" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.17.9.tgz#9c94189a6062f0291418ca021077983058e171ef" integrity sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg== @@ -1380,6 +1427,22 @@ "@babel/parser" "^7.16.7" "@babel/types" "^7.16.7" +"@babel/traverse@^7.1.0", "@babel/traverse@^7.17.9": + version "7.17.9" + resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.9.tgz#1f9b207435d9ae4a8ed6998b2b82300d83c37a0d" + integrity sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.17.9" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.17.9" + "@babel/helper-hoist-variables" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + "@babel/parser" "^7.17.9" + "@babel/types" "^7.17.0" + debug "^4.1.0" + globals "^11.1.0" + "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.0", "@babel/traverse@^7.16.10", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": version "7.16.10" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz#448f940defbe95b5a8029975b051f75993e8239f" @@ -2600,7 +2663,7 @@ prop-types "^15.6.2" scheduler "^0.19.1" -"@hot-loader/react-dom-v17@npm:@hot-loader/react-dom@^17.0.2": +"@hot-loader/react-dom-v17@npm:@hot-loader/react-dom@^17.0.2", "@hot-loader/react-dom@^17.0.2": version "17.0.2" resolved "https://registry.npmjs.org/@hot-loader/react-dom/-/react-dom-17.0.2.tgz#0b24e484093e8f97eb5c72bebdda44fc20bc8400" integrity sha512-G2RZrFhsQClS+bdDh/Ojpk3SgocLPUGnvnJDTQYnmKSSwXtU+Yh+8QMs+Ia3zaAvBiOSpIIDSUxuN69cvKqrWg== @@ -2658,6 +2721,18 @@ resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== +"@jest/console@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" + integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + jest-message-util "^26.6.2" + jest-util "^26.6.2" + slash "^3.0.0" + "@jest/console@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" @@ -2670,6 +2745,40 @@ jest-util "^27.5.1" slash "^3.0.0" +"@jest/core@^26.6.3": + version "26.6.3" + resolved "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" + integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== + dependencies: + "@jest/console" "^26.6.2" + "@jest/reporters" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-changed-files "^26.6.2" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-resolve-dependencies "^26.6.3" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + jest-watcher "^26.6.2" + micromatch "^4.0.2" + p-each-series "^2.1.0" + rimraf "^3.0.0" + slash "^3.0.0" + strip-ansi "^6.0.0" + "@jest/core@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" @@ -2704,6 +2813,16 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/environment@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" + integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== + dependencies: + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + "@jest/environment@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" @@ -2714,6 +2833,18 @@ "@types/node" "*" jest-mock "^27.5.1" +"@jest/fake-timers@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" + integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== + dependencies: + "@jest/types" "^26.6.2" + "@sinonjs/fake-timers" "^6.0.1" + "@types/node" "*" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-util "^26.6.2" + "@jest/fake-timers@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" @@ -2726,6 +2857,15 @@ jest-mock "^27.5.1" jest-util "^27.5.1" +"@jest/globals@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" + integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/types" "^26.6.2" + expect "^26.6.2" + "@jest/globals@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" @@ -2735,6 +2875,38 @@ "@jest/types" "^27.5.1" expect "^27.5.1" +"@jest/reporters@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" + integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== + dependencies: + "@bcoe/v8-coverage" "^0.2.3" + "@jest/console" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + chalk "^4.0.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.2" + graceful-fs "^4.2.4" + istanbul-lib-coverage "^3.0.0" + istanbul-lib-instrument "^4.0.3" + istanbul-lib-report "^3.0.0" + istanbul-lib-source-maps "^4.0.0" + istanbul-reports "^3.0.2" + jest-haste-map "^26.6.2" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + slash "^3.0.0" + source-map "^0.6.0" + string-length "^4.0.1" + terminal-link "^2.0.0" + v8-to-istanbul "^7.0.0" + optionalDependencies: + node-notifier "^8.0.0" + "@jest/reporters@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" @@ -2766,6 +2938,15 @@ terminal-link "^2.0.0" v8-to-istanbul "^8.1.0" +"@jest/source-map@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" + integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== + dependencies: + callsites "^3.0.0" + graceful-fs "^4.2.4" + source-map "^0.6.0" + "@jest/source-map@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" @@ -2775,6 +2956,16 @@ graceful-fs "^4.2.9" source-map "^0.6.0" +"@jest/test-result@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" + integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== + dependencies: + "@jest/console" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/istanbul-lib-coverage" "^2.0.0" + collect-v8-coverage "^1.0.0" + "@jest/test-result@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" @@ -2785,6 +2976,17 @@ "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" +"@jest/test-sequencer@^26.6.3": + version "26.6.3" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" + integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== + dependencies: + "@jest/test-result" "^26.6.2" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.2" + jest-runner "^26.6.3" + jest-runtime "^26.6.3" + "@jest/test-sequencer@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" @@ -2795,6 +2997,27 @@ jest-haste-map "^27.5.1" jest-runtime "^27.5.1" +"@jest/transform@^26.6.2": + version "26.6.2" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" + integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== + dependencies: + "@babel/core" "^7.1.0" + "@jest/types" "^26.6.2" + babel-plugin-istanbul "^6.0.0" + chalk "^4.0.0" + convert-source-map "^1.4.0" + fast-json-stable-stringify "^2.0.0" + graceful-fs "^4.2.4" + jest-haste-map "^26.6.2" + jest-regex-util "^26.0.0" + jest-util "^26.6.2" + micromatch "^4.0.2" + pirates "^4.0.1" + slash "^3.0.0" + source-map "^0.6.1" + write-file-atomic "^3.0.0" + "@jest/transform@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" @@ -4174,7 +4397,7 @@ resolved "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.1.tgz#155ef21065427901994e765da8a0ba0eaae8b8bd" integrity sha512-6Wci+Tp3CgPt/B9B0a3J4s3yMgLNSku6w5TV6mN+61C71UqsRBv2FUibBf3tPGlNxebgPHMEUzKpb1ggE8KCKw== -"@mswjs/cookies@^0.1.6", "@mswjs/cookies@^0.1.7": +"@mswjs/cookies@^0.1.5", "@mswjs/cookies@^0.1.6", "@mswjs/cookies@^0.1.7": version "0.1.7" resolved "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.1.7.tgz#d334081b2c51057a61c1dd7b76ca3cac02251651" integrity sha512-bDg1ReMBx+PYDB4Pk7y1Q07Zz1iKIEUWQpkEXiA2lEWg9gvOZ8UBmGXilCEUvyYoRFlmr/9iXTRR69TrgSwX/Q== @@ -4190,6 +4413,17 @@ "@types/set-cookie-parser" "^2.4.0" set-cookie-parser "^2.4.6" +"@mswjs/interceptors@^0.10.0": + version "0.10.0" + resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.10.0.tgz#f5aad03c2c0591d164e3ed178b21942f1c2f8061" + integrity sha512-/M0GGpid5q2EDI+Keas1sLYF3VZFXHDE5gCmX/jHdp+OJFruVNca3PUk7A8KnGdPpuycZogdPsmRBSOXwjyA7A== + dependencies: + "@open-draft/until" "^1.0.3" + debug "^4.3.0" + headers-utils "^3.0.2" + strict-event-emitter "^0.2.0" + xmldom "^0.6.0" + "@mswjs/interceptors@^0.12.6", "@mswjs/interceptors@^0.12.7": version "0.12.7" resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.12.7.tgz#0d1cd4cd31a0f663e0455993951201faa09d0909" @@ -4682,7 +4916,7 @@ "@octokit/plugin-request-log" "^1.0.2" "@octokit/plugin-rest-endpoint-methods" "5.3.1" -"@octokit/rest@^18.12.0": +"@octokit/rest@^18.12.0", "@octokit/rest@^18.6.7": version "18.12.0" resolved "https://registry.npmjs.org/@octokit/rest/-/rest-18.12.0.tgz#f06bc4952fc87130308d810ca9d00e79f6988881" integrity sha512-gDPiOHlyGavxr72y0guQEhLsemgVjwRePayJ+FcKc2SJqKUbxbkvf5kAZEWA/MKvsfYlQAMVzNJE3ezQcxMJ2Q== @@ -5101,16 +5335,31 @@ resolved "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== +"@spotify/eslint-config-base@^12.0.0": + version "12.0.0" + resolved "https://registry.npmjs.org/@spotify/eslint-config-base/-/eslint-config-base-12.0.0.tgz#0b1e41bb436d5c1c20714703629514d64c3c0f06" + integrity sha512-5Uud/TmzakqmdUNCZpD8JFQRa2VG3dVd3DanSMpU/nVdu6K5LyX8EMU3Tz1vGP18Wih8iAu/sBSJhntNzw7e6w== + "@spotify/eslint-config-base@^13.0.0": version "13.0.0" resolved "https://registry.npmjs.org/@spotify/eslint-config-base/-/eslint-config-base-13.0.0.tgz#bb748bb2b705ffb5085f873aa0daf94dfad59985" integrity sha512-BrnexUcUQkp6XUw8HWSmE4LpWtJGgEC6A7vrSkgpgKJtZaYkpw8O+Xnk60DA266ecbFHYbQD6ngqKHlvjNB+pA== +"@spotify/eslint-config-react@^12.0.0": + version "12.0.0" + resolved "https://registry.npmjs.org/@spotify/eslint-config-react/-/eslint-config-react-12.0.0.tgz#5b8d4bc3b81a8ec2824648f482f1f6c3cf711893" + integrity sha512-lNHZRtJesNA273OJHBVUGAg2JYyVDZ+bsT7h3OwnX1HYgejJ3YcKPSziPM8TGFAN8DruH3tHFfaM63uAIA1+uw== + "@spotify/eslint-config-react@^13.0.0": version "13.0.1" resolved "https://registry.npmjs.org/@spotify/eslint-config-react/-/eslint-config-react-13.0.1.tgz#f309f5d3c53ef1e2c7c6ce05f76ee681970112c3" integrity sha512-gyC0CtJ2H9K57HyQG5/RcMsJiB6qmVbBHOHWukZcPLfYtwkK201kgMjHrVfJXoSN+mJxcWhDVPxqe+eA7LHshQ== +"@spotify/eslint-config-typescript@^12.0.0": + version "12.0.0" + resolved "https://registry.npmjs.org/@spotify/eslint-config-typescript/-/eslint-config-typescript-12.0.0.tgz#4c7af3f74a47668bec0c860b72e2a0103e78a138" + integrity sha512-nMVll8ZkN/W8+IHn6Iz3YzCKW0qhrn3TVfyxkAr3qmXm5cex+GzyUdZEuxb8rdN2inZL6A1Il2NFfO5p/UKxog== + "@spotify/eslint-config-typescript@^13.0.0": version "13.0.1" resolved "https://registry.npmjs.org/@spotify/eslint-config-typescript/-/eslint-config-typescript-13.0.1.tgz#47801a66d5569074a110f4422eba60aafc6bd7f8" @@ -5276,6 +5525,20 @@ "@babel/runtime" "^7.14.6" "@testing-library/dom" "^8.1.0" +"@testing-library/dom@^7.28.1": + version "7.31.2" + resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a" + integrity sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/runtime" "^7.12.5" + "@types/aria-query" "^4.2.0" + aria-query "^4.2.2" + chalk "^4.1.0" + dom-accessibility-api "^0.5.6" + lz-string "^1.4.4" + pretty-format "^26.6.2" + "@testing-library/dom@^8.0.0", "@testing-library/dom@^8.1.0": version "8.11.3" resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.3.tgz#38fd63cbfe14557021e88982d931e33fb7c1a808" @@ -5313,6 +5576,14 @@ "@babel/runtime" "^7.12.5" react-error-boundary "^3.1.0" +"@testing-library/react@^11.2.5": + version "11.2.7" + resolved "https://registry.npmjs.org/@testing-library/react/-/react-11.2.7.tgz#b29e2e95c6765c815786c0bc1d5aed9cb2bf7818" + integrity sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA== + dependencies: + "@babel/runtime" "^7.12.5" + "@testing-library/dom" "^7.28.1" + "@testing-library/react@^12.1.3": version "12.1.5" resolved "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" @@ -5442,6 +5713,17 @@ "@types/babel__template" "*" "@types/babel__traverse" "*" +"@types/babel__core@^7.1.7": + version "7.1.19" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" + integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + "@types/babel__generator@*": version "7.6.1" resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04" @@ -5587,7 +5869,7 @@ dependencies: "@types/express" "*" -"@types/cookie@^0.4.1": +"@types/cookie@^0.4.0", "@types/cookie@^0.4.1": version "0.4.1" resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== @@ -5910,7 +6192,7 @@ resolved "https://registry.npmjs.org/@types/humanize-duration/-/humanize-duration-3.27.1.tgz#f14740d1f585a0a8e3f46359b62fda8b0eaa31e7" integrity sha512-K3e+NZlpCKd6Bd/EIdqjFJRFHbrq5TzPPLwREk5Iv/YoIjQrs6ljdAUCo+Lb2xFlGNOjGSE0dqsVD19cZL137w== -"@types/inquirer@^7.3.3": +"@types/inquirer@^7.3.1", "@types/inquirer@^7.3.3": version "7.3.3" resolved "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.3.tgz#92e6676efb67fa6925c69a2ee638f67a822952ac" integrity sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ== @@ -6297,6 +6579,11 @@ resolved "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.29.tgz#6ffa33ed1fc8813c469b859681d09707eb40d03c" integrity sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA== +"@types/prettier@^2.0.0": + version "2.6.0" + resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.0.tgz#efcbd41937f9ae7434c714ab698604822d890759" + integrity sha512-G/AdOadiZhnJp0jXCaBQU449W2h716OW/EoXeYkCytxKL06X1WCXB4DZpp8TpZ8eyIJVS1cw4lrlkkSYU21cDw== + "@types/prettier@^2.1.5": version "2.4.3" resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.3.tgz#a3c65525b91fca7da00ab1a3ac2b5a2a4afbffbf" @@ -7298,6 +7585,16 @@ ajv@^6.10.0, ajv@^6.10.1, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.5.5, ajv json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^7.0.3: + version "7.2.4" + resolved "https://registry.npmjs.org/ajv/-/ajv-7.2.4.tgz#8e239d4d56cf884bccca8cca362f508446dc160f" + integrity sha512-nBeQgg/ZZA3u3SYxyaDvpvDtgZ/EZPF547ARgZBrG9Bhu1vKDwAIjtIf+sDtJUKa2zOcEbmRLBRSyMraS/Oy1A== + dependencies: + fast-deep-equal "^3.1.1" + json-schema-traverse "^1.0.0" + require-from-string "^2.0.2" + uri-js "^4.2.2" + ajv@^8.0.0, ajv@^8.10.0, ajv@^8.8.0: version "8.11.0" resolved "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" @@ -7423,6 +7720,14 @@ any-promise@^1.0.0: resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= +anymatch@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== + dependencies: + micromatch "^3.1.4" + normalize-path "^2.1.1" + anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -7937,6 +8242,20 @@ babel-core@^7.0.0-bridge.0: resolved "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg== +babel-jest@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" + integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== + dependencies: + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/babel__core" "^7.1.7" + babel-plugin-istanbul "^6.0.0" + babel-preset-jest "^26.6.2" + chalk "^4.0.0" + graceful-fs "^4.2.4" + slash "^3.0.0" + babel-jest@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" @@ -7958,7 +8277,7 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" -babel-plugin-istanbul@^6.1.1: +babel-plugin-istanbul@^6.0.0, babel-plugin-istanbul@^6.1.1: version "6.1.1" resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== @@ -7969,6 +8288,16 @@ babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" +babel-plugin-jest-hoist@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" + integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== + dependencies: + "@babel/template" "^7.3.3" + "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" + "@types/babel__traverse" "^7.0.6" + babel-plugin-jest-hoist@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" @@ -8068,6 +8397,14 @@ babel-preset-fbjs@^3.4.0: "@babel/plugin-transform-template-literals" "^7.0.0" babel-plugin-syntax-trailing-function-commas "^7.0.0-beta.0" +babel-preset-jest@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" + integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== + dependencies: + babel-plugin-jest-hoist "^26.6.2" + babel-preset-current-node-syntax "^1.0.0" + babel-preset-jest@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" @@ -8766,7 +9103,7 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.2.0, camelcase@^6.3.0: +camelcase@^6.0.0, camelcase@^6.2.0, camelcase@^6.3.0: version "6.3.0" resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -8804,6 +9141,13 @@ capital-case@^1.0.4: tslib "^2.0.3" upper-case-first "^2.0.2" +capture-exit@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" + integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== + dependencies: + rsvp "^4.8.4" + caseless@~0.12.0: version "0.12.0" resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -9013,6 +9357,11 @@ circleci-api@^4.0.0: dependencies: axios "^0.21.1" +cjs-module-lexer@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" + integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== + cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" @@ -9404,6 +9753,11 @@ commander@^5.1.0: resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== +commander@^6.1.0: + version "6.2.1" + resolved "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + commander@^7.2.0: version "7.2.0" resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" @@ -10569,7 +10923,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -11213,6 +11567,11 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" +emittery@^0.7.1: + version "0.7.2" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" + integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== + emittery@^0.8.1: version "0.8.1" resolved "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" @@ -11668,6 +12027,13 @@ eslint-plugin-import@^2.25.4: resolve "^1.22.0" tsconfig-paths "^3.14.1" +eslint-plugin-jest@^25.3.4: + version "25.7.0" + resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz#ff4ac97520b53a96187bad9c9814e7d00de09a6a" + integrity sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ== + dependencies: + "@typescript-eslint/experimental-utils" "^5.0.0" + eslint-plugin-jest@^26.1.2: version "26.2.2" resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-26.2.2.tgz#74e000544259f1ef0462a609a3fc9e5da3768f6c" @@ -11773,6 +12139,18 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== +eslint-webpack-plugin@^2.6.0: + version "2.6.0" + resolved "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-2.6.0.tgz#3bd4ada4e539cb1f6687d2f619073dbb509361cd" + integrity sha512-V+LPY/T3kur5QO3u+1s34VDTcRxjXWPUGM4hlmTb5DwVD0OQz631yGTxJZf4SpAqAjdbBVe978S8BJeHpAdOhQ== + dependencies: + "@types/eslint" "^7.28.2" + arrify "^2.0.1" + jest-worker "^27.3.1" + micromatch "^4.0.4" + normalize-path "^3.0.0" + schema-utils "^3.1.1" + eslint-webpack-plugin@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.1.1.tgz#83dad2395e5f572d6f4d919eedaa9cf902890fcb" @@ -12019,7 +12397,12 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: react-use "^17.2.4" zen-observable "^0.8.15" -execa@4.1.0: +exec-sh@^0.3.2: + version "0.3.6" + resolved "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" + integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== + +execa@4.1.0, execa@^4.0.0: version "4.1.0" resolved "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== @@ -12117,6 +12500,18 @@ expand-template@^2.0.3: resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== +expect@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" + integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== + dependencies: + "@jest/types" "^26.6.2" + ansi-styles "^4.0.0" + jest-get-type "^26.3.0" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-regex-util "^26.0.0" + expect@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" @@ -12803,6 +13198,16 @@ fs-extra@10.1.0, fs-extra@^10.0.0, fs-extra@^10.0.1: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@9.1.0, fs-extra@^9.0.0, fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@^7.0.1, fs-extra@~7.0.1: version "7.0.1" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -12821,16 +13226,6 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.0, fs-extra@^9.1.0: - version "9.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs-minipass@^1.2.7: version "1.2.7" resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" @@ -12855,7 +13250,7 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^2.3.2, fsevents@~2.3.2: +fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -13438,7 +13833,7 @@ graphql-ws@^5.4.1: resolved "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.5.5.tgz#f375486d3f196e2a2527b503644693ae3a8670a9" integrity sha512-hvyIS71vs4Tu/yUYHPvGXsTgo0t3arU820+lT5VjZS2go0ewp2LqyCgxEN56CzOG7Iys52eRhHBiD1gGRdiQtw== -graphql@^15.5.1: +graphql@^15.4.0, graphql@^15.5.1: version "15.8.0" resolved "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38" integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== @@ -13453,6 +13848,11 @@ grouped-queue@^2.0.0: resolved "https://registry.npmjs.org/grouped-queue/-/grouped-queue-2.0.0.tgz#a2c6713f2171e45db2c300a3a9d7c119d694dac8" integrity sha512-/PiFUa7WIsl48dUeCvhIHnwNmAAzlI/eHoJl0vu3nsFA366JleY7Ff8EVTplZu5kO0MIdZjKTTnzItL61ahbnw== +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= + gtoken@^5.0.4: version "5.1.0" resolved "https://registry.npmjs.org/gtoken/-/gtoken-5.1.0.tgz#4ba8d2fc9a8459098f76e7e8fd7beaa39fda9fe4" @@ -15025,6 +15425,16 @@ istanbul-lib-coverage@^3.2.0: resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== +istanbul-lib-instrument@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" @@ -15054,7 +15464,7 @@ istanbul-lib-source-maps@^4.0.0: istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" -istanbul-reports@^3.1.3: +istanbul-reports@^3.0.2, istanbul-reports@^3.1.3: version "3.1.4" resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== @@ -15079,6 +15489,15 @@ jenkins@^0.28.1: dependencies: papi "^0.29.0" +jest-changed-files@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" + integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== + dependencies: + "@jest/types" "^26.6.2" + execa "^4.0.0" + throat "^5.0.0" + jest-changed-files@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" @@ -15113,6 +15532,25 @@ jest-circus@^27.5.1: stack-utils "^2.0.3" throat "^6.0.1" +jest-cli@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" + integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== + dependencies: + "@jest/core" "^26.6.3" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + chalk "^4.0.0" + exit "^0.1.2" + graceful-fs "^4.2.4" + import-local "^3.0.2" + is-ci "^2.0.0" + jest-config "^26.6.3" + jest-util "^26.6.2" + jest-validate "^26.6.2" + prompts "^2.0.1" + yargs "^15.4.1" + jest-cli@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" @@ -15131,6 +15569,30 @@ jest-cli@^27.5.1: prompts "^2.0.1" yargs "^16.2.0" +jest-config@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" + integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== + dependencies: + "@babel/core" "^7.1.0" + "@jest/test-sequencer" "^26.6.3" + "@jest/types" "^26.6.2" + babel-jest "^26.6.3" + chalk "^4.0.0" + deepmerge "^4.2.2" + glob "^7.1.1" + graceful-fs "^4.2.4" + jest-environment-jsdom "^26.6.2" + jest-environment-node "^26.6.2" + jest-get-type "^26.3.0" + jest-jasmine2 "^26.6.3" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + micromatch "^4.0.2" + pretty-format "^26.6.2" + jest-config@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" @@ -15168,7 +15630,7 @@ jest-css-modules@^2.1.0: dependencies: identity-obj-proxy "3.0.0" -jest-diff@^26.0.0: +jest-diff@^26.0.0, jest-diff@^26.6.2: version "26.6.2" resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== @@ -15188,6 +15650,13 @@ jest-diff@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-docblock@^26.0.0: + version "26.0.0" + resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" + integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== + dependencies: + detect-newline "^3.0.0" + jest-docblock@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" @@ -15195,6 +15664,17 @@ jest-docblock@^27.5.1: dependencies: detect-newline "^3.0.0" +jest-each@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" + integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== + dependencies: + "@jest/types" "^26.6.2" + chalk "^4.0.0" + jest-get-type "^26.3.0" + jest-util "^26.6.2" + pretty-format "^26.6.2" + jest-each@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" @@ -15206,6 +15686,19 @@ jest-each@^27.5.1: jest-util "^27.5.1" pretty-format "^27.5.1" +jest-environment-jsdom@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" + integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" + jsdom "^16.4.0" + jest-environment-jsdom@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" @@ -15219,6 +15712,18 @@ jest-environment-jsdom@^27.5.1: jest-util "^27.5.1" jsdom "^16.6.0" +jest-environment-node@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" + integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== + dependencies: + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock "^26.6.2" + jest-util "^26.6.2" + jest-environment-node@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" @@ -15241,6 +15746,27 @@ jest-get-type@^27.5.1: resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== +jest-haste-map@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" + integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== + dependencies: + "@jest/types" "^26.6.2" + "@types/graceful-fs" "^4.1.2" + "@types/node" "*" + anymatch "^3.0.3" + fb-watchman "^2.0.0" + graceful-fs "^4.2.4" + jest-regex-util "^26.0.0" + jest-serializer "^26.6.2" + jest-util "^26.6.2" + jest-worker "^26.6.2" + micromatch "^4.0.2" + sane "^4.0.3" + walker "^1.0.7" + optionalDependencies: + fsevents "^2.1.2" + jest-haste-map@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" @@ -15261,6 +15787,30 @@ jest-haste-map@^27.5.1: optionalDependencies: fsevents "^2.3.2" +jest-jasmine2@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" + integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== + dependencies: + "@babel/traverse" "^7.1.0" + "@jest/environment" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + co "^4.6.0" + expect "^26.6.2" + is-generator-fn "^2.0.0" + jest-each "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-runtime "^26.6.3" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + pretty-format "^26.6.2" + throat "^5.0.0" + jest-jasmine2@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" @@ -15284,6 +15834,14 @@ jest-jasmine2@^27.5.1: pretty-format "^27.5.1" throat "^6.0.1" +jest-leak-detector@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" + integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== + dependencies: + jest-get-type "^26.3.0" + pretty-format "^26.6.2" + jest-leak-detector@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" @@ -15302,6 +15860,21 @@ jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" +jest-message-util@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" + integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@jest/types" "^26.6.2" + "@types/stack-utils" "^2.0.0" + chalk "^4.0.0" + graceful-fs "^4.2.4" + micromatch "^4.0.2" + pretty-format "^26.6.2" + slash "^3.0.0" + stack-utils "^2.0.2" + jest-message-util@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" @@ -15317,6 +15890,14 @@ jest-message-util@^27.5.1: slash "^3.0.0" stack-utils "^2.0.3" +jest-mock@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" + integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + jest-mock@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" @@ -15330,11 +15911,25 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== +jest-regex-util@^26.0.0: + version "26.0.0" + resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" + integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== + jest-regex-util@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== +jest-resolve-dependencies@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" + integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== + dependencies: + "@jest/types" "^26.6.2" + jest-regex-util "^26.0.0" + jest-snapshot "^26.6.2" + jest-resolve-dependencies@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" @@ -15344,6 +15939,20 @@ jest-resolve-dependencies@^27.5.1: jest-regex-util "^27.5.1" jest-snapshot "^27.5.1" +jest-resolve@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" + integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== + dependencies: + "@jest/types" "^26.6.2" + chalk "^4.0.0" + graceful-fs "^4.2.4" + jest-pnp-resolver "^1.2.2" + jest-util "^26.6.2" + read-pkg-up "^7.0.1" + resolve "^1.18.1" + slash "^3.0.0" + jest-resolve@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" @@ -15360,6 +15969,32 @@ jest-resolve@^27.5.1: resolve.exports "^1.1.0" slash "^3.0.0" +jest-runner@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" + integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== + dependencies: + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + emittery "^0.7.1" + exit "^0.1.2" + graceful-fs "^4.2.4" + jest-config "^26.6.3" + jest-docblock "^26.0.0" + jest-haste-map "^26.6.2" + jest-leak-detector "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + jest-runtime "^26.6.3" + jest-util "^26.6.2" + jest-worker "^26.6.2" + source-map-support "^0.5.6" + throat "^5.0.0" + jest-runner@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" @@ -15387,6 +16022,39 @@ jest-runner@^27.5.1: source-map-support "^0.5.6" throat "^6.0.1" +jest-runtime@^26.6.3: + version "26.6.3" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" + integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== + dependencies: + "@jest/console" "^26.6.2" + "@jest/environment" "^26.6.2" + "@jest/fake-timers" "^26.6.2" + "@jest/globals" "^26.6.2" + "@jest/source-map" "^26.6.2" + "@jest/test-result" "^26.6.2" + "@jest/transform" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/yargs" "^15.0.0" + chalk "^4.0.0" + cjs-module-lexer "^0.6.0" + collect-v8-coverage "^1.0.0" + exit "^0.1.2" + glob "^7.1.3" + graceful-fs "^4.2.4" + jest-config "^26.6.3" + jest-haste-map "^26.6.2" + jest-message-util "^26.6.2" + jest-mock "^26.6.2" + jest-regex-util "^26.0.0" + jest-resolve "^26.6.2" + jest-snapshot "^26.6.2" + jest-util "^26.6.2" + jest-validate "^26.6.2" + slash "^3.0.0" + strip-bom "^4.0.0" + yargs "^15.4.1" + jest-runtime@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" @@ -15415,6 +16083,14 @@ jest-runtime@^27.5.1: slash "^3.0.0" strip-bom "^4.0.0" +jest-serializer@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" + integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== + dependencies: + "@types/node" "*" + graceful-fs "^4.2.4" + jest-serializer@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" @@ -15423,6 +16099,28 @@ jest-serializer@^27.5.1: "@types/node" "*" graceful-fs "^4.2.9" +jest-snapshot@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" + integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== + dependencies: + "@babel/types" "^7.0.0" + "@jest/types" "^26.6.2" + "@types/babel__traverse" "^7.0.4" + "@types/prettier" "^2.0.0" + chalk "^4.0.0" + expect "^26.6.2" + graceful-fs "^4.2.4" + jest-diff "^26.6.2" + jest-get-type "^26.3.0" + jest-haste-map "^26.6.2" + jest-matcher-utils "^26.6.2" + jest-message-util "^26.6.2" + jest-resolve "^26.6.2" + natural-compare "^1.4.0" + pretty-format "^26.6.2" + semver "^7.3.2" + jest-snapshot@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" @@ -15458,6 +16156,18 @@ jest-transform-yaml@^1.0.0: dependencies: js-yaml "4.1.0" +jest-util@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" + integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== + dependencies: + "@jest/types" "^26.6.2" + "@types/node" "*" + chalk "^4.0.0" + graceful-fs "^4.2.4" + is-ci "^2.0.0" + micromatch "^4.0.2" + jest-util@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" @@ -15470,6 +16180,18 @@ jest-util@^27.5.1: graceful-fs "^4.2.9" picomatch "^2.2.3" +jest-validate@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" + integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== + dependencies: + "@jest/types" "^26.6.2" + camelcase "^6.0.0" + chalk "^4.0.0" + jest-get-type "^26.3.0" + leven "^3.1.0" + pretty-format "^26.6.2" + jest-validate@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" @@ -15482,6 +16204,19 @@ jest-validate@^27.5.1: leven "^3.1.0" pretty-format "^27.5.1" +jest-watcher@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" + integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== + dependencies: + "@jest/test-result" "^26.6.2" + "@jest/types" "^26.6.2" + "@types/node" "*" + ansi-escapes "^4.2.1" + chalk "^4.0.0" + jest-util "^26.6.2" + string-length "^4.0.1" + jest-watcher@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" @@ -15500,6 +16235,15 @@ jest-when@^3.1.0: resolved "https://registry.npmjs.org/jest-when/-/jest-when-3.5.1.tgz#33ab6f923661cf878cd08fe9df64b507934603db" integrity sha512-o+HiaIVCg1IC95sMDKHU9G5v5N5l3UHqXvJpf0PgAMThZeQo4Hf5Sgoj+wpCBRGg4/KtzSAZZZEKNiLqE0i4eQ== +jest-worker@^26.6.2: + version "26.6.2" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" + integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^7.0.0" + jest-worker@^27.3.1, jest-worker@^27.4.5, jest-worker@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" @@ -15509,6 +16253,15 @@ jest-worker@^27.3.1, jest-worker@^27.4.5, jest-worker@^27.5.1: merge-stream "^2.0.0" supports-color "^8.0.0" +jest@^26.0.1: + version "26.6.3" + resolved "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" + integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q== + dependencies: + "@jest/core" "^26.6.3" + import-local "^3.0.2" + jest-cli "^26.6.3" + jest@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" @@ -15684,7 +16437,7 @@ jscodeshift@^0.13.0: temp "^0.8.4" write-file-atomic "^2.3.0" -jsdom@^16.5.2, jsdom@^16.6.0: +jsdom@^16.4.0, jsdom@^16.5.2, jsdom@^16.6.0: version "16.7.0" resolved "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== @@ -15862,7 +16615,7 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2, json5@^2.1.3, json5@^2.2.0: +json5@^2.1.2, json5@^2.1.3, json5@^2.2.0, json5@^2.2.1: version "2.2.1" resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== @@ -17021,6 +17774,13 @@ make-fetch-happen@^9.1.0: socks-proxy-agent "^6.0.0" ssri "^8.0.0" +makeerror@1.0.12: + version "1.0.12" + resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" + integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== + dependencies: + tmpl "1.0.5" + makeerror@1.0.x: version "1.0.11" resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -17644,7 +18404,7 @@ micromark@^3.0.0: micromark-util-types "^1.0.1" parse-entities "^3.0.0" -micromatch@^3.1.10: +micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -17784,6 +18544,13 @@ minimatch@3.0.4: dependencies: brace-expansion "^1.1.7" +minimatch@5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.0.0.tgz#281d8402aaaeed18a9e8406ad99c46a19206c6ef" + integrity sha512-EU+GCVjXD00yOUf1TwAHVP7v3fBD3A8RkkPYsWWKGWesxM/572sL53wJQnHxquHlRhYUV36wHkqrN8cdikKc2g== + dependencies: + brace-expansion "^2.0.1" + minimatch@5.0.1, minimatch@^5.0.0, minimatch@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" @@ -17814,7 +18581,7 @@ minimist-options@4.1.0, minimist-options@^4.0.2: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: version "1.2.6" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== @@ -18010,6 +18777,31 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +msw@^0.29.0: + version "0.29.0" + resolved "https://registry.npmjs.org/msw/-/msw-0.29.0.tgz#7242d575cb01db0c925241587df1fc2b79230d78" + integrity sha512-C/wz1d5uAEZRvAPAYrXG1rwLxXl0+BOs+JPrCzasoABZW3ATwS6ifSze+/DAgA93e9M86RXwvy6yDtZeZWmCFQ== + dependencies: + "@mswjs/cookies" "^0.1.5" + "@mswjs/interceptors" "^0.10.0" + "@open-draft/until" "^1.0.3" + "@types/cookie" "^0.4.0" + "@types/inquirer" "^7.3.1" + "@types/js-levenshtein" "^1.1.0" + chalk "^4.1.1" + chokidar "^3.4.2" + cookie "^0.4.1" + graphql "^15.4.0" + headers-utils "^3.0.2" + inquirer "^8.1.0" + js-levenshtein "^1.1.6" + node-fetch "^2.6.1" + node-match-path "^0.6.3" + statuses "^2.0.0" + strict-event-emitter "^0.2.0" + type-fest "^1.1.3" + yargs "^17.0.1" + msw@^0.35.0: version "0.35.0" resolved "https://registry.npmjs.org/msw/-/msw-0.35.0.tgz#18a4ceb6c822ef226a30421d434413bc45030d38" @@ -18379,6 +19171,18 @@ node-modules-regexp@^1.0.0: resolved "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= +node-notifier@^8.0.0: + version "8.0.2" + resolved "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5" + integrity sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg== + dependencies: + growly "^1.3.0" + is-wsl "^2.2.0" + semver "^7.3.2" + shellwords "^0.1.1" + uuid "^8.3.0" + which "^2.0.2" + node-releases@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" @@ -18998,6 +19802,11 @@ p-cancelable@^2.0.0: resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== +p-each-series@^2.1.0: + version "2.2.0" + resolved "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" + integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== + p-filter@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz#1b1472562ae7a0f742f0f3d3d3718ea66ff9c09c" @@ -21935,6 +22744,11 @@ rollup-plugin-esbuild@^4.7.2: joycon "^3.0.1" jsonc-parser "^3.0.0" +rollup-plugin-peer-deps-external@^2.2.2: + version "2.2.4" + resolved "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.4.tgz#8a420bbfd6dccc30aeb68c9bf57011f2f109570d" + integrity sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g== + rollup-plugin-postcss@*, rollup-plugin-postcss@^4.0.0: version "4.0.2" resolved "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-4.0.2.tgz#15e9462f39475059b368ce0e49c800fa4b1f7050" @@ -21976,6 +22790,11 @@ rollup@^2.60.2: optionalDependencies: fsevents "~2.3.2" +rsvp@^4.8.4: + version "4.8.5" + resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" + integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== + rtl-css-js@^1.14.0: version "1.14.0" resolved "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.14.0.tgz#daa4f192a92509e292a0519f4b255e6e3c076b7d" @@ -22060,6 +22879,21 @@ safe-stable-stringify@^2.2.0, safe-stable-stringify@^2.3.1: resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== +sane@^4.0.3: + version "4.1.0" + resolved "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" + integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== + dependencies: + "@cnakazawa/watch" "^1.0.3" + anymatch "^2.0.0" + capture-exit "^2.0.0" + exec-sh "^0.3.2" + execa "^1.0.0" + fb-watchman "^2.0.0" + micromatch "^3.1.4" + minimist "^1.1.1" + walker "~1.0.5" + sanitize-filename@^1.6.1: version "1.6.3" resolved "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378" @@ -22396,6 +23230,11 @@ shelljs@^0.8.5: interpret "^1.0.0" rechoir "^0.6.2" +shellwords@^0.1.1: + version "0.1.1" + resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" + integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== + shx@^0.3.2: version "0.3.4" resolved "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz#74289230b4b663979167f94e1935901406e40f02" @@ -22936,7 +23775,7 @@ stack-trace@0.0.x: resolved "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= -stack-utils@^2.0.3: +stack-utils@^2.0.2, stack-utils@^2.0.3: version "2.0.5" resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== @@ -23754,6 +24593,11 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" +throat@^5.0.0: + version "5.0.0" + resolved "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" + integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== + throat@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" @@ -23869,7 +24713,7 @@ tmp@^0.2.0, tmp@~0.2.1: dependencies: rimraf "^3.0.0" -tmpl@1.0.x: +tmpl@1.0.5, tmpl@1.0.x: version "1.0.5" resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== @@ -24208,7 +25052,7 @@ type-fest@^0.8.1: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-fest@^1.2.1, type-fest@^1.2.2: +type-fest@^1.1.3, type-fest@^1.2.1, type-fest@^1.2.2: version "1.4.0" resolved "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== @@ -24242,6 +25086,19 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript-json-schema@^0.52.0: + version "0.52.0" + resolved "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.52.0.tgz#954560ec90e5486e8f7a5b7706ec59286a708e29" + integrity sha512-3ZdHzx116gZ+D9LmMl5/+d1G3Rpt8baWngKzepYWHnXbAa8Winv64CmFRqLlMKneE1c40yugYDFcWdyX1FjGzQ== + dependencies: + "@types/json-schema" "^7.0.9" + "@types/node" "^16.9.2" + glob "^7.1.7" + safe-stable-stringify "^2.2.0" + ts-node "^10.2.1" + typescript "~4.4.4" + yargs "^17.1.1" + typescript-json-schema@^0.53.0: version "0.53.0" resolved "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.53.0.tgz#ac5b89e4b0af55be422f475a041360e0556f88ea" @@ -24765,6 +25622,15 @@ v8-compile-cache@^2.0.3: resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== +v8-to-istanbul@^7.0.0: + version "7.1.2" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1" + integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow== + dependencies: + "@types/istanbul-lib-coverage" "^2.0.1" + convert-source-map "^1.6.0" + source-map "^0.7.3" + v8-to-istanbul@^8.1.0: version "8.1.1" resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" @@ -24961,6 +25827,13 @@ walker@^1.0.7: dependencies: makeerror "1.0.x" +walker@~1.0.5: + version "1.0.8" + resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" + integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== + dependencies: + makeerror "1.0.12" + watchpack@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25" @@ -25485,6 +26358,11 @@ xmlchars@^2.2.0: resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== +xmldom@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz#43a96ecb8beece991cef382c08397d82d4d0c46f" + integrity sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg== + xmlhttprequest-ssl@~1.6.2: version "1.6.3" resolved "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" @@ -25579,7 +26457,7 @@ yargs-parser@^3.2.0: camelcase "^3.0.0" lodash.assign "^4.1.0" -yargs@^15.1.0, yargs@^15.3.1: +yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.1: version "15.4.1" resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== From 4f3848e4c0cd37fc3079fcbd8389ff8fc9bd17a5 Mon Sep 17 00:00:00 2001 From: Talita Gregory Nunes Freire Date: Fri, 22 Apr 2022 17:59:54 +0200 Subject: [PATCH 106/149] feat: README update with how to implement plugin Signed-off-by: Talita Gregory Nunes Freire --- .../app/src/components/catalog/EntityPage.tsx | 4 + plugins/github-pull-requests-board/README.md | 72 +- .../docs/pull-requests-board.png | Bin 0 -> 411335 bytes .../github-pull-requests-board/package.json | 28 +- .../TeamPullRequestsBoard.tsx} | 14 +- .../components/TeamPullRequestsBoard/index.ts | 16 + .../components/TeamPullRequestsTable/index.ts | 1 - .../github-pull-requests-board/src/index.ts | 2 +- .../src/plugin.test.ts | 6 +- .../github-pull-requests-board/src/plugin.ts | 8 +- yarn.lock | 947 +----------------- 11 files changed, 146 insertions(+), 952 deletions(-) create mode 100644 plugins/github-pull-requests-board/docs/pull-requests-board.png rename plugins/github-pull-requests-board/src/components/{TeamPullRequestsTable/TeamPullRequestsTable.tsx => TeamPullRequestsBoard/TeamPullRequestsBoard.tsx} (89%) create mode 100644 plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/index.ts delete mode 100644 plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/index.ts diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 799fe3e4d9..94713b66d8 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -137,6 +137,7 @@ import { EntityNewRelicDashboardCard, } from '@backstage/plugin-newrelic-dashboard'; import { EntityGoCdContent, isGoCdAvailable } from '@backstage/plugin-gocd'; +import { TeamPullRequestsPage } from '@backstage/plugin-github-pull-requests-board'; import React, { ReactNode, useMemo, useState } from 'react'; @@ -623,6 +624,9 @@ const groupPage = ( + + + ); diff --git a/plugins/github-pull-requests-board/README.md b/plugins/github-pull-requests-board/README.md index 2beff349a8..6205a34d95 100644 --- a/plugins/github-pull-requests-board/README.md +++ b/plugins/github-pull-requests-board/README.md @@ -1,15 +1,75 @@ -# github-pull-requests-board +# GitHub Pull Requests Board Plugin -Welcome to the github-pull-requests-board plugin! +The GitHub Pull Requests Board Plugin helps to visualise all **Open Pull Requests** related to the owned team repository. -This plugin will help you and your team stay on top of open pull requests, hopefully reducing the time from open to merged. It's particularly useful when your team deals with many repositories. +![github-pull-requests-board](./docs/pull-requests-board.png) + +It will help you and your team stay on top of open pull requests, hopefully reducing the time from open to merged. It's particularly useful when your team deals with many repositories. + +## Prerequisites + +- [GitHub Authentication Provider](https://backstage.io/docs/auth/github/provider) ## Getting started -The plugin exports the **TeamPullRequestsTable** component which should be added into the Team page level, so it can consume the backstage **"team"** entity. +The plugin exports the **TeamPullRequestsBoard** component which can be added to the Overview page ot the team at `backstage/packages/app/src/components/catalog/EntityPage.tsx` ```javascript -import { TeamPullRequestsTable } from '@backstage/plugin-github-pull-requests-board'; +import { TeamPullRequestsBoard } from '@backstage/plugin-github-pull-requests-board'; -; +const groupPage = ( + + + + {entityWarningContent} + + + + + + + + + + + + + + + +); +``` + +Or you can also import the **TeamPullRequestsPage** component which can be used to add a new page on the group page at `backstage/packages/app/src/components/catalog/EntityPage.tsx` + +```javascript +import { TeamPullRequestsPage } from '@backstage/plugin-github-pull-requests-board'; + +const groupPage = ( + + + + {entityWarningContent} + + + + + + + + + + + + + + + ; +) ``` diff --git a/plugins/github-pull-requests-board/docs/pull-requests-board.png b/plugins/github-pull-requests-board/docs/pull-requests-board.png new file mode 100644 index 0000000000000000000000000000000000000000..9b7daa981a32e4d26b4f5ddaba0aa724b229a404 GIT binary patch literal 411335 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 nDO`T6*c3v(1`@I+zi>BB>9+8 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 z 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�N7&?b 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 XZv6UFR?6L&0SG)@{an^LB{Ts5=LAY@ literal 0 HcmV?d00001 diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json index 2ffcd90bd1..7497bff479 100644 --- a/plugins/github-pull-requests-board/package.json +++ b/plugins/github-pull-requests-board/package.json @@ -35,30 +35,30 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^1.0.0", - "@backstage/core-components": "^0.9.2", - "@backstage/core-plugin-api": "^1.0.0", - "@backstage/plugin-catalog-react": "^1.0.0", + "@backstage/catalog-model": "^1.0.1", + "@backstage/core-components": "^0.9.3", + "@backstage/core-plugin-api": "^1.0.1", + "@backstage/plugin-catalog-react": "^1.0.1", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", - "@material-ui/lab": "4.0.0-alpha.45", + "@material-ui/lab": "4.0.0-alpha.57", "@octokit/rest": "^18.6.7", "moment": "^2.29.1", "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.14.1", - "@backstage/core-app-api": "^0.5.2", - "@backstage/dev-utils": "^0.2.5", - "@backstage/test-utils": "^0.2.0", + "@backstage/cli": "^0.17.0", + "@backstage/core-app-api": "^1.0.1", + "@backstage/dev-utils": "^1.0.1", + "@backstage/test-utils": "^1.0.1", "@testing-library/jest-dom": "^5.10.1", - "@testing-library/react": "^11.2.5", - "@testing-library/user-event": "^13.1.8", + "@testing-library/react": "^12.1.3", + "@testing-library/user-event": "^14.0.0", "@types/jest": "^26.0.7", - "@types/node": "^14.14.32", - "cross-fetch": "^3.0.6", - "msw": "^0.29.0" + "@types/node": "^16.11.26", + "cross-fetch": "^3.1.5", + "msw": "^0.35.0" }, "peerDependencies": { "@types/react": "^16.13.1 || ^17.0.0", diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/TeamPullRequestsTable.tsx b/plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/TeamPullRequestsBoard.tsx similarity index 89% rename from plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/TeamPullRequestsTable.tsx rename to plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/TeamPullRequestsBoard.tsx index f96cf6a5dd..f0f47c6a16 100644 --- a/plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/TeamPullRequestsTable.tsx +++ b/plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/TeamPullRequestsBoard.tsx @@ -19,16 +19,16 @@ import FullscreenIcon from '@material-ui/icons/Fullscreen'; import { Progress, InfoCard } from '@backstage/core-components'; -import { InfoCardHeader } from '../../components/InfoCardHeader'; -import { PullRequestBoardOptions } from '../../components/PullRequestBoardOptions'; -import { Wrapper } from '../../components/Wrapper'; -import { PullRequestCard } from '../../components/PullRequestCard'; +import { InfoCardHeader } from '../InfoCardHeader'; +import { PullRequestBoardOptions } from '../PullRequestBoardOptions'; +import { Wrapper } from '../Wrapper'; +import { PullRequestCard } from '../PullRequestCard'; import { usePullRequestsByTeam } from '../../hooks/usePullRequestsByTeam'; import { PRCardFormating } from '../../utils/types'; -import { DraftPrIcon } from '../../components/icons/DraftPr' +import { DraftPrIcon } from '../icons/DraftPr' import { useUserRepositories } from '../../hooks/useUserRepositories'; -const TeamPullRequestsTable: FunctionComponent = () => { +const TeamPullRequestsBoard: FunctionComponent = () => { const [infoCardFormat, setInfoCardFormat] = useState([]); const { repositories } = useUserRepositories(); const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam(repositories); @@ -117,4 +117,4 @@ const TeamPullRequestsTable: FunctionComponent = () => { return {getContent()}; }; -export default TeamPullRequestsTable; +export default TeamPullRequestsBoard; diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/index.ts b/plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/index.ts new file mode 100644 index 0000000000..c8e25cfff8 --- /dev/null +++ b/plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { default as TeamPullRequestsBoard } from './TeamPullRequestsBoard'; diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/index.ts b/plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/index.ts deleted file mode 100644 index 0da871526a..0000000000 --- a/plugins/github-pull-requests-board/src/components/TeamPullRequestsTable/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as TeamPullRequestsTable } from './TeamPullRequestsTable'; diff --git a/plugins/github-pull-requests-board/src/index.ts b/plugins/github-pull-requests-board/src/index.ts index 684b62f8cd..579b19b315 100644 --- a/plugins/github-pull-requests-board/src/index.ts +++ b/plugins/github-pull-requests-board/src/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { TeamPullRequestsTable, TeamPullRequestsPage } from './plugin'; +export { TeamPullRequestsBoard, TeamPullRequestsPage } from './plugin'; diff --git a/plugins/github-pull-requests-board/src/plugin.test.ts b/plugins/github-pull-requests-board/src/plugin.test.ts index c12c8b1f06..0204620ba5 100644 --- a/plugins/github-pull-requests-board/src/plugin.test.ts +++ b/plugins/github-pull-requests-board/src/plugin.test.ts @@ -13,11 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TeamPullRequestsTable, TeamPullRequestsPage } from './plugin'; +import { TeamPullRequestsBoard, TeamPullRequestsPage } from './plugin'; describe('github-pull-requests-board', () => { - it('should export TeamPullRequestsTable', () => { - expect(TeamPullRequestsTable).toBeDefined(); + it('should export TeamPullRequestsBoard', () => { + expect(TeamPullRequestsBoard).toBeDefined(); }); it('should export TeamPullRequestsPage', () => { expect(TeamPullRequestsPage).toBeDefined(); diff --git a/plugins/github-pull-requests-board/src/plugin.ts b/plugins/github-pull-requests-board/src/plugin.ts index 38edd7562e..8d45761def 100644 --- a/plugins/github-pull-requests-board/src/plugin.ts +++ b/plugins/github-pull-requests-board/src/plugin.ts @@ -27,13 +27,13 @@ const githubPullRequestsBoardPlugin = createPlugin({ }, }); -export const TeamPullRequestsTable = githubPullRequestsBoardPlugin.provide( +export const TeamPullRequestsBoard = githubPullRequestsBoardPlugin.provide( createComponentExtension({ - name: 'TeamPullRequestsTable', + name: 'TeamPullRequestsBoard', component: { lazy: () => - import('./components/TeamPullRequestsTable').then( - m => m.TeamPullRequestsTable, + import('./components/TeamPullRequestsBoard').then( + m => m.TeamPullRequestsBoard, ), }, }), diff --git a/yarn.lock b/yarn.lock index d456b5e8e4..58d2e2f803 100644 --- a/yarn.lock +++ b/yarn.lock @@ -354,27 +354,6 @@ json5 "^2.1.2" semver "^6.3.0" -"@babel/core@^7.7.5": - version "7.17.9" - resolved "https://registry.npmjs.org/@babel/core/-/core-7.17.9.tgz#6bae81a06d95f4d0dec5bb9d74bbc1f58babdcfe" - integrity sha512-5ug+SfZCpDAkVp9SFIZAzlW18rlzsOcJGaetCjkySnrXXDUw9AR8cDUm1iByTmdWM6yxX6/zycaV76w3YTF2gw== - dependencies: - "@ampproject/remapping" "^2.1.0" - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.9" - "@babel/helper-compilation-targets" "^7.17.7" - "@babel/helper-module-transforms" "^7.17.7" - "@babel/helpers" "^7.17.9" - "@babel/parser" "^7.17.9" - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.9" - "@babel/types" "^7.17.0" - convert-source-map "^1.7.0" - debug "^4.1.0" - gensync "^1.0.0-beta.2" - json5 "^2.2.1" - semver "^6.3.0" - "@babel/generator@^7.14.0", "@babel/generator@^7.16.8", "@babel/generator@^7.7.2": version "7.16.8" resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz#359d44d966b8cd059d543250ce79596f792f2ebe" @@ -393,15 +372,6 @@ jsesc "^2.5.1" source-map "^0.5.0" -"@babel/generator@^7.17.9": - version "7.17.9" - resolved "https://registry.npmjs.org/@babel/generator/-/generator-7.17.9.tgz#f4af9fd38fa8de143c29fce3f71852406fc1e2fc" - integrity sha512-rAdDousTwxbIxbz5I7GEQ3lUip+xVCXooZNbsydCWs3xA7ZsYOv+CFRdzGxRX78BmQHu9B1Eso59AOZQOJDEdQ== - dependencies: - "@babel/types" "^7.17.0" - jsesc "^2.5.1" - source-map "^0.5.0" - "@babel/helper-annotate-as-pure@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" @@ -495,14 +465,6 @@ "@babel/template" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/helper-function-name@^7.17.9": - version "7.17.9" - resolved "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.17.9.tgz#136fcd54bc1da82fcb47565cf16fd8e444b1ff12" - integrity sha512-7cRisGlVtiVqZ0MW0/yFB4atgpGLWEHUVYnb448hZK4x+vih0YO5UoS11XIYtZYqHd0dIPMdUSv8q5K4LdMnIg== - dependencies: - "@babel/template" "^7.16.7" - "@babel/types" "^7.17.0" - "@babel/helper-get-function-arity@^7.16.7": version "7.16.7" resolved "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz#ea08ac753117a669f1508ba06ebcc49156387419" @@ -657,15 +619,6 @@ "@babel/traverse" "^7.17.3" "@babel/types" "^7.17.0" -"@babel/helpers@^7.17.9": - version "7.17.9" - resolved "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.9.tgz#b2af120821bfbe44f9907b1826e168e819375a1a" - integrity sha512-cPCt915ShDWUEzEp3+UNRktO2n6v49l5RSnG9M5pS24hA+2FAc5si+Pn1i4VVbQQ+jh+bIZhPFQOJOzbrOYY1Q== - dependencies: - "@babel/template" "^7.16.7" - "@babel/traverse" "^7.17.9" - "@babel/types" "^7.17.0" - "@babel/highlight@^7.0.0", "@babel/highlight@^7.16.7": version "7.16.10" resolved "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz#744f2eb81579d6eea753c227b0f570ad785aba88" @@ -675,7 +628,7 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.10", "@babel/parser@^7.16.12", "@babel/parser@^7.16.7", "@babel/parser@^7.16.8", "@babel/parser@^7.17.3", "@babel/parser@^7.17.8", "@babel/parser@^7.17.9": +"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.10", "@babel/parser@^7.16.12", "@babel/parser@^7.16.7", "@babel/parser@^7.16.8", "@babel/parser@^7.17.3", "@babel/parser@^7.17.8": version "7.17.9" resolved "https://registry.npmjs.org/@babel/parser/-/parser-7.17.9.tgz#9c94189a6062f0291418ca021077983058e171ef" integrity sha512-vqUSBLP8dQHFPdPi9bc5GK9vRkYHJ49fsZdtoJ8EQ8ibpwk5rPKfvNIwChB0KVXcIjcepEBBd2VHC5r9Gy8ueg== @@ -1427,22 +1380,6 @@ "@babel/parser" "^7.16.7" "@babel/types" "^7.16.7" -"@babel/traverse@^7.1.0", "@babel/traverse@^7.17.9": - version "7.17.9" - resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.9.tgz#1f9b207435d9ae4a8ed6998b2b82300d83c37a0d" - integrity sha512-PQO8sDIJ8SIwipTPiR71kJQCKQYB5NGImbOviK8K+kg5xkNSYXLBupuX9QhatFowrsvo9Hj8WgArg3W7ijNAQw== - dependencies: - "@babel/code-frame" "^7.16.7" - "@babel/generator" "^7.17.9" - "@babel/helper-environment-visitor" "^7.16.7" - "@babel/helper-function-name" "^7.17.9" - "@babel/helper-hoist-variables" "^7.16.7" - "@babel/helper-split-export-declaration" "^7.16.7" - "@babel/parser" "^7.17.9" - "@babel/types" "^7.17.0" - debug "^4.1.0" - globals "^11.1.0" - "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.0", "@babel/traverse@^7.16.10", "@babel/traverse@^7.16.7", "@babel/traverse@^7.16.8", "@babel/traverse@^7.4.5", "@babel/traverse@^7.7.2": version "7.16.10" resolved "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz#448f940defbe95b5a8029975b051f75993e8239f" @@ -2663,7 +2600,7 @@ prop-types "^15.6.2" scheduler "^0.19.1" -"@hot-loader/react-dom-v17@npm:@hot-loader/react-dom@^17.0.2", "@hot-loader/react-dom@^17.0.2": +"@hot-loader/react-dom-v17@npm:@hot-loader/react-dom@^17.0.2": version "17.0.2" resolved "https://registry.npmjs.org/@hot-loader/react-dom/-/react-dom-17.0.2.tgz#0b24e484093e8f97eb5c72bebdda44fc20bc8400" integrity sha512-G2RZrFhsQClS+bdDh/Ojpk3SgocLPUGnvnJDTQYnmKSSwXtU+Yh+8QMs+Ia3zaAvBiOSpIIDSUxuN69cvKqrWg== @@ -2721,18 +2658,6 @@ resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== -"@jest/console@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz#4e04bc464014358b03ab4937805ee36a0aeb98f2" - integrity sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g== - dependencies: - "@jest/types" "^26.6.2" - "@types/node" "*" - chalk "^4.0.0" - jest-message-util "^26.6.2" - jest-util "^26.6.2" - slash "^3.0.0" - "@jest/console@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba" @@ -2745,40 +2670,6 @@ jest-util "^27.5.1" slash "^3.0.0" -"@jest/core@^26.6.3": - version "26.6.3" - resolved "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz#7639fcb3833d748a4656ada54bde193051e45fad" - integrity sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw== - dependencies: - "@jest/console" "^26.6.2" - "@jest/reporters" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-changed-files "^26.6.2" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-resolve-dependencies "^26.6.3" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - jest-watcher "^26.6.2" - micromatch "^4.0.2" - p-each-series "^2.1.0" - rimraf "^3.0.0" - slash "^3.0.0" - strip-ansi "^6.0.0" - "@jest/core@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626" @@ -2813,16 +2704,6 @@ slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz#ba364cc72e221e79cc8f0a99555bf5d7577cf92c" - integrity sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA== - dependencies: - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - jest-mock "^26.6.2" - "@jest/environment@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74" @@ -2833,18 +2714,6 @@ "@types/node" "*" jest-mock "^27.5.1" -"@jest/fake-timers@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz#459c329bcf70cee4af4d7e3f3e67848123535aad" - integrity sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA== - dependencies: - "@jest/types" "^26.6.2" - "@sinonjs/fake-timers" "^6.0.1" - "@types/node" "*" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-util "^26.6.2" - "@jest/fake-timers@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74" @@ -2857,15 +2726,6 @@ jest-mock "^27.5.1" jest-util "^27.5.1" -"@jest/globals@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz#5b613b78a1aa2655ae908eba638cc96a20df720a" - integrity sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA== - dependencies: - "@jest/environment" "^26.6.2" - "@jest/types" "^26.6.2" - expect "^26.6.2" - "@jest/globals@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b" @@ -2875,38 +2735,6 @@ "@jest/types" "^27.5.1" expect "^27.5.1" -"@jest/reporters@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz#1f518b99637a5f18307bd3ecf9275f6882a667f6" - integrity sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw== - dependencies: - "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - chalk "^4.0.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.2" - graceful-fs "^4.2.4" - istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.3" - istanbul-lib-report "^3.0.0" - istanbul-lib-source-maps "^4.0.0" - istanbul-reports "^3.0.2" - jest-haste-map "^26.6.2" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" - slash "^3.0.0" - source-map "^0.6.0" - string-length "^4.0.1" - terminal-link "^2.0.0" - v8-to-istanbul "^7.0.0" - optionalDependencies: - node-notifier "^8.0.0" - "@jest/reporters@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04" @@ -2938,15 +2766,6 @@ terminal-link "^2.0.0" v8-to-istanbul "^8.1.0" -"@jest/source-map@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz#29af5e1e2e324cafccc936f218309f54ab69d535" - integrity sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA== - dependencies: - callsites "^3.0.0" - graceful-fs "^4.2.4" - source-map "^0.6.0" - "@jest/source-map@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf" @@ -2956,16 +2775,6 @@ graceful-fs "^4.2.9" source-map "^0.6.0" -"@jest/test-result@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz#55da58b62df134576cc95476efa5f7949e3f5f18" - integrity sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ== - dependencies: - "@jest/console" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/istanbul-lib-coverage" "^2.0.0" - collect-v8-coverage "^1.0.0" - "@jest/test-result@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb" @@ -2976,17 +2785,6 @@ "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^26.6.3": - version "26.6.3" - resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz#98e8a45100863886d074205e8ffdc5a7eb582b17" - integrity sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw== - dependencies: - "@jest/test-result" "^26.6.2" - graceful-fs "^4.2.4" - jest-haste-map "^26.6.2" - jest-runner "^26.6.3" - jest-runtime "^26.6.3" - "@jest/test-sequencer@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b" @@ -2997,27 +2795,6 @@ jest-haste-map "^27.5.1" jest-runtime "^27.5.1" -"@jest/transform@^26.6.2": - version "26.6.2" - resolved "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz#5ac57c5fa1ad17b2aae83e73e45813894dcf2e4b" - integrity sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA== - dependencies: - "@babel/core" "^7.1.0" - "@jest/types" "^26.6.2" - babel-plugin-istanbul "^6.0.0" - chalk "^4.0.0" - convert-source-map "^1.4.0" - fast-json-stable-stringify "^2.0.0" - graceful-fs "^4.2.4" - jest-haste-map "^26.6.2" - jest-regex-util "^26.0.0" - jest-util "^26.6.2" - micromatch "^4.0.2" - pirates "^4.0.1" - slash "^3.0.0" - source-map "^0.6.1" - write-file-atomic "^3.0.0" - "@jest/transform@^27.5.1": version "27.5.1" resolved "https://registry.npmjs.org/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409" @@ -4397,7 +4174,7 @@ resolved "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.1.tgz#155ef21065427901994e765da8a0ba0eaae8b8bd" integrity sha512-6Wci+Tp3CgPt/B9B0a3J4s3yMgLNSku6w5TV6mN+61C71UqsRBv2FUibBf3tPGlNxebgPHMEUzKpb1ggE8KCKw== -"@mswjs/cookies@^0.1.5", "@mswjs/cookies@^0.1.6", "@mswjs/cookies@^0.1.7": +"@mswjs/cookies@^0.1.6", "@mswjs/cookies@^0.1.7": version "0.1.7" resolved "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.1.7.tgz#d334081b2c51057a61c1dd7b76ca3cac02251651" integrity sha512-bDg1ReMBx+PYDB4Pk7y1Q07Zz1iKIEUWQpkEXiA2lEWg9gvOZ8UBmGXilCEUvyYoRFlmr/9iXTRR69TrgSwX/Q== @@ -4413,17 +4190,6 @@ "@types/set-cookie-parser" "^2.4.0" set-cookie-parser "^2.4.6" -"@mswjs/interceptors@^0.10.0": - version "0.10.0" - resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.10.0.tgz#f5aad03c2c0591d164e3ed178b21942f1c2f8061" - integrity sha512-/M0GGpid5q2EDI+Keas1sLYF3VZFXHDE5gCmX/jHdp+OJFruVNca3PUk7A8KnGdPpuycZogdPsmRBSOXwjyA7A== - dependencies: - "@open-draft/until" "^1.0.3" - debug "^4.3.0" - headers-utils "^3.0.2" - strict-event-emitter "^0.2.0" - xmldom "^0.6.0" - "@mswjs/interceptors@^0.12.6", "@mswjs/interceptors@^0.12.7": version "0.12.7" resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.12.7.tgz#0d1cd4cd31a0f663e0455993951201faa09d0909" @@ -5335,31 +5101,16 @@ resolved "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5" integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ== -"@spotify/eslint-config-base@^12.0.0": - version "12.0.0" - resolved "https://registry.npmjs.org/@spotify/eslint-config-base/-/eslint-config-base-12.0.0.tgz#0b1e41bb436d5c1c20714703629514d64c3c0f06" - integrity sha512-5Uud/TmzakqmdUNCZpD8JFQRa2VG3dVd3DanSMpU/nVdu6K5LyX8EMU3Tz1vGP18Wih8iAu/sBSJhntNzw7e6w== - "@spotify/eslint-config-base@^13.0.0": version "13.0.0" resolved "https://registry.npmjs.org/@spotify/eslint-config-base/-/eslint-config-base-13.0.0.tgz#bb748bb2b705ffb5085f873aa0daf94dfad59985" integrity sha512-BrnexUcUQkp6XUw8HWSmE4LpWtJGgEC6A7vrSkgpgKJtZaYkpw8O+Xnk60DA266ecbFHYbQD6ngqKHlvjNB+pA== -"@spotify/eslint-config-react@^12.0.0": - version "12.0.0" - resolved "https://registry.npmjs.org/@spotify/eslint-config-react/-/eslint-config-react-12.0.0.tgz#5b8d4bc3b81a8ec2824648f482f1f6c3cf711893" - integrity sha512-lNHZRtJesNA273OJHBVUGAg2JYyVDZ+bsT7h3OwnX1HYgejJ3YcKPSziPM8TGFAN8DruH3tHFfaM63uAIA1+uw== - "@spotify/eslint-config-react@^13.0.0": version "13.0.1" resolved "https://registry.npmjs.org/@spotify/eslint-config-react/-/eslint-config-react-13.0.1.tgz#f309f5d3c53ef1e2c7c6ce05f76ee681970112c3" integrity sha512-gyC0CtJ2H9K57HyQG5/RcMsJiB6qmVbBHOHWukZcPLfYtwkK201kgMjHrVfJXoSN+mJxcWhDVPxqe+eA7LHshQ== -"@spotify/eslint-config-typescript@^12.0.0": - version "12.0.0" - resolved "https://registry.npmjs.org/@spotify/eslint-config-typescript/-/eslint-config-typescript-12.0.0.tgz#4c7af3f74a47668bec0c860b72e2a0103e78a138" - integrity sha512-nMVll8ZkN/W8+IHn6Iz3YzCKW0qhrn3TVfyxkAr3qmXm5cex+GzyUdZEuxb8rdN2inZL6A1Il2NFfO5p/UKxog== - "@spotify/eslint-config-typescript@^13.0.0": version "13.0.1" resolved "https://registry.npmjs.org/@spotify/eslint-config-typescript/-/eslint-config-typescript-13.0.1.tgz#47801a66d5569074a110f4422eba60aafc6bd7f8" @@ -5525,20 +5276,6 @@ "@babel/runtime" "^7.14.6" "@testing-library/dom" "^8.1.0" -"@testing-library/dom@^7.28.1": - version "7.31.2" - resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-7.31.2.tgz#df361db38f5212b88555068ab8119f5d841a8c4a" - integrity sha512-3UqjCpey6HiTZT92vODYLPxTBWlM8ZOOjr3LX5F37/VRipW2M1kX6I/Cm4VXzteZqfGfagg8yXywpcOgQBlNsQ== - dependencies: - "@babel/code-frame" "^7.10.4" - "@babel/runtime" "^7.12.5" - "@types/aria-query" "^4.2.0" - aria-query "^4.2.2" - chalk "^4.1.0" - dom-accessibility-api "^0.5.6" - lz-string "^1.4.4" - pretty-format "^26.6.2" - "@testing-library/dom@^8.0.0", "@testing-library/dom@^8.1.0": version "8.11.3" resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.3.tgz#38fd63cbfe14557021e88982d931e33fb7c1a808" @@ -5576,14 +5313,6 @@ "@babel/runtime" "^7.12.5" react-error-boundary "^3.1.0" -"@testing-library/react@^11.2.5": - version "11.2.7" - resolved "https://registry.npmjs.org/@testing-library/react/-/react-11.2.7.tgz#b29e2e95c6765c815786c0bc1d5aed9cb2bf7818" - integrity sha512-tzRNp7pzd5QmbtXNG/mhdcl7Awfu/Iz1RaVHY75zTdOkmHCuzMhRL83gWHSgOAcjS3CCbyfwUHMZgRJb4kAfpA== - dependencies: - "@babel/runtime" "^7.12.5" - "@testing-library/dom" "^7.28.1" - "@testing-library/react@^12.1.3": version "12.1.5" resolved "https://registry.npmjs.org/@testing-library/react/-/react-12.1.5.tgz#bb248f72f02a5ac9d949dea07279095fa577963b" @@ -5713,17 +5442,6 @@ "@types/babel__template" "*" "@types/babel__traverse" "*" -"@types/babel__core@^7.1.7": - version "7.1.19" - resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.19.tgz#7b497495b7d1b4812bdb9d02804d0576f43ee460" - integrity sha512-WEOTgRsbYkvA/KCsDwVEGkd7WAr1e3g31VHQ8zy5gul/V1qKullU/BU5I68X5v7V3GnB9eotmom4v5a5gjxorw== - dependencies: - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - "@types/babel__generator" "*" - "@types/babel__template" "*" - "@types/babel__traverse" "*" - "@types/babel__generator@*": version "7.6.1" resolved "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04" @@ -5869,7 +5587,7 @@ dependencies: "@types/express" "*" -"@types/cookie@^0.4.0", "@types/cookie@^0.4.1": +"@types/cookie@^0.4.1": version "0.4.1" resolved "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz#bfd02c1f2224567676c1545199f87c3a861d878d" integrity sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q== @@ -6192,7 +5910,7 @@ resolved "https://registry.npmjs.org/@types/humanize-duration/-/humanize-duration-3.27.1.tgz#f14740d1f585a0a8e3f46359b62fda8b0eaa31e7" integrity sha512-K3e+NZlpCKd6Bd/EIdqjFJRFHbrq5TzPPLwREk5Iv/YoIjQrs6ljdAUCo+Lb2xFlGNOjGSE0dqsVD19cZL137w== -"@types/inquirer@^7.3.1", "@types/inquirer@^7.3.3": +"@types/inquirer@^7.3.3": version "7.3.3" resolved "https://registry.npmjs.org/@types/inquirer/-/inquirer-7.3.3.tgz#92e6676efb67fa6925c69a2ee638f67a822952ac" integrity sha512-HhxyLejTHMfohAuhRun4csWigAMjXTmRyiJTU1Y/I1xmggikFMkOUoMQRlFm+zQcPEGHSs3io/0FAmNZf8EymQ== @@ -6579,11 +6297,6 @@ resolved "https://registry.npmjs.org/@types/pluralize/-/pluralize-0.0.29.tgz#6ffa33ed1fc8813c469b859681d09707eb40d03c" integrity sha512-BYOID+l2Aco2nBik+iYS4SZX0Lf20KPILP5RGmM1IgzdwNdTs0eebiFriOPcej1sX9mLnSoiNte5zcFxssgpGA== -"@types/prettier@^2.0.0": - version "2.6.0" - resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.6.0.tgz#efcbd41937f9ae7434c714ab698604822d890759" - integrity sha512-G/AdOadiZhnJp0jXCaBQU449W2h716OW/EoXeYkCytxKL06X1WCXB4DZpp8TpZ8eyIJVS1cw4lrlkkSYU21cDw== - "@types/prettier@^2.1.5": version "2.4.3" resolved "https://registry.npmjs.org/@types/prettier/-/prettier-2.4.3.tgz#a3c65525b91fca7da00ab1a3ac2b5a2a4afbffbf" @@ -7585,16 +7298,6 @@ ajv@^6.10.0, ajv@^6.10.1, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.12.5, ajv@^6.5.5, ajv json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^7.0.3: - version "7.2.4" - resolved "https://registry.npmjs.org/ajv/-/ajv-7.2.4.tgz#8e239d4d56cf884bccca8cca362f508446dc160f" - integrity sha512-nBeQgg/ZZA3u3SYxyaDvpvDtgZ/EZPF547ARgZBrG9Bhu1vKDwAIjtIf+sDtJUKa2zOcEbmRLBRSyMraS/Oy1A== - dependencies: - fast-deep-equal "^3.1.1" - json-schema-traverse "^1.0.0" - require-from-string "^2.0.2" - uri-js "^4.2.2" - ajv@^8.0.0, ajv@^8.10.0, ajv@^8.8.0: version "8.11.0" resolved "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz#977e91dd96ca669f54a11e23e378e33b884a565f" @@ -7720,14 +7423,6 @@ any-promise@^1.0.0: resolved "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= -anymatch@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" - integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== - dependencies: - micromatch "^3.1.4" - normalize-path "^2.1.1" - anymatch@^3.0.3, anymatch@~3.1.2: version "3.1.2" resolved "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -8242,20 +7937,6 @@ babel-core@^7.0.0-bridge.0: resolved "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz#95a492ddd90f9b4e9a4a1da14eb335b87b634ece" integrity sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg== -babel-jest@^26.6.3: - version "26.6.3" - resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz#d87d25cb0037577a0c89f82e5755c5d293c01056" - integrity sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA== - dependencies: - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/babel__core" "^7.1.7" - babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^26.6.2" - chalk "^4.0.0" - graceful-fs "^4.2.4" - slash "^3.0.0" - babel-jest@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444" @@ -8277,7 +7958,7 @@ babel-plugin-dynamic-import-node@^2.3.3: dependencies: object.assign "^4.1.0" -babel-plugin-istanbul@^6.0.0, babel-plugin-istanbul@^6.1.1: +babel-plugin-istanbul@^6.1.1: version "6.1.1" resolved "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73" integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA== @@ -8288,16 +7969,6 @@ babel-plugin-istanbul@^6.0.0, babel-plugin-istanbul@^6.1.1: istanbul-lib-instrument "^5.0.4" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz#8185bd030348d254c6d7dd974355e6a28b21e62d" - integrity sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw== - dependencies: - "@babel/template" "^7.3.3" - "@babel/types" "^7.3.3" - "@types/babel__core" "^7.0.0" - "@types/babel__traverse" "^7.0.6" - babel-plugin-jest-hoist@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e" @@ -8397,14 +8068,6 @@ babel-preset-fbjs@^3.4.0: "@babel/plugin-transform-template-literals" "^7.0.0" babel-plugin-syntax-trailing-function-commas "^7.0.0-beta.0" -babel-preset-jest@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz#747872b1171df032252426586881d62d31798fee" - integrity sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ== - dependencies: - babel-plugin-jest-hoist "^26.6.2" - babel-preset-current-node-syntax "^1.0.0" - babel-preset-jest@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81" @@ -9103,7 +8766,7 @@ camelcase@^5.0.0, camelcase@^5.3.1: resolved "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -camelcase@^6.0.0, camelcase@^6.2.0, camelcase@^6.3.0: +camelcase@^6.2.0, camelcase@^6.3.0: version "6.3.0" resolved "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== @@ -9141,13 +8804,6 @@ capital-case@^1.0.4: tslib "^2.0.3" upper-case-first "^2.0.2" -capture-exit@^2.0.0: - version "2.0.0" - resolved "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" - integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g== - dependencies: - rsvp "^4.8.4" - caseless@~0.12.0: version "0.12.0" resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" @@ -9357,11 +9013,6 @@ circleci-api@^4.0.0: dependencies: axios "^0.21.1" -cjs-module-lexer@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz#4186fcca0eae175970aee870b9fe2d6cf8d5655f" - integrity sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw== - cjs-module-lexer@^1.0.0: version "1.2.2" resolved "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.2.2.tgz#9f84ba3244a512f3a54e5277e8eef4c489864e40" @@ -9753,11 +9404,6 @@ commander@^5.1.0: resolved "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== -commander@^6.1.0: - version "6.2.1" - resolved "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" - integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== - commander@^7.2.0: version "7.2.0" resolved "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" @@ -10923,7 +10569,7 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: +debug@4, debug@4.3.4, debug@^4.0.0, debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: version "4.3.4" resolved "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== @@ -11567,11 +11213,6 @@ elliptic@^6.0.0: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -emittery@^0.7.1: - version "0.7.2" - resolved "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz#25595908e13af0f5674ab419396e2fb394cdfa82" - integrity sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ== - emittery@^0.8.1: version "0.8.1" resolved "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860" @@ -12027,13 +11668,6 @@ eslint-plugin-import@^2.25.4: resolve "^1.22.0" tsconfig-paths "^3.14.1" -eslint-plugin-jest@^25.3.4: - version "25.7.0" - resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-25.7.0.tgz#ff4ac97520b53a96187bad9c9814e7d00de09a6a" - integrity sha512-PWLUEXeeF7C9QGKqvdSbzLOiLTx+bno7/HC9eefePfEb257QFHg7ye3dh80AZVkaa/RQsBB1Q/ORQvg2X7F0NQ== - dependencies: - "@typescript-eslint/experimental-utils" "^5.0.0" - eslint-plugin-jest@^26.1.2: version "26.2.2" resolved "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-26.2.2.tgz#74e000544259f1ef0462a609a3fc9e5da3768f6c" @@ -12139,18 +11773,6 @@ eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0: resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== -eslint-webpack-plugin@^2.6.0: - version "2.6.0" - resolved "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-2.6.0.tgz#3bd4ada4e539cb1f6687d2f619073dbb509361cd" - integrity sha512-V+LPY/T3kur5QO3u+1s34VDTcRxjXWPUGM4hlmTb5DwVD0OQz631yGTxJZf4SpAqAjdbBVe978S8BJeHpAdOhQ== - dependencies: - "@types/eslint" "^7.28.2" - arrify "^2.0.1" - jest-worker "^27.3.1" - micromatch "^4.0.4" - normalize-path "^3.0.0" - schema-utils "^3.1.1" - eslint-webpack-plugin@^3.1.1: version "3.1.1" resolved "https://registry.npmjs.org/eslint-webpack-plugin/-/eslint-webpack-plugin-3.1.1.tgz#83dad2395e5f572d6f4d919eedaa9cf902890fcb" @@ -12397,12 +12019,7 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: react-use "^17.2.4" zen-observable "^0.8.15" -exec-sh@^0.3.2: - version "0.3.6" - resolved "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc" - integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w== - -execa@4.1.0, execa@^4.0.0: +execa@4.1.0: version "4.1.0" resolved "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== @@ -12500,18 +12117,6 @@ expand-template@^2.0.3: resolved "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== -expect@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz#c6b996bf26bf3fe18b67b2d0f51fc981ba934417" - integrity sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA== - dependencies: - "@jest/types" "^26.6.2" - ansi-styles "^4.0.0" - jest-get-type "^26.3.0" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-regex-util "^26.0.0" - expect@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74" @@ -13198,16 +12803,6 @@ fs-extra@10.1.0, fs-extra@^10.0.0, fs-extra@^10.0.1: jsonfile "^6.0.1" universalify "^2.0.0" -fs-extra@9.1.0, fs-extra@^9.0.0, fs-extra@^9.1.0: - version "9.1.0" - resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" - integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== - dependencies: - at-least-node "^1.0.0" - graceful-fs "^4.2.0" - jsonfile "^6.0.1" - universalify "^2.0.0" - fs-extra@^7.0.1, fs-extra@~7.0.1: version "7.0.1" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9" @@ -13226,6 +12821,16 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-extra@^9.0.0, fs-extra@^9.1.0: + version "9.1.0" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" + integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-minipass@^1.2.7: version "1.2.7" resolved "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz#ccff8570841e7fe4265693da88936c55aed7f7c7" @@ -13250,7 +12855,7 @@ fs.realpath@^1.0.0: resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^2.1.2, fsevents@^2.3.2, fsevents@~2.3.2: +fsevents@^2.3.2, fsevents@~2.3.2: version "2.3.2" resolved "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== @@ -13833,7 +13438,7 @@ graphql-ws@^5.4.1: resolved "https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.5.5.tgz#f375486d3f196e2a2527b503644693ae3a8670a9" integrity sha512-hvyIS71vs4Tu/yUYHPvGXsTgo0t3arU820+lT5VjZS2go0ewp2LqyCgxEN56CzOG7Iys52eRhHBiD1gGRdiQtw== -graphql@^15.4.0, graphql@^15.5.1: +graphql@^15.5.1: version "15.8.0" resolved "https://registry.npmjs.org/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38" integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== @@ -13848,11 +13453,6 @@ grouped-queue@^2.0.0: resolved "https://registry.npmjs.org/grouped-queue/-/grouped-queue-2.0.0.tgz#a2c6713f2171e45db2c300a3a9d7c119d694dac8" integrity sha512-/PiFUa7WIsl48dUeCvhIHnwNmAAzlI/eHoJl0vu3nsFA366JleY7Ff8EVTplZu5kO0MIdZjKTTnzItL61ahbnw== -growly@^1.3.0: - version "1.3.0" - resolved "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" - integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= - gtoken@^5.0.4: version "5.1.0" resolved "https://registry.npmjs.org/gtoken/-/gtoken-5.1.0.tgz#4ba8d2fc9a8459098f76e7e8fd7beaa39fda9fe4" @@ -14858,13 +14458,6 @@ is-core-module@^2.1.0, is-core-module@^2.2.0, is-core-module@^2.8.0: dependencies: has "^1.0.3" -is-core-module@^2.8.1: - version "2.9.0" - resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" - integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== - dependencies: - has "^1.0.3" - is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -15425,16 +15018,6 @@ istanbul-lib-coverage@^3.2.0: resolved "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw== -istanbul-lib-instrument@^4.0.3: - version "4.0.3" - resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" - integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== - dependencies: - "@babel/core" "^7.7.5" - "@istanbuljs/schema" "^0.1.2" - istanbul-lib-coverage "^3.0.0" - semver "^6.3.0" - istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0: version "5.1.0" resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.1.0.tgz#7b49198b657b27a730b8e9cb601f1e1bff24c59a" @@ -15464,7 +15047,7 @@ istanbul-lib-source-maps@^4.0.0: istanbul-lib-coverage "^3.0.0" source-map "^0.6.1" -istanbul-reports@^3.0.2, istanbul-reports@^3.1.3: +istanbul-reports@^3.1.3: version "3.1.4" resolved "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.4.tgz#1b6f068ecbc6c331040aab5741991273e609e40c" integrity sha512-r1/DshN4KSE7xWEknZLLLLDn5CJybV3nw01VTkp6D5jzLuELlcbudfj/eSQFvrKsJuTVCGnePO7ho82Nw9zzfw== @@ -15489,15 +15072,6 @@ jenkins@^0.28.1: dependencies: papi "^0.29.0" -jest-changed-files@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz#f6198479e1cc66f22f9ae1e22acaa0b429c042d0" - integrity sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ== - dependencies: - "@jest/types" "^26.6.2" - execa "^4.0.0" - throat "^5.0.0" - jest-changed-files@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5" @@ -15532,25 +15106,6 @@ jest-circus@^27.5.1: stack-utils "^2.0.3" throat "^6.0.1" -jest-cli@^26.6.3: - version "26.6.3" - resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz#43117cfef24bc4cd691a174a8796a532e135e92a" - integrity sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg== - dependencies: - "@jest/core" "^26.6.3" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" - chalk "^4.0.0" - exit "^0.1.2" - graceful-fs "^4.2.4" - import-local "^3.0.2" - is-ci "^2.0.0" - jest-config "^26.6.3" - jest-util "^26.6.2" - jest-validate "^26.6.2" - prompts "^2.0.1" - yargs "^15.4.1" - jest-cli@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145" @@ -15569,30 +15124,6 @@ jest-cli@^27.5.1: prompts "^2.0.1" yargs "^16.2.0" -jest-config@^26.6.3: - version "26.6.3" - resolved "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz#64f41444eef9eb03dc51d5c53b75c8c71f645349" - integrity sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg== - dependencies: - "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^26.6.3" - "@jest/types" "^26.6.2" - babel-jest "^26.6.3" - chalk "^4.0.0" - deepmerge "^4.2.2" - glob "^7.1.1" - graceful-fs "^4.2.4" - jest-environment-jsdom "^26.6.2" - jest-environment-node "^26.6.2" - jest-get-type "^26.3.0" - jest-jasmine2 "^26.6.3" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - micromatch "^4.0.2" - pretty-format "^26.6.2" - jest-config@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41" @@ -15630,7 +15161,7 @@ jest-css-modules@^2.1.0: dependencies: identity-obj-proxy "3.0.0" -jest-diff@^26.0.0, jest-diff@^26.6.2: +jest-diff@^26.0.0: version "26.6.2" resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz#1aa7468b52c3a68d7d5c5fdcdfcd5e49bd164394" integrity sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA== @@ -15650,13 +15181,6 @@ jest-diff@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" -jest-docblock@^26.0.0: - version "26.0.0" - resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz#3e2fa20899fc928cb13bd0ff68bd3711a36889b5" - integrity sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w== - dependencies: - detect-newline "^3.0.0" - jest-docblock@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0" @@ -15664,17 +15188,6 @@ jest-docblock@^27.5.1: dependencies: detect-newline "^3.0.0" -jest-each@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz#02526438a77a67401c8a6382dfe5999952c167cb" - integrity sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A== - dependencies: - "@jest/types" "^26.6.2" - chalk "^4.0.0" - jest-get-type "^26.3.0" - jest-util "^26.6.2" - pretty-format "^26.6.2" - jest-each@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e" @@ -15686,19 +15199,6 @@ jest-each@^27.5.1: jest-util "^27.5.1" pretty-format "^27.5.1" -jest-environment-jsdom@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz#78d09fe9cf019a357009b9b7e1f101d23bd1da3e" - integrity sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q== - dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" - jsdom "^16.4.0" - jest-environment-jsdom@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546" @@ -15712,18 +15212,6 @@ jest-environment-jsdom@^27.5.1: jest-util "^27.5.1" jsdom "^16.6.0" -jest-environment-node@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz#824e4c7fb4944646356f11ac75b229b0035f2b0c" - integrity sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag== - dependencies: - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - jest-mock "^26.6.2" - jest-util "^26.6.2" - jest-environment-node@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e" @@ -15746,27 +15234,6 @@ jest-get-type@^27.5.1: resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1" integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw== -jest-haste-map@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz#dd7e60fe7dc0e9f911a23d79c5ff7fb5c2cafeaa" - integrity sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w== - dependencies: - "@jest/types" "^26.6.2" - "@types/graceful-fs" "^4.1.2" - "@types/node" "*" - anymatch "^3.0.3" - fb-watchman "^2.0.0" - graceful-fs "^4.2.4" - jest-regex-util "^26.0.0" - jest-serializer "^26.6.2" - jest-util "^26.6.2" - jest-worker "^26.6.2" - micromatch "^4.0.2" - sane "^4.0.3" - walker "^1.0.7" - optionalDependencies: - fsevents "^2.1.2" - jest-haste-map@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f" @@ -15787,30 +15254,6 @@ jest-haste-map@^27.5.1: optionalDependencies: fsevents "^2.3.2" -jest-jasmine2@^26.6.3: - version "26.6.3" - resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz#adc3cf915deacb5212c93b9f3547cd12958f2edd" - integrity sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg== - dependencies: - "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - chalk "^4.0.0" - co "^4.6.0" - expect "^26.6.2" - is-generator-fn "^2.0.0" - jest-each "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-runtime "^26.6.3" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - pretty-format "^26.6.2" - throat "^5.0.0" - jest-jasmine2@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4" @@ -15834,14 +15277,6 @@ jest-jasmine2@^27.5.1: pretty-format "^27.5.1" throat "^6.0.1" -jest-leak-detector@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz#7717cf118b92238f2eba65054c8a0c9c653a91af" - integrity sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg== - dependencies: - jest-get-type "^26.3.0" - pretty-format "^26.6.2" - jest-leak-detector@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8" @@ -15860,21 +15295,6 @@ jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1: jest-get-type "^27.5.1" pretty-format "^27.5.1" -jest-message-util@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz#58173744ad6fc0506b5d21150b9be56ef001ca07" - integrity sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA== - dependencies: - "@babel/code-frame" "^7.0.0" - "@jest/types" "^26.6.2" - "@types/stack-utils" "^2.0.0" - chalk "^4.0.0" - graceful-fs "^4.2.4" - micromatch "^4.0.2" - pretty-format "^26.6.2" - slash "^3.0.0" - stack-utils "^2.0.2" - jest-message-util@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf" @@ -15890,14 +15310,6 @@ jest-message-util@^27.5.1: slash "^3.0.0" stack-utils "^2.0.3" -jest-mock@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz#d6cb712b041ed47fe0d9b6fc3474bc6543feb302" - integrity sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew== - dependencies: - "@jest/types" "^26.6.2" - "@types/node" "*" - jest-mock@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6" @@ -15911,25 +15323,11 @@ jest-pnp-resolver@^1.2.2: resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== -jest-regex-util@^26.0.0: - version "26.0.0" - resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" - integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== - jest-regex-util@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95" integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg== -jest-resolve-dependencies@^26.6.3: - version "26.6.3" - resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz#6680859ee5d22ee5dcd961fe4871f59f4c784fb6" - integrity sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg== - dependencies: - "@jest/types" "^26.6.2" - jest-regex-util "^26.0.0" - jest-snapshot "^26.6.2" - jest-resolve-dependencies@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8" @@ -15939,20 +15337,6 @@ jest-resolve-dependencies@^27.5.1: jest-regex-util "^27.5.1" jest-snapshot "^27.5.1" -jest-resolve@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz#a3ab1517217f469b504f1b56603c5bb541fbb507" - integrity sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ== - dependencies: - "@jest/types" "^26.6.2" - chalk "^4.0.0" - graceful-fs "^4.2.4" - jest-pnp-resolver "^1.2.2" - jest-util "^26.6.2" - read-pkg-up "^7.0.1" - resolve "^1.18.1" - slash "^3.0.0" - jest-resolve@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384" @@ -15969,32 +15353,6 @@ jest-resolve@^27.5.1: resolve.exports "^1.1.0" slash "^3.0.0" -jest-runner@^26.6.3: - version "26.6.3" - resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz#2d1fed3d46e10f233fd1dbd3bfaa3fe8924be159" - integrity sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ== - dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - chalk "^4.0.0" - emittery "^0.7.1" - exit "^0.1.2" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-docblock "^26.0.0" - jest-haste-map "^26.6.2" - jest-leak-detector "^26.6.2" - jest-message-util "^26.6.2" - jest-resolve "^26.6.2" - jest-runtime "^26.6.3" - jest-util "^26.6.2" - jest-worker "^26.6.2" - source-map-support "^0.5.6" - throat "^5.0.0" - jest-runner@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5" @@ -16022,39 +15380,6 @@ jest-runner@^27.5.1: source-map-support "^0.5.6" throat "^6.0.1" -jest-runtime@^26.6.3: - version "26.6.3" - resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz#4f64efbcfac398331b74b4b3c82d27d401b8fa2b" - integrity sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw== - dependencies: - "@jest/console" "^26.6.2" - "@jest/environment" "^26.6.2" - "@jest/fake-timers" "^26.6.2" - "@jest/globals" "^26.6.2" - "@jest/source-map" "^26.6.2" - "@jest/test-result" "^26.6.2" - "@jest/transform" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - cjs-module-lexer "^0.6.0" - collect-v8-coverage "^1.0.0" - exit "^0.1.2" - glob "^7.1.3" - graceful-fs "^4.2.4" - jest-config "^26.6.3" - jest-haste-map "^26.6.2" - jest-message-util "^26.6.2" - jest-mock "^26.6.2" - jest-regex-util "^26.0.0" - jest-resolve "^26.6.2" - jest-snapshot "^26.6.2" - jest-util "^26.6.2" - jest-validate "^26.6.2" - slash "^3.0.0" - strip-bom "^4.0.0" - yargs "^15.4.1" - jest-runtime@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af" @@ -16083,14 +15408,6 @@ jest-runtime@^27.5.1: slash "^3.0.0" strip-bom "^4.0.0" -jest-serializer@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz#d139aafd46957d3a448f3a6cdabe2919ba0742d1" - integrity sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g== - dependencies: - "@types/node" "*" - graceful-fs "^4.2.4" - jest-serializer@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64" @@ -16099,28 +15416,6 @@ jest-serializer@^27.5.1: "@types/node" "*" graceful-fs "^4.2.9" -jest-snapshot@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz#f3b0af1acb223316850bd14e1beea9837fb39c84" - integrity sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og== - dependencies: - "@babel/types" "^7.0.0" - "@jest/types" "^26.6.2" - "@types/babel__traverse" "^7.0.4" - "@types/prettier" "^2.0.0" - chalk "^4.0.0" - expect "^26.6.2" - graceful-fs "^4.2.4" - jest-diff "^26.6.2" - jest-get-type "^26.3.0" - jest-haste-map "^26.6.2" - jest-matcher-utils "^26.6.2" - jest-message-util "^26.6.2" - jest-resolve "^26.6.2" - natural-compare "^1.4.0" - pretty-format "^26.6.2" - semver "^7.3.2" - jest-snapshot@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1" @@ -16156,18 +15451,6 @@ jest-transform-yaml@^1.0.0: dependencies: js-yaml "4.1.0" -jest-util@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz#907535dbe4d5a6cb4c47ac9b926f6af29576cbc1" - integrity sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q== - dependencies: - "@jest/types" "^26.6.2" - "@types/node" "*" - chalk "^4.0.0" - graceful-fs "^4.2.4" - is-ci "^2.0.0" - micromatch "^4.0.2" - jest-util@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9" @@ -16180,18 +15463,6 @@ jest-util@^27.5.1: graceful-fs "^4.2.9" picomatch "^2.2.3" -jest-validate@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz#23d380971587150467342911c3d7b4ac57ab20ec" - integrity sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ== - dependencies: - "@jest/types" "^26.6.2" - camelcase "^6.0.0" - chalk "^4.0.0" - jest-get-type "^26.3.0" - leven "^3.1.0" - pretty-format "^26.6.2" - jest-validate@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067" @@ -16204,19 +15475,6 @@ jest-validate@^27.5.1: leven "^3.1.0" pretty-format "^27.5.1" -jest-watcher@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz#a5b683b8f9d68dbcb1d7dae32172d2cca0592975" - integrity sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ== - dependencies: - "@jest/test-result" "^26.6.2" - "@jest/types" "^26.6.2" - "@types/node" "*" - ansi-escapes "^4.2.1" - chalk "^4.0.0" - jest-util "^26.6.2" - string-length "^4.0.1" - jest-watcher@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2" @@ -16235,15 +15493,6 @@ jest-when@^3.1.0: resolved "https://registry.npmjs.org/jest-when/-/jest-when-3.5.1.tgz#33ab6f923661cf878cd08fe9df64b507934603db" integrity sha512-o+HiaIVCg1IC95sMDKHU9G5v5N5l3UHqXvJpf0PgAMThZeQo4Hf5Sgoj+wpCBRGg4/KtzSAZZZEKNiLqE0i4eQ== -jest-worker@^26.6.2: - version "26.6.2" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" - integrity sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ== - dependencies: - "@types/node" "*" - merge-stream "^2.0.0" - supports-color "^7.0.0" - jest-worker@^27.3.1, jest-worker@^27.4.5, jest-worker@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" @@ -16253,15 +15502,6 @@ jest-worker@^27.3.1, jest-worker@^27.4.5, jest-worker@^27.5.1: merge-stream "^2.0.0" supports-color "^8.0.0" -jest@^26.0.1: - version "26.6.3" - resolved "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz#40e8fdbe48f00dfa1f0ce8121ca74b88ac9148ef" - integrity sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q== - dependencies: - "@jest/core" "^26.6.3" - import-local "^3.0.2" - jest-cli "^26.6.3" - jest@^27.5.1: version "27.5.1" resolved "https://registry.npmjs.org/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc" @@ -16437,7 +15677,7 @@ jscodeshift@^0.13.0: temp "^0.8.4" write-file-atomic "^2.3.0" -jsdom@^16.4.0, jsdom@^16.5.2, jsdom@^16.6.0: +jsdom@^16.5.2, jsdom@^16.6.0: version "16.7.0" resolved "https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710" integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw== @@ -16615,7 +15855,7 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2, json5@^2.1.3, json5@^2.2.0, json5@^2.2.1: +json5@^2.1.2, json5@^2.1.3, json5@^2.2.0: version "2.2.1" resolved "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== @@ -17774,13 +17014,6 @@ make-fetch-happen@^9.1.0: socks-proxy-agent "^6.0.0" ssri "^8.0.0" -makeerror@1.0.12: - version "1.0.12" - resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a" - integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg== - dependencies: - tmpl "1.0.5" - makeerror@1.0.x: version "1.0.11" resolved "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -18404,7 +17637,7 @@ micromark@^3.0.0: micromark-util-types "^1.0.1" parse-entities "^3.0.0" -micromatch@^3.1.10, micromatch@^3.1.4: +micromatch@^3.1.10: version "3.1.10" resolved "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -18544,13 +17777,6 @@ minimatch@3.0.4: dependencies: brace-expansion "^1.1.7" -minimatch@5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.0.0.tgz#281d8402aaaeed18a9e8406ad99c46a19206c6ef" - integrity sha512-EU+GCVjXD00yOUf1TwAHVP7v3fBD3A8RkkPYsWWKGWesxM/572sL53wJQnHxquHlRhYUV36wHkqrN8cdikKc2g== - dependencies: - brace-expansion "^2.0.1" - minimatch@5.0.1, minimatch@^5.0.0, minimatch@^5.0.1: version "5.0.1" resolved "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" @@ -18581,7 +17807,7 @@ minimist-options@4.1.0, minimist-options@^4.0.2: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1.2.6: version "1.2.6" resolved "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== @@ -18777,31 +18003,6 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: resolved "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== -msw@^0.29.0: - version "0.29.0" - resolved "https://registry.npmjs.org/msw/-/msw-0.29.0.tgz#7242d575cb01db0c925241587df1fc2b79230d78" - integrity sha512-C/wz1d5uAEZRvAPAYrXG1rwLxXl0+BOs+JPrCzasoABZW3ATwS6ifSze+/DAgA93e9M86RXwvy6yDtZeZWmCFQ== - dependencies: - "@mswjs/cookies" "^0.1.5" - "@mswjs/interceptors" "^0.10.0" - "@open-draft/until" "^1.0.3" - "@types/cookie" "^0.4.0" - "@types/inquirer" "^7.3.1" - "@types/js-levenshtein" "^1.1.0" - chalk "^4.1.1" - chokidar "^3.4.2" - cookie "^0.4.1" - graphql "^15.4.0" - headers-utils "^3.0.2" - inquirer "^8.1.0" - js-levenshtein "^1.1.6" - node-fetch "^2.6.1" - node-match-path "^0.6.3" - statuses "^2.0.0" - strict-event-emitter "^0.2.0" - type-fest "^1.1.3" - yargs "^17.0.1" - msw@^0.35.0: version "0.35.0" resolved "https://registry.npmjs.org/msw/-/msw-0.35.0.tgz#18a4ceb6c822ef226a30421d434413bc45030d38" @@ -19171,18 +18372,6 @@ node-modules-regexp@^1.0.0: resolved "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= -node-notifier@^8.0.0: - version "8.0.2" - resolved "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.2.tgz#f3167a38ef0d2c8a866a83e318c1ba0efeb702c5" - integrity sha512-oJP/9NAdd9+x2Q+rfphB2RJCHjod70RcRLjosiPMMu5gjIfwVnOUGq2nbTjTUbmy0DJ/tFIVT30+Qe3nzl4TJg== - dependencies: - growly "^1.3.0" - is-wsl "^2.2.0" - semver "^7.3.2" - shellwords "^0.1.1" - uuid "^8.3.0" - which "^2.0.2" - node-releases@^2.0.1: version "2.0.1" resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz#3d1d395f204f1f2f29a54358b9fb678765ad2fc5" @@ -19802,11 +18991,6 @@ p-cancelable@^2.0.0: resolved "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz#4a3740f5bdaf5ed5d7c3e34882c6fb5d6b266a6e" integrity sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg== -p-each-series@^2.1.0: - version "2.2.0" - resolved "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz#105ab0357ce72b202a8a8b94933672657b5e2a9a" - integrity sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA== - p-filter@^2.1.0: version "2.1.0" resolved "https://registry.npmjs.org/p-filter/-/p-filter-2.1.0.tgz#1b1472562ae7a0f742f0f3d3d3718ea66ff9c09c" @@ -22744,11 +21928,6 @@ rollup-plugin-esbuild@^4.7.2: joycon "^3.0.1" jsonc-parser "^3.0.0" -rollup-plugin-peer-deps-external@^2.2.2: - version "2.2.4" - resolved "https://registry.npmjs.org/rollup-plugin-peer-deps-external/-/rollup-plugin-peer-deps-external-2.2.4.tgz#8a420bbfd6dccc30aeb68c9bf57011f2f109570d" - integrity sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g== - rollup-plugin-postcss@*, rollup-plugin-postcss@^4.0.0: version "4.0.2" resolved "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-4.0.2.tgz#15e9462f39475059b368ce0e49c800fa4b1f7050" @@ -22790,11 +21969,6 @@ rollup@^2.60.2: optionalDependencies: fsevents "~2.3.2" -rsvp@^4.8.4: - version "4.8.5" - resolved "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734" - integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== - rtl-css-js@^1.14.0: version "1.14.0" resolved "https://registry.npmjs.org/rtl-css-js/-/rtl-css-js-1.14.0.tgz#daa4f192a92509e292a0519f4b255e6e3c076b7d" @@ -22879,21 +22053,6 @@ safe-stable-stringify@^2.2.0, safe-stable-stringify@^2.3.1: resolved "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sane@^4.0.3: - version "4.1.0" - resolved "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" - integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA== - dependencies: - "@cnakazawa/watch" "^1.0.3" - anymatch "^2.0.0" - capture-exit "^2.0.0" - exec-sh "^0.3.2" - execa "^1.0.0" - fb-watchman "^2.0.0" - micromatch "^3.1.4" - minimist "^1.1.1" - walker "~1.0.5" - sanitize-filename@^1.6.1: version "1.6.3" resolved "https://registry.npmjs.org/sanitize-filename/-/sanitize-filename-1.6.3.tgz#755ebd752045931977e30b2025d340d7c9090378" @@ -23230,11 +22389,6 @@ shelljs@^0.8.5: interpret "^1.0.0" rechoir "^0.6.2" -shellwords@^0.1.1: - version "0.1.1" - resolved "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b" - integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww== - shx@^0.3.2: version "0.3.4" resolved "https://registry.npmjs.org/shx/-/shx-0.3.4.tgz#74289230b4b663979167f94e1935901406e40f02" @@ -23775,7 +22929,7 @@ stack-trace@0.0.x: resolved "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz#547c70b347e8d32b4e108ea1a2a159e5fdde19c0" integrity sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA= -stack-utils@^2.0.2, stack-utils@^2.0.3: +stack-utils@^2.0.3: version "2.0.5" resolved "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz#d25265fca995154659dbbfba3b49254778d2fdd5" integrity sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA== @@ -24593,11 +23747,6 @@ thenify-all@^1.0.0: dependencies: any-promise "^1.0.0" -throat@^5.0.0: - version "5.0.0" - resolved "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b" - integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== - throat@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz#d514fedad95740c12c2d7fc70ea863eb51ade375" @@ -24713,7 +23862,7 @@ tmp@^0.2.0, tmp@~0.2.1: dependencies: rimraf "^3.0.0" -tmpl@1.0.5, tmpl@1.0.x: +tmpl@1.0.x: version "1.0.5" resolved "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw== @@ -25052,7 +24201,7 @@ type-fest@^0.8.1: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== -type-fest@^1.1.3, type-fest@^1.2.1, type-fest@^1.2.2: +type-fest@^1.2.1, type-fest@^1.2.2: version "1.4.0" resolved "https://registry.npmjs.org/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== @@ -25086,19 +24235,6 @@ typedarray@^0.0.6: resolved "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript-json-schema@^0.52.0: - version "0.52.0" - resolved "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.52.0.tgz#954560ec90e5486e8f7a5b7706ec59286a708e29" - integrity sha512-3ZdHzx116gZ+D9LmMl5/+d1G3Rpt8baWngKzepYWHnXbAa8Winv64CmFRqLlMKneE1c40yugYDFcWdyX1FjGzQ== - dependencies: - "@types/json-schema" "^7.0.9" - "@types/node" "^16.9.2" - glob "^7.1.7" - safe-stable-stringify "^2.2.0" - ts-node "^10.2.1" - typescript "~4.4.4" - yargs "^17.1.1" - typescript-json-schema@^0.53.0: version "0.53.0" resolved "https://registry.npmjs.org/typescript-json-schema/-/typescript-json-schema-0.53.0.tgz#ac5b89e4b0af55be422f475a041360e0556f88ea" @@ -25622,15 +24758,6 @@ v8-compile-cache@^2.0.3: resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== -v8-to-istanbul@^7.0.0: - version "7.1.2" - resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.2.tgz#30898d1a7fa0c84d225a2c1434fb958f290883c1" - integrity sha512-TxNb7YEUwkLXCQYeudi6lgQ/SZrzNO4kMdlqVxaZPUIUjCv6iSSypUQX70kNBSERpQ8fk48+d61FXk+tgqcWow== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.1" - convert-source-map "^1.6.0" - source-map "^0.7.3" - v8-to-istanbul@^8.1.0: version "8.1.1" resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed" @@ -25827,13 +24954,6 @@ walker@^1.0.7: dependencies: makeerror "1.0.x" -walker@~1.0.5: - version "1.0.8" - resolved "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f" - integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ== - dependencies: - makeerror "1.0.12" - watchpack@^2.3.1: version "2.3.1" resolved "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz#4200d9447b401156eeca7767ee610f8809bc9d25" @@ -26358,11 +25478,6 @@ xmlchars@^2.2.0: resolved "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb" integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw== -xmldom@^0.6.0: - version "0.6.0" - resolved "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz#43a96ecb8beece991cef382c08397d82d4d0c46f" - integrity sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg== - xmlhttprequest-ssl@~1.6.2: version "1.6.3" resolved "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.6.3.tgz#03b713873b01659dfa2c1c5d056065b27ddc2de6" @@ -26457,7 +25572,7 @@ yargs-parser@^3.2.0: camelcase "^3.0.0" lodash.assign "^4.1.0" -yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.1: +yargs@^15.1.0, yargs@^15.3.1: version "15.4.1" resolved "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== From 37f2d5be527d06fd730b902e872b7e7ef4b1f66a Mon Sep 17 00:00:00 2001 From: Talita Gregory Nunes Freire Date: Fri, 22 Apr 2022 18:06:03 +0200 Subject: [PATCH 107/149] chore: remove changes on example app Signed-off-by: Talita Gregory Nunes Freire --- packages/app/src/components/catalog/EntityPage.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index 94713b66d8..799fe3e4d9 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -137,7 +137,6 @@ import { EntityNewRelicDashboardCard, } from '@backstage/plugin-newrelic-dashboard'; import { EntityGoCdContent, isGoCdAvailable } from '@backstage/plugin-gocd'; -import { TeamPullRequestsPage } from '@backstage/plugin-github-pull-requests-board'; import React, { ReactNode, useMemo, useState } from 'react'; @@ -624,9 +623,6 @@ const groupPage = ( - - - ); From 09286fefa77a971fc44f6f42db7b01696b41ddb4 Mon Sep 17 00:00:00 2001 From: Talita Gregory Nunes Freire Date: Fri, 22 Apr 2022 20:24:42 +0200 Subject: [PATCH 108/149] feat: logo and plugin information added Signed-off-by: Talita Gregory Nunes Freire --- .../plugins/github-pull-requests-board.yaml | 9 ++++++ .../img/github-pull-requests-board-logo.svg | 30 +++++++++++++++++++ plugins/github-pull-requests-board/README.md | 4 +-- 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 microsite/data/plugins/github-pull-requests-board.yaml create mode 100644 microsite/static/img/github-pull-requests-board-logo.svg diff --git a/microsite/data/plugins/github-pull-requests-board.yaml b/microsite/data/plugins/github-pull-requests-board.yaml new file mode 100644 index 0000000000..90feb84010 --- /dev/null +++ b/microsite/data/plugins/github-pull-requests-board.yaml @@ -0,0 +1,9 @@ +--- +title: Github Pull Requests Board +author: DAZN +authorUrl: https://engineering.dazn.com/ +category: Source Control Mgmt +description: View All open GitHub pull requests owned by your team in Backstage. +documentation: https://github.com/backstage/backstage/tree/master/plugins/github-pull-requests-board +iconUrl: img/github-pull-requests-board-logo.svg +npmPackageName: '@backstage/plugin-github-pull-requests-board' \ No newline at end of file diff --git a/microsite/static/img/github-pull-requests-board-logo.svg b/microsite/static/img/github-pull-requests-board-logo.svg new file mode 100644 index 0000000000..0114b50d08 --- /dev/null +++ b/microsite/static/img/github-pull-requests-board-logo.svg @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/plugins/github-pull-requests-board/README.md b/plugins/github-pull-requests-board/README.md index 6205a34d95..26691e838d 100644 --- a/plugins/github-pull-requests-board/README.md +++ b/plugins/github-pull-requests-board/README.md @@ -12,7 +12,7 @@ It will help you and your team stay on top of open pull requests, hopefully redu ## Getting started -The plugin exports the **TeamPullRequestsBoard** component which can be added to the Overview page ot the team at `backstage/packages/app/src/components/catalog/EntityPage.tsx` +The plugin exports the **TeamPullRequestsBoard** component which can be added to the Overview page of the team at `backstage/packages/app/src/components/catalog/EntityPage.tsx` ```javascript import { TeamPullRequestsBoard } from '@backstage/plugin-github-pull-requests-board'; @@ -43,7 +43,7 @@ const groupPage = ( ); ``` -Or you can also import the **TeamPullRequestsPage** component which can be used to add a new page on the group page at `backstage/packages/app/src/components/catalog/EntityPage.tsx` +Or you can also import the **TeamPullRequestsPage** component which can be used to add a new tab under the group page at `backstage/packages/app/src/components/catalog/EntityPage.tsx` ```javascript import { TeamPullRequestsPage } from '@backstage/plugin-github-pull-requests-board'; From 4a6ffbf3adc3629f141b702952b9ddbf816687b7 Mon Sep 17 00:00:00 2001 From: Talita Gregory Nunes Freire Date: Wed, 27 Apr 2022 11:15:26 +0200 Subject: [PATCH 109/149] feat: add support for custom github apiBaseUrl Signed-off-by: Talita Gregory Nunes Freire --- .../src/api/useOctokitGraphQl.ts | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts b/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts index 2460588987..91ce559ed2 100644 --- a/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts +++ b/plugins/github-pull-requests-board/src/api/useOctokitGraphQl.ts @@ -14,23 +14,34 @@ * limitations under the License. */ import { Octokit } from '@octokit/rest'; -import { useApi, githubAuthApiRef } from '@backstage/core-plugin-api'; +import { + useApi, + githubAuthApiRef, + configApiRef, +} from '@backstage/core-plugin-api'; +import { readGitHubIntegrationConfigs } from '@backstage/integration'; let octokit: any; export const useOctokitGraphQl = () => { const auth = useApi(githubAuthApiRef); + const config = useApi(configApiRef); + + const baseUrl = readGitHubIntegrationConfigs( + config.getOptionalConfigArray('providers.github') ?? [], + )[0].apiBaseUrl; return (path: string, options?: any): Promise => - auth.getAccessToken(['repo']) + auth + .getAccessToken(['repo']) .then((token: string) => { - if(!octokit) { - octokit = new Octokit({ auth: token }) + if (!octokit) { + octokit = new Octokit({ auth: token, ...(baseUrl && { baseUrl }) }); } - return octokit + return octokit; }) .then(octokitInstance => { - return octokitInstance.graphql(path, options) + return octokitInstance.graphql(path, options); }); }; From 18664eaa525024285e4298d40f4286301298873e Mon Sep 17 00:00:00 2001 From: Talita Gregory Nunes Freire Date: Wed, 27 Apr 2022 11:20:43 +0200 Subject: [PATCH 110/149] fix: variable name typo, dependencies and pr card rendering Signed-off-by: Talita Gregory Nunes Freire --- .../github-pull-requests-board/package.json | 3 +- .../TeamPullRequestsBoard.tsx | 81 +++++++++--------- .../TeamPullRequestsPage.tsx | 82 ++++++++----------- .../src/components/icons/DraftPr/DraftPr.tsx | 11 ++- .../src/utils/functions.ts | 77 +++++++++-------- 5 files changed, 129 insertions(+), 125 deletions(-) diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json index 7497bff479..5e76257d5d 100644 --- a/plugins/github-pull-requests-board/package.json +++ b/plugins/github-pull-requests-board/package.json @@ -38,12 +38,13 @@ "@backstage/catalog-model": "^1.0.1", "@backstage/core-components": "^0.9.3", "@backstage/core-plugin-api": "^1.0.1", + "@backstage/integration": "^1.1.0", "@backstage/plugin-catalog-react": "^1.0.1", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", "@material-ui/lab": "4.0.0-alpha.57", - "@octokit/rest": "^18.6.7", + "@octokit/rest": "^18.12.0", "moment": "^2.29.1", "react-use": "^17.2.4" }, diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/TeamPullRequestsBoard.tsx b/plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/TeamPullRequestsBoard.tsx index f0f47c6a16..8aee285d36 100644 --- a/plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/TeamPullRequestsBoard.tsx +++ b/plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/TeamPullRequestsBoard.tsx @@ -25,30 +25,31 @@ import { Wrapper } from '../Wrapper'; import { PullRequestCard } from '../PullRequestCard'; import { usePullRequestsByTeam } from '../../hooks/usePullRequestsByTeam'; import { PRCardFormating } from '../../utils/types'; -import { DraftPrIcon } from '../icons/DraftPr' +import { DraftPrIcon } from '../icons/DraftPr'; import { useUserRepositories } from '../../hooks/useUserRepositories'; const TeamPullRequestsBoard: FunctionComponent = () => { const [infoCardFormat, setInfoCardFormat] = useState([]); const { repositories } = useUserRepositories(); - const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam(repositories); + const { loading, pullRequests, refreshPullRequests } = + usePullRequestsByTeam(repositories); const header = ( setInfoCardFormat(newFormats)} + onClickOption={newFormats => setInfoCardFormat(newFormats)} value={infoCardFormat} options={[ { icon: , value: 'draft', - ariaLabel: 'Show draft PRs' + ariaLabel: 'Show draft PRs', }, { icon: , value: 'fullscreen', - ariaLabel: 'Info card is set to fullscreen' - } + ariaLabel: 'Info card is set to fullscreen', + }, ]} /> @@ -67,44 +68,36 @@ const TeamPullRequestsBoard: FunctionComponent = () => { key={columnTitle} fullscreen={infoCardFormat.includes('fullscreen')} > - - {columnTitle} - - {content.map(({ - id, - title, - createdAt, - lastEditedAt, - author, - url, - latestReviews, - repository, - isDraft - }, index) => ( - isDraft ? (infoCardFormat.includes('draft') === isDraft) && - - : - ))} + {columnTitle} + {content.map( + ( + { + id, + title, + createdAt, + lastEditedAt, + author, + url, + latestReviews, + repository, + isDraft, + }, + index, + ) => + infoCardFormat.includes('draft') === isDraft && ( + + ), + )} )) ) : ( diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx b/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx index be388e24e9..8761c55682 100644 --- a/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx +++ b/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx @@ -23,24 +23,25 @@ import { Wrapper } from '../../components/Wrapper'; import { PullRequestCard } from '../../components/PullRequestCard'; import { usePullRequestsByTeam } from '../../hooks/usePullRequestsByTeam'; import { PRCardFormating } from '../../utils/types'; -import { DraftPrIcon } from '../../components/icons/DraftPr' +import { DraftPrIcon } from '../../components/icons/DraftPr'; import { useUserRepositories } from '../../hooks/useUserRepositories'; const TeamPullRequestsPage: FunctionComponent = () => { const [infoCardFormat, setInfoCardFormat] = useState([]); const { repositories } = useUserRepositories(); - const { loading, pullRequests, refreshPullRequests } = usePullRequestsByTeam(repositories); + const { loading, pullRequests, refreshPullRequests } = + usePullRequestsByTeam(repositories); const header = ( setInfoCardFormat(newFormats)} + onClickOption={newFormats => setInfoCardFormat(newFormats)} value={infoCardFormat} options={[ { icon: , value: 'draft', - ariaLabel: 'Show draft PRs' + ariaLabel: 'Show draft PRs', }, ]} /> @@ -56,48 +57,37 @@ const TeamPullRequestsPage: FunctionComponent = () => { {pullRequests.length ? ( pullRequests.map(({ title: columnTitle, content }) => ( - - - {columnTitle} - - {content.map(({ - id, - title, - createdAt, - lastEditedAt, - author, - url, - latestReviews, - repository, - isDraft - }, index) => ( - isDraft ? (infoCardFormat.includes('draft') === isDraft) && - - : - ))} + + {columnTitle} + {content.map( + ( + { + id, + title, + createdAt, + lastEditedAt, + author, + url, + latestReviews, + repository, + isDraft, + }, + index, + ) => + infoCardFormat.includes('draft') === isDraft && ( + + ), + )} )) ) : ( diff --git a/plugins/github-pull-requests-board/src/components/icons/DraftPr/DraftPr.tsx b/plugins/github-pull-requests-board/src/components/icons/DraftPr/DraftPr.tsx index eca407dee8..6dc5e306c1 100644 --- a/plugins/github-pull-requests-board/src/components/icons/DraftPr/DraftPr.tsx +++ b/plugins/github-pull-requests-board/src/components/icons/DraftPr/DraftPr.tsx @@ -16,8 +16,15 @@ import React from 'react'; const DraftPr = () => ( - {title} - - + + Created at: {getElapsedTime(createdAt)} - { - updatedAt && ( - - Last update: {getElapsedTime(updatedAt)} - - ) - } + {updatedAt && ( + + Last update: {getElapsedTime(updatedAt)} + + )} - ); }; diff --git a/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx b/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx index 47bab88693..9ba94dfa75 100644 --- a/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx +++ b/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx @@ -18,23 +18,23 @@ import { Typography, Box, IconButton } from '@material-ui/core'; import RefreshIcon from '@material-ui/icons/Refresh'; type Props = { - onRefresh: () => void; -} + onRefresh: () => void; +}; const InfoCardHeader = (props: PropsWithChildren) => { - const { children, onRefresh } = props; + const { children, onRefresh } = props; - return ( - - - Open pull requests - - - - - {children} - - ); + return ( + + + Open pull requests + + + + + {children} + + ); }; export default InfoCardHeader; diff --git a/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx b/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx index e914b774b3..6390b36aae 100644 --- a/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx +++ b/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx @@ -22,13 +22,13 @@ type Option = { icon: ReactNode; value: string; ariaLabel: string; -} +}; type Props = { value: string[]; onClickOption: (selectedOptions: PRCardFormating[]) => void; options: Option[]; -} +}; const PullRequestBoardOptions = (props: Props) => { const { value, onClickOption, options } = props; @@ -39,19 +39,20 @@ const PullRequestBoardOptions = (props: Props) => { onChange={(_event, selectedOptions) => onClickOption(selectedOptions)} aria-label="Pull Request board settings" > - { - options.map(({ icon, value: toggleValue, ariaLabel }, index) => ( - - - - {icon} - - - - )) - } + {options.map(({ icon, value: toggleValue, ariaLabel }, index) => ( + + + + {icon} + + + + ))} - ); }; diff --git a/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx b/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx index 2884f728d3..7e1596f5c2 100644 --- a/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx +++ b/plugins/github-pull-requests-board/src/components/PullRequestCard/PullRequestCard.tsx @@ -14,7 +14,11 @@ * limitations under the License. */ import React, { FunctionComponent } from 'react'; -import { getApprovedReviews, getChangeRequests, getCommentedReviews } from '../../utils/functions'; +import { + getApprovedReviews, + getChangeRequests, + getCommentedReviews, +} from '../../utils/functions'; import { Reviews, Author } from '../../utils/types'; import { Card } from '../Card'; import { UserHeaderList } from '../UserHeaderList'; @@ -28,7 +32,7 @@ type Props = { reviews: Reviews; repositoryName: string; isDraft: boolean; -} +}; const PullRequestCard: FunctionComponent = (props: Props) => { const { @@ -59,16 +63,26 @@ const PullRequestCard: FunctionComponent = (props: Props) => { prUrl={url} > {!!approvedReviews.length && ( - reviewAuthor)} /> + reviewAuthor, + )} + /> )} {!!commentsReviews.length && ( reviewAuthor)} + label="💬" + users={commentsReviews.map( + ({ author: reviewAuthor }) => reviewAuthor, + )} /> )} {!!changeRequests.length && ( - reviewAuthor)} /> + reviewAuthor)} + /> )} ); diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/index.ts b/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/index.ts index b58aed2482..b2f3619eaf 100644 --- a/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/index.ts +++ b/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/index.ts @@ -1 +1,16 @@ +/* + * 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 { default as TeamPullRequestsPage } from './TeamPullRequestsPage'; diff --git a/plugins/github-pull-requests-board/src/components/UserHeader/UserHeader.tsx b/plugins/github-pull-requests-board/src/components/UserHeader/UserHeader.tsx index 7860994e31..f78ca44f66 100644 --- a/plugins/github-pull-requests-board/src/components/UserHeader/UserHeader.tsx +++ b/plugins/github-pull-requests-board/src/components/UserHeader/UserHeader.tsx @@ -14,24 +14,19 @@ * limitations under the License. */ import React from 'react'; -import { - Typography, - Box, - Avatar, - makeStyles -} from '@material-ui/core'; +import { Typography, Box, Avatar, makeStyles } from '@material-ui/core'; type Props = { name: string; avatar?: string; -} +}; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles(theme => ({ small: { width: theme.spacing(4), height: theme.spacing(4), - marginLeft: theme.spacing(1) - } + marginLeft: theme.spacing(1), + }, })); const UserHeader = (props: Props) => { @@ -39,8 +34,8 @@ const UserHeader = (props: Props) => { const classes = useStyles(); return ( - - + + {name} diff --git a/plugins/github-pull-requests-board/src/components/UserHeaderList/UserHeaderList.tsx b/plugins/github-pull-requests-board/src/components/UserHeaderList/UserHeaderList.tsx index 3faf2c519c..8bddeb7433 100644 --- a/plugins/github-pull-requests-board/src/components/UserHeaderList/UserHeaderList.tsx +++ b/plugins/github-pull-requests-board/src/components/UserHeaderList/UserHeaderList.tsx @@ -23,15 +23,23 @@ import { Author } from '../../utils/types'; type Props = { label?: string; users: Author[]; -} +}; const UserHeaderList = (props: Props) => { const { users, label } = props; return ( - - {label && {label}} - {filterSameUser(users).map(({ login, avatarUrl }) => )} + + {label && {label}} + {filterSameUser(users).map(({ login, avatarUrl }) => ( + + ))} ); }; diff --git a/plugins/github-pull-requests-board/src/components/Wrapper/Wrapper.tsx b/plugins/github-pull-requests-board/src/components/Wrapper/Wrapper.tsx index cb6e66fab8..18b47eafbf 100644 --- a/plugins/github-pull-requests-board/src/components/Wrapper/Wrapper.tsx +++ b/plugins/github-pull-requests-board/src/components/Wrapper/Wrapper.tsx @@ -18,18 +18,18 @@ import { Grid, Box } from '@material-ui/core'; type Props = { fullscreen: boolean; -} +}; const Wrapper = (props: PropsWithChildren) => { const { children, fullscreen } = props; return ( - + {children} - ) + ); }; export default Wrapper; diff --git a/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx b/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx index ce2636d5a2..57542d096d 100644 --- a/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx +++ b/plugins/github-pull-requests-board/src/hooks/usePullRequestsByTeam.tsx @@ -25,39 +25,42 @@ export function usePullRequestsByTeam(repositories: string[]) { const getPullRequests = useGetPullRequestsFromRepository(); const getPullRequestDetails = useGetPullRequestDetails(); - const getPRsPerRepository = useCallback(async (repository: string): Promise => { + const getPRsPerRepository = useCallback( + async (repository: string): Promise => { + const pullRequestsNumbers = await getPullRequests(repository); - const pullRequestsNumbers = await getPullRequests(repository) + const pullRequestsWithDetails = await Promise.all( + pullRequestsNumbers.map(async ({ node }) => { + const pullRequest = await getPullRequestDetails( + repository, + node.number, + ); - const pullRequestsWithDetails = await Promise.all( - pullRequestsNumbers.map(async ({ node }) => { - const pullRequest = await getPullRequestDetails( - repository, - node.number, - ); + return pullRequest; + }), + ); - return pullRequest; - }), - ); - - return pullRequestsWithDetails; - }, [getPullRequests, getPullRequestDetails]); + return pullRequestsWithDetails; + }, + [getPullRequests, getPullRequestDetails], + ); const getPRsFromTeam = useCallback( async (teamRepositories: string[]): Promise => { - const teamRepositoriesPromises = teamRepositories.map(repository => getPRsPerRepository(repository), ); - const teamPullRequests = await Promise.allSettled(teamRepositoriesPromises) - .then(promises => promises.reduce((acc, curr) => { + const teamPullRequests = await Promise.allSettled( + teamRepositoriesPromises, + ).then(promises => + promises.reduce((acc, curr) => { if (curr.status === 'fulfilled') { return [...acc, ...curr.value]; } return acc; - }, [] as PullRequests) - ); + }, [] as PullRequests), + ); return teamPullRequests; }, @@ -70,11 +73,10 @@ export function usePullRequestsByTeam(repositories: string[]) { const teamPullRequests = await getPRsFromTeam(repositories); setPullRequests(formatPRsByReviewDecision(teamPullRequests)); setLoading(false); - }, [getPRsFromTeam, repositories]); useEffect(() => { - getAllPullRequests() + getAllPullRequests(); }, [getAllPullRequests]); return { diff --git a/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx b/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx index f974e7e457..68c4af96e4 100644 --- a/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx +++ b/plugins/github-pull-requests-board/src/hooks/useUserRepositories.tsx @@ -33,14 +33,14 @@ export function useUserRepositories() { }); const entitiesNames: string[] = entitiesList.items.map(componentEntity => - getProjectNameFromEntity(componentEntity) + getProjectNameFromEntity(componentEntity), ); setRepositories([...new Set(entitiesNames)]); }, [catalogApi, teamEntity?.metadata?.name]); useEffect(() => { - getRepositoriesNames() + getRepositoriesNames(); }, [getRepositoriesNames]); return { diff --git a/plugins/github-pull-requests-board/src/plugin.ts b/plugins/github-pull-requests-board/src/plugin.ts index 8d45761def..e302d138fb 100644 --- a/plugins/github-pull-requests-board/src/plugin.ts +++ b/plugins/github-pull-requests-board/src/plugin.ts @@ -43,7 +43,9 @@ export const TeamPullRequestsPage = githubPullRequestsBoardPlugin.provide( createRoutableExtension({ name: 'PullRequestPage', component: () => - import('./components/TeamPullRequestsPage').then(m => m.TeamPullRequestsPage), + import('./components/TeamPullRequestsPage').then( + m => m.TeamPullRequestsPage, + ), mountPoint: rootRouteRef, }), ); diff --git a/plugins/github-pull-requests-board/src/utils/constants.ts b/plugins/github-pull-requests-board/src/utils/constants.ts index 22b23b259d..525fd9e1ce 100644 --- a/plugins/github-pull-requests-board/src/utils/constants.ts +++ b/plugins/github-pull-requests-board/src/utils/constants.ts @@ -14,7 +14,7 @@ * limitations under the License. */ export const COLUMNS = Object.freeze({ - REVIEW_REQUIRED: '🔍 Review required', - REVIEW_IN_PROGRESS: '📝 Review in progress', - APPROVED: '👍 Approved' -}) + REVIEW_REQUIRED: '🔍 Review required', + REVIEW_IN_PROGRESS: '📝 Review in progress', + APPROVED: '👍 Approved', +}); diff --git a/plugins/github-pull-requests-board/src/utils/types.tsx b/plugins/github-pull-requests-board/src/utils/types.tsx index b8ae1058f0..a2b121a1b8 100644 --- a/plugins/github-pull-requests-board/src/utils/types.tsx +++ b/plugins/github-pull-requests-board/src/utils/types.tsx @@ -15,31 +15,31 @@ */ export type GraphQlPullRequest = { repository: { - pullRequest: T - } -} + pullRequest: T; + }; +}; export type GraphQlPullRequests = { repository: { pullRequests: { - edges: T - } - } -} + edges: T; + }; + }; +}; export type PullRequestsNumber = { node: { number: number; - } -} + }; +}; export type Review = { state: - | 'PENDING' - | 'COMMENTED' - | 'APPROVED' - | 'CHANGES_REQUESTED' - | 'DISMISSED'; + | 'PENDING' + | 'COMMENTED' + | 'APPROVED' + | 'CHANGES_REQUESTED' + | 'DISMISSED'; author: Author; }; @@ -69,7 +69,7 @@ export type PullRequest = { reviewDecision: ReviewDecision | null; isDraft: boolean; createdAt: string; - author: Author + author: Author; }; export type PullRequests = PullRequest[]; @@ -81,4 +81,4 @@ export type PullRequestsColumn = { export type PRCardFormating = 'compacted' | 'fullscreen' | 'draft'; -export type ReviewDecision = 'IN_PROGRESS' | 'APPROVED' | 'REVIEW_REQUIRED' \ No newline at end of file +export type ReviewDecision = 'IN_PROGRESS' | 'APPROVED' | 'REVIEW_REQUIRED'; From 4a9fc4329887798ce2bb3c0f5051808e186c91a9 Mon Sep 17 00:00:00 2001 From: Talita Gregory Nunes Freire Date: Sat, 7 May 2022 11:15:41 +0200 Subject: [PATCH 114/149] fix: added missing FunctionComponent types Signed-off-by: Talita Gregory Nunes Freire --- .../src/components/Card/CardHeader.tsx | 4 ++-- .../src/components/InfoCardHeader/InfoCardHeader.tsx | 6 ++++-- .../PullRequestBoardOptions/PullRequestBoardOptions.tsx | 4 ++-- .../src/components/UserHeader/UserHeader.tsx | 4 ++-- .../src/components/UserHeaderList/UserHeaderList.tsx | 4 ++-- .../src/components/Wrapper/Wrapper.tsx | 4 ++-- .../src/components/icons/DraftPr/DraftPr.tsx | 4 ++-- 7 files changed, 16 insertions(+), 14 deletions(-) diff --git a/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx b/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx index b1259ec993..831423a464 100644 --- a/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx +++ b/plugins/github-pull-requests-board/src/components/Card/CardHeader.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { FunctionComponent } from 'react'; import { Typography, Box } from '@material-ui/core'; import { getElapsedTime } from '../../utils/functions'; import { UserHeader } from '../UserHeader'; @@ -27,7 +27,7 @@ type Props = { repositoryName: string; }; -const CardHeader = (props: Props) => { +const CardHeader: FunctionComponent = (props: Props) => { const { title, createdAt, diff --git a/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx b/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx index 9ba94dfa75..8c126bcaa4 100644 --- a/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx +++ b/plugins/github-pull-requests-board/src/components/InfoCardHeader/InfoCardHeader.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { PropsWithChildren } from 'react'; +import React, { PropsWithChildren, FunctionComponent } from 'react'; import { Typography, Box, IconButton } from '@material-ui/core'; import RefreshIcon from '@material-ui/icons/Refresh'; @@ -21,7 +21,9 @@ type Props = { onRefresh: () => void; }; -const InfoCardHeader = (props: PropsWithChildren) => { +const InfoCardHeader: FunctionComponent = ( + props: PropsWithChildren, +) => { const { children, onRefresh } = props; return ( diff --git a/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx b/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx index 6390b36aae..6938210e68 100644 --- a/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx +++ b/plugins/github-pull-requests-board/src/components/PullRequestBoardOptions/PullRequestBoardOptions.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { ReactNode } from 'react'; +import React, { ReactNode, FunctionComponent } from 'react'; import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab'; import { Tooltip, Box } from '@material-ui/core'; import { PRCardFormating } from '../../utils/types'; @@ -30,7 +30,7 @@ type Props = { options: Option[]; }; -const PullRequestBoardOptions = (props: Props) => { +const PullRequestBoardOptions: FunctionComponent = (props: Props) => { const { value, onClickOption, options } = props; return ( ({ }, })); -const UserHeader = (props: Props) => { +const UserHeader: FunctionComponent = (props: Props) => { const { name, avatar } = props; const classes = useStyles(); diff --git a/plugins/github-pull-requests-board/src/components/UserHeaderList/UserHeaderList.tsx b/plugins/github-pull-requests-board/src/components/UserHeaderList/UserHeaderList.tsx index 8bddeb7433..a2c52a6d5e 100644 --- a/plugins/github-pull-requests-board/src/components/UserHeaderList/UserHeaderList.tsx +++ b/plugins/github-pull-requests-board/src/components/UserHeaderList/UserHeaderList.tsx @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { FunctionComponent } from 'react'; import { Typography, Box } from '@material-ui/core'; import { filterSameUser } from '../../utils/functions'; @@ -25,7 +25,7 @@ type Props = { users: Author[]; }; -const UserHeaderList = (props: Props) => { +const UserHeaderList: FunctionComponent = (props: Props) => { const { users, label } = props; return ( diff --git a/plugins/github-pull-requests-board/src/components/Wrapper/Wrapper.tsx b/plugins/github-pull-requests-board/src/components/Wrapper/Wrapper.tsx index 18b47eafbf..6d8f17cb72 100644 --- a/plugins/github-pull-requests-board/src/components/Wrapper/Wrapper.tsx +++ b/plugins/github-pull-requests-board/src/components/Wrapper/Wrapper.tsx @@ -13,14 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { PropsWithChildren } from 'react'; +import React, { PropsWithChildren, FunctionComponent } from 'react'; import { Grid, Box } from '@material-ui/core'; type Props = { fullscreen: boolean; }; -const Wrapper = (props: PropsWithChildren) => { +const Wrapper: FunctionComponent = (props: PropsWithChildren) => { const { children, fullscreen } = props; return ( diff --git a/plugins/github-pull-requests-board/src/components/icons/DraftPr/DraftPr.tsx b/plugins/github-pull-requests-board/src/components/icons/DraftPr/DraftPr.tsx index 6dc5e306c1..05d21b21b9 100644 --- a/plugins/github-pull-requests-board/src/components/icons/DraftPr/DraftPr.tsx +++ b/plugins/github-pull-requests-board/src/components/icons/DraftPr/DraftPr.tsx @@ -13,9 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React from 'react'; +import React, { FunctionComponent } from 'react'; -const DraftPr = () => ( +const DraftPr: FunctionComponent = () => ( - + @@ -47,10 +47,10 @@ const groupPage = ( ); ``` -Or you can also import the **TeamPullRequestsPage** component which can be used to add a new tab under the group page at `backstage/packages/app/src/components/catalog/EntityPage.tsx` +Or you can also import the **EntityTeamPullRequestsContent** component which can be used to add a new tab under the group page at `backstage/packages/app/src/components/catalog/EntityPage.tsx` ```javascript -import { TeamPullRequestsPage } from '@backstage/plugin-github-pull-requests-board'; +import { EntityTeamPullRequestsContent } from '@backstage/plugin-github-pull-requests-board'; const groupPage = ( @@ -72,7 +72,7 @@ const groupPage = ( - + ; ) diff --git a/plugins/github-pull-requests-board/api-report.md b/plugins/github-pull-requests-board/api-report.md index 8bded2d581..33814c76b4 100644 --- a/plugins/github-pull-requests-board/api-report.md +++ b/plugins/github-pull-requests-board/api-report.md @@ -7,15 +7,15 @@ import { FunctionComponent } from 'react'; -// Warning: (ae-missing-release-tag) "TeamPullRequestsBoard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "EntityTeamPullRequestsCard" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const TeamPullRequestsBoard: FunctionComponent<{}>; +export const EntityTeamPullRequestsCard: FunctionComponent<{}>; -// Warning: (ae-missing-release-tag) "TeamPullRequestsPage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// Warning: (ae-missing-release-tag) "EntityTeamPullRequestsContent" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const TeamPullRequestsPage: FunctionComponent<{}>; +export const EntityTeamPullRequestsContent: FunctionComponent<{}>; // (No @packageDocumentation comment for this package) ``` diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/TeamPullRequestsBoard.tsx b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx similarity index 97% rename from plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/TeamPullRequestsBoard.tsx rename to plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx index 8aee285d36..b19651536a 100644 --- a/plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/TeamPullRequestsBoard.tsx +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/EntityTeamPullRequestsCard.tsx @@ -28,7 +28,7 @@ import { PRCardFormating } from '../../utils/types'; import { DraftPrIcon } from '../icons/DraftPr'; import { useUserRepositories } from '../../hooks/useUserRepositories'; -const TeamPullRequestsBoard: FunctionComponent = () => { +const EntityTeamPullRequestsCard: FunctionComponent = () => { const [infoCardFormat, setInfoCardFormat] = useState([]); const { repositories } = useUserRepositories(); const { loading, pullRequests, refreshPullRequests } = @@ -110,4 +110,4 @@ const TeamPullRequestsBoard: FunctionComponent = () => { return {getContent()}; }; -export default TeamPullRequestsBoard; +export default EntityTeamPullRequestsCard; diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/index.ts b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/index.ts similarity index 87% rename from plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/index.ts rename to plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/index.ts index c8e25cfff8..bac16f1424 100644 --- a/plugins/github-pull-requests-board/src/components/TeamPullRequestsBoard/index.ts +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsCard/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { default as TeamPullRequestsBoard } from './TeamPullRequestsBoard'; +export { default as EntityTeamPullRequestsCard } from './EntityTeamPullRequestsCard'; diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.tsx similarity index 88% rename from plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx rename to plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.tsx index 8761c55682..33980e2364 100644 --- a/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/TeamPullRequestsPage.tsx +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/EntityTeamPullRequestsContent.tsx @@ -17,16 +17,16 @@ import React, { FunctionComponent, useState } from 'react'; import { Grid, Typography } from '@material-ui/core'; import { Progress, InfoCard } from '@backstage/core-components'; -import { InfoCardHeader } from '../../components/InfoCardHeader'; -import { PullRequestBoardOptions } from '../../components/PullRequestBoardOptions'; -import { Wrapper } from '../../components/Wrapper'; -import { PullRequestCard } from '../../components/PullRequestCard'; +import { InfoCardHeader } from '../InfoCardHeader'; +import { PullRequestBoardOptions } from '../PullRequestBoardOptions'; +import { Wrapper } from '../Wrapper'; +import { PullRequestCard } from '../PullRequestCard'; import { usePullRequestsByTeam } from '../../hooks/usePullRequestsByTeam'; import { PRCardFormating } from '../../utils/types'; -import { DraftPrIcon } from '../../components/icons/DraftPr'; +import { DraftPrIcon } from '../icons/DraftPr'; import { useUserRepositories } from '../../hooks/useUserRepositories'; -const TeamPullRequestsPage: FunctionComponent = () => { +const EntityTeamPullRequestsContent: FunctionComponent = () => { const [infoCardFormat, setInfoCardFormat] = useState([]); const { repositories } = useUserRepositories(); const { loading, pullRequests, refreshPullRequests } = @@ -100,4 +100,4 @@ const TeamPullRequestsPage: FunctionComponent = () => { return {getContent()}; }; -export default TeamPullRequestsPage; +export default EntityTeamPullRequestsContent; diff --git a/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/index.ts b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/index.ts similarity index 86% rename from plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/index.ts rename to plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/index.ts index b2f3619eaf..c2be57464c 100644 --- a/plugins/github-pull-requests-board/src/components/TeamPullRequestsPage/index.ts +++ b/plugins/github-pull-requests-board/src/components/EntityTeamPullRequestsContent/index.ts @@ -13,4 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { default as TeamPullRequestsPage } from './TeamPullRequestsPage'; +export { default as EntityTeamPullRequestsContent } from './EntityTeamPullRequestsContent'; diff --git a/plugins/github-pull-requests-board/src/index.ts b/plugins/github-pull-requests-board/src/index.ts index 579b19b315..d9cffaa468 100644 --- a/plugins/github-pull-requests-board/src/index.ts +++ b/plugins/github-pull-requests-board/src/index.ts @@ -13,4 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -export { TeamPullRequestsBoard, TeamPullRequestsPage } from './plugin'; +export { + EntityTeamPullRequestsCard, + EntityTeamPullRequestsContent, +} from './plugin'; diff --git a/plugins/github-pull-requests-board/src/plugin.test.ts b/plugins/github-pull-requests-board/src/plugin.test.ts index 0204620ba5..84f5d801e6 100644 --- a/plugins/github-pull-requests-board/src/plugin.test.ts +++ b/plugins/github-pull-requests-board/src/plugin.test.ts @@ -13,13 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TeamPullRequestsBoard, TeamPullRequestsPage } from './plugin'; +import { + EntityTeamPullRequestsCard, + EntityTeamPullRequestsContent, +} from './plugin'; describe('github-pull-requests-board', () => { - it('should export TeamPullRequestsBoard', () => { - expect(TeamPullRequestsBoard).toBeDefined(); + it('should export EntityTeamPullRequestsCard', () => { + expect(EntityTeamPullRequestsCard).toBeDefined(); }); - it('should export TeamPullRequestsPage', () => { - expect(TeamPullRequestsPage).toBeDefined(); + it('should export EntityTeamPullRequestsContent', () => { + expect(EntityTeamPullRequestsContent).toBeDefined(); }); }); diff --git a/plugins/github-pull-requests-board/src/plugin.ts b/plugins/github-pull-requests-board/src/plugin.ts index e302d138fb..c3ed426dec 100644 --- a/plugins/github-pull-requests-board/src/plugin.ts +++ b/plugins/github-pull-requests-board/src/plugin.ts @@ -27,25 +27,26 @@ const githubPullRequestsBoardPlugin = createPlugin({ }, }); -export const TeamPullRequestsBoard = githubPullRequestsBoardPlugin.provide( +export const EntityTeamPullRequestsCard = githubPullRequestsBoardPlugin.provide( createComponentExtension({ - name: 'TeamPullRequestsBoard', + name: 'EntityTeamPullRequestsCard', component: { lazy: () => - import('./components/TeamPullRequestsBoard').then( - m => m.TeamPullRequestsBoard, + import('./components/EntityTeamPullRequestsCard').then( + m => m.EntityTeamPullRequestsCard, ), }, }), ); -export const TeamPullRequestsPage = githubPullRequestsBoardPlugin.provide( - createRoutableExtension({ - name: 'PullRequestPage', - component: () => - import('./components/TeamPullRequestsPage').then( - m => m.TeamPullRequestsPage, - ), - mountPoint: rootRouteRef, - }), -); +export const EntityTeamPullRequestsContent = + githubPullRequestsBoardPlugin.provide( + createRoutableExtension({ + name: 'PullRequestPage', + component: () => + import('./components/EntityTeamPullRequestsContent').then( + m => m.EntityTeamPullRequestsContent, + ), + mountPoint: rootRouteRef, + }), + ); From c25df958cb05d5f63188bbf45cb11375146d71ce Mon Sep 17 00:00:00 2001 From: Talita Gregory Nunes Freire Date: Fri, 27 May 2022 12:23:26 +0200 Subject: [PATCH 124/149] fix: updated dependencies Signed-off-by: Talita Gregory Nunes Freire --- plugins/github-pull-requests-board/package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/github-pull-requests-board/package.json b/plugins/github-pull-requests-board/package.json index d8cd97bab0..402e21f94e 100644 --- a/plugins/github-pull-requests-board/package.json +++ b/plugins/github-pull-requests-board/package.json @@ -36,10 +36,10 @@ }, "dependencies": { "@backstage/catalog-model": "^1.0.2-next.0", - "@backstage/core-components": "^0.9.4", + "@backstage/core-components": "^0.9.5-next.0", "@backstage/core-plugin-api": "^1.0.2", - "@backstage/integration": "^1.2.0-next.0", - "@backstage/plugin-catalog-react": "^1.1.0-next.1", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/plugin-catalog-react": "^1.1.1-next.0", "@backstage/theme": "^0.2.15", "@material-ui/core": "^4.12.2", "@material-ui/icons": "^4.9.1", @@ -49,8 +49,8 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.17.1", - "@backstage/dev-utils": "^1.0.2", + "@backstage/cli": "^0.17.2-next.0", + "@backstage/dev-utils": "^1.0.3-next.0", "@backstage/test-utils": "^1.1.0", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^12.1.3", From 7f108513b8e7c3c1d1f4fbd80a1dcda2042d6a10 Mon Sep 17 00:00:00 2001 From: Alex Crome Date: Fri, 27 May 2022 14:46:15 +0100 Subject: [PATCH 125/149] Log errors when tasks fail * Log errors that occur when running a task * Use a child logger when initalising a new TaskWorker to include the task id with any logs. Signed-off-by: Alex Crome --- .changeset/wise-nails-hang.md | 5 ++++ .../src/tasks/PluginTaskSchedulerImpl.ts | 7 +++++- .../src/tasks/TaskWorker.test.ts | 25 +++++++++++++++++++ .../backend-tasks/src/tasks/TaskWorker.ts | 1 + 4 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 .changeset/wise-nails-hang.md diff --git a/.changeset/wise-nails-hang.md b/.changeset/wise-nails-hang.md new file mode 100644 index 0000000000..cec8c1d397 --- /dev/null +++ b/.changeset/wise-nails-hang.md @@ -0,0 +1,5 @@ +--- +'@backstage/backend-tasks': patch +--- + +Add error logging when a background task throws an error rather than silently swallowing it. diff --git a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts index e43d0c1be6..e57ec93c1c 100644 --- a/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts +++ b/packages/backend-tasks/src/tasks/PluginTaskSchedulerImpl.ts @@ -57,7 +57,12 @@ export class PluginTaskSchedulerImpl implements PluginTaskScheduler { if (scope === 'global') { const knex = await this.databaseFactory(); - const worker = new TaskWorker(task.id, task.fn, knex, this.logger); + const worker = new TaskWorker( + task.id, + task.fn, + knex, + this.logger.child({ task: task.id }), + ); await worker.start( { diff --git a/packages/backend-tasks/src/tasks/TaskWorker.test.ts b/packages/backend-tasks/src/tasks/TaskWorker.test.ts index 14688d0f98..8a1be3e8a9 100644 --- a/packages/backend-tasks/src/tasks/TaskWorker.test.ts +++ b/packages/backend-tasks/src/tasks/TaskWorker.test.ts @@ -119,6 +119,31 @@ describe('TaskWorker', () => { 60_000, ); + it.each(databases.eachSupportedId())( + 'logs error when the task throws, %p', + async databaseId => { + const knex = await databases.init(databaseId); + await migrateBackendTasks(knex); + + jest.spyOn(logger, 'error'); + const fn = jest.fn().mockRejectedValue(new Error('failed')); + const settings: TaskSettingsV2 = { + version: 2, + initialDelayDuration: undefined, + cadence: '* * * * * *', + timeoutAfterDuration: Duration.fromMillis(60000).toISO(), + }; + const checkFrequency = Duration.fromObject({ milliseconds: 100 }); + const worker = new TaskWorker('task1', fn, knex, logger, checkFrequency); + worker.start(settings); + + await waitForExpect(() => { + expect(logger.error).toBeCalled(); + }); + }, + 60_000, + ); + it.each(databases.eachSupportedId())( 'runs tasks more than once even when the task throws, %p', async databaseId => { diff --git a/packages/backend-tasks/src/tasks/TaskWorker.ts b/packages/backend-tasks/src/tasks/TaskWorker.ts index 07e1cd90ec..121a717848 100644 --- a/packages/backend-tasks/src/tasks/TaskWorker.ts +++ b/packages/backend-tasks/src/tasks/TaskWorker.ts @@ -145,6 +145,7 @@ export class TaskWorker { await this.fn(taskAbortController.signal); taskAbortController.abort(); // releases resources } catch (e) { + this.logger.error(e); await this.tryReleaseTask(ticket, taskSettings); return { result: 'failed' }; } finally { From 281cec1b61ae2d7fbfb685a8f5534a5edfb283c7 Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Wed, 25 May 2022 16:55:56 -0400 Subject: [PATCH 126/149] fix(elasticSearch): use more precise matching for query filters Signed-off-by: Phil Kuang --- .changeset/search-eight-hounds-worry.md | 5 ++++ .../engines/ElasticSearchSearchEngine.test.ts | 23 +++++++++++++++---- .../src/engines/ElasticSearchSearchEngine.ts | 4 +++- 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 .changeset/search-eight-hounds-worry.md diff --git a/.changeset/search-eight-hounds-worry.md b/.changeset/search-eight-hounds-worry.md new file mode 100644 index 0000000000..dcce19e65d --- /dev/null +++ b/.changeset/search-eight-hounds-worry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-elasticsearch': patch +--- + +Use more precise matching for query filters diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts index 24ceec2875..d3ca3481ff 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.test.ts @@ -153,7 +153,7 @@ describe('ElasticSearchSearchEngine', () => { }, filter: { match: { - kind: 'testKind', + 'kind.keyword': 'testKind', }, }, }, @@ -204,7 +204,12 @@ describe('ElasticSearchSearchEngine', () => { const actualTranslatedQuery = translatorUnderTest({ types: ['indexName'], term: 'testTerm', - filters: { kind: 'testKind', namespace: 'testNameSpace' }, + filters: { + kind: 'testKind', + namespace: 'testNameSpace', + foo: 123, + bar: true, + }, }) as ConcreteElasticSearchQuery; expect(actualTranslatedQuery).toMatchObject({ @@ -228,12 +233,22 @@ describe('ElasticSearchSearchEngine', () => { filter: [ { match: { - kind: 'testKind', + 'kind.keyword': 'testKind', }, }, { match: { - namespace: 'testNameSpace', + 'namespace.keyword': 'testNameSpace', + }, + }, + { + match: { + foo: '123', + }, + }, + { + match: { + bar: 'true', }, }, ], diff --git a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts index 00c25905c3..70b085e5a7 100644 --- a/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts +++ b/plugins/search-backend-module-elasticsearch/src/engines/ElasticSearchSearchEngine.ts @@ -168,7 +168,9 @@ export class ElasticSearchSearchEngine implements SearchEngine { .filter(([_, value]) => Boolean(value)) .map(([key, value]: [key: string, value: any]) => { if (['string', 'number', 'boolean'].includes(typeof value)) { - return esb.matchQuery(key, value.toString()); + // Use exact matching for string datatype fields + const keyword = typeof value === 'string' ? `${key}.keyword` : key; + return esb.matchQuery(keyword, value.toString()); } if (Array.isArray(value)) { return esb From 1dffa7dd4ded230cf080a4d08f5b47ee01d44506 Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Fri, 22 Apr 2022 19:13:09 +0200 Subject: [PATCH 127/149] feat: add `bitbucket-cloud-common` with new client Add `bitbucket-cloud-common` (`@backstage/plugin-bitbucket-cloud-common`) with a new client for Bitbucket Cloud. The lib contains auto-generated models which got generated using `@openapitools/openapi-generator-cli`. Signed-off-by: Patrick Jungermann --- .changeset/slow-apes-appear.md | 10 + plugins/bitbucket-cloud-common/.eslintrc.js | 1 + plugins/bitbucket-cloud-common/README.md | 64 + plugins/bitbucket-cloud-common/api-report.md | 386 + .../bitbucket-cloud.oas.json | 25555 ++++++++++++++++ .../bitbucket-cloud-common/openapitools.json | 22 + plugins/bitbucket-cloud-common/package.json | 43 + .../scripts/adjust-models.js | 115 + .../scripts/generate-models.sh | 25 + .../scripts/prepare-schema.js | 279 + .../scripts/reduce-models.js | 100 + .../src/.openapi-generator-ignore | 9 + .../src/.openapi-generator/FILES | 1 + .../src/.openapi-generator/VERSION | 1 + .../src/BitbucketCloudClient.test.ts | 110 + .../src/BitbucketCloudClient.ts | 120 + plugins/bitbucket-cloud-common/src/index.ts | 26 + .../src/models/index.ts | 532 + .../src/pagination.test.ts | 124 + .../bitbucket-cloud-common/src/pagination.ts | 66 + .../bitbucket-cloud-common/src/setupTests.ts | 16 + plugins/bitbucket-cloud-common/src/types.ts | 35 + .../templates/licenseInfo.mustache | 11 + .../templates/modelGenericInterfaces.mustache | 61 + .../templates/models.index.mustache | 47 + yarn.lock | 249 +- 26 files changed, 27972 insertions(+), 36 deletions(-) create mode 100644 .changeset/slow-apes-appear.md create mode 100644 plugins/bitbucket-cloud-common/.eslintrc.js create mode 100644 plugins/bitbucket-cloud-common/README.md create mode 100644 plugins/bitbucket-cloud-common/api-report.md create mode 100644 plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json create mode 100644 plugins/bitbucket-cloud-common/openapitools.json create mode 100644 plugins/bitbucket-cloud-common/package.json create mode 100755 plugins/bitbucket-cloud-common/scripts/adjust-models.js create mode 100755 plugins/bitbucket-cloud-common/scripts/generate-models.sh create mode 100755 plugins/bitbucket-cloud-common/scripts/prepare-schema.js create mode 100755 plugins/bitbucket-cloud-common/scripts/reduce-models.js create mode 100644 plugins/bitbucket-cloud-common/src/.openapi-generator-ignore create mode 100644 plugins/bitbucket-cloud-common/src/.openapi-generator/FILES create mode 100644 plugins/bitbucket-cloud-common/src/.openapi-generator/VERSION create mode 100644 plugins/bitbucket-cloud-common/src/BitbucketCloudClient.test.ts create mode 100644 plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts create mode 100644 plugins/bitbucket-cloud-common/src/index.ts create mode 100644 plugins/bitbucket-cloud-common/src/models/index.ts create mode 100644 plugins/bitbucket-cloud-common/src/pagination.test.ts create mode 100644 plugins/bitbucket-cloud-common/src/pagination.ts create mode 100644 plugins/bitbucket-cloud-common/src/setupTests.ts create mode 100644 plugins/bitbucket-cloud-common/src/types.ts create mode 100644 plugins/bitbucket-cloud-common/templates/licenseInfo.mustache create mode 100644 plugins/bitbucket-cloud-common/templates/modelGenericInterfaces.mustache create mode 100644 plugins/bitbucket-cloud-common/templates/models.index.mustache diff --git a/.changeset/slow-apes-appear.md b/.changeset/slow-apes-appear.md new file mode 100644 index 0000000000..8771561893 --- /dev/null +++ b/.changeset/slow-apes-appear.md @@ -0,0 +1,10 @@ +--- +'@backstage/plugin-bitbucket-cloud-common': minor +--- + +Add new common library `bitbucket-cloud-common` with a client for Bitbucket Cloud. + +This client can be reused across all packages and might be the future place for additional +features like managing the rate limits, etc. + +The client itself was generated in parts using the `@openapitools/openapi-generator-cli`. diff --git a/plugins/bitbucket-cloud-common/.eslintrc.js b/plugins/bitbucket-cloud-common/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/bitbucket-cloud-common/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/bitbucket-cloud-common/README.md b/plugins/bitbucket-cloud-common/README.md new file mode 100644 index 0000000000..f4c3bef1e2 --- /dev/null +++ b/plugins/bitbucket-cloud-common/README.md @@ -0,0 +1,64 @@ +# @backstage/plugin-bitbucket-cloud-common + +Welcome to the common package for bitbucket-cloud plugins! + +This common package provides a reusable API client for the Bitbucket Cloud API +which can be reused in catalog-backend-module plugins, scaffolder modules, etc. + +Using a shared client allows to control all traffic going from Backstage to +the Bitbucket Cloud API compared to separate clients or inline API calls. + +We may want to leverage this later to add rate limiting, etc. + +## How to maintain the generated code + +### Update the Models + +This command will + +1. [refresh the schema/OpenAPI Specification](#refresh-the-schema) +2. [re-generate the models](#generate-models) +3. [reduce the models to the minimal needed](#reduce-models) + +### Refresh the schema + +This command will download the latest version of the Bitbucket Cloud OpenAPI Specification +and apply some mutations to fix bugs or improve the schema for a better code generation output. + +```sh +yarn refresh-schema +``` + +### Generate Models + +The models used are created based on the [local OpenAPI Specification file](bitbucket-cloud.oas.json) +using a code generator. +Some post-cleanup is applied to improve the generated output. + +The client itself using the models is not generated. + +```sh +yarn generate-models +``` + +### Reduce Models + +In order to keep the API surface minimal, this command helps to only keep the minimal part of the +generated models by considering all `Models` module members directly or transitively used by the +client implementation. + +```sh +yarn reduce-models +``` + +## Adding a New Client Method + +If you want to add a new method to the client implementation which may use a new endpoint or "new" models +you can + +1. optionally [refresh the schema](#refresh-the-schema) to get the latest version +2. and [generate the models](#generate-models). + +At this point, you have **all** models usable for adding a new method using any of them. + +If you are ready with your addition to the client, you can [reduce the models to the minimal needed](#reduce-models). diff --git a/plugins/bitbucket-cloud-common/api-report.md b/plugins/bitbucket-cloud-common/api-report.md new file mode 100644 index 0000000000..367a47b9e3 --- /dev/null +++ b/plugins/bitbucket-cloud-common/api-report.md @@ -0,0 +1,386 @@ +## API Report File for "@backstage/plugin-bitbucket-cloud-common" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { BitbucketCloudIntegrationConfig } from '@backstage/integration'; + +// @public (undocumented) +export class BitbucketCloudClient { + // (undocumented) + static fromConfig( + config: BitbucketCloudIntegrationConfig, + ): BitbucketCloudClient; + // (undocumented) + listRepositoriesByWorkspace( + workspace: string, + options?: FilterAndSortOptions & PartialResponseOptions, + ): WithPagination; + // (undocumented) + searchCode( + workspace: string, + query: string, + options?: FilterAndSortOptions & PartialResponseOptions, + ): WithPagination; +} + +// @public (undocumented) +export type FilterAndSortOptions = { + q?: string; + sort?: string; +}; + +// @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) + avatar?: Link; + // (undocumented) + followers?: Link; + // (undocumented) + following?: Link; + // (undocumented) + html?: Link; + // (undocumented) + repositories?: Link; + // (undocumented) + self?: Link; + } + export interface Author extends ModelObject { + raw?: string; + // (undocumented) + user?: Account; + } + export interface BaseCommit extends ModelObject { + // (undocumented) + author?: Author; + // (undocumented) + date?: string; + // (undocumented) + hash?: string; + // (undocumented) + message?: string; + // (undocumented) + parents?: Array; + // (undocumented) + summary?: BaseCommitSummary; + } + // (undocumented) + export interface BaseCommitSummary { + html?: string; + markup?: BaseCommitSummaryMarkupEnum; + raw?: string; + } + const BaseCommitSummaryMarkupEnum: { + readonly Markdown: 'markdown'; + readonly Creole: 'creole'; + readonly Plaintext: 'plaintext'; + }; + export type BaseCommitSummaryMarkupEnum = + typeof BaseCommitSummaryMarkupEnum[keyof typeof BaseCommitSummaryMarkupEnum]; + export interface Branch { + default_merge_strategy?: string; + // (undocumented) + links?: RefLinks; + merge_strategies?: Array; + name?: string; + // (undocumented) + target?: Commit; + // (undocumented) + type: string; + } + const BranchMergeStrategiesEnum: { + readonly MergeCommit: 'merge_commit'; + readonly Squash: 'squash'; + readonly FastForward: 'fast_forward'; + }; + export type BranchMergeStrategiesEnum = + typeof BranchMergeStrategiesEnum[keyof typeof BranchMergeStrategiesEnum]; + export interface Commit extends BaseCommit { + // (undocumented) + participants?: Array; + // (undocumented) + repository?: Repository; + } + export interface CommitFile { + // (undocumented) + [key: string]: unknown; + // (undocumented) + attributes?: CommitFileAttributesEnum; + // (undocumented) + commit?: Commit; + escaped_path?: string; + path?: string; + // (undocumented) + type: string; + } + const // (undocumented) + CommitFileAttributesEnum: { + readonly Link: 'link'; + readonly Executable: 'executable'; + readonly Subrepository: 'subrepository'; + readonly Binary: 'binary'; + readonly Lfs: 'lfs'; + }; + // (undocumented) + export type CommitFileAttributesEnum = + typeof CommitFileAttributesEnum[keyof typeof CommitFileAttributesEnum]; + export interface Link { + // (undocumented) + href?: string; + // (undocumented) + name?: string; + } + export interface ModelObject { + // (undocumented) + [key: string]: unknown; + // (undocumented) + type: string; + } + export interface Paginated { + next?: string; + page?: number; + pagelen?: number; + previous?: string; + size?: number; + values?: Array | Set; + } + export interface PaginatedRepositories extends Paginated { + values?: Set; + } + export interface Participant extends ModelObject { + // (undocumented) + approved?: boolean; + participated_on?: string; + // (undocumented) + role?: ParticipantRoleEnum; + // (undocumented) + state?: ParticipantStateEnum; + // (undocumented) + user?: User; + } + const // (undocumented) + ParticipantRoleEnum: { + readonly Participant: 'PARTICIPANT'; + readonly Reviewer: 'REVIEWER'; + }; + // (undocumented) + export type ParticipantRoleEnum = + typeof ParticipantRoleEnum[keyof typeof ParticipantRoleEnum]; + const // (undocumented) + ParticipantStateEnum: { + readonly Approved: 'approved'; + readonly ChangesRequested: 'changes_requested'; + readonly Null: 'null'; + }; + // (undocumented) + export type ParticipantStateEnum = + typeof ParticipantStateEnum[keyof typeof ParticipantStateEnum]; + export interface Project extends ModelObject { + // (undocumented) + created_on?: string; + // (undocumented) + description?: string; + has_publicly_visible_repos?: boolean; + is_private?: boolean; + key?: string; + // (undocumented) + links?: ProjectLinks; + name?: string; + // (undocumented) + owner?: Team; + // (undocumented) + updated_on?: string; + uuid?: string; + } + // (undocumented) + export interface ProjectLinks { + // (undocumented) + avatar?: Link; + // (undocumented) + html?: Link; + } + // (undocumented) + export interface RefLinks { + // (undocumented) + commits?: Link; + // (undocumented) + html?: Link; + // (undocumented) + self?: Link; + } + export interface Repository extends ModelObject { + // (undocumented) + created_on?: string; + // (undocumented) + description?: string; + fork_policy?: RepositoryForkPolicyEnum; + full_name?: string; + // (undocumented) + has_issues?: boolean; + // (undocumented) + has_wiki?: boolean; + // (undocumented) + is_private?: boolean; + // (undocumented) + language?: string; + // (undocumented) + links?: RepositoryLinks; + // (undocumented) + mainbranch?: Branch; + // (undocumented) + name?: string; + // (undocumented) + owner?: Account; + // (undocumented) + parent?: Repository; + // (undocumented) + project?: Project; + // (undocumented) + scm?: RepositoryScmEnum; + // (undocumented) + size?: number; + slug?: string; + // (undocumented) + updated_on?: string; + uuid?: string; + } + const RepositoryForkPolicyEnum: { + readonly AllowForks: 'allow_forks'; + readonly NoPublicForks: 'no_public_forks'; + readonly NoForks: 'no_forks'; + }; + export type RepositoryForkPolicyEnum = + typeof RepositoryForkPolicyEnum[keyof typeof RepositoryForkPolicyEnum]; + const // (undocumented) + RepositoryScmEnum: { + readonly Git: 'git'; + }; + // (undocumented) + export interface RepositoryLinks { + // (undocumented) + avatar?: Link; + // (undocumented) + clone?: Array; + // (undocumented) + commits?: Link; + // (undocumented) + downloads?: Link; + // (undocumented) + forks?: Link; + // (undocumented) + hooks?: Link; + // (undocumented) + html?: Link; + // (undocumented) + pullrequests?: Link; + // (undocumented) + self?: Link; + // (undocumented) + watchers?: Link; + } + // (undocumented) + export type RepositoryScmEnum = + typeof RepositoryScmEnum[keyof typeof RepositoryScmEnum]; + // (undocumented) + export interface SearchCodeSearchResult { + // (undocumented) + readonly content_match_count?: number; + // (undocumented) + readonly content_matches?: Array; + // (undocumented) + file?: CommitFile; + // (undocumented) + readonly path_matches?: Array; + // (undocumented) + readonly type?: string; + } + // (undocumented) + export interface SearchContentMatch { + // (undocumented) + readonly lines?: Array; + } + // (undocumented) + export interface SearchLine { + // (undocumented) + readonly line?: number; + // (undocumented) + readonly segments?: Array; + } + // (undocumented) + export interface SearchResultPage extends Paginated { + // (undocumented) + readonly query_substituted?: boolean; + readonly values?: Array; + } + // (undocumented) + export interface SearchSegment { + // (undocumented) + readonly match?: boolean; + // (undocumented) + readonly text?: string; + } + export interface Team extends Account {} + export interface User extends Account { + account_id?: string; + // (undocumented) + is_staff?: boolean; + } +} + +// @public (undocumented) +export type PaginationOptions = { + page?: number; + pagelen?: number; +}; + +// @public (undocumented) +export type PartialResponseOptions = { + fields?: string; +}; + +// @public (undocumented) +export type RequestOptions = FilterAndSortOptions & + PaginationOptions & + PartialResponseOptions & { + [key: string]: string | number | undefined; + }; + +// @public (undocumented) +export class WithPagination< + TPage extends Models.Paginated, + TResultItem, +> { + constructor( + createUrl: (options: PaginationOptions) => URL, + fetch: (url: URL) => Promise, + ); + // (undocumented) + getPage(options?: PaginationOptions): Promise; + // (undocumented) + iteratePages(options?: PaginationOptions): AsyncGenerator; + // (undocumented) + iterateResults( + options?: PaginationOptions, + ): AsyncGenerator, void, unknown>; +} +``` diff --git a/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json b/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json new file mode 100644 index 0000000000..01cd910171 --- /dev/null +++ b/plugins/bitbucket-cloud-common/bitbucket-cloud.oas.json @@ -0,0 +1,25555 @@ +{ + "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.", + "contact": { + "url": "https://support.atlassian.com/bitbucket-cloud/", + "name": "Bitbucket Support", + "email": "support@bitbucket.org" + } + }, + "paths": { + "/addon": { + "delete": { + "responses": { + "204": { + "description": "Request has succeeded. The application has been deleted for the user." + }, + "401": { + "description": "No authorization.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "Improper authentication.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Addon"], + "summary": "Delete an app", + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "204": { + "description": "Request has succeeded. The installation has been updated to the new descriptor." + }, + "400": { + "description": "Scopes have increased or decreased to none.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "No authorization.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "Improper authentication.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Addon"], + "summary": "Update an installed app", + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "Successful." + }, + "401": { + "description": "Authentication must use app JWT", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Addon"], + "summary": "List linkers for an app", + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Gets a list of all [linkers](/cloud/bitbucket/modules/linker/)\nfor the authenticated application." + }, + "parameters": [] + }, + "/addon/linkers/{linker_key}": { + "get": { + "responses": { + "200": { + "description": "Successful." + }, + "401": { + "description": "Authentication must use app JWT", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The linker does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Addon"], + "summary": "Get a linker for an app", + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Gets a [linker](/cloud/bitbucket/modules/linker/) specified by `linker_key`\nfor the authenticated application." + }, + "parameters": [ + { + "name": "linker_key", + "in": "path", + "description": "The unique key of a [linker module](/cloud/bitbucket/modules/linker/)\nas defined in an application descriptor.", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/addon/linkers/{linker_key}/values": { + "delete": { + "responses": { + "204": { + "description": "Successfully deleted the linker values." + }, + "401": { + "description": "Authentication must use app JWT", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The linker does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Addon"], + "summary": "Delete all linker values", + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Delete all [linker](/cloud/bitbucket/modules/linker/) values for the\nspecified linker of the authenticated application." + }, + "get": { + "responses": { + "200": { + "description": "Successful." + }, + "401": { + "description": "Authentication must use app JWT", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The linker does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Addon"], + "summary": "List linker values for a linker", + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "201": { + "description": "Successfully created the linker value." + }, + "401": { + "description": "Authentication must use app JWT", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The linker does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "409": { + "description": "The linker already has the value being added.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Addon"], + "summary": "Create a linker value", + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "204": { + "description": "Successfully updated the linker values." + }, + "400": { + "description": "Invalid input.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "Authentication must use app JWT", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The linker does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Addon"], + "summary": "Update a linker value", + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "linker_key", + "in": "path", + "description": "The unique key of a [linker module](/cloud/bitbucket/modules/linker/)\nas defined in an application descriptor.", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/addon/linkers/{linker_key}/values/{value_id}": { + "delete": { + "responses": { + "204": { + "description": "Successfully deleted the linker value." + }, + "401": { + "description": "Authentication must use app JWT", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The linker value does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Addon"], + "summary": "Delete a linker value", + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Delete a single [linker](/cloud/bitbucket/modules/linker/) value\nof the authenticated application." + }, + "get": { + "responses": { + "200": { + "description": "Successful." + }, + "401": { + "description": "Authentication must use app JWT", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The linker value does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Addon"], + "summary": "Get a linker value", + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Get a single [linker](/cloud/bitbucket/modules/linker/) value\nof the authenticated application." + }, + "parameters": [ + { + "name": "linker_key", + "in": "path", + "description": "The unique key of a [linker module](/cloud/bitbucket/modules/linker/)\nas defined in an application descriptor.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "value_id", + "in": "path", + "description": "The numeric ID of the linker value.", + "required": true, + "schema": { + "type": "integer" + } + } + ] + }, + "/hook_events": { + "get": { + "responses": { + "200": { + "description": "A mapping of resource/subject types pointing to their individual event types.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/subject_types" + } + } + } + } + }, + "tags": ["Webhooks"], + "summary": "Get a webhook resource", + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "A paginated list of webhook types available to subscribe on.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_hook_events" + } + } + } + }, + "404": { + "description": "If an invalid `{subject_type}` value was specified.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Webhooks"], + "summary": "List subscribable webhook types", + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "subject_type", + "in": "path", + "description": "A resource or subject type.", + "required": true, + "schema": { + "type": "string", + "enum": ["workspace", "user", "repository", "team"] + } + } + ] + }, + "/pullrequests/{selected_user}": { + "get": { + "responses": { + "200": { + "description": "All pull requests authored by the specified user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pullrequests" + } + } + } + }, + "404": { + "description": "If the specified user does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "state", + "in": "query", + "description": "Only return pull requests that are in this state. This parameter can be repeated.", + "schema": { + "type": "string", + "enum": ["MERGED", "SUPERSEDED", "OPEN", "DECLINED"] + } + } + ], + "tags": ["Pullrequests"], + "summary": "List pull requests for a user", + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "selected_user", + "in": "path", + "description": "This can either be the username of the pull request author, the author's UUID\nsurrounded by curly-braces, for example: `{account UUID}`, or the author's Atlassian ID.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/repositories": { + "get": { + "responses": { + "200": { + "description": "All public repositories.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_repositories" + } + } + } + } + }, + "parameters": [ + { + "name": "after", + "in": "query", + "description": "Filter the results to include only repositories created on or\nafter this [ISO-8601](https://en.wikipedia.org/wiki/ISO_8601)\n timestamp. Example: `YYYY-MM-DDTHH:mm:ss.sssZ`", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "role", + "in": "query", + "description": "Filters the result based on the authenticated user's role on each repository.\n\n* **member**: returns repositories to which the user has explicit read access\n* **contributor**: returns repositories to which the user has explicit write access\n* **admin**: returns repositories to which the user has explicit administrator access\n* **owner**: returns all repositories owned by the current user\n", + "required": false, + "schema": { + "type": "string", + "enum": ["admin", "contributor", "member", "owner"] + } + }, + { + "name": "q", + "in": "query", + "description": "Query string to narrow down the response as per [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).\n`role` parameter must also be specified.\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "Field by which the results should be sorted as per [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "tags": ["Repositories"], + "summary": "List public repositories", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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}": { + "get": { + "responses": { + "200": { + "description": "The repositories owned by the specified account.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_repositories" + } + } + } + }, + "404": { + "description": "If the specified account does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "410": { + "description": "If the specified account marked as spam.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "role", + "in": "query", + "description": "\nFilters the result based on the authenticated user's role on each repository.\n\n* **member**: returns repositories to which the user has explicit read access\n* **contributor**: returns repositories to which the user has explicit write access\n* **admin**: returns repositories to which the user has explicit administrator access\n* **owner**: returns all repositories owned by the current user\n", + "required": false, + "schema": { + "type": "string", + "enum": ["admin", "contributor", "member", "owner"] + } + }, + { + "name": "q", + "in": "query", + "description": "\nQuery string to narrow down the response as per [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "\nField by which the results should be sorted as per [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).\n ", + "required": false, + "schema": { + "type": "string" + } + } + ], + "tags": ["Repositories"], + "summary": "List repositories in a workspace", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}": { + "delete": { + "responses": { + "204": { + "description": "Indicates successful deletion." + }, + "403": { + "description": "If the caller either does not have admin access to the repository, or the repository is set to read-only.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "redirect_to", + "in": "query", + "description": "If a repository has been moved to a new location, use this parameter to\nshow users a friendly message in the Bitbucket UI that the repository\nhas moved to a new location. However, a GET to this endpoint will still\nreturn a 404.\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "tags": ["Repositories"], + "summary": "Delete a repository", + "security": [ + { + "oauth2": ["repository:delete"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Deletes the repository. This is an irreversible operation.\n\nThis does not affect its forks." + }, + "get": { + "responses": { + "200": { + "description": "The repository object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository" + } + } + } + }, + "403": { + "description": "If the repository is private and the authenticated user does not have access to it.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If no repository exists at this location.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Repositories"], + "summary": "Get a repository", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the object describing this repository." + }, + "post": { + "responses": { + "200": { + "description": "The newly created repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository" + } + } + } + }, + "400": { + "description": "If the input document was invalid, or if the caller lacks the privilege to create repositories under the targeted account.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "If the request was not authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository" + } + } + }, + "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"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The existing repository has been updated", + "headers": { + "Location": { + "description": "The location of the repository. This header is only\nprovided when the repository's name is changed.", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository" + } + } + } + }, + "201": { + "description": "A new repository has been created", + "headers": { + "Location": { + "description": "The location of the newly created repository", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository" + } + } + } + }, + "400": { + "description": "If the input document was invalid, or if the caller lacks the privilege to create repositories under the targeted account.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "If the request was not authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository" + } + } + }, + "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"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/branch-restrictions": { + "get": { + "responses": { + "200": { + "description": "A paginated list of branch restrictions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_branchrestrictions" + } + } + } + }, + "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 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" + } + } + } + } + }, + "parameters": [ + { + "name": "kind", + "in": "query", + "description": "Branch restrictions of this type", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "pattern", + "in": "query", + "description": "Branch restrictions applied to branches of this pattern", + "required": false, + "schema": { + "type": "string" + } + } + ], + "tags": ["Branch restrictions"], + "summary": "List branch restrictions", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns a paginated list of all branch restrictions on the\nrepository." + }, + "post": { + "responses": { + "201": { + "description": "A paginated list of branch restrictions", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/branchrestriction" + } + } + } + }, + "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 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" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/branchrestriction" + } + } + }, + "description": "The new rule", + "required": true + }, + "tags": ["Branch restrictions"], + "summary": "Create a branch restriction rule", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/branch-restrictions/{id}": { + "delete": { + "responses": { + "204": { + "description": "" + }, + "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 repository", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the repository or branch restriction id does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Branch restrictions"], + "summary": "Delete a branch restriction rule", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Deletes an existing branch restriction rule." + }, + "get": { + "responses": { + "200": { + "description": "The branch restriction rule", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/branchrestriction" + } + } + } + }, + "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 repository", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the repository or branch restriction id does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Branch restrictions"], + "summary": "Get a branch restriction rule", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns a specific branch restriction rule." + }, + "put": { + "responses": { + "200": { + "description": "The updated branch restriction rule", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/branchrestriction" + } + } + } + }, + "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 repository", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the repository or branch restriction id does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/branchrestriction" + } + } + }, + "description": "The new version of the existing rule", + "required": true + }, + "tags": ["Branch restrictions"], + "summary": "Update a branch restriction rule", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "id", + "in": "path", + "description": "The restriction rule's id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/branching-model": { + "get": { + "responses": { + "200": { + "description": "The branching model object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/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" + } + } + } + } + }, + "tags": ["Branching model"], + "summary": "Get the branching model for a repository", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/branching-model/settings": { + "get": { + "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 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" + } + } + } + } + }, + "tags": ["Branching model"], + "summary": "Get the branching model config for a repository", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The updated branching model configuration", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/branching_model_settings" + } + } + } + }, + "400": { + "description": "If the request contains 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 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" + } + } + } + } + }, + "tags": ["Branching model"], + "summary": "Update the branching model config for a repository", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/commit/{commit}": { + "get": { + "responses": { + "200": { + "description": "The commit object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/commit" + } + } + } + }, + "404": { + "description": "If the specified commit or repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Commits"], + "summary": "Get a commit", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "commit", + "in": "path", + "description": "The commit's SHA1.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/commit/{commit}/approve": { + "delete": { + "responses": { + "204": { + "description": "An empty response indicating the authenticated user's approval has been withdrawn." + }, + "404": { + "description": "If the specified commit, or the repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Commits"], + "summary": "Unapprove a commit", + "security": [ + { + "oauth2": ["repository:write"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The `participant` object recording that the authenticated user approved the commit.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/participant" + } + } + } + }, + "404": { + "description": "If the specified commit, or the repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Commits"], + "summary": "Approve a commit", + "security": [ + { + "oauth2": ["repository:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "commit", + "in": "path", + "description": "The commit's SHA1.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/commit/{commit}/comments": { + "get": { + "responses": { + "200": { + "description": "A paginated list of commit comments.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_commit_comments" + } + } + } + } + }, + "parameters": [ + { + "name": "q", + "in": "query", + "description": "Query string to narrow down the response as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "Field by which the results should be sorted as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "tags": ["Commits"], + "summary": "List a commit's comments", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "201": { + "description": "The newly created comment.", + "headers": { + "Location": { + "description": "The location of the newly created comment.", + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "If the comment was detected as spam, or if the parent comment is not attached to the same node as the new comment" + }, + "404": { + "description": "If a parent ID was passed in that cannot be found" + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/commit_comment" + } + } + }, + "description": "The specified comment.", + "required": true + }, + "tags": ["Commits"], + "summary": "Create comment for a commit", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "commit", + "in": "path", + "description": "The commit's SHA1.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/commit/{commit}/comments/{comment_id}": { + "get": { + "responses": { + "200": { + "description": "The commit comment.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/commit_comment" + } + } + } + } + }, + "tags": ["Commits"], + "summary": "Get a commit comment", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the specified commit comment." + }, + "parameters": [ + { + "name": "comment_id", + "in": "path", + "description": "The id of the comment.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "commit", + "in": "path", + "description": "The commit's SHA1.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/commit/{commit}/properties/{app_key}/{property_name}": { + "put": { + "responses": { + "204": { + "description": "An empty response." + } + }, + "parameters": [ + { + "required": true, + "description": "The repository container; either the workspace slug or the UUID in curly braces.", + "in": "path", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The repository.", + "in": "path", + "name": "repo_slug", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The commit.", + "in": "path", + "name": "commit", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The key of the Connect app.", + "in": "path", + "name": "app_key", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The name of the property.", + "in": "path", + "name": "property_name", + "schema": { + "type": "string" + } + } + ], + "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" + }, + "delete": { + "responses": { + "204": { + "description": "An empty response." + } + }, + "parameters": [ + { + "required": true, + "description": "The repository container; either the workspace slug or the UUID in curly braces.", + "in": "path", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The repository.", + "in": "path", + "name": "repo_slug", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The commit.", + "in": "path", + "name": "commit", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The key of the Connect app.", + "in": "path", + "name": "app_key", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The name of the property.", + "in": "path", + "name": "property_name", + "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" + }, + "get": { + "responses": { + "200": { + "description": "The value of the property.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/application_property" + } + } + } + } + }, + "parameters": [ + { + "required": true, + "description": "The repository container; either the workspace slug or the UUID in curly braces.", + "in": "path", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The repository.", + "in": "path", + "name": "repo_slug", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The commit.", + "in": "path", + "name": "commit", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The key of the Connect app.", + "in": "path", + "name": "app_key", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The name of the property.", + "in": "path", + "name": "property_name", + "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" + } + }, + "/repositories/{workspace}/{repo_slug}/commit/{commit}/pullrequests": { + "get": { + "responses": { + "200": { + "description": "The paginated list of pull requests.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pullrequests" + } + } + } + }, + "202": { + "description": "The repository's pull requests are still being indexed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pullrequests" + } + } + } + }, + "404": { + "description": "Either the repository does not exist, or pull request commit links have not yet been indexed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_reports" + } + } + } + } + }, + "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 for which to retrieve reports.", + "required": true, + "name": "commit", + "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": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/report" + } + } + } + }, + "400": { + "description": "The provided Report 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" + } + } + ], + "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", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/report" + } + } + } + }, + "404": { + "description": "The report with the given ID was not found.", + "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" + } + } + ], + "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" + } + } + } + } + }, + "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 for which to retrieve reports.", + "required": true, + "name": "commit", + "in": "path", + "schema": { + "type": "string" + } + }, + { + "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", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_annotations" + } + } + } + } + }, + "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 for which to retrieve reports.", + "required": true, + "name": "commit", + "in": "path", + "schema": { + "type": "string" + } + }, + { + "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" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/report_annotation" + } + } + }, + "description": "The annotation 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" + }, + "get": { + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/report_annotation" + } + } + } + }, + "404": { + "description": "The annotation with the given ID was not found.", + "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" + } + } + ], + "tags": ["Reports", "Commits"], + "summary": "Get an annotation", + "operationId": "getAnnotation", + "description": "Returns a single Annotation 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 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": { + "responses": { + "200": { + "description": "A paginated list of all commit statuses for this commit.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_commitstatuses" + } + } + } + }, + "401": { + "description": "If the repository is private and the request was not authenticated." + }, + "404": { + "description": "If the repository or commit does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "q", + "in": "query", + "description": "Query string to narrow down the response as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "Field by which the results should be sorted as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).\nDefaults to `created_on`.\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "tags": ["Commit statuses"], + "summary": "List commit statuses for a commit", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns all statuses (e.g. build results) for a specific commit." + }, + "parameters": [ + { + "name": "commit", + "in": "path", + "description": "The commit's SHA1.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/commit/{commit}/statuses/build": { + "post": { + "responses": { + "201": { + "description": "The newly created build status object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/commitstatus" + } + } + } + }, + "401": { + "description": "If the repository is private and the request was not authenticated." + }, + "404": { + "description": "If the repository, commit, or build status key does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/commitstatus" + } + } + }, + "description": "The new commit status object." + }, + "tags": ["Commit statuses"], + "summary": "Create a build status for a commit", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "commit", + "in": "path", + "description": "The commit's SHA1.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/commit/{commit}/statuses/build/{key}": { + "get": { + "responses": { + "200": { + "description": "The build status object with the specified key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/commitstatus" + } + } + } + }, + "401": { + "description": "If the repository is private and the request was not authenticated." + }, + "404": { + "description": "If the repository, commit, or build status key does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Commit statuses"], + "summary": "Get a build status for a commit", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the specified build status for a commit." + }, + "put": { + "responses": { + "200": { + "description": "The updated build status object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/commitstatus" + } + } + } + }, + "401": { + "description": "If the repository is private and the request was not authenticated." + }, + "404": { + "description": "If the repository or build does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/commitstatus" + } + } + }, + "description": "The updated build status object" + }, + "tags": ["Commit statuses"], + "summary": "Update a build status for a commit", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "commit", + "in": "path", + "description": "The commit's SHA1.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "key", + "in": "path", + "description": "The build status' unique key", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/commits": { + "get": { + "responses": { + "200": { + "description": "A paginated list of commits", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_changeset" + } + } + } + }, + "404": { + "description": "If the specified repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Commits"], + "summary": "List commits", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "A paginated list of commits", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_changeset" + } + } + } + }, + "404": { + "description": "If the specified repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Commits"], + "summary": "List commits with include/exclude", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/commits/{revision}": { + "get": { + "responses": { + "200": { + "description": "A paginated list of commits", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_changeset" + } + } + } + }, + "404": { + "description": "If the specified revision does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Commits"], + "summary": "List commits for revision", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "A paginated list of commits", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_changeset" + } + } + } + }, + "404": { + "description": "If the specified revision does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Commits"], + "summary": "List commits for revision using include/exclude", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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": "revision", + "in": "path", + "description": "The commit's SHA1.", + "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}/components": { + "get": { + "responses": { + "200": { + "description": "The components that have been defined in the issue tracker.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_components" + } + } + } + }, + "404": { + "description": "The specified repository does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "List components", + "security": [ + { + "oauth2": ["issue"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/components/{component_id}": { + "get": { + "responses": { + "200": { + "description": "The specified component object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/component" + } + } + } + }, + "404": { + "description": "The specified repository or component does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Get a component for issues", + "security": [ + { + "oauth2": ["issue"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the specified issue tracker component object." + }, + "parameters": [ + { + "name": "component_id", + "in": "path", + "description": "The component's id", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/default-reviewers": { + "get": { + "responses": { + "200": { + "description": "The paginated list of default reviewers" + }, + "403": { + "description": "If the authenticated user does not have access to view the default reviewers", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "List default reviewers", + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/default-reviewers/{target_username}": { + "delete": { + "responses": { + "204": { + "description": "The specified user successfully removed from the default reviewers" + }, + "403": { + "description": "If the authenticated user does not have access modify the default reviewers", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the specified user does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "Remove a user from the default reviewers", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Removes a default reviewer from the repository." + }, + "get": { + "responses": { + "200": { + "description": "The specified user is a default reviewer" + }, + "403": { + "description": "If the authenticated user does not have access to check if the specified user is a default reviewer", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the specified user does not exist or is not a default reviewer", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "Get a default reviewer", + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The specified user was successfully added to the default reviewers" + }, + "400": { + "description": "If the authenticated user tried to add a team, bot user, or user without access to the repository to the default reviewers", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have permission to modify the default reviewers", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the specified user does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "Add a user to the default reviewers", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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": "target_username", + "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" + } + } + ] + }, + "/repositories/{workspace}/{repo_slug}/deploy-keys": { + "get": { + "responses": { + "200": { + "description": "Deploy keys matching the repository", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_deploy_keys" + } + } + } + }, + "403": { + "description": "If the specified user or repository is not accessible to the current user" + }, + "404": { + "description": "If the specified user or repository does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Deployments"], + "summary": "List deploy keys", + "security": [ + { + "oauth2": ["repository", "repository:admin"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The deploy key that was created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/deploy_key" + } + } + } + }, + "400": { + "description": "Invalid deploy key inputs" + }, + "403": { + "description": "If the specified user or repository is not accessible to the current user" + }, + "404": { + "description": "If the specified user or repository does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Deployments"], + "summary": "Add a deploy key", + "security": [ + { + "oauth2": ["repository", "repository:admin"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/deploy-keys/{key_id}": { + "delete": { + "responses": { + "204": { + "description": "The key has been deleted" + }, + "403": { + "description": "If the current user does not have permission to delete a key for the specified user" + }, + "404": { + "description": "If the specified user, repository, or deploy key does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Deployments"], + "summary": "Delete a deploy key", + "security": [ + { + "oauth2": ["repository", "repository:admin"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "Deploy key matching the key ID", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/deploy_key" + } + } + } + }, + "403": { + "description": "If the specified user or repository is not accessible to the current user" + }, + "404": { + "description": "If the specified user or repository does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Deployments"], + "summary": "Get a deploy key", + "security": [ + { + "oauth2": ["repository", "repository:admin"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The newly updated deploy key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/deploy_key" + } + } + } + }, + "400": { + "description": "If the submitted key or related value is invalid", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the current user does not have permission to add a key for the specified user" + }, + "404": { + "description": "If the specified user, repository, or deploy key does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Deployments"], + "summary": "Update a deploy key", + "security": [ + { + "oauth2": ["repository", "repository:admin"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "key_id", + "in": "path", + "description": "The key ID matching the deploy key.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/deployments/": { + "get": { + "responses": { + "200": { + "description": "The matching deployments.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_deployments" + } + } + } + } + }, + "description": "Find deployments", + "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 deployments", + "operationId": "getDeploymentsForRepository" + } + }, + "/repositories/{workspace}/{repo_slug}/deployments/{deployment_uuid}": { + "get": { + "responses": { + "200": { + "description": "The deployment.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/deployment" + } + } + } + }, + "404": { + "description": "No account, repository or deployment with the UUID provided exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Retrieve a deployment", + "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 deployment UUID.", + "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": { + "post": { + "responses": { + "201": { + "headers": { + "Location": { + "description": "The URL of the newly created variable.", + "schema": { + "type": "string" + } + } + }, + "description": "The variable was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/deployment_variable" + } + } + } + }, + "404": { + "description": "The account, repository, environment or variable with the given UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "409": { + "description": "A variable with the provided key already exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Create 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" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/deployment_variable" + } + } + }, + "description": "The variable to create", + "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.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/deployment_variable" + } + } + } + }, + "404": { + "description": "The account, repository, environment or variable with the given UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Update 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 update.", + "required": true, + "name": "variable_uuid", + "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." + }, + "404": { + "description": "The account, repository, environment or variable with given UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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": { + "responses": { + "200": { + "description": "The raw diff" + }, + "555": { + "description": "If the diff was too large and timed out.\n\nSince this endpoint does not employ any form of pagination, but\ninstead returns the diff as a single document, it can run into\ntrouble on very large diffs. If Bitbucket times out in cases\nlike these, a 555 status code is returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "context", + "in": "query", + "description": "Generate diffs with lines of context instead of the usual three.", + "required": false, + "schema": { + "type": "integer" + } + }, + { + "name": "path", + "in": "query", + "description": "Limit the diff to a particular file (this parameter\ncan be repeated for multiple paths).", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "ignore_whitespace", + "in": "query", + "description": "Generate diffs that ignore whitespace.", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "binary", + "in": "query", + "description": "Generate diffs that include binary files, true if omitted.", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "renames", + "in": "query", + "description": "Whether to perform rename detection, true if omitted.", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "merge", + "in": "query", + "description": "This parameter is deprecated and will be removed at the end\nof 2022. The 'topic' parameter should be used instead. The\n'merge' and 'topic' parameters cannot be both used at the same\ntime.\n\nIf true, the source commit is merged into the\ndestination commit, and then a diff from the\ndestination to the merge result is returned. If false,\na simple 'two dot' diff between the source and\ndestination is returned. True if omitted.", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "topic", + "in": "query", + "description": "If true, returns 2-way 'three-dot' diff.\nThis is a diff between the source commit and the merge base\nof the source commit and the destination commit.\nIf false, a simple 'two dot' diff between the source and\ndestination is returned.", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "tags": ["Commits"], + "summary": "Compare two commits", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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": "spec", + "in": "path", + "description": "A commit SHA (e.g. `3a8b42`) or a commit range using double dot\nnotation (e.g. `3a8b42..9ff173`).\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}/diffstat/{spec}": { + "get": { + "responses": { + "200": { + "description": "The diff stats", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_diffstats" + } + } + } + }, + "555": { + "description": "If generating the diffstat timed out.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Commits"], + "summary": "Compare two commit diff stats", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "ignore_whitespace", + "in": "query", + "description": "Generate diffs that ignore whitespace", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "merge", + "in": "query", + "description": "This parameter is deprecated and will be removed at the end\nof 2022. The 'topic' parameter should be used instead. The\n'merge' and 'topic' parameters cannot be both used at the same\ntime.\n\nIf true, the source commit is merged into the\ndestination commit, and then a diffstat from the\ndestination to the merge result is returned. If false,\na simple 'two dot' diffstat between the source and\ndestination is returned. True if omitted.", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "name": "path", + "in": "query", + "description": "Limit the diffstat to a particular file (this parameter\ncan be repeated for multiple paths).", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "renames", + "in": "query", + "description": "Whether to perform rename detection, true if omitted.", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "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": "spec", + "in": "path", + "description": "A commit SHA (e.g. `3a8b42`) or a commit range using double dot\nnotation (e.g. `3a8b42..9ff173`).\n", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "topic", + "in": "query", + "description": "If true, returns 2-way 'three-dot' diff.\nThis is a diff between the source commit and the merge base\nof the source commit and the destination commit.\nIf false, a simple 'two dot' diff between the source and\ndestination is returned.", + "required": false, + "schema": { + "type": "boolean" + } + }, + { + "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}/downloads": { + "get": { + "responses": { + "200": { + "description": "Returns a paginated list of the downloads associated with the repository." + }, + "403": { + "description": "User is not authorized to read from the repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Downloads"], + "summary": "List download artifacts", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns a list of download links associated with the repository." + }, + "post": { + "responses": { + "201": { + "description": "The artifact was uploaded sucessfully." + }, + "400": { + "description": "Bad Request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "User is not authorized to write to the repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "406": { + "description": "Unsupported Content-Type. Use multiplart/form-data.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Downloads"], + "summary": "Upload a download artifact", + "security": [ + { + "oauth2": ["repository:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/downloads/{filename}": { + "delete": { + "responses": { + "204": { + "description": "The specified download artifact was deleted." + }, + "403": { + "description": "User is not authorized to write to the repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified download does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Downloads"], + "summary": "Delete a download artifact", + "security": [ + { + "oauth2": ["repository:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Deletes the specified download artifact from the repository." + }, + "get": { + "responses": { + "302": { + "description": "Redirects to the url of the specified download artifact." + }, + "403": { + "description": "User is not authorized to read from the repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified download artifact does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Downloads"], + "summary": "Get a download artifact link", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "filename", + "in": "path", + "description": "Name of the file.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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/": { + "post": { + "responses": { + "201": { + "headers": { + "Location": { + "description": "The URL of the newly created environment.", + "schema": { + "type": "string" + } + } + }, + "description": "The environment was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/deployment_environment" + } + } + } + }, + "404": { + "description": "The account or repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "409": { + "description": "An environment host with the provided name already exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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": { + "responses": { + "200": { + "description": "The environment.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/deployment_environment" + } + } + } + }, + "404": { + "description": "No account, repository or environment with the UUID provided exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Retrieve 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": "Get an environment", + "operationId": "getEnvironmentForRepository" + }, + "delete": { + "responses": { + "204": { + "description": "The environment was deleted." + }, + "404": { + "description": "No account or repository with the UUID provided exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Delete 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": "Delete an environment", + "operationId": "deleteEnvironmentForRepository" + } + }, + "/repositories/{workspace}/{repo_slug}/environments/{environment_uuid}/changes/": { + "post": { + "responses": { + "202": { + "description": "The environment update request was accepted." + }, + "404": { + "description": "No account, repository or environment with the UUID provided exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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": { + "responses": { + "200": { + "description": "A paginated list of commits that modified the specified file", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_files" + } + } + } + }, + "404": { + "description": "If the repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "renames", + "in": "query", + "description": "\nWhen `true`, Bitbucket will follow the history of the file across\nrenames (this is the default behavior). This can be turned off by\nspecifying `false`.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "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": ["Source", "Repositories"], + "summary": "List commits that modified a file", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "commit", + "in": "path", + "description": "The commit's SHA1.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "path", + "in": "path", + "description": "Path to the file.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/forks": { + "get": { + "responses": { + "200": { + "description": "All forks.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_repositories" + } + } + } + } + }, + "parameters": [ + { + "name": "role", + "in": "query", + "description": "Filters the result based on the authenticated user's role on each repository.\n\n* **member**: returns repositories to which the user has explicit read access\n* **contributor**: returns repositories to which the user has explicit write access\n* **admin**: returns repositories to which the user has explicit administrator access\n* **owner**: returns all repositories owned by the current user\n", + "required": false, + "schema": { + "type": "string", + "enum": ["admin", "contributor", "member", "owner"] + } + }, + { + "name": "q", + "in": "query", + "description": "Query string to narrow down the response as per [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "Field by which the results should be sorted as per [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "tags": ["Repositories"], + "summary": "List repository forks", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns a paginated list of all the forks of the specified\nrepository." + }, + "post": { + "responses": { + "201": { + "description": "The newly created fork.", + "headers": { + "Location": { + "description": "The URL of the newly created fork", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository" + } + } + }, + "description": "A repository object. This can be left blank." + }, + "tags": ["Repositories"], + "summary": "Fork a repository", + "security": [ + { + "oauth2": ["repository:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/hooks": { + "get": { + "responses": { + "200": { + "description": "The paginated list of installed webhooks.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_webhook_subscriptions" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have permission to access the webhooks.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Repositories", "Webhooks"], + "summary": "List webhooks for a repository", + "security": [ + { + "oauth2": ["webhook"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns a paginated list of webhooks installed on this repository." + }, + "post": { + "responses": { + "201": { + "description": "If the webhook was registered successfully.", + "headers": { + "Location": { + "description": "The URL of new newly created webhook.", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/webhook_subscription" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have permission to install webhooks on the specified 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" + } + } + } + } + }, + "tags": ["Repositories", "Webhooks"], + "summary": "Create a webhook for a repository", + "security": [ + { + "oauth2": ["webhook"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/hooks/{uid}": { + "delete": { + "responses": { + "204": { + "description": "When the webhook was deleted successfully" + }, + "403": { + "description": "If the authenticated user does not have permission to delete the webhook.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the webhook or repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Repositories", "Webhooks"], + "summary": "Delete a webhook for a repository", + "security": [ + { + "oauth2": ["webhook"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Deletes the specified webhook subscription from the given\nrepository." + }, + "get": { + "responses": { + "200": { + "description": "The webhook subscription object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/webhook_subscription" + } + } + } + }, + "404": { + "description": "If the webhook or repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Repositories", "Webhooks"], + "summary": "Get a webhook for a repository", + "security": [ + { + "oauth2": ["webhook"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the webhook with the specified id installed on the specified\nrepository." + }, + "put": { + "responses": { + "200": { + "description": "The webhook subscription object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/webhook_subscription" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have permission to update the webhook.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the webhook or repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Repositories", "Webhooks"], + "summary": "Update a webhook for a repository", + "security": [ + { + "oauth2": ["webhook"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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": "uid", + "in": "path", + "description": "Installed webhook's ID", + "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}/issues": { + "get": { + "responses": { + "200": { + "description": "A paginated list of the issues matching any filter criteria that were provided.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_issues" + } + } + } + }, + "404": { + "description": "The specified repository does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "List issues", + "security": [ + { + "oauth2": ["issue"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the issues in the issue tracker." + }, + "post": { + "responses": { + "201": { + "description": "The newly created issue.", + "headers": { + "Location": { + "description": "The (absolute) URL of the newly created issue.", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue" + } + } + } + }, + "401": { + "description": "When the request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "When the authenticated user isn't authorized to create the issue.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue" + } + } + }, + "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"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/issues/export": { + "post": { + "responses": { + "202": { + "description": "The export job has been accepted" + }, + "401": { + "description": "The request wasn't authenticated properly", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "When the authenticated user does not have admin permission on the repo", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The repo does not exist or does not have an issue tracker", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/export_options" + } + } + }, + "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"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/issues/export/{repo_name}-issues-{task_id}.zip": { + "get": { + "responses": { + "202": { + "description": "Export job accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue_job_status" + } + } + } + }, + "401": { + "description": "The request wasn't authenticated properly", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "When the authenticated user does not have admin permission on the repo", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "No export job has begun", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Check issue export status", + "security": [ + { + "oauth2": ["issue", "repository:admin"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "repo_name", + "in": "path", + "description": "The name of the repo", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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": "task_id", + "in": "path", + "description": "The ID of the export task", + "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}/issues/import": { + "get": { + "responses": { + "200": { + "description": "Import job complete with either FAILURE or SUCCESS status", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue_job_status" + } + } + } + }, + "202": { + "description": "Import job started", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue_job_status" + } + } + } + }, + "401": { + "description": "The request wasn't authenticated properly", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "When the authenticated user does not have admin permission on the repo", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "No export job has begun", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Check issue import status", + "security": [ + { + "oauth2": ["issue:write", "repository:admin"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "202": { + "description": "Import job accepted", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue_job_status" + } + } + } + }, + "401": { + "description": "The request wasn't authenticated properly", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "When the authenticated user does not have admin permission on the repo", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "No export job has begun", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "409": { + "description": "Import already running", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Import issues", + "security": [ + { + "oauth2": ["issue:write", "repository:admin"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/issues/{issue_id}": { + "delete": { + "responses": { + "200": { + "description": "The issue object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue" + } + } + } + }, + "403": { + "description": "When the authenticated user isn't authorized to delete the issue.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository or issue does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Delete an issue", + "security": [ + { + "oauth2": ["issue:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Deletes the specified issue. This requires write access to the\nrepository." + }, + "get": { + "responses": { + "200": { + "description": "The issue object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue" + } + } + } + }, + "403": { + "description": "When the authenticated user isn't authorized to access the issue.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository or issue does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "410": { + "description": "The specified issue is unavailable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Get an issue", + "security": [ + { + "oauth2": ["issue"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the specified issue." + }, + "put": { + "responses": { + "200": { + "description": "The updated issue object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue" + } + } + } + }, + "403": { + "description": "When the authenticated user isn't authorized to access the issue.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository or issue does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Update an issue", + "security": [ + { + "oauth2": ["issue:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "issue_id", + "in": "path", + "description": "The issue id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/issues/{issue_id}/attachments": { + "get": { + "responses": { + "200": { + "description": "A paginated list of all attachments for this issue.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_issue_attachments" + } + } + } + }, + "401": { + "description": "If the issue tracker is private and the request was not authenticated." + }, + "404": { + "description": "The specified repository or issue does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "List attachments for an issue", + "security": [ + { + "oauth2": ["issue"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "201": { + "description": "An empty response document.", + "headers": { + "Location": { + "description": "The URL to the issue's collection of attachments.", + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "If no files were uploaded, or if the wrong `Content-Type` was used." + }, + "401": { + "description": "If the issue tracker is private and the request was not authenticated." + }, + "404": { + "description": "The specified repository or issue does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Upload an attachment to an issue", + "security": [ + { + "oauth2": ["issue:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "issue_id", + "in": "path", + "description": "The issue id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/issues/{issue_id}/attachments/{path}": { + "delete": { + "responses": { + "204": { + "description": "Indicates that the deletion was successful" + }, + "401": { + "description": "If the issue tracker is private and the request was not authenticated." + }, + "404": { + "description": "The specified repository or issue does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Delete an attachment for an issue", + "security": [ + { + "oauth2": ["issue:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Deletes an attachment." + }, + "get": { + "responses": { + "302": { + "description": "A redirect to the file's contents", + "headers": { + "Location": { + "schema": { + "type": "string" + } + } + } + }, + "401": { + "description": "If the issue tracker is private and the request was not authenticated." + }, + "404": { + "description": "The specified repository or issue does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Get attachment for an issue", + "security": [ + { + "oauth2": ["issue"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "issue_id", + "in": "path", + "description": "The issue id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "path", + "in": "path", + "description": "Path to the file.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/issues/{issue_id}/changes": { + "get": { + "responses": { + "200": { + "description": "Returns all the issue changes that were made on the specified issue.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_log_entries" + } + } + } + }, + "404": { + "description": "The specified repository or issue does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "q", + "in": "query", + "description": "\nQuery string to narrow down the response. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for details.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "\nName of a response property to sort results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#sorting-query-results)\nfor details.\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "tags": ["Issue tracker"], + "summary": "List changes on an issue", + "security": [ + { + "oauth2": ["issue"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "201": { + "description": "The newly created issue change.", + "headers": { + "Location": { + "description": "The (absolute) URL of the newly created issue change.", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue_change" + } + } + } + }, + "401": { + "description": "When the request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "When the authenticated user isn't authorized to modify the issue.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository or issue does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue_change" + } + } + }, + "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"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "issue_id", + "in": "path", + "description": "The issue id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/issues/{issue_id}/changes/{change_id}": { + "get": { + "responses": { + "200": { + "description": "The specified issue change object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue_change" + } + } + } + }, + "404": { + "description": "The specified repository or issue change does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Get issue change object", + "security": [ + { + "oauth2": ["issue"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the specified issue change object.\n\nThis resource is only available on repositories that have the issue\ntracker enabled." + }, + "parameters": [ + { + "name": "change_id", + "in": "path", + "description": "The issue change id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "issue_id", + "in": "path", + "description": "The issue id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/issues/{issue_id}/comments": { + "get": { + "responses": { + "200": { + "description": "A paginated list of issue comments.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_issue_comments" + } + } + } + } + }, + "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" + } + } + ], + "tags": ["Issue tracker"], + "summary": "List comments on an issue", + "security": [ + { + "oauth2": ["issue"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "201": { + "description": "The newly created comment.", + "headers": { + "Location": { + "description": "The location of the newly issue comment.", + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "If the input was invalid, or if the comment being created is detected as spam ", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue_comment" + } + } + }, + "description": "The new issue comment object.", + "required": true + }, + "tags": ["Issue tracker"], + "summary": "Create a comment on an issue", + "security": [ + { + "oauth2": ["issue:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "issue_id", + "in": "path", + "description": "The issue id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/issues/{issue_id}/comments/{comment_id}": { + "delete": { + "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"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Deletes the specified comment." + }, + "get": { + "responses": { + "200": { + "description": "The issue comment.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue_comment" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Get a comment on an issue", + "security": [ + { + "oauth2": ["issue"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the specified issue comment object." + }, + "put": { + "responses": { + "200": { + "description": "The updated issue comment.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/issue_comment" + } + } + } + }, + "400": { + "description": "If the input was invalid, or if the update to the comment is detected as spam ", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/issue_comment" + }, + "tags": ["Issue tracker"], + "summary": "Update a comment on an issue", + "security": [ + { + "oauth2": ["issue:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "comment_id", + "in": "path", + "description": "The id of the comment.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "issue_id", + "in": "path", + "description": "The issue id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/issues/{issue_id}/vote": { + "delete": { + "responses": { + "default": { + "description": "Unexpected error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Remove vote for an issue", + "security": [ + { + "oauth2": ["account:write", "issue:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Retract your vote." + }, + "get": { + "responses": { + "204": { + "description": "If the authenticated user has not voted for this issue.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "When the request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the authenticated user has not voted for this issue, or when the repo does not exist, or does not have an issue tracker.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Check if current user voted for an issue", + "security": [ + { + "oauth2": ["account", "issue"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "204": { + "description": "Indicating the authenticated user has cast their vote successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "When the request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository or issue does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Vote for an issue", + "security": [ + { + "oauth2": ["account:write", "issue"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "issue_id", + "in": "path", + "description": "The issue id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/issues/{issue_id}/watch": { + "delete": { + "responses": { + "204": { + "description": "Indicates that the authenticated user successfully stopped watching this issue.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "When the request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository or issue does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Stop watching an issue", + "security": [ + { + "oauth2": ["account:write", "issue:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Stop watching this issue." + }, + "get": { + "responses": { + "204": { + "description": "If the authenticated user is watching this issue.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "When the request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the authenticated user is not watching this issue, or when the repo does not exist, or does not have an issue tracker.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Check if current user is watching a issue", + "security": [ + { + "oauth2": ["account", "issue"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Indicated whether or not the authenticated user is watching this\nissue." + }, + "put": { + "responses": { + "204": { + "description": "Indicates that the authenticated user successfully started watching this issue.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "When the request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the authenticated user is not watching this issue, or when the repo does not exist, or does not have an issue tracker.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Watch an issue", + "security": [ + { + "oauth2": ["account:write", "issue"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "issue_id", + "in": "path", + "description": "The issue id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/merge-base/{revspec}": { + "get": { + "responses": { + "200": { + "description": "The merge base of the provided spec.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/commit" + } + } + } + }, + "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 access to any of the repositories specified.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the repository or ref in the spec does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Commits"], + "summary": "Get the common ancestor between two commits", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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": "revspec", + "in": "path", + "description": "A commit range using double dot notation (e.g. `3a8b42..9ff173`).\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}/milestones": { + "get": { + "responses": { + "200": { + "description": "The milestones that have been defined in the issue tracker.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_milestones" + } + } + } + }, + "404": { + "description": "The specified repository does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "List milestones", + "security": [ + { + "oauth2": ["issue"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/milestones/{milestone_id}": { + "get": { + "responses": { + "200": { + "description": "The specified milestone object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/milestone" + } + } + } + }, + "404": { + "description": "The specified repository or milestone does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Get a milestone", + "security": [ + { + "oauth2": ["issue"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the specified issue tracker milestone object." + }, + "parameters": [ + { + "name": "milestone_id", + "in": "path", + "description": "The milestone's id", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/patch/{spec}": { + "get": { + "responses": { + "200": { + "description": "The raw patches" + }, + "555": { + "description": "If the diff was too large and timed out.\n\nSince this endpoint does not employ any form of pagination, but\ninstead returns the diff as a single document, it can run into\ntrouble on very large diffs. If Bitbucket times out in cases\nlike these, a 555 status code is returned.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Commits"], + "summary": "Get a patch for two commits", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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": "spec", + "in": "path", + "description": "A commit SHA (e.g. `3a8b42`) or a commit range using double dot\nnotation (e.g. `3a8b42..9ff173`).\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}/permissions-config/groups": { + "get": { + "responses": { + "200": { + "description": "Paginated of explicit group permissions on the repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_repository_group_permissions" + } + } + } + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The requesting user isn't an admin of the repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "One or both of the workspace and repository doesn't exist for the given identifiers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Repositories"], + "summary": "List explicit group permissions for a repository", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/permissions-config/groups/{group_slug}": { + "delete": { + "responses": { + "204": { + "description": "Group permission deleted" + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The requesting user isn't an admin of the repository, or the authentication method was not via app password.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The workspace does not exist, the repository does not exist,or the group does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Repositories"], + "summary": "Delete an explicit group permission for a repository", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "Group permission for group slug and repository", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository_group_permission" + } + } + } + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The requesting user isn't an admin of the repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The given user, workspace, and/or repository could not be found", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Repositories"], + "summary": "Get an explicit group permission for a repository", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "Group permission updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository_group_permission" + } + } + } + }, + "400": { + "description": "No permission value was provided or the value is invalid(not one of read, write, or admin)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The requesting user isn't an admin of the repository, or the authentication method was not via app password.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The workspace does not exist, the repository does not exist,or the group does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Repositories"], + "summary": "Update an explicit group permission for a repository", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "group_slug", + "in": "path", + "description": "Slug of the requested group.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/permissions-config/users": { + "get": { + "responses": { + "200": { + "description": "Paginated of explicit user permissions on the repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_repository_user_permissions" + } + } + } + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The requesting user isn't an admin of the repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "No repository exists for the given repo slug and workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Repositories"], + "summary": "List explicit user permissions for a repository", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/permissions-config/users/{selected_user_id}": { + "delete": { + "responses": { + "204": { + "description": "The repository user permission was deleted and no content returned." + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The requesting user isn't an admin of the repository, or the authentication method was not via app password.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "One or more of the workspace, repository, and user doesn't exist for the given identifiers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Repositories"], + "summary": "Delete an explicit user permission for a repository", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "Explicit user permission for user and repository", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository_user_permission" + } + } + } + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The requesting user isn't an admin of the repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "One or both of the workspace and repository doesn't exist for the given identifiers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Repositories"], + "summary": "Get an explicit user permission for a repository", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "Explicit user permission updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/repository_user_permission" + } + } + } + }, + "400": { + "description": "No permission value was provided or the value is invalid (not one of read, write, or admin), or the selected user is not a valid user to update.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "The user couldn't be authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "The requesting user isn't an admin of the repository, or the authentication method was not via app password.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "One or more of the workspace, repo, and selected user doesn't exist for the given identifiers.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Repositories"], + "summary": "Update an explicit user permission for a repository", + "security": [ + { + "oauth2": ["repository:admin"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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": "selected_user_id", + "in": "path", + "description": "This can either be the UUID of the account, surrounded by curly-braces, for\nexample: `{account UUID}`, OR an Atlassian Account ID.\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}/pipelines-config/caches/": { + "get": { + "responses": { + "200": { + "description": "The list of caches for the given repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pipeline_caches" + } + } + } + }, + "404": { + "description": "The account or repository was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Retrieve the repository pipelines caches.", + "parameters": [ + { + "description": "The account.", + "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 caches", + "operationId": "getRepositoryPipelineCaches" + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines-config/caches/{cache_uuid}": { + "delete": { + "responses": { + "204": { + "description": "The cache was deleted." + }, + "404": { + "description": "The workspace, repository or cache_uuid with given UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Delete a repository cache.", + "parameters": [ + { + "description": "The account.", + "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 cache to delete.", + "required": true, + "name": "cache_uuid", + "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.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_cache_content_uri" + } + } + } + }, + "404": { + "description": "The workspace, repository or cache_uuid with given UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Retrieve the URI of the content of the specified cache.", + "parameters": [ + { + "description": "The account.", + "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 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/": { + "post": { + "responses": { + "201": { + "headers": { + "Location": { + "description": "The URL of the newly created pipeline.", + "schema": { + "type": "string" + } + } + }, + "description": "The initiated pipeline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline" + } + } + } + }, + "400": { + "description": "The account or repository is not enabled, the yml file does not exist in the repository for the given revision, or the request body contained invalid properties.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The account or repository was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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": { + "responses": { + "200": { + "description": "The pipeline.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline" + } + } + } + }, + "404": { + "description": "No account, repository or pipeline with the UUID provided exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Retrieve a specified pipeline", + "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 pipeline UUID.", + "required": true, + "name": "pipeline_uuid", + "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.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pipeline_steps" + } + } + } + } + }, + "description": "Find steps for the given pipeline.", + "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 pipeline.", + "required": true, + "name": "pipeline_uuid", + "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.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_step" + } + } + } + }, + "404": { + "description": "No account, repository, pipeline or step with the UUID provided exists for the pipeline with the UUID provided.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Retrieve a given step of a pipeline.", + "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 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" + } + } + ], + "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." + }, + "304": { + "description": "The log has the same etag as the provided If-None-Match header.", + "content": { + "application/octet-stream": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "A pipeline with the given UUID does not exist, a step with the given UUID does not exist in the pipeline or a log file does not exist for the given step.", + "content": { + "application/octet-stream": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "416": { + "description": "The requested range does not exist for requests that specified the [HTTP Range header](https://tools.ietf.org/html/rfc7233#section-3.1).", + "content": { + "application/octet-stream": { + "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 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" + } + } + ], + "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." + }, + "404": { + "description": "No account, repository, pipeline, step or log exist for the provided path.", + "content": { + "application/octet-stream": { + "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 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": "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." + }, + "404": { + "description": "No account, repository, pipeline, step or test reports exist for the provided path.", + "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 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" + } + } + ], + "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." + }, + "404": { + "description": "No account, repository, pipeline, step or test reports exist for the provided path.", + "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 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" + } + } + ], + "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)." + }, + "404": { + "description": "No account, repository, pipeline, step or test case with the UUID provided exists for the pipeline with the UUID provided.", + "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 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." + }, + "400": { + "description": "The specified pipeline has already completed.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "Either the account, repository or pipeline with the given UUID does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Signal the stop of a pipeline and all of its steps that not have completed yet.", + "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 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.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipelines_config" + } + } + } + } + }, + "description": "Update the pipelines configuration for a 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" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipelines_config" + } + } + }, + "description": "The updated repository pipelines configuration.", + "required": true + }, + "tags": ["Pipelines"], + "summary": "Update configuration", + "operationId": "updateRepositoryPipelineConfig" + }, + "get": { + "responses": { + "200": { + "description": "The repository pipelines configuration.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipelines_config" + } + } + } + } + }, + "description": "Retrieve the repository pipelines configuration.", + "parameters": [ + { + "description": "The account.", + "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": "Get configuration", + "operationId": "getRepositoryPipelineConfig" + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines_config/build_number": { + "put": { + "responses": { + "200": { + "description": "The build number has been configured.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_build_number" + } + } + } + }, + "400": { + "description": "The update failed because the next number was invalid (it should be higher than the current number).", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The account or repository was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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.", + "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_build_number" + } + } + }, + "description": "The build number to update.", + "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.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_schedule" + } + } + } + }, + "400": { + "description": "There were errors validating the request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "The maximum limit of schedules for this repository was reached.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The account or repository was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Create a schedule 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" + } + } + ], + "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.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pipeline_schedules" + } + } + } + }, + "404": { + "description": "The account or repository was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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.", + "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" + } + } + ], + "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.", + "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": "Retrieve a schedule by its UUID.", + "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": "Get a schedule", + "operationId": "getRepositoryPipelineSchedule" + }, + "delete": { + "responses": { + "204": { + "description": "The schedule was deleted." + }, + "404": { + "description": "The account, repository or schedule was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Delete a 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": "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.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pipeline_schedule_executions" + } + } + } + }, + "404": { + "description": "The account or repository was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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.", + "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_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.", + "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": "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.", + "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": "Get SSH key pair", + "operationId": "getRepositoryPipelineSshKeyPair" + }, + "delete": { + "responses": { + "204": { + "description": "The SSH key pair was deleted." + }, + "404": { + "description": "The account, repository or SSH key pair was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Delete the repository SSH key pair.", + "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": "Delete SSH key pair", + "operationId": "deleteRepositoryPipelineKeyPair" + } + }, + "/repositories/{workspace}/{repo_slug}/pipelines_config/ssh/known_hosts/": { + "post": { + "responses": { + "201": { + "headers": { + "Location": { + "description": "The URL of the newly created pipeline known host.", + "schema": { + "type": "string" + } + } + }, + "description": "The known host was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_known_host" + } + } + } + }, + "404": { + "description": "The account or repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "409": { + "description": "A known host with the provided hostname already exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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.", + "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 known host to update.", + "required": true, + "name": "known_host_uuid", + "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.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_known_host" + } + } + } + }, + "404": { + "description": "The account, repository or known host with the specified UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Retrieve 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" + } + }, + { + "description": "The UUID of the known host to retrieve.", + "required": true, + "name": "known_host_uuid", + "in": "path", + "schema": { + "type": "string" + } + } + ], + "tags": ["Pipelines"], + "summary": "Get a known host", + "operationId": "getRepositoryPipelineKnownHost" + }, + "delete": { + "responses": { + "204": { + "description": "The known host was deleted." + }, + "404": { + "description": "The account, repository or known host with given UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Delete 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" + } + }, + { + "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/": { + "post": { + "responses": { + "201": { + "headers": { + "Location": { + "description": "The URL of the newly created pipeline variable.", + "schema": { + "type": "string" + } + } + }, + "description": "The variable was created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + } + }, + "404": { + "description": "The account or repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "409": { + "description": "A variable with the provided key already exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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.", + "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 update.", + "required": true, + "name": "variable_uuid", + "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.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + } + }, + "404": { + "description": "The account, repository or variable with the specified UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Retrieve 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 retrieve.", + "required": true, + "name": "variable_uuid", + "in": "path", + "schema": { + "type": "string" + } + } + ], + "tags": ["Pipelines"], + "summary": "Get a variable for a repository", + "operationId": "getRepositoryPipelineVariable" + }, + "delete": { + "responses": { + "204": { + "description": "The variable was deleted." + }, + "404": { + "description": "The account, repository or variable with given UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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}": { + "put": { + "responses": { + "204": { + "description": "An empty response." + } + }, + "parameters": [ + { + "required": true, + "description": "The repository container; either the workspace slug or the UUID in curly braces.", + "in": "path", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The repository.", + "in": "path", + "name": "repo_slug", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The key of the Connect app.", + "in": "path", + "name": "app_key", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The name of the property.", + "in": "path", + "name": "property_name", + "schema": { + "type": "string" + } + } + ], + "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" + }, + "delete": { + "responses": { + "204": { + "description": "An empty response." + } + }, + "parameters": [ + { + "required": true, + "description": "The repository container; either the workspace slug or the UUID in curly braces.", + "in": "path", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The repository.", + "in": "path", + "name": "repo_slug", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The key of the Connect app.", + "in": "path", + "name": "app_key", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The name of the property.", + "in": "path", + "name": "property_name", + "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" + }, + "get": { + "responses": { + "200": { + "description": "The value of the property.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/application_property" + } + } + } + } + }, + "parameters": [ + { + "required": true, + "description": "The repository container; either the workspace slug or the UUID in curly braces.", + "in": "path", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The repository.", + "in": "path", + "name": "repo_slug", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The key of the Connect app.", + "in": "path", + "name": "app_key", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The name of the property.", + "in": "path", + "name": "property_name", + "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" + } + }, + "/repositories/{workspace}/{repo_slug}/pullrequests": { + "get": { + "responses": { + "200": { + "description": "All pull requests on the specified repository.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pullrequests" + } + } + } + }, + "401": { + "description": "If the repository is private and the request was not authenticated." + }, + "404": { + "description": "If the specified repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "state", + "in": "query", + "description": "Only return pull requests that are in this state. This parameter can be repeated.", + "schema": { + "type": "string", + "enum": ["MERGED", "SUPERSEDED", "OPEN", "DECLINED"] + } + } + ], + "tags": ["Pullrequests"], + "summary": "List pull requests", + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "201": { + "description": "The newly created pull request.", + "headers": { + "Location": { + "description": "The URL of new newly created pull request.", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest" + } + } + } + }, + "400": { + "description": "If the input document was invalid, or if the caller lacks the privilege to create repositories under the targeted account.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "If the request was not authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest" + } + } + }, + "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"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/pullrequests/activity": { + "get": { + "responses": { + "200": { + "description": "The pull request activity log" + }, + "401": { + "description": "If the repository is private and the request was not authenticated." + }, + "404": { + "description": "If the specified repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "List a pull request activity log", + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/pullrequests/{pull_request_id}": { + "get": { + "responses": { + "200": { + "description": "The pull request object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest" + } + } + } + }, + "401": { + "description": "If the repository is private and the request was not authenticated." + }, + "404": { + "description": "If the repository or pull request does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "Get a pull request", + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the specified pull request." + }, + "put": { + "responses": { + "200": { + "description": "The updated pull request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest" + } + } + } + }, + "400": { + "description": "If the input document was invalid.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "401": { + "description": "If the request was not authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the repository or pull request id does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest" + } + } + }, + "description": "The pull request that is to be updated." + }, + "tags": ["Pullrequests"], + "summary": "Update a pull request", + "security": [ + { + "oauth2": ["pullrequest:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/pullrequests/{pull_request_id}/activity": { + "get": { + "responses": { + "200": { + "description": "The pull request activity log" + }, + "401": { + "description": "If the repository is private and the request was not authenticated." + }, + "404": { + "description": "If the specified repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "List a pull request activity log", + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/pullrequests/{pull_request_id}/approve": { + "delete": { + "responses": { + "204": { + "description": "An empty response indicating the authenticated user's approval has been withdrawn." + }, + "401": { + "description": "The request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified pull request or the repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "Unapprove a pull request", + "security": [ + { + "oauth2": ["pullrequest:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Redact the authenticated user's approval of the specified pull\nrequest." + }, + "post": { + "responses": { + "200": { + "description": "The `participant` object recording that the authenticated user approved the pull request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/participant" + } + } + } + }, + "401": { + "description": "The request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified pull request or the repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "Approve a pull request", + "security": [ + { + "oauth2": ["pullrequest:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Approve the specified pull request as the authenticated user." + }, + "parameters": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/pullrequests/{pull_request_id}/comments": { + "get": { + "responses": { + "200": { + "description": "A paginated list of comments made on the given pull request, in chronological order.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_pullrequest_comments" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the pull request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the pull request does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "List comments on a pull request", + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "201": { + "description": "The newly created comment.", + "headers": { + "Location": { + "description": "The URL of the new comment", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest_comment" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the pull request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the pull request does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest_comment" + } + } + }, + "description": "The comment object.", + "required": true + }, + "tags": ["Pullrequests"], + "summary": "Create a comment on a pull request", + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Creates a new pull request comment.\n\nReturns the newly created pull request comment." + }, + "parameters": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/pullrequests/{pull_request_id}/comments/{comment_id}": { + "delete": { + "responses": { + "204": { + "description": "Successful deletion." + }, + "403": { + "description": "If the authenticated user does not have access to delete the comment.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the comment does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "Delete a comment on a pull request", + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Deletes a specific pull request comment." + }, + "get": { + "responses": { + "200": { + "description": "The comment.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest_comment" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the pull request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the comment does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "Get a comment on a pull request", + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns a specific pull request comment." + }, + "put": { + "responses": { + "200": { + "description": "The updated comment.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest_comment" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the comment.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the comment does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest_comment" + } + } + }, + "description": "The contents of the updated comment.", + "required": true + }, + "tags": ["Pullrequests"], + "summary": "Update a comment on a pull request", + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Updates a specific pull request comment." + }, + "parameters": [ + { + "name": "comment_id", + "in": "path", + "description": "The id of the comment.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/pullrequests/{pull_request_id}/commits": { + "get": { + "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." + }, + "403": { + "description": "If the authenticated user does not have access to the pull request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the pull request does not exist or the source branch is from a forked repository which no longer exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "List commits on a pull request", + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/pullrequests/{pull_request_id}/decline": { + "post": { + "responses": { + "200": { + "description": "The pull request was successfully declined.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest" + } + } + } + }, + "555": { + "description": "If the decline took too long and timed out.\nIn this case the caller should retry the request later.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "Decline a pull request", + "security": [ + { + "oauth2": ["pullrequest:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Declines the pull request." + }, + "parameters": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/pullrequests/{pull_request_id}/diff": { + "get": { + "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"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/pullrequests/{pull_request_id}/diffstat": { + "get": { + "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"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/pullrequests/{pull_request_id}/merge": { + "post": { + "responses": { + "200": { + "description": "The pull request object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest" + } + } + } + }, + "202": { + "description": "In the Location header, the URL to poll for the pull request merge status" + }, + "555": { + "description": "If the merge took too long and timed out.\nIn this case the caller should retry the request later", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "async", + "in": "query", + "description": "Default value is false.\n\n\nWhen set to true, runs merge asynchronously and\nimmediately returns a 202 with polling link to\nthe task-status API in the Location header.\n\n\nWhen set to false, runs merge and waits for it to\ncomplete, returning 200 when it succeeds. If the\nduration of the merge exceeds a timeout threshold,\nthe API returns a 202 with polling link to the\ntask-status API in the Location header.", + "required": false, + "schema": { + "type": "boolean" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pullrequest_merge_parameters" + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "Merge a pull request", + "security": [ + { + "oauth2": ["pullrequest:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Merges the pull request." + }, + "parameters": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/pullrequests/{pull_request_id}/merge/task-status/{task_id}": { + "get": { + "responses": { + "200": { + "description": "Returns a task status if the merge is either pending or successful, and if it is successful, a pull request" + }, + "400": { + "description": "If the provided task ID does not relate to this pull request, or if something went wrong during the merge operation" + }, + "403": { + "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"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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": "task_id", + "in": "path", + "description": "ID of the merge task", + "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}/pullrequests/{pull_request_id}/patch": { + "get": { + "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"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/pullrequests/{pull_request_id}/request-changes": { + "delete": { + "responses": { + "204": { + "description": "An empty response indicating the authenticated user's request for change has been withdrawn." + }, + "401": { + "description": "The request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified pull request or the repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "Remove change request for a pull request", + "security": [ + { + "oauth2": ["pullrequest:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "" + }, + "post": { + "responses": { + "200": { + "description": "The `participant` object recording that the authenticated user requested changes on the pull request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/participant" + } + } + } + }, + "401": { + "description": "The request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified pull request or the repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Pullrequests"], + "summary": "Request changes for a pull request", + "security": [ + { + "oauth2": ["pullrequest:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "" + }, + "parameters": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/pullrequests/{pull_request_id}/statuses": { + "get": { + "responses": { + "200": { + "description": "A paginated list of all commit statuses for this pull request.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_commitstatuses" + } + } + } + }, + "401": { + "description": "If the repository is private and the request was not authenticated." + }, + "404": { + "description": "If the specified repository or pull request does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "q", + "in": "query", + "description": "Query string to narrow down the response as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "Field by which the results should be sorted as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).\nDefaults to `created_on`.\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "tags": ["Pullrequests", "Commit statuses"], + "summary": "List commit statuses for a pull request", + "security": [ + { + "oauth2": ["pullrequest"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns all statuses (e.g. build results) for the given pull\nrequest." + }, + "parameters": [ + { + "name": "pull_request_id", + "in": "path", + "description": "The id of the pull request.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/pullrequests/{pullrequest_id}/properties/{app_key}/{property_name}": { + "put": { + "responses": { + "204": { + "description": "An empty response." + } + }, + "parameters": [ + { + "required": true, + "description": "The repository container; either the workspace slug or the UUID in curly braces.", + "in": "path", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The repository.", + "in": "path", + "name": "repo_slug", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The pull request ID.", + "in": "path", + "name": "pullrequest_id", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The key of the Connect app.", + "in": "path", + "name": "app_key", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The name of the property.", + "in": "path", + "name": "property_name", + "schema": { + "type": "string" + } + } + ], + "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" + }, + "delete": { + "responses": { + "204": { + "description": "An empty response." + } + }, + "parameters": [ + { + "required": true, + "description": "The repository container; either the workspace slug or the UUID in curly braces.", + "in": "path", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The repository.", + "in": "path", + "name": "repo_slug", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The pull request ID.", + "in": "path", + "name": "pullrequest_id", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The key of the Connect app.", + "in": "path", + "name": "app_key", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The name of the property.", + "in": "path", + "name": "property_name", + "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" + }, + "get": { + "responses": { + "200": { + "description": "The value of the property.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/application_property" + } + } + } + } + }, + "parameters": [ + { + "required": true, + "description": "The repository container; either the workspace slug or the UUID in curly braces.", + "in": "path", + "name": "workspace", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The repository.", + "in": "path", + "name": "repo_slug", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The pull request ID.", + "in": "path", + "name": "pullrequest_id", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The key of the Connect app.", + "in": "path", + "name": "app_key", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The name of the property.", + "in": "path", + "name": "property_name", + "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" + } + }, + "/repositories/{workspace}/{repo_slug}/refs": { + "get": { + "responses": { + "200": { + "description": "A paginated list of refs matching any filter criteria that were provided.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_refs" + } + } + } + }, + "403": { + "description": "If the repository is private and the authenticated user does not have\naccess to it.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository does not exist.", + "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).", + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "\nField by which the results should be sorted as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering). The `name`\nfield is handled specially for refs in that, if specified as the sort field, it\nuses a natural sort order instead of the default lexicographical sort order. For example,\nit will return ['1.1', '1.2', '1.10'] instead of ['1.1', '1.10', '1.2'].", + "schema": { + "type": "string" + } + } + ], + "tags": ["Refs"], + "summary": "List branches and tags", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/refs/branches": { + "get": { + "responses": { + "200": { + "description": "A paginated list of branches matching any filter criteria that were provided.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_branches" + } + } + } + }, + "403": { + "description": "If the repository is private and the authenticated user does not have\naccess to it.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository does not exist.", + "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).", + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "\nField by which the results should be sorted as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering). The `name`\nfield is handled specially for branches in that, if specified as the sort field, it\nuses a natural sort order instead of the default lexicographical sort order. For example,\nit will return ['branch1', 'branch2', 'branch10'] instead of ['branch1', 'branch10', 'branch2'].", + "schema": { + "type": "string" + } + } + ], + "tags": ["Refs"], + "summary": "List open branches", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "201": { + "description": "The newly created branch object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/branch" + } + } + } + }, + "403": { + "description": "If the repository is private and the authenticated user does not have\naccess to it.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository or branch does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Refs"], + "summary": "Create a branch", + "security": [ + { + "oauth2": ["repository:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/refs/branches/{name}": { + "delete": { + "responses": { + "204": { + "description": "Indicates that the specified branch was successfully deleted." + }, + "403": { + "description": "If the repository is private and the authenticated user does not have\naccess to it.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository or branch does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Refs"], + "summary": "Delete a branch", + "security": [ + { + "oauth2": ["repository:write"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The branch object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/branch" + } + } + } + }, + "403": { + "description": "If the repository is private and the authenticated user does not have\naccess to it.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository or branch does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Refs"], + "summary": "Get a branch", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "name", + "in": "path", + "description": "The name of the branch.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/refs/tags": { + "get": { + "responses": { + "200": { + "description": "A paginated list of tags matching any filter criteria that were provided.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_tags" + } + } + } + }, + "403": { + "description": "If the repository is private and the authenticated user does not have\naccess to it.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository does not exist.", + "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).", + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "\nField by which the results should be sorted as per\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering). The `name`\nfield is handled specially for tags in that, if specified as the sort field, it\nuses a natural sort order instead of the default lexicographical sort order. For example,\nit will return ['1.1', '1.2', '1.10'] instead of ['1.1', '1.10', '1.2'].", + "schema": { + "type": "string" + } + } + ], + "tags": ["Refs"], + "summary": "List tags", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "201": { + "description": "The newly created tag.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/tag" + } + } + } + }, + "400": { + "description": "If the target hash is missing, ambiguous, or invalid, or if the name is not provided.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/tag" + } + } + }, + "required": true + }, + "tags": ["Refs"], + "summary": "Create a tag", + "security": [ + { + "oauth2": ["repository:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/refs/tags/{name}": { + "delete": { + "responses": { + "204": { + "description": "Indicates the specified tag was successfully deleted." + }, + "403": { + "description": "If the repository is private and the authenticated user does not have\naccess to it.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository or tag does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Refs"], + "summary": "Delete a tag", + "security": [ + { + "oauth2": ["repository:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Delete a tag in the specified repository.\n\nThe tag name should not include any prefixes (e.g. refs/tags)." + }, + "get": { + "responses": { + "200": { + "description": "The tag object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/tag" + } + } + } + }, + "403": { + "description": "If the repository is private and the authenticated user does not have\naccess to it.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "The specified repository or tag does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Refs"], + "summary": "Get a tag", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "name", + "in": "path", + "description": "The name of the tag.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/src": { + "get": { + "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", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_treeentries" + } + } + } + }, + "404": { + "description": "If the path or commit in the URL does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "format", + "in": "query", + "description": "Instead of returning the file's contents, return the (json) meta data for it.", + "required": false, + "schema": { + "type": "string", + "enum": ["meta"] + } + } + ], + "tags": ["Source", "Repositories"], + "summary": "Get the root directory of the main branch", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "201": { + "description": "\n" + }, + "403": { + "description": "If the authenticated user does not have write or admin access", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the repository does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "message", + "in": "query", + "description": "The commit message. When omitted, Bitbucket uses a canned string.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "author", + "in": "query", + "description": "\nThe raw string to be used as the new commit's author.\nThis string follows the format\n`Erik van Zijst `.\n\nWhen omitted, Bitbucket uses the authenticated user's\nfull/display name and primary email address. Commits cannot\nbe created anonymously.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "parents", + "in": "query", + "description": "\nA comma-separated list of SHA1s of the commits that should\nbe the parents of the newly created commit.\n\nWhen omitted, the new commit will inherit from and become\na child of the main branch's tip/HEAD commit.\n\nWhen more than one SHA1 is provided, the first SHA1\nidentifies the commit from which the content will be\ninherited.\".", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "files", + "in": "query", + "description": "\nOptional field that declares the files that the request is\nmanipulating. When adding a new file to a repo, or when\noverwriting an existing file, the client can just upload\nthe full contents of the file in a normal form field and\nthe use of this `files` meta data field is redundant.\nHowever, when the `files` field contains a file path that\ndoes not have a corresponding, identically-named form\nfield, then Bitbucket interprets that as the client wanting\nto replace the named file with the null set and the file is\ndeleted instead.\n\nPaths in the repo that are referenced in neither files nor\nan individual file field, remain unchanged and carry over\nfrom the parent to the new commit.\n\nThis API does not support renaming as an explicit feature.\nTo rename a file, simply delete it and recreate it under\nthe new name in the same commit.\n", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "branch", + "in": "query", + "description": "\nThe name of the branch that the new commit should be\ncreated on. When omitted, the commit will be created on top\nof the main branch and will become the main branch's new\nhead.\n\nWhen a branch name is provided that already exists in the\nrepo, then the commit will be created on top of that\nbranch. In this case, *if* a parent SHA1 was also provided,\nthen it is asserted that the parent is the branch's\ntip/HEAD at the time the request is made. When this is not\nthe case, a 409 is returned.\n\nWhen a new branch name is specified (that does not already\nexist in the repo), and no parent SHA1s are provided, then\nthe new commit will inherit from the current main branch's\ntip/HEAD commit, but not advance the main branch. The new\ncommit will be the new branch. When the request *also*\nspecifies a parent SHA1, then the new commit and branch\nare created directly on top of the parent commit,\nregardless of the state of the main branch.\n\nWhen a branch name is not specified, but a parent SHA1 is\nprovided, then Bitbucket asserts that it represents the\nmain branch's current HEAD/tip, or a 409 is returned.\n\nWhen a branch name is not specified and the repo is empty,\nthe new commit will become the repo's root commit and will\nbe on the main branch.\n\nWhen a branch name is specified and the repo is empty, the\nnew commit will become the repo's root commit and also\ndefine the repo's main branch going forward.\n\nThis API cannot be used to create additional root commits\nin non-empty repos.\n\nThe branch field cannot be repeated.\n\nAs a side effect, this API can be used to create a new\nbranch without modifying any files, by specifying a new\nbranch name in this field, together with `parents`, but\nomitting the `files` fields, while not sending any files.\nThis will create a new commit and branch with the same\ncontents as the first parent. The diff of this commit\nagainst its first parent will be empty.\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "tags": ["Source", "Repositories"], + "summary": "Create a commit by uploading a file", + "security": [ + { + "oauth2": ["repository:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/src/{commit}/{path}": { + "get": { + "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", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_treeentries" + } + } + } + }, + "404": { + "description": "If the path or commit in the URL does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "555": { + "description": "If the call times out, possibly because the specifiedrecursion depth is too large.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "format", + "in": "query", + "description": "If 'meta' is provided, returns the (json) meta data for the contents of the file. If 'rendered' is provided, returns the contents of a non-binary file in HTML-formatted rendered markup. Since Git does not generally track what text encoding scheme is used, this endpoint attempts to detect the most appropriate character encoding. While usually correct, determining the character encoding can be ambiguous which in exceptional cases can lead to misinterpretation of the characters. As such, the raw element in the response object should not be treated as equivalent to the file's actual contents.", + "required": false, + "schema": { + "type": "string", + "enum": ["meta", "rendered"] + } + }, + { + "name": "q", + "in": "query", + "description": "Optional filter expression as per [filtering and sorting](/cloud/bitbucket/rest/intro/#filtering).", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "Optional sorting parameter as per [filtering and sorting](/cloud/bitbucket/rest/intro/#sorting-query-results).", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "max_depth", + "in": "query", + "description": "If provided, returns the contents of the repository and its subdirectories recursively until the specified max_depth of nested directories. When omitted, this defaults to 1.", + "required": false, + "schema": { + "type": "integer" + } + } + ], + "tags": ["Source", "Repositories"], + "summary": "Get file or directory contents", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "commit", + "in": "path", + "description": "The commit's SHA1.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "path", + "in": "path", + "description": "Path to the file.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "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}/versions": { + "get": { + "responses": { + "200": { + "description": "The versions that have been defined in the issue tracker.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_versions" + } + } + } + }, + "404": { + "description": "The specified repository does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "List defined versions for issues", + "security": [ + { + "oauth2": ["issue"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/versions/{version_id}": { + "get": { + "responses": { + "200": { + "description": "The specified version object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/version" + } + } + } + }, + "404": { + "description": "The specified repository or version does not exist or does not have the issue tracker enabled.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Issue tracker"], + "summary": "Get a defined version for issues", + "security": [ + { + "oauth2": ["issue"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the specified issue tracker version object." + }, + "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": "version_id", + "in": "path", + "description": "The version's id", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "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}/watchers": { + "get": { + "responses": { + "200": { + "description": "A paginated list of all the watchers on the specified repository." + } + }, + "tags": ["Repositories"], + "summary": "List repositories watchers", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns a paginated list of all the watchers on the specified\nrepository." + }, + "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" + } + } + ] + }, + "/snippets": { + "get": { + "responses": { + "200": { + "description": "A paginated list of snippets.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_snippets" + } + } + } + }, + "404": { + "description": "If the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "role", + "in": "query", + "description": "Filter down the result based on the authenticated user's role (`owner`, `contributor`, or `member`).", + "required": false, + "schema": { + "type": "string", + "enum": ["owner", "contributor", "member"] + } + } + ], + "tags": ["Snippets"], + "summary": "List snippets", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "201": { + "description": "The newly created snippet object.", + "headers": { + "Location": { + "description": "The URL of the newly created snippet.", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + } + } + }, + "401": { + "description": "If the request was not authenticated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/snippet" + }, + "tags": ["Snippets"], + "summary": "Create a snippet", + "security": [ + { + "oauth2": ["snippet:write"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "A paginated list of snippets.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_snippets" + } + } + } + }, + "404": { + "description": "If the user does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "role", + "in": "query", + "description": "Filter down the result based on the authenticated user's role (`owner`, `contributor`, or `member`).", + "required": false, + "schema": { + "type": "string", + "enum": ["owner", "contributor", "member"] + } + } + ], + "tags": ["Snippets"], + "summary": "List snippets in a workspace", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "201": { + "description": "The newly created snippet object.", + "headers": { + "Location": { + "description": "The URL of the newly created snippet.", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + } + } + }, + "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 permission to create snippets in the specified workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "$ref": "#/components/requestBodies/snippet" + }, + "tags": ["Snippets"], + "summary": "Create a snippet for a workspace", + "security": [ + { + "oauth2": ["snippet:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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" + } + } + ] + }, + "/snippets/{workspace}/{encoded_id}": { + "delete": { + "responses": { + "204": { + "description": "If the snippet was deleted successfully." + }, + "401": { + "description": "If the snippet is private and the request was not authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If authenticated user does not have permission to delete the private snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Delete a snippet", + "security": [ + { + "oauth2": ["snippet:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Deletes a snippet and returns an empty response." + }, + "get": { + "responses": { + "200": { + "description": "The snippet object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + } + } + }, + "401": { + "description": "If the snippet is private and the request was not authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If authenticated user does not have access to the private snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "410": { + "description": "If the snippet marked as spam.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Get a snippet", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The updated snippet object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + } + } + }, + "401": { + "description": "If the snippet is private and the request was not authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If authenticated user does not have permission to update the private snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Update a snippet", + "security": [ + { + "oauth2": ["snippet:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "encoded_id", + "in": "path", + "description": "The snippet id.", + "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" + } + } + ] + }, + "/snippets/{workspace}/{encoded_id}/comments": { + "get": { + "responses": { + "200": { + "description": "A paginated list of snippet comments, ordered by creation date.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_snippet_comments" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "List comments on a snippet", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "201": { + "description": "The newly created comment.", + "headers": { + "Location": { + "description": "The URL of the new comment", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + } + }, + "description": "The contents of the new comment.", + "required": true + }, + "tags": ["Snippets"], + "summary": "Create a comment on a snippet", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "encoded_id", + "in": "path", + "description": "The snippet id.", + "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" + } + } + ] + }, + "/snippets/{workspace}/{encoded_id}/comments/{comment_id}": { + "delete": { + "responses": { + "204": { + "description": "Indicates the comment was deleted successfully." + }, + "403": { + "description": "If the authenticated user is not the author of the comment.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the comment or the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Delete a comment on a snippet", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Deletes a snippet comment.\n\nComments can only be removed by the comment author, snippet creator, or workspace admin." + }, + "get": { + "responses": { + "200": { + "description": "The specified comment.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/snippet_comment" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the comment or snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Get a comment on a snippet", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the specific snippet comment." + }, + "put": { + "responses": { + "200": { + "description": "The updated comment object." + }, + "403": { + "description": "If the authenticated user does not have access to the snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the comment or snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Update a comment on a snippet", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Updates a comment.\n\nComments can only be updated by their author." + }, + "parameters": [ + { + "name": "comment_id", + "in": "path", + "description": "The id of the comment.", + "required": true, + "schema": { + "type": "integer" + } + }, + { + "name": "encoded_id", + "in": "path", + "description": "The snippet id.", + "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" + } + } + ] + }, + "/snippets/{workspace}/{encoded_id}/commits": { + "get": { + "responses": { + "200": { + "description": "The paginated list of snippet commits.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_snippet_commits" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "List snippet changes", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the changes (commits) made on this snippet." + }, + "parameters": [ + { + "name": "encoded_id", + "in": "path", + "description": "The snippet id.", + "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" + } + } + ] + }, + "/snippets/{workspace}/{encoded_id}/commits/{revision}": { + "get": { + "responses": { + "200": { + "description": "The specified snippet commit.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/snippet_commit" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the commit or the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Get a previous snippet change", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the changes made on this snippet in this commit." + }, + "parameters": [ + { + "name": "encoded_id", + "in": "path", + "description": "The snippet id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "revision", + "in": "path", + "description": "The commit's SHA1.", + "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" + } + } + ] + }, + "/snippets/{workspace}/{encoded_id}/files/{path}": { + "get": { + "responses": { + "302": { + "description": "A redirect to the most recent revision of the specified file.", + "headers": { + "Location": { + "description": "The URL of the most recent file revision.", + "schema": { + "type": "string" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Get a snippet's raw file at HEAD", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "encoded_id", + "in": "path", + "description": "The snippet id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "path", + "in": "path", + "description": "Path to the file.", + "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" + } + } + ] + }, + "/snippets/{workspace}/{encoded_id}/watch": { + "delete": { + "responses": { + "204": { + "description": "Indicates the user stopped watching the snippet successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_users" + } + } + } + }, + "401": { + "description": "If the request was not authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Stop watching a snippet", + "security": [ + { + "oauth2": ["snippet:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Used to stop watching a specific snippet. Returns 204 (No Content)\nto indicate success." + }, + "get": { + "responses": { + "204": { + "description": "If the authenticated user is watching the snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_users" + } + } + } + }, + "404": { + "description": "If the snippet does not exist, or if the authenticated user is not watching the snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Check if the current user is watching a snippet", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "204": { + "description": "Indicates the authenticated user is now watching the snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_users" + } + } + } + }, + "401": { + "description": "If the request was not authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Watch a snippet", + "security": [ + { + "oauth2": ["snippet:write"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Used to start watching a specific snippet. Returns 204 (No Content)." + }, + "parameters": [ + { + "name": "encoded_id", + "in": "path", + "description": "The snippet id.", + "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" + } + } + ] + }, + "/snippets/{workspace}/{encoded_id}/watchers": { + "get": { + "responses": { + "200": { + "description": "The paginated list of users watching this snippet", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_users" + } + } + } + }, + "404": { + "description": "If the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "List users watching a snippet", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns a paginated list of all users watching a specific snippet.", + "deprecated": true + }, + "parameters": [ + { + "name": "encoded_id", + "in": "path", + "description": "The snippet id.", + "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" + } + } + ] + }, + "/snippets/{workspace}/{encoded_id}/{node_id}": { + "delete": { + "responses": { + "204": { + "description": "If the snippet was deleted successfully." + }, + "401": { + "description": "If the snippet is private and the request was not authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If authenticated user does not have permission to delete the private snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "405": { + "description": "If `{node_id}` is not the latest revision.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Delete a previous revision of a snippet", + "security": [ + { + "oauth2": ["snippet:write"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The snippet object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + } + } + }, + "401": { + "description": "If the snippet is private and the request was not authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If authenticated user does not have access to the private snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the snippet, or the revision does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Get a previous revision of a snippet", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The updated snippet object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + } + } + }, + "401": { + "description": "If the snippet is private and the request was not authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If authenticated user does not have permission to update the private snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the snippet or the revision does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "405": { + "description": "If `{node_id}` is not the latest revision.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/related": { + "schema": { + "$ref": "#/components/schemas/error" + } + }, + "multipart/form-data": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Update a previous revision of a snippet", + "security": [ + { + "oauth2": ["snippet:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "encoded_id", + "in": "path", + "description": "The snippet id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "node_id", + "in": "path", + "description": "A commit revision (SHA1).", + "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" + } + } + ] + }, + "/snippets/{workspace}/{encoded_id}/{node_id}/files/{path}": { + "get": { + "responses": { + "200": { + "description": "Returns the contents of the specified file.", + "headers": { + "Content-Type": { + "description": "The mime type as derived from the filename", + "schema": { + "type": "string" + } + }, + "Content-Disposition": { + "description": "attachment", + "schema": { + "type": "string" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have access to the snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the file or snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Get a snippet's raw file", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "encoded_id", + "in": "path", + "description": "The snippet id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "node_id", + "in": "path", + "description": "A commit revision (SHA1).", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "path", + "in": "path", + "description": "Path to the file.", + "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" + } + } + ] + }, + "/snippets/{workspace}/{encoded_id}/{revision}/diff": { + "get": { + "responses": { + "200": { + "description": "The raw diff contents." + }, + "403": { + "description": "If the authenticated user does not have access to the snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "path", + "in": "query", + "description": "When used, only one the diff of the specified file will be returned.", + "schema": { + "type": "string" + } + } + ], + "tags": ["Snippets"], + "summary": "Get snippet changes between versions", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "encoded_id", + "in": "path", + "description": "The snippet id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "revision", + "in": "path", + "description": "A revspec expression. This can simply be a commit SHA1, a ref name, or a compare expression like `staging..production`.", + "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" + } + } + ] + }, + "/snippets/{workspace}/{encoded_id}/{revision}/patch": { + "get": { + "responses": { + "200": { + "description": "The raw patch contents." + }, + "403": { + "description": "If the authenticated user does not have access to the snippet.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the snippet does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Snippets"], + "summary": "Get snippet patch between versions", + "security": [ + { + "oauth2": ["snippet"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "encoded_id", + "in": "path", + "description": "The snippet id.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "revision", + "in": "path", + "description": "A revspec expression. This can simply be a commit SHA1, a ref name, or a compare expression like `staging..production`.", + "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" + } + } + ] + }, + "/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/": { + "post": { + "responses": { + "201": { + "headers": { + "Location": { + "description": "The URL of the newly created pipeline variable.", + "schema": { + "type": "string" + } + } + }, + "description": "The created variable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + } + }, + "404": { + "description": "The account does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "409": { + "description": "A variable with the provided key already exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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" + } + } + } + } + }, + "parameters": [ + { + "description": "The account.", + "required": true, + "name": "username", + "in": "path", + "schema": { + "type": "string" + } + }, + { + "description": "The UUID of the variable.", + "required": true, + "name": "variable_uuid", + "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.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + } + }, + "404": { + "description": "The account or variable with the given UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "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", + "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/)." + }, + "delete": { + "responses": { + "204": { + "description": "The variable was deleted" + }, + "404": { + "description": "The account or the variable with the provided UUID does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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": { + "responses": { + "200": { + "description": "Successful search", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/search_result_page" + } + } + } + }, + "400": { + "description": "If the search request was invalid due to one of the\nfollowing reasons:\n\n* the specified type of target account doesn''t match the actual\naccount type;\n\n* malformed pagination properties;\n\n* missing or malformed search query, in the latter case an error\nkey will be returned in `error.data.key` property.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "Search is not enabled for the requested team, navigate to [https://bitbucket.org/search](https://bitbucket.org/search) to turn it on", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "429": { + "description": "Too many requests, try again later", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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": { + "responses": { + "200": { + "description": "The current user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + } + }, + "401": { + "description": "When the request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Users"], + "summary": "Get current user", + "security": [ + { + "oauth2": ["account"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the currently logged in user." + }, + "parameters": [] + }, + "/user/emails": { + "get": { + "responses": { + "default": { + "description": "Unexpected error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Users"], + "summary": "List email addresses for current user", + "security": [ + { + "oauth2": ["email"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns all the authenticated user's email addresses. Both\nconfirmed and unconfirmed." + }, + "parameters": [] + }, + "/user/emails/{email}": { + "get": { + "responses": { + "default": { + "description": "Unexpected error.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Users"], + "summary": "Get an email address for current user", + "security": [ + { + "oauth2": ["email"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "email", + "in": "path", + "description": "Email address of the user.", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/user/permissions/repositories": { + "get": { + "responses": { + "200": { + "description": "Repository permissions for the repositories a caller has explicit access to.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_repository_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/#filtering).", + "required": false, + "schema": { + "type": "string" + } + } + ], + "tags": ["Repositories"], + "summary": "List repository permissions for a user", + "security": [ + { + "oauth2": ["account", "repository"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "All of the workspace memberships for the authenticated user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_workspace_memberships" + } + } + } + }, + "401": { + "description": "The request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "q", + "in": "query", + "description": "\nQuery string to narrow down the response. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for details.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "\nName of a response property to sort results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#sorting-query-results)\nfor details.\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "tags": ["Workspaces"], + "summary": "List workspaces for the current user", + "security": [ + { + "oauth2": ["account"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The user object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/user" + } + } + } + }, + "404": { + "description": "If no user exists for the specified UUID, or if the specified account is a team account, not a personal account.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Users"], + "summary": "Get a user", + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "selected_user", + "in": "path", + "description": "This can either be the UUID of the account, surrounded by curly-braces, for\nexample: `{account UUID}`, OR an Atlassian Account ID.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/users/{selected_user}/pipelines_config/variables/": { + "post": { + "responses": { + "201": { + "headers": { + "Location": { + "description": "The URL of the newly created pipeline variable.", + "schema": { + "type": "string" + } + } + }, + "description": "The created variable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + } + }, + "404": { + "description": "The account does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "409": { + "description": "A variable with the provided key already exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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" + } + } + } + } + }, + "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.", + "required": true, + "name": "variable_uuid", + "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.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + } + }, + "404": { + "description": "The account or variable with the given UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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 retrieve.", + "required": true, + "name": "variable_uuid", + "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/)." + }, + "delete": { + "responses": { + "204": { + "description": "The variable was deleted" + }, + "404": { + "description": "The account or the variable with the provided UUID does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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}": { + "put": { + "responses": { + "204": { + "description": "An empty response." + } + }, + "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 key of the Connect app.", + "in": "path", + "name": "app_key", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The name of the property.", + "in": "path", + "name": "property_name", + "schema": { + "type": "string" + } + } + ], + "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" + }, + "delete": { + "responses": { + "204": { + "description": "An empty response." + } + }, + "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 key of the Connect app.", + "in": "path", + "name": "app_key", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The name of the property.", + "in": "path", + "name": "property_name", + "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" + }, + "get": { + "responses": { + "200": { + "description": "The value of the property.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/application_property" + } + } + } + } + }, + "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 key of the Connect app.", + "in": "path", + "name": "app_key", + "schema": { + "type": "string" + } + }, + { + "required": true, + "description": "The name of the property.", + "in": "path", + "name": "property_name", + "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" + } + }, + "/users/{selected_user}/search/code": { + "get": { + "responses": { + "200": { + "description": "Successful search", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/search_result_page" + } + } + } + }, + "400": { + "description": "If the search request was invalid due to one of the\nfollowing reasons:\n\n* the specified type of target account doesn''t match the actual\naccount type;\n\n* malformed pagination properties;\n\n* missing or malformed search query, in the latter case an error\nkey will be returned in `error.data.key` property.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "Search is not enabled for the requested user, navigate to [https://bitbucket.org/search](https://bitbucket.org/search) to turn it on", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "429": { + "description": "Too many requests, try again later", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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": { + "responses": { + "200": { + "description": "A list of the SSH keys associated with the account.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_ssh_user_keys" + } + } + } + }, + "403": { + "description": "If the specified user's keys are not accessible to the current user" + }, + "404": { + "description": "If the specified user does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Ssh"], + "summary": "List SSH keys", + "security": [ + { + "oauth2": ["account"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "201": { + "description": "The newly created SSH key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ssh_account_key" + } + } + } + }, + "400": { + "description": "If the submitted key or related value is invalid", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the current user does not have permission to add a key for the specified user" + }, + "404": { + "description": "If the specified user does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ssh_account_key" + } + } + }, + "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"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "selected_user", + "in": "path", + "description": "This can either be the UUID of the account, surrounded by curly-braces, for\nexample: `{account UUID}`, OR an Atlassian Account ID.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/users/{selected_user}/ssh-keys/{key_id}": { + "delete": { + "responses": { + "204": { + "description": "The key has been deleted" + }, + "400": { + "description": "If the submitted key or related value is invalid", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the current user does not have permission to add a key for the specified user" + }, + "404": { + "description": "If the specified user does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Ssh"], + "summary": "Delete a SSH key", + "security": [ + { + "oauth2": ["account:write"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The specific SSH key matching the user and UUID", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ssh_account_key" + } + } + } + }, + "403": { + "description": "If the specified user or key is not accessible to the current user" + }, + "404": { + "description": "If the specified user or key does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Ssh"], + "summary": "Get a SSH key", + "security": [ + { + "oauth2": ["account"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The newly updated SSH key.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ssh_account_key" + } + } + } + }, + "400": { + "description": "If the submitted key or related value is invalid", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "403": { + "description": "If the current user does not have permission to add a key for the specified user" + }, + "404": { + "description": "If the specified user does not exist", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ssh_account_key" + } + } + }, + "description": "The updated SSH key object" + }, + "tags": ["Ssh"], + "summary": "Update a SSH key", + "security": [ + { + "oauth2": ["account:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "key_id", + "in": "path", + "description": "The SSH key's UUID value.", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "selected_user", + "in": "path", + "description": "This can either be the UUID of the account, surrounded by curly-braces, for\nexample: `{account UUID}`, OR an Atlassian Account ID.\n", + "required": true, + "schema": { + "type": "string" + } + } + ] + }, + "/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": { + "responses": { + "200": { + "description": "The list of workspaces accessible by the authenticated user.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_workspaces" + } + } + } + }, + "401": { + "description": "The request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "parameters": [ + { + "name": "role", + "in": "query", + "description": "\n Filters the workspaces based on the authenticated user's role on each workspace.\n\n * **member**: returns a list of all the workspaces which the caller is a member of\n at least one workspace group or repository\n * **collaborator**: returns a list of workspaces which the caller has write access\n to at least one repository in the workspace\n * **owner**: returns a list of workspaces which the caller has administrator access\n ", + "required": false, + "schema": { + "type": "string", + "enum": ["owner", "collaborator", "member"] + } + }, + { + "name": "q", + "in": "query", + "description": "\nQuery string to narrow down the response. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#filtering) for details.", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "sort", + "in": "query", + "description": "\nName of a response property to sort results. See\n[filtering and sorting](/cloud/bitbucket/rest/intro/#sorting-query-results)\nfor details.\n", + "required": false, + "schema": { + "type": "string" + } + } + ], + "tags": ["Workspaces"], + "summary": "List workspaces for user", + "security": [ + { + "oauth2": ["account"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/workspace" + } + } + } + }, + "404": { + "description": "If no workspace exists for the specified name or UUID.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Workspaces"], + "summary": "Get a workspace", + "security": [ + { + "oauth2": [] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the requested workspace." + }, + "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/{workspace}/hooks": { + "get": { + "responses": { + "200": { + "description": "The paginated list of installed webhooks.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_webhook_subscriptions" + } + } + } + }, + "403": { + "description": "If the authenticated user is not an owner on the specified workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the specified workspace does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Workspaces", "Webhooks"], + "summary": "List webhooks for a workspace", + "security": [ + { + "oauth2": ["webhook"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns a paginated list of webhooks installed on this workspace." + }, + "post": { + "responses": { + "201": { + "description": "If the webhook was registered successfully.", + "headers": { + "Location": { + "description": "The URL of new newly created webhook.", + "schema": { + "type": "string" + } + } + }, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/webhook_subscription" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have permission to install webhooks on the specified workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the specified workspace does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Workspaces", "Webhooks"], + "summary": "Create a webhook for a workspace", + "security": [ + { + "oauth2": ["webhook"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/hooks/{uid}": { + "delete": { + "responses": { + "204": { + "description": "When the webhook was deleted successfully" + }, + "403": { + "description": "If the authenticated user does not have permission to delete the webhook.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the webhook or workspace does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Workspaces", "Webhooks"], + "summary": "Delete a webhook for a workspace", + "security": [ + { + "oauth2": ["webhook"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Deletes the specified webhook subscription from the given workspace." + }, + "get": { + "responses": { + "200": { + "description": "The webhook subscription object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/webhook_subscription" + } + } + } + }, + "404": { + "description": "If the webhook or workspace does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Workspaces", "Webhooks"], + "summary": "Get a webhook for a workspace", + "security": [ + { + "oauth2": ["webhook"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the webhook with the specified id installed on the given\nworkspace." + }, + "put": { + "responses": { + "200": { + "description": "The webhook subscription object.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/webhook_subscription" + } + } + } + }, + "403": { + "description": "If the authenticated user does not have permission to update the webhook.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "If the webhook or workspace does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Workspaces", "Webhooks"], + "summary": "Update a webhook for a workspace", + "security": [ + { + "oauth2": ["webhook"] + }, + { + "basic": [] + }, + { + "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": [ + { + "name": "uid", + "in": "path", + "description": "Installed webhook's ID", + "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}/members": { + "get": { + "responses": { + "200": { + "description": "The list of users that are part of a workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_workspace_memberships" + } + } + } + }, + "401": { + "description": "The request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Workspaces"], + "summary": "List users in a workspace", + "security": [ + { + "oauth2": ["account"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns all members of the requested workspace." + }, + "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/{workspace}/members/{member}": { + "get": { + "responses": { + "200": { + "description": "The user that is part of a workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/workspace_membership" + } + } + } + }, + "401": { + "description": "The request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "A workspace cannot be found, or a user cannot be found, or the user is not a a member of the workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Workspaces"], + "summary": "Get user membership for a workspace", + "security": [ + { + "oauth2": ["account"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the workspace membership, which includes\na `User` object for the member and a `Workspace` object\nfor the requested workspace." + }, + "parameters": [ + { + "name": "member", + "in": "path", + "description": "Member's UUID or Atlassian ID.", + "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}/permissions": { + "get": { + "responses": { + "200": { + "description": "The list of users that are part of a workspace, along with their permission.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_workspace_memberships" + } + } + } + }, + "401": { + "description": "The request wasn't authenticated.", + "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" + } + } + ], + "tags": ["Workspaces"], + "summary": "List user permissions in a workspace", + "security": [ + { + "oauth2": ["account"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/permissions/repositories": { + "get": { + "responses": { + "200": { + "description": "List of workspace's repository permissions.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_repository_permissions" + } + } + } + }, + "403": { + "description": "The requesting user isn't an admin of the workspace.", + "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": ["Workspaces"], + "summary": "List all repository permissions for a workspace", + "security": [ + { + "oauth2": ["account"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}/permissions/repositories/{repo_slug}": { + "get": { + "responses": { + "200": { + "description": "The repository permission for all users in this repository.", + "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": ["Workspaces"], + "summary": "List a repository permissions for a workspace", + "security": [ + { + "oauth2": ["repository"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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" + } + } + ] + }, + "/workspaces/{workspace}/pipelines-config/identity/oidc/.well-known/openid-configuration": { + "get": { + "responses": { + "200": { + "description": "The OpenID configuration" + }, + "404": { + "description": "The workspace was not found.", + "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" + } + } + ], + "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" + }, + "404": { + "description": "The workspace was not found.", + "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" + } + } + ], + "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": { + "post": { + "responses": { + "201": { + "headers": { + "Location": { + "description": "The URL of the newly created pipeline variable.", + "schema": { + "type": "string" + } + } + }, + "description": "The created variable.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + } + }, + "404": { + "description": "The workspace does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "409": { + "description": "A variable with the provided key already exists.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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.", + "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.", + "required": true, + "name": "variable_uuid", + "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.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + } + }, + "404": { + "description": "The workspace or variable with the given UUID was not found.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "description": "Retrieve 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 retrieve.", + "required": true, + "name": "variable_uuid", + "in": "path", + "schema": { + "type": "string" + } + } + ], + "tags": ["Pipelines"], + "summary": "Get variable for a workspace", + "operationId": "getPipelineVariableForWorkspace" + }, + "delete": { + "responses": { + "204": { + "description": "The variable was deleted" + }, + "404": { + "description": "The workspace or the variable with the provided UUID does not exist.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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": { + "responses": { + "200": { + "description": "The list of projects in this workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/paginated_projects" + } + } + } + }, + "404": { + "description": "A workspace doesn't exist at this location.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "tags": ["Workspaces"], + "summary": "List projects in a workspace", + "security": [ + { + "oauth2": ["project"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the list of projects in this workspace." + }, + "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 workspace 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 in a workspace", + "security": [ + { + "oauth2": ["project:write"] + }, + { + "basic": [] + }, + { + "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": [ + { + "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}": { + "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 for a workspace", + "security": [ + { + "oauth2": ["project:write"] + }, + { + "basic": [] + }, + { + "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": { + "responses": { + "200": { + "description": "The project that is part of a workspace.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/project" + } + } + } + }, + "401": { + "description": "The request wasn't authenticated.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "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", "Workspaces"], + "summary": "Get a project for a workspace", + "security": [ + { + "oauth2": ["project"] + }, + { + "basic": [] + }, + { + "api_key": [] + } + ], + "description": "Returns the requested project." + }, + "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 workspace 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 for a workspace", + "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#### 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": [ + { + "name": "project_key", + "in": "path", + "description": "The project in question. This is the actual `key` assigned\nto the project.\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}/search/code": { + "get": { + "responses": { + "200": { + "description": "Successful search", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/search_result_page" + } + } + } + }, + "400": { + "description": "If the search request was invalid due to one of the\nfollowing reasons:\n\n* the specified type of target account doesn''t match the actual\naccount type;\n\n* malformed pagination properties;\n\n* missing or malformed search query, in the latter case an error\nkey will be returned in `error.data.key` property.\n", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "404": { + "description": "Search is not enabled for the requested workspace, navigate to [https://bitbucket.org/search](https://bitbucket.org/search) to turn it on", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + }, + "429": { + "description": "Too many requests, try again later", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/error" + } + } + } + } + }, + "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" + } + } + }, + "tags": [ + { + "name": "Addon", + "description": "The addon resource is intended to use used by Bitbucket Cloud Connect\nApps, and only supports JWT authentication.\n" + }, + { + "name": "Branch restrictions", + "description": "Repository owners and administrators can set branch management\nrules on a repository that control what can be pushed by whom.\nThrough these rules, you can enforce a project or team\nworkflow. For example, owners or administrators can:\n\n* Limit push powers\n* Prevent branch deletion\n* Prevent history re-writes (Git only)\n" + }, + { + "name": "Branching model", + "description": "The branching model resource is used to modify the branching model\nfor a repository.\n\nYou can use the branching model to define a branch based workflow\nfor your repositories. When you map your workflow to branch types,\nyou can ensure that branches are named consistently by configuring\nwhich branch types to make available.\n" + }, + { + "name": "Commit statuses", + "description": "Commit statuses provide a way to tag commits with meta data,\nlike automated build results.\n" + }, + { + "name": "Commits", + "description": "These are the repository's commits. They are paginated and returned in\nreverse chronological order, similar to the output of git log.\n" + }, + { + "name": "Deployments", + "description": "Teams are deploying code faster than ever, thanks to continuous\ndelivery practices and tools like Bitbucket Pipelines. Bitbucket\nDeployments gives teams visibility into their deployment\nenvironments and helps teams to track how far changes have\nprogressed in their deployment pipeline.\n" + }, + { + "name": "Downloads", + "description": "Access the list of download links associated with the repository." + }, + { + "name": "Issue tracker", + "description": "The issue resources provide functionality for getting information on\nissues in an issue tracker, creating new issues, updating them and deleting\nthem.\n\nYou can access public issues without authentication, but you can't gain access\nto private repositories' issues. By authenticating, you will get the ability\nto create issues, as well as access to updating data or deleting issues you\nhave access to.\n" + }, + { + "name": "Pipelines", + "description": "Bitbucket Pipelines brings continuous delivery to Bitbucket\nCloud, empowering teams with full branching to deployment\nvisibility and faster feedback loops.\n" + }, + { + "name": "Projects", + "description": "Bitbucket Cloud projects make it easier for teams to focus on\na goal, product, or process by organizing their repositories.\n" + }, + { + "name": "Pullrequests", + "description": "Pull requests are a feature that makes it easier for developers\nto collaborate using Bitbucket. They provide a user-friendly web\ninterface for discussing proposed changes before integrating them\ninto the official project.\n" + }, + { + "name": "Refs", + "description": "The refs resource allows you access branches and tags in a repository.\nBy default, results will be in the order the underlying source control\nsystem returns them and identical to the ordering one sees when running\n\"$ git show-ref\". Note that this follows simple lexical ordering of the\n ref names.\n" + }, + { + "name": "Reports", + "description": "Code insights provides reports, annotations, and metrics to help you\nand your team improve code quality in pull requests throughout the code\nreview process. Some of the available code insights are static analysis\nreports, security scan results, artifact links, unit tests, and build\nstatus.\n" + }, + { + "name": "Repositories", + "description": "A Git repository is a virtual storage of your project. It\nallows you to save versions of your code, which you can access\nwhen needed. The repo resource allows you to access public repos,\nor repos that belong to a specific workspace.\n" + }, + { + "name": "Snippets", + "description": "Snippets allow you share code segments or files with yourself, members of\nyour workspace, or the world.\n\nLike pull requests, repositories and workspaces, the full set of snippets\nis defined by what the current user has access to. This includes all\nsnippets owned by any of the workspaces the user is a member of, or\nsnippets by other users that the current user is either watching or has\n collaborated on (for instance by commenting on it).\n" + }, + { + "name": "Source", + "description": "Browse the source code in the repository and\n create new commits by uploading." + }, + { + "name": "Ssh", + "description": "The SSH resource allows you to manage SSH keys.\n" + }, + { + "name": "Teams", + "description": "The teams resource has been deprecated, and the workspaces\nendpoint should be used instead.\n\nThe teams resource returns all the teams that the authenticated\nuser is associated with.\n" + }, + { + "name": "Users", + "description": "The users resource allows you to access public information\nassociated with a user account. Most resources in the users\nendpoint have been deprecated in favor of workspaces.\n" + }, + { + "name": "Webhooks", + "description": "Webhooks provide a way to configure Bitbucket Cloud to make requests to\nyour server (or another external service) whenever certain events occur in\nBitbucket Cloud.\n\nA webhook consists of:\n\n* A subject -- The resource that generates the events. Currently, this resource\nis the repository, user account, or team where you create the webhook.\n* One or more event -- The default event is a repository push, but you can\nselect multiple events that can trigger the webhook.\n* A URL -- The endpoint where you want Bitbucket to send the event payloads\nwhen a matching event happens.\n\nThere are two parts to getting a webhook to work: creating the webhook and\ntriggering the webhook. After you create a webhook for an event, every time\nthat event occurs, Bitbucket sends a payload request that describes the event\nto the specified URL. Thus, you can think of webhooks as a kind of\nnotification system.\n\nUse webhooks to integrate applications with Bitbucket Cloud. The following\nuse cases provides examples of when you would want to use webhooks:\n\n* Every time a user pushes commits in a repository, you may want to notify\nyour CI server to start a build.\n* Every time a user pushes commits or creates a pull request, you may want to\ndisplay a notification in your application.\n" + }, + { + "name": "Wiki", + "description": "The wiki is a simple place to keep documents. Some people use it\nas their project home page. The wiki is a Git repository, so you\ncan clone it and edit it like any other source files.\n" + }, + { + "name": "Workspaces", + "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-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", + "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=" + }, + { + "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", + "description": "Query the 2.0 API for specific objects", + "icon": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgMTk0LjE5MTkgMTQ3LjYwOTIiPgogIDxkZWZzPgogICAgPHN0eWxlPgogICAgICAuY2xzLTEgewogICAgICAgIGlzb2xhdGlvbjogaXNvbGF0ZTsKICAgICAgfQoKICAgICAgLmNscy0yIHsKICAgICAgICBmaWxsOiAjY2ZkNGRiOwogICAgICB9CgogICAgICAuY2xzLTMsIC5jbHMtNCB7CiAgICAgICAgZmlsbDogIzg3NzdkOTsKICAgICAgfQoKICAgICAgLmNscy00IHsKICAgICAgICBtaXgtYmxlbmQtbW9kZTogbXVsdGlwbHk7CiAgICAgIH0KCiAgICAgIC5jbHMtNSB7CiAgICAgICAgZmlsbDogIzAwNjVmZjsKICAgICAgfQoKICAgICAgLmNscy02IHsKICAgICAgICBmaWxsOiAjY2NlMGZmOwogICAgICB9CgogICAgICAuY2xzLTcgewogICAgICAgIGZpbGw6IHVybCgjbGluZWFyLWdyYWRpZW50KTsKICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0ibGluZWFyLWdyYWRpZW50IiB4MT0iNDE2LjMwODIiIHkxPSI3NS4wNDc5IiB4Mj0iNTg0Ljg1NTYiIHkyPSI3NS4wNDc5IiBncmFkaWVudFRyYW5zZm9ybT0idHJhbnNsYXRlKC00NDMuOTQ2NyAxMjMuMDY4Nikgcm90YXRlKC0xMy43OTc2KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPgogICAgICA8c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNmZmYiLz4KICAgICAgPHN0b3Agb2Zmc2V0PSIwLjY5MDgiIHN0b3AtY29sb3I9IiNmZmYiIHN0b3Atb3BhY2l0eT0iMC4xIi8+CiAgICA8L2xpbmVhckdyYWRpZW50PgogIDwvZGVmcz4KICA8dGl0bGU+TWFnbmlmeWluZyBHbGFzczwvdGl0bGU+CiAgPGcgY2xhc3M9ImNscy0xIj4KICAgIDxnIGlkPSJMYXllcl8yIiBkYXRhLW5hbWU9IkxheWVyIDIiPgogICAgICA8ZyBpZD0iT2JqZWN0cyI+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy0yIiBkPSJNMTMxLjExMjUsOTQuOTMwN2wtOS44ODc4LTUuOTg4OC04LjMyOTIsMTMuNzUxOSw5Ljg4NzgsNS45ODg4YTE1LjYwMywxNS42MDMsMCwwLDEsNS44OCw2LjM4MzVoMGExNS42MDMsMTUuNjAzLDAsMCwwLDUuODgsNi4zODM1bDQwLjExNDMsMjQuMjk2NGExMi44NjY0LDEyLjg2NjQsMCwwLDAsMTcuNjcwOS00LjM0aDBhMTIuODY2NCwxMi44NjY0LDAsMCwwLTQuMzQtMTcuNjcwOUwxNDcuODc0OSw5OS40MzkyYTE1LjYwMywxNS42MDMsMCwwLDAtOC4zODEyLTIuMjU0MmgwQTE1LjYwMywxNS42MDMsMCwwLDEsMTMxLjExMjUsOTQuOTMwN1oiLz4KICAgICAgICA8cGF0aCBpZD0iX1BhdGhfIiBkYXRhLW5hbWU9IiZsdDtQYXRoJmd0OyIgY2xhc3M9ImNscy0zIiBkPSJNMTMxLjExMjUsOTQuOTMwN2wtMy4wMTE4LTEuODI0MkE4LjAzODgsOC4wMzg4LDAsMCwwLDExNy4wNiw5NS44MTc4aDBhOC4wMzg4LDguMDM4OCwwLDAsMCwyLjcxMTMsMTEuMDQwNWwzLjAxMTgsMS44MjQyYTE1LjYwMywxNS42MDMsMCwwLDEsNS44OCw2LjM4MzVoMGExNS42MDMsMTUuNjAzLDAsMCwwLDUuODgsNi4zODM1bDQwLjExNDMsMjQuMjk2NGExMi44NjY0LDEyLjg2NjQsMCwwLDAsMTcuNjcwOS00LjM0aDBhMTIuODY2NCwxMi44NjY0LDAsMCwwLTQuMzQtMTcuNjcwOUwxNDcuODc0OSw5OS40MzkyYTE1LjYwMywxNS42MDMsMCwwLDAtOC4zODEyLTIuMjU0M2gwQTE1LjYwMywxNS42MDMsMCwwLDEsMTMxLjExMjUsOTQuOTMwN1oiLz4KICAgICAgICA8cGF0aCBjbGFzcz0iY2xzLTQiIGQ9Ik0xMzkuMTQzNyw5Ny4xNzkyYTE1LjU5NzMsMTUuNTk3MywwLDAsMS04LjAzMTItMi4yNDg1bC0zLjAxMTgtMS44MjQyQTguMDM4OCw4LjAzODgsMCwwLDAsMTE3LjA2LDk1LjgxNzhoMGE4LjAzODgsOC4wMzg4LDAsMCwwLDIuNzExMywxMS4wNDA1bDMuMDExOCwxLjgyNDJhMTUuNTk3LDE1LjU5NywwLDAsMSw1LjcwNjksNi4wNjQ4LDY3Ljg0ODEsNjcuODQ4MSwwLDAsMCwxMC42NTM2LTE3LjU2ODFaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy01IiBkPSJNODMuMjUzNywxMzIuNTU2QTY3LjIzNDgsNjcuMjM0OCwwLDAsMSw5LjcxLDMyLjQyOTUsNjYuNzk3NCw2Ni43OTc0LDAsMCwxLDUxLjE4MzcsMS45NjY2bC4wMDA3LDBBNjYuNzk2Miw2Ni43OTYyLDAsMCwxLDEwMi4wNTEsOS43NTI1aDBBNjcuMjM0Niw2Ny4yMzQ2LDAsMCwxLDgzLjI1MzcsMTMyLjU1NloiLz4KICAgICAgICA8cGF0aCBpZD0iX1BhdGhfMiIgZGF0YS1uYW1lPSImbHQ7UGF0aCZndDsiIGNsYXNzPSJjbHMtNiIgZD0iTTIzLjQzOSw0MC43NDgyQTUxLjE5MDgsNTEuMTkwOCwwLDAsMCwxMTEuMDEsOTMuNzg4OSw1MS4xOTA4LDUxLjE5MDgsMCwwLDAsMjMuNDM5LDQwLjc0ODJaIi8+CiAgICAgICAgPHBhdGggY2xhc3M9ImNscy03IiBkPSJNNzkuNDMzLDExNi45ODJBNTEuMjE2Miw1MS4yMTYyLDAsMCwwLDExOC40MjQxLDY3LjAzN2E0OS4xMzkxLDQ5LjEzOTEsMCwwLDEtNS4wODY3LDIuMjc4OWMtMTUuNzAyOSw1Ljk2MDktMjkuNjg5NSwyLjExLTM2LjQ5ODcuMTMwOC0yMC40MzA3LTUuOTM5LTI0Ljc5LTE3LjM3ODUtMzkuMDQxNC0yNC41ODIzYTQ4LjMwOTIsNDguMzA5MiwwLDAsMC0xNC4wOTM5LTQuNTNjLS4wODYyLjEzOTUtLjE3OTMuMjczLS4yNjQ0LjQxMzVBNTEuMTkwNyw1MS4xOTA3LDAsMCwwLDc5LjQzMywxMTYuOTgyWiIvPgogICAgICA8L2c+CiAgICA8L2c+CiAgPC9nPgo8L3N2Zz4K" + }, + { + "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", + "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+" + }, + { + "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", + "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" + }, + { + "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", + "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" + }, + { + "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", + "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=" + }, + { + "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", + "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=" + }, + { + "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", + "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==" + } + ] + }, + "servers": [ + { + "url": "https://api.bitbucket.org/2.0" + } + ], + "components": { + "requestBodies": { + "application_property": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/application_property" + } + } + }, + "description": "The application property to create or update.", + "required": true + }, + "pipeline_variable": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + }, + "description": "The updated variable.", + "required": true + }, + "snippet": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/snippet" + } + } + }, + "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": { + "schema": { + "$ref": "#/components/schemas/pipeline_variable" + } + } + }, + "description": "The variable to create." + }, + "project": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/project" + } + } + }, + "required": true + } + }, + "securitySchemes": { + "basic": { + "type": "http", + "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", + "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", + "team": "Read your team membership information", + "team:write": "Read and modify your team membership information", + "account:write": "Read and modify your account information" + } + } + } + } + }, + "schemas": { + "account": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "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" + }, + "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." + }, + "username": { + "type": "string", + "pattern": "^[a-zA-Z0-9_\\-]+$" + }, + "uuid": { + "type": "string" + }, + "website": { + "type": "string" + } + }, + "additionalProperties": true + } + ], + "title": "Account", + "description": "An account object." + }, + "application_property": { + "additionalProperties": true, + "type": "object", + "title": "Application Property", + "description": "An application property. It is a caller defined JSON object that Bitbucket will store and return. \nThe `_attributes` field at its top level can be used to control who is allowed to read and update the property. \nThe keys of the JSON object must match an allowed pattern. For details, \nsee [Application properties](https://developer.atlassian.com/cloud/bitbucket/application-properties/).\n", + "properties": { + "_attributes": { + "type": "array", + "items": { + "type": "string", + "enum": ["public", "read_only"] + } + } + } + }, + "author": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "raw": { + "type": "string", + "description": "The raw author value from the repository. This may be the only value available if the author does not match a user in Bitbucket." + }, + "user": { + "$ref": "#/components/schemas/account" + } + }, + "additionalProperties": true + } + ], + "title": "Author", + "description": "The author of a change in a repository" + }, + "base_commit": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "author": { + "$ref": "#/components/schemas/author" + }, + "date": { + "type": "string", + "format": "date-time" + }, + "hash": { + "type": "string", + "pattern": "[0-9a-f]{7,}?" + }, + "message": { + "type": "string" + }, + "parents": { + "type": "array", + "items": { + "$ref": "#/components/schemas/base_commit" + }, + "minItems": 0 + }, + "summary": { + "type": "object", + "properties": { + "html": { + "type": "string", + "description": "The user's content rendered as HTML." + }, + "markup": { + "type": "string", + "description": "The type of markup language the raw content is to be interpreted in.", + "enum": ["markdown", "creole", "plaintext"] + }, + "raw": { + "type": "string", + "description": "The text as it was typed by a user." + } + }, + "additionalProperties": false + } + }, + "additionalProperties": true + } + ], + "title": "Base Commit", + "description": "The common base type for both repository and snippet commits." + }, + "branch": { + "allOf": [ + { + "$ref": "#/components/schemas/ref" + }, + { + "type": "object", + "properties": { + "default_merge_strategy": { + "type": "string", + "description": "The default merge strategy for pull requests targeting this branch." + }, + "merge_strategies": { + "type": "array", + "description": "Available merge strategies for pull requests targeting this branch.", + "items": { + "type": "string", + "enum": ["merge_commit", "squash", "fast_forward"] + } + } + }, + "additionalProperties": true + } + ], + "title": "Branch", + "description": "A branch object, representing a branch in a repository." + }, + "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" + }, + "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." + }, + "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" + }, + "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." + }, + "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": "Branching Model", + "description": "A repository's branching model" + }, + "branching_model_settings": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "branch_types": { + "type": "array", + "items": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether the branch type is enabled or not. A disabled branch type may contain an invalid `prefix`." + }, + "kind": { + "type": "string", + "description": "The kind of the branch type.", + "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` of an enabled branch type must be a valid branch prefix.Additionally, it cannot be blank, empty or `null`. The `prefix` for a disabled branch type can be empty or invalid." + } + }, + "required": ["kind"], + "additionalProperties": false + }, + "minItems": 0, + "maxItems": 4, + "uniqueItems": true + }, + "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." + }, + "name": { + "type": "string", + "description": "The configured branch. It must be `null` when `use_mainbranch` is `true`. Otherwise it must be a non-empty value. It is possible for the configured branch to not exist (e.g. it was deleted after the settings are set). In this case `is_valid` will be `false`. The branch must exist when updating/setting the `name` or an error will occur." + }, + "use_mainbranch": { + "type": "boolean", + "description": "Indicates if the setting points at an explicit branch (`false`) or tracks the main branch (`true`). When `true` the `name` must be `null` or not provided. When `false` the `name` must contain a non-empty branch name." + } + }, + "additionalProperties": false + }, + "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 + }, + "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." + }, + "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." + }, + "name": { + "type": "string", + "description": "The configured branch. It must be `null` when `use_mainbranch` is `true`. Otherwise it must be a non-empty value. It is possible for the configured branch to not exist (e.g. it was deleted after the settings are set). In this case `is_valid` will be `false`. The branch must exist when updating/setting the `name` or an error will occur." + }, + "use_mainbranch": { + "type": "boolean", + "description": "Indicates if the setting points at an explicit branch (`false`) or tracks the main branch (`true`). When `true` the `name` must be `null` or not provided. When `false` the `name` must contain a non-empty branch name." + } + }, + "additionalProperties": false + } + }, + "additionalProperties": true + } + ], + "title": "Branching Model Settings", + "description": "A repository's branching model settings" + }, + "branchrestriction": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "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": { + "$ref": "#/components/schemas/group" + }, + "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 + } + ], + "title": "Branch Restriction", + "description": "A branch restriction rule." + }, + "comment": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "content": { + "type": "object", + "properties": { + "html": { + "type": "string", + "description": "The user's content rendered as HTML." + }, + "markup": { + "type": "string", + "description": "The type of markup language the raw content is to be interpreted in.", + "enum": ["markdown", "creole", "plaintext"] + }, + "raw": { + "type": "string", + "description": "The text as it was typed by a user." + } + }, + "additionalProperties": false + }, + "created_on": { + "type": "string", + "format": "date-time" + }, + "deleted": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "inline": { + "type": "object", + "properties": { + "from": { + "type": "integer", + "description": "The comment's anchor line in the old version of the file.", + "minimum": 1 + }, + "path": { + "type": "string", + "description": "The path of the file this comment is anchored to." + }, + "to": { + "type": "integer", + "description": "The comment's anchor line in the new version of the file. If the 'from' line is also provided, this value will be removed.", + "minimum": 1 + } + }, + "required": ["path"], + "additionalProperties": false + }, + "links": { + "type": "object", + "properties": { + "code": { + "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 + }, + "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 + }, + "parent": { + "$ref": "#/components/schemas/comment" + }, + "updated_on": { + "type": "string", + "format": "date-time" + }, + "user": { + "$ref": "#/components/schemas/user" + } + }, + "additionalProperties": true + } + ], + "title": "Comment", + "description": "The base type for all comments. This type should be considered abstract. Each of the \"commentable\" resources defines its own subtypes (e.g. `issue_comment`)." + }, + "commit": { + "allOf": [ + { + "$ref": "#/components/schemas/base_commit" + }, + { + "type": "object", + "properties": { + "participants": { + "type": "array", + "items": { + "$ref": "#/components/schemas/participant" + }, + "minItems": 0 + }, + "repository": { + "$ref": "#/components/schemas/repository" + } + }, + "additionalProperties": true + } + ], + "title": "Commit", + "description": "A repository commit object." + }, + "commit_comment": { + "allOf": [ + { + "$ref": "#/components/schemas/comment" + }, + { + "type": "object", + "properties": { + "commit": { + "$ref": "#/components/schemas/commit" + } + }, + "additionalProperties": true + } + ], + "title": "Commit Comment", + "description": "A commit comment." + }, + "commit_file": { + "type": "object", + "title": "Commit File", + "description": "A file object, representing a file at a commit in a repository", + "properties": { + "attributes": { + "type": "string", + "enum": ["link", "executable", "subrepository", "binary", "lfs"] + }, + "commit": { + "$ref": "#/components/schemas/commit" + }, + "escaped_path": { + "type": "string", + "description": "The escaped version of the path as it appears in a diff. If the path does not require escaping this will be the same as path." + }, + "path": { + "type": "string", + "description": "The path in the repository" + }, + "type": { + "type": "string" + } + }, + "required": ["type"], + "additionalProperties": true + }, + "commitstatus": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "created_on": { + "type": "string", + "format": "date-time" + }, + "description": { + "type": "string", + "description": "A description of the build (e.g. \"Unit tests in Bamboo\")" + }, + "key": { + "type": "string", + "description": "An identifier for the status that's unique to\n its type (current \"build\" is the only supported type) and the vendor,\n e.g. BB-DEPLOY" + }, + "links": { + "type": "object", + "properties": { + "commit": { + "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 + }, + "name": { + "type": "string", + "description": "An identifier for the build itself, e.g. BB-DEPLOY-1" + }, + "refname": { + "type": "string", + "description": "\nThe name of the ref that pointed to this commit at the time the status\nobject was created. Note that this the ref may since have moved off of\nthe commit. This optional field can be useful for build systems whose\nbuild triggers and configuration are branch-dependent (e.g. a Pipeline\nbuild).\nIt is legitimate for this field to not be set, or even apply (e.g. a\nstatic linting job)." + }, + "state": { + "type": "string", + "description": "Provides some indication of the status of this commit", + "enum": ["SUCCESSFUL", "FAILED", "INPROGRESS", "STOPPED"] + }, + "updated_on": { + "type": "string", + "format": "date-time" + }, + "url": { + "type": "string", + "description": "A URL linking back to the vendor or build system, for providing more information about whatever process produced this status. Accepts context variables `repository` and `commit` that Bitbucket will evaluate at runtime whenever at runtime. For example, one could use `https://foo.com/builds/{repository.full_name}` which Bitbucket will turn into https://foo.com/builds/foo/bar at render time." + }, + "uuid": { + "type": "string", + "description": "The commit status' id." + } + }, + "additionalProperties": true + } + ], + "title": "Commit Status", + "description": "A commit status object." + }, + "component": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "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 + }, + "name": { + "type": "string" + } + }, + "additionalProperties": true + } + ], + "title": "Component", + "description": "A component as defined in a repository's issue tracker." + }, + "ddev_report": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A report for a commit." + } + ], + "x-bb-default-fields": ["uuid", "commitHash"], + "x-bb-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/commits/{commitHash}/reports/{uuid}" + }, + "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)" + }, + "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 + }, + "owner": { + "$ref": "#/components/schemas/account" + }, + "repository": { + "$ref": "#/components/schemas/repository" + } + }, + "additionalProperties": true + } + ], + "title": "Deploy Key", + "description": "Represents deploy key for a repository." + }, + "deployment": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "environment": { + "$ref": "#/components/schemas/deployment_environment" + }, + "release": { + "$ref": "#/components/schemas/deployment_release" + }, + "state": { + "$ref": "#/components/schemas/deployment_state" + }, + "uuid": { + "type": "string", + "description": "The UUID identifying the deployment." + } + } + } + ], + "title": "Deployment", + "description": "A Bitbucket Deployment." + }, + "deployment_environment": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the environment." + }, + "uuid": { + "type": "string", + "description": "The UUID identifying the environment." + } + } + } + ], + "x-bb-default-fields": ["uuid"], + "x-bb-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments/{uuid}", + "x-bb-batch-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments_batch", + "x-bb-batch-max-size": 100, + "title": "Deployment Environment", + "description": "A Bitbucket Deployment Environment." + }, + "deployment_environment_lock": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "environmentUuid": { + "type": "string", + "description": "The UUID identifying the environment." + } + } + } + ], + "x-bb-default-fields": ["*", "lock_opener.*", "owner.*"], + "x-bb-batch-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments/locks_batch", + "x-bb-batch-max-size": 100, + "title": "Deployment Environment Lock", + "description": "A Bitbucket Deployment Environment Lock." + }, + "deployment_release": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "commit": { + "$ref": "#/components/schemas/commit" + }, + "created_on": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the release was created." + }, + "name": { + "type": "string", + "description": "The name of the release." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Link to the pipeline that produced the release." + }, + "uuid": { + "type": "string", + "description": "The UUID identifying the release." + } + } + } + ], + "title": "Deployment Release", + "description": "A Bitbucket Deployment Release." + }, + "deployment_state": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": {} + } + ], + "title": "Deployment State", + "description": "The representation of the progress state of a deployment." + }, + "deployment_state_completed": { + "allOf": [ + { + "$ref": "#/components/schemas/deployment_state" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Deployment COMPLETED deployment state.", + "properties": { + "completion_date": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the deployment completed." + }, + "deployer": { + "$ref": "#/components/schemas/account" + }, + "name": { + "enum": ["COMPLETED"], + "type": "string", + "description": "The name of deployment state (COMPLETED)." + }, + "start_date": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the deployment was started." + }, + "status": { + "$ref": "#/components/schemas/deployment_state_completed_status" + }, + "url": { + "type": "string", + "format": "uri", + "description": "Link to the deployment result." + } + } + } + ] + }, + "deployment_state_completed_status": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": {} + } + ], + "title": "Completed Deployment", + "description": "The status of a completed deployment." + }, + "deployment_state_completed_status_failed": { + "allOf": [ + { + "$ref": "#/components/schemas/deployment_state_completed_status" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A FAILED completed deployment status.", + "properties": { + "name": { + "enum": ["FAILED"], + "type": "string", + "description": "The name of the completed deployment status (FAILED)." + } + } + } + ] + }, + "deployment_state_completed_status_stopped": { + "allOf": [ + { + "$ref": "#/components/schemas/deployment_state_completed_status" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A STOPPED completed deployment status.", + "properties": { + "name": { + "enum": ["STOPPED"], + "type": "string", + "description": "The name of the completed deployment status (STOPPED)." + } + } + } + ] + }, + "deployment_state_completed_status_successful": { + "allOf": [ + { + "$ref": "#/components/schemas/deployment_state_completed_status" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A SUCCESSFUL completed deployment status.", + "properties": { + "name": { + "enum": ["SUCCESSFUL"], + "type": "string", + "description": "The name of the completed deployment status (SUCCESSFUL)." + } + } + } + ] + }, + "deployment_state_in_progress": { + "allOf": [ + { + "$ref": "#/components/schemas/deployment_state" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Deployment IN_PROGRESS deployment state.", + "properties": { + "deployer": { + "$ref": "#/components/schemas/account" + }, + "name": { + "enum": ["IN_PROGRESS"], + "type": "string", + "description": "The name of deployment state (IN_PROGRESS)." + }, + "start_date": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the deployment was started." + }, + "url": { + "type": "string", + "format": "uri", + "description": "Link to the deployment result." + } + } + } + ] + }, + "deployment_state_undeployed": { + "allOf": [ + { + "$ref": "#/components/schemas/deployment_state" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Deployment UNDEPLOYED deployment state.", + "properties": { + "name": { + "enum": ["UNDEPLOYED"], + "type": "string", + "description": "The name of deployment state (UNDEPLOYED)." + }, + "trigger_url": { + "type": "string", + "format": "uri", + "description": "Link to trigger the deployment." + } + } + } + ] + }, + "deployment_variable": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "The unique name of the variable." + }, + "secured": { + "type": "boolean", + "description": "If true, this variable will be treated as secured. The value will never be exposed in the logs or the REST API." + }, + "uuid": { + "type": "string", + "description": "The UUID identifying the variable." + }, + "value": { + "type": "string", + "description": "The value of the variable. If the variable is secured, this will be empty." + } + } + } + ], + "title": "Deployment Variable", + "description": "A Pipelines deployment variable." + }, + "deployments_ddev_deployment_environment": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the environment." + }, + "uuid": { + "type": "string", + "description": "The UUID identifying the environment." + } + } + } + ], + "x-bb-default-fields": ["uuid"], + "x-bb-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments/{uuid}", + "x-bb-batch-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments_batch", + "x-bb-batch-max-size": 100, + "title": "Deployment Environment", + "description": "A Bitbucket Deployment Environment." + }, + "deployments_ddev_deployment_environment_lock": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "environmentUuid": { + "type": "string", + "description": "The UUID identifying the environment." + } + } + } + ], + "x-bb-default-fields": ["*", "lock_opener.*", "owner.*"], + "x-bb-batch-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments/locks_batch", + "x-bb-batch-max-size": 100, + "title": "Deployment Environment Lock", + "description": "A Bitbucket Deployment Environment Lock." + }, + "deployments_ddev_paginated_environments": { + "title": "Paginated Deployment Environments", + "description": "A paged list of environments", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/deployments_ddev_deployment_environment" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "deployments_stg_west_deployment_environment": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the environment." + }, + "uuid": { + "type": "string", + "description": "The UUID identifying the environment." + } + } + } + ], + "x-bb-default-fields": ["uuid"], + "x-bb-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments/{uuid}", + "x-bb-batch-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments_batch", + "x-bb-batch-max-size": 100, + "title": "Deployment Environment", + "description": "A Bitbucket Deployment Environment." + }, + "deployments_stg_west_deployment_environment_lock": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "environmentUuid": { + "type": "string", + "description": "The UUID identifying the environment." + } + } + } + ], + "x-bb-default-fields": ["*", "lock_opener.*", "owner.*"], + "x-bb-batch-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/environments/locks_batch", + "x-bb-batch-max-size": 100, + "title": "Deployment Environment Lock", + "description": "A Bitbucket Deployment Environment Lock." + }, + "deployments_stg_west_paginated_environments": { + "title": "Paginated Deployment Environments", + "description": "A paged list of environments", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/deployments_stg_west_deployment_environment" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "diffstat": { + "type": "object", + "title": "Diff Stat", + "description": "A diffstat object that includes a summary of changes made to a file between two commits.", + "properties": { + "lines_added": { + "type": "integer" + }, + "lines_removed": { + "type": "integer" + }, + "new": { + "$ref": "#/components/schemas/commit_file" + }, + "old": { + "$ref": "#/components/schemas/commit_file" + }, + "status": { + "type": "string", + "enum": ["added", "removed", "modified", "renamed"] + }, + "type": { + "type": "string" + } + }, + "required": ["type"], + "additionalProperties": true + }, + "error": { + "type": "object", + "title": "Error", + "description": "Base type for most resource objects. It defines the common `type` element that identifies an object's type. It also identifies the element as Swagger's `discriminator`.", + "properties": { + "error": { + "type": "object", + "properties": { + "data": { + "type": "object", + "description": "Optional structured data that is endpoint-specific.", + "properties": {}, + "additionalProperties": true + }, + "detail": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "required": ["message"], + "additionalProperties": false + }, + "type": { + "type": "string" + } + }, + "required": ["type"], + "additionalProperties": true + }, + "export_options": { + "type": "object", + "title": "Export Options", + "description": "Options for issue export.", + "properties": { + "include_attachments": { + "type": "boolean" + }, + "project_key": { + "type": "string" + }, + "project_name": { + "type": "string" + }, + "send_email": { + "type": "boolean" + }, + "type": { + "type": "string" + } + }, + "required": ["type"], + "additionalProperties": true + }, + "group": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "full_slug": { + "type": "string", + "description": "The concatenation of the workspace's slug and the group's slug,\nseparated with a colon (e.g. `acme:developers`)\n" + }, + "links": { + "type": "object", + "properties": { + "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 + }, + "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 + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/account" + }, + "slug": { + "type": "string", + "description": "The \"sluggified\" version of the group's name. This contains only ASCII\ncharacters and can therefore be slightly different than the name" + }, + "workspace": { + "$ref": "#/components/schemas/workspace" + } + }, + "additionalProperties": true + } + ], + "title": "Group", + "description": "A group object" + }, + "hook_event": { + "type": "object", + "title": "Hook Event", + "description": "An event, associated with a resource or subject type.", + "properties": { + "category": { + "type": "string", + "description": "The category this event belongs to." + }, + "description": { + "type": "string", + "description": "More detailed description of the webhook event type." + }, + "event": { + "type": "string", + "description": "The event identifier.", + "enum": [ + "pullrequest:unapproved", + "issue:comment_created", + "repo:imported", + "repo:created", + "repo:commit_comment_created", + "pullrequest:approved", + "pullrequest:comment_updated", + "issue:updated", + "project:updated", + "repo:deleted", + "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:updated", + "pullrequest:rejected", + "pullrequest:fulfilled", + "pullrequest:created", + "pullrequest:changes_request_removed", + "repo:transfer", + "repo:push" + ] + }, + "label": { + "type": "string", + "description": "Summary of the webhook event type." + } + }, + "additionalProperties": false + }, + "issue": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "assignee": { + "$ref": "#/components/schemas/user" + }, + "component": { + "$ref": "#/components/schemas/component" + }, + "content": { + "type": "object", + "properties": { + "html": { + "type": "string", + "description": "The user's content rendered as HTML." + }, + "markup": { + "type": "string", + "description": "The type of markup language the raw content is to be interpreted in.", + "enum": ["markdown", "creole", "plaintext"] + }, + "raw": { + "type": "string", + "description": "The text as it was typed by a user." + } + }, + "additionalProperties": false + }, + "created_on": { + "type": "string", + "format": "date-time" + }, + "edited_on": { + "type": "string", + "format": "date-time" + }, + "id": { + "type": "integer" + }, + "kind": { + "type": "string", + "enum": ["bug", "enhancement", "proposal", "task"] + }, + "links": { + "type": "object", + "properties": { + "attachments": { + "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 + }, + "comments": { + "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 + }, + "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 + }, + "vote": { + "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 + }, + "watch": { + "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 + }, + "milestone": { + "$ref": "#/components/schemas/milestone" + }, + "priority": { + "type": "string", + "enum": ["trivial", "minor", "major", "critical", "blocker"] + }, + "reporter": { + "$ref": "#/components/schemas/user" + }, + "repository": { + "$ref": "#/components/schemas/repository" + }, + "state": { + "type": "string", + "enum": [ + "new", + "open", + "resolved", + "on hold", + "invalid", + "duplicate", + "wontfix", + "closed" + ] + }, + "title": { + "type": "string" + }, + "updated_on": { + "type": "string", + "format": "date-time" + }, + "version": { + "$ref": "#/components/schemas/version" + }, + "votes": { + "type": "integer" + } + }, + "additionalProperties": true + } + ], + "title": "Issue", + "description": "An issue." + }, + "issue_attachment": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "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 + }, + "name": { + "type": "string" + } + }, + "additionalProperties": true + } + ], + "title": "Issue Attachment", + "description": "An issue file attachment's meta data. Note this does not contain the file's actual contents." + }, + "issue_change": { + "type": "object", + "title": "Issue Change", + "description": "An issue change.", + "properties": { + "changes": { + "type": "object", + "properties": { + "assignee": { + "type": "object", + "properties": { + "new": { + "type": "string" + }, + "old": { + "type": "string" + } + }, + "additionalProperties": false + }, + "component": { + "type": "object", + "properties": { + "new": { + "type": "string" + }, + "old": { + "type": "string" + } + }, + "additionalProperties": false + }, + "content": { + "type": "object", + "properties": { + "new": { + "type": "string" + }, + "old": { + "type": "string" + } + }, + "additionalProperties": false + }, + "kind": { + "type": "object", + "properties": { + "new": { + "type": "string" + }, + "old": { + "type": "string" + } + }, + "additionalProperties": false + }, + "milestone": { + "type": "object", + "properties": { + "new": { + "type": "string" + }, + "old": { + "type": "string" + } + }, + "additionalProperties": false + }, + "priority": { + "type": "object", + "properties": { + "new": { + "type": "string" + }, + "old": { + "type": "string" + } + }, + "additionalProperties": false + }, + "state": { + "type": "object", + "properties": { + "new": { + "type": "string" + }, + "old": { + "type": "string" + } + }, + "additionalProperties": false + }, + "title": { + "type": "object", + "properties": { + "new": { + "type": "string" + }, + "old": { + "type": "string" + } + }, + "additionalProperties": false + }, + "version": { + "type": "object", + "properties": { + "new": { + "type": "string" + }, + "old": { + "type": "string" + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "created_on": { + "type": "string", + "format": "date-time" + }, + "issue": { + "$ref": "#/components/schemas/issue" + }, + "links": { + "type": "object", + "properties": { + "issue": { + "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 + }, + "message": { + "type": "object", + "properties": { + "html": { + "type": "string", + "description": "The user's content rendered as HTML." + }, + "markup": { + "type": "string", + "description": "The type of markup language the raw content is to be interpreted in.", + "enum": ["markdown", "creole", "plaintext"] + }, + "raw": { + "type": "string", + "description": "The text as it was typed by a user." + } + }, + "additionalProperties": false + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/user" + } + }, + "required": ["type"], + "additionalProperties": true + }, + "issue_comment": { + "allOf": [ + { + "$ref": "#/components/schemas/comment" + }, + { + "type": "object", + "properties": { + "issue": { + "$ref": "#/components/schemas/issue" + } + }, + "additionalProperties": true + } + ], + "title": "Issue Comment", + "description": "A issue comment." + }, + "issue_job_status": { + "type": "object", + "title": "Issue Job Status", + "description": "The status of an import or export job", + "properties": { + "count": { + "type": "integer", + "description": "The total number of issues already imported/exported" + }, + "pct": { + "type": "number", + "description": "The percentage of issues already imported/exported", + "minimum": 0, + "maximum": 100 + }, + "phase": { + "type": "string", + "description": "The phase of the import/export job" + }, + "status": { + "type": "string", + "description": "The status of the import/export job", + "enum": ["ACCEPTED", "STARTED", "RUNNING", "FAILURE"] + }, + "total": { + "type": "integer", + "description": "The total number of issues being imported/exported" + }, + "type": { + "type": "string" + } + }, + "additionalProperties": false + }, + "jira_project": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Jira Project." + } + ], + "x-bb-default-fields": ["type", "cloudId", "id"], + "x-bb-detail-fields": ["key", "name", "url", "avatarUrls.*", "site"], + "x-bb-url": "/api/{target_user.uuid}/jira/sites/{cloudId}/projects/{id}?atlassian_account_id={user.account_id}" + }, + "jira_site": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Jira Site." + } + ], + "x-bb-default-fields": ["type", "cloudId", "cloudUrl", "cloudName"], + "x-bb-detail-fields": ["connected"], + "x-bb-url": "/api/{target_user.uuid}/jira/sites/{cloudId}?atlassian_account_id={user.account_id}" + }, + "milestone": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "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 + }, + "name": { + "type": "string" + } + }, + "additionalProperties": true + } + ], + "title": "Milestone", + "description": "A milestone as defined in a repository's issue tracker." + }, + "object": { + "type": "object", + "description": "Base type for most resource objects. It defines the common `type` element that identifies an object's type. It also identifies the element as Swagger's `discriminator`.", + "properties": { + "type": { + "type": "string" + } + }, + "required": ["type"], + "additionalProperties": true, + "discriminator": { + "propertyName": "type" + } + }, + "paginated": { + "type": "object", + "title": "Paginated", + "description": "A generic paginated list.", + "discriminator": { + "propertyName": "type" + }, + "properties": { + "next": { + "type": "string", + "format": "uri", + "description": "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." + }, + "page": { + "type": "integer", + "description": "Page number of the current results. This is an optional element that is not provided in all responses." + }, + "pagelen": { + "type": "integer", + "description": "Current number of objects on the existing page. The default value is 10 with 100 being the maximum allowed value. Individual APIs may enforce different values." + }, + "previous": { + "type": "string", + "format": "uri", + "description": "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." + }, + "size": { + "type": "integer", + "description": "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." + }, + "values": { + "description": "The values of the current page.", + "oneOf": [ + { + "type": "array", + "minItems": 0, + "items": {}, + "uniqueItems": false + }, + { + "type": "array", + "minItems": 0, + "items": {}, + "uniqueItems": true + } + ] + } + } + }, + "paginated_annotations": { + "title": "Paginated Annotations", + "description": "A paginated list of annotations.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/report_annotation" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_branches": { + "title": "Paginated Branches", + "description": "A paginated list of branches.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/branch" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_branchrestrictions": { + "title": "Paginated Branch Restrictions", + "description": "A paginated list of branch restriction rules.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/branchrestriction" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_changeset": { + "title": "Page", + "description": "A paginated list of commits.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/base_commit" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_commit_comments": { + "title": "Paginated Commit Comments", + "description": "A paginated list of commit comments.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/commit_comment" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_commitstatuses": { + "title": "Paginated Commit Statuses", + "description": "A paginated list of commit status objects.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/commitstatus" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_components": { + "title": "Paginated Components", + "description": "A paginated list of issue tracker components.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/component" + }, + "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.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/deploy_key" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_deployment_variable": { + "title": "Paginated Deployment Variables", + "description": "A paged list of deployment variables.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/deployment_variable" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_deployments": { + "title": "Paginated Deployments", + "description": "A paged list of deployments", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/deployment" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_diffstats": { + "title": "Paginated Diff Stat", + "description": "A paginated list of diffstats.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/diffstat" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_environments": { + "title": "Paginated Deployment Environments", + "description": "A paged list of environments", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/deployment_environment" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_files": { + "title": "Paginated Files", + "description": "A paginated list of commit_file objects.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/commit_file" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_hook_events": { + "title": "Paginated Hook Events", + "description": "A paginated list of webhook types available to subscribe on.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/hook_event" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_issue_attachments": { + "title": "Paginated Issue Attachment", + "description": "A paginated list of issue attachments.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/issue_attachment" + }, + "minItems": 0, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_issue_comments": { + "title": "Paginated Issue Comments", + "description": "A paginated list of issue comments.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/issue_comment" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_issues": { + "title": "Paginated Issues", + "description": "A paginated list of issues.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/issue" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_log_entries": { + "title": "Paginated Log Entries", + "description": "A paginated list of issue changes.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/issue_change" + }, + "minItems": 0, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_milestones": { + "title": "Paginated Milestones", + "description": "A paginated list of issue tracker milestones.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/milestone" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_pipeline_caches": { + "title": "Paginated Pipeline Cache", + "description": "A paged list of pipeline caches", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/pipeline_cache" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_pipeline_known_hosts": { + "title": "Paginated Pipeline Known Hosts", + "description": "A paged list of known hosts.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/pipeline_known_host" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_pipeline_schedule_executions": { + "title": "Paginated Pipeline Schedule Executions", + "description": "A paged list of the executions of a schedule.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/pipeline_schedule_execution" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_pipeline_schedules": { + "title": "Paginated Pipeline Schedule", + "description": "A paged list of schedules", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/pipeline_schedule" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_pipeline_steps": { + "title": "Paginated Pipeline Steps", + "description": "A paged list of pipeline steps.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/pipeline_step" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_pipeline_variables": { + "title": "Paginated Pipeline Variables", + "description": "A paged list of variables.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/pipeline_variable" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_pipelines": { + "title": "Paginated Pipelines", + "description": "A paged list of pipelines", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/pipeline" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_projects": { + "title": "Paginated Projects", + "description": "A paginated list of projects", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/project" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_pullrequest_comments": { + "title": "Paginated Pull Request Comments", + "description": "A paginated list of pullrequest comments.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/pullrequest_comment" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_pullrequests": { + "title": "Paginated Pull Requests", + "description": "A paginated list of pullrequests.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/pullrequest" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_refs": { + "title": "Paginated Refs", + "description": "A paginated list of refs.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ref" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_reports": { + "title": "Paginated Reports", + "description": "A paginated list of reports.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "minItems": 0, + "items": { + "$ref": "#/components/schemas/report" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_repositories": { + "title": "Paginated Repositories", + "description": "A paginated list of repositories.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/repository" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_repository_group_permissions": { + "title": "Paginated Repository Group Permissions", + "description": "A paginated list of repository group permissions.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/repository_group_permission" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_repository_permissions": { + "title": "Paginated Repository Permissions", + "description": "A paginated list of repository permissions.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/repository_permission" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_repository_user_permissions": { + "title": "Paginated Repository User Permissions", + "description": "A paginated list of repository user permissions.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/repository_user_permission" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_snippet_comments": { + "title": "Paginated Snippet Comments", + "description": "A paginated list of snippet comments.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/snippet_comment" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_snippet_commits": { + "title": "Paginated Snippet Commits", + "description": "A paginated list of snippet commits.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/snippet_commit" + }, + "minItems": 0, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_snippets": { + "title": "Paginated Snippets", + "description": "A paginated list of snippets.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/snippet" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_ssh_user_keys": { + "title": "Paginated SSH User Keys", + "description": "A paginated list of SSH keys.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/ssh_account_key" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_tags": { + "title": "Paginated Tags", + "description": "A paginated list of tags.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/tag" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "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.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/treeentry" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "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.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/version" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_webhook_subscriptions": { + "title": "Paginated Webhook Subscriptions", + "description": "A paginated list of webhook subscriptions", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/webhook_subscription" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_workspace_memberships": { + "title": "Paginated Workspace Memberships", + "description": "A paginated list of workspace memberships.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/workspace_membership" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "paginated_workspaces": { + "title": "Paginated Workspaces", + "description": "A paginated list of workspaces.", + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "values": { + "type": "array", + "items": { + "$ref": "#/components/schemas/workspace" + }, + "minItems": 0, + "uniqueItems": true, + "description": "The values of the current page." + } + } + } + ] + }, + "participant": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "approved": { + "type": "boolean" + }, + "participated_on": { + "type": "string", + "description": "The ISO8601 timestamp of the participant's action. For approvers, this is the time of their approval. For commenters and pull request reviewers who are not approvers, this is the time they last commented, or null if they have not commented.", + "format": "date-time" + }, + "role": { + "type": "string", + "enum": ["PARTICIPANT", "REVIEWER"] + }, + "state": { + "type": "string", + "enum": ["approved", "changes_requested", null] + }, + "user": { + "$ref": "#/components/schemas/user" + } + }, + "additionalProperties": true + } + ], + "title": "Participant", + "description": "Object describing a user's role on resources like commits or pull requests." + }, + "pipeline": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "build_number": { + "type": "integer", + "description": "The build number of the pipeline." + }, + "build_seconds_used": { + "type": "integer", + "description": "The number of build seconds used by this pipeline." + }, + "completed_on": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the Pipeline was completed. This is not set if the pipeline is still in progress." + }, + "created_on": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the pipeline was created." + }, + "creator": { + "$ref": "#/components/schemas/account" + }, + "repository": { + "$ref": "#/components/schemas/repository" + }, + "state": { + "$ref": "#/components/schemas/pipeline_state" + }, + "target": { + "$ref": "#/components/schemas/pipeline_target" + }, + "trigger": { + "$ref": "#/components/schemas/pipeline_trigger" + }, + "uuid": { + "type": "string", + "description": "The UUID identifying the pipeline." + } + } + } + ], + "title": "Pipeline", + "description": "A Bitbucket Pipeline. This represents an actual pipeline result." + }, + "pipeline_build_number": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "next": { + "type": "integer", + "description": "The next number that will be used as build number." + } + } + } + ], + "title": "Pipeline Build Number", + "description": "A Pipelines build number." + }, + "pipeline_cache": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "created_on": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the cache was created." + }, + "file_size_bytes": { + "type": "integer", + "description": "The size of the file containing the archive of the cache." + }, + "name": { + "type": "string", + "description": "The name of the cache." + }, + "path": { + "type": "string", + "description": "The path where the cache contents were retrieved from." + }, + "pipeline_uuid": { + "type": "string", + "description": "The UUID of the pipeline that created the cache." + }, + "step_uuid": { + "type": "string", + "description": "The uuid of the step that created the cache." + }, + "uuid": { + "type": "string", + "description": "The UUID identifying the pipeline cache." + } + } + } + ], + "title": "Pipeline Cache", + "description": "A representation of metadata for a pipeline cache for given repository." + }, + "pipeline_cache_content_uri": { + "type": "object", + "title": "Pipeline Cache Content URI", + "description": "A representation of the location of pipeline cache content.", + "properties": { + "uri": { + "type": "string", + "format": "uri", + "description": "The uri for pipeline cache content." + } + } + }, + "pipeline_command": { + "type": "object", + "title": "Pipeline Command", + "description": "An executable pipeline command.", + "properties": { + "command": { + "type": "string", + "description": "The executable command." + }, + "name": { + "type": "string", + "description": "The name of the command." + } + } + }, + "pipeline_commit_target": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_target" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "commit": { + "$ref": "#/components/schemas/commit" + }, + "selector": { + "$ref": "#/components/schemas/pipeline_selector" + } + } + } + ], + "title": "Pipeline Commit Target", + "description": "A Bitbucket Pipelines commit target." + }, + "pipeline_error": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "The error key." + }, + "message": { + "type": "string", + "description": "The error message." + } + } + } + ], + "title": "Pipeline Error", + "description": "An error causing a pipeline failure." + }, + "pipeline_image": { + "type": "object", + "title": "Pipeline Image", + "description": "The definition of a Docker image that can be used for a Bitbucket Pipelines step execution context.", + "properties": { + "email": { + "type": "string", + "description": "The email needed to authenticate with the Docker registry. Only required when using a private Docker image." + }, + "name": { + "type": "string", + "description": "The name of the image. If the image is hosted on DockerHub the short name can be used, otherwise the fully qualified name is required here." + }, + "password": { + "type": "string", + "description": "The password needed to authenticate with the Docker registry. Only required when using a private Docker image." + }, + "username": { + "type": "string", + "description": "The username needed to authenticate with the Docker registry. Only required when using a private Docker image." + } + } + }, + "pipeline_known_host": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "hostname": { + "type": "string", + "description": "The hostname of the known host." + }, + "public_key": { + "$ref": "#/components/schemas/pipeline_ssh_public_key" + }, + "uuid": { + "type": "string", + "description": "The UUID identifying the known host." + } + } + } + ], + "title": "Pipeline Known Host", + "description": "A Pipelines known host." + }, + "pipeline_ref_target": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_target" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "commit": { + "$ref": "#/components/schemas/commit" + }, + "ref_name": { + "type": "string", + "description": "The name of the reference." + }, + "ref_type": { + "enum": ["branch", "tag", "named_branch", "bookmark"], + "type": "string", + "description": "The type of reference (branch/tag)." + }, + "selector": { + "$ref": "#/components/schemas/pipeline_selector" + } + } + } + ], + "title": "Pipeline Ref Target", + "description": "A Bitbucket Pipelines reference target." + }, + "pipeline_schedule": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "created_on": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the schedule was created." + }, + "cron_pattern": { + "type": "string", + "description": "The cron expression that the schedule applies." + }, + "enabled": { + "type": "boolean", + "description": "Whether the schedule is enabled." + }, + "selector": { + "$ref": "#/components/schemas/pipeline_selector" + }, + "target": { + "$ref": "#/components/schemas/pipeline_target" + }, + "updated_on": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the schedule was updated." + }, + "uuid": { + "type": "string", + "description": "The UUID identifying the schedule." + } + } + } + ], + "title": "Pipeline Schedule", + "description": "A Pipelines schedule." + }, + "pipeline_schedule_execution": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": {} + } + ], + "title": "Pipeline Schedule Execution", + "description": "A Pipelines schedule execution." + }, + "pipeline_schedule_execution_errored": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_schedule_execution" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Pipelines schedule execution that failed to be executed.", + "properties": { + "error": { + "$ref": "#/components/schemas/pipeline_error" + } + } + } + ] + }, + "pipeline_schedule_execution_executed": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_schedule_execution" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Pipelines executed schedule execution.", + "properties": { + "pipeline": { + "$ref": "#/components/schemas/pipeline" + } + } + } + ] + }, + "pipeline_selector": { + "title": "Pipeline Selector", + "description": "A representation of the selector that was used to identify the pipeline in the YML file.", + "additionalProperties": true, + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "The name of the matching pipeline definition." + }, + "type": { + "enum": ["branches", "tags", "bookmarks", "default", "custom"], + "type": "string", + "description": "The type of selector." + } + } + }, + "pipeline_ssh_key_pair": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "private_key": { + "type": "string", + "description": "The SSH private key. This value will be empty when retrieving the SSH key pair." + }, + "public_key": { + "type": "string", + "description": "The SSH public key." + } + } + } + ], + "title": "Pipeline SSH Key Pair", + "description": "A Pipelines SSH key pair." + }, + "pipeline_ssh_public_key": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "The base64 encoded public key." + }, + "key_type": { + "type": "string", + "description": "The type of the public key." + }, + "md5_fingerprint": { + "type": "string", + "description": "The MD5 fingerprint of the public key." + }, + "sha256_fingerprint": { + "type": "string", + "description": "The SHA-256 fingerprint of the public key." + } + } + } + ], + "title": "Pipeline SSH Public Key", + "description": "A Pipelines known host public key." + }, + "pipeline_state": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": {} + } + ], + "title": "Pipeline State", + "description": "The representation of the progress state of a pipeline." + }, + "pipeline_state_completed": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_state" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "name": { + "enum": ["COMPLETED"], + "type": "string", + "description": "The name of pipeline state (COMPLETED)." + }, + "result": { + "$ref": "#/components/schemas/pipeline_state_completed_result" + } + } + } + ], + "title": "Pipeline Completed State", + "description": "A Bitbucket Pipelines COMPLETED pipeline state." + }, + "pipeline_state_completed_error": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_state_completed_result" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "error": { + "$ref": "#/components/schemas/pipeline_error" + }, + "name": { + "enum": ["ERROR"], + "type": "string", + "description": "The name of the result (ERROR)" + } + } + } + ], + "title": "Pipeline Completed Error", + "description": "A Bitbucket Pipelines ERROR pipeline result." + }, + "pipeline_state_completed_expired": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_state_completed_result" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines EXPIRED pipeline result.", + "properties": { + "name": { + "enum": ["EXPIRED"], + "type": "string", + "description": "The name of the stopped result (EXPIRED)." + } + } + } + ] + }, + "pipeline_state_completed_failed": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_state_completed_result" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines FAILED pipeline result.", + "properties": { + "name": { + "enum": ["FAILED"], + "type": "string", + "description": "The name of the failed result (FAILED)." + } + } + } + ] + }, + "pipeline_state_completed_result": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": {} + } + ], + "title": "Pipeline Completed Result", + "description": "A result of a completed pipeline state." + }, + "pipeline_state_completed_stopped": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_state_completed_result" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines STOPPED pipeline result.", + "properties": { + "name": { + "enum": ["STOPPED"], + "type": "string", + "description": "The name of the stopped result (STOPPED)." + } + } + } + ] + }, + "pipeline_state_completed_successful": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_state_completed_result" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines SUCCESSFUL pipeline result.", + "properties": { + "name": { + "enum": ["SUCCESSFUL"], + "type": "string", + "description": "The name of the successful result (SUCCESSFUL)." + } + } + } + ] + }, + "pipeline_state_in_progress": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_state" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "name": { + "enum": ["IN_PROGRESS"], + "type": "string", + "description": "The name of pipeline state (IN_PROGRESS)." + }, + "stage": { + "$ref": "#/components/schemas/pipeline_state_in_progress_stage" + } + } + } + ], + "title": "Pipeline In-Progress State", + "description": "A Bitbucket Pipelines IN_PROGRESS pipeline state." + }, + "pipeline_state_in_progress_paused": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_state_in_progress_stage" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines PAUSED stage of a pipeline that is in progress.", + "properties": { + "name": { + "enum": ["PAUSED"], + "type": "string", + "description": "The name of the stage (PAUSED)" + } + } + } + ] + }, + "pipeline_state_in_progress_running": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_state_in_progress_stage" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines RUNNING stage of a pipeline that is in progress.", + "properties": { + "name": { + "enum": ["RUNNING"], + "type": "string", + "description": "The name of the stage (RUNNING)" + } + } + } + ] + }, + "pipeline_state_in_progress_stage": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": {} + } + ], + "title": "Pipeline In-Progress Stage", + "description": "A result of an in progress pipeline state." + }, + "pipeline_state_pending": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_state" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "name": { + "enum": ["PENDING"], + "type": "string", + "description": "The name of pipeline state (PENDING)." + } + } + } + ], + "title": "Pipeline Pending State", + "description": "A Bitbucket Pipelines PENDING pipeline state." + }, + "pipeline_step": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "completed_on": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the step execution was completed. This is not set if the step is still in progress." + }, + "image": { + "$ref": "#/components/schemas/pipeline_image" + }, + "script_commands": { + "type": "array", + "items": { + "$ref": "#/components/schemas/pipeline_command" + }, + "description": "The list of build commands. These commands are executed in the build container." + }, + "setup_commands": { + "type": "array", + "items": { + "$ref": "#/components/schemas/pipeline_command" + }, + "description": "The list of commands that are executed as part of the setup phase of the build. These commands are executed outside the build container." + }, + "started_on": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the step execution was started. This is not set when the step hasn't executed yet." + }, + "state": { + "$ref": "#/components/schemas/pipeline_step_state" + }, + "uuid": { + "type": "string", + "description": "The UUID identifying the step." + } + } + } + ], + "x-bb-default-fields": ["uuid"], + "x-bb-url": "/rest/1.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/pipelines/{pipeline.uuid}/steps/{uuid}", + "x-bb-batch-url": "/rest/1.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/pipelines/steps_batch", + "x-bb-batch-max-size": 100, + "title": "Pipeline Step", + "description": "A step of a Bitbucket pipeline. This represents the actual result of the step execution." + }, + "pipeline_step_error": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "The error key." + }, + "message": { + "type": "string", + "description": "The error message." + } + } + } + ], + "title": "Pipeline Step Error", + "description": "An error causing a step failure." + }, + "pipeline_step_state": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": {} + } + ], + "title": "Pipeline Step State", + "description": "The representation of the progress state of a pipeline step." + }, + "pipeline_step_state_completed": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_step_state" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines COMPLETED pipeline step state.", + "properties": { + "name": { + "enum": ["COMPLETED"], + "type": "string", + "description": "The name of pipeline step state (COMPLETED)." + }, + "result": { + "$ref": "#/components/schemas/pipeline_step_state_completed_result" + } + } + } + ] + }, + "pipeline_step_state_completed_error": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_step_state_completed_result" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines ERROR pipeline step result.", + "properties": { + "error": { + "$ref": "#/components/schemas/pipeline_step_error" + }, + "name": { + "enum": ["ERROR"], + "type": "string", + "description": "The name of the result (ERROR)" + } + } + } + ] + }, + "pipeline_step_state_completed_expired": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_step_state_completed_result" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines EXPIRED pipeline step result.", + "properties": { + "name": { + "enum": ["EXPIRED"], + "type": "string", + "description": "The name of the result (EXPIRED)" + } + } + } + ] + }, + "pipeline_step_state_completed_failed": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_step_state_completed_result" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines FAILED pipeline step result.", + "properties": { + "name": { + "enum": ["FAILED"], + "type": "string", + "description": "The name of the result (FAILED)" + } + } + } + ] + }, + "pipeline_step_state_completed_not_run": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_step_state_completed_result" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines NOT_RUN pipeline step result.", + "properties": { + "name": { + "enum": ["NOT_RUN"], + "type": "string", + "description": "The name of the result (NOT_RUN)" + } + } + } + ] + }, + "pipeline_step_state_completed_result": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": {} + } + ], + "title": "Pipeline Completed Step Result", + "description": "A result of a completed pipeline step state." + }, + "pipeline_step_state_completed_stopped": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_step_state_completed_result" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines STOPPED pipeline step result.", + "properties": { + "name": { + "enum": ["STOPPED"], + "type": "string", + "description": "The name of the result (STOPPED)" + } + } + } + ] + }, + "pipeline_step_state_completed_successful": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_step_state_completed_result" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines SUCCESSFUL pipeline step result.", + "properties": { + "name": { + "enum": ["SUCCESSFUL"], + "type": "string", + "description": "The name of the result (SUCCESSFUL)" + } + } + } + ] + }, + "pipeline_step_state_in_progress": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_step_state" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines IN_PROGRESS pipeline step state.", + "properties": { + "name": { + "enum": ["IN_PROGRESS"], + "type": "string", + "description": "The name of pipeline step state (IN_PROGRESS)." + } + } + } + ] + }, + "pipeline_step_state_pending": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_step_state" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines PENDING pipeline step state.", + "properties": { + "name": { + "enum": ["PENDING"], + "type": "string", + "description": "The name of pipeline step state (PENDING)." + } + } + } + ] + }, + "pipeline_step_state_ready": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_step_state" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A Bitbucket Pipelines READY pipeline step state.", + "properties": { + "name": { + "enum": ["READY"], + "type": "string", + "description": "The name of pipeline step state (READY)." + } + } + } + ] + }, + "pipeline_target": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": {} + } + ], + "title": "Pipeline Target", + "description": "A representation of the target that a pipeline executes on." + }, + "pipeline_trigger": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": {} + } + ], + "title": "Pipeline Trigger", + "description": "A representation of the trigger used for a pipeline." + }, + "pipeline_trigger_manual": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_trigger" + }, + { + "additionalProperties": true, + "type": "object", + "properties": {} + } + ], + "title": "Pipeline Manual Trigger", + "description": "A Bitbucket Pipelines MANUAL trigger." + }, + "pipeline_trigger_push": { + "allOf": [ + { + "$ref": "#/components/schemas/pipeline_trigger" + }, + { + "additionalProperties": true, + "type": "object", + "properties": {} + } + ], + "title": "Pipeline Push Trigger", + "description": "A Bitbucket Pipelines PUSH trigger." + }, + "pipeline_variable": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "key": { + "type": "string", + "description": "The unique name of the variable." + }, + "secured": { + "type": "boolean", + "description": "If true, this variable will be treated as secured. The value will never be exposed in the logs or the REST API." + }, + "uuid": { + "type": "string", + "description": "The UUID identifying the variable." + }, + "value": { + "type": "string", + "description": "The value of the variable. If the variable is secured, this will be empty." + } + } + } + ], + "title": "Pipeline Variable", + "description": "A Pipelines variable." + }, + "pipelines_config": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Whether Pipelines is enabled for the repository." + }, + "repository": { + "$ref": "#/components/schemas/repository" + } + } + } + ], + "title": "Pipelines Configuration", + "description": "The Pipelines configuration for a repository." + }, + "pipelines_ddev_pipeline_step": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A step of a Bitbucket pipeline. This represents the actual result of the step execution." + } + ], + "x-bb-default-fields": ["uuid"], + "x-bb-url": "/rest/1.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/pipelines/{pipeline.uuid}/steps/{uuid}", + "x-bb-batch-url": "/rest/1.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/pipelines/steps_batch", + "x-bb-batch-max-size": 100 + }, + "pipelines_stg_west_pipeline_step": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A step of a Bitbucket pipeline. This represents the actual result of the step execution." + } + ], + "x-bb-default-fields": ["uuid"], + "x-bb-url": "/rest/1.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/pipelines/{pipeline.uuid}/steps/{uuid}", + "x-bb-batch-url": "/rest/1.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/pipelines/steps_batch", + "x-bb-batch-max-size": 100 + }, + "project": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "created_on": { + "type": "string", + "format": "date-time" + }, + "description": { + "type": "string" + }, + "has_publicly_visible_repos": { + "type": "boolean", + "description": "\nIndicates whether the project contains publicly visible repositories.\nNote that private projects cannot contain public repositories." + }, + "is_private": { + "type": "boolean", + "description": "\nIndicates whether the project is publicly accessible, or whether it is\nprivate to the team and consequently only visible to team members.\nNote that private projects cannot contain public repositories." + }, + "key": { + "type": "string", + "description": "The project's key." + }, + "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 + }, + "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 + } + }, + "additionalProperties": false + }, + "name": { + "type": "string", + "description": "The name of the project." + }, + "owner": { + "$ref": "#/components/schemas/team" + }, + "updated_on": { + "type": "string", + "format": "date-time" + }, + "uuid": { + "type": "string", + "description": "The project's immutable id." + } + }, + "additionalProperties": true + } + ], + "title": "Project", + "description": "A Bitbucket project.\n Projects are used by teams to organize repositories." + }, + "pullrequest": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "author": { + "$ref": "#/components/schemas/account" + }, + "close_source_branch": { + "type": "boolean", + "description": "A boolean flag indicating if merging the pull request closes the source branch." + }, + "closed_by": { + "$ref": "#/components/schemas/account" + }, + "comment_count": { + "type": "integer", + "description": "The number of comments for a specific pull request.", + "minimum": 0 + }, + "created_on": { + "type": "string", + "description": "The ISO8601 timestamp the request was created.", + "format": "date-time" + }, + "destination": { + "$ref": "#/components/schemas/pullrequest_endpoint" + }, + "id": { + "type": "integer", + "description": "The pull request's unique ID. Note that pull request IDs are only unique within their associated repository." + }, + "links": { + "type": "object", + "properties": { + "activity": { + "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 + }, + "approve": { + "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 + }, + "comments": { + "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 + }, + "commits": { + "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 + }, + "decline": { + "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 + }, + "diff": { + "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 + }, + "diffstat": { + "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 + }, + "merge": { + "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 + }, + "merge_commit": { + "type": "object", + "title": "Pull Request Commit", + "properties": { + "hash": { + "type": "string", + "pattern": "[0-9a-f]{7,}?" + } + }, + "additionalProperties": false + }, + "participants": { + "type": "array", + "description": " The list of users that are collaborating on this pull request.\n Collaborators are user that:\n\n * are added to the pull request as a reviewer (part of the reviewers\n list)\n * are not explicit reviewers, but have commented on the pull request\n * are not explicit reviewers, but have approved the pull request\n\n Each user is wrapped in an object that indicates the user's role and\n whether they have approved the pull request. For performance reasons,\n the API only returns this list when an API requests a pull request by\n id.\n ", + "items": { + "$ref": "#/components/schemas/participant" + } + }, + "reason": { + "type": "string", + "description": "Explains why a pull request was declined. This field is only applicable to pull requests in rejected state." + }, + "rendered": { + "type": "object", + "title": "Rendered Pull Request Markup", + "description": "User provided pull request text, interpreted in a markup language and rendered in HTML", + "properties": { + "description": { + "type": "object", + "properties": { + "html": { + "type": "string", + "description": "The user's content rendered as HTML." + }, + "markup": { + "type": "string", + "description": "The type of markup language the raw content is to be interpreted in.", + "enum": ["markdown", "creole", "plaintext"] + }, + "raw": { + "type": "string", + "description": "The text as it was typed by a user." + } + }, + "additionalProperties": false + }, + "reason": { + "type": "object", + "properties": { + "html": { + "type": "string", + "description": "The user's content rendered as HTML." + }, + "markup": { + "type": "string", + "description": "The type of markup language the raw content is to be interpreted in.", + "enum": ["markdown", "creole", "plaintext"] + }, + "raw": { + "type": "string", + "description": "The text as it was typed by a user." + } + }, + "additionalProperties": false + }, + "title": { + "type": "object", + "properties": { + "html": { + "type": "string", + "description": "The user's content rendered as HTML." + }, + "markup": { + "type": "string", + "description": "The type of markup language the raw content is to be interpreted in.", + "enum": ["markdown", "creole", "plaintext"] + }, + "raw": { + "type": "string", + "description": "The text as it was typed by a user." + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false + }, + "reviewers": { + "type": "array", + "description": "The list of users that were added as reviewers on this pull request when it was created. For performance reasons, the API only includes this list on a pull request's `self` URL.", + "items": { + "$ref": "#/components/schemas/account" + } + }, + "source": { + "$ref": "#/components/schemas/pullrequest_endpoint" + }, + "state": { + "type": "string", + "description": "The pull request's current status.", + "enum": ["MERGED", "SUPERSEDED", "OPEN", "DECLINED"] + }, + "summary": { + "type": "object", + "properties": { + "html": { + "type": "string", + "description": "The user's content rendered as HTML." + }, + "markup": { + "type": "string", + "description": "The type of markup language the raw content is to be interpreted in.", + "enum": ["markdown", "creole", "plaintext"] + }, + "raw": { + "type": "string", + "description": "The text as it was typed by a user." + } + }, + "additionalProperties": false + }, + "task_count": { + "type": "integer", + "description": "The number of open tasks for a specific pull request.", + "minimum": 0 + }, + "title": { + "type": "string", + "description": "Title of the pull request." + }, + "updated_on": { + "type": "string", + "description": "The ISO8601 timestamp the request was last updated.", + "format": "date-time" + } + }, + "additionalProperties": true + } + ], + "title": "Pull Request", + "description": "A pull request object." + }, + "pullrequest_comment": { + "allOf": [ + { + "$ref": "#/components/schemas/comment" + }, + { + "type": "object", + "properties": { + "pullrequest": { + "$ref": "#/components/schemas/pullrequest" + } + }, + "additionalProperties": true + } + ], + "title": "Pull Request Comment", + "description": "A pullrequest comment." + }, + "pullrequest_endpoint": { + "type": "object", + "title": "Pull Request Endpoint", + "properties": { + "branch": { + "type": "object", + "title": "Pull Request Branch", + "properties": { + "default_merge_strategy": { + "type": "string", + "description": "The default merge strategy, when this endpoint is the destination of the pull request." + }, + "merge_strategies": { + "type": "array", + "description": "Available merge strategies, when this endpoint is the destination of the pull request.", + "items": { + "type": "string", + "enum": ["merge_commit", "squash", "fast_forward"] + } + }, + "name": { + "type": "string" + } + }, + "additionalProperties": false + }, + "commit": { + "type": "object", + "title": "Pull Request Commit", + "properties": { + "hash": { + "type": "string", + "pattern": "[0-9a-f]{7,}?" + } + }, + "additionalProperties": false + }, + "repository": { + "$ref": "#/components/schemas/repository" + } + }, + "additionalProperties": false + }, + "pullrequest_merge_parameters": { + "type": "object", + "title": "Pull Request Merge Parameters", + "description": "The metadata that describes a pull request merge.", + "properties": { + "close_source_branch": { + "type": "boolean", + "description": "Whether the source branch should be deleted. If this is not provided, we fallback to the value used when the pull request was created, which defaults to False" + }, + "merge_strategy": { + "type": "string", + "description": "The merge strategy that will be used to merge the pull request.", + "enum": ["merge_commit", "squash", "fast_forward"], + "default": "merge_commit" + }, + "message": { + "type": "string", + "description": "The commit message that will be used on the resulting commit." + }, + "type": { + "type": "string" + } + }, + "required": ["type"], + "additionalProperties": true + }, + "ref": { + "type": "object", + "title": "Ref", + "description": "A ref object, representing a branch or tag in a repository.", + "properties": { + "links": { + "type": "object", + "properties": { + "commits": { + "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 + }, + "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 + }, + "name": { + "type": "string", + "description": "The name of the ref." + }, + "target": { + "$ref": "#/components/schemas/commit" + }, + "type": { + "type": "string" + } + }, + "required": ["type"], + "additionalProperties": true + }, + "report": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "created_on": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the report was created." + }, + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/report_data" + }, + "description": "An array of data fields to display information on the report. Maximum 10." + }, + "details": { + "type": "string", + "description": "A string to describe the purpose of the report." + }, + "external_id": { + "type": "string", + "description": "ID of the report provided by the report creator. It can be used to identify the report as an alternative to it's generated uuid. It is not used by Bitbucket, but only by the report creator for updating or deleting this specific report. Needs to be unique." + }, + "link": { + "type": "string", + "format": "uri", + "description": "A URL linking to the results of the report in an external tool." + }, + "logo_url": { + "type": "string", + "format": "uri", + "description": "A URL to the report logo. If none is provided, the default insights logo will be used." + }, + "remote_link_enabled": { + "type": "boolean", + "description": "If enabled, a remote link is created in Jira for the issue associated with the commit the report belongs to." + }, + "report_type": { + "enum": ["SECURITY", "COVERAGE", "TEST", "BUG"], + "type": "string", + "description": "The type of the report." + }, + "reporter": { + "type": "string", + "description": "A string to describe the tool or company who created the report." + }, + "result": { + "enum": ["PASSED", "FAILED", "PENDING"], + "type": "string", + "description": "The state of the report. May be set to PENDING and later updated." + }, + "title": { + "type": "string", + "description": "The title of the report." + }, + "updated_on": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the report was updated." + }, + "uuid": { + "type": "string", + "description": "The UUID that can be used to identify the report." + } + } + } + ], + "x-bb-default-fields": ["uuid", "commitHash"], + "x-bb-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/commits/{commitHash}/reports/{uuid}", + "title": "Commit Report", + "description": "A report for a commit." + }, + "report_annotation": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "properties": { + "annotation_type": { + "enum": ["VULNERABILITY", "CODE_SMELL", "BUG"], + "type": "string", + "description": "The type of the report." + }, + "created_on": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the report was created." + }, + "details": { + "type": "string", + "description": "The details to show to users when clicking on the annotation." + }, + "external_id": { + "type": "string", + "description": "ID of the annotation provided by the annotation creator. It can be used to identify the annotation as an alternative to it's generated uuid. It is not used by Bitbucket, but only by the annotation creator for updating or deleting this specific annotation. Needs to be unique." + }, + "line": { + "type": "integer", + "description": "The line number that the annotation should belong to. If no line number is provided, then it will default to 0 and in a pull request it will appear at the top of the file specified by the path field.", + "minimum": 1 + }, + "link": { + "type": "string", + "format": "uri", + "description": "A URL linking to the annotation in an external tool." + }, + "path": { + "type": "string", + "description": "The path of the file on which this annotation should be placed. This is the path of the file relative to the git repository. If no path is provided, then it will appear in the overview modal on all pull requests where the tip of the branch is the given commit, regardless of which files were modified." + }, + "result": { + "enum": ["PASSED", "FAILED", "SKIPPED", "IGNORED"], + "type": "string", + "description": "The state of the report. May be set to PENDING and later updated." + }, + "severity": { + "enum": ["CRITICAL", "HIGH", "MEDIUM", "LOW"], + "type": "string", + "description": "The severity of the annotation." + }, + "summary": { + "type": "string", + "description": "The message to display to users." + }, + "updated_on": { + "type": "string", + "format": "date-time", + "description": "The timestamp when the report was updated." + }, + "uuid": { + "type": "string", + "description": "The UUID that can be used to identify the annotation." + } + } + } + ], + "x-bb-default-fields": ["uuid"], + "x-bb-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/commits/{commit.hash}/reports/{reportUuid}/annotations/{uuid}", + "title": "Report Annotation", + "description": "A report for a commit." + }, + "report_data": { + "type": "object", + "title": "Report Data", + "description": "A key-value element that will be displayed along with the report.", + "properties": { + "title": { + "type": "string", + "description": "A string describing what this data field represents." + }, + "type": { + "enum": [ + "BOOLEAN", + "DATE", + "DURATION", + "LINK", + "NUMBER", + "PERCENTAGE", + "TEXT" + ], + "type": "string", + "description": "The type of data contained in the value field. If not provided, then the value will be detected as a boolean, number or string." + }, + "value": { + "type": "object", + "description": "The value of the data element." + } + } + }, + "repository": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "created_on": { + "type": "string", + "format": "date-time" + }, + "description": { + "type": "string" + }, + "fork_policy": { + "type": "string", + "description": "\nControls the rules for forking this repository.\n\n* **allow_forks**: unrestricted forking\n* **no_public_forks**: restrict forking to private forks (forks cannot\n be made public later)\n* **no_forks**: deny all forking\n", + "enum": ["allow_forks", "no_public_forks", "no_forks"] + }, + "full_name": { + "type": "string", + "description": "The concatenation of the repository owner's username and the slugified name, e.g. \"evzijst/interruptingcow\". This is the same string used in Bitbucket URLs." + }, + "has_issues": { + "type": "boolean" + }, + "has_wiki": { + "type": "boolean" + }, + "is_private": { + "type": "boolean" + }, + "language": { + "type": "string" + }, + "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 + }, + "clone": { + "type": "array", + "items": { + "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 + } + }, + "commits": { + "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 + }, + "downloads": { + "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 + }, + "forks": { + "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 + }, + "hooks": { + "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 + }, + "pullrequests": { + "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 + }, + "watchers": { + "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 + }, + "mainbranch": { + "$ref": "#/components/schemas/branch" + }, + "name": { + "type": "string" + }, + "owner": { + "$ref": "#/components/schemas/account" + }, + "parent": { + "$ref": "#/components/schemas/repository" + }, + "project": { + "$ref": "#/components/schemas/project" + }, + "scm": { + "type": "string", + "enum": ["git"] + }, + "size": { + "type": "integer" + }, + "slug": { + "type": "string", + "description": "The \"sluggified\" version of the repository's name. This contains only ASCII characters and can therefore be slightly different than the name" + }, + "updated_on": { + "type": "string", + "format": "date-time" + }, + "uuid": { + "type": "string", + "description": "The repository's immutable id. This can be used as a substitute for the slug segment in URLs. Doing this guarantees your URLs will survive renaming of the repository by its owner, or even transfer of the repository to a different user." + } + }, + "additionalProperties": true + } + ], + "title": "Repository", + "description": "A Bitbucket repository." + }, + "repository_group_permission": { + "type": "object", + "title": "Repository Group Permission", + "description": "A group's permission for a given repository.", + "properties": { + "group": { + "$ref": "#/components/schemas/group" + }, + "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 + }, + "permission": { + "type": "string", + "enum": ["admin", "write", "read", "none"] + }, + "repository": { + "$ref": "#/components/schemas/repository" + }, + "type": { + "type": "string" + } + }, + "required": ["type"], + "additionalProperties": true + }, + "repository_permission": { + "type": "object", + "title": "Repository Permission", + "description": "A user's permission for a given repository.", + "properties": { + "permission": { + "type": "string", + "enum": ["admin", "write", "read", "none"] + }, + "repository": { + "$ref": "#/components/schemas/repository" + }, + "type": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/user" + } + }, + "required": ["type"], + "additionalProperties": true + }, + "repository_user_permission": { + "type": "object", + "title": "Repository User Permission", + "description": "A user's direct permission for a given repository.", + "properties": { + "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 + }, + "permission": { + "type": "string", + "enum": ["admin", "write", "read", "none"] + }, + "repository": { + "$ref": "#/components/schemas/repository" + }, + "type": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/user" + } + }, + "required": ["type"], + "additionalProperties": true + }, + "search_code_search_result": { + "type": "object", + "properties": { + "content_match_count": { + "type": "integer", + "format": "int64", + "readOnly": true + }, + "content_matches": { + "type": "array", + "readOnly": true, + "items": { + "$ref": "#/components/schemas/search_content_match" + } + }, + "file": { + "$ref": "#/components/schemas/commit_file" + }, + "path_matches": { + "type": "array", + "readOnly": true, + "items": { + "$ref": "#/components/schemas/search_segment" + } + }, + "type": { + "type": "string", + "readOnly": true + } + } + }, + "search_content_match": { + "type": "object", + "properties": { + "lines": { + "type": "array", + "readOnly": true, + "items": { + "$ref": "#/components/schemas/search_line" + } + } + } + }, + "search_line": { + "type": "object", + "properties": { + "line": { + "type": "integer", + "format": "int32", + "readOnly": true + }, + "segments": { + "type": "array", + "readOnly": true, + "items": { + "$ref": "#/components/schemas/search_segment" + } + } + } + }, + "search_result_page": { + "allOf": [ + { + "$ref": "#/components/schemas/paginated" + }, + { + "type": "object", + "properties": { + "query_substituted": { + "type": "boolean", + "readOnly": true + }, + "values": { + "type": "array", + "readOnly": true, + "items": { + "$ref": "#/components/schemas/search_code_search_result" + }, + "description": "The values of the current page." + } + } + } + ] + }, + "search_segment": { + "type": "object", + "properties": { + "match": { + "type": "boolean", + "readOnly": true + }, + "text": { + "type": "string", + "readOnly": true + } + } + }, + "snippet": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "created_on": { + "type": "string", + "format": "date-time" + }, + "creator": { + "$ref": "#/components/schemas/account" + }, + "id": { + "type": "integer", + "minimum": 0 + }, + "is_private": { + "type": "boolean" + }, + "owner": { + "$ref": "#/components/schemas/account" + }, + "scm": { + "type": "string", + "description": "The DVCS used to store the snippet.", + "enum": ["git"] + }, + "title": { + "type": "string" + }, + "updated_on": { + "type": "string", + "format": "date-time" + } + }, + "additionalProperties": true + } + ], + "title": "Snippet", + "description": "A snippet object." + }, + "snippet_comment": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "links": { + "type": "object", + "properties": { + "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 + }, + "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 + }, + "snippet": { + "$ref": "#/components/schemas/snippet" + } + }, + "additionalProperties": true + } + ], + "title": "Snippet Comment", + "description": "A comment on a snippet." + }, + "snippet_commit": { + "allOf": [ + { + "$ref": "#/components/schemas/base_commit" + }, + { + "type": "object", + "properties": { + "links": { + "type": "object", + "properties": { + "diff": { + "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 + }, + "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 + }, + "snippet": { + "$ref": "#/components/schemas/snippet" + } + }, + "additionalProperties": true + } + ], + "title": "Snippet Commit", + "description": "" + }, + "ssh_account_key": { + "allOf": [ + { + "$ref": "#/components/schemas/ssh_key" + }, + { + "type": "object", + "properties": { + "owner": { + "$ref": "#/components/schemas/account" + } + }, + "additionalProperties": true + } + ], + "title": "SSH Account Key", + "description": "Represents an SSH public key for a user." + }, + "ssh_key": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "comment": { + "type": "string", + "description": "The comment parsed from the SSH key (if present)" + }, + "created_on": { + "type": "string", + "format": "date-time" + }, + "key": { + "type": "string", + "description": "The SSH public key value in OpenSSH format." + }, + "label": { + "type": "string", + "description": "The user-defined label for the SSH 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 + }, + "uuid": { + "type": "string", + "description": "The SSH key's immutable ID." + } + }, + "additionalProperties": true + } + ], + "title": "SSH Key", + "description": "Base type for representing SSH public keys." + }, + "stg_west_report": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "additionalProperties": true, + "type": "object", + "description": "A report for a commit." + } + ], + "x-bb-default-fields": ["uuid", "commitHash"], + "x-bb-url": "/rest/2.0/accounts/{target_user.uuid}/repositories/{repository.uuid}/commits/{commitHash}/reports/{uuid}" + }, + "subject_types": { + "type": "object", + "title": "Subject Types", + "description": "The mapping of resource/subject types pointing to their individual event types.", + "properties": { + "repository": { + "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 + }, + "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": { + "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 + } + }, + "additionalProperties": false + }, + "tag": { + "allOf": [ + { + "$ref": "#/components/schemas/ref" + }, + { + "type": "object", + "properties": { + "date": { + "type": "string", + "description": "The date that the tag was created, if available", + "format": "date-time" + }, + "message": { + "type": "string", + "description": "The message associated with the tag, if available." + }, + "tagger": { + "$ref": "#/components/schemas/author" + } + }, + "additionalProperties": true + } + ], + "title": "Tag", + "description": "A tag object, representing a tag in a repository." + }, + "team": { + "allOf": [ + { + "$ref": "#/components/schemas/account" + }, + { + "type": "object", + "properties": {}, + "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": { + "$ref": "#/components/schemas/team" + }, + "type": { + "type": "string" + }, + "user": { + "$ref": "#/components/schemas/user" + } + }, + "required": ["type"], + "additionalProperties": true + }, + "treeentry": { + "type": "object", + "title": "Tree Entry", + "description": "Base type for most resource objects. It defines the common `type` element that identifies an object's type. It also identifies the element as Swagger's `discriminator`.", + "properties": { + "commit": { + "$ref": "#/components/schemas/commit" + }, + "path": { + "type": "string", + "description": "The path in the repository" + }, + "type": { + "type": "string" + } + }, + "required": ["type"], + "additionalProperties": true + }, + "user": { + "allOf": [ + { + "$ref": "#/components/schemas/account" + }, + { + "type": "object", + "properties": { + "account_id": { + "type": "string", + "description": "The user's Atlassian account ID." + }, + "is_staff": { + "type": "boolean" + } + }, + "additionalProperties": true + } + ], + "title": "User", + "description": "A user object." + }, + "version": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "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 + }, + "name": { + "type": "string" + } + }, + "additionalProperties": true + } + ], + "title": "Version", + "description": "A version as defined in a repository's issue tracker." + }, + "webhook_subscription": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "active": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "description": { + "type": "string", + "description": "A user-defined description of the webhook." + }, + "events": { + "type": "array", + "description": "The events this webhook is subscribed to.", + "items": { + "type": "string", + "enum": [ + "pullrequest:unapproved", + "issue:comment_created", + "repo:imported", + "repo:created", + "repo:commit_comment_created", + "pullrequest:approved", + "pullrequest:comment_updated", + "issue:updated", + "project:updated", + "repo:deleted", + "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:updated", + "pullrequest:rejected", + "pullrequest:fulfilled", + "pullrequest:created", + "pullrequest:changes_request_removed", + "repo:transfer", + "repo:push" + ] + }, + "minItems": 1, + "uniqueItems": true + }, + "subject": { + "$ref": "#/components/schemas/object" + }, + "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"] + }, + "url": { + "type": "string", + "description": "The URL events get delivered to.", + "format": "uri" + }, + "uuid": { + "type": "string", + "description": "The webhook's id" + } + }, + "additionalProperties": true + } + ], + "title": "Webhook Subscription", + "description": "A Webhook subscription." + }, + "workspace": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "created_on": { + "type": "string", + "format": "date-time" + }, + "is_private": { + "type": "boolean", + "description": "Indicates whether the workspace is publicly accessible, or whether it is\nprivate to the members and consequently only visible to members." + }, + "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 + }, + "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 + }, + "members": { + "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 + }, + "owners": { + "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 + }, + "projects": { + "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 + }, + "snippets": { + "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 + }, + "name": { + "type": "string", + "description": "The name of the workspace." + }, + "slug": { + "type": "string", + "description": "The short label that identifies this workspace." + }, + "updated_on": { + "type": "string", + "format": "date-time" + }, + "uuid": { + "type": "string", + "description": "The workspace's immutable id." + } + }, + "additionalProperties": true + } + ], + "title": "Workspace", + "description": "A Bitbucket workspace.\n Workspaces are used to organize repositories." + }, + "workspace_membership": { + "allOf": [ + { + "$ref": "#/components/schemas/object" + }, + { + "type": "object", + "properties": { + "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 + }, + "user": { + "$ref": "#/components/schemas/account" + }, + "workspace": { + "$ref": "#/components/schemas/workspace" + } + }, + "additionalProperties": true + } + ], + "title": "Workspace Membership", + "description": "A Bitbucket workspace membership.\n Links a user to a workspace." + } + } + } +} diff --git a/plugins/bitbucket-cloud-common/openapitools.json b/plugins/bitbucket-cloud-common/openapitools.json new file mode 100644 index 0000000000..c5bd3a2d0e --- /dev/null +++ b/plugins/bitbucket-cloud-common/openapitools.json @@ -0,0 +1,22 @@ +{ + "$schema": "../../node_modules/@openapitools/openapi-generator-cli/config.schema.json", + "spaces": 2, + "generator-cli": { + "version": "6.0.0-beta", + "generators": { + "backstage": { + "generatorName": "typescript-fetch", + "glob": "bitbucket-cloud.oas.json", + "output": "src", + "additionalProperties": { + "disallowAdditionalPropertiesIfNotPresent": false, + "enumPropertyNaming": "PascalCase", + "legacyDiscriminatorBehavior": false, + "modelPropertyNaming": "original", + "withoutRuntimeChecks": true + }, + "templateDir": "templates" + } + } + } +} diff --git a/plugins/bitbucket-cloud-common/package.json b/plugins/bitbucket-cloud-common/package.json new file mode 100644 index 0000000000..8dfaf0d5fa --- /dev/null +++ b/plugins/bitbucket-cloud-common/package.json @@ -0,0 +1,43 @@ +{ + "name": "@backstage/plugin-bitbucket-cloud-common", + "description": "Common functionalities for bitbucket-cloud plugins", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "module": "dist/index.esm.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "common-library" + }, + "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", + "refresh-schema": "scripts/prepare-schema.js && 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" + }, + "dependencies": { + "@backstage/integration": "^1.2.1-next.0", + "cross-fetch": "^3.1.5" + }, + "devDependencies": { + "@backstage/cli": "^0.17.2-next.0", + "@openapitools/openapi-generator-cli": "^2.4.26", + "msw": "^0.35.0", + "ts-morph": "^15.0.0" + }, + "files": [ + "dist" + ] +} diff --git a/plugins/bitbucket-cloud-common/scripts/adjust-models.js b/plugins/bitbucket-cloud-common/scripts/adjust-models.js new file mode 100755 index 0000000000..5d6eea845b --- /dev/null +++ b/plugins/bitbucket-cloud-common/scripts/adjust-models.js @@ -0,0 +1,115 @@ +#!/usr/bin/env node +'use strict'; +/* + * 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 tsMorph = require('ts-morph'); + +function cleanupWrongAllOfModels(modelsModule) { + const allOfInterfaces = modelsModule + .getInterfaces() + .filter(i => i.getName().includes('AllOf')); + allOfInterfaces.forEach(i => { + const name = i.getName(); + const realName = name.replace('AllOf', ''); + + const realInterface = modelsModule.getInterface(realName); + if (realInterface) { + i.remove(); + } else { + i.rename(realName); + } + }); + + const allOfTypes = modelsModule + .getTypeAliases() + .filter(t => t.getName().includes('AllOf')); + allOfTypes.forEach(t => { + const name = t.getName(); + const realName = name.replace('AllOf', ''); + + const varStmt = modelsModule.getVariableStatementOrThrow(t.getName()); + + const realType = modelsModule.getTypeAlias(realName); + if (realType) { + t.remove(); + varStmt.remove(); + } else { + t.rename(realName); + varStmt.getDeclarationList().getDeclarations()[0].rename(realName); + } + }); +} + +function makePaginatedGeneric(modelsModule) { + const paginated = modelsModule.getInterface('Paginated'); + + if (paginated.getTypeParameters().length === 0) { + paginated.addTypeParameter('TResultItem'); + } + + const valuesProperty = paginated.getPropertyOrThrow('values'); + let valuesType = valuesProperty + .getType() + .getText() + .replace('| null ', '') + .replace('any[]', 'Array') + .replaceAll('any', 'TResultItem'); + if (valuesProperty.hasQuestionToken()) { + valuesType = valuesType.replace(' | undefined', ''); + } + valuesProperty.setType(valuesType); +} + +function setPaginatedResultItemType(modelsModule) { + modelsModule + .getInterfaces() + .filter(i => + i.getExtends().find(it => it.getExpression().getText() === 'Paginated'), + ) + .forEach(i => { + const paginatedExtends = i + .getExtends() + .find(it => it.getExpression().getText() === 'Paginated'); + + const valuesProperty = i.getPropertyOrThrow('values'); + const resultItemType = valuesProperty + .getType() + .getUnionTypes() + .map(it => it.getText()) + .find(it => it !== 'undefined') + .replaceAll(/import[^ ]+.Models./g, '') + .replace('[]', '') + .replace(/(?:Array|Set)<(.*)>/, '$1'); + + paginatedExtends.setExpression(`Paginated<${resultItemType}>`); + }); +} + +const project = new tsMorph.Project({ + tsConfigFilePath: '../../tsconfig.json', + skipAddingFilesFromTsConfig: true, +}); +project.addSourceFilesAtPaths('src/**'); + +const modelsFile = project.getSourceFile('src/models/index.ts'); +const modelsModule = modelsFile.getModuleOrThrow('Models'); + +cleanupWrongAllOfModels(modelsModule); +makePaginatedGeneric(modelsModule); +setPaginatedResultItemType(modelsModule); + +project.saveSync(); diff --git a/plugins/bitbucket-cloud-common/scripts/generate-models.sh b/plugins/bitbucket-cloud-common/scripts/generate-models.sh new file mode 100755 index 0000000000..7dd3170fd2 --- /dev/null +++ b/plugins/bitbucket-cloud-common/scripts/generate-models.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash + +# 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. + +set -e + +SCRIPT_DIR=$(dirname $0) +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 diff --git a/plugins/bitbucket-cloud-common/scripts/prepare-schema.js b/plugins/bitbucket-cloud-common/scripts/prepare-schema.js new file mode 100755 index 0000000000..7454aecb40 --- /dev/null +++ b/plugins/bitbucket-cloud-common/scripts/prepare-schema.js @@ -0,0 +1,279 @@ +#!/usr/bin/env node +'use strict'; +/* + * 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 BASE_DOMAIN = 'https://developer.atlassian.com'; +const SCHEMA_SOURCE = `${BASE_DOMAIN}/cloud/bitbucket/swagger.v3.json`; + +const fetch = require('cross-fetch'); +const fs = require('fs'); + +const destFile = `${__dirname}/../bitbucket-cloud.oas.json`; + +const sortSelectedProperties = (key, value) => { + if (key !== 'schemas' && key !== 'properties') { + return value; + } + + if (value && typeof value === 'object') { + return Object.keys(value) + .sort() + .reduce((o, k) => { + o[k] = value[k]; + return o; + }, {}); + } + + return value; +}; + +// similar to definition of "slug" at ""#/components/schemas/group" +const repoSlugDefinition = { + type: 'string', + description: + 'The "sluggified" version of the repository\'s name. This contains only ASCII characters and can therefore be slightly different than the name', +}; + +const paginatedDefinition = { + type: 'object', + title: 'Paginated', + description: 'A generic paginated list.', + discriminator: { + propertyName: 'type', + }, + properties: { + next: { + type: 'string', + format: 'uri', + description: + '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.', + }, + page: { + type: 'integer', + description: + 'Page number of the current results. This is an optional element that is not provided in all responses.', + }, + pagelen: { + type: 'integer', + description: + 'Current number of objects on the existing page. The default value is 10 with 100 being the maximum allowed value. Individual APIs may enforce different values.', + }, + previous: { + type: 'string', + format: 'uri', + description: + '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.', + }, + size: { + type: 'integer', + description: + '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.', + }, + values: { + description: 'The values of the current page.', + oneOf: [ + { + type: 'array', + minItems: 0, + items: {}, + uniqueItems: false, + }, + { + type: 'array', + minItems: 0, + items: {}, + uniqueItems: true, + }, + ], + }, + }, +}; + +const addMissingRepoSlug = json => { + const repoProperties = json.components.schemas.repository.allOf.find( + item => item.properties, + ).properties; + if (repoProperties.slug) { + // eslint-disable-next-line no-console + console.log( + '[WARN] repository schema already contains slug property. Patch got obsolete.', + ); + } else { + repoProperties.slug = repoSlugDefinition; + } + + return json; +}; + +const removePageDefinition = json => { + delete json.components.schemas.page; + return json; +}; + +const renamePaginatedSnippetCommitToPlural = json => { + if (!json.components.schemas.paginated_snippet_commit) { + // eslint-disable-next-line no-console + console.log( + '[WARN] $.components.schemas.paginated_snippet_commit does not exist anymore. Patch got obsolete.', + ); + return json; + } + + json.components.schemas.paginated_snippet_commits = + json.components.schemas.paginated_snippet_commit; + delete json.components.schemas.paginated_snippet_commit; + + return JSON.parse( + JSON.stringify(json).replace( + '"#/components/schemas/paginated_snippet_commit"', + '"#/components/schemas/paginated_snippet_commits"', + ), + ); +}; + +const addPaginatedDefinition = json => { + json.components.schemas.paginated = paginatedDefinition; + return json; +}; + +// Changes "interface PaginatedXyz {" to "interface PaginatedXyz extends Paginated {" +// (generic type gets extracted from "values" property) +const paginatedDefinitionsExtendPaginated = json => { + // exception to the standard naming pattern PaginatedXyz / paginated_xyz + const exceptions = [ + 'deployments_ddev_paginated_environments', + 'deployments_stg_west_paginated_environments', + 'search_result_page', + ]; + + Object.keys(json.components.schemas) + .filter(name => name.startsWith('paginated_') || exceptions.includes(name)) + .forEach(name => { + // modify other paginated_[...] schemas + const old = json.components.schemas[name]; + const title = old.title; + const description = old.description; + delete old.title; + delete old.description; + delete old.properties.page; + delete old.properties.pagelen; + delete old.properties.size; + delete old.properties.previous; + delete old.properties.next; + delete old.additionalProperties; + old.properties.values.description = + old.properties.values.description ?? + paginatedDefinition.properties.values.description; + + json.components.schemas[name] = { + title: title, + description: description, + allOf: [ + { + $ref: '#/components/schemas/paginated', + }, + old, + ], + }; + }); + + return json; +}; + +const preventHardToDetectDuplicateInterfacesDueToAllOf = json => { + Object.keys(json.components.schemas).forEach(name => { + const schema = json.components.schemas[name]; + if (!schema.allOf) { + return; + } + + schema.allOf.forEach(allOfItem => { + if (allOfItem.title) { + schema.title = schema.title ?? allOfItem.title; + schema.description = schema.description ?? allOfItem.description; + delete allOfItem.title; + delete allOfItem.description; + } + }); + }); + + 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]; + delete schema.allOf; + + json.components.schemas.pipeline_selector = { + ...schema, + ...extension, + }; + + return json; +}; + +const escapeTsdocInDescription = json => { + const prop = json.components.schemas.commitstatus.allOf[1].properties.url; + prop.description = prop.description.replaceAll(/(\S*[{]\S+[}]\S*)/g, '`$1`'); + + return json; +}; + +const relativeToAbsoluteUrls = json => { + Object.keys(json.components.schemas).forEach(name => { + const schema = json.components.schemas[name]; + if (schema.description) { + schema.description = schema.description.replace( + /]\(\/cloud\/bitbucket\//g, + `](${BASE_DOMAIN}/cloud/bitbucket/`, + ); + } + }); + + return json; +}; + +fetch(SCHEMA_SOURCE) + .then(res => res.json()) + .then(addMissingRepoSlug) + .then(removePageDefinition) + .then(renamePaginatedSnippetCommitToPlural) + .then(addPaginatedDefinition) + .then(paginatedDefinitionsExtendPaginated) + .then(preventHardToDetectDuplicateInterfacesDueToAllOf) + .then(removeBuggyDescription) + .then(resolveConflictingInheritance) + .then(escapeTsdocInDescription) + .then(relativeToAbsoluteUrls) + .then(json => { + fs.writeFileSync(destFile, JSON.stringify(json, sortSelectedProperties, 2)); + return json; + }); diff --git a/plugins/bitbucket-cloud-common/scripts/reduce-models.js b/plugins/bitbucket-cloud-common/scripts/reduce-models.js new file mode 100755 index 0000000000..9d458f4e74 --- /dev/null +++ b/plugins/bitbucket-cloud-common/scripts/reduce-models.js @@ -0,0 +1,100 @@ +#!/usr/bin/env node +'use strict'; +/* + * 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 tsMorph = require('ts-morph'); + +const project = new tsMorph.Project({ + tsConfigFilePath: '../../tsconfig.json', + skipAddingFilesFromTsConfig: true, +}); +project.addSourceFilesAtPaths('src/**'); + +const modelsFile = project.getSourceFile('src/models/index.ts'); +const modelsModule = modelsFile.getModuleOrThrow('Models'); + +const clientFile = project.getSourceFile('src/BitbucketCloudClient.ts'); +const clientClass = clientFile.getClassOrThrow('BitbucketCloudClient'); + +/** + * Returns an array of the unique items of the provided array. + * + * @param {string[]} array array with potentially non-unique items. + * @returns {string[]} array with unique items. + */ +function unique(array) { + return [...new Set(array)]; +} + +/** + * + * @param {tsMorph.ClassDeclaration | tsMorph.InterfaceDeclaration | tsMorph.TypeAliasDeclaration} stmt Statement like interface or type alias. + * @param {string[]=} processed Keeps track of which statement was already processed. + * @returns {string[]} + */ +function referencedModelsIdentifiers(stmt, processed) { + // eslint-disable-next-line no-param-reassign + processed = processed ?? []; + const name = stmt.getName(); + + if (processed.includes(name)) { + return []; + } + + const referenced = unique( + stmt + .getDescendantsOfKind(tsMorph.SyntaxKind.Identifier) + .map(it => it.getSymbol()) + .filter(it => it) + .map(it => it.getFullyQualifiedName()) + .filter(it => it.includes('Models.')) + .map(it => it.substring(it.indexOf('Models.') + 7)) + .filter(it => !it.includes('.')) + .filter(it => it !== name), + ); + processed.push(name); + + const transitivelyReferenced = referenced + .map( + it => + modelsModule.getInterface(it) ?? modelsModule.getTypeAliasOrThrow(it), + ) + .flatMap(it => referencedModelsIdentifiers(it, processed)); + + return unique([...referenced, ...transitivelyReferenced]); +} + +// all directly or transitively referenced/used `Models.[...]` are allowed to stay +const allowed = referencedModelsIdentifiers(clientClass); + +// remove everything not part of the "allow list" +modelsModule + .getInterfaces() + .filter(it => !allowed.includes(it.getName())) + .forEach(it => it.remove()); + +modelsModule + .getTypeAliases() + .filter(it => !allowed.includes(it.getName())) + .forEach(it => { + const varStmt = modelsModule.getVariableStatementOrThrow(it.getName()); + + it.remove(); + varStmt.remove(); + }); + +project.saveSync(); diff --git a/plugins/bitbucket-cloud-common/src/.openapi-generator-ignore b/plugins/bitbucket-cloud-common/src/.openapi-generator-ignore new file mode 100644 index 0000000000..aec8c32345 --- /dev/null +++ b/plugins/bitbucket-cloud-common/src/.openapi-generator-ignore @@ -0,0 +1,9 @@ +# OpenAPI Generator Ignore +# Generated by openapi-generator https://github.com/openapitools/openapi-generator + +# we maintain this file manually +index.ts + +# we only want the models to be generated +apis/ +runtime.ts diff --git a/plugins/bitbucket-cloud-common/src/.openapi-generator/FILES b/plugins/bitbucket-cloud-common/src/.openapi-generator/FILES new file mode 100644 index 0000000000..a93701aec1 --- /dev/null +++ b/plugins/bitbucket-cloud-common/src/.openapi-generator/FILES @@ -0,0 +1 @@ +models/index.ts diff --git a/plugins/bitbucket-cloud-common/src/.openapi-generator/VERSION b/plugins/bitbucket-cloud-common/src/.openapi-generator/VERSION new file mode 100644 index 0000000000..ec76f3788a --- /dev/null +++ b/plugins/bitbucket-cloud-common/src/.openapi-generator/VERSION @@ -0,0 +1 @@ +6.0.0-beta \ No newline at end of file diff --git a/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.test.ts b/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.test.ts new file mode 100644 index 0000000000..c5c0d604be --- /dev/null +++ b/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.test.ts @@ -0,0 +1,110 @@ +/* + * 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 { BitbucketCloudIntegrationConfig } from '@backstage/integration'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; +import { BitbucketCloudClient } from './BitbucketCloudClient'; +import { Models } from './models'; + +const server = setupServer(); + +describe('BitbucketCloudClient', () => { + const config: BitbucketCloudIntegrationConfig = { + host: 'bitbucket.org', + apiBaseUrl: 'https://api.bitbucket.org/2.0', + username: 'test-user', + appPassword: 'test-pw', + }; + const client = BitbucketCloudClient.fromConfig(config); + + beforeAll(() => server.listen({ onUnhandledRequest: 'error' })); + afterAll(() => server.close()); + afterEach(() => server.resetHandlers()); + + it('searchCode', async () => { + server.use( + rest.get( + `https://api.bitbucket.org/2.0/workspaces/ws/search/code`, + (req, res, ctx) => { + if ( + req.headers.get('authorization') !== + 'Basic dGVzdC11c2VyOnRlc3QtcHc=' + ) { + return res(ctx.status(400)); + } + + const query = req.url.searchParams.get('search_query'); + if (query !== 'query') { + return res(ctx.json({ values: [] } as Models.SearchResultPage)); + } + + const response: Models.SearchResultPage = { + values: [ + { + content_match_count: 1, + file: { + type: 'commit_file', + path: 'path/to/file', + }, + }, + ], + }; + return res(ctx.json(response)); + }, + ), + ); + + const pagination = client.searchCode('ws', 'query'); + + const results = []; + for await (const result of pagination.iterateResults()) { + results.push(result); + } + + expect(results).toHaveLength(1); + expect(results[0].file!.path).toEqual('path/to/file'); + }); + + it('listRepositoriesByWorkspace', async () => { + server.use( + rest.get( + 'https://api.bitbucket.org/2.0/repositories/ws', + (_, res, ctx) => { + const response = { + values: [ + { + type: 'repository', + slug: 'repo1', + } as Models.Repository, + ], + }; + return res(ctx.json(response)); + }, + ), + ); + + const pagination = client.listRepositoriesByWorkspace('ws'); + + const results = []; + for await (const result of pagination.iterateResults()) { + results.push(result); + } + + expect(results).toHaveLength(1); + expect(results[0].slug).toEqual('repo1'); + }); +}); diff --git a/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts b/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts new file mode 100644 index 0000000000..3ac005fe2b --- /dev/null +++ b/plugins/bitbucket-cloud-common/src/BitbucketCloudClient.ts @@ -0,0 +1,120 @@ +/* + * 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 { BitbucketCloudIntegrationConfig } from '@backstage/integration'; +import fetch, { Request } from 'cross-fetch'; +import { Models } from './models'; +import { WithPagination } from './pagination'; +import { + FilterAndSortOptions, + PartialResponseOptions, + RequestOptions, +} from './types'; + +/** @public */ +export class BitbucketCloudClient { + static fromConfig( + config: BitbucketCloudIntegrationConfig, + ): BitbucketCloudClient { + return new BitbucketCloudClient(config); + } + + private constructor( + private readonly config: BitbucketCloudIntegrationConfig, + ) {} + + searchCode( + workspace: string, + query: string, + options?: FilterAndSortOptions & PartialResponseOptions, + ): WithPagination { + const workspaceEnc = encodeURIComponent(workspace); + return new WithPagination( + paginationOptions => + this.createUrl(`/workspaces/${workspaceEnc}/search/code`, { + ...paginationOptions, + ...options, + search_query: query, + }), + url => this.getTypeMapped(url), + ); + } + + listRepositoriesByWorkspace( + workspace: string, + options?: FilterAndSortOptions & PartialResponseOptions, + ): WithPagination { + const workspaceEnc = encodeURIComponent(workspace); + + return new WithPagination( + paginationOptions => + this.createUrl(`/repositories/${workspaceEnc}`, { + ...paginationOptions, + ...options, + }), + url => this.getTypeMapped(url), + ); + } + + private createUrl(endpoint: string, options?: RequestOptions): URL { + const request = new URL(this.config.apiBaseUrl + endpoint); + for (const key in options) { + if (options[key]) { + request.searchParams.append(key, options[key]!.toString()); + } + } + + return request; + } + + private async getTypeMapped(url: URL): Promise { + return this.get(url).then( + (response: Response) => response.json() as Promise, + ); + } + + private async get(url: URL): Promise { + return this.request(new Request(url.toString(), { method: 'GET' })); + } + + private async request(req: Request): Promise { + return fetch(req, { headers: this.getAuthHeaders() }).then( + (response: Response) => { + if (!response.ok) { + throw new Error( + `Unexpected response for ${req.method} ${req.url}. Expected 200 but got ${response.status} - ${response.statusText}`, + ); + } + + return response; + }, + ); + } + + private getAuthHeaders(): Record { + const headers: Record = {}; + + if (this.config.username) { + const buffer = Buffer.from( + `${this.config.username}:${this.config.appPassword}`, + 'utf8', + ); + headers.Authorization = `Basic ${buffer.toString('base64')}`; + } + + return headers; + } +} diff --git a/plugins/bitbucket-cloud-common/src/index.ts b/plugins/bitbucket-cloud-common/src/index.ts new file mode 100644 index 0000000000..4edf148240 --- /dev/null +++ b/plugins/bitbucket-cloud-common/src/index.ts @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/** + * Common functionalities for bitbucket-cloud plugins. + * + * @packageDocumentation + */ + +export * from './BitbucketCloudClient'; +export * from './models'; +export * from './pagination'; +export * from './types'; diff --git a/plugins/bitbucket-cloud-common/src/models/index.ts b/plugins/bitbucket-cloud-common/src/models/index.ts new file mode 100644 index 0000000000..14cb8865b8 --- /dev/null +++ b/plugins/bitbucket-cloud-common/src/models/index.ts @@ -0,0 +1,532 @@ +/* + * 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. + */ + +/** + * Bitbucket API + * 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. + * + * The version of the OpenAPI document: 2.0 + * Contact: support@bitbucket.org + * + * NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ + +/** @public */ +export namespace Models { + /** + * An account object. + * @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; + } + + /** + * @public + */ + export interface AccountLinks { + avatar?: Link; + followers?: Link; + following?: Link; + html?: Link; + repositories?: Link; + self?: Link; + } + + /** + * The author of a change in a repository + * @public + */ + export interface Author extends ModelObject { + /** + * The raw author value from the repository. This may be the only value available if the author does not match a user in Bitbucket. + */ + raw?: string; + user?: Account; + } + + /** + * The common base type for both repository and snippet commits. + * @public + */ + export interface BaseCommit extends ModelObject { + author?: Author; + date?: string; + hash?: string; + message?: string; + parents?: Array; + summary?: BaseCommitSummary; + } + + /** + * @public + */ + export interface BaseCommitSummary { + /** + * The user's content rendered as HTML. + */ + html?: string; + /** + * The type of markup language the raw content is to be interpreted in. + */ + markup?: BaseCommitSummaryMarkupEnum; + /** + * The text as it was typed by a user. + */ + raw?: string; + } + + /** + * The type of markup language the raw content is to be interpreted in. + * @public + */ + export const BaseCommitSummaryMarkupEnum = { + Markdown: 'markdown', + Creole: 'creole', + Plaintext: 'plaintext', + } as const; + + /** + * The type of markup language the raw content is to be interpreted in. + * @public + */ + export type BaseCommitSummaryMarkupEnum = + typeof BaseCommitSummaryMarkupEnum[keyof typeof BaseCommitSummaryMarkupEnum]; + + /** + * A branch object, representing a branch in a repository. + * @public + */ + export interface Branch { + links?: RefLinks; + /** + * The name of the ref. + */ + name?: string; + target?: Commit; + type: string; + /** + * The default merge strategy for pull requests targeting this branch. + */ + default_merge_strategy?: string; + /** + * Available merge strategies for pull requests targeting this branch. + */ + merge_strategies?: Array; + } + + /** + * Available merge strategies for pull requests targeting this branch. + * @public + */ + export const BranchMergeStrategiesEnum = { + MergeCommit: 'merge_commit', + Squash: 'squash', + FastForward: 'fast_forward', + } as const; + + /** + * Available merge strategies for pull requests targeting this branch. + * @public + */ + export type BranchMergeStrategiesEnum = + typeof BranchMergeStrategiesEnum[keyof typeof BranchMergeStrategiesEnum]; + + /** + * A repository commit object. + * @public + */ + export interface Commit extends BaseCommit { + participants?: Array; + repository?: Repository; + } + + /** + * A file object, representing a file at a commit in a repository + * @public + */ + export interface CommitFile { + [key: string]: unknown; + attributes?: CommitFileAttributesEnum; + commit?: Commit; + /** + * The escaped version of the path as it appears in a diff. If the path does not require escaping this will be the same as path. + */ + escaped_path?: string; + /** + * The path in the repository + */ + path?: string; + type: string; + } + + /** + * @public + */ + export const CommitFileAttributesEnum = { + Link: 'link', + Executable: 'executable', + Subrepository: 'subrepository', + Binary: 'binary', + Lfs: 'lfs', + } as const; + + /** + * @public + */ + export type CommitFileAttributesEnum = + typeof CommitFileAttributesEnum[keyof typeof CommitFileAttributesEnum]; + + /** + * A link to a resource related to this object. + * @public + */ + export interface Link { + href?: string; + name?: string; + } + + /** + * Base type for most resource objects. It defines the common `type` element that identifies an object's type. It also identifies the element as Swagger's `discriminator`. + * @public + */ + export interface ModelObject { + [key: string]: unknown; + type: string; + } + + /** + * A generic paginated list. + * @public + */ + export interface Paginated { + /** + * 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. + */ + next?: string; + /** + * Page number of the current results. This is an optional element that is not provided in all responses. + */ + page?: number; + /** + * Current number of objects on the existing page. The default value is 10 with 100 being the maximum allowed value. Individual APIs may enforce different values. + */ + pagelen?: number; + /** + * 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. + */ + previous?: string; + /** + * 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. + */ + size?: number; + /** + * The values of the current page. + */ + values?: Array | Set; + } + + /** + * A paginated list of repositories. + * @public + */ + export interface PaginatedRepositories extends Paginated { + /** + * The values of the current page. + */ + values?: Set; + } + + /** + * Object describing a user's role on resources like commits or pull requests. + * @public + */ + export interface Participant extends ModelObject { + approved?: boolean; + /** + * The ISO8601 timestamp of the participant's action. For approvers, this is the time of their approval. For commenters and pull request reviewers who are not approvers, this is the time they last commented, or null if they have not commented. + */ + participated_on?: string; + role?: ParticipantRoleEnum; + state?: ParticipantStateEnum; + user?: User; + } + + /** + * @public + */ + export const ParticipantRoleEnum = { + Participant: 'PARTICIPANT', + Reviewer: 'REVIEWER', + } as const; + + /** + * @public + */ + export type ParticipantRoleEnum = + typeof ParticipantRoleEnum[keyof typeof ParticipantRoleEnum]; + + /** + * @public + */ + export const ParticipantStateEnum = { + Approved: 'approved', + ChangesRequested: 'changes_requested', + Null: 'null', + } as const; + + /** + * @public + */ + export type ParticipantStateEnum = + typeof ParticipantStateEnum[keyof typeof ParticipantStateEnum]; + + /** + * A Bitbucket project. + * Projects are used by teams to organize repositories. + * @public + */ + export interface Project extends ModelObject { + created_on?: string; + description?: string; + /** + * + * Indicates whether the project contains publicly visible repositories. + * Note that private projects cannot contain public repositories. + */ + has_publicly_visible_repos?: boolean; + /** + * + * Indicates whether the project is publicly accessible, or whether it is + * private to the team and consequently only visible to team members. + * Note that private projects cannot contain public repositories. + */ + is_private?: boolean; + /** + * The project's key. + */ + key?: string; + links?: ProjectLinks; + /** + * The name of the project. + */ + name?: string; + owner?: Team; + updated_on?: string; + /** + * The project's immutable id. + */ + uuid?: string; + } + + /** + * @public + */ + export interface ProjectLinks { + avatar?: Link; + html?: Link; + } + + /** + * @public + */ + export interface RefLinks { + commits?: Link; + html?: Link; + self?: Link; + } + + /** + * A Bitbucket repository. + * @public + */ + export interface Repository extends ModelObject { + created_on?: string; + description?: string; + /** + * + * Controls the rules for forking this repository. + * + * * **allow_forks**: unrestricted forking + * * **no_public_forks**: restrict forking to private forks (forks cannot + * be made public later) + * * **no_forks**: deny all forking + */ + fork_policy?: RepositoryForkPolicyEnum; + /** + * The concatenation of the repository owner's username and the slugified name, e.g. "evzijst/interruptingcow". This is the same string used in Bitbucket URLs. + */ + full_name?: string; + has_issues?: boolean; + has_wiki?: boolean; + is_private?: boolean; + language?: string; + links?: RepositoryLinks; + mainbranch?: Branch; + name?: string; + owner?: Account; + parent?: Repository; + project?: Project; + scm?: RepositoryScmEnum; + size?: number; + /** + * The "sluggified" version of the repository's name. This contains only ASCII characters and can therefore be slightly different than the name + */ + slug?: string; + updated_on?: string; + /** + * The repository's immutable id. This can be used as a substitute for the slug segment in URLs. Doing this guarantees your URLs will survive renaming of the repository by its owner, or even transfer of the repository to a different user. + */ + uuid?: string; + } + + /** + * + * Controls the rules for forking this repository. + * + * * **allow_forks**: unrestricted forking + * * **no_public_forks**: restrict forking to private forks (forks cannot + * be made public later) + * * **no_forks**: deny all forking + * @public + */ + export const RepositoryForkPolicyEnum = { + AllowForks: 'allow_forks', + NoPublicForks: 'no_public_forks', + NoForks: 'no_forks', + } as const; + + /** + * + * Controls the rules for forking this repository. + * + * * **allow_forks**: unrestricted forking + * * **no_public_forks**: restrict forking to private forks (forks cannot + * be made public later) + * * **no_forks**: deny all forking + * @public + */ + export type RepositoryForkPolicyEnum = + typeof RepositoryForkPolicyEnum[keyof typeof RepositoryForkPolicyEnum]; + + /** + * @public + */ + export const RepositoryScmEnum = { + Git: 'git', + } as const; + + /** + * @public + */ + export type RepositoryScmEnum = + typeof RepositoryScmEnum[keyof typeof RepositoryScmEnum]; + + /** + * @public + */ + export interface RepositoryLinks { + avatar?: Link; + clone?: Array; + commits?: Link; + downloads?: Link; + forks?: Link; + hooks?: Link; + html?: Link; + pullrequests?: Link; + self?: Link; + watchers?: Link; + } + + /** + * @public + */ + export interface SearchCodeSearchResult { + readonly content_match_count?: number; + readonly content_matches?: Array; + file?: CommitFile; + readonly path_matches?: Array; + readonly type?: string; + } + + /** + * @public + */ + export interface SearchContentMatch { + readonly lines?: Array; + } + + /** + * @public + */ + export interface SearchLine { + readonly line?: number; + readonly segments?: Array; + } + + /** + * @public + */ + export interface SearchResultPage extends Paginated { + readonly query_substituted?: boolean; + /** + * The values of the current page. + */ + readonly values?: Array; + } + + /** + * @public + */ + export interface SearchSegment { + readonly match?: boolean; + readonly text?: string; + } + + /** + * A team object. + * @public + */ + export interface Team extends Account {} + + /** + * A user object. + * @public + */ + export interface User extends Account { + /** + * The user's Atlassian account ID. + */ + account_id?: string; + is_staff?: boolean; + } +} diff --git a/plugins/bitbucket-cloud-common/src/pagination.test.ts b/plugins/bitbucket-cloud-common/src/pagination.test.ts new file mode 100644 index 0000000000..f263f51897 --- /dev/null +++ b/plugins/bitbucket-cloud-common/src/pagination.test.ts @@ -0,0 +1,124 @@ +/* + * 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 { Models } from './models'; +import { WithPagination } from './pagination'; + +interface TestResultItem { + url: string; +} + +interface TestPage extends Models.Paginated {} + +describe('WithPagination', () => { + const createPagination = () => + new WithPagination( + opts => + new URL( + `http://localhost/create-url?page=${opts.page}&pagelen=${opts.pagelen}`, + ), + async url => { + const currentPage = Number.parseInt(url.searchParams.get('page')!, 10); + const next = new URL(url.toString()); + next.searchParams.set('page', (currentPage + 1).toString()); + + return { + page: currentPage, + ...(currentPage >= 2 ? {} : { next: next.toString() }), + values: [ + { + url: url.toString(), + }, + ], + }; + }, + ); + + it('iterateResults', async () => { + const pagination = createPagination(); + + const urls = []; + for await (const result of pagination.iterateResults()) { + urls.push(result.url.toString()); + } + + expect(urls).toHaveLength(2); + expect(urls).toEqual([ + 'http://localhost/create-url?page=1&pagelen=100', + 'http://localhost/create-url?page=2&pagelen=100', + ]); + }); + + it('iteratePages', async () => { + const pagination = createPagination(); + + const pages = []; + for await (const page of pagination.iteratePages()) { + pages.push(page); + } + + expect(pages).toHaveLength(2); + expect(pages).toEqual([ + { + next: 'http://localhost/create-url?page=2&pagelen=100', + page: 1, + values: [ + { + url: 'http://localhost/create-url?page=1&pagelen=100', + }, + ], + }, + { + page: 2, + values: [ + { + url: 'http://localhost/create-url?page=2&pagelen=100', + }, + ], + }, + ]); + }); + + describe('getPage', () => { + it('default opts', async () => { + const pagination = createPagination(); + + const page = await pagination.getPage(); + + expect(page.page).toEqual(1); + expect(page.next).toEqual( + 'http://localhost/create-url?page=2&pagelen=100', + ); + expect(page.values).toHaveLength(1); + expect((page.values! as TestResultItem[])[0].url).toEqual( + 'http://localhost/create-url?page=1&pagelen=100', + ); + }); + + it('custom opts', async () => { + const pagination = createPagination(); + + const page = await pagination.getPage({ page: 4 }); + + expect(page.page).toEqual(4); + expect(page.next).toBeUndefined(); + expect(page.values).toHaveLength(1); + expect((page.values! as TestResultItem[])[0].url).toEqual( + 'http://localhost/create-url?page=4&pagelen=100', + ); + }); + }); +}); diff --git a/plugins/bitbucket-cloud-common/src/pagination.ts b/plugins/bitbucket-cloud-common/src/pagination.ts new file mode 100644 index 0000000000..98e4f9f9d7 --- /dev/null +++ b/plugins/bitbucket-cloud-common/src/pagination.ts @@ -0,0 +1,66 @@ +/* + * 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 { Models } from './models'; + +/** @public */ +export type PaginationOptions = { + page?: number; + pagelen?: number; +}; + +/** @public */ +export class WithPagination< + TPage extends Models.Paginated, + TResultItem, +> { + constructor( + private readonly createUrl: (options: PaginationOptions) => URL, + private readonly fetch: (url: URL) => Promise, + ) {} + + getPage(options?: PaginationOptions): Promise { + const opts = { page: 1, pagelen: 100, ...options }; + const url = this.createUrl(opts); + return this.fetch(url); + } + + async *iteratePages( + options?: PaginationOptions, + ): AsyncGenerator { + const opts = { page: 1, pagelen: 100, ...options }; + let url: URL | undefined = this.createUrl(opts); + let res; + do { + res = await this.fetch(url); + url = res.next ? new URL(res.next) : undefined; + yield res; + } while (url); + } + + async *iterateResults(options?: PaginationOptions) { + const opts = { page: 1, pagelen: 100, ...options }; + let url: URL | undefined = this.createUrl(opts); + let res; + do { + res = await this.fetch(url); + url = res.next ? new URL(res.next) : undefined; + for (const item of res.values ?? []) { + yield item; + } + } while (url); + } +} diff --git a/plugins/bitbucket-cloud-common/src/setupTests.ts b/plugins/bitbucket-cloud-common/src/setupTests.ts new file mode 100644 index 0000000000..8b9b6bd586 --- /dev/null +++ b/plugins/bitbucket-cloud-common/src/setupTests.ts @@ -0,0 +1,16 @@ +/* + * 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/bitbucket-cloud-common/src/types.ts b/plugins/bitbucket-cloud-common/src/types.ts new file mode 100644 index 0000000000..8de4abc6f1 --- /dev/null +++ b/plugins/bitbucket-cloud-common/src/types.ts @@ -0,0 +1,35 @@ +/* + * 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 { PaginationOptions } from './pagination'; + +/** @public */ +export type FilterAndSortOptions = { + q?: string; + sort?: string; +}; + +/** @public */ +export type PartialResponseOptions = { + fields?: string; +}; + +/** @public */ +export type RequestOptions = FilterAndSortOptions & + PaginationOptions & + PartialResponseOptions & { + [key: string]: string | number | undefined; + }; diff --git a/plugins/bitbucket-cloud-common/templates/licenseInfo.mustache b/plugins/bitbucket-cloud-common/templates/licenseInfo.mustache new file mode 100644 index 0000000000..118dbbdfdb --- /dev/null +++ b/plugins/bitbucket-cloud-common/templates/licenseInfo.mustache @@ -0,0 +1,11 @@ +/** + * {{{appName}}} + * {{{appDescription}}} + * + * {{#version}}The version of the OpenAPI document: {{{.}}}{{/version}} + * {{#infoEmail}}Contact: {{{.}}}{{/infoEmail}} + * + * NOTE: This file was auto generated by OpenAPI Generator (https://openapi-generator.tech). + * https://openapi-generator.tech + * Do not edit the class manually. + */ diff --git a/plugins/bitbucket-cloud-common/templates/modelGenericInterfaces.mustache b/plugins/bitbucket-cloud-common/templates/modelGenericInterfaces.mustache new file mode 100644 index 0000000000..38027d21a7 --- /dev/null +++ b/plugins/bitbucket-cloud-common/templates/modelGenericInterfaces.mustache @@ -0,0 +1,61 @@ +/** +{{#unescapedDescription}} + * {{#lambda.indented_star_1}}{{{unescapedDescription}}}{{/lambda.indented_star_1}} +{{/unescapedDescription}} + * @public +{{#deprecated}} + * @deprecated +{{/deprecated}} + */ +export interface {{classname}} {{#parent}}extends {{{.}}} {{/parent}}{ +{{#additionalPropertiesType}} + [key: string]: unknown; +{{/additionalPropertiesType}} +{{#vars}} +{{#unescapedDescription}} + /** + * {{#lambda.indented_star_4}}{{{unescapedDescription}}}{{/lambda.indented_star_4}} + {{#deprecated}} + * @deprecated + {{/deprecated}} + */ + {{/unescapedDescription}} + {{^unescapedDescription}} + {{#deprecated}} + /** @deprecated */ + {{/deprecated}} + {{/unescapedDescription}} + {{#isReadOnly}}readonly {{/isReadOnly}}{{name}}{{^required}}?{{/required}}: {{#isEnum}}{{{datatypeWithEnum}}}{{/isEnum}}{{^isEnum}}{{{datatype}}}{{#isNullable}} | null{{/isNullable}}{{/isEnum}}; +{{/vars}} +}{{#hasEnums}} +{{#vars}} +{{#isEnum}} + +/** +{{#unescapedDescription}} + * {{#lambda.indented_star_1}}{{{unescapedDescription}}}{{/lambda.indented_star_1}} +{{/unescapedDescription}} + * @public +{{#deprecated}} + * @deprecated +{{/deprecated}} + */ +export const {{classname}}{{enumName}} = { +{{#allowableValues}} + {{#enumVars}} + {{{name}}}: {{{value}}}{{^-last}},{{/-last}} + {{/enumVars}} +{{/allowableValues}} +} as const; + +/** +{{#unescapedDescription}} + * {{#lambda.indented_star_1}}{{{unescapedDescription}}}{{/lambda.indented_star_1}} +{{/unescapedDescription}} + * @public +{{#deprecated}} + * @deprecated +{{/deprecated}} + */ +export type {{classname}}{{enumName}} = typeof {{classname}}{{enumName}}[keyof typeof {{classname}}{{enumName}}]; +{{/isEnum}}{{/vars}}{{/hasEnums}} diff --git a/plugins/bitbucket-cloud-common/templates/models.index.mustache b/plugins/bitbucket-cloud-common/templates/models.index.mustache new file mode 100644 index 0000000000..9e9dbd697e --- /dev/null +++ b/plugins/bitbucket-cloud-common/templates/models.index.mustache @@ -0,0 +1,47 @@ +/* + * 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. + */ + +{{>licenseInfo}} +/** @public */ +export namespace Models { +{{#models}} +{{#model}} +{{^withoutRuntimeChecks}} +export * from './{{{ classFilename }}}'; +{{#useSagaAndRecords}} +{{^isEnum}} +export * from './{{{ classFilename }}}Record'; +{{/isEnum}} +{{/useSagaAndRecords}} +{{/withoutRuntimeChecks}} +{{#withoutRuntimeChecks}} +{{#isEnum}} +{{>modelEnumInterfaces}} +{{/isEnum}} +{{^isEnum}} +{{#oneOf}} +{{#-first}} +{{>modelOneOfInterfaces}} +{{/-first}} +{{/oneOf}} +{{^oneOf}} +{{>modelGenericInterfaces}} +{{/oneOf}} +{{/isEnum}} +{{/withoutRuntimeChecks}} +{{/model}} +{{/models}} +} diff --git a/yarn.lock b/yarn.lock index 79492064a9..4941e1a448 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4224,6 +4224,29 @@ resolved "https://registry.npmjs.org/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-3.1.0.tgz#be450c97d1c7cd6af1a992d53232704454345df9" integrity sha512-K4scWxGhdQM0masHHy4gIQs2iGiLEXCrXttumknyPJqtdl4J179BjpibWSSQ1fxKdCcHgIlCTKXJU6cMM6D6Wg== +"@nestjs/common@8.4.4": + version "8.4.4" + resolved "https://registry.npmjs.org/@nestjs/common/-/common-8.4.4.tgz#0914c6c0540b5a344c7c8fd6072faa1a49af1158" + integrity sha512-QHi7QcgH/5Jinz+SCfIZJkFHc6Cch1YsAEGFEhi6wSp6MILb0sJMQ1CX06e9tCOAjSlBwaJj4PH0eFCVau5v9Q== + dependencies: + axios "0.26.1" + iterare "1.2.1" + tslib "2.3.1" + uuid "8.3.2" + +"@nestjs/core@8.4.4": + version "8.4.4" + resolved "https://registry.npmjs.org/@nestjs/core/-/core-8.4.4.tgz#94fd2d63fd77791f616fbecafb79faa2235eeeff" + integrity sha512-Ef3yJPuzAttpNfehnGqIV5kHIL9SHptB5F4ERxoU7pT61H3xiYpZw6hSjx68cJO7cc6rm7/N+b4zeuJvFHtvBg== + dependencies: + "@nuxtjs/opencollective" "0.3.2" + fast-safe-stringify "2.1.1" + iterare "1.2.1" + object-hash "3.0.0" + path-to-regexp "3.2.0" + tslib "2.3.1" + uuid "8.3.2" + "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" @@ -4399,6 +4422,15 @@ node-gyp "^8.2.0" read-package-json-fast "^2.0.1" +"@nuxtjs/opencollective@0.3.2": + version "0.3.2" + resolved "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz#620ce1044f7ac77185e825e1936115bb38e2681c" + integrity sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA== + dependencies: + chalk "^4.1.0" + consola "^2.15.0" + node-fetch "^2.6.1" + "@octokit/app@^12.0.4": version "12.0.5" resolved "https://registry.npmjs.org/@octokit/app/-/app-12.0.5.tgz#0b25446daffcb36967b26944410eab1ccbba0c06" @@ -4746,6 +4778,27 @@ fast-deep-equal "^3.1.3" lodash.clonedeep "^4.5.0" +"@openapitools/openapi-generator-cli@^2.4.26": + version "2.5.1" + resolved "https://registry.npmjs.org/@openapitools/openapi-generator-cli/-/openapi-generator-cli-2.5.1.tgz#3825be4f7317199183fbd0d464dd8a7ba055fcf5" + integrity sha512-WSRQBU0dCSVD+0Qv8iCsv0C4iMaZe/NpJ/CT4SmrEYLH3txoKTE8wEfbdj/kqShS8Or0YEGDPUzhSIKY151L0w== + dependencies: + "@nestjs/common" "8.4.4" + "@nestjs/core" "8.4.4" + "@nuxtjs/opencollective" "0.3.2" + chalk "4.1.2" + commander "8.3.0" + compare-versions "4.1.3" + concurrently "6.5.1" + console.table "0.10.0" + fs-extra "10.0.1" + glob "7.1.6" + inquirer "8.2.2" + lodash "4.17.21" + reflect-metadata "0.1.13" + rxjs "7.5.5" + tslib "2.0.3" + "@opentelemetry/api@^1.0.1": version "1.0.4" resolved "https://registry.npmjs.org/@opentelemetry/api/-/api-1.0.4.tgz#a167e46c10d05a07ab299fc518793b0cff8f6924" @@ -5357,6 +5410,16 @@ resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== +"@ts-morph/common@~0.15.0": + version "0.15.0" + resolved "https://registry.npmjs.org/@ts-morph/common/-/common-0.15.0.tgz#aece752746fc0d779d2acfaece95fb2c23327ba5" + integrity sha512-QefRbadcwfBnd3HWrltpjRJprHgeKfQsnbyGbRF8pEjMqISAljJwq4wfRETxxojsmN4GWuJv3PWG+W7kBIHMMw== + dependencies: + fast-glob "^3.2.11" + minimatch "^5.0.1" + mkdirp "^1.0.4" + path-browserify "^1.0.1" + "@tsconfig/node10@^1.0.7": version "1.0.8" resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9" @@ -7912,6 +7975,13 @@ axios-cached-dns-resolve@0.5.2: pino "^5.12.2" pino-pretty "^2.6.0" +axios@0.26.1: + version "0.26.1" + resolved "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz#1ede41c51fcf51bbbd6fd43669caaa4f0495aaa9" + integrity sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA== + dependencies: + follow-redirects "^1.14.8" + axios@^0.21.1, axios@^0.21.4: version "0.21.4" resolved "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" @@ -8838,6 +8908,14 @@ chalk@4.1.1: ansi-styles "^4.1.0" supports-color "^7.1.0" +chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: + version "4.1.2" + resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + chalk@^1.0.0, chalk@^1.1.3: version "1.1.3" resolved "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -8857,14 +8935,6 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1.2: - version "4.1.2" - resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" - integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - change-case-all@1.0.14: version "1.0.14" resolved "https://registry.npmjs.org/change-case-all/-/change-case-all-1.0.14.tgz#bac04da08ad143278d0ac3dda7eccd39280bfba1" @@ -9213,6 +9283,13 @@ co@^4.6.0: resolved "https://registry.npmjs.org/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= +code-block-writer@^11.0.0: + version "11.0.0" + resolved "https://registry.npmjs.org/code-block-writer/-/code-block-writer-11.0.0.tgz#5956fb186617f6740e2c3257757fea79315dd7d4" + integrity sha512-GEqWvEWWsOvER+g9keO4ohFoD3ymwyCnqY3hoTr7GZipYFwEhMHJw+TtV0rfgRhNImM6QWZGO2XYjlJVyYT62w== + dependencies: + tslib "2.3.1" + code-error-fragment@0.0.230: version "0.0.230" resolved "https://registry.npmjs.org/code-error-fragment/-/code-error-fragment-0.0.230.tgz#d736d75c832445342eca1d1fedbf17d9618b14d7" @@ -9379,7 +9456,7 @@ command-exists@^1.2.9: resolved "https://registry.npmjs.org/command-exists/-/command-exists-1.2.9.tgz#c50725af3808c8ab0260fd60b01fbfa25b954f69" integrity sha512-LTQ/SGc+s0Xc0Fu5WaKnR0YiygZkm9eKFvyS+fRsU7/ZWFF8ykFM6Pc9aCVf1+xasOOZpO3BAVgVrKvsqKHV7w== -commander@*, commander@^8.3.0: +commander@*, commander@8.3.0, commander@^8.3.0: version "8.3.0" resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz#4837ea1b2da67b9c616a67afbb0fafee567bca66" integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww== @@ -9437,6 +9514,11 @@ compare-func@^2.0.0: array-ify "^1.0.0" dot-prop "^5.1.0" +compare-versions@4.1.3: + version "4.1.3" + resolved "https://registry.npmjs.org/compare-versions/-/compare-versions-4.1.3.tgz#8f7b8966aef7dc4282b45dfa6be98434fc18a1a4" + integrity sha512-WQfnbDcrYnGr55UwbxKiQKASnTtNnaAWVi8jZyy8NTpVAXWACSne8lMD1iaIo9AiU6mnuLvSVshCzewVuWxHUg== + component-bind@1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz#00c608ab7dcd93897c0009651b1d3a8e1e73bbd1" @@ -9536,6 +9618,20 @@ concat-with-sourcemaps@^1.1.0: dependencies: source-map "^0.6.1" +concurrently@6.5.1: + version "6.5.1" + resolved "https://registry.npmjs.org/concurrently/-/concurrently-6.5.1.tgz#4518c67f7ac680cf5c34d5adf399a2a2047edc8c" + integrity sha512-FlSwNpGjWQfRwPLXvJ/OgysbBxPkWpiVjy1042b0U7on7S7qwwMIILRj7WTN1mTgqa582bG6NFuScOoh6Zgdag== + dependencies: + chalk "^4.1.0" + date-fns "^2.16.1" + lodash "^4.17.21" + rxjs "^6.6.3" + spawn-command "^0.0.2-1" + supports-color "^8.1.0" + tree-kill "^1.2.2" + yargs "^16.2.0" + concurrently@^7.0.0: version "7.2.0" resolved "https://registry.npmjs.org/concurrently/-/concurrently-7.2.0.tgz#4d9b4d1e527b8a8cb101bc2aee317e09496fad43" @@ -9576,6 +9672,11 @@ connect-history-api-fallback@^1.6.0: resolved "https://registry.npmjs.org/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc" integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg== +consola@^2.15.0: + version "2.15.3" + resolved "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550" + integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw== + console-browserify@^1.1.0: version "1.2.0" resolved "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" @@ -9586,6 +9687,13 @@ console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control- resolved "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= +console.table@0.10.0: + version "0.10.0" + resolved "https://registry.npmjs.org/console.table/-/console.table-0.10.0.tgz#0917025588875befd70cf2eff4bef2c6e2d75d04" + integrity sha1-CRcCVYiHW+/XDPLv9L7yxuLXXQQ= + dependencies: + easy-table "1.1.0" + constant-case@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz#3b84a9aeaf4cf31ec45e6bf5de91bdfb0589faf1" @@ -11148,6 +11256,13 @@ eastasianwidth@^0.2.0: resolved "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +easy-table@1.1.0: + version "1.1.0" + resolved "https://registry.npmjs.org/easy-table/-/easy-table-1.1.0.tgz#86f9ab4c102f0371b7297b92a651d5824bc8cb73" + integrity sha1-hvmrTBAvA3G3KXuSplHVgkvIy3M= + optionalDependencies: + wcwidth ">=1.0.1" + ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" @@ -12296,7 +12411,7 @@ fast-equals@^2.0.0: resolved "https://registry.npmjs.org/fast-equals/-/fast-equals-2.0.4.tgz#3add9410585e2d7364c2deeb6a707beadb24b927" integrity sha512-caj/ZmjHljPrZtbzJ3kfH5ia/k4mTJe/qSiXAGzxZWRZgsgDV0cvNaQULqUX8t0/JVlzzEdYOwCN5DmzTxoD4w== -fast-glob@^3.2.9: +fast-glob@^3.2.11, fast-glob@^3.2.9: version "3.2.11" resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== @@ -12332,7 +12447,7 @@ fast-redact@^2.0.0: resolved "https://registry.npmjs.org/fast-redact/-/fast-redact-2.1.0.tgz#dfe3c1ca69367fb226f110aa4ec10ec85462ffdf" integrity sha512-0LkHpTLyadJavq9sRzzyqIoMZemWli77K2/MGOkafrR64B9ItrvZ9aT+jluvNDsv0YEHjSNhlMBtbokuoqii4A== -fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.0.7, fast-safe-stringify@^2.1.1: +fast-safe-stringify@2.1.1, fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.0.7, fast-safe-stringify@^2.1.1: version "2.1.1" resolved "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== @@ -12631,6 +12746,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.14.0: resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz#016996fb9a11a100566398b1c6839337d7bfa8fc" integrity sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA== +follow-redirects@^1.14.8: + version "1.15.0" + resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.0.tgz#06441868281c86d0dda4ad8bdaead2d02dca89d4" + integrity sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ== + for-in@^1.0.2: version "1.0.2" resolved "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -12794,6 +12914,15 @@ fs-constants@^1.0.0: resolved "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-extra@10.0.1: + version "10.0.1" + resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz#27de43b4320e833f6867cc044bfce29fdf0ef3b8" + integrity sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@10.1.0, fs-extra@^10.0.0, fs-extra@^10.0.1: version "10.1.0" resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf" @@ -14214,6 +14343,26 @@ inline-style-prefixer@^6.0.0: dependencies: css-in-js-utils "^2.0.0" +inquirer@8.2.2: + version "8.2.2" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.2.2.tgz#1310517a87a0814d25336c78a20b44c3d9b7629d" + integrity sha512-pG7I/si6K/0X7p1qU+rfWnpTE1UIkTONN1wxtzh0d+dHXtT/JG6qBgLxoyHVsQa8cFABxAPh0pD6uUUHiAoaow== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.4.1" + run-async "^2.4.0" + rxjs "^7.5.5" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + inquirer@^7.3.3: version "7.3.3" resolved "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" @@ -15062,6 +15211,11 @@ istanbul-reports@^3.1.3: html-escaper "^2.0.0" istanbul-lib-report "^3.0.0" +iterare@1.2.1: + version "1.2.1" + resolved "https://registry.npmjs.org/iterare/-/iterare-1.2.1.tgz#139c400ff7363690e33abffa33cbba8920f00042" + integrity sha512-RKYVTCjAnRthyJes037NX/IiqeidgN1xc3j1RjFfECFp28A1GVwK9nA+i0rJPaHqSZwygLzRnFlzUuHFoWWy+Q== + jake@^10.8.5: version "10.8.5" resolved "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz#f2183d2c59382cb274226034543b9c03b8164c46" @@ -16771,7 +16925,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.10, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.7.0, lodash@~4.17.0, lodash@~4.17.15, lodash@~4.17.4: +lodash@4.17.21, lodash@^4.17.10, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.7.0, lodash@~4.17.0, lodash@~4.17.15, lodash@~4.17.4: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -18676,16 +18830,16 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" +object-hash@3.0.0, object-hash@^3.0.0: + version "3.0.0" + resolved "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" + integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== + object-hash@^2.0.1: version "2.2.0" resolved "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== -object-hash@^3.0.0: - version "3.0.0" - resolved "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" - integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== - object-inspect@^1.11.0, object-inspect@^1.12.0, object-inspect@^1.9.0: version "1.12.0" resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" @@ -19518,6 +19672,11 @@ path-browserify@0.0.1: resolved "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-case@^3.0.4: version "3.0.4" resolved "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz#9168645334eb942658375c56f80b4c0cb5f82c6f" @@ -19590,6 +19749,11 @@ path-to-regexp@2.2.1: resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45" integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== +path-to-regexp@3.2.0: + version "3.2.0" + resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz#fa7877ecbc495c601907562222453c43cc204a5f" + integrity sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA== + path-to-regexp@^1.7.0: version "1.8.0" resolved "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" @@ -21406,7 +21570,7 @@ redux@^4.0.0, redux@^4.0.4, redux@^4.1.2: dependencies: "@babel/runtime" "^7.9.2" -reflect-metadata@^0.1.13: +reflect-metadata@0.1.13, reflect-metadata@^0.1.13: version "0.1.13" resolved "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz#67ae3ca57c972a2aa1642b10fe363fe32d49dc08" integrity sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg== @@ -22000,6 +22164,13 @@ run-script-webpack-plugin@^0.0.11: resolved "https://registry.npmjs.org/run-script-webpack-plugin/-/run-script-webpack-plugin-0.0.11.tgz#04c510bed06b907fa2285e75feece71a25691171" integrity sha512-QmuBhiqBPmhQLpO5vMBHVTAGyoPBnrCM5gQ3IzgieiImBXiBbXcIv4kysCT1gilFNFxQk22oKQfiIhWbT/zXCw== +rxjs@7.5.5, rxjs@^7.1.0, rxjs@^7.2.0, rxjs@^7.5.1, rxjs@^7.5.5: + version "7.5.5" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f" + integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw== + dependencies: + tslib "^2.1.0" + rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.6.0, rxjs@^6.6.3: version "6.6.7" resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" @@ -22007,13 +22178,6 @@ rxjs@^6.3.3, rxjs@^6.4.0, rxjs@^6.6.0, rxjs@^6.6.3: dependencies: tslib "^1.9.0" -rxjs@^7.1.0, rxjs@^7.2.0, rxjs@^7.5.1, rxjs@^7.5.5: - version "7.5.5" - resolved "https://registry.npmjs.org/rxjs/-/rxjs-7.5.5.tgz#2ebad89af0f560f460ad5cc4213219e1f7dd4e9f" - integrity sha512-sy+H0pQofO95VDmFLzyaw9xNJU4KTRSwQIGM6+iG3SypAtCiLDzpeG8sJrNCWn2Up9km+KhkvTdbkrdy+yzZdw== - dependencies: - tslib "^2.1.0" - sade@^1.7.3: version "1.8.1" resolved "https://registry.npmjs.org/sade/-/sade-1.8.1.tgz#0a78e81d658d394887be57d2a409bf703a3b2701" @@ -24057,6 +24221,14 @@ ts-log@^2.2.3: resolved "https://registry.npmjs.org/ts-log/-/ts-log-2.2.3.tgz#4da5640fe25a9fb52642cd32391c886721318efb" integrity sha512-XvB+OdKSJ708Dmf9ore4Uf/q62AYDTzFcAdxc8KNML1mmAWywRFVt/dn1KYJH8Agt5UJNujfM3znU5PxgAzA2w== +ts-morph@^15.0.0: + version "15.0.0" + resolved "https://registry.npmjs.org/ts-morph/-/ts-morph-15.0.0.tgz#927a85d22909b95fa81e399c94fea655d98be514" + integrity sha512-OZkg0TI1h6FVe8DZXyBo6p7NfCN9EZZkkA736f243KzQ3cypYWtaLc9eyNn/JH/fWYfQ4d6wIA4oM0vElRTGcQ== + dependencies: + "@ts-morph/common" "~0.15.0" + code-block-writer "^11.0.0" + ts-node@^10.0.0, ts-node@^10.2.1, ts-node@^10.4.0: version "10.7.0" resolved "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5" @@ -24098,16 +24270,21 @@ tsconfig-paths@^3.14.1: minimist "^1.2.6" strip-bom "^3.0.0" +tslib@2.0.3: + version "2.0.3" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz#8e0741ac45fc0c226e58a17bfc3e64b9bc6ca61c" + integrity sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ== + +tslib@2.3.1, tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@~2.3.0: + version "2.3.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" + integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== + tslib@^1.13.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2, tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@~2.3.0: - version "2.3.1" - resolved "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" - integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw== - tslib@^2.1.0, tslib@~2.4.0: version "2.4.0" resolved "https://registry.npmjs.org/tslib/-/tslib-2.4.0.tgz#7cecaa7f073ce680a05847aa77be941098f36dc3" @@ -24735,16 +24912,16 @@ uuid@3.3.2: resolved "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== +uuid@8.3.2, uuid@^8.0.0, uuid@^8.2.0, uuid@^8.3.0, uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + uuid@^3.3.2, uuid@^3.4.0: version "3.4.0" resolved "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -uuid@^8.0.0, uuid@^8.2.0, uuid@^8.3.0, uuid@^8.3.2: - version "8.3.2" - resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" - integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== - uvu@^0.5.0: version "0.5.3" resolved "https://registry.npmjs.org/uvu/-/uvu-0.5.3.tgz#3d83c5bc1230f153451877bfc7f4aea2392219ae" @@ -24976,7 +25153,7 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" -wcwidth@^1.0.0, wcwidth@^1.0.1: +wcwidth@>=1.0.1, wcwidth@^1.0.0, wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= From 1c01c0fd1468dea48851d4b8425018802e78432a Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Wed, 27 Apr 2022 00:57:24 +0200 Subject: [PATCH 128/149] feat: integrate `bitbucket-cloud-common` at `catalog-backend-module-bitbucket` Signed-off-by: Patrick Jungermann --- .changeset/warm-bats-jump.md | 5 + .../package.json | 1 + .../src/BitbucketDiscoveryProcessor.test.ts | 154 ++++++++++++++---- .../src/BitbucketDiscoveryProcessor.ts | 66 ++++---- .../src/lib/BitbucketCloudClient.ts | 143 ---------------- .../src/lib/index.ts | 4 +- .../src/lib/types.ts | 30 ---- 7 files changed, 164 insertions(+), 239 deletions(-) create mode 100644 .changeset/warm-bats-jump.md delete mode 100644 plugins/catalog-backend-module-bitbucket/src/lib/BitbucketCloudClient.ts diff --git a/.changeset/warm-bats-jump.md b/.changeset/warm-bats-jump.md new file mode 100644 index 0000000000..1547734434 --- /dev/null +++ b/.changeset/warm-bats-jump.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket': minor +--- + +Integrate `@backstage/plugin-bitbucket-cloud-common` as replacement for the `BitbucketCloudClient`. diff --git a/plugins/catalog-backend-module-bitbucket/package.json b/plugins/catalog-backend-module-bitbucket/package.json index 51f5a6e603..4e40fa9400 100644 --- a/plugins/catalog-backend-module-bitbucket/package.json +++ b/plugins/catalog-backend-module-bitbucket/package.json @@ -38,6 +38,7 @@ "@backstage/config": "^1.0.1", "@backstage/errors": "^1.0.0", "@backstage/integration": "^1.2.1-next.0", + "@backstage/plugin-bitbucket-cloud-common": "^0.0.0", "@backstage/plugin-catalog-backend": "^1.2.0-next.0", "@backstage/types": "^1.0.0", "lodash": "^4.17.21", diff --git a/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.test.ts b/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.test.ts index 5f441b787c..ad893e7c87 100644 --- a/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.test.ts +++ b/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.test.ts @@ -16,6 +16,7 @@ import { getVoidLogger } from '@backstage/backend-common'; import { ConfigReader } from '@backstage/config'; +import { Models } from '@backstage/plugin-bitbucket-cloud-common'; import { LocationSpec, processingResult, @@ -23,7 +24,7 @@ import { import { RequestHandler, rest } from 'msw'; import { setupServer } from 'msw/node'; import { BitbucketDiscoveryProcessor } from './BitbucketDiscoveryProcessor'; -import { BitbucketRepository20, PagedResponse, PagedResponse20 } from './lib'; +import { PagedResponse } from './lib'; const server = setupServer(); @@ -84,14 +85,14 @@ function setupStubs( function setupBitbucketCloudStubs( workspace: string, - repositories: Pick[], + repositories: Pick[], ) { const stubCallerFn = jest.fn(); - function pagedResponse(values: any): PagedResponse20 { + function pagedResponse(values: any): Models.PaginatedRepositories { return { values: values, page: 1, - } as PagedResponse20; + } as Models.PaginatedRepositories; } server.use( @@ -121,15 +122,15 @@ function setupBitbucketCloudStubs( function setupBitbucketCloudSearchStubs( workspace: string, - repositories: Pick[], + repositories: Pick[], catalogPath: string, ) { const stubCallerFn = jest.fn(); - function pagedResponse(values: any): PagedResponse20 { + function pagedResponse(values: any): Models.PaginatedRepositories { return { values: values, page: 1, - } as PagedResponse20; + } as Models.PaginatedRepositories; } server.use( @@ -555,8 +556,14 @@ describe('BitbucketDiscoveryProcessor', () => { it('output all repositories by default', async () => { setupBitbucketCloudStubs('myworkspace', [ - { project: { key: 'prj-one' }, slug: 'repository-one' }, - { project: { key: 'prj-two' }, slug: 'repository-two' }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, + { + project: { type: 'project', key: 'prj-two' }, + slug: 'repository-two', + }, ]); const location: LocationSpec = { type: 'bitbucket-discovery', @@ -590,8 +597,14 @@ describe('BitbucketDiscoveryProcessor', () => { it('uses provided catalog path', async () => { setupBitbucketCloudStubs('myworkspace', [ - { project: { key: 'prj-one' }, slug: 'repository-one' }, - { project: { key: 'prj-two' }, slug: 'repository-two' }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, + { + project: { type: 'project', key: 'prj-two' }, + slug: 'repository-two', + }, ]); const location: LocationSpec = { type: 'bitbucket-discovery', @@ -626,8 +639,14 @@ describe('BitbucketDiscoveryProcessor', () => { it('output all repositories', async () => { setupBitbucketCloudStubs('myworkspace', [ - { project: { key: 'prj-one' }, slug: 'repository-one' }, - { project: { key: 'prj-two' }, slug: 'repository-two' }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, + { + project: { type: 'project', key: 'prj-two' }, + slug: 'repository-two', + }, ]); const location: LocationSpec = { type: 'bitbucket-discovery', @@ -662,8 +681,14 @@ describe('BitbucketDiscoveryProcessor', () => { it('output repositories with wildcards', async () => { setupBitbucketCloudStubs('myworkspace', [ - { project: { key: 'prj-one' }, slug: 'repository-one' }, - { project: { key: 'prj-two' }, slug: 'repository-two' }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, + { + project: { type: 'project', key: 'prj-two' }, + slug: 'repository-two', + }, ]); const location: LocationSpec = { type: 'bitbucket-discovery', @@ -688,9 +713,18 @@ describe('BitbucketDiscoveryProcessor', () => { it('filter unrelated repositories', async () => { setupBitbucketCloudStubs('myworkspace', [ - { project: { key: 'prj-one' }, slug: 'repository-one' }, - { project: { key: 'prj-one' }, slug: 'repository-two' }, - { project: { key: 'prj-one' }, slug: 'repository-three' }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-two', + }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-three', + }, ]); const location: LocationSpec = { type: 'bitbucket-discovery', @@ -715,7 +749,10 @@ describe('BitbucketDiscoveryProcessor', () => { it('submits query', async () => { const mockCall = setupBitbucketCloudStubs('myworkspace', [ - { project: { key: 'prj-one' }, slug: 'repository-one' }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, ]); const location: LocationSpec = { type: 'bitbucket-discovery', @@ -750,7 +787,10 @@ describe('BitbucketDiscoveryProcessor', () => { ${'https://bitbucket.org/workspaces/myworkspace/projects/prj-one/repos/repository-*/'} `("target '$target' adds default path to catalog", async ({ target }) => { setupBitbucketCloudStubs('myworkspace', [ - { project: { key: 'prj-one' }, slug: 'repository-one' }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, ]); const location: LocationSpec = { @@ -778,7 +818,10 @@ describe('BitbucketDiscoveryProcessor', () => { ${'https://bitbucket.org/test'} `("target '$target' is rejected", async ({ target }) => { setupBitbucketCloudStubs('myworkspace', [ - { project: { key: 'prj-one' }, slug: 'repository-one' }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, ]); const location: LocationSpec = { @@ -813,8 +856,14 @@ describe('BitbucketDiscoveryProcessor', () => { setupBitbucketCloudSearchStubs( 'myworkspace', [ - { project: { key: 'prj-one' }, slug: 'repository-one' }, - { project: { key: 'prj-two' }, slug: 'repository-two' }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, + { + project: { type: 'project', key: 'prj-two' }, + slug: 'repository-two', + }, ], 'catalog-info.yaml', ); @@ -852,8 +901,14 @@ describe('BitbucketDiscoveryProcessor', () => { setupBitbucketCloudSearchStubs( 'myworkspace', [ - { project: { key: 'prj-one' }, slug: 'repository-one' }, - { project: { key: 'prj-two' }, slug: 'repository-two' }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, + { + project: { type: 'project', key: 'prj-two' }, + slug: 'repository-two', + }, ], 'my/nested/path/catalog.yaml', ); @@ -892,8 +947,14 @@ describe('BitbucketDiscoveryProcessor', () => { setupBitbucketCloudSearchStubs( 'myworkspace', [ - { project: { key: 'prj-one' }, slug: 'repository-one' }, - { project: { key: 'prj-two' }, slug: 'repository-two' }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, + { + project: { type: 'project', key: 'prj-two' }, + slug: 'repository-two', + }, ], 'catalog.yaml', ); @@ -932,8 +993,14 @@ describe('BitbucketDiscoveryProcessor', () => { setupBitbucketCloudSearchStubs( 'myworkspace', [ - { project: { key: 'prj-one' }, slug: 'repository-one' }, - { project: { key: 'prj-two' }, slug: 'repository-two' }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, + { + project: { type: 'project', key: 'prj-two' }, + slug: 'repository-two', + }, ], 'catalog.yaml', ); @@ -962,9 +1029,18 @@ describe('BitbucketDiscoveryProcessor', () => { setupBitbucketCloudSearchStubs( 'myworkspace', [ - { project: { key: 'prj-one' }, slug: 'repository-one' }, - { project: { key: 'prj-one' }, slug: 'repository-two' }, - { project: { key: 'prj-one' }, slug: 'repository-three' }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-two', + }, + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-three', + }, ], 'catalog.yaml', ); @@ -997,7 +1073,12 @@ describe('BitbucketDiscoveryProcessor', () => { `("target '$target' adds default path to catalog", async ({ target }) => { setupBitbucketCloudSearchStubs( 'myworkspace', - [{ project: { key: 'prj-one' }, slug: 'repository-one' }], + [ + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, + ], 'catalog-info.yaml', ); @@ -1027,7 +1108,12 @@ describe('BitbucketDiscoveryProcessor', () => { `("target '$target' is rejected", async ({ target }) => { setupBitbucketCloudSearchStubs( 'myworkspace', - [{ project: { key: 'prj-one' }, slug: 'repository-one' }], + [ + { + project: { type: 'project', key: 'prj-one' }, + slug: 'repository-one', + }, + ], 'catalog-info.yaml', ); diff --git a/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts b/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts index a025eda962..14bf90ee62 100644 --- a/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts +++ b/plugins/catalog-backend-module-bitbucket/src/BitbucketDiscoveryProcessor.ts @@ -20,6 +20,10 @@ import { ScmIntegrationRegistry, ScmIntegrations, } from '@backstage/integration'; +import { + BitbucketCloudClient, + Models, +} from '@backstage/plugin-bitbucket-cloud-common'; import { CatalogProcessor, CatalogProcessorEmit, @@ -27,14 +31,11 @@ import { } from '@backstage/plugin-catalog-backend'; import { Logger } from 'winston'; import { - BitbucketCloudClient, BitbucketRepository, - BitbucketRepository20, BitbucketRepositoryParser, BitbucketServerClient, defaultRepositoryParser, paginated, - paginated20, } from './lib'; const DEFAULT_BRANCH = 'master'; @@ -119,9 +120,7 @@ export class BitbucketDiscoveryProcessor implements CatalogProcessor { options: ProcessOptions, ): Promise { const { location, integration, emit } = options; - const client = new BitbucketCloudClient({ - config: integration.config, - }); + const client = BitbucketCloudClient.fromConfig(integration.config); const { searchEnabled } = parseBitbucketCloudUrl(location.target); @@ -226,28 +225,40 @@ export async function searchBitbucketCloudLocations( catalogPath.lastIndexOf('/') + 1, ); - const searchResults = paginated20(options => - client.searchCode( - workspacePath, - `"${catalogFilename}" path:${catalogPath}`, - options, - ), - ); + // load all fields relevant for creating refs later, but not more + const fields = [ + // exclude code/content match details + '-values.content_matches', + // include/add relevant repository details + '+values.file.commit.repository.mainbranch.name', + '+values.file.commit.repository.project.key', + '+values.file.commit.repository.slug', + // remove irrelevant links + '-values.*.links', + '-values.*.*.links', + '-values.*.*.*.links', + // ...except the one we need + '+values.file.commit.repository.links.html.href', + ].join(','); + const query = `"${catalogFilename}" path:${catalogPath}`; + const searchResults = client + .searchCode(workspacePath, query, { fields }) + .iterateResults(); for await (const searchResult of searchResults) { // not a file match, but a code match - if (searchResult.path_matches.length === 0) { + if (searchResult.path_matches!.length === 0) { continue; } - const repository = searchResult.file.commit.repository; + const repository = searchResult.file!.commit!.repository!; if (!matchesPostFilters(repository, projectSearchPath, repoSearchPath)) { continue; } - const repoUrl = repository.links.html.href; + const repoUrl = repository.links!.html!.href; const branch = repository.mainbranch?.name ?? DEFAULT_BRANCH; - const filePath = searchResult.file.path; + const filePath = searchResult.file!.path; const location = `${repoUrl}/src/${branch}/${filePath}`; result.matches.push(location); @@ -268,7 +279,7 @@ export async function readBitbucketCloudLocations( return readBitbucketCloud(client, target).then(result => { const matches = result.matches.map(repository => { const branch = repository.mainbranch?.name ?? DEFAULT_BRANCH; - return `${repository.links.html.href}/src/${branch}${catalogPath}`; + return `${repository.links!.html!.href}/src/${branch}${catalogPath}`; }); return { @@ -281,7 +292,7 @@ export async function readBitbucketCloudLocations( export async function readBitbucketCloud( client: BitbucketCloudClient, target: string, -): Promise> { +): Promise> { const { workspacePath, queryParam: q, @@ -289,13 +300,10 @@ export async function readBitbucketCloud( repoSearchPath, } = parseBitbucketCloudUrl(target); - const repositories = paginated20( - options => client.listRepositoriesByWorkspace(workspacePath, options), - { - q, - }, - ); - const result: Result = { + const repositories = client + .listRepositoriesByWorkspace(workspacePath, { q }) + .iterateResults(); + const result: Result = { scanned: 0, matches: [], }; @@ -310,13 +318,13 @@ export async function readBitbucketCloud( } function matchesPostFilters( - repository: BitbucketRepository20, + repository: Models.Repository, projectSearchPath: RegExp | undefined, repoSearchPath: RegExp | undefined, ): boolean { return ( - (!projectSearchPath || projectSearchPath.test(repository.project.key)) && - (!repoSearchPath || repoSearchPath.test(repository.slug)) + (!projectSearchPath || projectSearchPath.test(repository.project!.key!)) && + (!repoSearchPath || repoSearchPath.test(repository.slug!)) ); } diff --git a/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketCloudClient.ts b/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketCloudClient.ts deleted file mode 100644 index 80826ce154..0000000000 --- a/plugins/catalog-backend-module-bitbucket/src/lib/BitbucketCloudClient.ts +++ /dev/null @@ -1,143 +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 { - BitbucketIntegrationConfig, - getBitbucketRequestOptions, -} from '@backstage/integration'; -import fetch from 'node-fetch'; -import { BitbucketRepository20 } from './types'; - -export class BitbucketCloudClient { - private readonly config: BitbucketIntegrationConfig; - - constructor(options: { config: BitbucketIntegrationConfig }) { - this.config = options.config; - } - - async searchCode( - workspace: string, - query: string, - options?: ListOptions20, - ): Promise> { - // load all fields relevant for creating refs later, but not more - const fields = [ - // exclude code/content match details - '-values.content_matches', - // include/add relevant repository details - '+values.file.commit.repository.mainbranch.name', - '+values.file.commit.repository.project.key', - '+values.file.commit.repository.slug', - // remove irrelevant links - '-values.*.links', - '-values.*.*.links', - '-values.*.*.*.links', - // ...except the one we need - '+values.file.commit.repository.links.html.href', - ].join(','); - - return this.pagedRequest( - `${this.config.apiBaseUrl}/workspaces/${encodeURIComponent( - workspace, - )}/search/code`, - { - ...options, - fields: fields, - search_query: query, - }, - ); - } - - async listRepositoriesByWorkspace( - workspace: string, - options?: ListOptions20, - ): Promise> { - return this.pagedRequest( - `${this.config.apiBaseUrl}/repositories/${encodeURIComponent(workspace)}`, - options, - ); - } - - private async pagedRequest( - endpoint: string, - options?: ListOptions20, - ): Promise> { - const request = new URL(endpoint); - for (const key in options) { - if (options[key]) { - request.searchParams.append(key, options[key]!.toString()); - } - } - - const response = await fetch( - request.toString(), - getBitbucketRequestOptions(this.config), - ); - if (!response.ok) { - throw new Error( - `Unexpected response when fetching ${request.toString()}. Expected 200 but got ${ - response.status - } - ${response.statusText}`, - ); - } - return response.json() as Promise>; - } -} - -export type CodeSearchResultItem = { - type: string; - content_match_count: number; - path_matches: Array<{ - text: string; - match?: boolean; - }>; - file: { - path: string; - type: string; - commit: { - repository: BitbucketRepository20; - }; - }; -}; - -export type ListOptions20 = { - [key: string]: string | number | undefined; - page?: number | undefined; - pagelen?: number | undefined; -}; - -export type PagedResponse20 = { - page: number; - pagelen: number; - size: number; - values: T[]; - next: string; -}; - -export async function* paginated20( - request: (options: ListOptions20) => Promise>, - options?: ListOptions20, -) { - const opts = { page: 1, pagelen: 100, ...options }; - let res; - do { - res = await request(opts); - opts.page = opts.page + 1; - for (const item of res.values) { - yield item; - } - } while (res.next); -} diff --git a/plugins/catalog-backend-module-bitbucket/src/lib/index.ts b/plugins/catalog-backend-module-bitbucket/src/lib/index.ts index 8faf3db08f..c93bd7afd9 100644 --- a/plugins/catalog-backend-module-bitbucket/src/lib/index.ts +++ b/plugins/catalog-backend-module-bitbucket/src/lib/index.ts @@ -16,8 +16,6 @@ export { defaultRepositoryParser } from './BitbucketRepositoryParser'; export type { BitbucketRepositoryParser } from './BitbucketRepositoryParser'; -export { BitbucketCloudClient, paginated20 } from './BitbucketCloudClient'; export { BitbucketServerClient, paginated } from './BitbucketServerClient'; -export type { PagedResponse20 } from './BitbucketCloudClient'; export type { PagedResponse } from './BitbucketServerClient'; -export type { BitbucketRepository, BitbucketRepository20 } from './types'; +export type { BitbucketRepository } from './types'; diff --git a/plugins/catalog-backend-module-bitbucket/src/lib/types.ts b/plugins/catalog-backend-module-bitbucket/src/lib/types.ts index db8cf69688..b1bb416363 100644 --- a/plugins/catalog-backend-module-bitbucket/src/lib/types.ts +++ b/plugins/catalog-backend-module-bitbucket/src/lib/types.ts @@ -29,33 +29,3 @@ export type BitbucketRepository = BitbucketRepositoryBase & { }[] >; }; - -export type BitbucketRepository20 = BitbucketRepositoryBase & { - links: Record< - | 'self' - | 'source' - | 'html' - | 'avatar' - | 'pullrequests' - | 'commits' - | 'forks' - | 'watchers' - | 'downloads' - | 'hooks', - { - href: string; - name?: string; - } - > & - Record< - 'clone', - { - href: string; - name?: string; - }[] - >; - mainbranch?: { - type: string; - name: string; - }; -}; From 5e2fc28e4d027c4a4dafa5fc1efc77452bd1807d Mon Sep 17 00:00:00 2001 From: Stefan Buck Date: Mon, 30 May 2022 09:45:59 +0200 Subject: [PATCH 129/149] Add Brandwatch to adopters list Signed-off-by: Stefan Buck --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 3762fddbea..d851686e0c 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -131,3 +131,4 @@ _If you're using Backstage in your organization, please try to add your company |[Siemens](https://www.siemens.com/global/en.html)|[Nizar Chaouch](mailto:nizar.chaouch@siemens.com)|We are using Backstage as our Developer portal |[The Warehouse Group](https://www.thewarehouse.co.nz)|[Matt Law](mailto:matt.law@thewarehouse.co.nz)|Backstage enables us to bootstrap our middleware environment of new services for our Dev teams in a matter of seconds. CI, CD, testing, logging, deployments are all taken care of to get them up and running in less than 60 seconds. | [Tink](https://tink.com/) | [Sebastian Olsson](https://github.com/Sebelino), [Błażej Szum](https://github.com/blazejszumtink), [Anders Eurenius Runvald](https://github.com/anders-er-at-tink) | Internal developer portal which provides templates for creating new Java or Go microservices seamlessly. Also includes a tech radar and a visualization of our CD pipeline. | +| [Brandwatch](https://brandwatch.com)| [Stefan Buck](https://github.com/stefanbuck) | Our primary focus is on the service catalog. Backstage is replacing our homemade service catalog. The switch was quite simple due to the catalog processor API. From 55b79c4a71af046997a732c72c6293205a4f3f81 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 30 May 2022 11:44:58 +0200 Subject: [PATCH 130/149] backend: drop catalog user requirement from github sign-in Signed-off-by: Patrik Oldsberg --- packages/backend/src/plugins/auth.ts | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/plugins/auth.ts b/packages/backend/src/plugins/auth.ts index cd0c42afe5..8beda6f3fb 100644 --- a/packages/backend/src/plugins/auth.ts +++ b/packages/backend/src/plugins/auth.ts @@ -44,7 +44,27 @@ export default async function createPlugin( // It is here for demo purposes only. github: providers.github.create({ signIn: { - resolver: providers.github.resolvers.usernameMatchingUserEntityName(), + async resolver({ result: { fullProfile } }, ctx) { + const userId = fullProfile.username; + if (!userId) { + throw new Error( + `GitHub user profile does not contain a username`, + ); + } + + const userEntityRef = stringifyEntityRef({ + kind: 'User', + name: userId, + namespace: DEFAULT_NAMESPACE, + }); + + return ctx.issueToken({ + claims: { + sub: userEntityRef, + ent: [userEntityRef], + }, + }); + }, }, }), gitlab: providers.gitlab.create({ From b5bd9f2bcee0a7bb0c20b3588cfef540e1bd19ff Mon Sep 17 00:00:00 2001 From: Renjie Xu Date: Tue, 3 May 2022 14:51:23 -0700 Subject: [PATCH 131/149] Update getting-started docs to provide sign in resolver Signed-off-by: Renjie Xu --- docs/getting-started/configuration.md | 50 +++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index fc78217fbc..cf845c8d4c 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -187,6 +187,56 @@ components: { }, ``` +Since [v1.1.0](https://github.com/backstage/backstage/releases/tag/v1.1.0-next.3), you must provide an explicit sign-in resolver. + +Open `packages/backend/src/plugins/auth.ts` and replace with following code snippet: + +```typescript +import { + createRouter, + providers, + defaultAuthProviderFactories, +} from '@backstage/plugin-auth-backend'; +import { Router } from 'express'; +import { PluginEnvironment } from '../types'; +import { stringifyEntityRef } from '@backstage/catalog-model'; + +export default async function createPlugin( + env: PluginEnvironment, +): Promise { + return await createRouter({ + ...env, + providerFactories: { + ...defaultAuthProviderFactories, + github: providers.github.create({ + signIn: { + resolver: async ({ profile }, ctx) => { + if (!profile.email) { + throw new Error( + 'Login failed, user profile does not contain an email', + ); + } + const [localPart] = profile.email.split('@'); + + const userEntityRef = stringifyEntityRef({ + kind: 'User', + name: localPart, + namespace: 'DEFAULT_NAMESPACE', + }); + return ctx.issueToken({ + claims: { + sub: userEntityRef, + ent: [userEntityRef], + }, + }); + }, + }, + }), + }, + }); +} +``` + That should be it. You can stop your Backstage App. When you start it again and go to your Backstage portal in your browser, you should have your login prompt! From 276b95503f2aced13a8681d3705a2f9e44f873bd Mon Sep 17 00:00:00 2001 From: Hasan Oezdemir <21654050+nodify-at@users.noreply.github.com> Date: Mon, 30 May 2022 11:49:43 +0200 Subject: [PATCH 132/149] bugfix: fix typo, use bug fix instead of bugfix in wording Signed-off-by: Hasan Oezdemir <21654050+nodify-at@users.noreply.github.com> --- .changeset/pretty-wolves-whisper.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/pretty-wolves-whisper.md b/.changeset/pretty-wolves-whisper.md index 5ab603de23..498eac7173 100644 --- a/.changeset/pretty-wolves-whisper.md +++ b/.changeset/pretty-wolves-whisper.md @@ -2,4 +2,4 @@ '@backstage/plugin-jenkins-backend': patch --- -bugfix: provide backstage token for rebuild api call +bug fix: provide backstage token for rebuild api call From fe3ac2f6bbfcc3da8781ce9562efd44cabde7a78 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 30 May 2022 11:50:34 +0200 Subject: [PATCH 133/149] chore: reworking the initial work to link out to the sign in reolver Signed-off-by: blam --- docs/getting-started/configuration.md | 50 +-------------------------- 1 file changed, 1 insertion(+), 49 deletions(-) diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index cf845c8d4c..0660497276 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -187,55 +187,7 @@ components: { }, ``` -Since [v1.1.0](https://github.com/backstage/backstage/releases/tag/v1.1.0-next.3), you must provide an explicit sign-in resolver. - -Open `packages/backend/src/plugins/auth.ts` and replace with following code snippet: - -```typescript -import { - createRouter, - providers, - defaultAuthProviderFactories, -} from '@backstage/plugin-auth-backend'; -import { Router } from 'express'; -import { PluginEnvironment } from '../types'; -import { stringifyEntityRef } from '@backstage/catalog-model'; - -export default async function createPlugin( - env: PluginEnvironment, -): Promise { - return await createRouter({ - ...env, - providerFactories: { - ...defaultAuthProviderFactories, - github: providers.github.create({ - signIn: { - resolver: async ({ profile }, ctx) => { - if (!profile.email) { - throw new Error( - 'Login failed, user profile does not contain an email', - ); - } - const [localPart] = profile.email.split('@'); - - const userEntityRef = stringifyEntityRef({ - kind: 'User', - name: localPart, - namespace: 'DEFAULT_NAMESPACE', - }); - return ctx.issueToken({ - claims: { - sub: userEntityRef, - ent: [userEntityRef], - }, - }); - }, - }, - }), - }, - }); -} -``` +> Since [v1.1.0](https://github.com/backstage/backstage/releases/tag/v1.1.0-next.3), you must provide an [explicit sign-in resolver](https://backstage.io/docs/auth/identity-resolver). That should be it. You can stop your Backstage App. When you start it again and go to your Backstage portal in your browser, you should have your login prompt! From 9e2fb90151d1ef2dee06f4292facfdabec442e33 Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 30 May 2022 11:51:16 +0200 Subject: [PATCH 134/149] chore: fixing up the link Signed-off-by: blam --- docs/getting-started/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index 0660497276..8de584f1e9 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -187,7 +187,7 @@ components: { }, ``` -> Since [v1.1.0](https://github.com/backstage/backstage/releases/tag/v1.1.0-next.3), you must provide an [explicit sign-in resolver](https://backstage.io/docs/auth/identity-resolver). +> Since [v1.1.0](https://github.com/backstage/backstage/releases/tag/v1.1.0-next.3), you must provide an [explicit sign-in resolver](../auth/identity-resolver). That should be it. You can stop your Backstage App. When you start it again and go to your Backstage portal in your browser, you should have your login prompt! From b10bf25802240a2d6a823d173573620293a7ba0e Mon Sep 17 00:00:00 2001 From: blam Date: Mon, 30 May 2022 12:01:00 +0200 Subject: [PATCH 135/149] chore: woops Signed-off-by: blam --- docs/getting-started/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/configuration.md b/docs/getting-started/configuration.md index 8de584f1e9..1dc1ffc1b1 100644 --- a/docs/getting-started/configuration.md +++ b/docs/getting-started/configuration.md @@ -187,7 +187,7 @@ components: { }, ``` -> Since [v1.1.0](https://github.com/backstage/backstage/releases/tag/v1.1.0-next.3), you must provide an [explicit sign-in resolver](../auth/identity-resolver). +> Since [v1.1.0](https://github.com/backstage/backstage/releases/tag/v1.1.0-next.3), you must provide an [explicit sign-in resolver](../auth/identity-resolver.md). That should be it. You can stop your Backstage App. When you start it again and go to your Backstage portal in your browser, you should have your login prompt! From dfc4efcbf0b3561d083dc2f3432a64f98e749e5a Mon Sep 17 00:00:00 2001 From: Patrick Jungermann Date: Wed, 6 Apr 2022 01:07:09 +0200 Subject: [PATCH 136/149] feat: add `BitbucketCloudEntityProvider` (new plugin) Add a new entity provider `BitbucketCloudEntityProvider` as a new plugin `@backstage/plugin-catalog-backend-module-bitbucket-cloud`. The new plugin utilizes `@backstage/plugin-bitbucket-cloud-common` and it fully independent of `@backstage/plugin-catalog-backend-module-bitbucket` which provides a catalog processors supporting Bitbucket Cloud and Bitbucket Server. Relates-to: #9923 Relates-to: #10183 Signed-off-by: Patrick Jungermann --- .changeset/fluffy-cherries-own.md | 58 ++++ docs/integrations/bitbucket/locations.md | 20 +- docs/integrations/bitbucketCloud/discovery.md | 95 ++++++ docs/integrations/bitbucketCloud/locations.md | 36 +++ microsite/sidebars.json | 8 + mkdocs.yml | 3 + .../.eslintrc.js | 1 + .../CHANGELOG.md | 1 + .../README.md | 9 + .../api-report.md | 31 ++ .../config.d.ts | 90 ++++++ .../package.json | 55 ++++ .../src/BitbucketCloudEntityProvider.test.ts | 281 ++++++++++++++++++ .../src/BitbucketCloudEntityProvider.ts | 240 +++++++++++++++ ...BitbucketCloudEntityProviderConfig.test.ts | 115 +++++++ .../src/BitbucketCloudEntityProviderConfig.ts | 93 ++++++ .../src/index.ts | 23 ++ .../src/setupTests.ts | 17 ++ 18 files changed, 1157 insertions(+), 19 deletions(-) create mode 100644 .changeset/fluffy-cherries-own.md create mode 100644 docs/integrations/bitbucketCloud/discovery.md create mode 100644 docs/integrations/bitbucketCloud/locations.md create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/.eslintrc.js create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/README.md create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/api-report.md create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/config.d.ts create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/package.json create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProvider.test.ts create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProvider.ts create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.test.ts create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.ts create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/src/index.ts create mode 100644 plugins/catalog-backend-module-bitbucket-cloud/src/setupTests.ts diff --git a/.changeset/fluffy-cherries-own.md b/.changeset/fluffy-cherries-own.md new file mode 100644 index 0000000000..93d7272b14 --- /dev/null +++ b/.changeset/fluffy-cherries-own.md @@ -0,0 +1,58 @@ +--- +'@backstage/plugin-catalog-backend-module-bitbucket-cloud': minor +--- + +Add new plugin `catalog-backend-module-bitbucket-cloud` with `BitbucketCloudEntityProvider`. + +This entity provider is an alternative/replacement to the `BitbucketDiscoveryProcessor` **_(for Bitbucket Cloud only!)_**. +It replaces use cases using `search=true` and should be powerful enough as a complete replacement. + +If any feature for Bitbucket Cloud is missing and preventing you from switching, please raise an issue. + +**Before:** + +```typescript +// packages/backend/src/plugins/catalog.ts + +builder.addProcessor( + BitbucketDiscoveryProcessor.fromConfig(env.config, { logger: env.logger }), +); +``` + +```yaml +# app-config.yaml + +catalog: + locations: + - type: bitbucket-discovery + target: 'https://bitbucket.org/workspaces/workspace-name/projects/apis-*/repos/service-*?search=true&catalogPath=/catalog-info.yaml' +``` + +**After:** + +```typescript +// packages/backend/src/plugins/catalog.ts +builder.addEntityProvider( + BitbucketCloudEntityProvider.fromConfig(env.config, { + logger: env.logger, + schedule: env.scheduler.createScheduledTaskRunner({ + frequency: { minutes: 30 }, + timeout: { minutes: 3 }, + }), + }), +); +``` + +```yaml +# app-config.yaml + +catalog: + providers: + bitbucketCloud: + yourProviderId: # identifies your ingested dataset + catalogPath: /catalog-info.yaml # default value + filters: # optional + projectKey: '^apis-.*$' # optional; RegExp + repoSlug: '^service-.*$' # optional; RegExp + workspace: workspace-name +``` diff --git a/docs/integrations/bitbucket/locations.md b/docs/integrations/bitbucket/locations.md index c0c6afc7a3..878030a29c 100644 --- a/docs/integrations/bitbucket/locations.md +++ b/docs/integrations/bitbucket/locations.md @@ -15,25 +15,7 @@ plugin. ## Bitbucket Cloud -```yaml -integrations: - bitbucketCloud: - - username: ${BITBUCKET_CLOUD_USERNAME} - appPassword: ${BITBUCKET_CLOUD_PASSWORD} -``` - -> Note: A public Bitbucket Cloud provider is added automatically at startup for -> convenience, so you only need to list it if you want to supply credentials. - -Directly under the `bitbucketCloud` key is a list of provider configurations, where -you can list the Bitbucket Cloud providers you want to fetch data from. -In the case of Bitbucket Cloud, you will have up to one entry. - -This one entry will have the following elements: - -- `username`: The Bitbucket Cloud username to use in API requests. If - neither a username nor token are supplied, anonymous access will be used. -- `appPassword`: The app password for the Bitbucket Cloud user. +Please see [the Bitbucket Cloud documentation](../bitbucketCloud/locations.md). ## Bitbucket Server diff --git a/docs/integrations/bitbucketCloud/discovery.md b/docs/integrations/bitbucketCloud/discovery.md new file mode 100644 index 0000000000..6609707868 --- /dev/null +++ b/docs/integrations/bitbucketCloud/discovery.md @@ -0,0 +1,95 @@ +--- +id: discovery +title: Bitbucket Cloud Discovery +sidebar_label: Discovery +# prettier-ignore +description: Automatically discovering catalog entities from repositories in Bitbucket Cloud +--- + +The Bitbucket Cloud integration has a special entity provider for discovering +catalog files located in [Bitbucket Cloud](https://bitbucket.org). +The provider will search your Bitbucket Cloud account and register catalog files matching the configured path +as Location entity and via following processing steps add all contained catalog entities. +This can be useful as an alternative to static locations or manually adding things to the catalog. + +## Installation + +You will have to add the entity provider in the catalog initialization code of your +backend. The provider is not installed by default, therefore you have to add a +dependency to `@backstage/plugin-catalog-backend-module-bitbucket-cloud` to your backend +package. + +```bash +# From your Backstage root directory +yarn add --cwd packages/backend @backstage/plugin-catalog-backend-module-bitbucket-cloud +``` + +And then add the entity provider to your catalog builder: + +```diff + // In packages/backend/src/plugins/catalog.ts ++ import { BitbucketCloudEntityProvider } from '@backstage/plugin-catalog-backend-module-bitbucket-cloud'; + + export default async function createPlugin( + env: PluginEnvironment, + ): Promise { + const builder = await CatalogBuilder.create(env); ++ builder.addEntityProvider( ++ BitbucketCloudEntityProvider.fromConfig(env.config, { ++ logger: env.logger, ++ schedule: env.scheduler.createScheduledTaskRunner({ ++ frequency: { minutes: 30 }, ++ timeout: { minutes: 3 }, ++ }), ++ }), ++ ); + + // [...] + } +``` + +## Configuration + +To use the entity provider, you'll need a [Bitbucket Cloud integration set up](locations.md). +Very likely a `username` and `appPassword` will be required +(you are restricted to public repositories and a very low rate limit otherwise). + +Additionally, you need to configure your entity provider instance(s): + +```yaml +# app-config.yaml + +catalog: + providers: + bitbucketCloud: + yourProviderId: # identifies your ingested dataset + catalogPath: /catalog-info.yaml # default value + filters: # optional + projectKey: '^apis-.*$' # optional; RegExp + repoSlug: '^service-.*$' # optional; RegExp + 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)_: + 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)_: + Regular expression used to filter results based on the project key. + - **repoSlug** _(optional)_: + Regular expression used to filter results based on the repo slug. +- **workspace**: + Name of your organization account/workspace. + If you want to add multiple workspaces, you need to add one provider config each. + +## Alternative + +_Deprecated!_ Please raise issues for use cases not covered by the entity provider. + +[You can use the `BitbucketDiscoveryProcessor`.](../bitbucket/discovery.md#bitbucket-cloud) diff --git a/docs/integrations/bitbucketCloud/locations.md b/docs/integrations/bitbucketCloud/locations.md new file mode 100644 index 0000000000..b886a49346 --- /dev/null +++ b/docs/integrations/bitbucketCloud/locations.md @@ -0,0 +1,36 @@ +--- +id: locations +title: Bitbucket Cloud Locations +sidebar_label: Locations +# prettier-ignore +description: Integrating source code stored in Bitbucket Cloud into the Backstage catalog +--- + +The Bitbucket Cloud integration supports loading catalog entities from [bitbucket.org](https://bitbucket.org). +Entities can be added to +[static catalog configuration](../../features/software-catalog/configuration.md), +or registered with the +[catalog-import](https://github.com/backstage/backstage/tree/master/plugins/catalog-import) +plugin. + +## Configuration + +```yaml +integrations: + bitbucketCloud: + - username: ${BITBUCKET_CLOUD_USERNAME} + appPassword: ${BITBUCKET_CLOUD_PASSWORD} +``` + +> Note: A public Bitbucket Cloud provider is added automatically at startup for +> convenience, so you only need to list it if you want to supply credentials. + +Directly under the `bitbucketCloud` key is a list of provider configurations, where +you can list the Bitbucket Cloud providers you want to fetch data from. +In the case of Bitbucket Cloud, you will have up to one entry. + +This one entry will have the following elements: + +- `username`: The Bitbucket Cloud username to use in API requests. If + neither a username nor token are supplied, anonymous access will be used. +- `appPassword`: The app password for the Bitbucket Cloud user. diff --git a/microsite/sidebars.json b/microsite/sidebars.json index 7bbe67f58f..d285800c58 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -149,6 +149,14 @@ "integrations/bitbucket/discovery" ] }, + { + "type": "subcategory", + "label": "Bitbucket Cloud", + "ids": [ + "integrations/bitbucketCloud/locations", + "integrations/bitbucketCloud/discovery" + ] + }, { "type": "subcategory", "label": "Datadog", diff --git a/mkdocs.yml b/mkdocs.yml index caaee37f29..7200471326 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -97,6 +97,9 @@ nav: - Bitbucket: - Locations: 'integrations/bitbucket/locations.md' - Discovery: 'integrations/bitbucket/discovery.md' + - Bitbucket Cloud: + - Locations: 'integrations/bitbucketCloud/locations.md' + - Discovery: 'integrations/bitbucketCloud/discovery.md' - Datadog: - Installation: 'integrations/datadog-rum/installation.md' - Gerrit: diff --git a/plugins/catalog-backend-module-bitbucket-cloud/.eslintrc.js b/plugins/catalog-backend-module-bitbucket-cloud/.eslintrc.js new file mode 100644 index 0000000000..e2a53a6ad2 --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/.eslintrc.js @@ -0,0 +1 @@ +module.exports = require('@backstage/cli/config/eslint-factory')(__dirname); diff --git a/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md new file mode 100644 index 0000000000..d9c2148dcf --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/CHANGELOG.md @@ -0,0 +1 @@ +# @backstage/plugin-catalog-backend-module-bitbucket-cloud diff --git a/plugins/catalog-backend-module-bitbucket-cloud/README.md b/plugins/catalog-backend-module-bitbucket-cloud/README.md new file mode 100644 index 0000000000..11f40776ca --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/README.md @@ -0,0 +1,9 @@ +# Catalog Backend Module for Bitbucket Cloud + +This is an extension module to the catalog-backend plugin, +providing extensions targeted at Bitbucket Cloud offerings. + +## Getting started + +See [Backstage documentation](https://backstage.io/docs/integrations/bitbucketCloud/discovery) +for details on how to install and configure the plugin. diff --git a/plugins/catalog-backend-module-bitbucket-cloud/api-report.md b/plugins/catalog-backend-module-bitbucket-cloud/api-report.md new file mode 100644 index 0000000000..b886cc224f --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/api-report.md @@ -0,0 +1,31 @@ +## API Report File for "@backstage/plugin-catalog-backend-module-bitbucket-cloud" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts +import { Config } from '@backstage/config'; +import { EntityProvider } from '@backstage/plugin-catalog-backend'; +import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; +import { Logger } from 'winston'; +import { TaskRunner } from '@backstage/backend-tasks'; + +// @public +export class BitbucketCloudEntityProvider implements EntityProvider { + // (undocumented) + connect(connection: EntityProviderConnection): Promise; + // (undocumented) + static fromConfig( + config: Config, + options: { + logger: Logger; + schedule: TaskRunner; + }, + ): BitbucketCloudEntityProvider[]; + // (undocumented) + getProviderName(): string; + // (undocumented) + getTaskId(): string; + // (undocumented) + refresh(logger: Logger): Promise; +} +``` diff --git a/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts new file mode 100644 index 0000000000..29289bb70e --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/config.d.ts @@ -0,0 +1,90 @@ +/* + * Copyright 2022 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface Config { + catalog?: { + /** + * List of provider-specific options and attributes + */ + providers?: { + /** + * BitbucketCloudEntityProvider configuration + * + * Uses "default" as default id for the single config variant. + */ + bitbucketCloud?: + | { + /** + * (Optional) Path to the catalog file. Default to "/catalog-info.yaml". + * @visibility frontend + */ + catalogPath?: string; + /** + * (Required) Your workspace. + * @visibility frontend + */ + workspace: string; + /** + * (Optional) Filters applied to discovered catalog files in repositories. + * @visibility frontend + */ + filters?: { + /** + * (Optional) Filter for the repository slug. + * @visibility frontend + */ + repoSlug?: RegExp; + /** + * (Optional) Filter for the project key. + * @visibility frontend + */ + projectKey?: RegExp; + }; + } + | Record< + string, + { + /** + * (Optional) Path to the catalog file. Default to "/catalog-info.yaml". + * @visibility frontend + */ + catalogPath?: string; + /** + * (Required) Your workspace. + * @visibility frontend + */ + workspace: string; + /** + * (Optional) Filters applied to discovered catalog files in repositories. + * @visibility frontend + */ + filters?: { + /** + * (Optional) Filter for the repository slug. + * @visibility frontend + */ + repoSlug?: RegExp; + /** + * (Optional) Filter for the project key. + * @visibility frontend + */ + projectKey?: RegExp; + }; + } + >; + }; + }; +} diff --git a/plugins/catalog-backend-module-bitbucket-cloud/package.json b/plugins/catalog-backend-module-bitbucket-cloud/package.json new file mode 100644 index 0000000000..8286e0cf00 --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/package.json @@ -0,0 +1,55 @@ +{ + "name": "@backstage/plugin-catalog-backend-module-bitbucket-cloud", + "description": "A Backstage catalog backend module that helps integrate towards Bitbucket Cloud", + "version": "0.0.0", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": false, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "backstage": { + "role": "backend-plugin-module" + }, + "homepage": "https://backstage.io", + "repository": { + "type": "git", + "url": "https://github.com/backstage/backstage", + "directory": "plugins/catalog-backend-module-bitbucket-cloud" + }, + "keywords": [ + "backstage" + ], + "scripts": { + "build": "backstage-cli package 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" + }, + "dependencies": { + "@backstage/backend-tasks": "^0.3.2-next.0", + "@backstage/config": "^1.0.1", + "@backstage/integration": "^1.2.1-next.0", + "@backstage/plugin-bitbucket-cloud-common": "^0.0.0", + "@backstage/plugin-catalog-backend": "^1.2.0-next.0", + "uuid": "^8.0.0", + "winston": "^3.2.1" + }, + "devDependencies": { + "@backstage/backend-common": "^0.13.6-next.0", + "@backstage/backend-test-utils": "^0.1.25-next.0", + "@backstage/cli": "^0.17.2-next.0", + "msw": "^0.35.0" + }, + "files": [ + "dist", + "config.d.ts" + ], + "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 new file mode 100644 index 0000000000..efb1328643 --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProvider.test.ts @@ -0,0 +1,281 @@ +/* + * 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 { 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'; +import { BitbucketCloudEntityProvider } from './BitbucketCloudEntityProvider'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; + +class PersistingTaskRunner implements TaskRunner { + private tasks: TaskInvocationDefinition[] = []; + + getTasks() { + return this.tasks; + } + + run(task: TaskInvocationDefinition): Promise { + this.tasks.push(task); + return Promise.resolve(undefined); + } +} + +const logger = getVoidLogger(); + +const server = setupServer(); + +describe('BitbucketCloudEntityProvider', () => { + setupRequestMockHandlers(server); + afterEach(() => jest.resetAllMocks()); + + it('no provider config', () => { + const schedule = new PersistingTaskRunner(); + const config = new ConfigReader({}); + const providers = BitbucketCloudEntityProvider.fromConfig(config, { + logger, + schedule, + }); + + expect(providers).toHaveLength(0); + }); + + it('single simple provider config', () => { + const schedule = new PersistingTaskRunner(); + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketCloud: { + workspace: 'test-ws', + }, + }, + }, + }); + const providers = BitbucketCloudEntityProvider.fromConfig(config, { + logger, + schedule, + }); + + expect(providers).toHaveLength(1); + expect(providers[0].getProviderName()).toEqual( + 'bitbucketCloud-provider:default', + ); + }); + + it('multiple provider configs', () => { + const schedule = new PersistingTaskRunner(); + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketCloud: { + myProvider: { + workspace: 'test-ws1', + }, + anotherProvider: { + workspace: 'test-ws2', + }, + }, + }, + }, + }); + const providers = BitbucketCloudEntityProvider.fromConfig(config, { + logger, + schedule, + }); + + expect(providers).toHaveLength(2); + expect(providers[0].getProviderName()).toEqual( + 'bitbucketCloud-provider:myProvider', + ); + expect(providers[1].getProviderName()).toEqual( + 'bitbucketCloud-provider:anotherProvider', + ); + }); + + it('apply full update on scheduled execution', async () => { + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketCloud: { + myProvider: { + workspace: 'test-ws', + catalogPath: 'custom/path/catalog-custom.yaml', + filters: { + projectKey: 'test-.*', + repoSlug: 'test-.*', + }, + }, + }, + }, + }, + }); + const schedule = new PersistingTaskRunner(); + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + }; + const provider = BitbucketCloudEntityProvider.fromConfig(config, { + logger, + schedule, + })[0]; + expect(provider.getProviderName()).toEqual( + 'bitbucketCloud-provider:myProvider', + ); + + server.use( + rest.get( + `https://api.bitbucket.org/2.0/workspaces/test-ws/search/code`, + (_req, res, ctx) => { + const response = { + values: [ + { + // skipped as empty + path_matches: [], + file: { + type: 'commit_file', + path: 'path/to/ignored/file', + }, + }, + { + path_matches: [ + { + match: true, + text: 'catalog-custom.yaml', + }, + ], + file: { + type: 'commit_file', + path: 'custom/path/catalog-custom.yaml', + commit: { + repository: { + // skipped as no match with filter + slug: 'repo', + project: { + key: 'test-project', + }, + mainbranch: { + name: 'main', + }, + links: { + html: { + href: 'https://bitbucket.org/test-ws/repo', + }, + }, + }, + }, + }, + }, + { + path_matches: [ + { + match: true, + text: 'catalog-custom.yaml', + }, + ], + file: { + type: 'commit_file', + path: 'custom/path/catalog-custom.yaml', + commit: { + repository: { + slug: 'test-repo1', + project: { + // skipped as no match with filter + key: 'project', + }, + mainbranch: { + name: 'main', + }, + links: { + html: { + href: 'https://bitbucket.org/test-ws/test-repo1', + }, + }, + }, + }, + }, + }, + { + path_matches: [ + { + match: true, + text: 'catalog-custom.yaml', + }, + ], + file: { + type: 'commit_file', + path: 'custom/path/catalog-custom.yaml', + commit: { + repository: { + slug: 'test-repo2', + project: { + key: 'test-project', + }, + mainbranch: { + name: 'main', + }, + links: { + html: { + href: 'https://bitbucket.org/test-ws/test-repo2', + }, + }, + }, + }, + }, + }, + ], + }; + return res(ctx.json(response)); + }, + ), + ); + + await provider.connect(entityProviderConnection); + + const taskDef = schedule.getTasks()[0]; + expect(taskDef.id).toEqual('bitbucketCloud-provider:myProvider:refresh'); + await (taskDef.fn as () => Promise)(); + + const url = `https://bitbucket.org/test-ws/test-repo2/src/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-7c2e6263b6cc2d14e69fd4d029afba601ad6dc3b', + }, + spec: { + presence: 'required', + target: `${url}`, + type: 'url', + }, + }, + locationKey: 'bitbucketCloud-provider:myProvider', + }, + ]; + + expect(entityProviderConnection.applyMutation).toBeCalledTimes(1); + expect(entityProviderConnection.applyMutation).toBeCalledWith({ + type: 'full', + entities: expectedEntities, + }); + }); +}); diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProvider.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProvider.ts new file mode 100644 index 0000000000..74d642ed9b --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProvider.ts @@ -0,0 +1,240 @@ +/* + * 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 { TaskRunner } from '@backstage/backend-tasks'; +import { Config } from '@backstage/config'; +import { + BitbucketCloudIntegration, + ScmIntegrations, +} from '@backstage/integration'; +import { + BitbucketCloudClient, + Models, +} from '@backstage/plugin-bitbucket-cloud-common'; +import { + EntityProvider, + EntityProviderConnection, + LocationSpec, + locationSpecToLocationEntity, +} from '@backstage/plugin-catalog-backend'; +import { + BitbucketCloudEntityProviderConfig, + readProviderConfigs, +} from './BitbucketCloudEntityProviderConfig'; +import * as uuid from 'uuid'; +import { Logger } from 'winston'; + +const DEFAULT_BRANCH = 'master'; + +/** + * Discovers catalog files located in [Bitbucket Cloud](https://bitbucket.org). + * The provider will search your Bitbucket Cloud account and register catalog files matching the configured path + * as Location entity and via following processing steps add all contained catalog entities. + * This can be useful as an alternative to static locations or manually adding things to the catalog. + * + * @public + */ +export class BitbucketCloudEntityProvider implements EntityProvider { + private readonly client: BitbucketCloudClient; + private readonly config: BitbucketCloudEntityProviderConfig; + private readonly logger: Logger; + private readonly scheduleFn: () => Promise; + private connection?: EntityProviderConnection; + + static fromConfig( + config: Config, + options: { + logger: Logger; + schedule: TaskRunner; + }, + ): BitbucketCloudEntityProvider[] { + const integrations = ScmIntegrations.fromConfig(config); + const integration = integrations.bitbucketCloud.byHost('bitbucket.org'); + if (!integration) { + // this should never happen as we add a default integration, + // but as a general safeguard, e.g. if this approach gets changed + throw new Error('No integration for bitbucket.org available'); + } + + return readProviderConfigs(config).map( + providerConfig => + new BitbucketCloudEntityProvider( + providerConfig, + integration, + options.logger, + options.schedule, + ), + ); + } + + private constructor( + config: BitbucketCloudEntityProviderConfig, + integration: BitbucketCloudIntegration, + logger: Logger, + schedule: TaskRunner, + ) { + this.client = BitbucketCloudClient.fromConfig(integration.config); + this.config = config; + this.logger = logger.child({ + target: this.getProviderName(), + }); + this.scheduleFn = this.createScheduleFn(schedule); + } + + private createScheduleFn(schedule: TaskRunner): () => Promise { + return async () => { + const taskId = this.getTaskId(); + return schedule.run({ + id: taskId, + fn: async () => { + const logger = this.logger.child({ + class: BitbucketCloudEntityProvider.prototype.constructor.name, + taskId, + taskInstanceId: uuid.v4(), + }); + + try { + await this.refresh(logger); + } catch (error) { + logger.error(error); + } + }, + }); + }; + } + + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getProviderName} */ + getProviderName(): string { + return `bitbucketCloud-provider:${this.config.id}`; + } + + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.getTaskId} */ + getTaskId(): string { + return `${this.getProviderName()}:refresh`; + } + + /** {@inheritdoc @backstage/plugin-catalog-backend#EntityProvider.connect} */ + async connect(connection: EntityProviderConnection): Promise { + this.connection = connection; + await this.scheduleFn(); + } + + async refresh(logger: Logger) { + if (!this.connection) { + throw new Error('Not initialized'); + } + + logger.info('Discovering catalog files in Bitbucket Cloud repositories'); + + const targets = await this.findCatalogFiles(); + const entities = targets + .map(BitbucketCloudEntityProvider.toLocationSpec) + .map(location => locationSpecToLocationEntity({ location })) + .map(entity => { + return { + locationKey: this.getProviderName(), + entity: entity, + }; + }); + + await this.connection.applyMutation({ + type: 'full', + entities: entities, + }); + + logger.info( + `Committed ${entities.length} Locations for catalog files in Bitbucket Cloud repositories`, + ); + } + + private async findCatalogFiles(): Promise { + const workspace = this.config.workspace; + const catalogPath = this.config.catalogPath; + + const catalogFilename = catalogPath.substring( + catalogPath.lastIndexOf('/') + 1, + ); + + // load all fields relevant for creating refs later, but not more + const fields = [ + // exclude code/content match details + '-values.content_matches', + // include/add relevant repository details + '+values.file.commit.repository.mainbranch.name', + '+values.file.commit.repository.project.key', + '+values.file.commit.repository.slug', + // remove irrelevant links + '-values.*.links', + '-values.*.*.links', + '-values.*.*.*.links', + // ...except the one we need + '+values.file.commit.repository.links.html.href', + ].join(','); + const query = `"${catalogFilename}" path:${catalogPath}`; + const searchResults = this.client + .searchCode(workspace, query, { fields }) + .iterateResults(); + + const result: string[] = []; + + for await (const searchResult of searchResults) { + // not a file match, but a code match + if (searchResult.path_matches!.length === 0) { + continue; + } + + const repository = searchResult.file!.commit!.repository!; + if (this.matchesFilters(repository)) { + result.push( + BitbucketCloudEntityProvider.toUrl( + repository, + searchResult.file!.path!, + ), + ); + } + } + + return result; + } + + private matchesFilters(repository: Models.Repository): boolean { + const filters = this.config.filters; + return ( + !filters || + ((!filters.projectKey || + filters.projectKey.test(repository.project!.key!)) && + (!filters.repoSlug || filters.repoSlug.test(repository.slug!))) + ); + } + + private static toUrl( + repository: Models.Repository, + filePath: string, + ): string { + const repoUrl = repository.links!.html!.href; + const branch = repository.mainbranch?.name ?? DEFAULT_BRANCH; + + return `${repoUrl}/src/${branch}/${filePath}`; + } + + private static toLocationSpec(target: string): LocationSpec { + return { + type: 'url', + target: target, + presence: 'required', + }; + } +} diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.test.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.test.ts new file mode 100644 index 0000000000..a42bd2cde7 --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.test.ts @@ -0,0 +1,115 @@ +/* + * 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 { readProviderConfigs } from './BitbucketCloudEntityProviderConfig'; + +describe('readProviderConfigs', () => { + afterEach(() => jest.resetAllMocks()); + + it('no provider config', () => { + const config = new ConfigReader({}); + const providerConfigs = readProviderConfigs(config); + + expect(providerConfigs).toHaveLength(0); + }); + + it('single simple provider config', () => { + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketCloud: { + workspace: 'test-ws', + }, + }, + }, + }); + const providerConfigs = readProviderConfigs(config); + + expect(providerConfigs).toHaveLength(1); + expect(providerConfigs[0].id).toEqual('default'); + expect(providerConfigs[0].workspace).toEqual('test-ws'); + }); + + it('multiple provider configs', () => { + const config = new ConfigReader({ + catalog: { + providers: { + bitbucketCloud: { + providerWorkspaceOnly: { + workspace: 'test-ws1', + }, + providerCustomCatalogPath: { + workspace: 'test-ws2', + catalogPath: 'custom/path/catalog-info.yaml', + }, + providerWithProjectKeyFilter: { + workspace: 'test-ws3', + filters: { + projectKey: 'projectKey.*filter', + }, + }, + providerWithRepoSlugFilter: { + workspace: 'test-ws4', + filters: { + repoSlug: 'repoSlug.*filter', + }, + }, + }, + }, + }, + }); + const providerConfigs = readProviderConfigs(config); + + expect(providerConfigs).toHaveLength(4); + expect(providerConfigs[0]).toEqual({ + id: 'providerWorkspaceOnly', + workspace: 'test-ws1', + catalogPath: '/catalog-info.yaml', + filters: { + projectKey: undefined, + repoSlug: undefined, + }, + }); + expect(providerConfigs[1]).toEqual({ + id: 'providerCustomCatalogPath', + workspace: 'test-ws2', + catalogPath: 'custom/path/catalog-info.yaml', + filters: { + projectKey: undefined, + repoSlug: undefined, + }, + }); + expect(providerConfigs[2]).toEqual({ + id: 'providerWithProjectKeyFilter', + workspace: 'test-ws3', + catalogPath: '/catalog-info.yaml', + filters: { + projectKey: /^projectKey.*filter$/, + repoSlug: undefined, + }, + }); + expect(providerConfigs[3]).toEqual({ + id: 'providerWithRepoSlugFilter', + workspace: 'test-ws4', + catalogPath: '/catalog-info.yaml', + filters: { + projectKey: undefined, + repoSlug: /^repoSlug.*filter$/, + }, + }); + }); +}); diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.ts new file mode 100644 index 0000000000..95e995f8c3 --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/BitbucketCloudEntityProviderConfig.ts @@ -0,0 +1,93 @@ +/* + * 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'; + +const DEFAULT_CATALOG_PATH = '/catalog-info.yaml'; +const DEFAULT_PROVIDER_ID = 'default'; + +export type BitbucketCloudEntityProviderConfig = { + id: string; + catalogPath: string; + workspace: string; + filters?: { + projectKey?: RegExp; + repoSlug?: RegExp; + }; +}; + +export function readProviderConfigs( + config: Config, +): BitbucketCloudEntityProviderConfig[] { + const providersConfig = config.getOptionalConfig( + 'catalog.providers.bitbucketCloud', + ); + if (!providersConfig) { + return []; + } + + if (providersConfig.has('workspace')) { + // simple/single config variant + return [readProviderConfig(DEFAULT_PROVIDER_ID, providersConfig)]; + } + + return providersConfig.keys().map(id => { + const providerConfig = providersConfig.getConfig(id); + + return readProviderConfig(id, providerConfig); + }); +} + +function readProviderConfig( + id: string, + config: Config, +): BitbucketCloudEntityProviderConfig { + const workspace = config.getString('workspace'); + const catalogPath = + config.getOptionalString('catalogPath') ?? DEFAULT_CATALOG_PATH; + const projectKeyPattern = config.getOptionalString('filters.projectKey'); + const repoSlugPattern = config.getOptionalString('filters.repoSlug'); + + return { + id, + catalogPath, + workspace, + filters: { + projectKey: projectKeyPattern + ? compileRegExp(projectKeyPattern) + : undefined, + repoSlug: repoSlugPattern ? compileRegExp(repoSlugPattern) : undefined, + }, + }; +} + +/** + * Compiles a RegExp while enforcing the pattern to contain + * the start-of-line and end-of-line anchors. + * + * @param pattern + */ +function compileRegExp(pattern: string): RegExp { + let fullLinePattern = pattern; + if (!fullLinePattern.startsWith('^')) { + fullLinePattern = `^${fullLinePattern}`; + } + if (!fullLinePattern.endsWith('$')) { + fullLinePattern = `${fullLinePattern}$`; + } + + return new RegExp(fullLinePattern); +} diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/index.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/index.ts new file mode 100644 index 0000000000..1c15ad4e8f --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/src/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. + */ + +/** + * A Backstage catalog backend module that helps integrate towards Bitbucket Cloud + * + * @packageDocumentation + */ + +export { BitbucketCloudEntityProvider } from './BitbucketCloudEntityProvider'; diff --git a/plugins/catalog-backend-module-bitbucket-cloud/src/setupTests.ts b/plugins/catalog-backend-module-bitbucket-cloud/src/setupTests.ts new file mode 100644 index 0000000000..813cdeaae3 --- /dev/null +++ b/plugins/catalog-backend-module-bitbucket-cloud/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 {}; From 09f1fbd2b8722c9d00488c63f567a2a7b958ef8c Mon Sep 17 00:00:00 2001 From: Suzanne Daniels Date: Mon, 30 May 2022 12:40:55 +0200 Subject: [PATCH 137/149] - Adding the new community session - adding recordings to the microsite Signed-off-by: Suzanne Daniels --- microsite/data/on-demand/20220518-01.yaml | 2 +- microsite/data/on-demand/20220525.yaml | 9 +++++++++ microsite/data/on-demand/20220615-01.yaml | 9 +++++++++ microsite/data/on-demand/20220622-1.yaml | 9 +++++++++ microsite/pages/en/live.js | 4 ++-- 5 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 microsite/data/on-demand/20220525.yaml create mode 100644 microsite/data/on-demand/20220615-01.yaml create mode 100644 microsite/data/on-demand/20220622-1.yaml diff --git a/microsite/data/on-demand/20220518-01.yaml b/microsite/data/on-demand/20220518-01.yaml index e98c79005a..b84c78455f 100644 --- a/microsite/data/on-demand/20220518-01.yaml +++ b/microsite/data/on-demand/20220518-01.yaml @@ -1,7 +1,7 @@ --- title: Adopters Community Sessions date: May 18, 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/dEd1fl3wRv youtubeImgUrl: https://backstage.io/img/b-sessions.png diff --git a/microsite/data/on-demand/20220525.yaml b/microsite/data/on-demand/20220525.yaml new file mode 100644 index 0000000000..d39e113439 --- /dev/null +++ b/microsite/data/on-demand/20220525.yaml @@ -0,0 +1,9 @@ +--- +title: Contributor Community Sessions +date: May 25, 2022 +category: Meetup +description: Join the maintainers and contributors for the Contributor Community Sessions +youtubeUrl: https://youtu.be/evf_LV0KzIk +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/46 diff --git a/microsite/data/on-demand/20220615-01.yaml b/microsite/data/on-demand/20220615-01.yaml new file mode 100644 index 0000000000..8ac660a71c --- /dev/null +++ b/microsite/data/on-demand/20220615-01.yaml @@ -0,0 +1,9 @@ +--- +title: Adopters Community Sessions +date: June 15, 2022 +category: Upcoming +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/aKZnjnE5Wy8 +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/49 diff --git a/microsite/data/on-demand/20220622-1.yaml b/microsite/data/on-demand/20220622-1.yaml new file mode 100644 index 0000000000..0bbd53525a --- /dev/null +++ b/microsite/data/on-demand/20220622-1.yaml @@ -0,0 +1,9 @@ +--- +title: Contributor Community Sessions +date: June 22, 2022 +category: Upcoming +description: Join the maintainers and contributors for the Contributor Community Sessions +youtubeUrl: https://youtu.be/aKZnjnE5Wy8 +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/49 diff --git a/microsite/pages/en/live.js b/microsite/pages/en/live.js index 9f8a0e24b0..062af419fc 100644 --- a/microsite/pages/en/live.js +++ b/microsite/pages/en/live.js @@ -34,7 +34,7 @@ const Background = props => { From cea6b2f50e4303459392a31c079a1de56da38fb1 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 30 May 2022 11:08:13 +0000 Subject: [PATCH 138/149] chore(deps): update dependency concurrently to v7.2.1 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 280f76116d..c812f0b5d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9537,9 +9537,9 @@ concat-with-sourcemaps@^1.1.0: source-map "^0.6.1" concurrently@^7.0.0: - version "7.2.0" - resolved "https://registry.npmjs.org/concurrently/-/concurrently-7.2.0.tgz#4d9b4d1e527b8a8cb101bc2aee317e09496fad43" - integrity sha512-4KIVY5HopDRhN3ndAgfFOLsMk1PZUPgghlgTMZ5Pb5aTrqYg86RcZaIZC2Cz+qpZ9DsX36WHGjvWnXPqdnblhw== + version "7.2.1" + resolved "https://registry.npmjs.org/concurrently/-/concurrently-7.2.1.tgz#88b144060443403060aad46f837dd17451f7e55e" + integrity sha512-7cab/QyqipqghrVr9qZmoWbidu0nHsmxrpNqQ7r/67vfl1DWJElexehQnTH1p+87tDkihaAjM79xTZyBQh7HLw== dependencies: chalk "^4.1.0" date-fns "^2.16.1" From cc1b793bdf717cab1888416efd17c9f054a159c9 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 30 May 2022 11:56:50 +0000 Subject: [PATCH 139/149] chore(deps): update dependency del to v6.1.1 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index c812f0b5d5..b154c22f49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10744,9 +10744,9 @@ define-property@^2.0.2: isobject "^3.0.1" del@^6.0.0: - version "6.1.0" - resolved "https://registry.npmjs.org/del/-/del-6.1.0.tgz#aa79a5b0a2a9ecc985c0a075e8ad9a5b23bf949c" - integrity sha512-OpcRktOt7G7HBfyxP0srBH4Djg4824EQORX8E1qvIhIzthNNArxxhrB/Mm7dRMiLi1nvFyUpDhzD2cTtbBhV8A== + version "6.1.1" + resolved "https://registry.npmjs.org/del/-/del-6.1.1.tgz#3b70314f1ec0aa325c6b14eb36b95786671edb7a" + integrity sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg== dependencies: globby "^11.0.1" graceful-fs "^4.2.4" From fa3d25ec048c0e50a0313521f635014374fbc8cf Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 30 May 2022 12:40:17 +0000 Subject: [PATCH 140/149] chore(deps): update dependency esbuild to v0.14.42 Signed-off-by: Renovate Bot --- yarn.lock | 206 +++++++++++++++++++++++++++--------------------------- 1 file changed, 103 insertions(+), 103 deletions(-) diff --git a/yarn.lock b/yarn.lock index 245c2158bb..b9fe38d431 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11526,75 +11526,75 @@ es6-error@^4.1.1: resolved "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz#9e3af407459deed47e9a91f9b885a84eb05c561d" integrity sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg== -esbuild-android-64@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.39.tgz#09f12a372eed9743fd77ff6d889ac14f7b340c21" - integrity sha512-EJOu04p9WgZk0UoKTqLId9VnIsotmI/Z98EXrKURGb3LPNunkeffqQIkjS2cAvidh+OK5uVrXaIP229zK6GvhQ== +esbuild-android-64@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.42.tgz#d7ab3d44d3671218d22bce52f65642b12908d954" + integrity sha512-P4Y36VUtRhK/zivqGVMqhptSrFILAGlYp0Z8r9UQqHJ3iWztRCNWnlBzD9HRx0DbueXikzOiwyOri+ojAFfW6A== -esbuild-android-arm64@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.39.tgz#f608d00ea03fe26f3b1ab92a30f99220390f3071" - integrity sha512-+twajJqO7n3MrCz9e+2lVOnFplRsaGRwsq1KL/uOy7xK7QdRSprRQcObGDeDZUZsacD5gUkk6OiHiYp6RzU3CA== +esbuild-android-arm64@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.42.tgz#45336d8bec49abddb3a022996a23373f45a57c27" + integrity sha512-0cOqCubq+RWScPqvtQdjXG3Czb3AWI2CaKw3HeXry2eoA2rrPr85HF7IpdU26UWdBXgPYtlTN1LUiuXbboROhg== -esbuild-darwin-64@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.39.tgz#31528daa75b4c9317721ede344195163fae3e041" - integrity sha512-ImT6eUw3kcGcHoUxEcdBpi6LfTRWaV6+qf32iYYAfwOeV+XaQ/Xp5XQIBiijLeo+LpGci9M0FVec09nUw41a5g== +esbuild-darwin-64@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.42.tgz#6dff5e44cd70a88c33323e2f5fb598e40c68a9e0" + integrity sha512-ipiBdCA3ZjYgRfRLdQwP82rTiv/YVMtW36hTvAN5ZKAIfxBOyPXY7Cejp3bMXWgzKD8B6O+zoMzh01GZsCuEIA== -esbuild-darwin-arm64@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.39.tgz#247f770d86d90a215fa194f24f90e30a0bd97245" - integrity sha512-/fcQ5UhE05OiT+bW5v7/up1bDsnvaRZPJxXwzXsMRrr7rZqPa85vayrD723oWMT64dhrgWeA3FIneF8yER0XTw== +esbuild-darwin-arm64@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.42.tgz#2c7313e1b12d2fa5b889c03213d682fb92ca8c4f" + integrity sha512-bU2tHRqTPOaoH/4m0zYHbFWpiYDmaA0gt90/3BMEFaM0PqVK/a6MA2V/ypV5PO0v8QxN6gH5hBPY4YJ2lopXgA== -esbuild-freebsd-64@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.39.tgz#479414d294905055eb396ebe455ed42213284ee0" - integrity sha512-oMNH8lJI4wtgN5oxuFP7BQ22vgB/e3Tl5Woehcd6i2r6F3TszpCnNl8wo2d/KvyQ4zvLvCWAlRciumhQg88+kQ== +esbuild-freebsd-64@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.42.tgz#ad1c5a564a7e473b8ce95ee7f76618d05d6daffc" + integrity sha512-75h1+22Ivy07+QvxHyhVqOdekupiTZVLN1PMwCDonAqyXd8TVNJfIRFrdL8QmSJrOJJ5h8H1I9ETyl2L8LQDaw== -esbuild-freebsd-arm64@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.39.tgz#cedeb10357c88533615921ae767a67dc870a474c" - integrity sha512-1GHK7kwk57ukY2yI4ILWKJXaxfr+8HcM/r/JKCGCPziIVlL+Wi7RbJ2OzMcTKZ1HpvEqCTBT/J6cO4ZEwW4Ypg== +esbuild-freebsd-arm64@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.42.tgz#4bdb480234144f944f1930829bace7561135ddc7" + integrity sha512-W6Jebeu5TTDQMJUJVarEzRU9LlKpNkPBbjqSu+GUPTHDCly5zZEQq9uHkmHHl7OKm+mQ2zFySN83nmfCeZCyNA== -esbuild-linux-32@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.39.tgz#d9f008c4322d771f3958f59c1eee5a05cdf92485" - integrity sha512-g97Sbb6g4zfRLIxHgW2pc393DjnkTRMeq3N1rmjDUABxpx8SjocK4jLen+/mq55G46eE2TA0MkJ4R3SpKMu7dg== +esbuild-linux-32@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.42.tgz#ef18fd19f067e9d2b5f677d6b82fa81519f5a8c2" + integrity sha512-Ooy/Bj+mJ1z4jlWcK5Dl6SlPlCgQB9zg1UrTCeY8XagvuWZ4qGPyYEWGkT94HUsRi2hKsXvcs6ThTOjBaJSMfg== -esbuild-linux-64@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.39.tgz#ba58d7f66858913aeb1ab5c6bde1bbd824731795" - integrity sha512-4tcgFDYWdI+UbNMGlua9u1Zhu0N5R6u9tl5WOM8aVnNX143JZoBZLpCuUr5lCKhnD0SCO+5gUyMfupGrHtfggQ== +esbuild-linux-64@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.42.tgz#d84e7333b1c1b22cf8b5b9dbb5dd9b2ecb34b79f" + integrity sha512-2L0HbzQfbTuemUWfVqNIjOfaTRt9zsvjnme6lnr7/MO9toz/MJ5tZhjqrG6uDWDxhsaHI2/nsDgrv8uEEN2eoA== -esbuild-linux-arm64@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.39.tgz#708785a30072702b5b1c16b65cf9c25c51202529" - integrity sha512-23pc8MlD2D6Px1mV8GMglZlKgwgNKAO8gsgsLLcXWSs9lQsCYkIlMo/2Ycfo5JrDIbLdwgP8D2vpfH2KcBqrDQ== +esbuild-linux-arm64@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.42.tgz#dc19e282f8c4ffbaa470c02a4d171e4ae0180cca" + integrity sha512-c3Ug3e9JpVr8jAcfbhirtpBauLxzYPpycjWulD71CF6ZSY26tvzmXMJYooQ2YKqDY4e/fPu5K8bm7MiXMnyxuA== -esbuild-linux-arm@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.39.tgz#4e8b5deaa7ab60d0d28fab131244ef82b40684f4" - integrity sha512-t0Hn1kWVx5UpCzAJkKRfHeYOLyFnXwYynIkK54/h3tbMweGI7dj400D1k0Vvtj2u1P+JTRT9tx3AjtLEMmfVBQ== +esbuild-linux-arm@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.42.tgz#d49870e63e2242b8156bf473f2ee5154226be328" + integrity sha512-STq69yzCMhdRaWnh29UYrLSr/qaWMm/KqwaRF1pMEK7kDiagaXhSL1zQGXbYv94GuGY/zAwzK98+6idCMUOOCg== -esbuild-linux-mips64le@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.39.tgz#6f3bf3023f711084e5a1e8190487d2020f39f0f7" - integrity sha512-epwlYgVdbmkuRr5n4es3B+yDI0I2e/nxhKejT9H0OLxFAlMkeQZxSpxATpDc9m8NqRci6Kwyb/SfmD1koG2Zuw== +esbuild-linux-mips64le@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.42.tgz#f4e6ff9bf8a6f175470498826f48d093b054fc22" + integrity sha512-QuvpHGbYlkyXWf2cGm51LBCHx6eUakjaSrRpUqhPwjh/uvNUYvLmz2LgPTTPwCqaKt0iwL+OGVL0tXA5aDbAbg== -esbuild-linux-ppc64le@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.39.tgz#900e718a4ea3f6aedde8424828eeefdd4b48d4b9" - integrity sha512-W/5ezaq+rQiQBThIjLMNjsuhPHg+ApVAdTz2LvcuesZFMsJoQAW2hutoyg47XxpWi7aEjJGrkS26qCJKhRn3QQ== +esbuild-linux-ppc64le@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.42.tgz#ac9c66fc80ba9f8fda15a4cc08f4e55f6c0aed63" + integrity sha512-8ohIVIWDbDT+i7lCx44YCyIRrOW1MYlks9fxTo0ME2LS/fxxdoJBwHWzaDYhjvf8kNpA+MInZvyOEAGoVDrMHg== -esbuild-linux-riscv64@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.39.tgz#dcbff622fa37047a75d2ff7a1d8d2949d80277e4" - integrity sha512-IS48xeokcCTKeQIOke2O0t9t14HPvwnZcy+5baG13Z1wxs9ZrC5ig5ypEQQh4QMKxURD5TpCLHw2W42CLuVZaA== +esbuild-linux-riscv64@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.42.tgz#21e0ae492a3a9bf4eecbfc916339a66e204256d0" + integrity sha512-DzDqK3TuoXktPyG1Lwx7vhaF49Onv3eR61KwQyxYo4y5UKTpL3NmuarHSIaSVlTFDDpcIajCDwz5/uwKLLgKiQ== -esbuild-linux-s390x@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.39.tgz#3f725a7945b419406c99d93744b28552561dcdfd" - integrity sha512-zEfunpqR8sMomqXhNTFEKDs+ik7HC01m3M60MsEjZOqaywHu5e5682fMsqOlZbesEAAaO9aAtRBsU7CHnSZWyA== +esbuild-linux-s390x@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.42.tgz#06d40b957250ffd9a2183bfdfc9a03d6fd21b3e8" + integrity sha512-YFRhPCxl8nb//Wn6SiS5pmtplBi4z9yC2gLrYoYI/tvwuB1jldir9r7JwAGy1Ck4D7sE7wBN9GFtUUX/DLdcEQ== esbuild-loader@^2.18.0: version "2.19.0" @@ -11608,61 +11608,61 @@ esbuild-loader@^2.18.0: tapable "^2.2.0" webpack-sources "^2.2.0" -esbuild-netbsd-64@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.39.tgz#e10e40b6a765798b90d4eb85901cc85c8b7ff85e" - integrity sha512-Uo2suJBSIlrZCe4E0k75VDIFJWfZy+bOV6ih3T4MVMRJh1lHJ2UyGoaX4bOxomYN3t+IakHPyEoln1+qJ1qYaA== +esbuild-netbsd-64@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.42.tgz#185664f05f10914f14ed43bd9e22b7de584267f7" + integrity sha512-QYSD2k+oT9dqB/4eEM9c+7KyNYsIPgzYOSrmfNGDIyJrbT1d+CFVKvnKahDKNJLfOYj8N4MgyFaU9/Ytc6w5Vw== -esbuild-openbsd-64@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.39.tgz#935ec143f75ce10bd9cdb1c87fee00287eb0edbc" - integrity sha512-secQU+EpgUPpYjJe3OecoeGKVvRMLeKUxSMGHnK+aK5uQM3n1FPXNJzyz1LHFOo0WOyw+uoCxBYdM4O10oaCAA== +esbuild-openbsd-64@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.42.tgz#c29006f659eb4e55283044bbbd4eb4054fae8839" + integrity sha512-M2meNVIKWsm2HMY7+TU9AxM7ZVwI9havdsw6m/6EzdXysyCFFSoaTQ/Jg03izjCsK17FsVRHqRe26Llj6x0MNA== -esbuild-sunos-64@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.39.tgz#0e7aa82b022a2e6d55b0646738b2582c2d72c3c0" - integrity sha512-qHq0t5gePEDm2nqZLb+35p/qkaXVS7oIe32R0ECh2HOdiXXkj/1uQI9IRogGqKkK+QjDG+DhwiUw7QoHur/Rwg== +esbuild-sunos-64@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.42.tgz#aa9eec112cd1e7105e7bb37000eca7d460083f8f" + integrity sha512-uXV8TAZEw36DkgW8Ak3MpSJs1ofBb3Smkc/6pZ29sCAN1KzCAQzsje4sUwugf+FVicrHvlamCOlFZIXgct+iqQ== -esbuild-windows-32@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.39.tgz#3f1538241f31b538545f4b5841b248cac260fa35" - integrity sha512-XPjwp2OgtEX0JnOlTgT6E5txbRp6Uw54Isorm3CwOtloJazeIWXuiwK0ONJBVb/CGbiCpS7iP2UahGgd2p1x+Q== +esbuild-windows-32@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.42.tgz#c3fc450853c61a74dacc5679de301db23b73e61e" + integrity sha512-4iw/8qWmRICWi9ZOnJJf9sYt6wmtp3hsN4TdI5NqgjfOkBVMxNdM9Vt3626G1Rda9ya2Q0hjQRD9W1o+m6Lz6g== -esbuild-windows-64@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.39.tgz#b100c59f96d3c2da2e796e42fee4900d755d3e03" - integrity sha512-E2wm+5FwCcLpKsBHRw28bSYQw0Ikxb7zIMxw3OPAkiaQhLVr3dnVO8DofmbWhhf6b97bWzg37iSZ45ZDpLw7Ow== +esbuild-windows-64@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.42.tgz#b877aa37ff47d9fcf0ccb1ca6a24b31475a5e555" + integrity sha512-j3cdK+Y3+a5H0wHKmLGTJcq0+/2mMBHPWkItR3vytp/aUGD/ua/t2BLdfBIzbNN9nLCRL9sywCRpOpFMx3CxzA== -esbuild-windows-arm64@0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.39.tgz#00268517e665b33c89778d61f144e4256b39f631" - integrity sha512-sBZQz5D+Gd0EQ09tZRnz/PpVdLwvp/ufMtJ1iDFYddDaPpZXKqPyaxfYBLs3ueiaksQ26GGa7sci0OqFzNs7KA== +esbuild-windows-arm64@0.14.42: + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.42.tgz#79da8744626f24bc016dc40d016950b5a4a2bac5" + integrity sha512-+lRAARnF+hf8J0mN27ujO+VbhPbDqJ8rCcJKye4y7YZLV6C4n3pTRThAb388k/zqF5uM0lS5O201u0OqoWSicw== esbuild@^0.14.1, esbuild@^0.14.10, esbuild@^0.14.39: - version "0.14.39" - resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.39.tgz#c926b2259fe6f6d3a94f528fb42e103c5a6d909a" - integrity sha512-2kKujuzvRWYtwvNjYDY444LQIA3TyJhJIX3Yo4+qkFlDDtGlSicWgeHVJqMUP/2sSfH10PGwfsj+O2ro1m10xQ== + version "0.14.42" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.14.42.tgz#98587df0b024d5f6341b12a1d735a2bff55e1836" + integrity sha512-V0uPZotCEHokJdNqyozH6qsaQXqmZEOiZWrXnds/zaH/0SyrIayRXWRB98CENO73MIZ9T3HBIOsmds5twWtmgw== optionalDependencies: - esbuild-android-64 "0.14.39" - esbuild-android-arm64 "0.14.39" - esbuild-darwin-64 "0.14.39" - esbuild-darwin-arm64 "0.14.39" - esbuild-freebsd-64 "0.14.39" - esbuild-freebsd-arm64 "0.14.39" - esbuild-linux-32 "0.14.39" - esbuild-linux-64 "0.14.39" - esbuild-linux-arm "0.14.39" - esbuild-linux-arm64 "0.14.39" - esbuild-linux-mips64le "0.14.39" - esbuild-linux-ppc64le "0.14.39" - esbuild-linux-riscv64 "0.14.39" - esbuild-linux-s390x "0.14.39" - esbuild-netbsd-64 "0.14.39" - esbuild-openbsd-64 "0.14.39" - esbuild-sunos-64 "0.14.39" - esbuild-windows-32 "0.14.39" - esbuild-windows-64 "0.14.39" - esbuild-windows-arm64 "0.14.39" + esbuild-android-64 "0.14.42" + esbuild-android-arm64 "0.14.42" + esbuild-darwin-64 "0.14.42" + esbuild-darwin-arm64 "0.14.42" + esbuild-freebsd-64 "0.14.42" + esbuild-freebsd-arm64 "0.14.42" + esbuild-linux-32 "0.14.42" + esbuild-linux-64 "0.14.42" + esbuild-linux-arm "0.14.42" + esbuild-linux-arm64 "0.14.42" + esbuild-linux-mips64le "0.14.42" + esbuild-linux-ppc64le "0.14.42" + esbuild-linux-riscv64 "0.14.42" + esbuild-linux-s390x "0.14.42" + esbuild-netbsd-64 "0.14.42" + esbuild-openbsd-64 "0.14.42" + esbuild-sunos-64 "0.14.42" + esbuild-windows-32 "0.14.42" + esbuild-windows-64 "0.14.42" + esbuild-windows-arm64 "0.14.42" escalade@^3.1.1: version "3.1.1" From 6b33e49261c7310a76542d39993e383c201f8429 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 30 May 2022 12:41:04 +0000 Subject: [PATCH 141/149] chore(deps): update dependency lint-staged to v12.4.3 Signed-off-by: Renovate Bot --- yarn.lock | 47 +++++++++++++++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 16 deletions(-) diff --git a/yarn.lock b/yarn.lock index 245c2158bb..15ef2cf1bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9491,6 +9491,11 @@ commander@^9.1.0: resolved "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz#6e21014b2ed90d8b7c9647230d8b7a94a4a419a9" integrity sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w== +commander@^9.3.0: + version "9.3.0" + resolved "https://registry.npmjs.org/commander/-/commander-9.3.0.tgz#f619114a5a2d2054e0d9ff1b31d5ccf89255e26b" + integrity sha512-hv95iU5uXPbK83mjrJKuZyFM/LBAoCV/XhVGkS5Je6tl7sxr6A0ITMw5WoRV46/UaJ46Nllm3Xt7IaJhXTIkzw== + common-ancestor-path@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz#4f7d2d1394d91b7abdf51871c62f71eadb0182a7" @@ -15089,7 +15094,7 @@ isbinaryfile@^5.0.0: isexe@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" - integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== isobject@^2.0.0: version "2.1.0" @@ -16498,7 +16503,12 @@ libnpmpublish@^4.0.0: semver "^7.1.3" ssri "^8.0.0" -lilconfig@2.0.4, lilconfig@^2.0.3: +lilconfig@2.0.5: + version "2.0.5" + resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" + integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== + +lilconfig@^2.0.3: version "2.0.4" resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA== @@ -16516,23 +16526,23 @@ linkify-it@^3.0.1: uc.micro "^1.0.1" lint-staged@^12.2.0: - version "12.4.1" - resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-12.4.1.tgz#63fa27bfc8a33515f6902f63f6670864f1fb233c" - integrity sha512-PTXgzpflrQ+pODQTG116QNB+Q6uUTDg5B5HqGvNhoQSGt8Qy+MA/6zSnR8n38+sxP5TapzeQGTvoKni0KRS8Vg== + version "12.4.3" + resolved "https://registry.npmjs.org/lint-staged/-/lint-staged-12.4.3.tgz#914fa468458364e14cc952145db552d87c8847b6" + integrity sha512-eH6SKOmdm/ZwCRMTZAmM3q3dPkpq6vco/BfrOw8iGun4Xs/thYegPD/MLIwKO+iPkzibkLJuQcRhRLXKvaKreg== dependencies: cli-truncate "^3.1.0" colorette "^2.0.16" - commander "^8.3.0" - debug "^4.3.3" + commander "^9.3.0" + debug "^4.3.4" execa "^5.1.1" - lilconfig "2.0.4" - listr2 "^4.0.1" - micromatch "^4.0.4" + lilconfig "2.0.5" + listr2 "^4.0.5" + micromatch "^4.0.5" normalize-path "^3.0.0" - object-inspect "^1.12.0" + object-inspect "^1.12.2" pidtree "^0.5.0" string-argv "^0.3.1" - supports-color "^9.2.1" + supports-color "^9.2.2" yaml "^1.10.2" listenercount@~1.0.1: @@ -16583,7 +16593,7 @@ listr2@^3.8.3: through "^2.3.8" wrap-ansi "^7.0.0" -listr2@^4.0.1: +listr2@^4.0.5: version "4.0.5" resolved "https://registry.npmjs.org/listr2/-/listr2-4.0.5.tgz#9dcc50221583e8b4c71c43f9c7dfd0ef546b75d5" integrity sha512-juGHV1doQdpNT3GSTs9IUN43QJb7KHdF9uqg7Vufs/tG9VTzpFphqF4pm/ICdAABGQxsyNn9CiYA3StkI6jpwA== @@ -17817,7 +17827,7 @@ micromatch@^3.1.10: snapdragon "^0.8.1" to-regex "^3.0.2" -micromatch@^4.0.2, micromatch@^4.0.4: +micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== @@ -18833,11 +18843,16 @@ object-hash@^2.0.1: resolved "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz#5ad518581eefc443bd763472b8ff2e9c2c0d54a5" integrity sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw== -object-inspect@^1.11.0, object-inspect@^1.12.0, object-inspect@^1.9.0: +object-inspect@^1.11.0, object-inspect@^1.9.0: version "1.12.0" resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" integrity sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g== +object-inspect@^1.12.2: + version "1.12.2" + resolved "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" + integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== + object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -23550,7 +23565,7 @@ supports-color@^8.0.0, supports-color@^8.1.0, supports-color@^8.1.1: dependencies: has-flag "^4.0.0" -supports-color@^9.2.1: +supports-color@^9.2.2: version "9.2.2" resolved "https://registry.npmjs.org/supports-color/-/supports-color-9.2.2.tgz#502acaf82f2b7ee78eb7c83dcac0f89694e5a7bb" integrity sha512-XC6g/Kgux+rJXmwokjm9ECpD6k/smUoS5LKlUCcsYr4IY3rW0XyAympon2RmxGrlnZURMpg5T18gWDP9CsHXFA== From a69690f4009061bf59342af19feb59e76e0ae322 Mon Sep 17 00:00:00 2001 From: Jack Palmer Date: Fri, 27 May 2022 16:15:07 +0100 Subject: [PATCH 142/149] fix: HeaderTabs Firefox overflow scroll bug with min-width: 0 Signed-off-by: Jack Palmer --- packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx index bee4bdfce2..0a937683a5 100644 --- a/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx +++ b/packages/core-components/src/layout/HeaderTabs/HeaderTabs.tsx @@ -35,6 +35,7 @@ const useStyles = makeStyles( gridArea: 'pageSubheader', backgroundColor: theme.palette.background.paper, paddingLeft: theme.spacing(3), + minWidth: 0, }, defaultTab: { padding: theme.spacing(3, 3), From feb4e8de0733ec0d5f8e088b6b7cc7ccda01c15b Mon Sep 17 00:00:00 2001 From: Jack Palmer Date: Mon, 30 May 2022 14:56:39 +0100 Subject: [PATCH 143/149] Add changeset Signed-off-by: Jack Palmer --- .changeset/lovely-gifts-itch.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/lovely-gifts-itch.md diff --git a/.changeset/lovely-gifts-itch.md b/.changeset/lovely-gifts-itch.md new file mode 100644 index 0000000000..3fd1f5a38f --- /dev/null +++ b/.changeset/lovely-gifts-itch.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-components': patch +--- + +Fix EntityPage tab scrolling overflow bug on Firefox From 0545d44a79639285f938a1dec9086646c195ac9d Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 30 May 2022 14:04:44 +0000 Subject: [PATCH 144/149] fix(deps): update dependency @google-cloud/storage to v5.20.5 Signed-off-by: Renovate Bot --- yarn.lock | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 89ac81d70d..252533f1bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2126,9 +2126,9 @@ integrity sha512-d4VSA86eL/AFTe5xtyZX+ePUjE8dIFu2T8zmdeNBSa5/kNgXPCx/o/wbFNHAGLJdGnk1vddRuMESD9HbOC8irw== "@google-cloud/storage@^5.6.0", "@google-cloud/storage@^5.8.0": - version "5.20.4" - resolved "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.20.4.tgz#9f5eaab85bbbc8656e660cc4fb2056443a71dcc9" - integrity sha512-FAquqI1imd6Nq1ifdismGenYzC65DENEzIFAq4uwZMulxh32RvdzWdzx28fKzUDsoAkEfBpXJuptC31hLaHZYg== + version "5.20.5" + resolved "https://registry.npmjs.org/@google-cloud/storage/-/storage-5.20.5.tgz#1de71fc88d37934a886bc815722c134b162d335d" + integrity sha512-lOs/dCyveVF8TkVFnFSF7IGd0CJrTm91qiK6JLu+Z8qiT+7Ag0RyVhxZIWkhiACqwABo7kSHDm8FdH8p2wxSSw== dependencies: "@google-cloud/paginator" "^3.0.7" "@google-cloud/projectify" "^2.0.0" @@ -2151,6 +2151,7 @@ retry-request "^4.2.2" stream-events "^1.0.4" teeny-request "^7.1.3" + uuid "^8.0.0" xdg-basedir "^4.0.0" "@graphiql/toolkit@^0.4.5": From 3ac8c7723a0719c8c4c562be85e998367a168239 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 30 May 2022 15:22:06 +0000 Subject: [PATCH 145/149] fix(deps): update dependency @roadiehq/backstage-plugin-buildkite to v2.0.4 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 66125687b0..e67453105d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4908,9 +4908,9 @@ integrity sha512-8UiDeDbjCImFSfOegGu13otQ7OdP9FOYpcLjeouppnhs+MPeIEAtYS+jCcBKmi3reyTagC15/KVSRhde1wS1vg== "@roadiehq/backstage-plugin-buildkite@^2.0.0": - version "2.0.3" - resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-2.0.3.tgz#73f3586c176c4c2ffe55af78ee8ce86c3c363dc6" - integrity sha512-Jna1m/pj52G7qz0PKmldQUXHsw5IUTxtgxVh2n1X2CTq+o10xySVspmEyEmLdNxkeQdYO+a280fYWjEKo9xoyQ== + version "2.0.4" + resolved "https://registry.npmjs.org/@roadiehq/backstage-plugin-buildkite/-/backstage-plugin-buildkite-2.0.4.tgz#cd72fd35a9f8b6bb1c2ccd3c13d2aece2ecaf86e" + integrity sha512-bOrqKO9MmRB5jgue+S8WEmJk83h6g4EdGp2qmirVODQddL03gtFNgMTauQkKAV7T8N+C948xDcgdL3pjvLYJdA== dependencies: "@backstage/catalog-model" "^1.0.0" "@backstage/core-components" "^0.9.0" From aaeb0662521023728d9be0e27bf84e3ec71c339e Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 30 May 2022 16:12:18 +0000 Subject: [PATCH 146/149] fix(deps): update dependency @yarnpkg/parsers to v3.0.0-rc.6 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index e67453105d..3ee9833781 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7201,9 +7201,9 @@ integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== "@yarnpkg/parsers@^3.0.0-rc.4": - version "3.0.0-rc.4" - resolved "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.4.tgz#d7b19fa22ce7ff2423e5cbf008f7be0a85e9f1e3" - integrity sha512-ScXXCUwGdx+aEIP20U8VEGtuXmxcMPFdJTb9G9a8MRpQPxIkky/GYXEL5Hf4oqJJXGyCv/DN31zfmxyj31SLKw== + version "3.0.0-rc.6" + resolved "https://registry.npmjs.org/@yarnpkg/parsers/-/parsers-3.0.0-rc.6.tgz#3c93267fdae4470e4eaaf8c5f81d0d00ea177f76" + integrity sha512-YqtJ9VQqQixZsJJS4X83e6RMpgK1jmQJSIrCfd1wO3i/7vPk9QoLvvZS4bwZ2ha8QWqWlO/alAcXCGBezEI1Ig== dependencies: js-yaml "^3.10.0" tslib "^1.13.0" From 863e14d466930144785ba1f6bb6b2e66477e3eda Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 30 May 2022 17:05:41 +0000 Subject: [PATCH 147/149] fix(deps): update dependency core-js to v3.22.7 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3ee9833781..dd85b7ff0e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9885,9 +9885,9 @@ core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.10: integrity sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ== core-js@^3.4.1, core-js@^3.6.5: - version "3.22.5" - resolved "https://registry.npmjs.org/core-js/-/core-js-3.22.5.tgz#a5f5a58e663d5c0ebb4e680cd7be37536fb2a9cf" - integrity sha512-VP/xYuvJ0MJWRAobcmQ8F2H6Bsn+s7zqAAjFaHGBMc5AQm7zaelhD1LGduFn2EehEcQcU+br6t+fwbpQ5d1ZWA== + version "3.22.7" + resolved "https://registry.npmjs.org/core-js/-/core-js-3.22.7.tgz#8d6c37f630f6139b8732d10f2c114c3f1d00024f" + integrity sha512-Jt8SReuDKVNZnZEzyEQT5eK6T2RRCXkfTq7Lo09kpm+fHjgGewSbNjV+Wt4yZMhPDdzz2x1ulI5z/w4nxpBseg== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" From 566e26668347a8d13ccdfaafd31653561b546fbc Mon Sep 17 00:00:00 2001 From: Eric Peterson Date: Mon, 30 May 2022 19:19:28 +0200 Subject: [PATCH 148/149] Consume legacy context in hook too, just in case. Signed-off-by: Eric Peterson --- .../Sidebar/SidebarOpenStateContext.test.tsx | 20 +++++++++++++++++ .../Sidebar/SidebarOpenStateContext.tsx | 20 +++++++++-------- .../Sidebar/SidebarPinStateContext.test.tsx | 22 +++++++++++++++++++ .../layout/Sidebar/SidebarPinStateContext.tsx | 16 ++++++++------ 4 files changed, 62 insertions(+), 16 deletions(-) diff --git a/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.test.tsx b/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.test.tsx index 4953e1341b..f1900f025c 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.test.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.test.tsx @@ -56,6 +56,26 @@ describe('SidebarOpenStateContext', () => { }); describe('useSidebarOpenState', () => { + it('can be invoked within legacy context', () => { + const wrapper = ({ children }: { children: ReactNode }) => ( + {}, + }} + > + {children} + + ); + + const { result } = renderHook(() => useSidebarOpenState(), { + wrapper, + }); + + expect(result.current.isOpen).toBe(true); + expect(typeof result.current.setOpen).toBe('function'); + }); + it('does not need to be invoked within provider', () => { const { result } = renderHook(() => useSidebarOpenState()); expect(result.current.isOpen).toBe(false); diff --git a/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.tsx b/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.tsx index be15785f29..635306959a 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarOpenStateContext.tsx @@ -50,7 +50,7 @@ export type SidebarOpenState = { setOpen: (open: boolean) => void; }; -const defaultSidebarContext = { +const defaultSidebarOpenStateContext = { isOpen: false, setOpen: () => {}, }; @@ -62,7 +62,7 @@ const defaultSidebarContext = { * Use `` + `useSidebar()` instead. */ export const LegacySidebarContext = createContext( - defaultSidebarContext, + defaultSidebarOpenStateContext, ); const VersionedSidebarContext = createVersionedContext<{ @@ -97,17 +97,19 @@ export const SidebarOpenStateProvider = ({ * @public */ export const useSidebarOpenState = (): SidebarOpenState => { - const versionedSidebarContext = useContext(VersionedSidebarContext); + const versionedOpenStateContext = useContext(VersionedSidebarContext); + const legacyOpenStateContext = useContext(LegacySidebarContext); - // Invoked from outside a SidebarOpenStateProvider, return a default value. - if (versionedSidebarContext === undefined) { - return defaultSidebarContext; + // Invoked from outside a SidebarOpenStateProvider: check for the legacy + // context's value, but otherwise return the default. + if (versionedOpenStateContext === undefined) { + return legacyOpenStateContext || defaultSidebarOpenStateContext; } - const sidebarContext = versionedSidebarContext.atVersion(1); - if (sidebarContext === undefined) { + const openStateContext = versionedOpenStateContext.atVersion(1); + if (openStateContext === undefined) { throw new Error('No context found for version 1.'); } - return sidebarContext; + return openStateContext; }; diff --git a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx index 1a86ec612e..8bd1597725 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.test.tsx @@ -63,6 +63,28 @@ describe('SidebarPinStateContext', () => { }); describe('useSidebarPinState', () => { + it('can be invoked within legacy context', () => { + const wrapper = ({ children }: { children: ReactNode }) => ( + {}, + }} + > + {children} + + ); + + const { result } = renderHook(() => useSidebarPinState(), { + wrapper, + }); + + expect(result.current.isPinned).toBe(true); + expect(result.current.isMobile).toBe(true); + expect(typeof result.current.toggleSidebarPinState).toBe('function'); + }); + it('does not need to be invoked within provider', () => { const { result } = renderHook(() => useSidebarPinState()); expect(result.current.isPinned).toBe(true); diff --git a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx index a85929ab70..e00ed259e9 100644 --- a/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx +++ b/packages/core-components/src/layout/Sidebar/SidebarPinStateContext.tsx @@ -103,17 +103,19 @@ export const SidebarPinStateProvider = ({ * @public */ export const useSidebarPinState = (): SidebarPinState => { - const versionedSidebarContext = useContext(VersionedSidebarPinStateContext); + const versionedPinStateContext = useContext(VersionedSidebarPinStateContext); + const legacyPinStateContext = useContext(LegacySidebarPinStateContext); - // Invoked from outside a SidebarPinStateProvider: default value. - if (versionedSidebarContext === undefined) { - return defaultSidebarPinStateContext; + // Invoked from outside a SidebarPinStateProvider: check for the legacy + // context's value, but otherwise return the default. + if (versionedPinStateContext === undefined) { + return legacyPinStateContext || defaultSidebarPinStateContext; } - const sidebarContext = versionedSidebarContext.atVersion(1); - if (sidebarContext === undefined) { + const pinStateContext = versionedPinStateContext.atVersion(1); + if (pinStateContext === undefined) { throw new Error('No context found for version 1.'); } - return sidebarContext; + return pinStateContext; }; From 09f4c2c2ab12d3a577c5d43fc5207cc053f491a8 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Mon, 30 May 2022 18:02:11 +0000 Subject: [PATCH 149/149] fix(deps): update dependency humanize-duration to v3.27.2 Signed-off-by: Renovate Bot --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index dd85b7ff0e..3b4ccd4b1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14080,9 +14080,9 @@ human-signals@^2.1.0: integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== humanize-duration@^3.25.1, humanize-duration@^3.26.0, humanize-duration@^3.27.0, humanize-duration@^3.27.1: - version "3.27.1" - resolved "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.1.tgz#2cd4ea4b03bd92184aee6d90d77a8f3d7628df69" - integrity sha512-jCVkMl+EaM80rrMrAPl96SGG4NRac53UyI1o/yAzebDntEY6K6/Fj2HOjdPg8omTqIe5Y0wPBai2q5xXrIbarA== + version "3.27.2" + resolved "https://registry.npmjs.org/humanize-duration/-/humanize-duration-3.27.2.tgz#4b4e565bec098d22c9a54344e16156d1c649f160" + integrity sha512-A15OmA3FLFRnehvF4ZMocsxTZYvHq4ze7L+AgR1DeHw0xC9vMd4euInY83uqGU9/XXKNnVIEeKc1R8G8nKqtzg== humanize-ms@^1.2.1: version "1.2.1"