Initial github link
This commit is contained in:
@@ -16,6 +16,7 @@
|
||||
|
||||
import { CatalogApi } from './types';
|
||||
import { DescriptorEnvelope } from '../types';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
export class CatalogClient implements CatalogApi {
|
||||
private apiOrigin: string;
|
||||
@@ -42,4 +43,23 @@ export class CatalogClient implements CatalogApi {
|
||||
if (entity) return entity;
|
||||
throw new Error(`'Entity not found: ${name}`);
|
||||
}
|
||||
async getLocationByEntity(entity: Entity): Promise<any> {
|
||||
const findLocationIdInEntity = (e: Entity) => {
|
||||
return e.metadata.annotations
|
||||
? e.metadata.annotations['backstage.io/managed-by-location']
|
||||
: null;
|
||||
};
|
||||
|
||||
const locationId = findLocationIdInEntity(entity);
|
||||
if (!locationId) return null;
|
||||
|
||||
const response = await fetch(
|
||||
`${this.apiOrigin}${this.basePath}/locations/${locationId}`,
|
||||
);
|
||||
if (response.ok) {
|
||||
const location = await response.json();
|
||||
if (location) return location;
|
||||
}
|
||||
throw new Error(`'Location not found: ${locationId}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,4 +25,5 @@ export const catalogApiRef = createApiRef<CatalogApi>({
|
||||
export interface CatalogApi {
|
||||
getEntities(): Promise<Entity[]>;
|
||||
getEntityByName(name: string): Promise<Entity>;
|
||||
getLocationByEntity(entity: Entity): Promise<any>;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { FC } from 'react';
|
||||
import React, { FC, useCallback, useState, useEffect } from 'react';
|
||||
import {
|
||||
Content,
|
||||
ContentHeader,
|
||||
@@ -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 } from '@backstage/catalog-model';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
contentWrapper: {
|
||||
@@ -50,16 +52,52 @@ import { envelopeToComponent } from '../../data/utils';
|
||||
const CatalogPage: FC<{}> = () => {
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const { value, error, loading } = useAsync(() => catalogApi.getEntities());
|
||||
const [selectedFilter, setSelectedFilter] = React.useState<CatalogFilterItem>(
|
||||
const [locations, setLocations] = useState<any>([]);
|
||||
const [selectedFilter, setSelectedFilter] = useState<CatalogFilterItem>(
|
||||
defaultFilter,
|
||||
);
|
||||
|
||||
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.map(l => l.data))
|
||||
.then(setLocations);
|
||||
}
|
||||
}, [value, catalogApi]);
|
||||
|
||||
const actions = [
|
||||
(rowData: any) => ({
|
||||
icon: GitHub,
|
||||
tooltop: 'View on GitHub',
|
||||
onClick: () => {
|
||||
if (!rowData || !rowData.location) return;
|
||||
window.open(rowData.location.target, '_blank');
|
||||
},
|
||||
isHidden:
|
||||
rowData && rowData.location
|
||||
? rowData.location.type === 'github'
|
||||
: false,
|
||||
}),
|
||||
];
|
||||
|
||||
const findLocationForEntity = (entity: Entity, l: any) => {
|
||||
const entityLocationId =
|
||||
entity.metadata.annotations?.['backstage.io/managed-by-location'];
|
||||
return l.find((location: any) => location.id === entityLocationId);
|
||||
};
|
||||
|
||||
return (
|
||||
<Page theme={pageTheme.home}>
|
||||
<Header title="Service Catalog" subtitle="Keep track of your software">
|
||||
@@ -98,9 +136,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}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -17,4 +17,5 @@ export type Component = {
|
||||
name: string;
|
||||
kind: string;
|
||||
description: string;
|
||||
location: any;
|
||||
};
|
||||
|
||||
@@ -16,10 +16,14 @@
|
||||
import { Component } from './component';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
|
||||
export function envelopeToComponent(envelope: Entity): Component {
|
||||
export function envelopeToComponent(
|
||||
envelope: Entity,
|
||||
location: any,
|
||||
): Component {
|
||||
return {
|
||||
name: envelope.metadata?.name ?? '',
|
||||
kind: envelope.kind ?? 'unknown',
|
||||
description: envelope.metadata?.annotations?.description ?? 'placeholder',
|
||||
location,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user