+ );
+};
diff --git a/plugins/sentry/src/components/ErrorGraph/ErrorGraph.tsx b/plugins/sentry/src/components/ErrorGraph/ErrorGraph.tsx
new file mode 100644
index 0000000000..7643c5ac6e
--- /dev/null
+++ b/plugins/sentry/src/components/ErrorGraph/ErrorGraph.tsx
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { SentryIssue } from '../../data/sentry-issue';
+import { Sparklines, SparklinesBars } from 'react-sparklines';
+
+export const ErrorGraph: FC<{ sentryIssue: SentryIssue }> = ({
+ sentryIssue,
+}) => {
+ const data =
+ '12h' in sentryIssue.stats
+ ? sentryIssue.stats['12h']
+ : sentryIssue.stats['24h'];
+
+ return (
+ val)} svgHeight={48} margin={4}>
+
+
+ );
+};
diff --git a/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx
new file mode 100644
index 0000000000..08159b16fc
--- /dev/null
+++ b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.test.tsx
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { render } from '@testing-library/react';
+import SentryIssuesTable from './SentryIssuesTable';
+import { SentryIssue } from '../../data/sentry-issue';
+import mockIssue from '../../data/sentry-issue-mock.json';
+import { ThemeProvider } from '@material-ui/styles';
+import { lightTheme } from '@backstage/theme';
+
+describe('SentryIssuesTable', () => {
+ it('should render headers in a table', async () => {
+ const issues: SentryIssue[] = [
+ {
+ ...mockIssue,
+ metadata: {
+ type: 'Exception',
+ value: 'exception was thrown',
+ },
+ count: '1',
+ userCount: 2,
+ },
+ ];
+ const table = await render(
+
+
+ ,
+ );
+ expect(await table.findByText('Error')).toBeInTheDOM();
+ expect(await table.findByText('Graph')).toBeInTheDOM();
+ expect(await table.findByText('First seen')).toBeInTheDOM();
+ expect(await table.findByText('Last seen')).toBeInTheDOM();
+ expect(await table.findByText('Events')).toBeInTheDOM();
+ expect(await table.findByText('Users')).toBeInTheDOM();
+ });
+ it('should render values in a table', async () => {
+ const issues: SentryIssue[] = [
+ {
+ ...mockIssue,
+ metadata: {
+ type: 'Exception',
+ value: 'exception was thrown',
+ },
+ count: '101',
+ userCount: 202,
+ },
+ ];
+ const table = await render(
+
+
+ ,
+ );
+ expect(await table.findByText('Exception')).toBeInTheDOM();
+ expect(await table.findByText('exception was thrown')).toBeInTheDOM();
+ expect(await table.findByText('101')).toBeInTheDOM();
+ expect(await table.findByText('202')).toBeInTheDOM();
+ });
+});
diff --git a/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx
new file mode 100644
index 0000000000..3e5ed98dba
--- /dev/null
+++ b/plugins/sentry/src/components/SentryIssuesTable/SentryIssuesTable.tsx
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { Table, TableColumn } from '@backstage/core';
+import { SentryIssue } from '../../data/sentry-issue';
+import { format } from 'timeago.js';
+import { ErrorCell } from '../ErrorCell/ErrorCell';
+import { ErrorGraph } from '../ErrorGraph/ErrorGraph';
+
+const columns: TableColumn[] = [
+ {
+ title: 'Error',
+ render: (data) => ,
+ },
+ {
+ title: 'Graph',
+ render: (data) => ,
+ },
+ {
+ title: 'First seen',
+ field: 'firstSeen',
+ render: (data) => {
+ const { firstSeen } = data as SentryIssue;
+ return format(firstSeen);
+ },
+ },
+ {
+ title: 'Last seen',
+ field: 'lastSeen',
+ render: (data) => {
+ const { lastSeen } = data as SentryIssue;
+ return format(lastSeen);
+ },
+ },
+ {
+ title: 'Events',
+ field: 'count',
+ },
+ {
+ title: 'Users',
+ field: 'userCount',
+ },
+];
+
+type SentryIssuesTableProps = {
+ sentryIssues: SentryIssue[];
+};
+
+const SentryIssuesTable: FC = ({ sentryIssues }) => {
+ return (
+
+ );
+};
+
+export default SentryIssuesTable;
diff --git a/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx
new file mode 100644
index 0000000000..cb22885861
--- /dev/null
+++ b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.test.tsx
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { render } from '@testing-library/react';
+import mockFetch from 'jest-fetch-mock';
+import SentryPluginPage from './SentryPluginPage';
+import { ThemeProvider } from '@material-ui/core';
+import { lightTheme } from '@backstage/theme';
+
+describe('SentryPluginPage', () => {
+ it('should render header and time switched', () => {
+ mockFetch.mockResponse(() => new Promise(() => {}));
+ const rendered = render(
+
+
+ ,
+ );
+ expect(rendered.getByText('Sentry issues')).toBeInTheDocument();
+ expect(rendered.getByText('24H')).toBeInTheDocument();
+ expect(rendered.getByText('12H')).toBeInTheDocument();
+ });
+});
diff --git a/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx
new file mode 100644
index 0000000000..d7bf731a93
--- /dev/null
+++ b/plugins/sentry/src/components/SentryPluginPage/SentryPluginPage.tsx
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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, useState } from 'react';
+import { Grid } from '@material-ui/core';
+import {
+ Header,
+ Page,
+ pageTheme,
+ Content,
+ ContentHeader,
+ SupportButton,
+} from '@backstage/core';
+import SentryPluginWidget from '../SentryPluginWidget/SentryPluginWidget';
+import { ToggleButton, ToggleButtonGroup } from '@material-ui/lab';
+
+const SentryPluginPage: FC<{}> = () => {
+ const [statsFor, setStatsFor] = useState<'12h' | '24h'>('12h');
+ const toggleStatsFor = () =>
+ statsFor === '12h' ? setStatsFor('24h') : setStatsFor('12h');
+ return (
+
+
+
+
+ 24H
+
+
+ 12H
+
+
+
+
+
+
+ Sentry plugin allows you to preview issues and navigate to sentry.
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default SentryPluginPage;
diff --git a/plugins/sentry/src/components/SentryPluginPage/index.ts b/plugins/sentry/src/components/SentryPluginPage/index.ts
new file mode 100644
index 0000000000..67b34db517
--- /dev/null
+++ b/plugins/sentry/src/components/SentryPluginPage/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 } from './SentryPluginPage';
diff --git a/plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx b/plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx
new file mode 100644
index 0000000000..9b5131d01e
--- /dev/null
+++ b/plugins/sentry/src/components/SentryPluginWidget/SentryPluginWidget.tsx
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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, useEffect } from 'react';
+import {
+ ErrorApi,
+ errorApiRef,
+ InfoCard,
+ Progress,
+ useApi,
+} from '@backstage/core';
+import SentryIssuesTable from '../SentryIssuesTable/SentryIssuesTable';
+import { useAsync } from 'react-use';
+import { sentryApiFactory } from '../../data/api-factory';
+
+const api = sentryApiFactory('spotify');
+
+const SentryPluginWidget: FC<{
+ sentryProjectId: string;
+ statsFor: '24h' | '12h';
+}> = ({ sentryProjectId, statsFor }) => {
+ const errorApi = useApi(errorApiRef);
+
+ const { loading, value, error } = useAsync(
+ () => api.fetchIssues(sentryProjectId, statsFor),
+ [statsFor, sentryProjectId],
+ );
+
+ useEffect(() => {
+ if (error) {
+ errorApi.post(error);
+ }
+ }, [error]);
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+
+ return ;
+};
+
+export default SentryPluginWidget;
diff --git a/plugins/sentry/src/data/api-factory.ts b/plugins/sentry/src/data/api-factory.ts
new file mode 100644
index 0000000000..32ff4bde5c
--- /dev/null
+++ b/plugins/sentry/src/data/api-factory.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { SentryApi } from './sentry-api';
+import { MockSentryApi } from './mock-api';
+import { ProductionSentryApi } from './production-api';
+
+export function sentryApiFactory(organization: string): SentryApi {
+ if (process.env.NODE_ENV === 'production') {
+ return new ProductionSentryApi(organization);
+ }
+ return new MockSentryApi();
+}
diff --git a/plugins/sentry/src/data/mock-api.ts b/plugins/sentry/src/data/mock-api.ts
new file mode 100644
index 0000000000..2eab8e30df
--- /dev/null
+++ b/plugins/sentry/src/data/mock-api.ts
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { SentryIssue } from './sentry-issue';
+import { SentryApi } from './sentry-api';
+import mockData from './sentry-issue-mock.json';
+function getMockIssue(): SentryIssue {
+ const randomizedStats = {
+ '12h': new Array(12)
+ .fill(0)
+ .map(() => [0, Math.floor(Math.random() * 100)]),
+ };
+ return {
+ ...mockData,
+ userCount: Math.floor(Math.random() * 1000),
+ stats: randomizedStats,
+ };
+}
+function getMockIssues(number: number): SentryIssue[] {
+ return new Array(number).fill(0).map(getMockIssue);
+}
+export class MockSentryApi implements SentryApi {
+ fetchIssues(project: string, statsFor: string): Promise {
+ console.info('Fetching mock responses for', project, statsFor);
+ return new Promise((resolve) => {
+ setTimeout(() => resolve(getMockIssues(14)), 800);
+ });
+ }
+}
diff --git a/plugins/sentry/src/data/production-api.ts b/plugins/sentry/src/data/production-api.ts
new file mode 100644
index 0000000000..0e8f22452f
--- /dev/null
+++ b/plugins/sentry/src/data/production-api.ts
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { SentryIssue } from './sentry-issue';
+import { SentryApi } from './sentry-api';
+
+const API_BASE_URL = 'http://localhost:7000/sentry/api/0/projects/';
+
+export class ProductionSentryApi implements SentryApi {
+ private organization: string;
+
+ constructor(organization: string) {
+ this.organization = organization;
+ }
+
+ async fetchIssues(project: string, statsFor: string): Promise {
+ try {
+ const response = await fetch(
+ `${API_BASE_URL}/${this.organization}/${project}/issues/?statsFor=${statsFor}`,
+ );
+
+ if (response.status >= 400 && response.status < 600) {
+ throw new Error('Failed fetching Sentry issues');
+ }
+
+ return (await response.json()) as SentryIssue[];
+ } catch (exception) {
+ if (exception.detail) {
+ return exception;
+ }
+ throw new Error('Unknown error');
+ }
+ }
+}
diff --git a/plugins/sentry/src/data/sentry-api.ts b/plugins/sentry/src/data/sentry-api.ts
new file mode 100644
index 0000000000..538900b738
--- /dev/null
+++ b/plugins/sentry/src/data/sentry-api.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { SentryIssue } from './sentry-issue';
+
+export interface SentryApi {
+ fetchIssues(project: string, statsFor: string): Promise;
+}
diff --git a/plugins/sentry/src/data/sentry-issue-mock.json b/plugins/sentry/src/data/sentry-issue-mock.json
new file mode 100644
index 0000000000..115be77b19
--- /dev/null
+++ b/plugins/sentry/src/data/sentry-issue-mock.json
@@ -0,0 +1,61 @@
+{
+ "platform": "javascript",
+ "lastSeen": "2020-05-15T09:17:17.384804Z",
+ "numComments": 0,
+ "userCount": 0,
+ "stats": {
+ "12h": [
+ [1589450400, 7],
+ [1589454000, 2],
+ [1589457600, 6],
+ [1589461200, 8],
+ [1589464800, 9],
+ [1589468400, 11],
+ [1589472000, 4],
+ [1589475600, 19],
+ [1589479200, 3],
+ [1589482800, 24],
+ [1589486400, 8],
+ [1589490000, 5],
+ [1589493600, 5],
+ [1589497200, 10],
+ [1589500800, 16],
+ [1589504400, 24],
+ [1589508000, 24],
+ [1589511600, 54],
+ [1589515200, 4],
+ [1589518800, 7],
+ [1589522400, 4],
+ [1589526000, 4],
+ [1589529600, 13],
+ [1589533200, 1]
+ ]
+ },
+ "culprit": "https://www.example.com/de/account//",
+ "title": "TypeError: Failed to fetch",
+ "id": "991214716",
+ "assignedTo": null,
+ "logger": null,
+ "type": "error",
+ "annotations": [],
+ "metadata": { "type": "TypeError", "value": "Failed to fetch" },
+ "status": "unresolved",
+ "subscriptionDetails": null,
+ "isPublic": false,
+ "hasSeen": true,
+ "shortId": "example-slug-21",
+ "shareId": null,
+ "firstSeen": "2019-04-18T23:36:40.988000Z",
+ "count": "169815",
+ "permalink": "https://sentry.io/organizations/example/issues/99176416/",
+ "level": "error",
+ "isSubscribed": false,
+ "isBookmarked": false,
+ "project": {
+ "platform": "javascript-react",
+ "slug": "example-slug",
+ "id": "1282343",
+ "name": "example-slug"
+ },
+ "statusDetails": {}
+}
diff --git a/plugins/sentry/src/data/sentry-issue.ts b/plugins/sentry/src/data/sentry-issue.ts
new file mode 100644
index 0000000000..14621bf629
--- /dev/null
+++ b/plugins/sentry/src/data/sentry-issue.ts
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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.
+ */
+type SentryPlatform = 'javascript' | 'javascript-react' | string;
+
+type EventPoint = number[];
+
+type SentryProject = {
+ platform: SentryPlatform;
+ slug: string;
+ id: string;
+ name: string;
+};
+
+type SentryIssueMetadata = {
+ function?: string;
+ type?: string;
+ value?: string;
+ filename?: string;
+};
+
+export type SentryIssue = {
+ platform: SentryPlatform;
+ lastSeen: string;
+ numComments: number;
+ userCount: number;
+ stats: {
+ '24h'?: EventPoint[];
+ '12h'?: EventPoint[];
+ };
+ culprit: string;
+ title: string;
+ id: string;
+ assignedTo: any;
+ logger: any;
+ type: string;
+ annotations: any[];
+ metadata: SentryIssueMetadata;
+ status: string;
+ subscriptionDetails: any;
+ isPublic: boolean;
+ hasSeen: boolean;
+ shortId: string;
+ shareId: string | null;
+ firstSeen: string;
+ count: string;
+ permalink: string;
+ level: string;
+ isSubscribed: boolean;
+ isBookmarked: boolean;
+ project: SentryProject;
+ statusDetails: any;
+};
+
+export type SentryApiError = {
+ detail: string;
+};
diff --git a/plugins/sentry/src/index.ts b/plugins/sentry/src/index.ts
new file mode 100644
index 0000000000..350b8fd668
--- /dev/null
+++ b/plugins/sentry/src/index.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { plugin } from './plugin';
+export { default as SentryIssuesWidget } from './components/SentryPluginWidget/SentryPluginWidget';
diff --git a/plugins/sentry/src/plugin.test.ts b/plugins/sentry/src/plugin.test.ts
new file mode 100644
index 0000000000..8f24236586
--- /dev/null
+++ b/plugins/sentry/src/plugin.test.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 { plugin } from './plugin';
+
+describe('sentry', () => {
+ it('should export plugin', () => {
+ expect(plugin).toBeDefined();
+ });
+});
diff --git a/plugins/sentry/src/plugin.ts b/plugins/sentry/src/plugin.ts
new file mode 100644
index 0000000000..6b4080dd2b
--- /dev/null
+++ b/plugins/sentry/src/plugin.ts
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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 } from '@backstage/core';
+import SentryPluginPage from './components/SentryPluginPage';
+
+export const plugin = createPlugin({
+ id: 'sentry',
+ register({ router }) {
+ router.registerRoute('/sentry', SentryPluginPage);
+ },
+});
diff --git a/plugins/sentry/src/setupTests.ts b/plugins/sentry/src/setupTests.ts
new file mode 100644
index 0000000000..1a907ab8e6
--- /dev/null
+++ b/plugins/sentry/src/setupTests.ts
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2020 Spotify AB
+ *
+ * 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/extend-expect';
+require('jest-fetch-mock').enableMocks();
diff --git a/plugins/sentry/tsconfig.json b/plugins/sentry/tsconfig.json
new file mode 100644
index 0000000000..b663b01fa2
--- /dev/null
+++ b/plugins/sentry/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "extends": "../../tsconfig.json",
+ "include": ["src", "dev"],
+ "compilerOptions": {}
+}
diff --git a/yarn.lock b/yarn.lock
index 0d2cda2b05..0e0edd4bf3 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -19958,6 +19958,11 @@ tildify@2.0.0:
resolved "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz#f205f3674d677ce698b7067a99e949ce03b4754a"
integrity sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==
+timeago.js@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/timeago.js/-/timeago.js-4.0.2.tgz#724e8c8833e3490676c7bb0a75f5daf20e558028"
+ integrity sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w==
+
timed-out@^4.0.0:
version "4.0.1"
resolved "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"