diff --git a/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.test.tsx b/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.test.tsx
index aaedf1bcba..ef12e52363 100644
--- a/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.test.tsx
+++ b/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.test.tsx
@@ -13,28 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+import { Entity } from '@backstage/catalog-model';
+import { render } from '@testing-library/react';
import React from 'react';
import { ComponentMetadataCard } from './ComponentMetadataCard';
-import { Component } from '../../data/component';
-import { render } from '@testing-library/react';
describe('ComponentMetadataCard component', () => {
it('should display component name if provided', async () => {
- const testComponent: Component = {
- name: 'test',
+ const testEntity: Entity = {
+ apiVersion: 'backstage.io/v1beta1',
kind: 'Component',
metadata: { name: 'test' },
- description: 'Placeholder',
};
const rendered = await render(
- ,
+ ,
);
expect(await rendered.findByText('test')).toBeInTheDocument();
});
- it('should display loader when loading is set to true', async () => {
- const rendered = await render(
- ,
- );
- expect(await rendered.findByRole('progressbar')).toBeInTheDocument();
- });
});
diff --git a/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.tsx b/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.tsx
index d765b9cc0b..60c9284121 100644
--- a/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.tsx
+++ b/plugins/catalog/src/components/ComponentMetadataCard/ComponentMetadataCard.tsx
@@ -13,29 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { InfoCard, Progress, StructuredMetadataTable } from '@backstage/core';
+import { Entity } from '@backstage/catalog-model';
+import { InfoCard, StructuredMetadataTable } from '@backstage/core';
import React, { FC } from 'react';
-import { Component } from '../../data/component';
type Props = {
- loading: boolean;
- component: Component | undefined;
+ entity: Entity;
};
-export const ComponentMetadataCard: FC = ({ loading, component }) => {
- if (loading) {
- return (
-
-
-
- );
- }
- if (!component) {
- return null;
- }
- return (
-
-
-
- );
-};
+export const ComponentMetadataCard: FC = ({ entity }) => (
+
+
+
+);
diff --git a/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx b/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx
index e0d5c99e4d..75393bd10e 100644
--- a/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx
+++ b/plugins/catalog/src/components/ComponentPage/ComponentPage.test.tsx
@@ -47,8 +47,8 @@ describe('ComponentPage', () => {
[
catalogApiRef,
({
- async getEntity() {},
- } as unknown) as CatalogApi,
+ async getEntityByName() {},
+ } as Partial) as CatalogApi,
],
])}
>
diff --git a/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx b/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx
index 3f3ecfa006..17ad2d880d 100644
--- a/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx
+++ b/plugins/catalog/src/components/ComponentPage/ComponentPage.tsx
@@ -13,23 +13,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+
+import { Entity } from '@backstage/catalog-model';
import {
Content,
- ErrorApi,
errorApiRef,
Header,
HeaderTabs,
Page,
pageTheme,
+ Progress,
useApi,
} from '@backstage/core';
import { SentryIssuesWidget } from '@backstage/plugin-sentry';
import { Grid } from '@material-ui/core';
+import { Alert } from '@material-ui/lab';
import React, { FC, useEffect, useState } from 'react';
import { useAsync } from 'react-use';
import { catalogApiRef } from '../..';
-import { Component } from '../../data/component';
-import { entityToComponent } from '../../data/utils';
import { ComponentContextMenu } from '../ComponentContextMenu/ComponentContextMenu';
import { ComponentMetadataCard } from '../ComponentMetadataCard/ComponentMetadataCard';
import { ComponentRemovalDialog } from '../ComponentRemovalDialog/ComponentRemovalDialog';
@@ -48,48 +49,61 @@ type ComponentPageProps = {
};
};
+function headerProps(
+ kind: string,
+ namespace: string | undefined,
+ name: string,
+ entity: Entity | undefined,
+): { headerTitle: string; headerType: string } {
+ return {
+ headerTitle: `${name}${namespace ? ` in ${namespace}` : ''}`,
+ headerType: (() => {
+ let t = kind.toLowerCase();
+ if (entity && entity.spec && 'type' in entity.spec) {
+ t += ' — ';
+ t += (entity.spec as { type: string }).type.toLowerCase();
+ }
+ return t;
+ })(),
+ };
+}
+
export const ComponentPage: FC = ({ match, history }) => {
- const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
- const [removingPending, setRemovingPending] = useState(false);
- const showRemovalDialog = () => setConfirmationDialogOpen(true);
- const hideRemovalDialog = () => setConfirmationDialogOpen(false);
const { optionalNamespaceAndName, kind } = match.params;
const [name, namespace] = optionalNamespaceAndName.split(':').reverse();
- const errorApi = useApi(errorApiRef);
+ const errorApi = useApi(errorApiRef);
const catalogApi = useApi(catalogApiRef);
- const { value: component, error, loading } = useAsync(async () => {
- const entity = await catalogApi.getEntityByName({ name, namespace, kind });
- if (!entity) {
- throw new Error(`No entity found with that name`);
- }
- const location = await catalogApi.getLocationByEntity(entity);
- return { ...entityToComponent(entity), location };
- });
+
+ const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false);
+ const { value: entity, error, loading } = useAsync(
+ () => catalogApi.getEntityByName({ kind, namespace, name }),
+ [catalogApi, kind, namespace, name],
+ );
useEffect(() => {
- if (error) {
+ if (!error && !loading && !entity) {
errorApi.post(new Error('Component not found!'));
setTimeout(() => {
history.push('/');
}, REDIRECT_DELAY);
}
- }, [error, errorApi, history]);
+ }, [errorApi, history, error, loading, entity]);
- if (name === '') {
+ if (!name) {
history.push('/catalog');
return null;
}
const removeComponent = async () => {
setConfirmationDialogOpen(false);
- setRemovingPending(true);
// await componentFactory.removeComponentByName(componentName);
-
- await catalogApi;
history.push('/');
};
+ const showRemovalDialog = () => setConfirmationDialogOpen(true);
+ const hideRemovalDialog = () => setConfirmationDialogOpen(false);
+
// TODO - Replace with proper tabs implementation
const tabs = [
{
@@ -118,38 +132,56 @@ export const ComponentPage: FC = ({ match, history }) => {
},
];
+ const { headerTitle, headerType } = headerProps(
+ kind,
+ namespace,
+ name,
+ entity,
+ );
+
return (
// TODO: Switch theme and type props based on component type (website, library, ...)
-
-
+
-
- {confirmationDialogOpen && component && (
-
+ {loading && }
+
+ {error && (
+
+ {error.toString()}
+
+ )}
+
+ {entity && (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
)}
-
-
-
-
-
-
-
-
-
-
);
};
diff --git a/plugins/catalog/src/components/ComponentRemovalDialog/ComponentRemovalDialog.tsx b/plugins/catalog/src/components/ComponentRemovalDialog/ComponentRemovalDialog.tsx
index 9ea4e64908..aa8f99e01e 100644
--- a/plugins/catalog/src/components/ComponentRemovalDialog/ComponentRemovalDialog.tsx
+++ b/plugins/catalog/src/components/ComponentRemovalDialog/ComponentRemovalDialog.tsx
@@ -32,37 +32,36 @@ import React, { FC } from 'react';
import { useAsync } from 'react-use';
import { AsyncState } from 'react-use/lib/useAsync';
import { catalogApiRef } from '../../api/types';
-import { Component } from '../../data/component';
type ComponentRemovalDialogProps = {
+ open: boolean;
onConfirm: () => any;
- onCancel: () => any;
onClose: () => any;
- component: Component;
+ entity: Entity;
};
-function useColocatedEntities(component: Component): AsyncState {
+function useColocatedEntities(entity: Entity): AsyncState {
const catalogApi = useApi(catalogApiRef);
return useAsync(async () => {
- const myLocation = component.metadata.annotations?.[LOCATION_ANNOTATION];
+ const myLocation = entity.metadata.annotations?.[LOCATION_ANNOTATION];
return myLocation
? await catalogApi.getEntities({ [LOCATION_ANNOTATION]: myLocation })
: [];
- }, [catalogApi, component]);
+ }, [catalogApi, entity]);
}
export const ComponentRemovalDialog: FC = ({
+ open,
onConfirm,
- onCancel,
onClose,
- component,
+ entity,
}) => {
- const { value: entities, loading, error } = useColocatedEntities(component);
+ const { value: entities, loading, error } = useColocatedEntities(entity);
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
return (
-