From 308b0eef3c9c860c0d37b94dc07ae419900ebc5b Mon Sep 17 00:00:00 2001 From: David Roberts Date: Wed, 7 Feb 2024 11:43:08 +0000 Subject: [PATCH 01/14] Ability to fetch the README file from a different AZD path, using an annotation on the entity Signed-off-by: David Roberts --- .../azure-devops-backend/src/api/AzureDevOpsApi.ts | 3 ++- plugins/azure-devops-backend/src/service/router.ts | 2 ++ plugins/azure-devops-common/src/constants.ts | 2 ++ plugins/azure-devops-common/src/types.ts | 1 + plugins/azure-devops/src/api/AzureDevOpsClient.ts | 3 +++ plugins/azure-devops/src/hooks/useReadme.ts | 11 +++++++++-- .../src/utils/getAnnotationValuesFromEntity.ts | 7 +++++++ 7 files changed, 26 insertions(+), 3 deletions(-) diff --git a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts index d50888006b..cc5cad92b7 100644 --- a/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts +++ b/plugins/azure-devops-backend/src/api/AzureDevOpsApi.ts @@ -527,11 +527,12 @@ export class AzureDevOpsApi { org: string, project: string, repo: string, + path: string, ): Promise<{ url: string; content: string; }> { - const url = buildEncodedUrl(host, org, project, repo, 'README.md'); + const url = buildEncodedUrl(host, org, project, repo, path); const response = await this.urlReader.readUrl(url); const buffer = await response.buffer(); const content = await replaceReadme( diff --git a/plugins/azure-devops-backend/src/service/router.ts b/plugins/azure-devops-backend/src/service/router.ts index 9a71c8f03d..a00b0ce910 100644 --- a/plugins/azure-devops-backend/src/service/router.ts +++ b/plugins/azure-devops-backend/src/service/router.ts @@ -216,12 +216,14 @@ export async function createRouter( req.query.host?.toString() ?? config.getString('azureDevOps.host'); const org = req.query.org?.toString() ?? config.getString('azureDevOps.organization'); + const path = req.query.path?.toString() ?? 'README.md'; const { projectName, repoName } = req.params; const readme = await azureDevOpsApi.getReadme( host, org, projectName, repoName, + path, ); res.status(200).json(readme); }); diff --git a/plugins/azure-devops-common/src/constants.ts b/plugins/azure-devops-common/src/constants.ts index 5cc6b54592..05eb430cc9 100644 --- a/plugins/azure-devops-common/src/constants.ts +++ b/plugins/azure-devops-common/src/constants.ts @@ -22,6 +22,8 @@ export const AZURE_DEVOPS_HOST_ORG_ANNOTATION = 'dev.azure.com/host-org'; /** @public */ export const AZURE_DEVOPS_PROJECT_ANNOTATION = 'dev.azure.com/project'; /** @public */ +export const AZURE_DEVOPS_README_ANNOTATION = 'dev.azure.com/readme-path'; +/** @public */ export const AZURE_DEVOPS_REPO_ANNOTATION = 'dev.azure.com/project-repo'; /** @public */ export const AZURE_DEVOPS_DEFAULT_TOP: number = 10; diff --git a/plugins/azure-devops-common/src/types.ts b/plugins/azure-devops-common/src/types.ts index afb6083867..0390bb1f6e 100644 --- a/plugins/azure-devops-common/src/types.ts +++ b/plugins/azure-devops-common/src/types.ts @@ -212,6 +212,7 @@ export interface ReadmeConfig { repo: string; host?: string; org?: string; + path?: string; } /** @public */ diff --git a/plugins/azure-devops/src/api/AzureDevOpsClient.ts b/plugins/azure-devops/src/api/AzureDevOpsClient.ts index 76e38b064c..a10b10ff14 100644 --- a/plugins/azure-devops/src/api/AzureDevOpsClient.ts +++ b/plugins/azure-devops/src/api/AzureDevOpsClient.ts @@ -189,6 +189,9 @@ export class AzureDevOpsClient implements AzureDevOpsApi { if (opts.org) { queryString.append('org', opts.org); } + if (opts.path) { + queryString.append('path', opts.path); + } return await this.get( `readme/${encodeURIComponent(opts.project)}/${encodeURIComponent( opts.repo, diff --git a/plugins/azure-devops/src/hooks/useReadme.ts b/plugins/azure-devops/src/hooks/useReadme.ts index 348b4219f0..9aa376e0bd 100644 --- a/plugins/azure-devops/src/hooks/useReadme.ts +++ b/plugins/azure-devops/src/hooks/useReadme.ts @@ -30,8 +30,15 @@ export function useReadme(entity: Entity): { const api = useApi(azureDevOpsApiRef); const { value, loading, error } = useAsync(() => { - const { project, repo, host, org } = getAnnotationValuesFromEntity(entity); - return api.getReadme({ project, repo: repo as string, host, org }); + const { project, repo, host, org, readmePath } = + getAnnotationValuesFromEntity(entity); + return api.getReadme({ + project, + repo: repo as string, + host, + org, + path: readmePath, + }); }, [api]); return { diff --git a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts index 7533c473ed..fb38a531e1 100644 --- a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts +++ b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts @@ -18,6 +18,7 @@ import { Entity } from '@backstage/catalog-model'; import { AZURE_DEVOPS_PROJECT_ANNOTATION, AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION, + AZURE_DEVOPS_README_ANNOTATION, AZURE_DEVOPS_REPO_ANNOTATION, AZURE_DEVOPS_HOST_ORG_ANNOTATION, } from '@backstage/plugin-azure-devops-common'; @@ -28,6 +29,7 @@ export function getAnnotationValuesFromEntity(entity: Entity): { definition?: string; host?: string; org?: string; + readmePath?: string; } { const hostOrg = getHostOrg(entity.metadata.annotations); const projectRepo = getProjectRepo(entity.metadata.annotations); @@ -35,12 +37,15 @@ export function getAnnotationValuesFromEntity(entity: Entity): { entity.metadata.annotations?.[AZURE_DEVOPS_PROJECT_ANNOTATION]; const definition = entity.metadata.annotations?.[AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION]; + const readmePath = + entity.metadata.annotations?.[AZURE_DEVOPS_README_ANNOTATION]; if (definition) { if (project) { return { project, definition, + readmePath: readmePath, ...hostOrg, }; } @@ -49,6 +54,7 @@ export function getAnnotationValuesFromEntity(entity: Entity): { project: projectRepo.project, repo: projectRepo.repo, definition, + readmePath: readmePath, ...hostOrg, }; } @@ -60,6 +66,7 @@ export function getAnnotationValuesFromEntity(entity: Entity): { return { project: projectRepo.project, repo: projectRepo.repo, + readmePath: readmePath, ...hostOrg, }; } From 9fdb86a91f5d24ad9e12b756dcf53eb614c8a7a4 Mon Sep 17 00:00:00 2001 From: David Roberts Date: Wed, 7 Feb 2024 11:56:28 +0000 Subject: [PATCH 02/14] Document the changes Signed-off-by: David Roberts --- .changeset/itchy-news-drive.md | 15 +++++++++++++++ plugins/azure-devops/README.md | 10 +++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 .changeset/itchy-news-drive.md diff --git a/.changeset/itchy-news-drive.md b/.changeset/itchy-news-drive.md new file mode 100644 index 0000000000..b5bcdaec73 --- /dev/null +++ b/.changeset/itchy-news-drive.md @@ -0,0 +1,15 @@ +--- +'@backstage/plugin-azure-devops-backend': minor +'@backstage/plugin-azure-devops-common': minor +'@backstage/plugin-azure-devops': minor +--- + +Ability to fetch the README file from a different AZD path. + +Defaults to the current, AZD default behaviour (`README.md` in the root of the git repo); to use a different path, add the annotation `dev.azure.com/readme-path` + +Example: + +```yaml +dev.azure.com/readme-path: /my-path/CHANGELOG.md +``` diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md index f897588987..c1191b1566 100644 --- a/plugins/azure-devops/README.md +++ b/plugins/azure-devops/README.md @@ -63,13 +63,21 @@ spec: #### Mono repos -If you have multiple entities within a single repo, you will need to specify which pipelines belong to each entity. +If you have multiple entities within a single repo, you will need to specify which pipelines belong to each entity: ```yaml dev.azure.com/project-repo: / dev.azure.com/build-definition: ``` +...and which README file belongs to each entity. + +Example: + +```yaml +dev.azure.com/readme-path: //.md +``` + #### Pipeline in different project to repo If your pipeline is in a different project to the source code, you will need to specify this in the project annotation. From 0081922e4710095ca156606a0c7f4cb6db3c410b Mon Sep 17 00:00:00 2001 From: David Roberts Date: Wed, 7 Feb 2024 12:11:16 +0000 Subject: [PATCH 03/14] Update the API reports Signed-off-by: David Roberts --- plugins/azure-devops-backend/api-report.md | 1 + plugins/azure-devops-common/api-report.md | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/plugins/azure-devops-backend/api-report.md b/plugins/azure-devops-backend/api-report.md index 6dead7f11b..d98d178cff 100644 --- a/plugins/azure-devops-backend/api-report.md +++ b/plugins/azure-devops-backend/api-report.md @@ -124,6 +124,7 @@ export class AzureDevOpsApi { org: string, project: string, repo: string, + path: string, ): Promise<{ url: string; content: string; diff --git a/plugins/azure-devops-common/api-report.md b/plugins/azure-devops-common/api-report.md index 5bd1674fc8..e8343178e3 100644 --- a/plugins/azure-devops-common/api-report.md +++ b/plugins/azure-devops-common/api-report.md @@ -16,6 +16,9 @@ export const AZURE_DEVOPS_HOST_ORG_ANNOTATION = 'dev.azure.com/host-org'; // @public (undocumented) export const AZURE_DEVOPS_PROJECT_ANNOTATION = 'dev.azure.com/project'; +// @public (undocumented) +export const AZURE_DEVOPS_README_ANNOTATION = 'dev.azure.com/readme-path'; + // @public (undocumented) export const AZURE_DEVOPS_REPO_ANNOTATION = 'dev.azure.com/project-repo'; @@ -228,6 +231,8 @@ export interface ReadmeConfig { // (undocumented) org?: string; // (undocumented) + path?: string; + // (undocumented) project: string; // (undocumented) repo: string; From ded6e4df61f1b6b367248d15417f039321409cbb Mon Sep 17 00:00:00 2001 From: David Roberts Date: Wed, 7 Feb 2024 13:39:01 +0000 Subject: [PATCH 04/14] Update the tests to reflect the new parameter and default Signed-off-by: David Roberts --- .../src/service/router.test.ts | 31 +++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/plugins/azure-devops-backend/src/service/router.test.ts b/plugins/azure-devops-backend/src/service/router.test.ts index 65cb27271e..9e8f968826 100644 --- a/plugins/azure-devops-backend/src/service/router.test.ts +++ b/plugins/azure-devops-backend/src/service/router.test.ts @@ -482,7 +482,7 @@ describe('createRouter', () => { }); describe('GET /readme/:projectName/:repoName', () => { - it('fetches readme file', async () => { + it('fetches default readme file', async () => { const content = getReadmeMock(); const url = `https://host.com/myOrg/myProject/_git/myRepo?path=README.md`; @@ -491,14 +491,41 @@ describe('createRouter', () => { url, }); + const response = await request(app).get('/readme/myProject/myRepo'); + expect(azureDevOpsApi.getReadme).toHaveBeenCalledWith( + 'host.com', + 'myOrg', + 'myProject', + 'myRepo', + 'README.md', + ); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + content, + url, + }); + }); + }); + + describe('GET /readme/:projectName/:repoName with readme path', () => { + it('fetches specified readme file', async () => { + const content = getReadmeMock(); + const url = `https://host.com/myOrg/myProject/_git/myRepo?path=README_NOT_DEFAULT.md`; + + azureDevOpsApi.getReadme.mockResolvedValueOnce({ + content, + url, + }); + const response = await request(app).get( - '/readme/myProject/myRepo?path=README.md', + '/readme/myProject/myRepo?path=README_NOT_DEFAULT.md', ); expect(azureDevOpsApi.getReadme).toHaveBeenCalledWith( 'host.com', 'myOrg', 'myProject', 'myRepo', + 'README_NOT_DEFAULT.md', ); expect(response.status).toEqual(200); expect(response.body).toEqual({ From d294557f5ec2727e3485fb2f4936e1bd312bade8 Mon Sep 17 00:00:00 2001 From: David Roberts <61826520+DavidRobertsOrbis@users.noreply.github.com> Date: Mon, 12 Feb 2024 11:27:53 +0000 Subject: [PATCH 05/14] Better example annotation value Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Signed-off-by: David Roberts <61826520+DavidRobertsOrbis@users.noreply.github.com> --- .changeset/itchy-news-drive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/itchy-news-drive.md b/.changeset/itchy-news-drive.md index b5bcdaec73..4cabc647f0 100644 --- a/.changeset/itchy-news-drive.md +++ b/.changeset/itchy-news-drive.md @@ -11,5 +11,5 @@ Defaults to the current, AZD default behaviour (`README.md` in the root of the g Example: ```yaml -dev.azure.com/readme-path: /my-path/CHANGELOG.md +dev.azure.com/readme-path: /my-path/README.md ``` From ad528cc4957f45eab617c7f551efe57a83019ff9 Mon Sep 17 00:00:00 2001 From: David Roberts <61826520+DavidRobertsOrbis@users.noreply.github.com> Date: Mon, 12 Feb 2024 11:28:39 +0000 Subject: [PATCH 06/14] Better instructions for implementation Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Signed-off-by: David Roberts <61826520+DavidRobertsOrbis@users.noreply.github.com> --- plugins/azure-devops/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md index c1191b1566..6521f0e237 100644 --- a/plugins/azure-devops/README.md +++ b/plugins/azure-devops/README.md @@ -63,7 +63,7 @@ spec: #### Mono repos -If you have multiple entities within a single repo, you will need to specify which pipelines belong to each entity: +If you have multiple entities within a single repo, you will need to specify which pipelines belong to each entity, like this: ```yaml dev.azure.com/project-repo: / From b0002dfca3ba023ab0753e9fc3804ca93826e4d1 Mon Sep 17 00:00:00 2001 From: David Roberts <61826520+DavidRobertsOrbis@users.noreply.github.com> Date: Mon, 12 Feb 2024 13:22:53 +0000 Subject: [PATCH 07/14] Replace acronym AZD with the full name Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Signed-off-by: David Roberts <61826520+DavidRobertsOrbis@users.noreply.github.com> --- .changeset/itchy-news-drive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/itchy-news-drive.md b/.changeset/itchy-news-drive.md index 4cabc647f0..44963ec773 100644 --- a/.changeset/itchy-news-drive.md +++ b/.changeset/itchy-news-drive.md @@ -6,7 +6,7 @@ Ability to fetch the README file from a different AZD path. -Defaults to the current, AZD default behaviour (`README.md` in the root of the git repo); to use a different path, add the annotation `dev.azure.com/readme-path` +Defaults to the current, Azure DevOps default behaviour (`README.md` in the root of the git repo); to use a different path, add the annotation `dev.azure.com/readme-path` Example: From fed13da396381f3678becf864ee5d6f8ed781c3b Mon Sep 17 00:00:00 2001 From: David Roberts <61826520+DavidRobertsOrbis@users.noreply.github.com> Date: Mon, 12 Feb 2024 13:23:05 +0000 Subject: [PATCH 08/14] Replace acronym AZD with the full name Co-authored-by: Andre Wanlin <67169551+awanlin@users.noreply.github.com> Signed-off-by: David Roberts <61826520+DavidRobertsOrbis@users.noreply.github.com> --- .changeset/itchy-news-drive.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/itchy-news-drive.md b/.changeset/itchy-news-drive.md index 44963ec773..97cca5b155 100644 --- a/.changeset/itchy-news-drive.md +++ b/.changeset/itchy-news-drive.md @@ -4,7 +4,7 @@ '@backstage/plugin-azure-devops': minor --- -Ability to fetch the README file from a different AZD path. +Ability to fetch the README file from a different Azure DevOps path. Defaults to the current, Azure DevOps default behaviour (`README.md` in the root of the git repo); to use a different path, add the annotation `dev.azure.com/readme-path` From d8294546d8edca6f4c87d2027510d4bfc71ccdb6 Mon Sep 17 00:00:00 2001 From: David Roberts Date: Mon, 12 Feb 2024 15:51:32 +0000 Subject: [PATCH 09/14] add a test for subfolder as well as filename Signed-off-by: David Roberts --- .../src/service/router.test.ts | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/plugins/azure-devops-backend/src/service/router.test.ts b/plugins/azure-devops-backend/src/service/router.test.ts index 9e8f968826..8793860708 100644 --- a/plugins/azure-devops-backend/src/service/router.test.ts +++ b/plugins/azure-devops-backend/src/service/router.test.ts @@ -482,7 +482,7 @@ describe('createRouter', () => { }); describe('GET /readme/:projectName/:repoName', () => { - it('fetches default readme file', async () => { + it('fetches default default readme file', async () => { const content = getReadmeMock(); const url = `https://host.com/myOrg/myProject/_git/myRepo?path=README.md`; @@ -507,7 +507,7 @@ describe('createRouter', () => { }); }); - describe('GET /readme/:projectName/:repoName with readme path', () => { + describe('GET /readme/:projectName/:repoName with readme filename', () => { it('fetches specified readme file', async () => { const content = getReadmeMock(); const url = `https://host.com/myOrg/myProject/_git/myRepo?path=README_NOT_DEFAULT.md`; @@ -534,6 +534,34 @@ describe('createRouter', () => { }); }); }); + + describe('GET /readme/:projectName/:repoName with readme path', () => { + it('fetches specified readme file from subfolder', async () => { + const content = getReadmeMock(); + const url = `https://host.com/myOrg/myProject/_git/myRepo?path=/my-path/README.md`; + + azureDevOpsApi.getReadme.mockResolvedValueOnce({ + content, + url, + }); + + const response = await request(app).get( + '/readme/myProject/myRepo?path=/my-path/README.md', + ); + expect(azureDevOpsApi.getReadme).toHaveBeenCalledWith( + 'host.com', + 'myOrg', + 'myProject', + 'myRepo', + '/my-path/README.md', + ); + expect(response.status).toEqual(200); + expect(response.body).toEqual({ + content, + url, + }); + }); + }); }); function getReadmeMock() { From 5bda681a1cc8ee5dd24753c1a5450af1073106b7 Mon Sep 17 00:00:00 2001 From: David Roberts Date: Mon, 12 Feb 2024 16:13:24 +0000 Subject: [PATCH 10/14] add tests for readme extraction Signed-off-by: David Roberts --- .../getAnnotationValuesFromEntity.test.ts | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts index 9325a79039..e196e9dfc7 100644 --- a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts +++ b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts @@ -52,6 +52,7 @@ describe('getAnnotationValuesFromEntity', () => { project: 'projectName', repo: 'repoName', definition: undefined, + readmePath: undefined, host: undefined, org: undefined, }); @@ -149,6 +150,7 @@ describe('getAnnotationValuesFromEntity', () => { project: 'projectName', repo: undefined, definition: 'buildDefinitionName', + readmePath: undefined, host: undefined, org: undefined, }); @@ -220,6 +222,7 @@ describe('getAnnotationValuesFromEntity', () => { project: 'projectName', repo: 'repoName', definition: undefined, + readmePath: undefined, host: 'hostName', org: 'organizationName', }); @@ -246,6 +249,7 @@ describe('getAnnotationValuesFromEntity', () => { project: 'projectName', repo: undefined, definition: 'buildDefinitionName', + readmePath: undefined, host: 'hostName', org: 'organizationName', }); @@ -344,6 +348,7 @@ describe('getAnnotationValuesFromEntity', () => { project: 'projectName', repo: 'repoName', definition: undefined, + readmePath: undefined, host: 'company.com/tfs', org: 'organizationName', }); @@ -417,6 +422,7 @@ describe('getAnnotationValuesFromEntity', () => { project: 'projectName', repo: 'repoName', definition: 'buildDefinitionName', + readmePath: undefined, host: undefined, org: undefined, }); @@ -443,6 +449,87 @@ describe('getAnnotationValuesFromEntity', () => { project: 'projectName', repo: undefined, definition: 'buildDefinitionName', + readmePath: undefined, + host: undefined, + org: undefined, + }); + }); + }); + + describe('definition, project and readme', () => { + it('returns with the readme path', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'project-repo', + annotations: { + 'dev.azure.com/project': 'projectName', + 'dev.azure.com/build-definition': 'buildDefinitionName', + 'dev.azure.com/readme-path': 'readme/path.md', + }, + }, + }; + const values = getAnnotationValuesFromEntity(entity); + expect(values).toEqual({ + project: 'projectName', + repo: undefined, + definition: 'buildDefinitionName', + readmePath: 'readme/path.md', + host: undefined, + org: undefined, + }); + }); + }); + + describe('definition, projectRepo and readme', () => { + it('returns with the readme path', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'project-repo', + annotations: { + 'dev.azure.com/project-repo': 'projectName/repoName', + 'dev.azure.com/build-definition': 'buildDefinitionName', + 'dev.azure.com/readme-path': 'readme/path.md', + }, + }, + }; + const values = getAnnotationValuesFromEntity(entity); + expect(values).toEqual({ + project: 'projectName', + repo: 'repoName', + definition: 'buildDefinitionName', + readmePath: 'readme/path.md', + host: undefined, + org: undefined, + }); + }); + }); + + describe('projectRepo and readme', () => { + it('returns with the readme path', () => { + const entity: Entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + namespace: 'default', + name: 'project-repo', + annotations: { + 'dev.azure.com/project-repo': 'projectName/repoName', + 'dev.azure.com/readme-path': 'readme/path.md', + }, + }, + }; + const values = getAnnotationValuesFromEntity(entity); + expect(values).toEqual({ + project: 'projectName', + repo: 'repoName', + definition: undefined, + readmePath: 'readme/path.md', host: undefined, org: undefined, }); From a29f6833bb82476641d54af959fe599f92531b1f Mon Sep 17 00:00:00 2001 From: David Roberts Date: Mon, 12 Feb 2024 16:31:36 +0000 Subject: [PATCH 11/14] explicit rejection of bad parameter types Signed-off-by: David Roberts --- .../azure-devops-backend/src/service/router.test.ts | 10 ++++++++++ plugins/azure-devops-backend/src/service/router.ts | 13 ++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/plugins/azure-devops-backend/src/service/router.test.ts b/plugins/azure-devops-backend/src/service/router.test.ts index 8793860708..7bf558612f 100644 --- a/plugins/azure-devops-backend/src/service/router.test.ts +++ b/plugins/azure-devops-backend/src/service/router.test.ts @@ -562,6 +562,16 @@ describe('createRouter', () => { }); }); }); + + describe('GET /readme/:projectName/:repoName with a bad readme path (multiple values)', () => { + it('throws InputError', async () => { + const response = await request(app).get( + '/readme/myProject/myRepo?path=1&path=2', + ); + expect(azureDevOpsApi.getReadme).not.toHaveBeenCalled(); + expect(response.status).toEqual(400); + }); + }); }); function getReadmeMock() { diff --git a/plugins/azure-devops-backend/src/service/router.ts b/plugins/azure-devops-backend/src/service/router.ts index a00b0ce910..230ff08c8b 100644 --- a/plugins/azure-devops-backend/src/service/router.ts +++ b/plugins/azure-devops-backend/src/service/router.ts @@ -26,6 +26,7 @@ import { Logger } from 'winston'; import { PullRequestsDashboardProvider } from '../api/PullRequestsDashboardProvider'; import Router from 'express-promise-router'; import { errorHandler, UrlReader } from '@backstage/backend-common'; +import { InputError } from '@backstage/errors'; import express from 'express'; const DEFAULT_TOP = 10; @@ -216,7 +217,17 @@ export async function createRouter( req.query.host?.toString() ?? config.getString('azureDevOps.host'); const org = req.query.org?.toString() ?? config.getString('azureDevOps.organization'); - const path = req.query.path?.toString() ?? 'README.md'; + let path = req.query.path; + + if (path === undefined) { + // if the annotation is missing, default to the previous behaviour (look for README.md in the root of the repo) + path = 'README.md'; + } + + if (typeof path !== 'string') { + throw new InputError('Invalid path param'); + } + const { projectName, repoName } = req.params; const readme = await azureDevOpsApi.getReadme( host, From 184c8877c99a4d2338e82cd1be8bed72eb9c4784 Mon Sep 17 00:00:00 2001 From: David Roberts Date: Mon, 12 Feb 2024 16:49:47 +0000 Subject: [PATCH 12/14] throw if we are passed the empty string - we know this won't work Signed-off-by: David Roberts --- plugins/azure-devops-backend/src/service/router.test.ts | 8 ++++++++ plugins/azure-devops-backend/src/service/router.ts | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/plugins/azure-devops-backend/src/service/router.test.ts b/plugins/azure-devops-backend/src/service/router.test.ts index 7bf558612f..c12b13a5aa 100644 --- a/plugins/azure-devops-backend/src/service/router.test.ts +++ b/plugins/azure-devops-backend/src/service/router.test.ts @@ -572,6 +572,14 @@ describe('createRouter', () => { expect(response.status).toEqual(400); }); }); + + describe('GET /readme/:projectName/:repoName with a bad readme path (empty string)', () => { + it('throws InputError', async () => { + const response = await request(app).get('/readme/myProject/myRepo?path='); + expect(azureDevOpsApi.getReadme).not.toHaveBeenCalled(); + expect(response.status).toEqual(400); + }); + }); }); function getReadmeMock() { diff --git a/plugins/azure-devops-backend/src/service/router.ts b/plugins/azure-devops-backend/src/service/router.ts index 230ff08c8b..bc13a4eee2 100644 --- a/plugins/azure-devops-backend/src/service/router.ts +++ b/plugins/azure-devops-backend/src/service/router.ts @@ -228,6 +228,10 @@ export async function createRouter( throw new InputError('Invalid path param'); } + if (path === '') { + throw new InputError('If present, the path param should not be empty'); + } + const { projectName, repoName } = req.params; const readme = await azureDevOpsApi.getReadme( host, From 1f60f53d247094475a3f73df25e7da5386109643 Mon Sep 17 00:00:00 2001 From: David Roberts Date: Mon, 12 Feb 2024 17:32:08 +0000 Subject: [PATCH 13/14] add explicit dependency on the errors package Signed-off-by: David Roberts --- plugins/azure-devops-backend/package.json | 1 + yarn.lock | 1 + 2 files changed, 2 insertions(+) diff --git a/plugins/azure-devops-backend/package.json b/plugins/azure-devops-backend/package.json index 41b1839ce4..22889db6a1 100644 --- a/plugins/azure-devops-backend/package.json +++ b/plugins/azure-devops-backend/package.json @@ -32,6 +32,7 @@ "@backstage/backend-plugin-api": "workspace:^", "@backstage/catalog-model": "workspace:^", "@backstage/config": "workspace:^", + "@backstage/errors": "workspace:^", "@backstage/integration": "workspace:^", "@backstage/plugin-azure-devops-common": "workspace:^", "@backstage/plugin-catalog-common": "workspace:^", diff --git a/yarn.lock b/yarn.lock index 0e90b16d15..2415b3a4fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5020,6 +5020,7 @@ __metadata: "@backstage/catalog-model": "workspace:^" "@backstage/cli": "workspace:^" "@backstage/config": "workspace:^" + "@backstage/errors": "workspace:^" "@backstage/integration": "workspace:^" "@backstage/plugin-azure-devops-common": "workspace:^" "@backstage/plugin-catalog-common": "workspace:^" From e39eb80da9e7d66c3247503ee7cc2b5ff9c79a92 Mon Sep 17 00:00:00 2001 From: David Roberts Date: Fri, 16 Feb 2024 13:26:34 +0000 Subject: [PATCH 14/14] better docs Signed-off-by: David Roberts --- plugins/azure-devops/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/azure-devops/README.md b/plugins/azure-devops/README.md index 6521f0e237..935e78c82c 100644 --- a/plugins/azure-devops/README.md +++ b/plugins/azure-devops/README.md @@ -70,9 +70,7 @@ dev.azure.com/project-repo: / dev.azure.com/build-definition: ``` -...and which README file belongs to each entity. - -Example: +Then to display the `README` file that belongs to each entity you would do this: ```yaml dev.azure.com/readme-path: //.md