address comments

This commit is contained in:
Fredrik Adelöw
2020-06-18 11:54:12 +02:00
parent 8d961027a9
commit 3bff1d6a68
9 changed files with 83 additions and 79 deletions
+1
View File
@@ -20,6 +20,7 @@
"clean": "backstage-cli clean"
},
"dependencies": {
"@backstage/config": "^0.1.1-alpha.9",
"@types/yup": "^0.28.2",
"lodash": "^4.17.15",
"uuid": "^8.0.0",
+9 -2
View File
@@ -14,6 +14,8 @@
* limitations under the License.
*/
import { JsonObject } from '@backstage/config';
/**
* The format envelope that's common to all versions/kinds of entity.
*
@@ -39,7 +41,7 @@ export type Entity = {
/**
* The specification data describing the entity itself.
*/
spec?: object;
spec?: JsonObject;
};
/**
@@ -48,7 +50,7 @@ export type Entity = {
* @see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.18/#objectmeta-v1-meta
* @see https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/
*/
export type EntityMeta = Record<string, any> & {
export type EntityMeta = JsonObject & {
/**
* A globally unique ID for the entity.
*
@@ -112,3 +114,8 @@ export type EntityMeta = Record<string, any> & {
*/
annotations?: Record<string, string>;
};
/**
* The keys of EntityMeta that are auto-generated.
*/
export const entityMetaGeneratedFields = ['uid', 'etag', 'generation'] as const;
@@ -14,6 +14,7 @@
* limitations under the License.
*/
export { entityMetaGeneratedFields } from './Entity';
export type { Entity, EntityMeta } from './Entity';
export * from './policies';
export {
+9 -6
View File
@@ -14,11 +14,11 @@
* limitations under the License.
*/
import yaml from 'yaml';
import { AppConfig, JsonObject, JsonValue } from '@backstage/config';
import { basename } from 'path';
import { isObject } from './utils';
import { JsonValue, JsonObject, AppConfig } from '@backstage/config';
import yaml from 'yaml';
import { ReaderContext } from './types';
import { isObject } from './utils';
/**
* Reads and parses, and validates, and transforms a single config file.
@@ -67,9 +67,12 @@ export async function readConfigFile(
const out: JsonObject = {};
for (const [key, value] of Object.entries(obj)) {
const result = await transform(value, `${path}.${key}`);
if (result !== undefined) {
out[key] = result;
// undefined covers optional fields
if (value !== undefined) {
const result = await transform(value, `${path}.${key}`);
if (result !== undefined) {
out[key] = result;
}
}
}
+2 -1
View File
@@ -122,7 +122,7 @@ export async function readSecret(
const { path } = secret;
const parts = typeof path === 'string' ? path.split('.') : path;
let value: JsonValue = await parser(content);
let value: JsonValue | undefined = await parser(content);
for (const [index, part] of parts.entries()) {
if (!isObject(value)) {
const errPath = parts.slice(0, index).join('.');
@@ -132,6 +132,7 @@ export async function readSecret(
}
value = value[part];
}
return String(value);
}
+1 -1
View File
@@ -14,7 +14,7 @@
* limitations under the License.
*/
export type JsonObject = { [key in string]: JsonValue };
export type JsonObject = { [key in string]?: JsonValue };
export type JsonArray = JsonValue[];
export type JsonValue =
| JsonObject
@@ -49,7 +49,7 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog {
const response = await this.database.transaction(tx =>
this.entityByNameInternal(tx, kind, name, namespace),
);
return response ? response.entity : undefined;
return response?.entity;
}
async addOrUpdateEntity(
@@ -22,6 +22,7 @@ import {
import {
Entity,
EntityMeta,
entityMetaGeneratedFields,
generateEntityEtag,
generateEntityUid,
Location,
@@ -44,63 +45,9 @@ import type {
EntityFilters,
} from './types';
function serializeMetadata(metadata: EntityMeta): string {
const withoutGeneratedFields = lodash.cloneDeep(metadata);
delete withoutGeneratedFields.uid;
delete withoutGeneratedFields.etag;
delete withoutGeneratedFields.generation;
return JSON.stringify(withoutGeneratedFields);
}
function serializeSpec(spec: Entity['spec']): DbEntitiesRow['spec'] {
if (!spec) {
return null;
}
return JSON.stringify(spec);
}
function toEntityRow(
locationId: string | undefined,
entity: Entity,
): DbEntitiesRow {
return {
id: entity.metadata.uid!,
location_id: locationId || null,
etag: entity.metadata.etag!,
generation: entity.metadata.generation!,
api_version: entity.apiVersion,
kind: entity.kind,
name: entity.metadata.name,
namespace: entity.metadata.namespace || null,
metadata: serializeMetadata(entity.metadata),
spec: serializeSpec(entity.spec),
};
}
function toEntityResponse(row: DbEntitiesRow): DbEntityResponse {
const entity: Entity = {
apiVersion: row.api_version,
kind: row.kind,
metadata: {
...(JSON.parse(row.metadata) as Entity['metadata']),
uid: row.id,
etag: row.etag,
generation: Number(row.generation), // cast because of sqlite
},
};
if (row.spec) {
const spec = JSON.parse(row.spec);
entity.spec = spec;
}
return {
locationId: row.location_id || undefined,
entity,
};
}
/**
* The core database implementation.
*/
export class CommonDatabase implements Database {
constructor(
private readonly database: Knex,
@@ -149,7 +96,7 @@ export class CommonDatabase implements Database {
generation: 1,
};
const newRow = toEntityRow(request.locationId, newEntity);
const newRow = this.toEntityRow(request.locationId, newEntity);
await tx<DbEntitiesRow>('entities').insert(newRow);
await this.updateEntitiesSearch(tx, newRow.id, newEntity);
@@ -202,7 +149,7 @@ export class CommonDatabase implements Database {
// Store the updated entity; select on the old etag to ensure that we do
// not lose to another writer
const newRow = toEntityRow(request.locationId, request.entity);
const newRow = this.toEntityRow(request.locationId, request.entity);
const updatedRows = await tx<DbEntitiesRow>('entities')
.where({ id: oldRow.id, etag: oldRow.etag })
.update(newRow);
@@ -270,7 +217,7 @@ export class CommonDatabase implements Database {
.orderBy('name', 'asc')
.groupBy('id');
return rows.map(row => toEntityResponse(row));
return rows.map(row => this.toEntityResponse(row));
}
async entity(
@@ -289,7 +236,7 @@ export class CommonDatabase implements Database {
return undefined;
}
return toEntityResponse(rows[0]);
return this.toEntityResponse(rows[0]);
}
async entityByUid(
@@ -304,7 +251,7 @@ export class CommonDatabase implements Database {
return undefined;
}
return toEntityResponse(rows[0]);
return this.toEntityResponse(rows[0]);
}
async removeEntity(txOpaque: unknown, uid: string): Promise<void> {
@@ -466,4 +413,47 @@ export class CommonDatabase implements Database {
}
}
}
private toEntityRow(
locationId: string | undefined,
entity: Entity,
): DbEntitiesRow {
return {
id: entity.metadata.uid!,
location_id: locationId || null,
etag: entity.metadata.etag!,
generation: entity.metadata.generation!,
api_version: entity.apiVersion,
kind: entity.kind,
name: entity.metadata.name,
namespace: entity.metadata.namespace || null,
metadata: JSON.stringify(
lodash.omit(entity.metadata, ...entityMetaGeneratedFields),
),
spec: entity.spec ? JSON.stringify(entity.spec) : null,
};
}
private toEntityResponse(row: DbEntitiesRow): DbEntityResponse {
const entity: Entity = {
apiVersion: row.api_version,
kind: row.kind,
metadata: {
...(JSON.parse(row.metadata) as EntityMeta),
uid: row.id,
etag: row.etag,
generation: Number(row.generation), // cast because of sqlite
},
};
if (row.spec) {
const spec = JSON.parse(row.spec);
entity.spec = spec;
}
return {
locationId: row.location_id || undefined,
entity,
};
}
}
@@ -14,13 +14,12 @@
* limitations under the License.
*/
import React, { ComponentProps } from 'react';
import { render, cleanup } from '@testing-library/react';
import { RegisterComponentResultDialog } from './RegisterComponentResultDialog';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import { ThemeProvider } from '@material-ui/core';
import { cleanup, render } from '@testing-library/react';
import React, { ComponentProps } from 'react';
import { MemoryRouter } from 'react-router-dom';
import { Entity } from '@backstage/catalog-model';
import { RegisterComponentResultDialog } from './RegisterComponentResultDialog';
const setup = (
props?: Partial<ComponentProps<typeof RegisterComponentResultDialog>>,
@@ -52,6 +51,7 @@ it('should show a list of components if success', async () => {
const { rendered } = setup({
entities: [
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'Component1',
@@ -61,6 +61,7 @@ it('should show a list of components if success', async () => {
},
},
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
name: 'Component2',
@@ -69,7 +70,7 @@ it('should show a list of components if success', async () => {
type: 'service',
},
},
] as Entity[],
],
});
expect(