feat(sentry): added possibility to specify organization per component
Signed-off-by: Antonio Musolino <antoniomusolino007@gmail.com>
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
---
|
||||
'@backstage/plugin-sentry': minor
|
||||
---
|
||||
|
||||
Added the possibility to specify organization per component, now the annotation `sentry.io/project-slug` can have the format of `[organization]/[project-slug]` or just `[project-slug]`.
|
||||
|
||||
**BREAKING**: The method `fetchIssue` changed the signature:
|
||||
|
||||
```diff
|
||||
export interface SentryApi {
|
||||
fetchIssues(
|
||||
- project: string,
|
||||
- statsFor: string,
|
||||
- query?: string,
|
||||
+ entity: Entity,
|
||||
+ options: {
|
||||
+ statsFor: string;
|
||||
+ query?: string;
|
||||
+ },
|
||||
): Promise<SentryIssue[]>;
|
||||
}
|
||||
```
|
||||
@@ -242,13 +242,14 @@ that entity if the periskop plugin is installed.
|
||||
# Example:
|
||||
metadata:
|
||||
annotations:
|
||||
sentry.io/project-slug: pump-station
|
||||
sentry.io/project-slug: backstage/pump-station
|
||||
```
|
||||
|
||||
The value of this annotation is the so-called slug (or alternatively, the ID) of
|
||||
a [Sentry](https://sentry.io) project within your organization. The organization
|
||||
slug is currently not configurable on a per-entity basis, but is assumed to be
|
||||
the same for all entities in the catalog.
|
||||
a [Sentry](https://sentry.io) project within your organization. The value can
|
||||
be the format of `[organization]/[project-slug]` or just `[project-slug]`. When
|
||||
the organization slug is omitted the `app-config.yaml` will be used as a
|
||||
fallback (`sentry.organization`).
|
||||
|
||||
Specifying this annotation may enable Sentry related features in Backstage for
|
||||
that entity.
|
||||
|
||||
@@ -53,9 +53,11 @@ export class ProductionSentryApi implements SentryApi {
|
||||
);
|
||||
// (undocumented)
|
||||
fetchIssues(
|
||||
project: string,
|
||||
statsFor: string,
|
||||
query?: string,
|
||||
entity: Entity,
|
||||
options: {
|
||||
statsFor: string;
|
||||
query?: string;
|
||||
},
|
||||
): Promise<SentryIssue[]>;
|
||||
}
|
||||
|
||||
@@ -70,9 +72,11 @@ export const Router: ({ entity }: { entity: Entity }) => JSX.Element;
|
||||
export interface SentryApi {
|
||||
// (undocumented)
|
||||
fetchIssues(
|
||||
project: string,
|
||||
statsFor: string,
|
||||
query?: string,
|
||||
entity: Entity,
|
||||
options: {
|
||||
statsFor: string;
|
||||
query?: string;
|
||||
},
|
||||
): Promise<SentryIssue[]>;
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,10 @@ import {
|
||||
SentryApi,
|
||||
sentryApiRef,
|
||||
} from '../src';
|
||||
import { SENTRY_PROJECT_SLUG_ANNOTATION } from '../src/components/useProjectSlug';
|
||||
import {
|
||||
SENTRY_PROJECT_SLUG_ANNOTATION,
|
||||
getProjectSlug,
|
||||
} from '../src/api/annotations';
|
||||
import { Content, Header, Page } from '@backstage/core-components';
|
||||
|
||||
const entity = (name?: string) =>
|
||||
@@ -47,8 +50,8 @@ createDevApp()
|
||||
deps: {},
|
||||
factory: () =>
|
||||
({
|
||||
fetchIssues: async (project: string) => {
|
||||
switch (project) {
|
||||
fetchIssues: async (e: Entity) => {
|
||||
switch (getProjectSlug(e)) {
|
||||
case 'error':
|
||||
throw new Error('Error!');
|
||||
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright 2022 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 {
|
||||
getOrganization,
|
||||
getProjectSlug,
|
||||
SENTRY_PROJECT_SLUG_ANNOTATION,
|
||||
} from './annotations';
|
||||
|
||||
const entity = (name?: string) =>
|
||||
({
|
||||
apiVersion: 'backstage.io/v1alpha1',
|
||||
kind: 'Component',
|
||||
metadata: {
|
||||
annotations: {
|
||||
[SENTRY_PROJECT_SLUG_ANNOTATION]: name,
|
||||
},
|
||||
name: 'something',
|
||||
},
|
||||
} as Entity);
|
||||
|
||||
describe('sentry annotations', () => {
|
||||
it('getOrganization should works', () => {
|
||||
const data = [
|
||||
[entity('orgs/some'), 'orgs'],
|
||||
[entity('spotify/some'), 'spotify'],
|
||||
[entity('slug'), ''],
|
||||
];
|
||||
|
||||
data.map(([input, expected]) =>
|
||||
expect(getOrganization(input as Entity)).toBe(expected),
|
||||
);
|
||||
});
|
||||
it('getProjectSlug should works', () => {
|
||||
const data = [
|
||||
[entity('orgs/some'), 'some'],
|
||||
[entity('spotify/some'), 'some'],
|
||||
[entity('slug'), 'slug'],
|
||||
[entity(''), ''],
|
||||
];
|
||||
|
||||
data.map(([input, expected]) =>
|
||||
expect(getProjectSlug(input as Entity)).toBe(expected),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2020 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';
|
||||
|
||||
export const SENTRY_PROJECT_SLUG_ANNOTATION = 'sentry.io/project-slug';
|
||||
|
||||
// The value can be the format of `[organization]/[project-slug]` or just `[project-slug]
|
||||
const parseAnnotation = (entity: Entity) => {
|
||||
return (entity?.metadata.annotations?.[SENTRY_PROJECT_SLUG_ANNOTATION] ?? '')
|
||||
.split('/')
|
||||
.reverse();
|
||||
};
|
||||
|
||||
export const getProjectSlug = (entity: Entity) => {
|
||||
const [projectSlug, _] = parseAnnotation(entity);
|
||||
return projectSlug ?? '';
|
||||
};
|
||||
|
||||
export const getOrganization = (entity: Entity) => {
|
||||
const [_, organization] = parseAnnotation(entity);
|
||||
return organization ?? '';
|
||||
};
|
||||
@@ -17,6 +17,8 @@
|
||||
import { SentryIssue } from './sentry-issue';
|
||||
import { SentryApi } from './sentry-api';
|
||||
import { DiscoveryApi, IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { getProjectSlug, getOrganization } from './annotations';
|
||||
|
||||
export class ProductionSentryApi implements SentryApi {
|
||||
constructor(
|
||||
@@ -26,22 +28,29 @@ export class ProductionSentryApi implements SentryApi {
|
||||
) {}
|
||||
|
||||
async fetchIssues(
|
||||
project: string,
|
||||
statsFor: string,
|
||||
query?: string,
|
||||
entity: Entity,
|
||||
options: {
|
||||
statsFor: string;
|
||||
query?: string;
|
||||
},
|
||||
): Promise<SentryIssue[]> {
|
||||
const { query, statsFor } = options;
|
||||
|
||||
const project = getProjectSlug(entity);
|
||||
const organization = getOrganization(entity) || this.organization;
|
||||
|
||||
if (!project) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const apiUrl = `${await this.discoveryApi.getBaseUrl('proxy')}/sentry/api`;
|
||||
const options = await this.authOptions();
|
||||
const authOpts = await this.authOptions();
|
||||
|
||||
const queryPart = query ? `&query=${query}` : '';
|
||||
|
||||
const response = await fetch(
|
||||
`${apiUrl}/0/projects/${this.organization}/${project}/issues/?statsPeriod=${statsFor}${queryPart}`,
|
||||
options,
|
||||
`${apiUrl}/0/projects/${organization}/${project}/issues/?statsPeriod=${statsFor}${queryPart}`,
|
||||
authOpts,
|
||||
);
|
||||
|
||||
if (response.status >= 400 && response.status < 600) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import { SentryIssue } from './sentry-issue';
|
||||
import { createApiRef } from '@backstage/core-plugin-api';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
export const sentryApiRef = createApiRef<SentryApi>({
|
||||
id: 'plugin.sentry.service',
|
||||
@@ -23,8 +24,10 @@ export const sentryApiRef = createApiRef<SentryApi>({
|
||||
|
||||
export interface SentryApi {
|
||||
fetchIssues(
|
||||
project: string,
|
||||
statsFor: string,
|
||||
query?: string,
|
||||
entity: Entity,
|
||||
options: {
|
||||
statsFor: string;
|
||||
query?: string;
|
||||
},
|
||||
): Promise<SentryIssue[]>;
|
||||
}
|
||||
|
||||
@@ -19,10 +19,7 @@ import React, { useEffect } from 'react';
|
||||
import useAsync from 'react-use/lib/useAsync';
|
||||
import { sentryApiRef } from '../../api';
|
||||
import SentryIssuesTable from '../SentryIssuesTable/SentryIssuesTable';
|
||||
import {
|
||||
SENTRY_PROJECT_SLUG_ANNOTATION,
|
||||
useProjectSlug,
|
||||
} from '../useProjectSlug';
|
||||
import { SENTRY_PROJECT_SLUG_ANNOTATION, useProjectSlug } from '../hooks';
|
||||
|
||||
import {
|
||||
EmptyState,
|
||||
@@ -54,7 +51,7 @@ export const SentryIssuesWidget = ({
|
||||
const projectId = useProjectSlug(entity);
|
||||
|
||||
const { loading, value, error } = useAsync(
|
||||
() => sentryApi.fetchIssues(projectId, statsFor, query),
|
||||
() => sentryApi.fetchIssues(entity, { statsFor, query }),
|
||||
[sentryApi, statsFor, projectId, query],
|
||||
);
|
||||
|
||||
|
||||
+8
-6
@@ -14,10 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import {
|
||||
getProjectSlug,
|
||||
getOrganization,
|
||||
SENTRY_PROJECT_SLUG_ANNOTATION,
|
||||
} from '../api/annotations';
|
||||
|
||||
export const SENTRY_PROJECT_SLUG_ANNOTATION = 'sentry.io/project-slug';
|
||||
|
||||
export const useProjectSlug = (entity: Entity) => {
|
||||
return entity?.metadata.annotations?.[SENTRY_PROJECT_SLUG_ANNOTATION] ?? '';
|
||||
};
|
||||
export const useProjectSlug = getProjectSlug;
|
||||
export const useOrganization = getOrganization;
|
||||
export { SENTRY_PROJECT_SLUG_ANNOTATION };
|
||||
@@ -15,7 +15,7 @@
|
||||
*/
|
||||
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { SENTRY_PROJECT_SLUG_ANNOTATION } from './useProjectSlug';
|
||||
import { SENTRY_PROJECT_SLUG_ANNOTATION } from './hooks';
|
||||
|
||||
/**
|
||||
* @public
|
||||
|
||||
Reference in New Issue
Block a user