From 2ebcfac8d4487313c9d4bd90225395965b5945b8 Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 21 Oct 2020 09:37:25 +0200 Subject: [PATCH] feat: add a validate button to the register-component page This allows the user to validate his location before adding it. --- .changeset/angry-crabs-relate.md | 2 +- .changeset/nervous-drinks-notice.md | 8 ++ .../catalog/DatabaseEntitiesCatalog.test.ts | 30 +++-- .../src/catalog/DatabaseEntitiesCatalog.ts | 126 +++++++++++------- plugins/catalog-backend/src/catalog/types.ts | 5 +- .../ingestion/HigherOrderOperations.test.ts | 99 ++++++++++++-- .../src/ingestion/HigherOrderOperations.ts | 15 +-- .../src/service/router.test.ts | 2 - plugins/catalog/src/api/CatalogClient.ts | 5 +- plugins/catalog/src/api/types.ts | 1 + .../RegisterComponentForm.test.tsx | 10 +- .../RegisterComponentForm.tsx | 48 +++++-- .../RegisterComponentPage.tsx | 12 +- .../RegisterComponentResultDialog.tsx | 115 +++++++++------- 14 files changed, 327 insertions(+), 151 deletions(-) create mode 100644 .changeset/nervous-drinks-notice.md diff --git a/.changeset/angry-crabs-relate.md b/.changeset/angry-crabs-relate.md index 66cdb29be5..678093c1ac 100644 --- a/.changeset/angry-crabs-relate.md +++ b/.changeset/angry-crabs-relate.md @@ -2,7 +2,7 @@ '@backstage/plugin-catalog-backend': minor --- -Add ability to dry run adding a new location ot the catalog API. +Add ability to dry run adding a new location to the catalog API. The location is now added in a transaction and afterwards rolled back. This allows users to dry run this operation to see if there entity has issues. diff --git a/.changeset/nervous-drinks-notice.md b/.changeset/nervous-drinks-notice.md new file mode 100644 index 0000000000..7c6af21f64 --- /dev/null +++ b/.changeset/nervous-drinks-notice.md @@ -0,0 +1,8 @@ +--- +'@backstage/plugin-catalog': minor +'@backstage/plugin-register-component': minor +--- + +Add a validate button to the register-component page + +This allows the user to validate his location before adding it. diff --git a/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.test.ts b/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.test.ts index 7e751bab92..90b11b179d 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.test.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.test.ts @@ -95,25 +95,33 @@ describe('DatabaseEntitiesCatalog', () => { namespace: 'd', }, }; - const existingTransaction: jest.Mocked = { rollback: jest.fn(), }; db.entities.mockResolvedValue([]); - db.addEntities.mockResolvedValue([{ entity }]); + db.addEntities.mockResolvedValue([ + { entity: { ...entity, metadata: { ...entity.metadata, uid: 'u' } } }, + ]); const catalog = new DatabaseEntitiesCatalog(db, getVoidLogger()); - const result = await catalog.addOrUpdateEntity(entity, { - tx: existingTransaction, - }); - - expect(db.addEntities).toHaveBeenCalledTimes(1); - expect(db.addEntities).toHaveBeenCalledWith( - existingTransaction, - expect.anything(), + const result = await catalog.batchAddOrUpdateEntities( + [{ entity, relations: [] }], + { tx: existingTransaction }, ); - expect(result).toBe(entity); + + expect(db.entities).toHaveBeenCalledTimes(1); + expect(db.entities).toHaveBeenCalledWith(expect.anything(), [ + { + kind: 'b', + 'metadata.namespace': 'd', + 'metadata.name': ['c'], + }, + ]); + expect(db.setRelations).toHaveBeenCalledTimes(1); + expect(db.setRelations).toHaveBeenCalledWith(expect.anything(), 'u', []); + expect(db.addEntities).toHaveBeenCalledTimes(1); + expect(result).toEqual([{ entityId: 'u' }]); }); it('updates when given uid', async () => { diff --git a/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts b/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts index 28a972e8e3..53fb50677b 100644 --- a/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts +++ b/plugins/catalog-backend/src/catalog/DatabaseEntitiesCatalog.ts @@ -70,46 +70,44 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog { tx?: Transaction; }): Promise { const filters = options?.filters; - - // TODO: Support with and without transaction! - - const items = await this.database.transaction(tx => - this.database.entities(tx, filters), - ); + const items = options?.tx + ? await this.database.entities(options?.tx, filters) + : await this.database.transaction(tx => + this.database.entities(tx, filters), + ); return items.map(i => i.entity); } private async addOrUpdateEntity( entity: Entity, + tx: Transaction, locationId?: string, ): Promise { - return await this.database.transaction(async tx => { - // Find a matching (by uid, or by compound name, depending on the given - // entity) existing entity, to know whether to update or add - const existing = entity.metadata.uid - ? await this.database.entityByUid(tx, entity.metadata.uid) - : await this.database.entityByName(tx, getEntityName(entity)); + // Find a matching (by uid, or by compound name, depending on the given + // entity) existing entity, to know whether to update or add + const existing = entity.metadata.uid + ? await this.database.entityByUid(tx, entity.metadata.uid) + : await this.database.entityByName(tx, getEntityName(entity)); - // If it's an update, run the algorithm for annotation merging, updating - // etag/generation, etc. - let response: DbEntityResponse; - if (existing) { - const updated = generateUpdatedEntity(existing.entity, entity); - response = await this.database.updateEntity( - tx, - { locationId, entity: updated }, - existing.entity.metadata.etag, - existing.entity.metadata.generation, - ); - } else { - const added = await this.database.addEntities(tx, [ - { locationId, entity }, - ]); - response = added[0]; - } + // If it's an update, run the algorithm for annotation merging, updating + // etag/generation, etc. + let response: DbEntityResponse; + if (existing) { + const updated = generateUpdatedEntity(existing.entity, entity); + response = await this.database.updateEntity( + tx, + { locationId, entity: updated }, + existing.entity.metadata.etag, + existing.entity.metadata.generation, + ); + } else { + const added = await this.database.addEntities(tx, [ + { locationId, entity }, + ]); + response = added[0]; + } - return response.entity; - }); + return response.entity; } async removeEntityByUid(uid: string): Promise { @@ -143,14 +141,41 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog { * Writes a number of entities efficiently to storage. * * @param entities Some entities - * @param locationId The location that they all belong to + * @param options.locationId The location that they all belong to + * @param options.tx A database transaction to execute the queries in */ async batchAddOrUpdateEntities( requests: EntityUpsertRequest[], - options?: { locationId?: string; tx?: Transaction }, + options?: { + locationId?: string; + tx?: Transaction; + }, ): Promise { - // TODO: How to work with the transaction here? + const locationId = options?.locationId; + if (options?.tx) { + return this.batchAddOrUpdateEntitiesInTransaction( + requests, + options.tx, + locationId, + ); + } + + return await this.database.transaction( + async tx => + await this.batchAddOrUpdateEntitiesInTransaction( + requests, + tx, + locationId, + ), + ); + } + + private async batchAddOrUpdateEntitiesInTransaction( + requests: EntityUpsertRequest[], + tx: Transaction, + locationId?: string, + ): Promise { // Group the entities by unique kind+namespace combinations const entitiesByKindAndNamespace = groupBy(requests, ({ entity }) => { const name = getEntityName(entity); @@ -180,29 +205,30 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog { const context = { kind, namespace, - locationId: options?.locationId, + locationId, }; for (let attempt = 1; attempt <= BATCH_ATTEMPTS; ++attempt) { try { const { toAdd, toUpdate, toIgnore } = await this.analyzeBatch( batch, context, + tx, ); if (toAdd.length) { modifiedEntityIds.push( - ...(await this.batchAdd(toAdd, context)), + ...(await this.batchAdd(toAdd, context, tx)), ); } if (toUpdate.length) { modifiedEntityIds.push( - ...(await this.batchUpdate(toUpdate, context)), + ...(await this.batchUpdate(toUpdate, context, tx)), ); } // TODO(Rugvip): We currently always update relations, but we // likely want to figure out a way to avoid that for (const { entity, relations } of toIgnore) { const entityId = entity.metadata.uid!; - await this.setRelations(entityId, relations); + await this.setRelations(entityId, relations, tx); modifiedEntityIds.push({ entityId }); } @@ -231,10 +257,9 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog { private async setRelations( originatingEntityId: string, relations: EntityRelationSpec[], + tx: Transaction, ): Promise { - return await this.database.transaction(tx => - this.database.setRelations(tx, originatingEntityId, relations), - ); + await this.database.setRelations(tx, originatingEntityId, relations); } // Given a batch of entities that were just read from a location, take them @@ -244,6 +269,7 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog { private async analyzeBatch( requests: EntityUpsertRequest[], { kind, namespace }: BatchContext, + tx: Transaction, ): Promise<{ toAdd: EntityUpsertRequest[]; toUpdate: EntityUpsertRequest[]; @@ -260,6 +286,7 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog { 'metadata.name': names, }, ], + tx, }); const oldEntitiesByName = new Map( @@ -299,15 +326,13 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog { private async batchAdd( requests: EntityUpsertRequest[], { locationId }: BatchContext, + tx: Transaction, ): Promise { const markTimestamp = process.hrtime(); - const res = await this.database.transaction( - async tx => - await this.database.addEntities( - tx, - requests.map(({ entity }) => ({ locationId, entity })), - ), + const res = await this.database.addEntities( + tx, + requests.map(({ entity }) => ({ locationId, entity })), ); const entityIds = res.map(({ entity }) => ({ @@ -315,7 +340,7 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog { })); for (const [index, { entityId }] of entityIds.entries()) { - await this.setRelations(entityId, requests[index].relations); + await this.setRelations(entityId, requests[index].relations, tx); } this.logger.debug( @@ -330,15 +355,16 @@ export class DatabaseEntitiesCatalog implements EntitiesCatalog { private async batchUpdate( requests: EntityUpsertRequest[], { locationId }: BatchContext, + tx: Transaction, ): Promise { const markTimestamp = process.hrtime(); const responseIds: EntityUpsertResponse[] = []; // TODO(freben): Still not batched for (const entity of requests) { - const res = await this.addOrUpdateEntity(entity.entity, locationId); + const res = await this.addOrUpdateEntity(entity.entity, tx, locationId); const entityId = res.metadata.uid!; responseIds.push({ entityId }); - await this.setRelations(entityId, entity.relations); + await this.setRelations(entityId, entity.relations, tx); } this.logger.debug( diff --git a/plugins/catalog-backend/src/catalog/types.ts b/plugins/catalog-backend/src/catalog/types.ts index 1ac06618b5..6f2977619a 100644 --- a/plugins/catalog-backend/src/catalog/types.ts +++ b/plugins/catalog-backend/src/catalog/types.ts @@ -45,7 +45,10 @@ export type EntitiesCatalog = { */ batchAddOrUpdateEntities( entities: EntityUpsertRequest[], - options?: { locationId?: string; tx?: Transaction }, + options?: { + tx?: Transaction; + locationId?: string; + }, ): Promise; }; diff --git a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.test.ts b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.test.ts index 174428799d..da722e56bc 100644 --- a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.test.ts +++ b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.test.ts @@ -63,6 +63,7 @@ describe('HigherOrderOperations', () => { entityByName: jest.fn(), entityByUid: jest.fn(), removeEntityByUid: jest.fn(), + setRelations: jest.fn(), addLocation: jest.fn(), removeLocation: jest.fn(), location: jest.fn(), @@ -81,6 +82,7 @@ describe('HigherOrderOperations', () => { beforeEach(() => { jest.resetAllMocks(); + database.transaction.mockImplementation(async f => f(transaction)); }); describe('addLocation', () => { @@ -89,7 +91,6 @@ describe('HigherOrderOperations', () => { type: 'a', target: 'b', }; - database.transaction.mockImplementation(x => x(transaction)); locationsCatalog.addLocation.mockImplementation(x => Promise.resolve(x)); locationsCatalog.locations.mockResolvedValue([]); locationReader.read.mockResolvedValue({ @@ -121,6 +122,62 @@ describe('HigherOrderOperations', () => { expect(transaction.rollback).not.toBeCalled(); }); + it('insert the location and its entities', async () => { + const spec = { + type: 'a', + target: 'b', + }; + const location: LocationSpec = { type: '', target: '' }; + const entity: Entity = { + apiVersion: 'a', + kind: 'b', + metadata: { name: 'n' }, + }; + locationsCatalog.addLocation.mockImplementation(x => Promise.resolve(x)); + locationsCatalog.locations.mockResolvedValue([]); + locationsCatalog.locations.mockResolvedValue([]); + entitiesCatalog.batchAddOrUpdateEntities.mockResolvedValue([ + { + entityId: 'id', + }, + ]); + entitiesCatalog.entities.mockResolvedValue([entity]); + locationReader.read.mockResolvedValue({ + entities: [ + { + location, + entity, + relations: [], + }, + ], + errors: [], + }); + + const result = await higherOrderOperation.addLocation(spec); + + expect(result.location).toEqual( + expect.objectContaining({ + id: expect.anything(), + ...spec, + }), + ); + expect(result.entities).toEqual([entity]); + expect(locationsCatalog.locations).toBeCalledTimes(1); + expect(locationsCatalog.addLocation).toBeCalledTimes(1); + expect(locationsCatalog.addLocation).toBeCalledWith( + expect.objectContaining({ + id: expect.anything(), + ...spec, + }), + { tx: transaction }, + ); + expect(locationReader.read).toBeCalledTimes(1); + expect(locationReader.read).toBeCalledWith({ type: 'a', target: 'b' }); + expect(entitiesCatalog.entities).toBeCalledTimes(1); + expect(entitiesCatalog.batchAddOrUpdateEntities).toBeCalledTimes(1); + expect(transaction.rollback).not.toBeCalled(); + }); + it('reuses the location if a match already existed', async () => { const spec = { type: 'a', @@ -131,7 +188,6 @@ describe('HigherOrderOperations', () => { ...spec, }; - database.transaction.mockImplementation(x => x(transaction)); locationsCatalog.locations.mockResolvedValue([ { currentStatus: { timestamp: '', status: '', message: '' }, @@ -167,7 +223,6 @@ describe('HigherOrderOperations', () => { metadata: { name: 'n' }, }; - database.transaction.mockImplementation(x => x(transaction)); locationsCatalog.locations.mockResolvedValue([]); locationReader.read.mockResolvedValue({ entities: [{ entity, location, relations: [] }], @@ -188,10 +243,31 @@ describe('HigherOrderOperations', () => { type: 'a', target: 'b', }; - database.transaction.mockImplementation(x => x(transaction)); + const location: LocationSpec = { type: '', target: '' }; + const entity: Entity = { + apiVersion: 'a', + kind: 'b', + metadata: { name: 'n' }, + }; locationsCatalog.addLocation.mockImplementation(x => Promise.resolve(x)); locationsCatalog.locations.mockResolvedValue([]); - locationReader.read.mockResolvedValue({ entities: [], errors: [] }); + locationsCatalog.locations.mockResolvedValue([]); + entitiesCatalog.batchAddOrUpdateEntities.mockResolvedValue([ + { + entityId: 'id', + }, + ]); + entitiesCatalog.entities.mockResolvedValue([entity]); + locationReader.read.mockResolvedValue({ + entities: [ + { + location, + entity, + relations: [], + }, + ], + errors: [], + }); const result = await higherOrderOperation.addLocation(spec, { dryRun: true, @@ -203,11 +279,8 @@ describe('HigherOrderOperations', () => { ...spec, }), ); - expect(result.entities).toEqual([]); + expect(result.entities).toEqual([entity]); expect(locationsCatalog.locations).toBeCalledTimes(1); - expect(locationReader.read).toBeCalledTimes(1); - expect(locationReader.read).toBeCalledWith({ type: 'a', target: 'b' }); - expect(entitiesCatalog.addOrUpdateEntity).not.toBeCalled(); expect(locationsCatalog.addLocation).toBeCalledTimes(1); expect(locationsCatalog.addLocation).toBeCalledWith( expect.objectContaining({ @@ -216,6 +289,10 @@ describe('HigherOrderOperations', () => { }), { tx: transaction }, ); + expect(locationReader.read).toBeCalledTimes(1); + expect(locationReader.read).toBeCalledWith({ type: 'a', target: 'b' }); + expect(entitiesCatalog.entities).toBeCalledTimes(1); + expect(entitiesCatalog.batchAddOrUpdateEntities).toBeCalledTimes(1); expect(transaction.rollback).toBeCalled(); }); }); @@ -281,7 +358,9 @@ describe('HigherOrderOperations', () => { relations: [], }), ], - '123', + { + locationId: '123', + }, ); }); diff --git a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts index 2ca79c60b4..ac21fc6155 100644 --- a/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts +++ b/plugins/catalog-backend/src/ingestion/HigherOrderOperations.ts @@ -90,21 +90,18 @@ export class HigherOrderOperations implements HigherOrderOperation { await this.locationsCatalog.addLocation(location, { tx }); } if (readerOutput.entities.length === 0) { - return { location, entities: [] }; + return []; } const writtenEntities = await this.entitiesCatalog.batchAddOrUpdateEntities( readerOutput.entities, - { - locationId: location.id, - tx, - }, + { locationId: location.id, tx }, ); - const outputEntities = await this.entitiesCatalog.entities( - [{ 'metadata.uid': writtenEntities.map(e => e.entityId) }], - { tx }, - ); + const outputEntities = await this.entitiesCatalog.entities({ + filters: [{ 'metadata.uid': writtenEntities.map(e => e.entityId) }], + tx, + }); if (dryRun) { // If this is only a dry run, cancel the database transaction even if it was successful. diff --git a/plugins/catalog-backend/src/service/router.test.ts b/plugins/catalog-backend/src/service/router.test.ts index 3a43ac96ad..95dc8e82d1 100644 --- a/plugins/catalog-backend/src/service/router.test.ts +++ b/plugins/catalog-backend/src/service/router.test.ts @@ -23,8 +23,6 @@ import { LocationResponse } from '../catalog/types'; import { HigherOrderOperation } from '../ingestion/types'; import { createRouter } from './router'; -// TODO: ... - describe('createRouter', () => { let entitiesCatalog: jest.Mocked; let locationsCatalog: jest.Mocked; diff --git a/plugins/catalog/src/api/CatalogClient.ts b/plugins/catalog/src/api/CatalogClient.ts index 40622da60a..85fd44cf01 100644 --- a/plugins/catalog/src/api/CatalogClient.ts +++ b/plugins/catalog/src/api/CatalogClient.ts @@ -95,9 +95,12 @@ export class CatalogClient implements CatalogApi { async addLocation({ type = 'url', target, + dryRun, }: AddLocationRequest): Promise { const response = await fetch( - `${await this.discoveryApi.getBaseUrl('catalog')}/locations`, + `${await this.discoveryApi.getBaseUrl('catalog')}/locations${ + dryRun ? '?dryRun=true' : '' + }`, { headers: { 'Content-Type': 'application/json', diff --git a/plugins/catalog/src/api/types.ts b/plugins/catalog/src/api/types.ts index 324a8d43fd..06030e6ac5 100644 --- a/plugins/catalog/src/api/types.ts +++ b/plugins/catalog/src/api/types.ts @@ -43,6 +43,7 @@ export interface CatalogApi { export type AddLocationRequest = { type?: string; target: string; + dryRun?: boolean; }; export type AddLocationResponse = { diff --git a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx index 875b1f8660..fae5d649b9 100644 --- a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx +++ b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx @@ -20,17 +20,18 @@ import React from 'react'; import { RegisterComponentForm } from './RegisterComponentForm'; describe('RegisterComponentForm', () => { - it('should initially render a disabled button', async () => { + it('should initially render disabled buttons', async () => { render(); expect( await screen.findByText(/Enter the full path to the catalog-info.yaml/), ).toBeInTheDocument(); - expect(screen.getByText('Submit').closest('button')).toBeDisabled(); + expect(screen.getByText('Validate').closest('button')).toBeDisabled(); + expect(screen.getByText('Register').closest('button')).toBeDisabled(); }); - it('should enable a submit button when the target url is set', async () => { + it('should enable the submit buttons when the target url is set', async () => { render(); await act(async () => { @@ -40,7 +41,8 @@ describe('RegisterComponentForm', () => { ); }); - expect(screen.getByText('Submit').closest('button')).not.toBeDisabled(); + expect(screen.getByText('Validate').closest('button')).not.toBeDisabled(); + expect(screen.getByText('Register').closest('button')).not.toBeDisabled(); }); it('should show spinner while submitting', async () => { diff --git a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx index 56dd50ea67..de183869f7 100644 --- a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx +++ b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx @@ -33,8 +33,11 @@ const useStyles = makeStyles(theme => ({ display: 'flex', flexFlow: 'column nowrap', }, - submit: { - marginTop: theme.spacing(1), + buttonSpacing: { + marginLeft: theme.spacing(1), + }, + buttons: { + marginTop: theme.spacing(2), }, select: { minWidth: 120, @@ -54,12 +57,21 @@ export const RegisterComponentForm = ({ onSubmit, submitting }: Props) => { const hasErrors = !!errors.entityLocation; const dirty = formState?.isDirty; + const onSubmitValidate = handleSubmit(data => { + data.mode = 'validate'; + onSubmit(data); + }); + + const onSubmitRegister = handleSubmit(data => { + data.mode = 'register'; + onSubmit(data); + }); + return submitting ? ( ) : (
@@ -87,15 +99,27 @@ export const RegisterComponentForm = ({ onSubmit, submitting }: Props) => { )} - +
+ + +
); }; diff --git a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx index c7f94e2060..d0b56598b2 100644 --- a/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx +++ b/plugins/register-component/src/components/RegisterComponentPage/RegisterComponentPage.tsx @@ -75,27 +75,30 @@ export const RegisterComponentPage = ({ location: Location; } | null; error: null | Error; + dryRun: boolean; }>({ data: null, error: null, + dryRun: false, }); const handleSubmit = async (formData: Record) => { setFormState(FormStates.Submitting); - const { entityLocation: target } = formData; + const { entityLocation: target, mode } = formData; + const dryRun = mode === 'validate'; try { - const data = await catalogApi.addLocation({ target }); + const data = await catalogApi.addLocation({ target, dryRun }); if (!isMounted()) return; - setResult({ error: null, data }); + setResult({ error: null, data, dryRun }); setFormState(FormStates.Success); } catch (e) { errorApi.post(e); if (!isMounted()) return; - setResult({ error: e, data: null }); + setResult({ error: e, data: null, dryRun }); setFormState(FormStates.Idle); } }; @@ -124,6 +127,7 @@ export const RegisterComponentPage = ({ {formState === FormStates.Success && ( setFormState(FormStates.Idle)} classes={{ paper: classes.dialogPaper }} catalogRouteRef={catalogRouteRef} diff --git a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx index 9631865ffb..2b57050661 100644 --- a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx +++ b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.tsx @@ -29,7 +29,7 @@ import { List, ListItem, } from '@material-ui/core'; -import React from 'react'; +import React, { useState } from 'react'; import { generatePath, resolvePath } from 'react-router'; import { Link as RouterLink } from 'react-router-dom'; @@ -37,6 +37,7 @@ type Props = { onClose: () => void; classes?: Record; entities: Entity[]; + dryRun?: boolean; catalogRouteRef: RouteRef; }; @@ -64,49 +65,71 @@ export const RegisterComponentResultDialog = ({ onClose, classes, entities, + dryRun, catalogRouteRef, -}: Props) => ( - - Registration Result - - - The following entities have been successfully created: - - - {entities.map((entity: any, index: number) => { - const entityPath = getEntityCatalogPath({ entity, catalogRouteRef }); - return ( - - - - {entityPath} - - ), - }} - /> - - {index < entities.length - 1 && } - - ); - })} - - - - - - -); +}: Props) => { + const [open, setOpen] = useState(true); + const handleClose = () => { + setOpen(false); + }; + + return ( + + + {dryRun ? 'Validation Result' : 'Registration Result'} + + + + {dryRun + ? 'The following entities would be created:' + : 'The following entities have been successfully created:'} + + + {entities.map((entity: any, index: number) => { + const entityPath = getEntityCatalogPath({ + entity, + catalogRouteRef, + }); + return ( + + + + {entityPath} + + ), + }} + /> + + {index < entities.length - 1 && } + + ); + })} + + + + {dryRun && ( + + )} + {!dryRun && ( + + )} + + + ); +};