Merge branch 'master' of github.com:backstage/backstage into improve-errors
This commit is contained in:
@@ -1,5 +1,35 @@
|
||||
# @backstage/plugin-techdocs
|
||||
|
||||
## 0.5.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 5fa3bdb55: Add `href` in addition to `onClick` to `ItemCard`. Ensure that the height of a
|
||||
`ItemCard` with and without tags is equal.
|
||||
- e44925723: `techdocs.requestUrl` and `techdocs.storageUrl` are now optional configs and the discovery API will be used to get the URL where techdocs plugin is hosted.
|
||||
- 019fe39a0: Switch dependency from `@backstage/plugin-catalog` to `@backstage/plugin-catalog-react`.
|
||||
- Updated dependencies [c777df180]
|
||||
- Updated dependencies [12ece98cd]
|
||||
- Updated dependencies [d82246867]
|
||||
- Updated dependencies [7fc89bae2]
|
||||
- Updated dependencies [c810082ae]
|
||||
- Updated dependencies [5fa3bdb55]
|
||||
- Updated dependencies [6e612ce25]
|
||||
- Updated dependencies [e44925723]
|
||||
- Updated dependencies [025e122c3]
|
||||
- Updated dependencies [21e624ba9]
|
||||
- Updated dependencies [da9f53c60]
|
||||
- Updated dependencies [32c95605f]
|
||||
- Updated dependencies [7881f2117]
|
||||
- Updated dependencies [f0320190d]
|
||||
- Updated dependencies [54c7d02f7]
|
||||
- Updated dependencies [11cb5ef94]
|
||||
- @backstage/techdocs-common@0.3.7
|
||||
- @backstage/core@0.6.0
|
||||
- @backstage/plugin-catalog-react@0.0.2
|
||||
- @backstage/theme@0.2.3
|
||||
- @backstage/catalog-model@0.7.1
|
||||
|
||||
## 0.5.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
Vendored
+13
-11
@@ -17,17 +17,6 @@
|
||||
export interface Config {
|
||||
/** Configuration options for the techdocs plugin */
|
||||
techdocs: {
|
||||
/**
|
||||
* attr: 'requestUrl' - accepts a string value
|
||||
* e.g. requestUrl: http://localhost:7000/api/techdocs
|
||||
* @visibility frontend
|
||||
*/
|
||||
requestUrl: string;
|
||||
/**
|
||||
* attr: 'storageUrl' - accepts a string value
|
||||
* e.g. storageUrl: http://localhost:7000/api/techdocs/static/docs
|
||||
*/
|
||||
storageUrl: string;
|
||||
/**
|
||||
* documentation building process depends on the builder attr
|
||||
* attr: 'builder' - accepts a string value
|
||||
@@ -184,5 +173,18 @@ export interface Config {
|
||||
credentials?: string;
|
||||
};
|
||||
};
|
||||
/**
|
||||
* attr: 'requestUrl' - accepts a string value
|
||||
* e.g. requestUrl: http://localhost:7000/api/techdocs
|
||||
* @visibility frontend
|
||||
* @deprecated
|
||||
*/
|
||||
requestUrl?: string;
|
||||
/**
|
||||
* attr: 'storageUrl' - accepts a string value
|
||||
* e.g. storageUrl: http://localhost:7000/api/techdocs/static/docs
|
||||
* @deprecated
|
||||
*/
|
||||
storageUrl?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -13,20 +13,38 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { DiscoveryApi } from '@backstage/core';
|
||||
import { Config } from '@backstage/config';
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import { TechDocsStorage } from '../src/api';
|
||||
|
||||
export class TechDocsDevStorageApi implements TechDocsStorage {
|
||||
public apiOrigin: string;
|
||||
public configApi: Config;
|
||||
public discoveryApi: DiscoveryApi;
|
||||
|
||||
constructor({ apiOrigin }: { apiOrigin: string }) {
|
||||
this.apiOrigin = apiOrigin;
|
||||
constructor({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
}: {
|
||||
configApi: Config;
|
||||
discoveryApi: DiscoveryApi;
|
||||
}) {
|
||||
this.configApi = configApi;
|
||||
this.discoveryApi = discoveryApi;
|
||||
}
|
||||
|
||||
async getApiOrigin() {
|
||||
return (
|
||||
this.configApi.getOptionalString('techdocs.requestUrl') ??
|
||||
(await this.discoveryApi.getBaseUrl('techdocs'))
|
||||
);
|
||||
}
|
||||
|
||||
async getEntityDocs(entityId: EntityName, path: string) {
|
||||
const { name } = entityId;
|
||||
|
||||
const url = `${this.apiOrigin}/${name}/${path}`;
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
const url = `${apiOrigin}/${name}/${path}`;
|
||||
|
||||
const request = await fetch(
|
||||
`${url.endsWith('/') ? url : `${url}/`}index.html`,
|
||||
@@ -39,8 +57,13 @@ export class TechDocsDevStorageApi implements TechDocsStorage {
|
||||
return request.text();
|
||||
}
|
||||
|
||||
getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): string {
|
||||
async getBaseUrl(
|
||||
oldBaseUrl: string,
|
||||
entityId: EntityName,
|
||||
path: string,
|
||||
): Promise<string> {
|
||||
const { name } = entityId;
|
||||
return new URL(oldBaseUrl, `${this.apiOrigin}/${name}/${path}`).toString();
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
return new URL(oldBaseUrl, `${apiOrigin}/${name}/${path}`).toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,19 +14,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { configApiRef, discoveryApiRef } from '@backstage/core';
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { plugin } from '../src/plugin';
|
||||
import { techdocsPlugin } from '../src/plugin';
|
||||
import { TechDocsDevStorageApi } from './api';
|
||||
import { techdocsStorageApiRef } from '../src';
|
||||
|
||||
createDevApp()
|
||||
.registerApi({
|
||||
api: techdocsStorageApiRef,
|
||||
deps: {},
|
||||
factory: () =>
|
||||
deps: { configApi: configApiRef, discoveryApi: discoveryApiRef },
|
||||
factory: ({ configApi, discoveryApi }) =>
|
||||
new TechDocsDevStorageApi({
|
||||
apiOrigin: 'http://localhost:3000/api',
|
||||
configApi,
|
||||
discoveryApi,
|
||||
}),
|
||||
})
|
||||
.registerPlugin(plugin)
|
||||
.registerPlugin(techdocsPlugin)
|
||||
.render();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@backstage/plugin-techdocs",
|
||||
"version": "0.5.4",
|
||||
"version": "0.5.5",
|
||||
"main": "src/index.ts",
|
||||
"types": "src/index.ts",
|
||||
"license": "Apache-2.0",
|
||||
@@ -31,12 +31,13 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/catalog-model": "^0.7.0",
|
||||
"@backstage/core": "^0.5.0",
|
||||
"@backstage/plugin-catalog-react": "^0.0.1",
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/catalog-model": "^0.7.1",
|
||||
"@backstage/core": "^0.6.0",
|
||||
"@backstage/plugin-catalog-react": "^0.0.2",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@backstage/theme": "^0.2.2",
|
||||
"@backstage/techdocs-common": "^0.3.6",
|
||||
"@backstage/theme": "^0.2.3",
|
||||
"@backstage/techdocs-common": "^0.3.7",
|
||||
"@material-ui/core": "^4.11.0",
|
||||
"@material-ui/icons": "^4.9.1",
|
||||
"@material-ui/lab": "4.0.0-alpha.45",
|
||||
@@ -49,8 +50,8 @@
|
||||
"sanitize-html": "^1.27.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.5.0",
|
||||
"@backstage/dev-utils": "^0.1.8",
|
||||
"@backstage/cli": "^0.6.0",
|
||||
"@backstage/dev-utils": "^0.1.9",
|
||||
"@backstage/test-utils": "^0.1.6",
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^10.4.1",
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Entity } from '@backstage/catalog-model';
|
||||
import { useEntity } from '@backstage/plugin-catalog-react';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { MissingAnnotationEmptyState } from '@backstage/core';
|
||||
import {
|
||||
@@ -38,7 +39,14 @@ export const Router = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export const EmbeddedDocsRouter = ({ entity }: { entity: Entity }) => {
|
||||
type Props = {
|
||||
/** @deprecated The entity is now grabbed from context instead */
|
||||
entity?: Entity;
|
||||
};
|
||||
|
||||
export const EmbeddedDocsRouter = (_props: Props) => {
|
||||
const { entity } = useEntity();
|
||||
|
||||
const projectId = entity.metadata.annotations?.[TECHDOCS_ANNOTATION];
|
||||
|
||||
if (!projectId) {
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Config } from '@backstage/config';
|
||||
import { UrlPatternDiscovery } from '@backstage/core';
|
||||
import { TechDocsStorageApi } from './api';
|
||||
|
||||
const DOC_STORAGE_URL = 'https://example-storage.com';
|
||||
|
||||
const mockEntity = {
|
||||
kind: 'Component',
|
||||
namespace: 'default',
|
||||
@@ -24,19 +24,31 @@ const mockEntity = {
|
||||
};
|
||||
|
||||
describe('TechDocsStorageApi', () => {
|
||||
it('should return correct base url based on defined storage', () => {
|
||||
const storageApi = new TechDocsStorageApi({ apiOrigin: DOC_STORAGE_URL });
|
||||
const mockBaseUrl = 'http://backstage:9191/api/techdocs';
|
||||
const configApi = {
|
||||
getOptionalString: () => 'http://backstage:9191/api/techdocs',
|
||||
} as Partial<Config>;
|
||||
const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl);
|
||||
|
||||
expect(storageApi.getBaseUrl('test.js', mockEntity, '')).toEqual(
|
||||
`${DOC_STORAGE_URL}/docs/${mockEntity.namespace}/${mockEntity.kind}/${mockEntity.name}/test.js`,
|
||||
it('should return correct base url based on defined storage', async () => {
|
||||
// @ts-ignore Partial<Config> not assignable to Config.
|
||||
const storageApi = new TechDocsStorageApi({ configApi, discoveryApi });
|
||||
|
||||
await expect(
|
||||
storageApi.getBaseUrl('test.js', mockEntity, ''),
|
||||
).resolves.toEqual(
|
||||
`${mockBaseUrl}/docs/${mockEntity.namespace}/${mockEntity.kind}/${mockEntity.name}/test.js`,
|
||||
);
|
||||
});
|
||||
|
||||
it('should return base url with correct entity structure', () => {
|
||||
const storageApi = new TechDocsStorageApi({ apiOrigin: DOC_STORAGE_URL });
|
||||
it('should return base url with correct entity structure', async () => {
|
||||
// @ts-ignore Partial<Config> not assignable to Config.
|
||||
const storageApi = new TechDocsStorageApi({ configApi, discoveryApi });
|
||||
|
||||
expect(storageApi.getBaseUrl('test/', mockEntity, '')).toEqual(
|
||||
`${DOC_STORAGE_URL}/docs/${mockEntity.namespace}/${mockEntity.kind}/${mockEntity.name}/test/`,
|
||||
await expect(
|
||||
storageApi.getBaseUrl('test/', mockEntity, ''),
|
||||
).resolves.toEqual(
|
||||
`${mockBaseUrl}/docs/${mockEntity.namespace}/${mockEntity.kind}/${mockEntity.name}/test/`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+74
-21
@@ -14,7 +14,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { createApiRef } from '@backstage/core';
|
||||
import { createApiRef, DiscoveryApi } from '@backstage/core';
|
||||
import { Config } from '@backstage/config';
|
||||
import { EntityName } from '@backstage/catalog-model';
|
||||
import { TechDocsMetadata } from './types';
|
||||
|
||||
@@ -30,7 +31,11 @@ export const techdocsApiRef = createApiRef<TechDocsApi>({
|
||||
|
||||
export interface TechDocsStorage {
|
||||
getEntityDocs(entityId: EntityName, path: string): Promise<string>;
|
||||
getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): string;
|
||||
getBaseUrl(
|
||||
oldBaseUrl: string,
|
||||
entityId: EntityName,
|
||||
path: string,
|
||||
): Promise<string>;
|
||||
}
|
||||
|
||||
export interface TechDocs {
|
||||
@@ -44,10 +49,25 @@ export interface TechDocs {
|
||||
* @property {string} apiOrigin Set to techdocs.requestUrl as the URL for techdocs-backend API
|
||||
*/
|
||||
export class TechDocsApi implements TechDocs {
|
||||
public apiOrigin: string;
|
||||
public configApi: Config;
|
||||
public discoveryApi: DiscoveryApi;
|
||||
|
||||
constructor({ apiOrigin }: { apiOrigin: string }) {
|
||||
this.apiOrigin = apiOrigin;
|
||||
constructor({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
}: {
|
||||
configApi: Config;
|
||||
discoveryApi: DiscoveryApi;
|
||||
}) {
|
||||
this.configApi = configApi;
|
||||
this.discoveryApi = discoveryApi;
|
||||
}
|
||||
|
||||
async getApiOrigin() {
|
||||
return (
|
||||
this.configApi.getOptionalString('techdocs.requestUrl') ??
|
||||
(await this.discoveryApi.getBaseUrl('techdocs'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -62,7 +82,8 @@ export class TechDocsApi implements TechDocs {
|
||||
async getTechDocsMetadata(entityId: EntityName) {
|
||||
const { kind, namespace, name } = entityId;
|
||||
|
||||
const requestUrl = `${this.apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`;
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
const requestUrl = `${apiOrigin}/metadata/techdocs/${namespace}/${kind}/${name}`;
|
||||
|
||||
const request = await fetch(`${requestUrl}`);
|
||||
const res = await request.json();
|
||||
@@ -81,7 +102,8 @@ export class TechDocsApi implements TechDocs {
|
||||
async getEntityMetadata(entityId: EntityName) {
|
||||
const { kind, namespace, name } = entityId;
|
||||
|
||||
const requestUrl = `${this.apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`;
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
const requestUrl = `${apiOrigin}/metadata/entity/${namespace}/${kind}/${name}`;
|
||||
|
||||
const request = await fetch(`${requestUrl}`);
|
||||
const res = await request.json();
|
||||
@@ -96,10 +118,25 @@ export class TechDocsApi implements TechDocs {
|
||||
* @property {string} apiOrigin Set to techdocs.requestUrl as the URL for techdocs-backend API
|
||||
*/
|
||||
export class TechDocsStorageApi implements TechDocsStorage {
|
||||
public apiOrigin: string;
|
||||
public configApi: Config;
|
||||
public discoveryApi: DiscoveryApi;
|
||||
|
||||
constructor({ apiOrigin }: { apiOrigin: string }) {
|
||||
this.apiOrigin = apiOrigin;
|
||||
constructor({
|
||||
configApi,
|
||||
discoveryApi,
|
||||
}: {
|
||||
configApi: Config;
|
||||
discoveryApi: DiscoveryApi;
|
||||
}) {
|
||||
this.configApi = configApi;
|
||||
this.discoveryApi = discoveryApi;
|
||||
}
|
||||
|
||||
async getApiOrigin() {
|
||||
return (
|
||||
this.configApi.getOptionalString('techdocs.requestUrl') ??
|
||||
(await this.discoveryApi.getBaseUrl('techdocs'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -113,31 +150,47 @@ export class TechDocsStorageApi implements TechDocsStorage {
|
||||
async getEntityDocs(entityId: EntityName, path: string) {
|
||||
const { kind, namespace, name } = entityId;
|
||||
|
||||
const url = `${this.apiOrigin}/docs/${namespace}/${kind}/${name}/${path}`;
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
const url = `${apiOrigin}/docs/${namespace}/${kind}/${name}/${path}`;
|
||||
|
||||
const request = await fetch(
|
||||
`${url.endsWith('/') ? url : `${url}/`}index.html`,
|
||||
);
|
||||
|
||||
if (request.status === 404) {
|
||||
let errorMessage = 'Page not found. ';
|
||||
// path is empty for the home page of an entity's docs site
|
||||
if (!path) {
|
||||
errorMessage +=
|
||||
'This could be because there is no index.md file in the root of the docs directory of this repository.';
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
let errorMessage = '';
|
||||
switch (request.status) {
|
||||
case 404:
|
||||
errorMessage = 'Page not found. ';
|
||||
// path is empty for the home page of an entity's docs site
|
||||
if (!path) {
|
||||
errorMessage +=
|
||||
'This could be because there is no index.md file in the root of the docs directory of this repository.';
|
||||
}
|
||||
throw new Error(errorMessage);
|
||||
case 500:
|
||||
errorMessage =
|
||||
'Could not generate documentation or an error in the TechDocs backend. ';
|
||||
throw new Error(errorMessage);
|
||||
default:
|
||||
// Do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
return request.text();
|
||||
}
|
||||
|
||||
getBaseUrl(oldBaseUrl: string, entityId: EntityName, path: string): string {
|
||||
async getBaseUrl(
|
||||
oldBaseUrl: string,
|
||||
entityId: EntityName,
|
||||
path: string,
|
||||
): Promise<string> {
|
||||
const { kind, namespace, name } = entityId;
|
||||
|
||||
const apiOrigin = await this.getApiOrigin();
|
||||
|
||||
return new URL(
|
||||
oldBaseUrl,
|
||||
`${this.apiOrigin}/docs/${namespace}/${kind}/${name}/${path}`,
|
||||
`${apiOrigin}/docs/${namespace}/${kind}/${name}/${path}`,
|
||||
).toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { plugin } from './plugin';
|
||||
export {
|
||||
techdocsPlugin,
|
||||
techdocsPlugin as plugin,
|
||||
TechdocsPage,
|
||||
EntityTechdocsContent,
|
||||
} from './plugin';
|
||||
export { Router, EmbeddedDocsRouter } from './Router';
|
||||
export * from './reader';
|
||||
export * from './api';
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { plugin } from './plugin';
|
||||
import { techdocsPlugin } from './plugin';
|
||||
|
||||
describe('techdocs', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(plugin).toBeDefined();
|
||||
expect(techdocsPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -34,6 +34,8 @@ import {
|
||||
createRouteRef,
|
||||
createApiFactory,
|
||||
configApiRef,
|
||||
discoveryApiRef,
|
||||
createRoutableExtension,
|
||||
} from '@backstage/core';
|
||||
import {
|
||||
techdocsStorageApiRef,
|
||||
@@ -57,25 +59,44 @@ export const rootCatalogDocsRouteRef = createRouteRef({
|
||||
title: 'Docs',
|
||||
});
|
||||
|
||||
// TODO: Use discovery API for frontend to get URL for techdocs-backend instead of requestUrl
|
||||
export const plugin = createPlugin({
|
||||
export const techdocsPlugin = createPlugin({
|
||||
id: 'techdocs',
|
||||
apis: [
|
||||
createApiFactory({
|
||||
api: techdocsStorageApiRef,
|
||||
deps: { configApi: configApiRef },
|
||||
factory: ({ configApi }) =>
|
||||
deps: { configApi: configApiRef, discoveryApi: discoveryApiRef },
|
||||
factory: ({ configApi, discoveryApi }) =>
|
||||
new TechDocsStorageApi({
|
||||
apiOrigin: configApi.getString('techdocs.requestUrl'),
|
||||
configApi,
|
||||
discoveryApi,
|
||||
}),
|
||||
}),
|
||||
createApiFactory({
|
||||
api: techdocsApiRef,
|
||||
deps: { configApi: configApiRef },
|
||||
factory: ({ configApi }) =>
|
||||
deps: { configApi: configApiRef, discoveryApi: discoveryApiRef },
|
||||
factory: ({ configApi, discoveryApi }) =>
|
||||
new TechDocsApi({
|
||||
apiOrigin: configApi.getString('techdocs.requestUrl'),
|
||||
configApi,
|
||||
discoveryApi,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
root: rootRouteRef,
|
||||
entityContent: rootCatalogDocsRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
export const TechdocsPage = techdocsPlugin.provide(
|
||||
createRoutableExtension({
|
||||
component: () => import('./Router').then(m => m.Router),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
export const EntityTechdocsContent = techdocsPlugin.provide(
|
||||
createRoutableExtension({
|
||||
component: () => import('./Router').then(m => m.EmbeddedDocsRouter),
|
||||
mountPoint: rootCatalogDocsRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -129,7 +129,7 @@ export const Reader = ({ entityId, onReady }: Props) => {
|
||||
},
|
||||
}),
|
||||
onCssReady({
|
||||
docStorageUrl: techdocsStorageApi.apiOrigin,
|
||||
docStorageUrl: techdocsStorageApi.getApiOrigin(),
|
||||
onLoading: (dom: Element) => {
|
||||
(dom as HTMLElement).style.setProperty('opacity', '0');
|
||||
},
|
||||
@@ -155,7 +155,9 @@ export const Reader = ({ entityId, onReady }: Props) => {
|
||||
]);
|
||||
|
||||
if (error) {
|
||||
return <TechDocsNotFound errorMessage={error.message} />;
|
||||
// TODO Enhance API call to return customize error objects so we can identify which we ran into
|
||||
// For now this defaults to display error code 404
|
||||
return <TechDocsNotFound statusCode={404} errorMessage={error.message} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
||||
@@ -27,13 +27,12 @@ import {
|
||||
import { catalogApiRef } from '@backstage/plugin-catalog-react';
|
||||
import { Grid } from '@material-ui/core';
|
||||
import React from 'react';
|
||||
import { generatePath, useNavigate } from 'react-router-dom';
|
||||
import { generatePath } from 'react-router-dom';
|
||||
import { useAsync } from 'react-use';
|
||||
import { rootDocsRouteRef } from '../../plugin';
|
||||
|
||||
export const TechDocsHome = () => {
|
||||
const catalogApi = useApi(catalogApiRef);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { value, loading, error } = useAsync(async () => {
|
||||
const response = await catalogApi.getEntities();
|
||||
@@ -87,15 +86,11 @@ export const TechDocsHome = () => {
|
||||
? value.map((entity, index: number) => (
|
||||
<Grid key={index} item xs={12} sm={6} md={3}>
|
||||
<ItemCard
|
||||
onClick={() =>
|
||||
navigate(
|
||||
generatePath(rootDocsRouteRef.path, {
|
||||
namespace: entity.metadata.namespace ?? 'default',
|
||||
kind: entity.kind,
|
||||
name: entity.metadata.name,
|
||||
}),
|
||||
)
|
||||
}
|
||||
href={generatePath(rootDocsRouteRef.path, {
|
||||
namespace: entity.metadata.namespace ?? 'default',
|
||||
kind: entity.kind,
|
||||
name: entity.metadata.name,
|
||||
})}
|
||||
title={entity.metadata.name}
|
||||
label="Read Docs"
|
||||
description={entity.metadata.description}
|
||||
|
||||
@@ -41,3 +41,20 @@ describe('<TechDocsNotFound errorMessage="This is a custom error message" />', (
|
||||
expect(rendered.getByTestId('go-back-link')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('<TechDocsNotFound statusCode={500} errorMessage="This is a custom error message" />', () => {
|
||||
it('should render with a custom status code, custom error message and go back link', () => {
|
||||
const rendered = render(
|
||||
wrapInTestApp(
|
||||
<TechDocsNotFound
|
||||
statusCode={500}
|
||||
errorMessage="This is a custom error message"
|
||||
/>,
|
||||
),
|
||||
);
|
||||
rendered.getByText(/This is a custom error message/i);
|
||||
rendered.getByText(/500/i);
|
||||
rendered.getByText(/Looks like someone dropped the mic!/i);
|
||||
expect(rendered.getByTestId('go-back-link')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,9 +19,10 @@ import { ErrorPage, useApi, configApiRef } from '@backstage/core';
|
||||
|
||||
type Props = {
|
||||
errorMessage?: string;
|
||||
statusCode?: number;
|
||||
};
|
||||
|
||||
export const TechDocsNotFound = ({ errorMessage }: Props) => {
|
||||
export const TechDocsNotFound = ({ errorMessage, statusCode }: Props) => {
|
||||
const techdocsBuilder = useApi(configApiRef).getOptionalString(
|
||||
'techdocs.builder',
|
||||
);
|
||||
@@ -37,7 +38,7 @@ export const TechDocsNotFound = ({ errorMessage }: Props) => {
|
||||
|
||||
return (
|
||||
<ErrorPage
|
||||
status="404"
|
||||
status={statusCode ? statusCode.toString() : '404'}
|
||||
statusMessage={errorMessage || 'Documentation not found'}
|
||||
additionalInfo={additionalInfo}
|
||||
/>
|
||||
|
||||
@@ -56,7 +56,8 @@ describe('<TechDocsPage />', () => {
|
||||
};
|
||||
const techdocsStorageApi: Partial<TechDocsStorageApi> = {
|
||||
getEntityDocs: (): Promise<string> => Promise.resolve('String'),
|
||||
getBaseUrl: (): string => '',
|
||||
getBaseUrl: (): Promise<string> => Promise.resolve('String'),
|
||||
getApiOrigin: (): Promise<string> => Promise.resolve('String'),
|
||||
};
|
||||
|
||||
const apiRegistry = ApiRegistry.from([
|
||||
|
||||
@@ -21,7 +21,7 @@ import { TechDocsStorage } from '../../api';
|
||||
const DOC_STORAGE_URL = 'https://example-host.storage.googleapis.com';
|
||||
|
||||
const techdocsStorageApi: TechDocsStorage = {
|
||||
getBaseUrl: jest.fn(() => DOC_STORAGE_URL),
|
||||
getBaseUrl: jest.fn(() => Promise.resolve(DOC_STORAGE_URL)),
|
||||
getEntityDocs: () => new Promise(resolve => resolve('yes!')),
|
||||
};
|
||||
|
||||
|
||||
@@ -35,12 +35,12 @@ export const addBaseUrl = ({
|
||||
): void => {
|
||||
Array.from(list)
|
||||
.filter(elem => !!elem.getAttribute(attributeName))
|
||||
.forEach((elem: T) => {
|
||||
.forEach(async (elem: T) => {
|
||||
const elemAttribute = elem.getAttribute(attributeName);
|
||||
if (!elemAttribute) return;
|
||||
elem.setAttribute(
|
||||
attributeName,
|
||||
techdocsStorageApi.getBaseUrl(elemAttribute, entityId, path),
|
||||
await techdocsStorageApi.getBaseUrl(elemAttribute, entityId, path),
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -22,8 +22,9 @@ import {
|
||||
} from '../../test-utils';
|
||||
import { onCssReady } from '../transformers';
|
||||
|
||||
const docStorageUrl: string =
|
||||
'https://techdocs-mock-sites.storage.googleapis.com';
|
||||
const docStorageUrl: Promise<string> = Promise.resolve(
|
||||
'https://techdocs-mock-sites.storage.googleapis.com',
|
||||
);
|
||||
|
||||
const fixture = `
|
||||
<link rel="stylesheet" href="${docStorageUrl}/test.css" />
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import type { Transformer } from './index';
|
||||
|
||||
type OnCssReadyOptions = {
|
||||
docStorageUrl: string;
|
||||
docStorageUrl: Promise<string>;
|
||||
onLoading: (dom: Element) => void;
|
||||
onLoaded: (dom: Element) => void;
|
||||
};
|
||||
@@ -30,7 +30,9 @@ export const onCssReady = ({
|
||||
return dom => {
|
||||
const cssPages = Array.from(
|
||||
dom.querySelectorAll('head > link[rel="stylesheet"]'),
|
||||
).filter(elem => elem.getAttribute('href')?.startsWith(docStorageUrl));
|
||||
).filter(async elem =>
|
||||
elem.getAttribute('href')?.startsWith(await docStorageUrl),
|
||||
);
|
||||
|
||||
let count = cssPages.length;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user