diff --git a/.changeset/tough-buckets-explain.md b/.changeset/tough-buckets-explain.md
new file mode 100644
index 0000000000..a2ca37d516
--- /dev/null
+++ b/.changeset/tough-buckets-explain.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-azure-devops': patch
+---
+
+Simplified queue time calculation in `BuildTable`.
diff --git a/plugins/azure-devops-common/.eslintrc.js b/plugins/azure-devops-common/.eslintrc.js
new file mode 100644
index 0000000000..13573efa9c
--- /dev/null
+++ b/plugins/azure-devops-common/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: [require.resolve('@backstage/cli/config/eslint')],
+};
diff --git a/plugins/azure-devops-common/api-report.md b/plugins/azure-devops-common/api-report.md
new file mode 100644
index 0000000000..0bc6568189
--- /dev/null
+++ b/plugins/azure-devops-common/api-report.md
@@ -0,0 +1,47 @@
+## API Report File for "@backstage/plugin-azure-devops-common"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+// Warning: (ae-missing-release-tag) "BuildResult" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export enum BuildResult {
+ Canceled = 32,
+ Failed = 8,
+ None = 0,
+ PartiallySucceeded = 4,
+ Succeeded = 2,
+}
+
+// Warning: (ae-missing-release-tag) "BuildStatus" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export enum BuildStatus {
+ All = 47,
+ Cancelling = 4,
+ Completed = 2,
+ InProgress = 1,
+ None = 0,
+ NotStarted = 32,
+ Postponed = 8,
+}
+
+// Warning: (ae-missing-release-tag) "RepoBuild" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public (undocumented)
+export type RepoBuild = {
+ id?: number;
+ title: string;
+ link?: string;
+ status?: BuildStatus;
+ result?: BuildResult;
+ queueTime?: Date;
+ startTime?: Date;
+ finishTime?: Date;
+ source: string;
+ uniqueName?: string;
+};
+
+// (No @packageDocumentation comment for this package)
+```
diff --git a/plugins/azure-devops-common/package.json b/plugins/azure-devops-common/package.json
new file mode 100644
index 0000000000..05f70f6596
--- /dev/null
+++ b/plugins/azure-devops-common/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "@backstage/plugin-azure-devops-common",
+ "version": "0.0.1",
+ "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"
+ },
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/azure-devops-common"
+ },
+ "keywords": [
+ "backstage"
+ ],
+ "scripts": {
+ "build": "backstage-cli build",
+ "lint": "backstage-cli lint",
+ "test": "backstage-cli test --passWithNoTests",
+ "prepack": "backstage-cli prepack",
+ "postpack": "backstage-cli postpack",
+ "clean": "backstage-cli clean"
+ },
+ "devDependencies": {
+ "@backstage/cli": "^0.8.1"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/plugins/azure-devops-common/src/index.ts b/plugins/azure-devops-common/src/index.ts
new file mode 100644
index 0000000000..26a854de89
--- /dev/null
+++ b/plugins/azure-devops-common/src/index.ts
@@ -0,0 +1,17 @@
+/*
+ * 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.
+ */
+
+export * from './types';
diff --git a/plugins/azure-devops-common/src/types.ts b/plugins/azure-devops-common/src/types.ts
new file mode 100644
index 0000000000..d7736d03e1
--- /dev/null
+++ b/plugins/azure-devops-common/src/types.ts
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+
+export enum BuildResult {
+ /**
+ * No result
+ */
+ None = 0,
+ /**
+ * The build completed successfully.
+ */
+ Succeeded = 2,
+ /**
+ * The build completed compilation successfully but had other errors.
+ */
+ PartiallySucceeded = 4,
+ /**
+ * The build completed unsuccessfully.
+ */
+ Failed = 8,
+ /**
+ * The build was canceled before starting.
+ */
+ Canceled = 32,
+}
+
+export enum BuildStatus {
+ /**
+ * No status.
+ */
+ None = 0,
+ /**
+ * The build is currently in progress.
+ */
+ InProgress = 1,
+ /**
+ * The build has completed.
+ */
+ Completed = 2,
+ /**
+ * The build is cancelling
+ */
+ Cancelling = 4,
+ /**
+ * The build is inactive in the queue.
+ */
+ Postponed = 8,
+ /**
+ * The build has not yet started.
+ */
+ NotStarted = 32,
+ /**
+ * All status.
+ */
+ All = 47,
+}
+
+export type RepoBuild = {
+ id?: number;
+ title: string;
+ link?: string;
+ status?: BuildStatus;
+ result?: BuildResult;
+ queueTime?: Date;
+ startTime?: Date;
+ finishTime?: Date;
+ source: string;
+ uniqueName?: string;
+};
diff --git a/plugins/azure-devops/package.json b/plugins/azure-devops/package.json
index a071205711..1ad5b07ebb 100644
--- a/plugins/azure-devops/package.json
+++ b/plugins/azure-devops/package.json
@@ -31,6 +31,7 @@
"@backstage/core-components": "^0.7.2",
"@backstage/core-plugin-api": "^0.1.12",
"@backstage/errors": "^0.1.4",
+ "@backstage/plugin-azure-devops-common": "^0.0.1",
"@backstage/plugin-catalog-react": "^0.6.2",
"@backstage/theme": "^0.2.12",
"@material-ui/core": "^4.12.2",
diff --git a/plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx b/plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx
new file mode 100644
index 0000000000..76a19155ba
--- /dev/null
+++ b/plugins/azure-devops/src/components/BuildTable/BuildTable.stories.tsx
@@ -0,0 +1,94 @@
+/*
+ * 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 {
+ BuildResult,
+ BuildStatus,
+ RepoBuild,
+} from '@backstage/plugin-azure-devops-common';
+
+import { BuildTable } from './BuildTable';
+import { MemoryRouter } from 'react-router';
+import React from 'react';
+
+export default {
+ title: 'Plugins/Azure Devops/Build Table',
+ component: BuildTable,
+};
+
+const buildStatuses: Array<[BuildStatus, BuildResult]> = [
+ [BuildStatus.InProgress, BuildResult.None], // In Progress
+ [BuildStatus.Completed, BuildResult.Succeeded], // Succeeded
+ [BuildStatus.Completed, BuildResult.Failed], // Failed
+ [BuildStatus.Completed, BuildResult.PartiallySucceeded], // Partially Succeeded
+ [BuildStatus.Completed, BuildResult.Canceled], // Cancelled
+ [BuildStatus.Completed, BuildResult.None], // Unknown
+ [BuildStatus.Cancelling, BuildResult.None], // Cancelling
+ [BuildStatus.Postponed, BuildResult.None], // Postponed
+ [BuildStatus.NotStarted, BuildResult.None], // Not Started
+ [BuildStatus.None, BuildResult.None], // Unknown
+];
+
+const generateTestData = (rows = 10): RepoBuild[] => {
+ const repoBuilds: RepoBuild[] = [];
+
+ for (let i = 0; i < rows; i++) {
+ const [status, result] = buildStatuses[i] ?? [
+ BuildStatus.Completed,
+ BuildResult.Succeeded,
+ ];
+
+ repoBuilds.push({
+ id: rows - i + 12534,
+ title: `backstage ci - 1.0.0-preview-${rows - i}`,
+ status,
+ result,
+ queueTime: new Date(Date.now() - i * 60000),
+ source: 'refs/heads/main',
+ link: '',
+ });
+ }
+
+ return repoBuilds;
+};
+
+export const Default = () => (
+
+
+
+);
+
+export const Empty = () => (
+
+
+
+);
+
+export const Loading = () => (
+
+
+
+);
+
+export const ErrorMessage = () => (
+
+
+
+);
diff --git a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx
index a308afb81b..f8bb031f0a 100644
--- a/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx
+++ b/plugins/azure-devops/src/components/BuildTable/BuildTable.tsx
@@ -149,9 +149,7 @@ const columns: TableColumn[] = [
field: 'queueTime',
width: 'auto',
render: (row: Partial) =>
- DateTime.fromISO(
- row.queueTime ? row.queueTime.toString() : new Date().toString(),
- ).toRelative(),
+ DateTime.fromJSDate(row.queueTime ?? new Date()).toRelative(),
},
];