Merge branch 'master' of https://github.com/spotify/backstage into lintMod
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/create-app': patch
|
||||
---
|
||||
|
||||
Remove techdocs.requestUrl and techdocs.storageUrl from app-config.yaml
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-catalog-import': patch
|
||||
---
|
||||
|
||||
Migrated to new composability API, exporting the plugin instance as `catalogImportPlugin`, and the page as `CatalogImportPage`.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-lighthouse': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `lighthousePlugin`, the top-level page as `LighthousePage`, the entity card as `EntityLastLighthouseAuditCard`, and the entity content as `EntityLighthouseContent`.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
'@backstage/techdocs-common': patch
|
||||
'@backstage/plugin-techdocs': patch
|
||||
'@backstage/plugin-techdocs-backend': patch
|
||||
---
|
||||
|
||||
`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.
|
||||
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'@backstage/plugin-org': patch
|
||||
---
|
||||
|
||||
Migrate to new composability API, exporting the plugin instance as `orgPlugin`, and the entity cards as `EntityGroupProfileCard`, `EntityMembersListCard`, `EntityOwnershipCard`, and `EntityUserProfileCard`.
|
||||
@@ -74,8 +74,6 @@ organization:
|
||||
|
||||
# Reference documentation http://backstage.io/docs/features/techdocs/configuration
|
||||
techdocs:
|
||||
requestUrl: http://localhost:7000/api/techdocs
|
||||
storageUrl: http://localhost:7000/api/techdocs/static/docs
|
||||
builder: 'local' # Alternatives - 'external'
|
||||
generators:
|
||||
techdocs: 'docker' # Alternatives - 'local'
|
||||
|
||||
@@ -13,15 +13,6 @@ configuration options for TechDocs.
|
||||
# File: app-config.yaml
|
||||
|
||||
techdocs:
|
||||
# TechDocs makes API calls to techdocs-backend using this URL. e.g. get docs of an entity, get metadata, etc.
|
||||
|
||||
requestUrl: http://localhost:7000/api/techdocs
|
||||
|
||||
# Just another route in techdocs-backend where TechDocs requests the static files from. This URL uses an HTTP middleware
|
||||
# to serve files from either a local directory or an External storage provider.
|
||||
|
||||
storageUrl: http://localhost:7000/api/techdocs/static/docs
|
||||
|
||||
# generators.techdocs can have two values: 'docker' or 'local'. This is to determine how to run the generator - whether to
|
||||
# spin up the techdocs-container docker image or to run mkdocs locally (assuming all the dependencies are taken care of).
|
||||
# You want to change this to 'local' if you are running Backstage using your own custom Docker setup and want to avoid running
|
||||
@@ -101,4 +92,15 @@ techdocs:
|
||||
# https://docs.microsoft.com/en-us/azure/storage/common/storage-auth?toc=/azure/storage/blobs/toc.json
|
||||
accountKey:
|
||||
$env: TECHDOCS_AZURE_BLOB_STORAGE_ACCOUNT_KEY
|
||||
|
||||
# (Optional and Legacy) TechDocs makes API calls to techdocs-backend using this URL. e.g. get docs of an entity, get metadata, etc.
|
||||
# You don't have to specify this anymore.
|
||||
|
||||
requestUrl: http://localhost:7000/api/techdocs
|
||||
|
||||
# (Optional and Legacy) Just another route in techdocs-backend where TechDocs requests the static files from. This URL uses an HTTP middleware
|
||||
# to serve files from either a local directory or an External storage provider.
|
||||
# You don't have to specify this anymore.
|
||||
|
||||
storageUrl: http://localhost:7000/api/techdocs/static/docs
|
||||
```
|
||||
|
||||
@@ -150,26 +150,10 @@ app. Now let us tweak some configurations to suit your needs.
|
||||
**See [TechDocs Configuration Options](configuration.md) for complete
|
||||
configuration reference.**
|
||||
|
||||
### Setting TechDocs URLs
|
||||
|
||||
```yaml
|
||||
techdocs:
|
||||
storageUrl: http://localhost:7000/api/techdocs/static/docs
|
||||
requestUrl: http://localhost:7000/api/techdocs/
|
||||
```
|
||||
|
||||
`requestUrl` is used by TechDocs frontend plugin to discover `techdocs-backend`
|
||||
endpoints, and the `storageUrl` is another endpoint in `techdocs-backend` which
|
||||
acts as a middleware between TechDocs and the storage (where the static
|
||||
generated docs site are stored). These default values should mostly work for
|
||||
you. These options will soon be optional to set.
|
||||
|
||||
### Should TechDocs Backend generate docs?
|
||||
|
||||
```yaml
|
||||
techdocs:
|
||||
storageUrl: http://localhost:7000/api/techdocs/static/docs
|
||||
requestUrl: http://localhost:7000/api/techdocs/
|
||||
builder: 'local'
|
||||
```
|
||||
|
||||
@@ -196,8 +180,6 @@ out Backstage for the first time. At a later time, review
|
||||
|
||||
```yaml
|
||||
techdocs:
|
||||
storageUrl: http://localhost:7000/api/techdocs/static/docs
|
||||
requestUrl: http://localhost:7000/api/techdocs/
|
||||
builder: 'local'
|
||||
publisher:
|
||||
type: 'local'
|
||||
@@ -219,6 +201,9 @@ no config is provided.
|
||||
|
||||
```yaml
|
||||
techdocs:
|
||||
builder: 'local'
|
||||
publisher:
|
||||
type: 'local'
|
||||
generators:
|
||||
techdocs: local
|
||||
```
|
||||
|
||||
@@ -56,14 +56,13 @@ proxy:
|
||||
target: 'https://example.com'
|
||||
changeOrigin: true
|
||||
|
||||
# Reference documentation http://backstage.io/docs/features/techdocs/configuration
|
||||
techdocs:
|
||||
requestUrl: http://localhost:7000/api/techdocs
|
||||
storageUrl: http://localhost:7000/api/techdocs/static/docs
|
||||
builder: 'local'
|
||||
builder: 'local' # Alternatives - 'external'
|
||||
generators:
|
||||
techdocs: 'docker'
|
||||
techdocs: 'docker' # Alternatives - 'local'
|
||||
publisher:
|
||||
type: 'local'
|
||||
type: 'local' # Alternatives - 'googleGcs' or 'awsS3'. Read documentation for using alternatives.
|
||||
|
||||
auth:
|
||||
# see https://backstage.io/docs/tutorials/quickstart-app-auth to know more about enabling auth providers
|
||||
|
||||
@@ -13,34 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-restricted-syntax */
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import {
|
||||
getVoidLogger,
|
||||
PluginEndpointDiscovery,
|
||||
} from '@backstage/backend-common';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import mockFs from 'mock-fs';
|
||||
import * as os from 'os';
|
||||
import { LocalPublish } from './local';
|
||||
|
||||
jest.mock('fs-extra', () => {
|
||||
const fsOriginal = jest.requireActual('fs-extra');
|
||||
return {
|
||||
...fsOriginal,
|
||||
access: jest.fn().mockImplementation((paths, checkType, callback) => {
|
||||
if (
|
||||
paths.includes('http://localhost:7000/static') &&
|
||||
checkType === fs.constants.F_OK
|
||||
) {
|
||||
callback();
|
||||
} else {
|
||||
callback(new Error());
|
||||
}
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
const createMockEntity = (annotations = {}) => {
|
||||
return {
|
||||
apiVersion: 'version',
|
||||
@@ -56,43 +37,33 @@ const createMockEntity = (annotations = {}) => {
|
||||
|
||||
const logger = getVoidLogger();
|
||||
|
||||
const tmpDir =
|
||||
os.platform() === 'win32' ? 'C:\\tmp\\generatedDir' : '/tmp/generatedDir';
|
||||
|
||||
describe('local publisher', () => {
|
||||
it('should publish generated documentation dir', async () => {
|
||||
const testDiscovery: jest.Mocked<PluginEndpointDiscovery> = {
|
||||
getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7000'),
|
||||
getExternalBaseUrl: jest.fn(),
|
||||
};
|
||||
|
||||
const mockConfig = new ConfigReader({
|
||||
techdocs: {
|
||||
requestUrl: 'http://localhost:7000',
|
||||
storageUrl: 'http://localhost:7000/static/docs',
|
||||
mockFs({
|
||||
[tmpDir]: {
|
||||
'index.html': '',
|
||||
},
|
||||
});
|
||||
|
||||
const testDiscovery: jest.Mocked<PluginEndpointDiscovery> = {
|
||||
getBaseUrl: jest
|
||||
.fn()
|
||||
.mockResolvedValue('http://localhost:7000/api/techdocs'),
|
||||
getExternalBaseUrl: jest.fn(),
|
||||
};
|
||||
|
||||
const mockConfig = new ConfigReader({});
|
||||
|
||||
const publisher = new LocalPublish(mockConfig, logger, testDiscovery);
|
||||
const mockEntity = createMockEntity();
|
||||
const tempDir = fs.mkdtempSync(`${__dirname}/test-component-folder-`);
|
||||
expect(tempDir).toBeTruthy();
|
||||
|
||||
fs.closeSync(fs.openSync(path.join(tempDir, '/mock-file'), 'w'));
|
||||
await publisher.publish({ entity: mockEntity, directory: tempDir });
|
||||
|
||||
const publishDir = path.resolve(
|
||||
__dirname,
|
||||
`../../../../../plugins/techdocs-backend/static/docs/${mockEntity.metadata.name}`,
|
||||
);
|
||||
const resultDir = path.resolve(
|
||||
__dirname,
|
||||
`../../../../../plugins/techdocs-backend/static/docs/default/${mockEntity.kind}/${mockEntity.metadata.name}`,
|
||||
);
|
||||
|
||||
expect(fs.existsSync(resultDir)).toBeTruthy();
|
||||
expect(fs.existsSync(path.join(resultDir, '/mock-file'))).toBeTruthy();
|
||||
await publisher.publish({ entity: mockEntity, directory: tmpDir });
|
||||
|
||||
expect(await publisher.hasDocsBeenGenerated(mockEntity)).toBe(true);
|
||||
|
||||
fs.removeSync(publishDir);
|
||||
fs.removeSync(tempDir);
|
||||
mockFs.restore();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,18 +13,17 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import fetch from 'cross-fetch';
|
||||
import {
|
||||
PluginEndpointDiscovery,
|
||||
resolvePackagePath,
|
||||
} from '@backstage/backend-common';
|
||||
import { Entity, EntityName } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import express from 'express';
|
||||
import fs from 'fs-extra';
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import path from 'path';
|
||||
import { Logger } from 'winston';
|
||||
import { Entity, EntityName } from '@backstage/catalog-model';
|
||||
import {
|
||||
resolvePackagePath,
|
||||
PluginEndpointDiscovery,
|
||||
} from '@backstage/backend-common';
|
||||
import { Config } from '@backstage/config';
|
||||
import {
|
||||
PublisherBase,
|
||||
PublishRequest,
|
||||
@@ -52,17 +51,14 @@ try {
|
||||
* called "static" at the root of techdocs-backend plugin.
|
||||
*/
|
||||
export class LocalPublish implements PublisherBase {
|
||||
private readonly config: Config;
|
||||
private readonly logger: Logger;
|
||||
private readonly discovery: PluginEndpointDiscovery;
|
||||
|
||||
// TODO: Use a static fromConfig method to create a LocalPublish instance, similar to aws/gcs publishers.
|
||||
// Move the logic of setting staticDocsDir based on config over to fromConfig,
|
||||
// and set the value as a class parameter.
|
||||
constructor(
|
||||
config: Config,
|
||||
logger: Logger,
|
||||
discovery: PluginEndpointDiscovery,
|
||||
// @ts-ignore
|
||||
private readonly config: Config,
|
||||
private readonly logger: Logger,
|
||||
private readonly discovery: PluginEndpointDiscovery,
|
||||
) {
|
||||
this.config = config;
|
||||
this.logger = logger;
|
||||
@@ -107,34 +103,25 @@ export class LocalPublish implements PublisherBase {
|
||||
});
|
||||
}
|
||||
|
||||
fetchTechDocsMetadata(entityName: EntityName): Promise<TechDocsMetadata> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.discovery.getBaseUrl('techdocs').then(techdocsApiUrl => {
|
||||
const storageUrl = new URL(
|
||||
new URL(this.config.getString('techdocs.storageUrl')).pathname,
|
||||
techdocsApiUrl,
|
||||
).toString();
|
||||
async fetchTechDocsMetadata(
|
||||
entityName: EntityName,
|
||||
): Promise<TechDocsMetadata> {
|
||||
const metadataPath = path.join(
|
||||
staticDocsDir,
|
||||
entityName.namespace,
|
||||
entityName.kind,
|
||||
entityName.name,
|
||||
'techdocs_metadata.json',
|
||||
);
|
||||
|
||||
const entityRootDir = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;
|
||||
const metadataURL = `${storageUrl}/${entityRootDir}/techdocs_metadata.json`;
|
||||
fetch(metadataURL)
|
||||
.then(response =>
|
||||
response
|
||||
.json()
|
||||
.then(techdocsMetadata => resolve(techdocsMetadata))
|
||||
.catch(err => {
|
||||
reject(
|
||||
`Unable to parse metadata JSON for ${entityRootDir}. Error: ${err}`,
|
||||
);
|
||||
}),
|
||||
)
|
||||
.catch(err => {
|
||||
reject(
|
||||
`Unable to fetch metadata for ${entityRootDir}. Error ${err}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
try {
|
||||
return await fs.readJson(metadataPath);
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`Unable to read techdocs_metadata.json at ${metadataPath}. Error: ${err}`,
|
||||
);
|
||||
throw new Error(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
docsRouter(): express.Handler {
|
||||
@@ -143,24 +130,21 @@ export class LocalPublish implements PublisherBase {
|
||||
|
||||
async hasDocsBeenGenerated(entity: Entity): Promise<boolean> {
|
||||
const namespace = entity.metadata.namespace ?? 'default';
|
||||
return new Promise(resolve => {
|
||||
this.discovery.getBaseUrl('techdocs').then(techdocsApiUrl => {
|
||||
const storageUrl = new URL(
|
||||
new URL(this.config.getString('techdocs.storageUrl')).pathname,
|
||||
techdocsApiUrl,
|
||||
).toString();
|
||||
|
||||
const entityRootDir = `${namespace}/${entity.kind}/${entity.metadata.name}`;
|
||||
const indexHtmlUrl = `${storageUrl}/${entityRootDir}/index.html`;
|
||||
// Check if the file exists
|
||||
fs.access(indexHtmlUrl, fs.constants.F_OK, err => {
|
||||
if (err) {
|
||||
resolve(false);
|
||||
} else {
|
||||
resolve(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
const indexHtmlPath = path.join(
|
||||
staticDocsDir,
|
||||
namespace,
|
||||
entity.kind,
|
||||
entity.metadata.name,
|
||||
'index.html',
|
||||
);
|
||||
|
||||
// Check if the file exists
|
||||
try {
|
||||
fs.access(indexHtmlPath, fs.constants.F_OK);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,6 @@
|
||||
*/
|
||||
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { plugin } from '../src/plugin';
|
||||
import { catalogImportPlugin } from '../src/plugin';
|
||||
|
||||
createDevApp().registerPlugin(plugin).render();
|
||||
createDevApp().registerPlugin(catalogImportPlugin).render();
|
||||
|
||||
@@ -14,6 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { plugin } from './plugin';
|
||||
export {
|
||||
catalogImportPlugin,
|
||||
catalogImportPlugin as plugin,
|
||||
CatalogImportPage,
|
||||
} from './plugin';
|
||||
export { Router } from './components/Router';
|
||||
export * from './api';
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { plugin } from './plugin';
|
||||
import { catalogImportPlugin } from './plugin';
|
||||
|
||||
describe('catalog-import', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(plugin).toBeDefined();
|
||||
expect(catalogImportPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
discoveryApiRef,
|
||||
githubAuthApiRef,
|
||||
configApiRef,
|
||||
createRoutableExtension,
|
||||
} from '@backstage/core';
|
||||
import { catalogImportApiRef } from './api/CatalogImportApi';
|
||||
import { CatalogImportClient } from './api/CatalogImportClient';
|
||||
@@ -30,7 +31,7 @@ export const rootRouteRef = createRouteRef({
|
||||
title: 'catalog-import',
|
||||
});
|
||||
|
||||
export const plugin = createPlugin({
|
||||
export const catalogImportPlugin = createPlugin({
|
||||
id: 'catalog-import',
|
||||
apis: [
|
||||
createApiFactory({
|
||||
@@ -44,4 +45,14 @@ export const plugin = createPlugin({
|
||||
new CatalogImportClient({ discoveryApi, githubAuthApi, configApi }),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
importPage: rootRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
export const CatalogImportPage = catalogImportPlugin.provide(
|
||||
createRoutableExtension({
|
||||
component: () => import('./components/Router').then(m => m.Router),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { plugin } from '../src/plugin';
|
||||
import { lighthousePlugin } from '../src/plugin';
|
||||
import { lighthouseApiRef, LighthouseRestApi } from '../src';
|
||||
|
||||
createDevApp()
|
||||
.registerPlugin(plugin)
|
||||
.registerPlugin(lighthousePlugin)
|
||||
.registerApi({
|
||||
api: lighthouseApiRef,
|
||||
deps: {},
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
|
||||
import React from 'react';
|
||||
import { Route, Routes } from 'react-router-dom';
|
||||
import { createAuditRouteRef, rootRouteRef, viewAuditRouteRef } from './plugin';
|
||||
import AuditList from './components/AuditList';
|
||||
import AuditView, { AuditViewContent } from './components/AuditView';
|
||||
import CreateAudit, { CreateAuditContent } from './components/CreateAudit';
|
||||
@@ -25,32 +24,26 @@ import { LIGHTHOUSE_WEBSITE_URL_ANNOTATION } from '../constants';
|
||||
import { AuditListForEntity } from './components/AuditList/AuditListForEntity';
|
||||
import { MissingAnnotationEmptyState } from '@backstage/core';
|
||||
|
||||
export const isPluginApplicableToEntity = (entity: Entity) =>
|
||||
export const isLighthouseAvailable = (entity: Entity) =>
|
||||
Boolean(entity.metadata.annotations?.[LIGHTHOUSE_WEBSITE_URL_ANNOTATION]);
|
||||
|
||||
export const Router = () => (
|
||||
<Routes>
|
||||
<Route path={`/${rootRouteRef.path}`} element={<AuditList />} />
|
||||
<Route path={`/${viewAuditRouteRef.path}`} element={<AuditView />} />
|
||||
<Route path={`/${createAuditRouteRef.path}`} element={<CreateAudit />} />
|
||||
<Route path="/" element={<AuditList />} />
|
||||
<Route path="/audit/:id" element={<AuditView />} />
|
||||
<Route path="/create-audit" element={<CreateAudit />} />
|
||||
</Routes>
|
||||
);
|
||||
|
||||
export const EmbeddedRouter = ({ entity }: { entity: Entity }) =>
|
||||
!isPluginApplicableToEntity(entity) ? (
|
||||
!isLighthouseAvailable(entity) ? (
|
||||
<MissingAnnotationEmptyState
|
||||
annotation={LIGHTHOUSE_WEBSITE_URL_ANNOTATION}
|
||||
/>
|
||||
) : (
|
||||
<Routes>
|
||||
<Route path={`/${rootRouteRef.path}`} element={<AuditListForEntity />} />
|
||||
<Route
|
||||
path={`/${viewAuditRouteRef.path}`}
|
||||
element={<AuditViewContent />}
|
||||
/>
|
||||
<Route
|
||||
path={`/${createAuditRouteRef.path}`}
|
||||
element={<CreateAuditContent />}
|
||||
/>
|
||||
<Route path="/" element={<AuditListForEntity />} />
|
||||
<Route path="/audit/:id" element={<AuditViewContent />} />
|
||||
<Route path="/create-audit" element={<CreateAuditContent />} />
|
||||
</Routes>
|
||||
);
|
||||
|
||||
@@ -25,7 +25,6 @@ import {
|
||||
} from '../../utils';
|
||||
import { Link, generatePath } from 'react-router-dom';
|
||||
import AuditStatusIcon from '../AuditStatusIcon';
|
||||
import { viewAuditRouteRef } from '../../plugin';
|
||||
|
||||
const columns: TableColumn[] = [
|
||||
{
|
||||
@@ -99,11 +98,7 @@ export const AuditListTable = ({ items }: { items: Website[] }) => {
|
||||
|
||||
return {
|
||||
websiteUrl: (
|
||||
<Link
|
||||
to={generatePath(viewAuditRouteRef.path, {
|
||||
id: website.lastAudit.id,
|
||||
})}
|
||||
>
|
||||
<Link to={generatePath('audit/:id', { id: website.lastAudit.id })}>
|
||||
{website.url}
|
||||
</Link>
|
||||
),
|
||||
|
||||
@@ -35,7 +35,6 @@ import { useQuery } from '../../utils';
|
||||
import LighthouseSupportButton from '../SupportButton';
|
||||
import LighthouseIntro, { LIGHTHOUSE_INTRO_LOCAL_STORAGE } from '../Intro';
|
||||
import AuditListTable from './AuditListTable';
|
||||
import { createAuditRouteRef } from '../../plugin';
|
||||
|
||||
export const LIMIT = 10;
|
||||
|
||||
@@ -110,7 +109,7 @@ const AuditList = () => {
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
onClick={() => navigate(createAuditRouteRef.path)}
|
||||
onClick={() => navigate('create-audit')}
|
||||
>
|
||||
Create Audit
|
||||
</Button>
|
||||
|
||||
@@ -47,7 +47,6 @@ import { lighthouseApiRef, Website, Audit } from '../../api';
|
||||
import AuditStatusIcon from '../AuditStatusIcon';
|
||||
import LighthouseSupportButton from '../SupportButton';
|
||||
import { formatTime } from '../../utils';
|
||||
import { viewAuditRouteRef, createAuditRouteRef } from '../../plugin';
|
||||
|
||||
const useStyles = makeStyles({
|
||||
contentGrid: {
|
||||
@@ -78,12 +77,7 @@ const AuditLinkList = ({ audits = [], selectedId }: AuditLinkListProps) => (
|
||||
button
|
||||
component={Link}
|
||||
replace
|
||||
to={resolvePath(
|
||||
generatePath(viewAuditRouteRef.path, {
|
||||
id: audit.id,
|
||||
}),
|
||||
'../../',
|
||||
)}
|
||||
to={resolvePath(generatePath('audit/:id', { id: audit.id }), '../../')}
|
||||
>
|
||||
<ListItemIcon>
|
||||
<AuditStatusIcon audit={audit} />
|
||||
@@ -163,7 +157,7 @@ export const AuditViewContent = () => {
|
||||
);
|
||||
}
|
||||
|
||||
let createAuditButtonUrl = createAuditRouteRef.path;
|
||||
let createAuditButtonUrl = 'create-audit';
|
||||
if (value?.url) {
|
||||
createAuditButtonUrl += `?url=${encodeURIComponent(value.url)}`;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,18 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { plugin } from './plugin';
|
||||
export { Router, isPluginApplicableToEntity, EmbeddedRouter } from './Router';
|
||||
export {
|
||||
lighthousePlugin,
|
||||
lighthousePlugin as plugin,
|
||||
LighthousePage,
|
||||
EntityLighthouseContent,
|
||||
EntityLastLighthouseAuditCard,
|
||||
} from './plugin';
|
||||
export {
|
||||
Router,
|
||||
isLighthouseAvailable as isPluginApplicableToEntity,
|
||||
isLighthouseAvailable,
|
||||
EmbeddedRouter,
|
||||
} from './Router';
|
||||
export * from './api';
|
||||
export * from './components/Cards';
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { plugin } from './plugin';
|
||||
import { lighthousePlugin } from './plugin';
|
||||
|
||||
describe('lighthouse', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(plugin).toBeDefined();
|
||||
expect(lighthousePlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -19,6 +19,8 @@ import {
|
||||
createRouteRef,
|
||||
createApiFactory,
|
||||
configApiRef,
|
||||
createRoutableExtension,
|
||||
createComponentExtension,
|
||||
} from '@backstage/core';
|
||||
import { lighthouseApiRef, LighthouseRestApi } from './api';
|
||||
|
||||
@@ -37,7 +39,11 @@ export const createAuditRouteRef = createRouteRef({
|
||||
title: 'Create Lighthouse Audit',
|
||||
});
|
||||
|
||||
export const plugin = createPlugin({
|
||||
export const entityContentRouteRef = createRouteRef({
|
||||
title: 'Lighthouse Entity Content',
|
||||
});
|
||||
|
||||
export const lighthousePlugin = createPlugin({
|
||||
id: 'lighthouse',
|
||||
apis: [
|
||||
createApiFactory({
|
||||
@@ -46,4 +52,31 @@ export const plugin = createPlugin({
|
||||
factory: ({ configApi }) => LighthouseRestApi.fromConfig(configApi),
|
||||
}),
|
||||
],
|
||||
routes: {
|
||||
root: createAuditRouteRef,
|
||||
entityContent: entityContentRouteRef,
|
||||
},
|
||||
});
|
||||
|
||||
export const LighthousePage = lighthousePlugin.provide(
|
||||
createRoutableExtension({
|
||||
component: () => import('./Router').then(m => m.Router),
|
||||
mountPoint: rootRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
export const EntityLighthouseContent = lighthousePlugin.provide(
|
||||
createRoutableExtension({
|
||||
component: () => import('./Router').then(m => m.EmbeddedRouter),
|
||||
mountPoint: entityContentRouteRef,
|
||||
}),
|
||||
);
|
||||
|
||||
export const EntityLastLighthouseAuditCard = lighthousePlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () =>
|
||||
import('./components/Cards').then(m => m.LastLighthouseAuditCard),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { plugin } from '../src/plugin';
|
||||
import { orgPlugin } from '../src/plugin';
|
||||
|
||||
createDevApp().registerPlugin(plugin).render();
|
||||
createDevApp().registerPlugin(orgPlugin).render();
|
||||
|
||||
@@ -13,5 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { plugin } from './plugin';
|
||||
export {
|
||||
orgPlugin,
|
||||
orgPlugin as plugin,
|
||||
EntityGroupProfileCard,
|
||||
EntityMembersListCard,
|
||||
EntityOwnershipCard,
|
||||
EntityUserProfileCard,
|
||||
} from './plugin';
|
||||
export * from './components';
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { plugin } from './plugin';
|
||||
import { orgPlugin } from './plugin';
|
||||
|
||||
describe('groups', () => {
|
||||
it('should export plugin', () => {
|
||||
expect(plugin).toBeDefined();
|
||||
expect(orgPlugin).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,8 +13,37 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { createPlugin } from '@backstage/core';
|
||||
import { createComponentExtension, createPlugin } from '@backstage/core';
|
||||
|
||||
export const plugin = createPlugin({
|
||||
export const orgPlugin = createPlugin({
|
||||
id: 'org',
|
||||
});
|
||||
|
||||
export const EntityGroupProfileCard = orgPlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () => import('./components').then(m => m.GroupProfileCard),
|
||||
},
|
||||
}),
|
||||
);
|
||||
export const EntityMembersListCard = orgPlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () => import('./components').then(m => m.MembersListCard),
|
||||
},
|
||||
}),
|
||||
);
|
||||
export const EntityOwnershipCard = orgPlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () => import('./components').then(m => m.OwnershipCard),
|
||||
},
|
||||
}),
|
||||
);
|
||||
export const EntityUserProfileCard = orgPlugin.provide(
|
||||
createComponentExtension({
|
||||
component: {
|
||||
lazy: () => import('./components').then(m => m.UserProfileCard),
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
Vendored
+8
-7
@@ -14,17 +14,12 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* techdocs schema below is an abstract of what's used within techdocs-backend and for its visibility
|
||||
* to view the complete techdoc schema please refer: plugins/techdocs/config.d.ts
|
||||
* TechDocs schema below is an abstract of what's used within techdocs-backend and for its visibility
|
||||
* to view the complete TechDocs schema please refer: plugins/techdocs/config.d.ts
|
||||
* */
|
||||
export interface Config {
|
||||
/** Configuration options for the techdocs-backend plugin */
|
||||
techdocs: {
|
||||
/**
|
||||
* 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
|
||||
@@ -45,5 +40,11 @@ export interface Config {
|
||||
*/
|
||||
type: 'local' | 'googleGcs' | 'awsS3';
|
||||
};
|
||||
/**
|
||||
* attr: 'storageUrl' - accepts a string value
|
||||
* e.g. storageUrl: http://localhost:7000/api/techdocs/static/docs
|
||||
* @deprecated
|
||||
*/
|
||||
storageUrl?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -102,7 +102,9 @@ export async function createRouter({
|
||||
|
||||
router.get('/docs/:namespace/:kind/:name/*', async (req, res) => {
|
||||
const { kind, namespace, name } = req.params;
|
||||
const storageUrl = config.getString('techdocs.storageUrl');
|
||||
const storageUrl =
|
||||
config.getOptionalString('techdocs.storageUrl') ??
|
||||
`${await discovery.getBaseUrl('techdocs')}/static/docs`;
|
||||
|
||||
const catalogUrl = await discovery.getBaseUrl('catalog');
|
||||
const triple = [kind, namespace, name].map(encodeURIComponent).join('/');
|
||||
|
||||
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,6 +14,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { configApiRef, discoveryApiRef } from '@backstage/core';
|
||||
import { createDevApp } from '@backstage/dev-utils';
|
||||
import { plugin } from '../src/plugin';
|
||||
import { TechDocsDevStorageApi } from './api';
|
||||
@@ -22,10 +23,11 @@ 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)
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
"clean": "backstage-cli clean"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/config": "^0.1.2",
|
||||
"@backstage/catalog-model": "^0.7.0",
|
||||
"@backstage/core": "^0.5.0",
|
||||
"@backstage/plugin-catalog-react": "^0.0.1",
|
||||
|
||||
@@ -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/`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+57
-13
@@ -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,7 +150,8 @@ 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`,
|
||||
@@ -132,12 +170,18 @@ export class TechDocsStorageApi implements TechDocsStorage {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
createRouteRef,
|
||||
createApiFactory,
|
||||
configApiRef,
|
||||
discoveryApiRef,
|
||||
} from '@backstage/core';
|
||||
import {
|
||||
techdocsStorageApiRef,
|
||||
@@ -57,24 +58,25 @@ 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({
|
||||
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,
|
||||
}),
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -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');
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from '@backstage/core';
|
||||
|
||||
export const settingsRouteRef = createRouteRef({
|
||||
path: '/settings',
|
||||
title: 'Settings',
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user