Introduce techdocs metadata in techdocs-common
* Add TechdocsMetadata type in backend plugin endpoint * Introduce TechDocsMetadata type in frontend * Add changeset * Remove old thennable metadata resp * Address PR feedback - Remove explicit type annotation on TechDocsMetadata - Reintroduce res.send instead of throwing an error - Change logger info to logger error * Add TechDocsMetadata type in frontend plugin * Commit yarn.lock * Introduce JSON5 and remove parsing in local pub Signed-off-by: Matei David <matei.david.35@gmail.com>
This commit is contained in:
@@ -50,6 +50,7 @@
|
||||
"fs-extra": "^9.0.1",
|
||||
"git-url-parse": "^11.4.3",
|
||||
"js-yaml": "^4.0.0",
|
||||
"json5": "^2.1.3",
|
||||
"mime-types": "^2.1.27",
|
||||
"mock-fs": "^4.13.0",
|
||||
"recursive-readdir": "^2.2.2",
|
||||
|
||||
@@ -18,7 +18,7 @@ import path from 'path';
|
||||
import * as winston from 'winston';
|
||||
import { ConfigReader } from '@backstage/config';
|
||||
import { AwsS3Publish } from './awsS3';
|
||||
import { PublisherBase } from './types';
|
||||
import { PublisherBase, TechDocsMetadata } from './types';
|
||||
import type { Entity, EntityName } from '@backstage/catalog-model';
|
||||
|
||||
const createMockEntity = (annotations = {}): Entity => {
|
||||
@@ -159,13 +159,39 @@ describe('AwsS3Publish', () => {
|
||||
|
||||
mockFs({
|
||||
[entityRootDir]: {
|
||||
'techdocs_metadata.json': 'file-content',
|
||||
'techdocs_metadata.json':
|
||||
'{"site_name": "backstage", "site_description": "site_content"}',
|
||||
},
|
||||
});
|
||||
|
||||
expect(await publisher.fetchTechDocsMetadata(entityNameMock)).toBe(
|
||||
'file-content',
|
||||
);
|
||||
const expectedMetadata: TechDocsMetadata = {
|
||||
site_name: 'backstage',
|
||||
site_description: 'site_content',
|
||||
};
|
||||
expect(
|
||||
await publisher.fetchTechDocsMetadata(entityNameMock),
|
||||
).toStrictEqual(expectedMetadata);
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
it('should return tech docs metadata when json encoded with single quotes', async () => {
|
||||
const entityNameMock = createMockEntityName();
|
||||
const entity = createMockEntity();
|
||||
const entityRootDir = getEntityRootDir(entity);
|
||||
|
||||
mockFs({
|
||||
[entityRootDir]: {
|
||||
'techdocs_metadata.json': `{'site_name': 'backstage', 'site_description': 'site_content'}`,
|
||||
},
|
||||
});
|
||||
|
||||
const expectedMetadata: TechDocsMetadata = {
|
||||
site_name: 'backstage',
|
||||
site_description: 'site_content',
|
||||
};
|
||||
expect(
|
||||
await publisher.fetchTechDocsMetadata(entityNameMock),
|
||||
).toStrictEqual(expectedMetadata);
|
||||
mockFs.restore();
|
||||
});
|
||||
|
||||
|
||||
@@ -20,9 +20,10 @@ import { Logger } from 'winston';
|
||||
import { Entity, EntityName } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getHeadersForFileExtension, getFileTreeRecursively } from './helpers';
|
||||
import { PublisherBase, PublishRequest } from './types';
|
||||
import { PublisherBase, PublishRequest, TechDocsMetadata } from './types';
|
||||
import fs from 'fs-extra';
|
||||
import { Readable } from 'stream';
|
||||
import JSON5 from 'json5';
|
||||
|
||||
const streamToBuffer = (stream: Readable): Promise<Buffer> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -32,7 +33,7 @@ const streamToBuffer = (stream: Readable): Promise<Buffer> => {
|
||||
stream.on('error', reject);
|
||||
stream.on('end', () => resolve(Buffer.concat(chunks)));
|
||||
} catch (e) {
|
||||
throw new Error(`Unable to parse the response data, ${e.message}`);
|
||||
throw new Error(`Unable to parse the response data ${e.message}`);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -162,9 +163,11 @@ export class AwsS3Publish implements PublisherBase {
|
||||
}
|
||||
}
|
||||
|
||||
async fetchTechDocsMetadata(entityName: EntityName): Promise<string> {
|
||||
async fetchTechDocsMetadata(
|
||||
entityName: EntityName,
|
||||
): Promise<TechDocsMetadata> {
|
||||
try {
|
||||
return await new Promise<string>((resolve, reject) => {
|
||||
return await new Promise<TechDocsMetadata>((resolve, reject) => {
|
||||
const entityRootDir = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;
|
||||
|
||||
this.storageClient
|
||||
@@ -182,8 +185,11 @@ export class AwsS3Publish implements PublisherBase {
|
||||
`Unable to parse the techdocs metadata file ${entityRootDir}/techdocs_metadata.json.`,
|
||||
);
|
||||
}
|
||||
const techdocsMetadata = JSON5.parse(
|
||||
techdocsMetadataJson.toString('utf-8'),
|
||||
);
|
||||
|
||||
resolve(techdocsMetadataJson.toString('utf-8'));
|
||||
resolve(techdocsMetadata);
|
||||
})
|
||||
.catch(err => {
|
||||
this.logger.error(err.message);
|
||||
|
||||
@@ -24,7 +24,8 @@ import { Logger } from 'winston';
|
||||
import { Entity, EntityName } from '@backstage/catalog-model';
|
||||
import { Config } from '@backstage/config';
|
||||
import { getHeadersForFileExtension, getFileTreeRecursively } from './helpers';
|
||||
import { PublisherBase, PublishRequest } from './types';
|
||||
import { PublisherBase, PublishRequest, TechDocsMetadata } from './types';
|
||||
import JSON5 from 'json5';
|
||||
|
||||
export class GoogleGCSPublish implements PublisherBase {
|
||||
static async fromConfig(
|
||||
@@ -132,7 +133,7 @@ export class GoogleGCSPublish implements PublisherBase {
|
||||
});
|
||||
}
|
||||
|
||||
fetchTechDocsMetadata(entityName: EntityName): Promise<string> {
|
||||
fetchTechDocsMetadata(entityName: EntityName): Promise<TechDocsMetadata> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const entityRootDir = `${entityName.namespace}/${entityName.kind}/${entityName.name}`;
|
||||
|
||||
@@ -152,7 +153,7 @@ export class GoogleGCSPublish implements PublisherBase {
|
||||
const techdocsMetadataJson = Buffer.concat(
|
||||
fileStreamChunks,
|
||||
).toString();
|
||||
resolve(techdocsMetadataJson);
|
||||
resolve(JSON5.parse(techdocsMetadataJson));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -14,4 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
export { Publisher } from './publish';
|
||||
export type { PublisherBase, PublisherType } from './types';
|
||||
export type { PublisherBase, PublisherType, TechDocsMetadata } from './types';
|
||||
|
||||
@@ -25,7 +25,12 @@ import {
|
||||
PluginEndpointDiscovery,
|
||||
} from '@backstage/backend-common';
|
||||
import { Config } from '@backstage/config';
|
||||
import { PublisherBase, PublishRequest, PublishResponse } from './types';
|
||||
import {
|
||||
PublisherBase,
|
||||
PublishRequest,
|
||||
PublishResponse,
|
||||
TechDocsMetadata,
|
||||
} from './types';
|
||||
|
||||
// TODO: Use a more persistent storage than node_modules or /tmp directory.
|
||||
// Make it configurable with techdocs.publisher.local.publishDirectory
|
||||
@@ -102,7 +107,7 @@ export class LocalPublish implements PublisherBase {
|
||||
});
|
||||
}
|
||||
|
||||
fetchTechDocsMetadata(entityName: EntityName): Promise<string> {
|
||||
fetchTechDocsMetadata(entityName: EntityName): Promise<TechDocsMetadata> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.discovery.getBaseUrl('techdocs').then(techdocsApiUrl => {
|
||||
const storageUrl = new URL(
|
||||
@@ -116,7 +121,7 @@ export class LocalPublish implements PublisherBase {
|
||||
.then(response =>
|
||||
response
|
||||
.json()
|
||||
.then(techdocsMetadataJson => resolve(techdocsMetadataJson))
|
||||
.then(techdocsMetadata => resolve(techdocsMetadata))
|
||||
.catch(err => {
|
||||
reject(
|
||||
`Unable to parse metadata JSON for ${entityRootDir}. Error: ${err}`,
|
||||
|
||||
@@ -32,6 +32,14 @@ export type PublishResponse = {
|
||||
remoteUrl?: string;
|
||||
} | void;
|
||||
|
||||
/**
|
||||
* Type to hold metadata found in techdocs_metadata.json and associated with each site
|
||||
*/
|
||||
export type TechDocsMetadata = {
|
||||
site_name: string;
|
||||
site_description: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base class for a TechDocs publisher (e.g. Local, Google GCS Bucket, AWS S3, etc.)
|
||||
* The publisher handles publishing of the generated static files after the prepare and generate steps of TechDocs.
|
||||
@@ -50,7 +58,7 @@ export interface PublisherBase {
|
||||
* Retrieve TechDocs Metadata about a site e.g. name, contributors, last updated, etc.
|
||||
* This API uses the techdocs_metadata.json file that co-exists along with the generated docs.
|
||||
*/
|
||||
fetchTechDocsMetadata(entityName: EntityName): Promise<string>;
|
||||
fetchTechDocsMetadata(entityName: EntityName): Promise<TechDocsMetadata>;
|
||||
|
||||
/**
|
||||
* Route middleware to serve static documentation files for an entity.
|
||||
|
||||
Reference in New Issue
Block a user