diff --git a/.changeset/poor-seahorses-rush.md b/.changeset/poor-seahorses-rush.md
new file mode 100644
index 0000000000..55313f706d
--- /dev/null
+++ b/.changeset/poor-seahorses-rush.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-azure-devops': patch
+---
+
+Consolidated getting the annotation values into a single function to help with future changes
diff --git a/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx b/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx
index ad278e3fc4..02a61a9a9a 100644
--- a/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx
+++ b/plugins/azure-devops/src/components/EntityPageAzurePipelines/EntityPageAzurePipelines.tsx
@@ -16,14 +16,14 @@
import { BuildTable } from '../BuildTable/BuildTable';
import React from 'react';
-import { getAnnotationFromEntity } from '../../utils/getAnnotationFromEntity';
+import { getAnnotationValuesFromEntity } from '../../utils/getAnnotationValuesFromEntity';
import { useBuildRuns } from '../../hooks/useBuildRuns';
import { useEntity } from '@backstage/plugin-catalog-react';
export const EntityPageAzurePipelines = (props: { defaultLimit?: number }) => {
const { entity } = useEntity();
- const { project, repo, definition } = getAnnotationFromEntity(entity);
+ const { project, repo, definition } = getAnnotationValuesFromEntity(entity);
const { items, loading, error } = useBuildRuns(
project,
diff --git a/plugins/azure-devops/src/components/ReadmeCard/ReadmeCard.tsx b/plugins/azure-devops/src/components/ReadmeCard/ReadmeCard.tsx
index d06726e929..d9afe8db5a 100644
--- a/plugins/azure-devops/src/components/ReadmeCard/ReadmeCard.tsx
+++ b/plugins/azure-devops/src/components/ReadmeCard/ReadmeCard.tsx
@@ -23,11 +23,11 @@ import {
ErrorPanel,
} from '@backstage/core-components';
import { useEntity } from '@backstage/plugin-catalog-react';
-import { useProjectRepoFromEntity } from '../../hooks';
import { useApi } from '@backstage/core-plugin-api';
import React from 'react';
import { azureDevOpsApiRef } from '../../api';
import useAsync from 'react-use/lib/useAsync';
+import { getAnnotationValuesFromEntity } from '../../utils';
const useStyles = makeStyles(theme => ({
readMe: {
@@ -87,16 +87,14 @@ export const ReadmeCard = (props: Props) => {
const classes = useStyles();
const api = useApi(azureDevOpsApiRef);
const { entity } = useEntity();
- const { project, repo } = useProjectRepoFromEntity(entity);
+ const { project, repo } = getAnnotationValuesFromEntity(entity);
- const { loading, error, value } = useAsync(
- () =>
- api.getReadme({
- project,
- repo,
- }),
- [api, project, repo, entity],
- );
+ const { loading, error, value } = useAsync(async () => {
+ if (repo) {
+ return await api.getReadme({ project, repo });
+ }
+ return undefined;
+ }, [api, project, repo, entity]);
if (loading) {
return ;
diff --git a/plugins/azure-devops/src/constants.ts b/plugins/azure-devops/src/constants.ts
index 95e5be4930..765f5a965b 100644
--- a/plugins/azure-devops/src/constants.ts
+++ b/plugins/azure-devops/src/constants.ts
@@ -16,6 +16,8 @@
export const AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION =
'dev.azure.com/build-definition';
+export const AZURE_DEVOPS_ORGANIZATION_ANNOTATION =
+ 'dev.azure.com/organization';
export const AZURE_DEVOPS_PROJECT_ANNOTATION = 'dev.azure.com/project';
export const AZURE_DEVOPS_REPO_ANNOTATION = 'dev.azure.com/project-repo';
export const AZURE_DEVOPS_DEFAULT_TOP: number = 10;
diff --git a/plugins/azure-devops/src/hooks/index.ts b/plugins/azure-devops/src/hooks/index.ts
index a3864c21f7..0b745ba586 100644
--- a/plugins/azure-devops/src/hooks/index.ts
+++ b/plugins/azure-devops/src/hooks/index.ts
@@ -16,7 +16,6 @@
export * from './useAllTeams';
export * from './useDashboardPullRequests';
-export * from './useProjectRepoFromEntity';
export * from './usePullRequests';
export * from './useRepoBuilds';
export * from './useUserEmail';
diff --git a/plugins/azure-devops/src/hooks/useGitTags.ts b/plugins/azure-devops/src/hooks/useGitTags.ts
index b940a7105d..f36f9224aa 100644
--- a/plugins/azure-devops/src/hooks/useGitTags.ts
+++ b/plugins/azure-devops/src/hooks/useGitTags.ts
@@ -20,7 +20,7 @@ import { Entity } from '@backstage/catalog-model';
import { azureDevOpsApiRef } from '../api';
import { useApi } from '@backstage/core-plugin-api';
import useAsync from 'react-use/lib/useAsync';
-import { useProjectRepoFromEntity } from './useProjectRepoFromEntity';
+import { getAnnotationValuesFromEntity } from '../utils';
export function useGitTags(entity: Entity): {
items?: GitTag[];
@@ -28,10 +28,13 @@ export function useGitTags(entity: Entity): {
error?: Error;
} {
const api = useApi(azureDevOpsApiRef);
- const { project, repo } = useProjectRepoFromEntity(entity);
+ const { project, repo } = getAnnotationValuesFromEntity(entity);
- const { value, loading, error } = useAsync(() => {
- return api.getGitTags(project, repo);
+ const { value, loading, error } = useAsync(async () => {
+ if (repo) {
+ return await api.getGitTags(project, repo);
+ }
+ return undefined;
}, [api, project, repo]);
return {
diff --git a/plugins/azure-devops/src/hooks/useProjectRepoFromEntity.ts b/plugins/azure-devops/src/hooks/useProjectRepoFromEntity.ts
deleted file mode 100644
index b484bca009..0000000000
--- a/plugins/azure-devops/src/hooks/useProjectRepoFromEntity.ts
+++ /dev/null
@@ -1,47 +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 { Entity } from '@backstage/catalog-model';
-import { AZURE_DEVOPS_REPO_ANNOTATION } from '../constants';
-
-export function useProjectRepoFromEntity(entity: Entity): {
- project: string;
- repo: string;
-} {
- const [project, repo] = (
- entity.metadata.annotations?.[AZURE_DEVOPS_REPO_ANNOTATION] ?? ''
- ).split('/');
-
- if (!project && !repo) {
- throw new Error(
- 'Value for annotation dev.azure.com/project-repo was not in the correct format: /',
- );
- }
-
- if (!project) {
- throw new Error(
- 'Project Name for annotation dev.azure.com/project-repo was not found; expected format is: /',
- );
- }
-
- if (!repo) {
- throw new Error(
- 'Repo Name for annotation dev.azure.com/project-repo was not found; expected format is: /',
- );
- }
-
- return { project, repo };
-}
diff --git a/plugins/azure-devops/src/hooks/usePullRequests.ts b/plugins/azure-devops/src/hooks/usePullRequests.ts
index 9ddca9650b..c412836ae5 100644
--- a/plugins/azure-devops/src/hooks/usePullRequests.ts
+++ b/plugins/azure-devops/src/hooks/usePullRequests.ts
@@ -25,7 +25,7 @@ import { Entity } from '@backstage/catalog-model';
import { azureDevOpsApiRef } from '../api';
import { useApi } from '@backstage/core-plugin-api';
import useAsync from 'react-use/lib/useAsync';
-import { useProjectRepoFromEntity } from './useProjectRepoFromEntity';
+import { getAnnotationValuesFromEntity } from '../utils';
export function usePullRequests(
entity: Entity,
@@ -44,10 +44,13 @@ export function usePullRequests(
};
const api = useApi(azureDevOpsApiRef);
- const { project, repo } = useProjectRepoFromEntity(entity);
+ const { project, repo } = getAnnotationValuesFromEntity(entity);
- const { value, loading, error } = useAsync(() => {
- return api.getPullRequests(project, repo, options);
+ const { value, loading, error } = useAsync(async () => {
+ if (repo) {
+ return await api.getPullRequests(project, repo, options);
+ }
+ return undefined;
}, [api, project, repo, top, status]);
return {
diff --git a/plugins/azure-devops/src/hooks/useRepoBuilds.ts b/plugins/azure-devops/src/hooks/useRepoBuilds.ts
index e0fe8c2093..4ba88bd5fb 100644
--- a/plugins/azure-devops/src/hooks/useRepoBuilds.ts
+++ b/plugins/azure-devops/src/hooks/useRepoBuilds.ts
@@ -24,7 +24,7 @@ import { Entity } from '@backstage/catalog-model';
import { azureDevOpsApiRef } from '../api';
import { useApi } from '@backstage/core-plugin-api';
import useAsync from 'react-use/lib/useAsync';
-import { useProjectRepoFromEntity } from './useProjectRepoFromEntity';
+import { getAnnotationValuesFromEntity } from '../utils';
export function useRepoBuilds(
entity: Entity,
@@ -40,10 +40,13 @@ export function useRepoBuilds(
};
const api = useApi(azureDevOpsApiRef);
- const { project, repo } = useProjectRepoFromEntity(entity);
+ const { project, repo } = getAnnotationValuesFromEntity(entity);
- const { value, loading, error } = useAsync(() => {
- return api.getRepoBuilds(project, repo, options);
+ const { value, loading, error } = useAsync(async () => {
+ if (repo) {
+ return await api.getRepoBuilds(project, repo, options);
+ }
+ return undefined;
}, [api, project, repo, entity]);
return {
diff --git a/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts
new file mode 100644
index 0000000000..a34dab9db5
--- /dev/null
+++ b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.test.ts
@@ -0,0 +1,230 @@
+/*
+ * 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 { Entity } from '@backstage/catalog-model';
+import { getAnnotationValuesFromEntity } from './getAnnotationValuesFromEntity';
+
+describe('getAnnotationValuesFromEntity', () => {
+ describe('with valid project-repo annotation', () => {
+ it('should return project and repo', () => {
+ const entity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ namespace: 'default',
+ name: 'project-repo',
+ annotations: {
+ 'dev.azure.com/project-repo': 'projectName/repoName',
+ },
+ },
+ };
+ const { project, repo, definition, org } =
+ getAnnotationValuesFromEntity(entity);
+ expect(project).toEqual('projectName');
+ expect(repo).toEqual('repoName');
+ expect(definition).toEqual(undefined);
+ expect(org).toEqual(undefined);
+ });
+ });
+
+ describe('with invalid project-repo annotation', () => {
+ it('should throw incorrect format error', () => {
+ const entity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ namespace: 'default',
+ name: 'project-repo',
+ annotations: {
+ 'dev.azure.com/project-repo': 'project',
+ },
+ },
+ };
+
+ const test = () => {
+ return getAnnotationValuesFromEntity(entity);
+ };
+
+ expect(test).toThrow(
+ 'Value for annotation dev.azure.com/project-repo was not in the correct format: /',
+ );
+ });
+ });
+
+ describe('with project-repo annotation missing project', () => {
+ it('should throw missing project error', () => {
+ const entity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ namespace: 'default',
+ name: 'project-repo',
+ annotations: {
+ 'dev.azure.com/project-repo': '/repo',
+ },
+ },
+ };
+
+ const test = () => {
+ return getAnnotationValuesFromEntity(entity);
+ };
+
+ expect(test).toThrow(
+ 'Project Name for annotation dev.azure.com/project-repo was not found; expected format is: /',
+ );
+ });
+ });
+
+ describe('with project-repo annotation missing repo', () => {
+ it('should throw missing repo error', () => {
+ const entity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ namespace: 'default',
+ name: 'project-repo',
+ annotations: {
+ 'dev.azure.com/project-repo': 'project/',
+ },
+ },
+ };
+
+ const test = () => {
+ return getAnnotationValuesFromEntity(entity);
+ };
+
+ expect(test).toThrow(
+ 'Repo Name for annotation dev.azure.com/project-repo was not found; expected format is: /',
+ );
+ });
+ });
+
+ describe('with valid project and build-definition annotations', () => {
+ it('should return project and definition', () => {
+ const entity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ namespace: 'default',
+ name: 'project-build-definition',
+ annotations: {
+ 'dev.azure.com/project': 'projectName',
+ 'dev.azure.com/build-definition': 'buildDefinitionName',
+ },
+ },
+ };
+ const { project, repo, definition, org } =
+ getAnnotationValuesFromEntity(entity);
+ expect(project).toEqual('projectName');
+ expect(repo).toEqual(undefined);
+ expect(definition).toEqual('buildDefinitionName');
+ expect(org).toEqual(undefined);
+ });
+ });
+
+ describe('with only project annotation', () => {
+ it('should return project and definition', () => {
+ const entity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ namespace: 'default',
+ name: 'project',
+ annotations: {
+ 'dev.azure.com/project': 'projectName',
+ },
+ },
+ };
+ const test = () => {
+ return getAnnotationValuesFromEntity(entity);
+ };
+
+ expect(test).toThrow(
+ 'Value for annotation dev.azure.com/build-definition was not found',
+ );
+ });
+ });
+
+ describe('with only build-definition annotation', () => {
+ it('should return project and definition', () => {
+ const entity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ namespace: 'default',
+ name: 'build-definition',
+ annotations: {
+ 'dev.azure.com/build-definition': 'buildDefinitionName',
+ },
+ },
+ };
+ const test = () => {
+ return getAnnotationValuesFromEntity(entity);
+ };
+
+ expect(test).toThrow(
+ 'Value for annotation dev.azure.com/project was not found',
+ );
+ });
+ });
+
+ describe('with valid project-repo and org annotations', () => {
+ it('should return project, repo, and org', () => {
+ 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/organization': 'organizationName',
+ },
+ },
+ };
+ const { project, repo, definition, org } =
+ getAnnotationValuesFromEntity(entity);
+ expect(project).toEqual('projectName');
+ expect(repo).toEqual('repoName');
+ expect(definition).toEqual(undefined);
+ expect(org).toEqual('organizationName');
+ });
+ });
+
+ describe('with valid project, build-definition, and org annotations', () => {
+ it('should return project, definition, and org', () => {
+ const entity: Entity = {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Component',
+ metadata: {
+ namespace: 'default',
+ name: 'project-build-definition',
+ annotations: {
+ 'dev.azure.com/project': 'projectName',
+ 'dev.azure.com/build-definition': 'buildDefinitionName',
+ 'dev.azure.com/organization': 'organizationName',
+ },
+ },
+ };
+ const { project, repo, definition, org } =
+ getAnnotationValuesFromEntity(entity);
+ expect(project).toEqual('projectName');
+ expect(repo).toEqual(undefined);
+ expect(definition).toEqual('buildDefinitionName');
+ expect(org).toEqual('organizationName');
+ });
+ });
+});
diff --git a/plugins/azure-devops/src/utils/getAnnotationFromEntity.ts b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts
similarity index 72%
rename from plugins/azure-devops/src/utils/getAnnotationFromEntity.ts
rename to plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts
index 41cc1e6628..1070983b27 100644
--- a/plugins/azure-devops/src/utils/getAnnotationFromEntity.ts
+++ b/plugins/azure-devops/src/utils/getAnnotationValuesFromEntity.ts
@@ -16,23 +16,28 @@
import {
AZURE_DEVOPS_BUILD_DEFINITION_ANNOTATION,
+ AZURE_DEVOPS_ORGANIZATION_ANNOTATION,
AZURE_DEVOPS_PROJECT_ANNOTATION,
AZURE_DEVOPS_REPO_ANNOTATION,
} from '../constants';
import { Entity } from '@backstage/catalog-model';
-export function getAnnotationFromEntity(entity: Entity): {
+export function getAnnotationValuesFromEntity(entity: Entity): {
project: string;
repo?: string;
definition?: string;
+ org?: string;
} {
+ const org =
+ entity.metadata.annotations?.[AZURE_DEVOPS_ORGANIZATION_ANNOTATION];
+
const annotation =
entity.metadata.annotations?.[AZURE_DEVOPS_REPO_ANNOTATION];
if (annotation) {
const { project, repo } = getProjectRepo(annotation);
const definition = undefined;
- return { project, repo, definition };
+ return { project, repo, definition, org };
}
const project =
@@ -50,20 +55,32 @@ export function getAnnotationFromEntity(entity: Entity): {
}
const repo = undefined;
- return { project, repo, definition };
+ return { project, repo, definition, org };
}
function getProjectRepo(annotation: string): {
project: string;
repo: string;
} {
- const [project, repo] = annotation.split('/');
-
- if (!project && !repo) {
+ if (!annotation.includes('/')) {
throw new Error(
'Value for annotation dev.azure.com/project-repo was not in the correct format: /',
);
}
+ const [project, repo] = annotation.split('/');
+
+ if (!project) {
+ throw new Error(
+ 'Project Name for annotation dev.azure.com/project-repo was not found; expected format is: /',
+ );
+ }
+
+ if (!repo) {
+ throw new Error(
+ 'Repo Name for annotation dev.azure.com/project-repo was not found; expected format is: /',
+ );
+ }
+
return { project, repo };
}
diff --git a/plugins/azure-devops/src/utils/index.ts b/plugins/azure-devops/src/utils/index.ts
index 8655b261c3..998bb0e23d 100644
--- a/plugins/azure-devops/src/utils/index.ts
+++ b/plugins/azure-devops/src/utils/index.ts
@@ -17,4 +17,4 @@
export * from './arrayHas';
export * from './equalsIgnoreCase';
export * from './getDurationFromDates';
-export * from './getAnnotationFromEntity';
+export * from './getAnnotationValuesFromEntity';