diff --git a/plugins/xcmetrics/.eslintrc.js b/plugins/xcmetrics/.eslintrc.js
new file mode 100644
index 0000000000..13573efa9c
--- /dev/null
+++ b/plugins/xcmetrics/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: [require.resolve('@backstage/cli/config/eslint')],
+};
diff --git a/plugins/xcmetrics/README.md b/plugins/xcmetrics/README.md
new file mode 100644
index 0000000000..a3bb93d2af
--- /dev/null
+++ b/plugins/xcmetrics/README.md
@@ -0,0 +1,38 @@
+# XCMetrics
+
+[XCMetrics](https://xcmetrics.io) is a tool for collecting build metrics from XCode.
+With this plugin, you can view data from XCMetrics directly in Backstage.
+
+
+
+## Getting started
+
+From `packages/app`:
+
+```bash
+yarn add @backstage/plugin-xcmetrics
+```
+
+In `packages/app/src/App.tsx`, add the following:
+
+```ts
+import { XcmetricsPage } from '@backstage/plugin-xcmetrics';
+```
+
+```tsx
+
+ {/* Other routes... */}
+ } />
+
+```
+
+Add the URL to your XCMetrics backend instance in `app-config.yaml` like so:
+
+```yaml
+proxy:
+ ...
+ '/xcmetrics':
+ target: http://127.0.0.1:8080/v1
+```
+
+Start Backstage and navigate to `/xcmetrics` to view your build metrics!
diff --git a/plugins/xcmetrics/api-report.md b/plugins/xcmetrics/api-report.md
new file mode 100644
index 0000000000..3509fec523
--- /dev/null
+++ b/plugins/xcmetrics/api-report.md
@@ -0,0 +1,23 @@
+## API Report File for "@backstage/plugin-xcmetrics"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+///
+
+import { BackstagePlugin } from '@backstage/core-plugin-api';
+import { RouteRef } from '@backstage/core-plugin-api';
+
+// @public (undocumented)
+export const XcmetricsPage: () => JSX.Element;
+
+// @public (undocumented)
+export const xcmetricsPlugin: BackstagePlugin<
+ {
+ root: RouteRef;
+ },
+ {}
+>;
+
+// (No @packageDocumentation comment for this package)
+```
diff --git a/plugins/xcmetrics/dev/index.tsx b/plugins/xcmetrics/dev/index.tsx
new file mode 100644
index 0000000000..f6663dd33d
--- /dev/null
+++ b/plugins/xcmetrics/dev/index.tsx
@@ -0,0 +1,27 @@
+/*
+ * 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 React from 'react';
+import { createDevApp } from '@backstage/dev-utils';
+import { xcmetricsPlugin, XcmetricsPage } from '../src/plugin';
+
+createDevApp()
+ .registerPlugin(xcmetricsPlugin)
+ .addPage({
+ element: ,
+ title: 'Root Page',
+ path: '/xcmetrics',
+ })
+ .render();
diff --git a/plugins/xcmetrics/docs/XCMetrics-overview.png b/plugins/xcmetrics/docs/XCMetrics-overview.png
new file mode 100644
index 0000000000..b0b7dc6153
Binary files /dev/null and b/plugins/xcmetrics/docs/XCMetrics-overview.png differ
diff --git a/plugins/xcmetrics/package.json b/plugins/xcmetrics/package.json
new file mode 100644
index 0000000000..e617d12e85
--- /dev/null
+++ b/plugins/xcmetrics/package.json
@@ -0,0 +1,52 @@
+{
+ "name": "@backstage/plugin-xcmetrics",
+ "version": "0.1.1",
+ "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/core-components": "^0.1.5",
+ "@backstage/core-plugin-api": "^0.1.3",
+ "@backstage/errors": "^0.1.1",
+ "@backstage/theme": "^0.2.8",
+ "@material-ui/core": "^4.11.0",
+ "@material-ui/icons": "^4.9.1",
+ "@material-ui/lab": "4.0.0-alpha.45",
+ "luxon": "^1.27.0",
+ "react": "^16.13.1",
+ "react-dom": "^16.13.1",
+ "react-use": "^17.2.4"
+ },
+ "devDependencies": {
+ "@backstage/cli": "^0.7.3",
+ "@backstage/core-app-api": "^0.1.4",
+ "@backstage/dev-utils": "^0.2.1",
+ "@backstage/test-utils": "^0.1.14",
+ "@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/luxon": "^1.27.0",
+ "@types/node": "^14.14.32",
+ "cross-fetch": "^3.0.6",
+ "msw": "^0.29.0"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/plugins/xcmetrics/src/api/XcmetricsClient.ts b/plugins/xcmetrics/src/api/XcmetricsClient.ts
new file mode 100644
index 0000000000..00087ad75d
--- /dev/null
+++ b/plugins/xcmetrics/src/api/XcmetricsClient.ts
@@ -0,0 +1,42 @@
+/*
+ * 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 { DiscoveryApi } from '@backstage/core-plugin-api';
+import { ResponseError } from '@backstage/errors';
+import { BuildItem, BuildsResult, XcmetricsApi } from './types';
+
+interface Options {
+ discoveryApi: DiscoveryApi;
+}
+
+export class XcmetricsClient implements XcmetricsApi {
+ private readonly discoveryApi: DiscoveryApi;
+
+ constructor(options: Options) {
+ this.discoveryApi = options.discoveryApi;
+ }
+
+ async getBuilds(): Promise {
+ const baseUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/xcmetrics`;
+ const response = await fetch(`${baseUrl}/build`);
+
+ if (!response.ok) {
+ throw await ResponseError.fromResponse(response);
+ }
+
+ return ((await response.json()) as BuildsResult).items;
+ }
+}
diff --git a/plugins/xcmetrics/src/api/index.ts b/plugins/xcmetrics/src/api/index.ts
new file mode 100644
index 0000000000..c184dc950d
--- /dev/null
+++ b/plugins/xcmetrics/src/api/index.ts
@@ -0,0 +1,18 @@
+/*
+ * 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';
+export * from './XcmetricsClient';
diff --git a/plugins/xcmetrics/src/api/types.ts b/plugins/xcmetrics/src/api/types.ts
new file mode 100644
index 0000000000..add59b1a86
--- /dev/null
+++ b/plugins/xcmetrics/src/api/types.ts
@@ -0,0 +1,63 @@
+/*
+ * 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 { createApiRef } from '@backstage/core-plugin-api';
+
+export type BuildStatus = 'succeeded' | 'failed' | 'stopped';
+
+export type BuildItem = {
+ userid: string;
+ warningCount: number;
+ duration: number;
+ startTimestamp: string;
+ isCi: boolean;
+ startTimestampMicroseconds: number;
+ category: string;
+ endTimestampMicroseconds: number;
+ day: string;
+ compilationEndTimestamp: string;
+ tag: string;
+ projectName: string;
+ compilationEndTimestampMicroseconds: number;
+ errorCount: number;
+ id: string;
+ buildStatus: BuildStatus;
+ compilationDuration: number;
+ schema: string;
+ compiledCount: number;
+ endTimestamp: string;
+ userid256: string;
+ machineName: string;
+ wasSuspended: boolean;
+};
+
+export type BuildsResult = {
+ items: BuildItem[];
+ metadata: {
+ per: number;
+ total: number;
+ page: number;
+ };
+};
+
+export interface XcmetricsApi {
+ getBuilds(): Promise;
+}
+
+export const xcmetricsApiRef = createApiRef({
+ id: 'plugin.xcmetrics.api',
+ description: 'Used by the XCMetrics plugin to make requests',
+});
diff --git a/plugins/xcmetrics/src/components/OverviewComponent/OverviewComponent.test.tsx b/plugins/xcmetrics/src/components/OverviewComponent/OverviewComponent.test.tsx
new file mode 100644
index 0000000000..d386d279ce
--- /dev/null
+++ b/plugins/xcmetrics/src/components/OverviewComponent/OverviewComponent.test.tsx
@@ -0,0 +1,75 @@
+/*
+ * 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 React from 'react';
+import { OverviewComponent } from './OverviewComponent';
+import { renderInTestApp } from '@backstage/test-utils';
+import { XcmetricsApi, xcmetricsApiRef } from '../../api';
+import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+
+describe('OverviewComponent', () => {
+ it('should render', async () => {
+ const mockUserId = 'mockUser';
+ const mockApi: jest.Mocked = {
+ getBuilds: jest.fn().mockResolvedValue([
+ {
+ userid: mockUserId,
+ warningCount: 1,
+ duration: 123.45,
+ isCi: false,
+ projectName: 'App',
+ buildStatus: 'succeeded',
+ schema: 'AppSchema',
+ },
+ ]),
+ };
+
+ const rendered = await renderInTestApp(
+
+
+ ,
+ );
+ expect(rendered.getByText('XCMetrics Dashboard')).toBeInTheDocument();
+ expect(rendered.getByText(mockUserId)).toBeInTheDocument();
+ expect(rendered.queryByText('CI')).toBeNull();
+ });
+
+ it('should render an empty state when no builds exist', async () => {
+ const mockApi: jest.Mocked = {
+ getBuilds: jest.fn().mockResolvedValue([]),
+ };
+
+ const rendered = await renderInTestApp(
+
+
+ ,
+ );
+ expect(rendered.getByText('No builds to show')).toBeInTheDocument();
+ });
+
+ it('should show an error when API not responding', async () => {
+ const errorMessage = 'MockErrorMessage';
+ const mockApi: jest.Mocked = {
+ getBuilds: jest.fn().mockRejectedValue({ message: errorMessage }),
+ };
+
+ const rendered = await renderInTestApp(
+
+
+ ,
+ );
+ expect(rendered.getByText(errorMessage)).toBeInTheDocument();
+ });
+});
diff --git a/plugins/xcmetrics/src/components/OverviewComponent/OverviewComponent.tsx b/plugins/xcmetrics/src/components/OverviewComponent/OverviewComponent.tsx
new file mode 100644
index 0000000000..37b35c9690
--- /dev/null
+++ b/plugins/xcmetrics/src/components/OverviewComponent/OverviewComponent.tsx
@@ -0,0 +1,120 @@
+/*
+ * 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 React from 'react';
+import {
+ ContentHeader,
+ SupportButton,
+ Progress,
+ StatusOK,
+ StatusError,
+ StatusWarning,
+ Table,
+ TableColumn,
+ EmptyState,
+} from '@backstage/core-components';
+import { useApi } from '@backstage/core-plugin-api';
+import { BuildItem, BuildStatus, xcmetricsApiRef } from '../../api';
+import { useAsync } from 'react-use';
+import { Alert } from '@material-ui/lab';
+import { Duration } from 'luxon';
+import { Chip } from '@material-ui/core';
+
+const formatStatus = (status: BuildStatus, warningCount: number) => {
+ const statusIcons = {
+ succeeded: ,
+ failed: ,
+ stopped: ,
+ };
+
+ return (
+ <>
+ {statusIcons[status]} {status[0].toUpperCase() + status.slice(1)}
+ {warningCount > 0 && ` with ${warningCount} warning`}
+ {warningCount > 1 && 's'}
+ >
+ );
+};
+
+const columns: TableColumn[] = [
+ {
+ title: 'Project',
+ field: 'projectName',
+ },
+ {
+ title: 'Schema',
+ field: 'schema',
+ },
+ {
+ title: 'Duration',
+ field: 'duration',
+ type: 'time',
+ searchable: false,
+ render: data => Duration.fromObject({ seconds: data.duration }).toISOTime(),
+ },
+ {
+ title: 'User',
+ field: 'userid',
+ },
+ {
+ title: 'Status',
+ field: 'buildStatus',
+ render: data => formatStatus(data.buildStatus, data.warningCount),
+ },
+ {
+ field: 'isCI',
+ render: data => data.isCi && ,
+ width: '10',
+ sorting: false,
+ },
+];
+
+export const OverviewComponent = () => {
+ const client = useApi(xcmetricsApiRef);
+ const { value: builds, loading, error } = useAsync(
+ async (): Promise => client.getBuilds(),
+ [],
+ );
+
+ if (loading) {
+ return ;
+ } else if (error) {
+ return {error.message};
+ }
+
+ if (!builds || !builds.length) {
+ return (
+
+ );
+ }
+
+ return (
+ <>
+
+ Dashboard for XCMetrics
+
+
+ >
+ );
+};
diff --git a/plugins/xcmetrics/src/components/OverviewComponent/index.ts b/plugins/xcmetrics/src/components/OverviewComponent/index.ts
new file mode 100644
index 0000000000..58b284e6f1
--- /dev/null
+++ b/plugins/xcmetrics/src/components/OverviewComponent/index.ts
@@ -0,0 +1,16 @@
+/*
+ * 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 './OverviewComponent';
diff --git a/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx b/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx
new file mode 100644
index 0000000000..96d841b083
--- /dev/null
+++ b/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.tsx
@@ -0,0 +1,30 @@
+/*
+ * 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 React from 'react';
+import { Content, Header, HeaderLabel, Page } from '@backstage/core-components';
+import { OverviewComponent } from '../OverviewComponent';
+
+export const XcmetricsLayout = () => (
+
+
+
+
+
+
+);
diff --git a/plugins/xcmetrics/src/components/XcmetricsLayout/index.ts b/plugins/xcmetrics/src/components/XcmetricsLayout/index.ts
new file mode 100644
index 0000000000..eaaadcbaea
--- /dev/null
+++ b/plugins/xcmetrics/src/components/XcmetricsLayout/index.ts
@@ -0,0 +1,16 @@
+/*
+ * 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 './XcmetricsLayout';
diff --git a/plugins/xcmetrics/src/index.ts b/plugins/xcmetrics/src/index.ts
new file mode 100644
index 0000000000..aa02224738
--- /dev/null
+++ b/plugins/xcmetrics/src/index.ts
@@ -0,0 +1,16 @@
+/*
+ * 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 { xcmetricsPlugin, XcmetricsPage } from './plugin';
diff --git a/plugins/xcmetrics/src/plugin.test.ts b/plugins/xcmetrics/src/plugin.test.ts
new file mode 100644
index 0000000000..055c647dd9
--- /dev/null
+++ b/plugins/xcmetrics/src/plugin.test.ts
@@ -0,0 +1,22 @@
+/*
+ * 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 { xcmetricsPlugin } from './plugin';
+
+describe('xcmetrics', () => {
+ it('should export plugin', () => {
+ expect(xcmetricsPlugin).toBeDefined();
+ });
+});
diff --git a/plugins/xcmetrics/src/plugin.ts b/plugins/xcmetrics/src/plugin.ts
new file mode 100644
index 0000000000..22250ff627
--- /dev/null
+++ b/plugins/xcmetrics/src/plugin.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021 The Backstage Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import {
+ createApiFactory,
+ createPlugin,
+ createRoutableExtension,
+ discoveryApiRef,
+} from '@backstage/core-plugin-api';
+import { xcmetricsApiRef, XcmetricsClient } from './api';
+import { rootRouteRef } from './routes';
+
+export const xcmetricsPlugin = createPlugin({
+ id: 'xcmetrics',
+ routes: {
+ root: rootRouteRef,
+ },
+ apis: [
+ createApiFactory({
+ api: xcmetricsApiRef,
+ deps: {
+ discoveryApi: discoveryApiRef,
+ },
+ factory({ discoveryApi }) {
+ return new XcmetricsClient({ discoveryApi });
+ },
+ }),
+ ],
+});
+
+export const XcmetricsPage = xcmetricsPlugin.provide(
+ createRoutableExtension({
+ component: () =>
+ import('./components/XcmetricsLayout').then(m => m.XcmetricsLayout),
+ mountPoint: rootRouteRef,
+ }),
+);
diff --git a/plugins/xcmetrics/src/routes.ts b/plugins/xcmetrics/src/routes.ts
new file mode 100644
index 0000000000..7acd62c363
--- /dev/null
+++ b/plugins/xcmetrics/src/routes.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 { createRouteRef } from '@backstage/core-plugin-api';
+
+export const rootRouteRef = createRouteRef({
+ title: 'XCMetrics',
+});
diff --git a/plugins/xcmetrics/src/setupTests.ts b/plugins/xcmetrics/src/setupTests.ts
new file mode 100644
index 0000000000..fc6dbd98f8
--- /dev/null
+++ b/plugins/xcmetrics/src/setupTests.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.
+ */
+import '@testing-library/jest-dom';
+import 'cross-fetch/polyfill';