Merge branch 'master' into lintMod

This commit is contained in:
Fredrik Adelöw
2021-02-04 07:58:13 +01:00
50 changed files with 880 additions and 495 deletions
+1
View File
@@ -34,6 +34,7 @@
"@backstage/backend-common": "^0.5.1",
"@backstage/catalog-model": "^0.7.0",
"@backstage/config": "^0.1.2",
"@backstage/integration": "^0.3.1",
"@octokit/graphql": "^4.5.8",
"@types/express": "^4.17.6",
"@types/ldapjs": "^1.0.9",
@@ -15,30 +15,73 @@
*/
import { LocationSpec } from '@backstage/catalog-model';
import { toAbsoluteUrl } from './LocationEntityProcessor';
import { ConfigReader } from '@backstage/config';
import {
ScmIntegrations,
ScmIntegrationRegistry,
} from '@backstage/integration';
import path from 'path';
import { toAbsoluteUrl } from './LocationEntityProcessor';
describe('LocationEntityProcessor', () => {
describe('toAbsoluteUrl', () => {
it('handles files', () => {
const integrations = ({} as unknown) as ScmIntegrationRegistry;
const base: LocationSpec = {
type: 'file',
target: `some${path.sep}path${path.sep}catalog-info.yaml`,
};
expect(toAbsoluteUrl(base, `.${path.sep}c`)).toBe(
expect(toAbsoluteUrl(integrations, base, `.${path.sep}c`)).toBe(
`some${path.sep}path${path.sep}c`,
);
expect(toAbsoluteUrl(base, `${path.sep}c`)).toBe(`${path.sep}c`);
expect(toAbsoluteUrl(integrations, base, `${path.sep}c`)).toBe(
`${path.sep}c`,
);
});
it('handles urls', () => {
const integrations = ScmIntegrations.fromConfig(new ConfigReader({}));
const base: LocationSpec = {
type: 'url',
target: 'http://a.com/b/catalog-info.yaml',
};
expect(toAbsoluteUrl(base, './c/d')).toBe('http://a.com/b/c/d');
expect(toAbsoluteUrl(base, 'c/d')).toBe('http://a.com/b/c/d');
expect(toAbsoluteUrl(base, 'http://b.com/z')).toBe('http://b.com/z');
jest.spyOn(integrations, 'resolveUrl');
expect(toAbsoluteUrl(integrations, base, './c/d')).toBe(
'http://a.com/b/c/d',
);
expect(toAbsoluteUrl(integrations, base, 'c/d')).toBe(
'http://a.com/b/c/d',
);
expect(toAbsoluteUrl(integrations, base, 'http://b.com/z')).toBe(
'http://b.com/z',
);
expect(integrations.resolveUrl).toBeCalledTimes(3);
});
it('handles azure urls specifically', () => {
const integrations = ScmIntegrations.fromConfig(
new ConfigReader({
integrations: {
azure: [{ host: 'dev.azure.com' }],
},
}),
);
expect(
toAbsoluteUrl(
integrations,
{
type: 'url',
target:
'https://dev.azure.com/organization/project/_git/repository?path=%2Fcatalog-info.yaml',
},
'./a.yaml',
),
).toBe(
'https://dev.azure.com/organization/project/_git/repository?path=%2Fa.yaml',
);
});
});
});
@@ -15,11 +15,16 @@
*/
import { Entity, LocationEntity, LocationSpec } from '@backstage/catalog-model';
import { ScmIntegrationRegistry } from '@backstage/integration';
import path from 'path';
import * as result from './results';
import { CatalogProcessor, CatalogProcessorEmit } from './types';
import path from 'path';
export function toAbsoluteUrl(base: LocationSpec, target: string): string {
export function toAbsoluteUrl(
integrations: ScmIntegrationRegistry,
base: LocationSpec,
target: string,
): string {
try {
if (base.type === 'file') {
if (target.startsWith('.')) {
@@ -27,13 +32,19 @@ export function toAbsoluteUrl(base: LocationSpec, target: string): string {
}
return target;
}
return new URL(target, base.target).toString();
return integrations.resolveUrl({ url: target, base: base.target });
} catch (e) {
return target;
}
}
export class LocationRefProcessor implements CatalogProcessor {
type Options = {
integrations: ScmIntegrationRegistry;
};
export class LocationEntityProcessor implements CatalogProcessor {
constructor(private readonly options: Options) {}
async postProcessEntity(
entity: Entity,
location: LocationSpec,
@@ -47,7 +58,7 @@ export class LocationRefProcessor implements CatalogProcessor {
emit(
result.inputError(
location,
`LocationRefProcessor cannot handle ${type} type location with target ${location.target} that ends with a path separator`,
`LocationEntityProcessor cannot handle ${type} type location with target ${location.target} that ends with a path separator`,
),
);
}
@@ -61,7 +72,11 @@ export class LocationRefProcessor implements CatalogProcessor {
}
for (const maybeRelativeTarget of targets) {
const target = toAbsoluteUrl(location, maybeRelativeTarget);
const target = toAbsoluteUrl(
this.options.integrations,
location,
maybeRelativeTarget,
);
emit(result.location({ type, target }, false));
}
}
@@ -23,7 +23,7 @@ export { CodeOwnersProcessor } from './CodeOwnersProcessor';
export { FileReaderProcessor } from './FileReaderProcessor';
export { GithubOrgReaderProcessor } from './GithubOrgReaderProcessor';
export { LdapOrgReaderProcessor } from './LdapOrgReaderProcessor';
export { LocationRefProcessor } from './LocationEntityProcessor';
export { LocationEntityProcessor } from './LocationEntityProcessor';
export { MicrosoftGraphOrgReaderProcessor } from './MicrosoftGraphOrgReaderProcessor';
export { PlaceholderProcessor } from './PlaceholderProcessor';
export type { PlaceholderResolver } from './PlaceholderProcessor';
@@ -26,6 +26,7 @@ import {
Validators,
} from '@backstage/catalog-model';
import { Config } from '@backstage/config';
import { ScmIntegrations } from '@backstage/integration';
import lodash from 'lodash';
import { Logger } from 'winston';
import {
@@ -46,8 +47,8 @@ import {
HigherOrderOperation,
HigherOrderOperations,
LdapOrgReaderProcessor,
LocationEntityProcessor,
LocationReaders,
LocationRefProcessor,
MicrosoftGraphOrgReaderProcessor,
PlaceholderProcessor,
PlaceholderResolver,
@@ -280,6 +281,7 @@ export class CatalogBuilder {
private buildProcessors(): CatalogProcessor[] {
const { config, logger, reader } = this.env;
const integrations = ScmIntegrations.fromConfig(config);
this.checkDeprecatedReaderProcessors();
@@ -306,7 +308,7 @@ export class CatalogBuilder {
MicrosoftGraphOrgReaderProcessor.fromConfig(config, { logger }),
new UrlReaderProcessor({ reader, logger }),
new CodeOwnersProcessor({ reader, logger }),
new LocationRefProcessor(),
new LocationEntityProcessor({ integrations }),
new AnnotateLocationEntityProcessor(),
);
}
@@ -76,13 +76,13 @@ describe('getPreviousPeriodTotalCost', () => {
change: changeOf(MockAggregatedDailyCosts),
trendline: trendlineOf(MockAggregatedDailyCosts),
};
const exclusiveEndDate = '2020-09-30';
const inclusiveEndDate = '2020-09-30';
expect(
getPreviousPeriodTotalCost(
mockGroupDailyCost.aggregation,
Duration.P30D,
exclusiveEndDate,
inclusiveEndDate,
),
).toEqual(100_000);
).toEqual(96_600);
});
});
+1 -5
View File
@@ -22,7 +22,6 @@ import {
GrowthType,
MetricData,
Duration,
DEFAULT_DATE_FORMAT,
DateAggregation,
} from '../types';
import dayjs, { OpUnitType } from 'dayjs';
@@ -73,10 +72,7 @@ export function getPreviousPeriodTotalCost(
inclusiveEndDate: string,
): number {
const dayjsDuration = dayjs.duration(duration);
const startDate = inclusiveStartDateOf(
duration,
dayjs(inclusiveEndDate).add(1, 'day').format(DEFAULT_DATE_FORMAT),
);
const startDate = inclusiveStartDateOf(duration, inclusiveEndDate);
// dayjs doesn't allow adding an ISO 8601 period to dates.
const [amount, type]: [number, OpUnitType] = dayjsDuration.days()
? [dayjsDuration.days(), 'day']
+6 -13
View File
@@ -24,23 +24,21 @@ export const DEFAULT_DURATION = Duration.P30D;
* Derive the start date of a given period, assuming two repeating intervals.
*
* @param duration see comment on Duration enum
* @param exclusiveEndDate from CostInsightsApi.getLastCompleteBillingDate + 1 day
* @param inclusiveEndDate from CostInsightsApi.getLastCompleteBillingDate
*/
export function inclusiveStartDateOf(
duration: Duration,
exclusiveEndDate: string,
inclusiveEndDate: string,
): string {
switch (duration) {
case Duration.P7D:
case Duration.P30D:
case Duration.P90D:
return moment(exclusiveEndDate)
.utc()
return moment(inclusiveEndDate)
.subtract(moment.duration(duration).add(moment.duration(duration)))
.format(DEFAULT_DATE_FORMAT);
case Duration.P3M:
return moment(exclusiveEndDate)
.utc()
return moment(inclusiveEndDate)
.startOf('quarter')
.subtract(moment.duration(duration).add(moment.duration(duration)))
.format(DEFAULT_DATE_FORMAT);
@@ -57,13 +55,9 @@ export function exclusiveEndDateOf(
case Duration.P7D:
case Duration.P30D:
case Duration.P90D:
return moment(inclusiveEndDate)
.utc()
.add(1, 'day')
.format(DEFAULT_DATE_FORMAT);
return moment(inclusiveEndDate).add(1, 'day').format(DEFAULT_DATE_FORMAT);
case Duration.P3M:
return moment(quarterEndDate(inclusiveEndDate))
.utc()
.add(1, 'day')
.format(DEFAULT_DATE_FORMAT);
default:
@@ -76,7 +70,6 @@ export function inclusiveEndDateOf(
inclusiveEndDate: string,
): string {
return moment(exclusiveEndDateOf(duration, inclusiveEndDate))
.utc()
.subtract(1, 'day')
.format(DEFAULT_DATE_FORMAT);
}
@@ -94,7 +87,7 @@ export function intervalsOf(
}
export function quarterEndDate(inclusiveEndDate: string): string {
const endDate = moment(inclusiveEndDate).utc();
const endDate = moment(inclusiveEndDate);
const endOfQuarter = endDate.endOf('quarter').format(DEFAULT_DATE_FORMAT);
if (endOfQuarter === inclusiveEndDate) {
return endDate.format(DEFAULT_DATE_FORMAT);
@@ -16,7 +16,7 @@
import moment from 'moment';
import pluralize from 'pluralize';
import { Duration, DEFAULT_DATE_FORMAT } from '../types';
import { Duration } from '../types';
import { inclusiveEndDateOf, inclusiveStartDateOf } from '../utils/duration';
export type Period = {
@@ -92,11 +92,8 @@ export function formatPercent(n: number): string {
}
export function formatLastTwoLookaheadQuarters(inclusiveEndDate: string) {
const exclusiveEndDate = moment(inclusiveEndDate)
.add(1, 'day')
.format(DEFAULT_DATE_FORMAT);
const start = moment(
inclusiveStartDateOf(Duration.P3M, exclusiveEndDate),
inclusiveStartDateOf(Duration.P3M, inclusiveEndDate),
).format('[Q]Q YYYY');
const end = moment(inclusiveEndDateOf(Duration.P3M, inclusiveEndDate)).format(
'[Q]Q YYYY',
+4 -3
View File
@@ -36,7 +36,7 @@ import {
getDefaultState as getDefaultLoadingState,
} from '../utils/loading';
import { findAlways } from '../utils/assert';
import { inclusiveStartDateOf } from './duration';
import { inclusiveEndDateOf, inclusiveStartDateOf } from './duration';
type mockAlertRenderer<T> = (alert: T) => T;
type mockEntityRenderer<T> = (entity: T) => T;
@@ -228,8 +228,9 @@ export function aggregationFor(
baseline: number,
): DateAggregation[] {
const { duration, endDate } = parseIntervals(intervals);
const inclusiveEndDate = inclusiveEndDateOf(duration, endDate);
const days = dayjs(endDate).diff(
inclusiveStartDateOf(duration, endDate),
inclusiveStartDateOf(duration, inclusiveEndDate),
'day',
);
@@ -244,7 +245,7 @@ export function aggregationFor(
return [...Array(days).keys()].reduce(
(values: DateAggregation[], i: number): DateAggregation[] => {
const last = values.length ? values[values.length - 1].amount : baseline;
const date = dayjs(inclusiveStartDateOf(duration, endDate))
const date = dayjs(inclusiveStartDateOf(duration, inclusiveEndDate))
.add(i, 'day')
.format(DEFAULT_DATE_FORMAT);
const amount = Math.max(0, last + nextDelta());
+4 -14
View File
@@ -19,17 +19,7 @@ yarn add @backstage/plugin-rollbar
export { plugin as Rollbar } from '@backstage/plugin-rollbar';
```
4. Add plugin API to your Backstage instance:
```ts
// packages/app/src/api.ts
import { RollbarClient, rollbarApiRef } from '@backstage/plugin-rollbar';
// ...
builder.add(rollbarApiRef, new RollbarClient({ discoveryApi }));
```
5. Add to the app `EntityPage` component:
4. Add to the app `EntityPage` component:
```ts
// packages/app/src/components/catalog/EntityPage.tsx
@@ -48,7 +38,7 @@ const ServiceEntityPage = ({ entity }: { entity: Entity }) => (
);
```
6. Setup the `app.config.yaml` and account token environment variable
5. Setup the `app.config.yaml` and account token environment variable
```yaml
# app.config.yaml
@@ -59,7 +49,7 @@ rollbar:
$env: ROLLBAR_ACCOUNT_TOKEN
```
7. Annotate entities with the rollbar project slug
6. Annotate entities with the rollbar project slug
```yaml
# pump-station-catalog-component.yaml
@@ -71,7 +61,7 @@ metadata:
rollbar.com/project-slug: project-name
```
8. Run app with `yarn start` and navigate to `/rollbar` or a catalog entity
7. Run app with `yarn start` and navigate to `/rollbar` or a catalog entity
## Features
+2 -2
View File
@@ -15,6 +15,6 @@
*/
import { createDevApp } from '@backstage/dev-utils';
import { plugin } from '../src/plugin';
import { rollbarPlugin } from '../src/plugin';
createDevApp().registerPlugin(plugin).render();
createDevApp().registerPlugin(rollbarPlugin).render();
@@ -14,14 +14,18 @@
* limitations under the License.
*/
import React from 'react';
import { Entity } from '@backstage/catalog-model';
import { useEntity } from '@backstage/plugin-catalog-react';
import React from 'react';
import { RollbarProject } from '../RollbarProject/RollbarProject';
type Props = {
entity: Entity;
/** @deprecated The entity is now grabbed from context instead */
entity?: Entity;
};
export const EntityPageRollbar = ({ entity }: Props) => {
export const EntityPageRollbar = (_props: Props) => {
const { entity } = useEntity();
return <RollbarProject entity={entity} />;
};
@@ -1,70 +0,0 @@
/*
* 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 * as React from 'react';
import {
ApiProvider,
ApiRegistry,
ConfigApi,
configApiRef,
} from '@backstage/core';
import { catalogApiRef, CatalogApi } from '@backstage/plugin-catalog-react';
import { wrapInTestApp } from '@backstage/test-utils';
import { render } from '@testing-library/react';
import { RollbarApi, rollbarApiRef } from '../../api/RollbarApi';
import { RollbarProject } from '../../api/types';
import { RollbarHome } from './RollbarHome';
describe('RollbarHome component', () => {
const projects: RollbarProject[] = [
{ id: 123, name: 'abc', accountId: 1, status: 'enabled' },
{ id: 456, name: 'xyz', accountId: 1, status: 'enabled' },
];
const rollbarApi: Partial<RollbarApi> = {
getAllProjects: () => Promise.resolve(projects),
};
const config: Partial<ConfigApi> = {
getString: () => 'foo',
getOptionalString: () => 'foo',
};
const renderWrapped = (children: React.ReactNode) =>
render(
wrapInTestApp(
<ApiProvider
apis={ApiRegistry.from([
[rollbarApiRef, rollbarApi],
[configApiRef, config],
[
catalogApiRef,
({
async getEntities() {
return { items: [] };
},
} as Partial<CatalogApi>) as CatalogApi,
],
])}
>
{children}
</ApiProvider>,
),
);
it('should render rollbar landing page', async () => {
const rendered = renderWrapped(<RollbarHome />);
expect(rendered.getByText(/Rollbar/)).toBeInTheDocument();
});
});
@@ -1,40 +0,0 @@
/*
* 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 { Content, Header, Page } from '@backstage/core';
import { RollbarProjectTable } from '../RollbarProjectTable/RollbarProjectTable';
import { useRollbarEntities } from '../../hooks/useRollbarEntities';
export const RollbarHome = () => {
const { entities, loading, error } = useRollbarEntities();
return (
<Page themeId="tool">
<Header
title="Rollbar"
subtitle="Real-time error tracking & debugging tools for developers"
/>
<Content>
<RollbarProjectTable
entities={entities || []}
loading={loading}
error={error}
/>
</Content>
</Page>
);
};
@@ -14,8 +14,8 @@
* limitations under the License.
*/
import React from 'react';
import { Entity } from '@backstage/catalog-model';
import React from 'react';
import { useTopActiveItems } from '../../hooks/useTopActiveItems';
import { RollbarTopItemsTable } from '../RollbarTopItemsTable/RollbarTopItemsTable';
@@ -1,86 +0,0 @@
/*
* 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 {
ApiProvider,
ApiRegistry,
ConfigApi,
configApiRef,
} from '@backstage/core';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
import { wrapInTestApp } from '@backstage/test-utils';
import { render } from '@testing-library/react';
import * as React from 'react';
import { RollbarApi, rollbarApiRef } from '../../api/RollbarApi';
import { RollbarTopActiveItem } from '../../api/types';
import { RollbarProjectPage } from './RollbarProjectPage';
describe('RollbarProjectPage component', () => {
const items: RollbarTopActiveItem[] = [
{
item: {
id: 9898989,
counter: 1234,
environment: 'production',
framework: 2,
lastOccurrenceTimestamp: new Date().getTime() / 1000,
level: 50,
occurrences: 100,
projectId: 12345,
title: 'error occurred',
uniqueOccurrences: 10,
},
counts: [10, 10, 10, 10, 10, 50],
},
];
const rollbarApi: Partial<RollbarApi> = {
getTopActiveItems: () => Promise.resolve(items),
};
const config: Partial<ConfigApi> = {
getString: () => 'foo',
getOptionalString: () => 'foo',
};
const renderWrapped = (children: React.ReactNode) =>
render(
wrapInTestApp(
<ApiProvider
apis={ApiRegistry.from([
[rollbarApiRef, rollbarApi],
[configApiRef, config],
[
catalogApiRef,
({
async getEntityByName() {
return {
metadata: { name: 'foo' },
spec: { owner: 'bar', lifecycle: 'experimental' },
} as any;
},
} as Partial<CatalogApi>) as CatalogApi,
],
])}
>
{children}
</ApiProvider>,
),
);
it('should render rollbar project page', async () => {
const rendered = renderWrapped(<RollbarProjectPage />);
expect(rendered.getByText(/Rollbar/)).toBeInTheDocument();
});
});
@@ -1,36 +0,0 @@
/*
* 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 { Content, Header, HeaderLabel, Page } from '@backstage/core';
import { useCatalogEntity } from '../../hooks/useCatalogEntity';
import { RollbarProject } from '../RollbarProject/RollbarProject';
export const RollbarProjectPage = () => {
const { entity } = useCatalogEntity();
return (
<Page themeId="tool">
<Header title={entity?.metadata?.name} subtitle="Rollbar Project">
<HeaderLabel label="Owner" value={entity?.spec?.owner} />
<HeaderLabel label="Lifecycle" value={entity?.spec?.lifecycle} />
</Header>
<Content>
{entity ? <RollbarProject entity={entity} /> : 'Loading'}
</Content>
</Page>
);
};
@@ -1,85 +0,0 @@
/*
* 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 { Link as RouterLink, generatePath } from 'react-router-dom';
import { Table, TableColumn } from '@backstage/core';
import { Entity } from '@backstage/catalog-model';
import { Link } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import { entityRouteRef } from '../../routes';
const columns: TableColumn[] = [
{
title: 'Name',
field: 'metadata.name',
type: 'string',
highlight: true,
render: (entity: any) => (
<Link
component={RouterLink}
to={generatePath(entityRouteRef.path, {
optionalNamespaceAndName: [
entity.metadata.namespace,
entity.metadata.name,
]
.filter(Boolean)
.join(':'),
kind: entity.kind,
})}
>
{entity.metadata.name}
</Link>
),
},
{
title: 'Description',
field: 'metadata.description',
},
];
type Props = {
entities: Entity[];
loading: boolean;
error?: any;
};
export const RollbarProjectTable = ({ entities, loading, error }: Props) => {
if (error) {
return (
<div>
<Alert severity="error">
Error encountered while fetching rollbar projects. {error.toString()}
</Alert>
</div>
);
}
return (
<Table
isLoading={loading}
columns={columns}
options={{
search: true,
paging: true,
pageSize: 10,
showEmptyDataSourceMessage: !loading,
}}
title="Projects"
data={entities}
/>
);
};
@@ -17,8 +17,8 @@
import { wrapInTestApp } from '@backstage/test-utils';
import { render } from '@testing-library/react';
import * as React from 'react';
import { RollbarTopItemsTable } from './RollbarTopItemsTable';
import { RollbarTopActiveItem } from '../../api/types';
import { RollbarTopItemsTable } from './RollbarTopItemsTable';
const items: RollbarTopActiveItem[] = [
{
@@ -14,10 +14,10 @@
* limitations under the License.
*/
import React from 'react';
import { Table, TableColumn } from '@backstage/core';
import { Box, Link, Typography } from '@material-ui/core';
import { Alert } from '@material-ui/lab';
import React from 'react';
import {
RollbarFrameworkId,
RollbarLevel,
+16 -9
View File
@@ -14,29 +14,36 @@
* limitations under the License.
*/
import React from 'react';
import { Routes, Route } from 'react-router';
import { Entity } from '@backstage/catalog-model';
import { MissingAnnotationEmptyState } from '@backstage/core';
import { catalogRouteRef } from '../routes';
import { useEntity } from '@backstage/plugin-catalog-react';
import React from 'react';
import { Route, Routes } from 'react-router';
import { ROLLBAR_ANNOTATION } from '../constants';
import { rootRouteRef } from '../plugin';
import { EntityPageRollbar } from './EntityPageRollbar/EntityPageRollbar';
export const isPluginApplicableToEntity = (entity: Entity) =>
Boolean(entity.metadata.annotations?.[ROLLBAR_ANNOTATION]);
type Props = {
entity: Entity;
/** @deprecated The entity is now grabbed from context instead */
entity?: Entity;
};
export const Router = ({ entity }: Props) =>
!isPluginApplicableToEntity(entity) ? (
<MissingAnnotationEmptyState annotation={ROLLBAR_ANNOTATION} />
) : (
export const Router = (_props: Props) => {
const { entity } = useEntity();
if (!isPluginApplicableToEntity(entity)) {
<MissingAnnotationEmptyState annotation={ROLLBAR_ANNOTATION} />;
}
return (
<Routes>
<Route
path={`/${catalogRouteRef.path}`}
path={`/${rootRouteRef.path}`}
element={<EntityPageRollbar entity={entity} />}
/>
</Routes>
);
};
@@ -14,9 +14,9 @@
* limitations under the License.
*/
import React from 'react';
import { render } from '@testing-library/react';
import { wrapInTestApp } from '@backstage/test-utils';
import { render } from '@testing-library/react';
import React from 'react';
import { TrendGraph } from './TrendGraph';
describe('TrendGraph component', () => {
+10 -4
View File
@@ -14,10 +14,16 @@
* limitations under the License.
*/
export { plugin } from './plugin';
export * from './api';
export * from './routes';
export { Router } from './components/Router';
export { RollbarProjectPage } from './components/RollbarProjectPage/RollbarProjectPage';
export { EntityPageRollbar } from './components/EntityPageRollbar/EntityPageRollbar';
export {
isPluginApplicableToEntity,
isPluginApplicableToEntity as isRollbarAvailable,
Router,
} from './components/Router';
export { ROLLBAR_ANNOTATION } from './constants';
export {
EntityRollbarContent,
rollbarPlugin as plugin,
rollbarPlugin,
} from './plugin';
+2 -2
View File
@@ -14,10 +14,10 @@
* limitations under the License.
*/
import { plugin } from './plugin';
import { rollbarPlugin } from './plugin';
describe('rollbar', () => {
it('should export plugin', () => {
expect(plugin).toBeDefined();
expect(rollbarPlugin).toBeDefined();
});
});
+18 -8
View File
@@ -15,17 +15,21 @@
*/
import {
createPlugin,
createApiFactory,
createPlugin,
createRoutableExtension,
createRouteRef,
discoveryApiRef,
} from '@backstage/core';
import { rootRouteRef, entityRouteRef } from './routes';
import { RollbarHome } from './components/RollbarHome/RollbarHome';
import { RollbarProjectPage } from './components/RollbarProjectPage/RollbarProjectPage';
import { rollbarApiRef } from './api/RollbarApi';
import { RollbarClient } from './api/RollbarClient';
export const plugin = createPlugin({
export const rootRouteRef = createRouteRef({
path: '',
title: 'Rollbar',
});
export const rollbarPlugin = createPlugin({
id: 'rollbar',
apis: [
createApiFactory({
@@ -34,8 +38,14 @@ export const plugin = createPlugin({
factory: ({ discoveryApi }) => new RollbarClient({ discoveryApi }),
}),
],
register({ router }) {
router.addRoute(rootRouteRef, RollbarHome);
router.addRoute(entityRouteRef, RollbarProjectPage);
routes: {
entityContent: rootRouteRef,
},
});
export const EntityRollbarContent = rollbarPlugin.provide(
createRoutableExtension({
component: () => import('./components/Router').then(m => m.Router),
mountPoint: rootRouteRef,
}),
);
-36
View File
@@ -1,36 +0,0 @@
/*
* 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 { createRouteRef } from '@backstage/core';
const NoIcon = () => null;
export const rootRouteRef = createRouteRef({
icon: NoIcon,
path: '/rollbar',
title: 'Rollbar Home',
});
export const entityRouteRef = createRouteRef({
path: '/rollbar/:optionalNamespaceAndName',
title: 'Rollbar',
});
export const catalogRouteRef = createRouteRef({
icon: NoIcon,
path: '',
title: 'Rollbar',
});