TechDocs: browse metadata (#2682)
* feat(techdocs): TechDocsPageWrapper to take in labels prop and use TechDocsHeader component * feat(techdocs): new TechDocsHeader component as wrapper around Header from core * feat(techdocs): TechDocsPage metadata * feat(techdocs): TechDocsMetadata class responsible for reading metadata from generated techdocs metadata json file * fix(techdocs): delete TechDocsPageWrapper component * fix(app-config): delete /docs from request url * feat(techdocs): TechDocsHeader with labels to browse metadata * fix(techdocs): move metadata to backend plugin * feat(techdocs-backend): introduce two new metadata routes * feat(techdocs): new techdocs api to be responsible to request metadata from backend * feat(techdocs): register new api for plugin * fix(techdocs): reader to take in onReady prop * fix(techdocs): TechDocsHome component to use its own header * fix(techdocs): TechDocsPage responsible to get metadata and pass it down to the TechDocsHeader * fix(techdocs): component tests for both TechDocsHeader and TechDocsPage * fix(techdocs): adjust api to use /docs in url since it was taken away in requestURL * fix(tests): move assertion into act * fix(techdocs): rename TechDocsHeader to TechDocsPageHeader * fix(techdocs): rename TechDocsHeader to TechDocsPageHeader * fix(techdocs-backend): add some logs * Update plugins/techdocs/src/reader/components/TechDocsPage.test.tsx * delete unused import
This commit is contained in:
+1
-1
@@ -42,7 +42,7 @@ organization:
|
||||
|
||||
techdocs:
|
||||
storageUrl: http://localhost:7000/api/techdocs/static/docs
|
||||
requestUrl: http://localhost:7000/api/techdocs/docs
|
||||
requestUrl: http://localhost:7000/api/techdocs
|
||||
generators:
|
||||
techdocs: 'docker'
|
||||
|
||||
|
||||
+22
-19
@@ -1,3 +1,4 @@
|
||||
import fetch from 'node-fetch';
|
||||
/*
|
||||
* Copyright 2020 Spotify AB
|
||||
*
|
||||
@@ -14,24 +15,26 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Header, Content, Page, pageTheme } from '@backstage/core';
|
||||
export class TechDocsMetadata {
|
||||
private async getMetadataFile(docsUrl: String) {
|
||||
const metadataURL = `${docsUrl}/techdocs_metadata.json`;
|
||||
|
||||
type TechDocsPageWrapperProps = {
|
||||
title: string;
|
||||
subtitle: string;
|
||||
children: any;
|
||||
};
|
||||
try {
|
||||
const req = await fetch(metadataURL);
|
||||
|
||||
export const TechDocsPageWrapper = ({
|
||||
children,
|
||||
title,
|
||||
subtitle,
|
||||
}: TechDocsPageWrapperProps) => {
|
||||
return (
|
||||
<Page theme={pageTheme.documentation}>
|
||||
<Header title={title} subtitle={subtitle} />
|
||||
<Content>{children}</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
return await req.json();
|
||||
} catch (error) {
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
public async getMkDocsMetaData(docsUrl: any) {
|
||||
const mkDocsMetadata = await this.getMetadataFile(docsUrl);
|
||||
|
||||
if (!mkDocsMetadata) return null;
|
||||
|
||||
return {
|
||||
...mkDocsMetadata,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import {
|
||||
} from '@backstage/backend-common';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { DocsBuilder } from './helpers';
|
||||
import { getLocationForEntity } from '../helpers';
|
||||
|
||||
type RouterOptions = {
|
||||
preparers: PreparerBuilder;
|
||||
@@ -60,6 +61,46 @@ export async function createRouter({
|
||||
}: RouterOptions): Promise<express.Router> {
|
||||
const router = Router();
|
||||
|
||||
router.get('/metadata/mkdocs/*', async (req, res) => {
|
||||
const storageUrl = config.getString('techdocs.storageUrl');
|
||||
const { '0': path } = req.params;
|
||||
|
||||
const metadataURL = `${storageUrl}/${path}/techdocs_metadata.json`;
|
||||
|
||||
try {
|
||||
const mkDocsMetadata = await (await fetch(metadataURL)).json();
|
||||
res.send(mkDocsMetadata);
|
||||
} catch (err) {
|
||||
logger.info(
|
||||
`[TechDocs] Unable to get metadata for ${path} with error ${err}`,
|
||||
);
|
||||
throw new Error(`Unable to get metadata for ${path} with error ${err}`);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/metadata/entity/:kind/:namespace/:name', async (req, res) => {
|
||||
const baseUrl = config.getString('backend.baseUrl');
|
||||
const { kind, namespace, name } = req.params;
|
||||
|
||||
try {
|
||||
const entity = (await (
|
||||
await fetch(
|
||||
`${baseUrl}/api/catalog/entities/by-name/${kind}/${namespace}/${name}`,
|
||||
)
|
||||
).json()) as Entity;
|
||||
|
||||
const locationMetadata = getLocationForEntity(entity);
|
||||
res.send({ ...entity, locationMetadata });
|
||||
} catch (err) {
|
||||
logger.info(
|
||||
`[TechDocs] Unable to get metadata for ${kind}/${namespace}/${name} with error ${err}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Unable to get metadata for ${kind}/${namespace}/${name} with error ${err}`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/docs/:kind/:namespace/:name/*', async (req, res) => {
|
||||
const storageUrl = config.getString('techdocs.storageUrl');
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('TechDocsStorageApi', () => {
|
||||
const storageApi = new TechDocsStorageApi({ apiOrigin: DOC_STORAGE_URL });
|
||||
|
||||
expect(storageApi.getBaseUrl('test.js', mockEntity, '')).toEqual(
|
||||
`${DOC_STORAGE_URL}/${mockEntity.kind}/${mockEntity.namespace}/${mockEntity.name}/test.js`,
|
||||
`${DOC_STORAGE_URL}/docs/${mockEntity.kind}/${mockEntity.namespace}/${mockEntity.name}/test.js`,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -36,7 +36,7 @@ describe('TechDocsStorageApi', () => {
|
||||
const storageApi = new TechDocsStorageApi({ apiOrigin: DOC_STORAGE_URL });
|
||||
|
||||
expect(storageApi.getBaseUrl('test/', mockEntity, '')).toEqual(
|
||||
`${DOC_STORAGE_URL}/${mockEntity.kind}/${mockEntity.namespace}/${mockEntity.name}/test/`,
|
||||
`${DOC_STORAGE_URL}/docs/${mockEntity.kind}/${mockEntity.namespace}/${mockEntity.name}/test/`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -23,6 +23,11 @@ export const techdocsStorageApiRef = createApiRef<TechDocsStorageApi>({
|
||||
description: 'Used to make requests towards the techdocs storage',
|
||||
});
|
||||
|
||||
export const techdocsApiRef = createApiRef<TechDocsApi>({
|
||||
id: 'plugin.techdocs.service',
|
||||
description: 'Used to make requests towards techdocs API',
|
||||
});
|
||||
|
||||
export interface TechDocsStorage {
|
||||
getEntityDocs(entityId: ParsedEntityId, path: string): Promise<string>;
|
||||
getBaseUrl(
|
||||
@@ -32,6 +37,31 @@ export interface TechDocsStorage {
|
||||
): string;
|
||||
}
|
||||
|
||||
export interface TechDocs {
|
||||
getMetadata(metadataType: string, entityId: ParsedEntityId): Promise<string>;
|
||||
}
|
||||
|
||||
export class TechDocsApi implements TechDocs {
|
||||
public apiOrigin: string;
|
||||
|
||||
constructor({ apiOrigin }: { apiOrigin: string }) {
|
||||
this.apiOrigin = apiOrigin;
|
||||
}
|
||||
|
||||
async getMetadata(metadataType: string, entityId: ParsedEntityId) {
|
||||
const { kind, namespace, name } = entityId;
|
||||
|
||||
const requestUrl = `${this.apiOrigin}/metadata/${metadataType}/${kind}/${
|
||||
namespace ? namespace : 'default'
|
||||
}/${name}`;
|
||||
|
||||
const request = await fetch(`${requestUrl}`);
|
||||
const res = await request.json();
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
export class TechDocsStorageApi implements TechDocsStorage {
|
||||
public apiOrigin: string;
|
||||
|
||||
@@ -42,7 +72,7 @@ export class TechDocsStorageApi implements TechDocsStorage {
|
||||
async getEntityDocs(entityId: ParsedEntityId, path: string) {
|
||||
const { kind, namespace, name } = entityId;
|
||||
|
||||
const url = `${this.apiOrigin}/${kind}/${
|
||||
const url = `${this.apiOrigin}/docs/${kind}/${
|
||||
namespace ? namespace : 'default'
|
||||
}/${name}/${path}`;
|
||||
|
||||
@@ -66,7 +96,7 @@ export class TechDocsStorageApi implements TechDocsStorage {
|
||||
|
||||
return new URL(
|
||||
oldBaseUrl,
|
||||
`${this.apiOrigin}/${kind}/${
|
||||
`${this.apiOrigin}/docs/${kind}/${
|
||||
namespace ? namespace : 'default'
|
||||
}/${name}/${path}`,
|
||||
).toString();
|
||||
|
||||
@@ -35,7 +35,12 @@ import {
|
||||
createApiFactory,
|
||||
configApiRef,
|
||||
} from '@backstage/core';
|
||||
import { techdocsStorageApiRef, TechDocsStorageApi } from './api';
|
||||
import {
|
||||
techdocsStorageApiRef,
|
||||
TechDocsStorageApi,
|
||||
techdocsApiRef,
|
||||
TechDocsApi,
|
||||
} from './api';
|
||||
|
||||
export const rootRouteRef = createRouteRef({
|
||||
path: '',
|
||||
@@ -63,5 +68,13 @@ export const plugin = createPlugin({
|
||||
apiOrigin: configApi.getString('techdocs.requestUrl'),
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: techdocsApiRef,
|
||||
deps: { configApi: configApiRef },
|
||||
factory: ({ configApi }) =>
|
||||
new TechDocsApi({
|
||||
apiOrigin: configApi.getString('techdocs.requestUrl'),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
@@ -39,9 +39,10 @@ import { TechDocsNotFound } from './TechDocsNotFound';
|
||||
|
||||
type Props = {
|
||||
entityId: ParsedEntityId;
|
||||
onReady?: () => void;
|
||||
};
|
||||
|
||||
export const Reader = ({ entityId }: Props) => {
|
||||
export const Reader = ({ entityId, onReady }: Props) => {
|
||||
const { kind, namespace, name } = entityId;
|
||||
const { '*': path } = useParams();
|
||||
const theme = useTheme<BackstageTheme>();
|
||||
@@ -58,7 +59,9 @@ export const Reader = ({ entityId }: Props) => {
|
||||
if (!shadowRoot || loading || error) {
|
||||
return; // Shadow DOM isn't ready / It's not ready / Docs was not found
|
||||
}
|
||||
|
||||
if (onReady) {
|
||||
onReady();
|
||||
}
|
||||
// Pre-render
|
||||
const transformedElement = transformer(value as string, [
|
||||
sanitizeDOM(),
|
||||
@@ -143,6 +146,7 @@ export const Reader = ({ entityId }: Props) => {
|
||||
navigate,
|
||||
techdocsStorageApi,
|
||||
theme,
|
||||
onReady,
|
||||
]);
|
||||
|
||||
if (error) {
|
||||
|
||||
@@ -18,8 +18,15 @@ import React from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import { useNavigate, generatePath } from 'react-router-dom';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import { ItemCard, Progress, useApi } from '@backstage/core';
|
||||
import { TechDocsPageWrapper } from './TechDocsPageWrapper';
|
||||
import {
|
||||
ItemCard,
|
||||
Progress,
|
||||
useApi,
|
||||
Content,
|
||||
Page,
|
||||
pageTheme,
|
||||
Header,
|
||||
} from '@backstage/core';
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog';
|
||||
import { rootDocsRouteRef } from '../../plugin';
|
||||
|
||||
@@ -36,53 +43,62 @@ export const TechDocsHome = () => {
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<TechDocsPageWrapper
|
||||
title="Documentation"
|
||||
subtitle="Documentation available in Backstage"
|
||||
>
|
||||
<Progress />
|
||||
</TechDocsPageWrapper>
|
||||
<Page theme={pageTheme.documentation}>
|
||||
<Header
|
||||
title="Documentation"
|
||||
subtitle="Documentation available in Backstage"
|
||||
/>
|
||||
<Content>
|
||||
<Progress />
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<TechDocsPageWrapper
|
||||
title="Documentation"
|
||||
subtitle="Documentation available in Backstage"
|
||||
>
|
||||
<p>{error.message}</p>
|
||||
</TechDocsPageWrapper>
|
||||
<Page theme={pageTheme.documentation}>
|
||||
<Header
|
||||
title="Documentation"
|
||||
subtitle="Documentation available in Backstage"
|
||||
/>
|
||||
<Content>
|
||||
<p>{error.message}</p>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TechDocsPageWrapper
|
||||
title="Documentation"
|
||||
subtitle="Documentation available in Backstage"
|
||||
>
|
||||
<Grid container data-testid="docs-explore">
|
||||
{value?.length
|
||||
? value.map((entity, index: number) => (
|
||||
<Grid key={index} item xs={12} sm={6} md={3}>
|
||||
<ItemCard
|
||||
onClick={() =>
|
||||
navigate(
|
||||
generatePath(rootDocsRouteRef.path, {
|
||||
entityId: `${entity.kind}:${
|
||||
entity.metadata.namespace ?? ''
|
||||
}:${entity.metadata.name}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
title={entity.metadata.name}
|
||||
label="Read Docs"
|
||||
description={entity.metadata.description}
|
||||
/>
|
||||
</Grid>
|
||||
))
|
||||
: null}
|
||||
</Grid>
|
||||
</TechDocsPageWrapper>
|
||||
<Page theme={pageTheme.documentation}>
|
||||
<Header
|
||||
title="Documentation"
|
||||
subtitle="Documentation available in Backstage"
|
||||
/>
|
||||
<Content>
|
||||
<Grid container data-testid="docs-explore">
|
||||
{value?.length
|
||||
? value.map((entity, index: number) => (
|
||||
<Grid key={index} item xs={12} sm={6} md={3}>
|
||||
<ItemCard
|
||||
onClick={() =>
|
||||
navigate(
|
||||
generatePath(rootDocsRouteRef.path, {
|
||||
entityId: `${entity.kind}:${
|
||||
entity.metadata.namespace ?? ''
|
||||
}:${entity.metadata.name}`,
|
||||
}),
|
||||
)
|
||||
}
|
||||
title={entity.metadata.name}
|
||||
label="Read Docs"
|
||||
description={entity.metadata.description}
|
||||
/>
|
||||
</Grid>
|
||||
))
|
||||
: null}
|
||||
</Grid>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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 { TechDocsPage } from './TechDocsPage';
|
||||
import { render, act } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
import { ApiRegistry, ApiProvider } from '@backstage/core-api';
|
||||
import {
|
||||
techdocsApiRef,
|
||||
TechDocsApi,
|
||||
techdocsStorageApiRef,
|
||||
TechDocsStorageApi,
|
||||
} from '../../api';
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actual = jest.requireActual('react-router-dom');
|
||||
return {
|
||||
...actual,
|
||||
useParams: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('./TechDocsPageHeader', () => {
|
||||
return {
|
||||
__esModule: true,
|
||||
TechDocsPageHeader: () => <div />,
|
||||
};
|
||||
});
|
||||
|
||||
const { useParams }: { useParams: jest.Mock } = jest.requireMock(
|
||||
'react-router-dom',
|
||||
);
|
||||
|
||||
describe('<TechDocsPage />', () => {
|
||||
it('should render techdocs page', async () => {
|
||||
useParams.mockReturnValue({
|
||||
entityId: 'Component::backstage',
|
||||
});
|
||||
|
||||
const techDocsApi: Partial<TechDocsApi> = {
|
||||
getMetadata: () => Promise.resolve([]),
|
||||
};
|
||||
const techDocsStorageApi: Partial<TechDocsStorageApi> = {
|
||||
getEntityDocs: (): Promise<string> => Promise.resolve('String'),
|
||||
getBaseUrl: (): string => '',
|
||||
};
|
||||
|
||||
const apiRegistry = ApiRegistry.from([
|
||||
[techdocsApiRef, techDocsApi],
|
||||
[techdocsStorageApiRef, techDocsStorageApi],
|
||||
]);
|
||||
|
||||
await act(async () => {
|
||||
const rendered = render(
|
||||
wrapInTestApp(
|
||||
<ApiProvider apis={apiRegistry}>
|
||||
<TechDocsPage />
|
||||
</ApiProvider>,
|
||||
),
|
||||
);
|
||||
expect(rendered.getByTestId('techdocs-content')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -14,26 +14,60 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { TechDocsPageWrapper } from './TechDocsPageWrapper';
|
||||
import { Content, Page, pageTheme, useApi } from '@backstage/core';
|
||||
import { Reader } from './Reader';
|
||||
import { useAsync } from 'react-use';
|
||||
import { TechDocsPageHeader } from './TechDocsPageHeader';
|
||||
import { techdocsApiRef } from '../../api';
|
||||
|
||||
export const TechDocsPage = () => {
|
||||
const [documentReady, setDocumentReady] = useState<boolean>(false);
|
||||
const { entityId } = useParams();
|
||||
|
||||
const [kind, namespace, name] = entityId.split(':');
|
||||
|
||||
const techDocsApi = useApi(techdocsApiRef);
|
||||
|
||||
const mkdocsMetadataRequest = useAsync(() => {
|
||||
if (documentReady) {
|
||||
return techDocsApi.getMetadata('mkdocs', { kind, namespace, name });
|
||||
}
|
||||
|
||||
return Promise.resolve({ loading: true });
|
||||
}, [kind, namespace, name, techDocsApi, documentReady]);
|
||||
|
||||
const entityMetadataRequest = useAsync(() => {
|
||||
return techDocsApi.getMetadata('entity', { kind, namespace, name });
|
||||
}, [kind, namespace, name, techDocsApi]);
|
||||
|
||||
const onReady = () => {
|
||||
setDocumentReady(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<TechDocsPageWrapper title={name} subtitle={name}>
|
||||
<Reader
|
||||
<Page theme={pageTheme.documentation}>
|
||||
<TechDocsPageHeader
|
||||
metadataRequest={{
|
||||
mkdocs: mkdocsMetadataRequest,
|
||||
entity: entityMetadataRequest,
|
||||
}}
|
||||
entityId={{
|
||||
kind,
|
||||
namespace,
|
||||
name,
|
||||
}}
|
||||
/>
|
||||
</TechDocsPageWrapper>
|
||||
<Content data-testid="techdocs-content">
|
||||
<Reader
|
||||
onReady={onReady}
|
||||
entityId={{
|
||||
kind,
|
||||
namespace,
|
||||
name,
|
||||
}}
|
||||
/>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* 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 { TechDocsPageHeader } from './TechDocsPageHeader';
|
||||
import { render, act } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
|
||||
describe('<TechDocsPageHeader />', () => {
|
||||
it('should render a techdocs page header', async () => {
|
||||
await act(async () => {
|
||||
const rendered = render(
|
||||
wrapInTestApp(
|
||||
<TechDocsPageHeader
|
||||
entityId={{
|
||||
kind: 'test',
|
||||
name: 'test-name',
|
||||
namespace: 'test-namespace',
|
||||
}}
|
||||
metadataRequest={{
|
||||
entity: {
|
||||
loading: false,
|
||||
value: {
|
||||
locationMetadata: {
|
||||
type: 'github',
|
||||
target: 'https://example.com/',
|
||||
},
|
||||
spec: {
|
||||
owner: 'test',
|
||||
},
|
||||
},
|
||||
},
|
||||
mkdocs: {
|
||||
loading: false,
|
||||
value: {
|
||||
site_name: 'test-site-name',
|
||||
site_description: 'test-site-desc',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
expect(rendered.container.innerHTML).toContain('header');
|
||||
expect(rendered.getByText('test-site-name')).toBeDefined();
|
||||
expect(rendered.getByText('test-site-desc')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
it('should render a techdocs page header even if metadata is missing', async () => {
|
||||
await act(async () => {
|
||||
const rendered = render(
|
||||
wrapInTestApp(
|
||||
<TechDocsPageHeader
|
||||
entityId={{
|
||||
kind: 'test',
|
||||
name: 'test-name',
|
||||
namespace: 'test-namespace',
|
||||
}}
|
||||
metadataRequest={{
|
||||
entity: {
|
||||
loading: false,
|
||||
},
|
||||
mkdocs: {
|
||||
loading: false,
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
),
|
||||
);
|
||||
|
||||
expect(rendered.container.innerHTML).toContain('header');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* 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 GitHubIcon from '@material-ui/icons/GitHub';
|
||||
import { Header, HeaderLabel, Link } from '@backstage/core';
|
||||
import { CircularProgress } from '@material-ui/core';
|
||||
import { ParsedEntityId } from '../../types';
|
||||
import { AsyncState } from 'react-use/lib/useAsync';
|
||||
|
||||
type TechDocsPageHeaderProps = {
|
||||
entityId: ParsedEntityId;
|
||||
metadataRequest: {
|
||||
entity: AsyncState<any>;
|
||||
mkdocs: AsyncState<any>;
|
||||
};
|
||||
};
|
||||
|
||||
export const TechDocsPageHeader = ({
|
||||
entityId,
|
||||
metadataRequest,
|
||||
}: TechDocsPageHeaderProps) => {
|
||||
const { mkdocs: mkdocsMetadata, entity: entityMetadata } = metadataRequest;
|
||||
|
||||
const { value: mkDocsMetadataValues } = mkdocsMetadata;
|
||||
const { value: entityMetadataValues } = entityMetadata;
|
||||
|
||||
const { kind, name } = entityId;
|
||||
|
||||
const { site_name: siteName, site_description: siteDescription } =
|
||||
mkDocsMetadataValues || {};
|
||||
|
||||
const {
|
||||
locationMetadata,
|
||||
spec: { owner, lifecycle },
|
||||
} = entityMetadataValues || { spec: {} };
|
||||
|
||||
const labels = (
|
||||
<>
|
||||
<HeaderLabel
|
||||
label="Component"
|
||||
value={
|
||||
<Link style={{ color: '#fff' }} to={`/catalog/${kind}/${name}`}>
|
||||
{name}
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
{owner ? <HeaderLabel label="Site Owner" value={owner} /> : null}
|
||||
{lifecycle ? <HeaderLabel label="Lifecycle" value={lifecycle} /> : null}
|
||||
{locationMetadata &&
|
||||
locationMetadata.type !== 'dir' &&
|
||||
locationMetadata.type !== 'file' ? (
|
||||
<HeaderLabel
|
||||
label=""
|
||||
value={
|
||||
<a href={locationMetadata.target} target="_blank">
|
||||
<GitHubIcon style={{ marginTop: '-25px', fill: '#fff' }} />
|
||||
</a>
|
||||
}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
||||
return (
|
||||
<Header
|
||||
title={siteName ? siteName : <CircularProgress />}
|
||||
subtitle={
|
||||
siteDescription && siteDescription !== 'None' ? siteDescription : ''
|
||||
}
|
||||
>
|
||||
{labels}
|
||||
</Header>
|
||||
);
|
||||
};
|
||||
@@ -1,33 +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 { TechDocsPageWrapper } from './TechDocsPageWrapper';
|
||||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import { wrapInTestApp } from '@backstage/test-utils';
|
||||
|
||||
describe('TechDocs Page Wrapper', () => {
|
||||
it('should render a TechDocs Page Wrapper', async () => {
|
||||
const { queryByText } = render(
|
||||
wrapInTestApp(
|
||||
<TechDocsPageWrapper title="test-title" subtitle="test-subtitle">
|
||||
Test
|
||||
</TechDocsPageWrapper>,
|
||||
),
|
||||
);
|
||||
expect(queryByText(/test-title/i)).toBeInTheDocument();
|
||||
expect(queryByText(/test-subtitle/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user