Merge pull request #1112 from hooloovooo/catalog-github-link

Catalog GitHub link
This commit is contained in:
Sebastian Qvarfordt
2020-06-04 15:06:56 +02:00
committed by GitHub
7 changed files with 98 additions and 10 deletions
+18
View File
@@ -16,6 +16,7 @@
import { CatalogApi } from './types';
import { DescriptorEnvelope } from '../types';
import { Entity, Location } from '@backstage/catalog-model';
export class CatalogClient implements CatalogApi {
private apiOrigin: string;
@@ -42,4 +43,21 @@ export class CatalogClient implements CatalogApi {
if (entity) return entity;
throw new Error(`'Entity not found: ${name}`);
}
async getLocationByEntity(entity: Entity): Promise<Location | undefined> {
const findLocationIdInEntity = (e: Entity): string | undefined =>
e.metadata.annotations?.['backstage.io/managed-by-location'];
const locationId = findLocationIdInEntity(entity);
if (!locationId) return undefined;
const response = await fetch(
`${this.apiOrigin}${this.basePath}/locations/${locationId}`,
);
if (response.ok) {
const location = await response.json();
if (location) return location.data;
}
return undefined;
}
}
+2 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { createApiRef } from '@backstage/core';
import { Entity } from '@backstage/catalog-model';
import { Entity, Location } from '@backstage/catalog-model';
export const catalogApiRef = createApiRef<CatalogApi>({
id: 'plugin.catalog.service',
@@ -25,4 +25,5 @@ export const catalogApiRef = createApiRef<CatalogApi>({
export interface CatalogApi {
getEntities(): Promise<Entity[]>;
getEntityByName(name: string): Promise<Entity>;
getLocationByEntity(entity: Entity): Promise<Location | undefined>;
}
@@ -22,7 +22,10 @@ import { wrapInTestApp } from '@backstage/test-utils';
import { catalogApiRef } from '../..';
const errorApi = { post: () => {} };
const catalogApi = { getEntities: () => Promise.resolve([{ kind: '' }]) };
const catalogApi = {
getEntities: () => Promise.resolve([{ kind: '', metadata: {} }]),
getLocationByEntity: () => Promise.resolve({ data: {} }),
};
describe('CatalogPage', () => {
// this test right now causes some red lines in the log output when running tests
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import React, { FC } from 'react';
import React, { FC, useCallback, useState, useEffect } from 'react';
import {
Content,
ContentHeader,
@@ -26,7 +26,7 @@ import {
pageTheme,
useApi,
} from '@backstage/core';
import { useAsync } from 'react-use';
import { useAsync, useMountedState } from 'react-use';
import CatalogTable from '../CatalogTable/CatalogTable';
import {
CatalogFilter,
@@ -34,6 +34,8 @@ import {
} from '../CatalogFilter/CatalogFilter';
import { Button, makeStyles, Typography, Link } from '@material-ui/core';
import { filterGroups, defaultFilter } from '../../data/filters';
import GitHub from '@material-ui/icons/GitHub';
import { Entity, Location } from '@backstage/catalog-model';
const useStyles = makeStyles(theme => ({
contentWrapper: {
@@ -46,20 +48,64 @@ const useStyles = makeStyles(theme => ({
import { catalogApiRef } from '../..';
import { envelopeToComponent } from '../../data/utils';
import { Component } from '../../data/component';
const CatalogPage: FC<{}> = () => {
const catalogApi = useApi(catalogApiRef);
const { value, error, loading } = useAsync(() => catalogApi.getEntities());
const [selectedFilter, setSelectedFilter] = React.useState<CatalogFilterItem>(
const [locations, setLocations] = useState<Location[]>([]);
const [selectedFilter, setSelectedFilter] = useState<CatalogFilterItem>(
defaultFilter,
);
const isMounted = useMountedState();
const onFilterSelected = React.useCallback(
const onFilterSelected = useCallback(
selected => setSelectedFilter(selected),
[],
);
const styles = useStyles();
useEffect(() => {
const getLocationDataForEntities = async (entities: Entity[]) => {
return Promise.all(
entities.map(entity => catalogApi.getLocationByEntity(entity)),
);
};
if (value) {
getLocationDataForEntities(value)
.then(
(location): Location[] =>
location.filter(l => !!l) as Array<Location>,
)
.then(location => {
if (isMounted()) setLocations(location);
});
}
}, [value, catalogApi, isMounted]);
const actions = [
(rowData: Component) => ({
icon: GitHub,
tooltip: 'View on GitHub',
onClick: () => {
if (!rowData || !rowData.location) return;
window.open(rowData.location.target, '_blank');
},
hidden:
rowData && rowData.location ? rowData.location.type !== 'github' : true,
}),
];
const findLocationForEntity = (
entity: Entity,
l: Location[],
): Location | undefined => {
const entityLocationId =
entity.metadata.annotations?.['backstage.io/managed-by-location'];
return l.find(location => location.id === entityLocationId);
};
return (
<Page theme={pageTheme.home}>
<Header title="Service Catalog" subtitle="Keep track of your software">
@@ -98,9 +144,19 @@ const CatalogPage: FC<{}> = () => {
</div>
<CatalogTable
titlePreamble={selectedFilter.label}
components={(value && value.map(envelopeToComponent)) || []}
components={
(value &&
value.map(val =>
envelopeToComponent(
val,
findLocationForEntity(val, locations),
),
)) ||
[]
}
loading={loading}
error={error}
actions={actions}
/>
</div>
</Content>
@@ -42,12 +42,14 @@ type CatalogTableProps = {
titlePreamble: string;
loading: boolean;
error?: any;
actions?: any;
};
const CatalogTable: FC<CatalogTableProps> = ({
components,
loading,
error,
titlePreamble,
actions,
}) => {
if (loading) {
return <Progress />;
@@ -64,9 +66,10 @@ const CatalogTable: FC<CatalogTableProps> = ({
return (
<Table
columns={columns}
options={{ paging: false }}
options={{ paging: false, actionsColumnIndex: -1 }}
title={`${titlePreamble} (${(components && components.length) || 0})`}
data={components}
actions={actions}
/>
);
};
+3
View File
@@ -13,8 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Location } from '@backstage/catalog-model';
export type Component = {
name: string;
kind: string;
description: string;
location?: Location;
};
+6 -2
View File
@@ -14,12 +14,16 @@
* limitations under the License.
*/
import { Component } from './component';
import { Entity } from '@backstage/catalog-model';
import { Entity, Location } from '@backstage/catalog-model';
export function envelopeToComponent(envelope: Entity): Component {
export function envelopeToComponent(
envelope: Entity,
location?: Location,
): Component {
return {
name: envelope.metadata?.name ?? '',
kind: envelope.kind ?? 'unknown',
description: envelope.metadata?.annotations?.description ?? 'placeholder',
location,
};
}