From 3e1a499cec737c5a3c784b19e24e5fdd4fd1bb47 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 4 Oct 2018 13:33:10 -0700 Subject: [PATCH] SHIBUI-914 Updating sources to use json-schema --- ui/src/app/app.module.ts | 3 + .../model/wizards/metadata-source-wizard.ts | 104 +++++ .../model/entity-attributes.filter.spec.ts | 4 +- .../filter/model/entity-attributes.filter.ts | 6 +- ui/src/app/metadata/metadata.module.ts | 7 +- .../metadata/provider/action/editor.action.ts | 43 +- .../provider-wizard-summary.component.ts | 2 +- .../container/provider-edit-step.component.ts | 9 +- .../container/provider-edit.component.ts | 4 +- .../provider-wizard-step.component.ts | 8 +- .../container/provider-wizard.component.ts | 3 +- .../metadata/provider/effect/editor.effect.ts | 16 +- .../provider/model/base.provider.form.spec.ts | 4 +- .../provider/model/base.provider.form.ts | 44 +- .../file-backed-http.provider.form.spec.ts | 4 +- ui/src/app/metadata/provider/model/index.ts | 4 +- .../app/metadata/provider/provider.module.ts | 9 +- .../provider/reducer/editor.reducer.ts | 48 +- ui/src/app/metadata/provider/reducer/index.ts | 29 -- .../metadata/resolver/action/editor.action.ts | 44 -- .../metadata/resolver/action/entity.action.ts | 46 ++ .../component/wizard-nav.component.ts | 8 +- .../container/copy-resolver.component.ts | 1 - .../resolver/container/draft.component.ts | 5 +- .../resolver/container/editor.component.ts | 16 +- ...nt.html => resolver-wizard.component.html} | 4 +- ...nt.scss => resolver-wizard.component.scss} | 0 .../container/resolver-wizard.component.ts | 119 +++++ .../resolver/container/wizard.component.ts | 155 ------ .../{editor.effect.ts => entity.effect.ts} | 14 +- .../metadata/resolver/effect/wizard.effect.ts | 10 +- .../resolver/reducer/editor.reducer.ts | 67 --- ...reducer.spec.ts => entity.reducer.spec.ts} | 96 ++-- .../resolver/reducer/entity.reducer.ts | 56 +++ ui/src/app/metadata/resolver/reducer/index.ts | 28 +- .../app/metadata/resolver/resolver.module.ts | 13 +- .../app/metadata/resolver/resolver.routing.ts | 4 +- .../metadata/resolver/wizard-definition.ts | 5 + ui/src/app/wizard/action/wizard.action.ts | 42 +- ui/src/app/wizard/model/form-definition.ts | 6 +- ui/src/app/wizard/model/wizard.ts | 7 + ui/src/app/wizard/reducer/index.ts | 29 ++ ui/src/app/wizard/reducer/wizard.reducer.ts | 51 +- .../assets/schema/source/metadata-source.json | 442 ++++++++++++++++++ 44 files changed, 1054 insertions(+), 565 deletions(-) create mode 100644 ui/src/app/metadata/domain/model/wizards/metadata-source-wizard.ts delete mode 100644 ui/src/app/metadata/resolver/action/editor.action.ts create mode 100644 ui/src/app/metadata/resolver/action/entity.action.ts rename ui/src/app/metadata/resolver/container/{wizard.component.html => resolver-wizard.component.html} (96%) rename ui/src/app/metadata/resolver/container/{wizard.component.scss => resolver-wizard.component.scss} (100%) create mode 100644 ui/src/app/metadata/resolver/container/resolver-wizard.component.ts delete mode 100644 ui/src/app/metadata/resolver/container/wizard.component.ts rename ui/src/app/metadata/resolver/effect/{editor.effect.ts => entity.effect.ts} (87%) delete mode 100644 ui/src/app/metadata/resolver/reducer/editor.reducer.ts rename ui/src/app/metadata/resolver/reducer/{editor.reducer.spec.ts => entity.reducer.spec.ts} (52%) create mode 100644 ui/src/app/metadata/resolver/reducer/entity.reducer.ts create mode 100644 ui/src/app/metadata/resolver/wizard-definition.ts create mode 100644 ui/src/assets/schema/source/metadata-source.json diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index 6d54d350a..63830bd95 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -26,6 +26,8 @@ import { FormModule } from './schema-form/schema-form.module'; import { environment } from '../environments/environment.prod'; import { getCurrentLocale } from './shared/util'; import { I18nModule } from './i18n/i18n.module'; +import { WidgetRegistry } from 'ngx-schema-form'; +import { CustomWidgetRegistry } from './schema-form/registry'; @NgModule({ declarations: [ @@ -59,6 +61,7 @@ import { I18nModule } from './i18n/i18n.module'; ], providers: [ NavigatorService, + { provide: WidgetRegistry, useClass: CustomWidgetRegistry }, { provide: RouterStateSerializer, useClass: CustomRouterStateSerializer }, { provide: HTTP_INTERCEPTORS, diff --git a/ui/src/app/metadata/domain/model/wizards/metadata-source-wizard.ts b/ui/src/app/metadata/domain/model/wizards/metadata-source-wizard.ts new file mode 100644 index 000000000..90fcb9d0c --- /dev/null +++ b/ui/src/app/metadata/domain/model/wizards/metadata-source-wizard.ts @@ -0,0 +1,104 @@ +import { Wizard, WizardStep } from '../../../../wizard/model'; +import { MetadataResolver } from '../metadata-resolver'; + +export class MetadataSourceWizard implements Wizard { + label = 'Metadata Source'; + type = '@MetadataProvider'; + steps: WizardStep[] = [ + { + index: 2, + id: 'org-info', + label: 'label.org-info', + schema: 'assets/schema/source/metadata-source.json', + fields: [ + 'organization', + 'contacts' + ] + }, + { + index: 3, + id: 'metadata-ui', + label: 'label.metadata-ui', + schema: 'assets/schema/source/metadata-source.json', + fields: [ + 'mdui' + ] + }, + { + index: 4, + id: 'descriptor-info', + label: 'label.descriptor-info', + schema: 'assets/schema/source/metadata-source.json', + fields: [ + 'serviceProviderSsoDescriptor' + ] + }, + { + index: 5, + id: 'logout-endpoints', + label: 'label.logout-endpoints', + schema: 'assets/schema/source/metadata-source.json', + fields: [ + 'logoutEndpoints' + ] + }, + { + index: 6, + id: 'key-info', + label: 'label.key-info', + schema: 'assets/schema/source/metadata-source.json', + fields: [ + 'securityInfo' + ] + }, + { + index: 7, + id: 'assertion', + label: 'label.assertion', + schema: 'assets/schema/source/metadata-source.json', + fields: [ + 'assertionConsumerServices' + ] + }, + { + index: 8, + id: 'relying-party', + label: 'label.relying-party', + schema: 'assets/schema/source/metadata-source.json', + fields: [ + 'relyingPartyOverrides' + ] + }, + { + index: 9, + id: 'attribute', + label: 'label.attribute-release', + schema: 'assets/schema/source/metadata-source.json', + fields: [ + 'attributeRelease' + ] + }, + { + index: 10, + id: 'finish', + label: 'label.finished', + schema: 'assets/schema/source/metadata-source.json', + fields: [ + 'serviceEnabled' + ], + summary: true + } + ]; + + parser (changes: Partial, schema?: any): any { + return changes; + } + + formatter (changes: Partial < MetadataResolver >, schema ?: any): any { + return changes; + } + + getValidators(...args: any[]): { [key: string]: any } { + return {}; + } +} diff --git a/ui/src/app/metadata/filter/model/entity-attributes.filter.spec.ts b/ui/src/app/metadata/filter/model/entity-attributes.filter.spec.ts index 35ebaff95..ba27a427d 100644 --- a/ui/src/app/metadata/filter/model/entity-attributes.filter.spec.ts +++ b/ui/src/app/metadata/filter/model/entity-attributes.filter.spec.ts @@ -7,8 +7,8 @@ describe('Entity Attributes filter form', () => { describe('transformer', () => { it('should not modify the object', () => { - expect(EntityAttributesFilter.translate.formatter({})).toEqual({}); - expect(EntityAttributesFilter.translate.parser({})).toEqual({}); + expect(EntityAttributesFilter.formatter({})).toEqual({}); + expect(EntityAttributesFilter.parser({})).toEqual({}); }); }); }); diff --git a/ui/src/app/metadata/filter/model/entity-attributes.filter.ts b/ui/src/app/metadata/filter/model/entity-attributes.filter.ts index bf2cec9b4..fc358dece 100644 --- a/ui/src/app/metadata/filter/model/entity-attributes.filter.ts +++ b/ui/src/app/metadata/filter/model/entity-attributes.filter.ts @@ -9,8 +9,6 @@ export const EntityAttributesFilter: FormDefinition = { const validators = {}; return validators; }, - translate: { - parser: (changes: any): MetadataFilter => changes, - formatter: (changes: MetadataFilter): any => changes - } + parser: (changes: any): MetadataFilter => changes, + formatter: (changes: MetadataFilter): any => changes }; diff --git a/ui/src/app/metadata/metadata.module.ts b/ui/src/app/metadata/metadata.module.ts index 9c88a04df..946c89423 100644 --- a/ui/src/app/metadata/metadata.module.ts +++ b/ui/src/app/metadata/metadata.module.ts @@ -1,6 +1,5 @@ import { NgModule } from '@angular/core'; - import { ResolverModule } from './resolver/resolver.module'; import { FilterModule } from './filter/filter.module'; import { DomainModule } from './domain/domain.module'; @@ -9,6 +8,8 @@ import { ManagerModule } from './manager/manager.module'; import { MetadataRoutingModule } from './metadata.routing'; import { ProviderModule } from './provider/provider.module'; import { I18nModule } from '../i18n/i18n.module'; +import { CustomWidgetRegistry } from '../schema-form/registry'; +import { WidgetRegistry } from 'ngx-schema-form'; @NgModule({ @@ -21,7 +22,9 @@ import { I18nModule } from '../i18n/i18n.module'; MetadataRoutingModule, I18nModule ], - providers: [], + providers: [ + { provide: WidgetRegistry, useClass: CustomWidgetRegistry } + ], declarations: [ MetadataPageComponent ] diff --git a/ui/src/app/metadata/provider/action/editor.action.ts b/ui/src/app/metadata/provider/action/editor.action.ts index 3c82c0c19..37a99c98b 100644 --- a/ui/src/app/metadata/provider/action/editor.action.ts +++ b/ui/src/app/metadata/provider/action/editor.action.ts @@ -2,16 +2,8 @@ import { Action } from '@ngrx/store'; export enum EditorActionTypes { UPDATE_STATUS = '[Provider Editor] Update Status', - LOAD_SCHEMA_REQUEST = '[Provider Editor] Load Schema Request', - LOAD_SCHEMA_SUCCESS = '[Provider Editor] Load Schema Success', - LOAD_SCHEMA_FAIL = '[Provider Editor] Load Schema Fail', - SELECT_PROVIDER_TYPE = '[Provider Editor] Select Provider Type', - - CLEAR = '[Provider Editor] Clear', - - LOCK = '[Provider Editor] Lock', - UNLOCK = '[Provider Editor] Unlock' + CLEAR = '[Provider Editor] Clear' } export class UpdateStatus implements Action { @@ -20,24 +12,6 @@ export class UpdateStatus implements Action { constructor(public payload: { [key: string]: string }) { } } -export class LoadSchemaRequest implements Action { - readonly type = EditorActionTypes.LOAD_SCHEMA_REQUEST; - - constructor(public payload: string) { } -} - -export class LoadSchemaSuccess implements Action { - readonly type = EditorActionTypes.LOAD_SCHEMA_SUCCESS; - - constructor(public payload: any) { } -} - -export class LoadSchemaFail implements Action { - readonly type = EditorActionTypes.LOAD_SCHEMA_FAIL; - - constructor(public payload: Error) { } -} - export class SelectProviderType implements Action { readonly type = EditorActionTypes.SELECT_PROVIDER_TYPE; @@ -48,20 +22,7 @@ export class ClearEditor implements Action { readonly type = EditorActionTypes.CLEAR; } -export class LockEditor implements Action { - readonly type = EditorActionTypes.LOCK; -} - -export class UnlockEditor implements Action { - readonly type = EditorActionTypes.UNLOCK; -} - export type EditorActionUnion = | UpdateStatus - | LoadSchemaRequest - | LoadSchemaSuccess - | LoadSchemaFail | SelectProviderType - | ClearEditor - | LockEditor - | UnlockEditor; + | ClearEditor; diff --git a/ui/src/app/metadata/provider/component/provider-wizard-summary.component.ts b/ui/src/app/metadata/provider/component/provider-wizard-summary.component.ts index c74c4935b..8d350d163 100644 --- a/ui/src/app/metadata/provider/component/provider-wizard-summary.component.ts +++ b/ui/src/app/metadata/provider/component/provider-wizard-summary.component.ts @@ -58,7 +58,7 @@ export class ProviderWizardSummaryComponent implements OnChanges { id: step.id, index: step.index, label: step.label, - properties: getStepProperties(schemas[step.id], def.translate.formatter(model)) + properties: getStepProperties(schemas[step.id], def.formatter(model)) }) ); diff --git a/ui/src/app/metadata/provider/container/provider-edit-step.component.ts b/ui/src/app/metadata/provider/container/provider-edit-step.component.ts index 6ffc7e13c..6ba2346bb 100644 --- a/ui/src/app/metadata/provider/container/provider-edit-step.component.ts +++ b/ui/src/app/metadata/provider/container/provider-edit-step.component.ts @@ -3,9 +3,10 @@ import { Observable, Subject } from 'rxjs'; import { Store } from '@ngrx/store'; import * as fromProvider from '../reducer'; -import { UpdateStatus, LockEditor, UnlockEditor } from '../action/editor.action'; +import { UpdateStatus } from '../action/editor.action'; import { Wizard, WizardStep } from '../../../wizard/model'; import { MetadataProvider } from '../../domain/model'; +import { LockEditor, UnlockEditor } from '../../../wizard/action/wizard.action'; import * as fromWizard from '../../../wizard/reducer'; import { withLatestFrom, map, skipWhile, distinctUntilChanged, startWith, combineLatest } from 'rxjs/operators'; @@ -46,7 +47,7 @@ export class ProviderEditStepComponent implements OnDestroy { this.changes$ = this.store.select(fromProvider.getEntityChanges); this.provider$ = this.store.select(fromProvider.getSelectedProvider); this.step$ = this.store.select(fromWizard.getCurrent); - this.schema$ = this.store.select(fromProvider.getSchema); + this.schema$ = this.store.select(fromWizard.getParsedSchema); this.step$.subscribe(s => { if (s && s.locked) { @@ -86,14 +87,14 @@ export class ProviderEditStepComponent implements OnDestroy { definition })), skipWhile(({ model, definition }) => !definition || !model), - map(({ model, definition }) => definition.translate.formatter(model)) + map(({ model, definition }) => definition.formatter(model)) ); this.valueChangeEmitted$.pipe( map(changes => changes.value), withLatestFrom(this.definition$), skipWhile(([ changes, definition ]) => !definition || !changes), - map(([ changes, definition ]) => definition.translate.parser(changes)) + map(([ changes, definition ]) => definition.parser(changes)) ) .subscribe(changes => this.store.dispatch(new UpdateProvider(changes))); diff --git a/ui/src/app/metadata/provider/container/provider-edit.component.ts b/ui/src/app/metadata/provider/container/provider-edit.component.ts index a2c2e0b86..a1de135b6 100644 --- a/ui/src/app/metadata/provider/container/provider-edit.component.ts +++ b/ui/src/app/metadata/provider/container/provider-edit.component.ts @@ -5,8 +5,8 @@ import { skipWhile, map, combineLatest } from 'rxjs/operators'; import { Store } from '@ngrx/store'; import * as fromWizard from '../../../wizard/reducer'; import * as fromProvider from '../reducer'; -import { ClearWizard, SetDefinition, SetIndex } from '../../../wizard/action/wizard.action'; -import { ClearEditor, LoadSchemaRequest } from '../action/editor.action'; +import { ClearWizard, SetDefinition, SetIndex, LoadSchemaRequest } from '../../../wizard/action/wizard.action'; +import { ClearEditor } from '../action/editor.action'; import { MetadataProvider } from '../../domain/model'; import { ClearProvider } from '../action/entity.action'; import { Wizard } from '../../../wizard/model'; diff --git a/ui/src/app/metadata/provider/container/provider-wizard-step.component.ts b/ui/src/app/metadata/provider/container/provider-wizard-step.component.ts index 0b9a7469e..db8e461ee 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard-step.component.ts +++ b/ui/src/app/metadata/provider/container/provider-wizard-step.component.ts @@ -42,10 +42,12 @@ export class ProviderWizardStepComponent implements OnDestroy { constructor( private store: Store, ) { - this.schema$ = this.store.select(fromProvider.getSchema); + this.schema$ = this.store.select(fromWizard.getParsedSchema); this.definition$ = this.store.select(fromWizard.getWizardDefinition); this.changes$ = this.store.select(fromProvider.getEntityChanges); + this.schema$.subscribe(s => console.log(s)); + this.validators$ = this.definition$.pipe( withLatestFrom( this.store.select(fromProvider.getProviderNames), @@ -71,14 +73,14 @@ export class ProviderWizardStepComponent implements OnDestroy { definition })), skipWhile(({ model, definition }) => !definition || !model), - map(({ model, definition }) => definition.translate.formatter(model)) + map(({ model, definition }) => definition.formatter(model)) ); this.valueChangeEmitted$.pipe( withLatestFrom(this.schema$, this.definition$), map(([changes, schema, definition]) => this.resetSelectedType(changes, schema, definition)), skipWhile(({ changes, definition }) => !definition || !changes), - map(({ changes, definition }) => definition.translate.parser(changes)) + map(({ changes, definition }) => definition.parser(changes)) ) .subscribe(changes => this.store.dispatch(new UpdateProvider(changes))); diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.ts b/ui/src/app/metadata/provider/container/provider-wizard.component.ts index 8a4e481f5..24140a0a2 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard.component.ts +++ b/ui/src/app/metadata/provider/container/provider-wizard.component.ts @@ -5,7 +5,8 @@ import { Store } from '@ngrx/store'; import * as fromProvider from '../reducer'; import * as fromWizard from '../../../wizard/reducer'; import { SetIndex, SetDisabled, ClearWizard, SetDefinition } from '../../../wizard/action/wizard.action'; -import { LoadSchemaRequest, ClearEditor } from '../action/editor.action'; +import { ClearEditor } from '../action/editor.action'; +import { LoadSchemaRequest } from '../../../wizard/action/wizard.action'; import { startWith } from 'rxjs/operators'; import { Wizard, WizardStep } from '../../../wizard/model'; import { MetadataProvider } from '../../domain/model'; diff --git a/ui/src/app/metadata/provider/effect/editor.effect.ts b/ui/src/app/metadata/provider/effect/editor.effect.ts index 3146ff91d..f9ac13baf 100644 --- a/ui/src/app/metadata/provider/effect/editor.effect.ts +++ b/ui/src/app/metadata/provider/effect/editor.effect.ts @@ -3,14 +3,18 @@ import { Effect, Actions, ofType } from '@ngrx/effects'; import { SchemaService } from '../../../schema-form/service/schema.service'; import { - LoadSchemaRequest, - LoadSchemaSuccess, - LoadSchemaFail, EditorActionTypes } from '../action/editor.action'; import { map, switchMap, catchError, withLatestFrom, debounceTime } from 'rxjs/operators'; import { of } from 'rxjs'; -import { SetDefinition, WizardActionTypes, AddSchema } from '../../../wizard/action/wizard.action'; +import { + LoadSchemaRequest, + LoadSchemaSuccess, + LoadSchemaFail, + SetDefinition, + WizardActionTypes, + AddSchema +} from '../../../wizard/action/wizard.action'; import { ResetChanges } from '../action/entity.action'; import * as fromWizard from '../../../wizard/reducer'; @@ -21,7 +25,7 @@ export class EditorEffects { @Effect() $loadSchemaRequest = this.actions$.pipe( - ofType(EditorActionTypes.LOAD_SCHEMA_REQUEST), + ofType(WizardActionTypes.LOAD_SCHEMA_REQUEST), map(action => action.payload), debounceTime(100), switchMap((schemaPath: string) => @@ -36,7 +40,7 @@ export class EditorEffects { @Effect() $loadSchemaSuccess = this.actions$.pipe( - ofType(EditorActionTypes.LOAD_SCHEMA_SUCCESS), + ofType(WizardActionTypes.LOAD_SCHEMA_SUCCESS), map(action => action.payload), withLatestFrom(this.store.select(fromWizard.getWizardIndex)), map(([schema, id]) => new AddSchema({ id, schema })) diff --git a/ui/src/app/metadata/provider/model/base.provider.form.spec.ts b/ui/src/app/metadata/provider/model/base.provider.form.spec.ts index 307368ef2..7e1aeff61 100644 --- a/ui/src/app/metadata/provider/model/base.provider.form.spec.ts +++ b/ui/src/app/metadata/provider/model/base.provider.form.spec.ts @@ -2,8 +2,8 @@ import { BaseMetadataProviderEditor } from './base.provider.form'; describe('BaseMetadataProviderForm', () => { - const parser = BaseMetadataProviderEditor.translate.parser; - const formatter = BaseMetadataProviderEditor.translate.formatter; + const parser = BaseMetadataProviderEditor.parser; + const formatter = BaseMetadataProviderEditor.formatter; const requiredValidUntilFilter = { maxValidityInterval: 1, diff --git a/ui/src/app/metadata/provider/model/base.provider.form.ts b/ui/src/app/metadata/provider/model/base.provider.form.ts index 33ff3aa3a..459eaa956 100644 --- a/ui/src/app/metadata/provider/model/base.provider.form.ts +++ b/ui/src/app/metadata/provider/model/base.provider.form.ts @@ -42,28 +42,26 @@ export const BaseMetadataProviderEditor: Wizard = { }; return validators; }, - translate: { - parser: (changes: any): BaseMetadataProvider => changes.metadataFilters ? ({ - ...changes, - metadataFilters: [ - ...Object.keys(changes.metadataFilters).reduce((collection, filterName) => ([ - ...collection, - { - ...changes.metadataFilters[filterName], - '@type': filterName - } - ]), []) - ] - }) : changes, - formatter: (changes: BaseMetadataProvider): any => changes.metadataFilters ? ({ - ...changes, - metadataFilters: { - ...(changes.metadataFilters || []).reduce((collection, filter) => ({ - ...collection, - [filter['@type']]: filter - }), {}) - } - }) : changes - }, + parser: (changes: any): BaseMetadataProvider => (changes.metadataFilters ? ({ + ...changes, + metadataFilters: [ + ...Object.keys(changes.metadataFilters).reduce((collection, filterName) => ([ + ...collection, + { + ...changes.metadataFilters[filterName], + '@type': filterName + } + ]), []) + ] + }) : changes), + formatter: (changes: BaseMetadataProvider): any => (changes.metadataFilters ? ({ + ...changes, + metadataFilters: { + ...(changes.metadataFilters || []).reduce((collection, filter) => ({ + ...collection, + [filter['@type']]: filter + }), {}) + } + }) : changes), steps: [] }; diff --git a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.spec.ts b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.spec.ts index 4bba62c7d..492bc8f3d 100644 --- a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.spec.ts +++ b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.spec.ts @@ -3,8 +3,8 @@ import { FileBackedHttpMetadataProvider } from '../../domain/model/providers'; describe('FileBackedHttpMetadataProviderWizard', () => { - const parser = FileBackedHttpMetadataProviderWizard.translate.parser; - const formatter = FileBackedHttpMetadataProviderWizard.translate.formatter; + const parser = FileBackedHttpMetadataProviderWizard.parser; + const formatter = FileBackedHttpMetadataProviderWizard.formatter; const requiredValidUntilFilter = { maxValidityInterval: 1, diff --git a/ui/src/app/metadata/provider/model/index.ts b/ui/src/app/metadata/provider/model/index.ts index a6c438df4..cfb64a08d 100644 --- a/ui/src/app/metadata/provider/model/index.ts +++ b/ui/src/app/metadata/provider/model/index.ts @@ -1,14 +1,12 @@ import { FileBackedHttpMetadataProviderWizard } from './file-backed-http.provider.form'; import { FileBackedHttpMetadataProviderEditor } from './file-backed-http.provider.form'; -import { BaseMetadataProviderEditor } from './base.provider.form'; export const MetadataProviderWizardTypes = [ FileBackedHttpMetadataProviderWizard ]; export const MetadataProviderEditorTypes = [ - FileBackedHttpMetadataProviderEditor, - BaseMetadataProviderEditor + FileBackedHttpMetadataProviderEditor ]; export * from './file-backed-http.provider.form'; diff --git a/ui/src/app/metadata/provider/provider.module.ts b/ui/src/app/metadata/provider/provider.module.ts index 219f3d19c..4ce52c7db 100644 --- a/ui/src/app/metadata/provider/provider.module.ts +++ b/ui/src/app/metadata/provider/provider.module.ts @@ -15,9 +15,7 @@ import { WizardModule } from '../../wizard/wizard.module'; import * as fromProvider from './reducer'; import { EditorEffects } from './effect/editor.effect'; -import { WidgetRegistry} from 'ngx-schema-form'; import { FormModule } from '../../schema-form/schema-form.module'; -import { CustomWidgetRegistry } from '../../schema-form/registry'; import { SummaryPropertyComponent } from './component/summary-property.component'; import { CollectionEffects } from './effect/collection.effect'; import { SharedModule } from '../../shared/shared.module'; @@ -32,6 +30,8 @@ import { UnsavedProviderComponent } from './component/unsaved-provider.dialog'; import { ContentionModule } from '../../contention/contention.module'; import { DeleteFilterComponent } from './component/delete-filter.component'; import { I18nModule } from '../../i18n/i18n.module'; +import { WidgetRegistry } from 'ngx-schema-form'; +import { CustomWidgetRegistry } from '../../schema-form/registry'; @NgModule({ declarations: [ @@ -70,10 +70,7 @@ import { I18nModule } from '../../i18n/i18n.module'; export class ProviderModule { static forRoot(): ModuleWithProviders { return { - ngModule: RootProviderModule, - providers: [ - { provide: WidgetRegistry, useClass: CustomWidgetRegistry } - ] + ngModule: RootProviderModule }; } } diff --git a/ui/src/app/metadata/provider/reducer/editor.reducer.ts b/ui/src/app/metadata/provider/reducer/editor.reducer.ts index 32c96c86c..0d56550b7 100644 --- a/ui/src/app/metadata/provider/reducer/editor.reducer.ts +++ b/ui/src/app/metadata/provider/reducer/editor.reducer.ts @@ -2,20 +2,12 @@ import { EditorActionTypes, EditorActionUnion } from '../action/editor.action'; export interface EditorState { status: { [key: string]: string }; - schemaPath: string; - loading: boolean; - schema: any; type: string; - locked: boolean; } export const initialState: EditorState = { status: {}, - schemaPath: null, - loading: false, - schema: null, - type: null, - locked: false + type: null }; export function reducer(state = initialState, action: EditorActionUnion): EditorState { @@ -40,49 +32,13 @@ export function reducer(state = initialState, action: EditorActionUnion): Editor } }; } - case EditorActionTypes.LOAD_SCHEMA_REQUEST: { - return { - ...state, - loading: true, - schemaPath: action.payload - }; - } - case EditorActionTypes.LOAD_SCHEMA_SUCCESS: { - return { - ...state, - loading: false, - schema: action.payload - }; - } - case EditorActionTypes.LOAD_SCHEMA_FAIL: { - return { - ...state, - loading: false, - schema: initialState.schema - }; - } - - case EditorActionTypes.LOCK: { - return { - ...state, - locked: true - }; - } - - case EditorActionTypes.UNLOCK: { - return { - ...state, - locked: false - }; - } default: { return state; } } } -export const getSchema = (state: EditorState) => state.schema; -export const getLocked = (state: EditorState) => state.locked; + export const isEditorValid = (state: EditorState) => !Object.keys(state.status).some(key => state.status[key] === ('INVALID')); diff --git a/ui/src/app/metadata/provider/reducer/index.ts b/ui/src/app/metadata/provider/reducer/index.ts index f9def08d4..c05a233e3 100644 --- a/ui/src/app/metadata/provider/reducer/index.ts +++ b/ui/src/app/metadata/provider/reducer/index.ts @@ -39,35 +39,6 @@ export const getCollectionState = createSelector(getProviderState, getCollection Editor State */ -export function getSchemaParseFn(schema, locked): any { - if (!schema) { - return null; - } - return { - ...schema, - properties: Object.keys(schema.properties).reduce((prev, current) => { - return { - ...prev, - [current]: { - ...schema.properties[current], - readOnly: locked, - ...(schema.properties[current].hasOwnProperty('properties') ? - getSchemaParseFn(schema.properties[current], locked) : - {} - ) - } - }; - }, {}) - }; -} - -export const getSchemaLockedFn = (step, locked) => step ? step.locked ? locked : false : false; -export const getLockedStatus = createSelector(getEditorState, fromEditor.getLocked); -export const getLocked = createSelector(fromWizard.getCurrent, getLockedStatus, getSchemaLockedFn); - -export const getSchemaObject = createSelector(getEditorState, fromEditor.getSchema); -export const getSchema = createSelector(getSchemaObject, getLocked, getSchemaParseFn); - export const getEditorIsValid = createSelector(getEditorState, fromEditor.isEditorValid); export const getFormStatus = createSelector(getEditorState, fromEditor.getFormStatus); diff --git a/ui/src/app/metadata/resolver/action/editor.action.ts b/ui/src/app/metadata/resolver/action/editor.action.ts deleted file mode 100644 index 80da7774e..000000000 --- a/ui/src/app/metadata/resolver/action/editor.action.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { Action } from '@ngrx/store'; -import { MetadataResolver } from '../../domain/model'; - -export const UPDATE_STATUS = '[Editor] Update Status'; -export const UPDATE_SAVED = '[Editor] Update Saved'; -export const UPDATE_CHANGES = '[Editor] Update Changes'; -export const CANCEL_CHANGES = '[Editor] Cancel Changes'; -export const SAVE_CHANGES = '[Editor] Save Changes'; -export const RESET_CHANGES = '[Editor] Reset Changes'; - -export class UpdateStatus implements Action { - readonly type = UPDATE_STATUS; - - constructor(public payload: { [key: string]: string }) { } -} - -export class UpdateChanges implements Action { - readonly type = UPDATE_CHANGES; - - constructor(public payload: MetadataResolver) { } -} - -export class CancelChanges implements Action { - readonly type = CANCEL_CHANGES; -} - -export class SaveChanges implements Action { - readonly type = SAVE_CHANGES; - - constructor(public payload: MetadataResolver) { } -} - -export class ResetChanges implements Action { - readonly type = RESET_CHANGES; - - constructor() { } -} - -export type Actions = - | UpdateStatus - | UpdateChanges - | CancelChanges - | SaveChanges - | ResetChanges; diff --git a/ui/src/app/metadata/resolver/action/entity.action.ts b/ui/src/app/metadata/resolver/action/entity.action.ts new file mode 100644 index 000000000..490cd6a03 --- /dev/null +++ b/ui/src/app/metadata/resolver/action/entity.action.ts @@ -0,0 +1,46 @@ +import { Action } from '@ngrx/store'; +import { MetadataResolver } from '../../domain/model'; + +export enum ResolverEntityActionTypes { + UPDATE_STATUS = '[Resolver Entity] Update Status', + UPDATE_SAVING = '[Resolver Entity] Update Saving', + UPDATE_CHANGES = '[Resolver Entity] Update Changes', + CLEAR = '[Resolver Entity] Clear', + CANCEL = '[Resolver Entity] Cancel' +} + +export class UpdateStatus implements Action { + readonly type = ResolverEntityActionTypes.UPDATE_STATUS; + + constructor(public payload: { [key: string]: string }) { } +} + +export class UpdateChanges implements Action { + readonly type = ResolverEntityActionTypes.UPDATE_CHANGES; + + constructor(public payload: MetadataResolver) { } +} + +export class UpdateSaving implements Action { + readonly type = ResolverEntityActionTypes.UPDATE_SAVING; + + constructor(public payload: boolean) { } +} + +export class Clear implements Action { + readonly type = ResolverEntityActionTypes.CLEAR; + + constructor() { } +} + +export class Cancel implements Action { + readonly type = ResolverEntityActionTypes.CANCEL; + + constructor() { } +} + +export type ResolverEntityActionUnion = + | UpdateStatus + | UpdateChanges + | UpdateSaving + | Clear; diff --git a/ui/src/app/metadata/resolver/component/wizard-nav.component.ts b/ui/src/app/metadata/resolver/component/wizard-nav.component.ts index c55a30191..e743c887e 100644 --- a/ui/src/app/metadata/resolver/component/wizard-nav.component.ts +++ b/ui/src/app/metadata/resolver/component/wizard-nav.component.ts @@ -2,7 +2,7 @@ import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { Store } from '@ngrx/store'; -import * as fromEditor from '../reducer'; +import * as fromResolver from '../reducer'; import { WIZARD as WizardDef, EditorFlowDefinition } from '../editor-definition.const'; @Component({ @@ -27,7 +27,7 @@ export class WizardNavComponent implements OnChanges { wizard: EditorFlowDefinition[] = WizardDef; constructor( - private store: Store + private store: Store ) {} ngOnChanges(): void { @@ -38,8 +38,8 @@ export class WizardNavComponent implements OnChanges { this.isFirstPage = WizardDef[0].index === this.index; this.wizard = WizardDef; - this.wizardIsValid$ = this.store.select(fromEditor.getEditorIsValid); - this.wizardIsSaving$ = this.store.select(fromEditor.getEditorIsSaving); + this.wizardIsValid$ = this.store.select(fromResolver.getEntityIsValid); + this.wizardIsSaving$ = this.store.select(fromResolver.getEntityIsSaving); this.wizardIsInvalid$ = this.wizardIsValid$.pipe(map(valid => !valid)); } } /* istanbul ignore next */ diff --git a/ui/src/app/metadata/resolver/container/copy-resolver.component.ts b/ui/src/app/metadata/resolver/container/copy-resolver.component.ts index 217fb4226..c07f88c35 100644 --- a/ui/src/app/metadata/resolver/container/copy-resolver.component.ts +++ b/ui/src/app/metadata/resolver/container/copy-resolver.component.ts @@ -16,7 +16,6 @@ import { SearchIds } from '../action/search.action'; import * as fromProvider from '../reducer'; import { CreateResolverCopyRequest, UpdateResolverCopySections } from '../action/copy.action'; - @Component({ selector: 'copy-resolver-form', templateUrl: './copy-resolver.component.html' diff --git a/ui/src/app/metadata/resolver/container/draft.component.ts b/ui/src/app/metadata/resolver/container/draft.component.ts index b1838f18e..e8c865c53 100644 --- a/ui/src/app/metadata/resolver/container/draft.component.ts +++ b/ui/src/app/metadata/resolver/container/draft.component.ts @@ -1,7 +1,6 @@ -import { Component, Output, Input, EventEmitter, OnDestroy, ChangeDetectionStrategy } from '@angular/core'; -import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms'; +import { Component, OnDestroy } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { Observable, Subscription } from 'rxjs'; +import { Subscription } from 'rxjs'; import { distinctUntilChanged, map } from 'rxjs/operators'; import { Store } from '@ngrx/store'; diff --git a/ui/src/app/metadata/resolver/container/editor.component.ts b/ui/src/app/metadata/resolver/container/editor.component.ts index 0071a97dc..312860f51 100644 --- a/ui/src/app/metadata/resolver/container/editor.component.ts +++ b/ui/src/app/metadata/resolver/container/editor.component.ts @@ -20,7 +20,7 @@ import * as fromResolver from '../reducer'; import { UpdateResolverRequest } from '../action/collection.action'; import { ProviderStatusEmitter, ProviderValueEmitter } from '../../domain/service/provider-change-emitter.service'; -import { UpdateStatus, UpdateChanges, CancelChanges } from '../action/editor.action'; +import { UpdateStatus, UpdateChanges, Cancel } from '../action/entity.action'; import { EDITOR as EditorDef, EditorFlowDefinition } from '../editor-definition.const'; import { UnsavedDialogComponent } from '../component/unsaved-dialog.component'; @@ -69,7 +69,7 @@ export class EditorComponent implements OnInit, OnDestroy { private modalService: NgbModal ) { this.resolver$ = this.store.select(fromResolver.getSelectedResolver); - this.changes$ = this.store.select(fromResolver.getEditorChanges); + this.changes$ = this.store.select(fromResolver.getEntityChanges); this.latest$ = this.resolver$.pipe( combineLatest(this.changes$, (base, changes) => Object.assign({}, base, changes)) @@ -79,11 +79,11 @@ export class EditorComponent implements OnInit, OnDestroy { this.editorIndex$ = this.route.params.pipe(map(params => Number(params.index))); this.currentPage$ = this.editorIndex$.pipe(map(index => EditorDef.find(r => r.index === index))); this.editor = EditorDef; - this.store.select(fromResolver.getEditorIsSaving).pipe( + this.store.select(fromResolver.getEntityIsSaving).pipe( takeUntil(this.ngUnsubscribe) ).subscribe(saving => this.saving = saving); - this.wizardIsValid$ = this.store.select(fromResolver.getEditorIsValid); + this.wizardIsValid$ = this.store.select(fromResolver.getEntityIsValid); this.wizardIsInvalid$ = this.wizardIsValid$.pipe(map(valid => !valid)); this.ids$ = this.store @@ -105,7 +105,7 @@ export class EditorComponent implements OnInit, OnDestroy { } cancel(): void { - this.store.dispatch(new CancelChanges()); + this.store.dispatch(new Cancel()); } go(event, index: number): void { @@ -147,7 +147,7 @@ export class EditorComponent implements OnInit, OnDestroy { skipWhile(() => this.saving) ).subscribe(latest => this.latest = latest); - this.invalidForms$ = this.store.select(fromResolver.getInvalidEditorForms); + this.invalidForms$ = this.store.select(fromResolver.getInvalidEntityForms); this.invalidForms$.pipe( distinctUntilChanged(), @@ -176,12 +176,12 @@ export class EditorComponent implements OnInit, OnDestroy { let modal = this.modalService.open(UnsavedDialogComponent); modal.componentInstance.resolver = this.latest; modal.componentInstance.message = 'editor'; - modal.componentInstance.action = new CancelChanges(); + modal.componentInstance.action = new Cancel(); modal.result.then( () => this.router.navigate([nextState.url]), () => console.warn('denied') ); } - return this.store.select(fromResolver.getEditorIsSaved); + return this.store.select(fromResolver.getEntityIsSaved); } } diff --git a/ui/src/app/metadata/resolver/container/wizard.component.html b/ui/src/app/metadata/resolver/container/resolver-wizard.component.html similarity index 96% rename from ui/src/app/metadata/resolver/container/wizard.component.html rename to ui/src/app/metadata/resolver/container/resolver-wizard.component.html index 6487cd6fc..b8a398c95 100644 --- a/ui/src/app/metadata/resolver/container/wizard.component.html +++ b/ui/src/app/metadata/resolver/container/resolver-wizard.component.html @@ -7,11 +7,11 @@ Add a new metadata source – {{ (currentPage$ | async).label | translate }} ({{ (resolver$ | async).serviceProviderName }}) - + diff --git a/ui/src/app/metadata/resolver/container/wizard.component.scss b/ui/src/app/metadata/resolver/container/resolver-wizard.component.scss similarity index 100% rename from ui/src/app/metadata/resolver/container/wizard.component.scss rename to ui/src/app/metadata/resolver/container/resolver-wizard.component.scss diff --git a/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts b/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts new file mode 100644 index 000000000..92b5738f6 --- /dev/null +++ b/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts @@ -0,0 +1,119 @@ +import { + Component, + OnDestroy, + Inject +} from '@angular/core'; +import { + ActivatedRoute, + Router, + ActivatedRouteSnapshot, + RouterStateSnapshot +} from '@angular/router'; +import { Observable, Subject, of } from 'rxjs'; +import { skipWhile } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; + +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; + +import { MetadataResolver } from '../../domain/model/metadata-resolver'; +import * as fromCollections from '../reducer'; +import * as draftActions from '../action/draft.action'; +import { AddResolverRequest } from '../action/collection.action'; +import * as fromResolver from '../reducer'; + +import { UpdateChanges } from '../action/entity.action'; +import { CanComponentDeactivate } from '../../../core/service/can-deactivate.guard'; + +import { UnsavedDialogComponent } from '../component/unsaved-dialog.component'; +import { METADATA_SOURCE_WIZARD } from '../wizard-definition'; +import { Wizard } from '../../../wizard/model'; +import { SetDefinition, SetIndex } from '../../../wizard/action/wizard.action'; + +import * as fromWizard from '../../../wizard/reducer'; +import { LoadSchemaRequest } from '../../../wizard/action/wizard.action'; + +@Component({ + selector: 'resolver-wizard-page', + templateUrl: './resolver-wizard.component.html', + styleUrls: ['./resolver-wizard.component.scss'] +}) +export class ResolverWizardComponent implements OnDestroy, CanComponentDeactivate { + + private ngUnsubscribe: Subject = new Subject(); + + resolver$: Observable; + resolver: MetadataResolver; + providerName$: Observable; + changes$: Observable; + changes: MetadataResolver; + latest: MetadataResolver; + + wizardIndex$: Observable; + wizardIndex: number; + currentPage$: Observable; + wizard$: Observable; + saved$: Observable; + saving: boolean; + + constructor( + private store: Store, + private route: ActivatedRoute, + private router: Router, + private modalService: NgbModal, + @Inject(METADATA_SOURCE_WIZARD) private sourceWizard: Wizard + ) { + this.store + .select(fromWizard.getCurrentWizardSchema) + .pipe( + skipWhile(s => !s) + ) + .subscribe(s => { + if (s) { + this.store.dispatch(new LoadSchemaRequest(s)); + } + }); + + this.store.dispatch(new SetDefinition(this.sourceWizard)); + this.store.dispatch(new SetIndex(this.sourceWizard.steps[0].id)); + + this.store.select(fromWizard.getParsedSchema).subscribe(s => console.log(s)); + } + + save(): void { + this.store.dispatch(new AddResolverRequest(this.latest)); + } + + next(index: number): void { + this.go(index.toString()); + } + + previous(index: number): void { + this.go(index.toString()); + } + + go(index: string): void { + this.store.dispatch(new SetIndex(index)); + } + + ngOnDestroy(): void { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } + + canDeactivate( + currentRoute: ActivatedRouteSnapshot, + currentState: RouterStateSnapshot, + nextState: RouterStateSnapshot + ): Observable { + if (nextState.url.match('wizard')) { return of(true); } + if (Object.keys(this.changes).length > 0) { + let modal = this.modalService.open(UnsavedDialogComponent); + modal.componentInstance.action = new UpdateChanges(this.latest); + modal.result.then( + () => this.router.navigate([nextState.url]), + () => console.warn('denied') + ); + } + return this.store.select(fromResolver.getEntityIsSaved); + } +} /* istanbul ignore next */ diff --git a/ui/src/app/metadata/resolver/container/wizard.component.ts b/ui/src/app/metadata/resolver/container/wizard.component.ts deleted file mode 100644 index e8efa06c3..000000000 --- a/ui/src/app/metadata/resolver/container/wizard.component.ts +++ /dev/null @@ -1,155 +0,0 @@ -import { - Component, - OnInit, - OnDestroy -} from '@angular/core'; -import { - ActivatedRoute, - Router, - ActivatedRouteSnapshot, - RouterStateSnapshot -} from '@angular/router'; -import { Observable, Subject, of } from 'rxjs'; -import { map, takeUntil, skipWhile, combineLatest } from 'rxjs/operators'; -import { Store } from '@ngrx/store'; - -import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; - -import { MetadataResolver } from '../../domain/model/metadata-resolver'; -import * as fromCollections from '../reducer'; -import * as draftActions from '../action/draft.action'; -import { AddResolverRequest } from '../action/collection.action'; -import * as fromEditor from '../reducer'; - -import { ProviderStatusEmitter, ProviderValueEmitter } from '../../domain/service/provider-change-emitter.service'; -import { UpdateStatus, UpdateChanges, SaveChanges } from '../action/editor.action'; -import { WIZARD as WizardDef, EditorFlowDefinition } from '../editor-definition.const'; -import { CanComponentDeactivate } from '../../../core/service/can-deactivate.guard'; - -import { UnsavedDialogComponent } from '../component/unsaved-dialog.component'; - - -@Component({ - selector: 'wizard-page', - templateUrl: './wizard.component.html', - styleUrls: ['./wizard.component.scss'] -}) -export class WizardComponent implements OnInit, OnDestroy, CanComponentDeactivate { - - private ngUnsubscribe: Subject = new Subject(); - - resolver$: Observable; - resolver: MetadataResolver; - providerName$: Observable; - changes$: Observable; - changes: MetadataResolver; - latest: MetadataResolver; - - wizardIndex$: Observable; - wizardIndex: number; - currentPage$: Observable; - wizard$: Observable; - wizard: EditorFlowDefinition[]; - saved$: Observable; - saving: boolean; - - constructor( - private store: Store, - private route: ActivatedRoute, - private router: Router, - private statusEmitter: ProviderStatusEmitter, - private valueEmitter: ProviderValueEmitter, - private modalService: NgbModal - ) { - this.resolver$ = this.store.select(fromCollections.getSelectedDraft); - this.providerName$ = this.resolver$.pipe( - map(p => p.serviceProviderName) - ); - this.changes$ = this.store.select(fromEditor.getEditorChanges); - - this.wizardIndex$ = this.route.params.pipe(map(params => Number(params.index))); - this.currentPage$ = this.wizardIndex$.pipe(map(index => WizardDef.find(r => r.index === index))); - this.wizard = WizardDef; - - this.wizardIndex$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(i => this.wizardIndex = i); - - this.saved$ = this.store.select(fromEditor.getEditorIsSaved); - - this.store.select(fromEditor.getEditorIsSaving).pipe(takeUntil(this.ngUnsubscribe)).subscribe(saving => this.saving = saving); - } - - save(): void { - this.store.dispatch(new AddResolverRequest(this.latest)); - } - - next(index: number): void { - this.go(index); - } - - previous(index: number): void { - this.go(index); - } - - go(index: number): void { - this.store.dispatch(new draftActions.UpdateDraftRequest(this.latest)); - this.router.navigate(['../', index], { relativeTo: this.route }); - } - - ngOnInit(): void { - this.subscribe(); - } - - subscribe(): void { - this.resolver$.pipe( - takeUntil(this.ngUnsubscribe), - skipWhile(() => this.saving) - ).subscribe(resolver => this.resolver = resolver); - this.changes$.pipe( - takeUntil(this.ngUnsubscribe), - skipWhile(() => this.saving) - ).subscribe(changes => this.changes = changes); - this.changes$.pipe( - takeUntil(this.ngUnsubscribe), - skipWhile(() => this.saving), - combineLatest(this.resolver$, (changes, base) => ({ ...base, ...changes })) - ).subscribe(latest => this.latest = latest); - - this.valueEmitter - .changeEmitted$ - .pipe( - takeUntil(this.ngUnsubscribe), - skipWhile(() => this.saving) - ).subscribe(changes => this.store.dispatch(new UpdateChanges(changes))); - this.statusEmitter - .changeEmitted$ - .pipe( - takeUntil(this.ngUnsubscribe), - skipWhile(() => this.saving), - combineLatest(this.currentPage$, (status: string, page: any) => { - return { [page.path]: status }; - }) - ).subscribe(status => this.store.dispatch(new UpdateStatus(status))); - } - - ngOnDestroy(): void { - this.ngUnsubscribe.next(); - this.ngUnsubscribe.complete(); - } - - canDeactivate( - currentRoute: ActivatedRouteSnapshot, - currentState: RouterStateSnapshot, - nextState: RouterStateSnapshot - ): Observable { - if (nextState.url.match('wizard')) { return of(true); } - if (Object.keys(this.changes).length > 0) { - let modal = this.modalService.open(UnsavedDialogComponent); - modal.componentInstance.action = new SaveChanges(this.latest); - modal.result.then( - () => this.router.navigate([nextState.url]), - () => console.warn('denied') - ); - } - return this.store.select(fromEditor.getEditorIsSaved); - } -} /* istanbul ignore next */ diff --git a/ui/src/app/metadata/resolver/effect/editor.effect.ts b/ui/src/app/metadata/resolver/effect/entity.effect.ts similarity index 87% rename from ui/src/app/metadata/resolver/effect/editor.effect.ts rename to ui/src/app/metadata/resolver/effect/entity.effect.ts index d194d05a7..1c487dec2 100644 --- a/ui/src/app/metadata/resolver/effect/editor.effect.ts +++ b/ui/src/app/metadata/resolver/effect/entity.effect.ts @@ -6,7 +6,11 @@ import { switchMap, map, withLatestFrom, tap } from 'rxjs/operators'; import * as fromResolver from '../reducer'; import * as fromRoot from '../../../app.reducer'; -import * as editor from '../action/editor.action'; +import { + ResolverEntityActionTypes, + Clear, + Cancel +} from '../action/entity.action'; import * as provider from '../action/collection.action'; import { ShowContentionAction } from '../../../contention/action/contention.action'; @@ -18,11 +22,11 @@ import { Store } from '@ngrx/store'; import { ContentionService } from '../../../contention/service/contention.service'; @Injectable() -export class EditorEffects { +export class EntityEffects { @Effect() cancelChanges$ = this.actions$.pipe( - ofType(editor.CANCEL_CHANGES), + ofType(ResolverEntityActionTypes.CANCEL), map(() => new provider.LoadResolverRequest()), tap(() => this.router.navigate(['metadata'])) ); @@ -31,7 +35,7 @@ export class EditorEffects { updateResolverSuccessRedirect$ = this.actions$.pipe( ofType(ResolverCollectionActionTypes.UPDATE_RESOLVER_SUCCESS), map(action => action.payload), - map(p => new editor.ResetChanges()) + map(p => new Clear()) ); @Effect() @@ -43,7 +47,7 @@ export class EditorEffects { return this.service.find(filter.id).pipe( map(data => new ShowContentionAction(this.contentionService.getContention(current, filter, data, { resolve: (obj) => this.store.dispatch(new provider.UpdateResolverRequest(obj)), - reject: (obj) => this.store.dispatch(new editor.CancelChanges()) + reject: (obj) => this.store.dispatch(new Cancel()) }))) ); }) diff --git a/ui/src/app/metadata/resolver/effect/wizard.effect.ts b/ui/src/app/metadata/resolver/effect/wizard.effect.ts index 3296d1f25..3916e349a 100644 --- a/ui/src/app/metadata/resolver/effect/wizard.effect.ts +++ b/ui/src/app/metadata/resolver/effect/wizard.effect.ts @@ -3,7 +3,11 @@ import { Effect, Actions, ofType } from '@ngrx/effects'; import { map, switchMap } from 'rxjs/operators'; -import * as editorActions from '../action/editor.action'; +import { + UpdateChanges, + Clear, + ResolverEntityActionTypes +} from '../action/entity.action'; import * as provider from '../action/collection.action'; import { ResolverCollectionActionTypes } from '../action/collection.action'; import { EntityDraftService } from '../../domain/service/draft.service'; @@ -13,7 +17,7 @@ export class WizardEffects { @Effect({ dispatch: false }) updateResolver$ = this.actions$.pipe( - ofType(editorActions.SAVE_CHANGES), + ofType(ResolverEntityActionTypes.UPDATE_CHANGES), map(action => action.payload), switchMap(provider => this.draftService.update(provider)) ); @@ -22,7 +26,7 @@ export class WizardEffects { addResolverSuccessDiscard$ = this.actions$.pipe( ofType(ResolverCollectionActionTypes.ADD_RESOLVER_SUCCESS), map(action => action.payload), - map(provider => new editorActions.ResetChanges()) + map(provider => new Clear()) ); constructor( diff --git a/ui/src/app/metadata/resolver/reducer/editor.reducer.ts b/ui/src/app/metadata/resolver/reducer/editor.reducer.ts deleted file mode 100644 index 290ce15fd..000000000 --- a/ui/src/app/metadata/resolver/reducer/editor.reducer.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { MetadataResolver } from '../../domain/model'; -import * as editor from '../action/editor.action'; -import { ResolverCollectionActionTypes, ResolverCollectionActionsUnion } from '../action/collection.action'; - -export interface EditorState { - saving: boolean; - formStatus: { [key: string]: string }; - changes: MetadataResolver; -} - -export const initialState: EditorState = { - saving: false, - formStatus: {}, - changes: {} as MetadataResolver -}; - -export function reducer(state = initialState, action: editor.Actions | ResolverCollectionActionsUnion): EditorState { - switch (action.type) { - case ResolverCollectionActionTypes.ADD_RESOLVER: { - return { - ...state, - saving: true, - }; - } - case ResolverCollectionActionTypes.ADD_RESOLVER_FAIL: { - return { - ...state, - saving: false - }; - } - case ResolverCollectionActionTypes.ADD_RESOLVER_SUCCESS: { - return { - ...state, - changes: { ...initialState.changes }, - saving: false - }; - } - case editor.UPDATE_STATUS: { - return Object.assign({}, state, { - formStatus: { ...state.formStatus, ...action.payload } - }); - } - case editor.UPDATE_CHANGES: { - return Object.assign({}, state, { - changes: { ...state.changes, ...action.payload } - }); - } - case editor.CANCEL_CHANGES: - case editor.SAVE_CHANGES: - case editor.RESET_CHANGES: - return Object.assign({}, state, { - changes: { ...initialState.changes } - }); - default: { - return state; - } - } -} - -export const isEditorValid = (state: EditorState) => - !Object.keys(state.formStatus).some(key => state.formStatus[key] === ('INVALID')); -export const isEditorSaved = (state: EditorState) => !Object.keys(state.changes).length; -export const getChanges = (state: EditorState) => state.changes; -export const isEditorSaving = (state: EditorState) => state.saving; -export const getFormStatus = (state: EditorState) => state.formStatus; -export const getInvalidForms = (state: EditorState) => - Object.keys(state.formStatus).filter(key => state.formStatus[key] === 'INVALID'); diff --git a/ui/src/app/metadata/resolver/reducer/editor.reducer.spec.ts b/ui/src/app/metadata/resolver/reducer/entity.reducer.spec.ts similarity index 52% rename from ui/src/app/metadata/resolver/reducer/editor.reducer.spec.ts rename to ui/src/app/metadata/resolver/reducer/entity.reducer.spec.ts index 78e272848..deaa49327 100644 --- a/ui/src/app/metadata/resolver/reducer/editor.reducer.spec.ts +++ b/ui/src/app/metadata/resolver/reducer/entity.reducer.spec.ts @@ -1,13 +1,16 @@ -import { reducer } from './editor.reducer'; -import * as fromEditor from './editor.reducer'; -import * as actions from '../action/editor.action'; -import * as collectionActions from '../action/collection.action'; +import { reducer } from './entity.reducer'; +import * as fromEntity from './entity.reducer'; +import { + UpdateChanges, + UpdateStatus, + Clear +} from '../action/entity.action'; import { MetadataResolver } from '../../domain/model'; describe('Editor Reducer', () => { - const initialState: fromEditor.EditorState = { + const initialState: fromEntity.EditorState = { saving: false, - formStatus: {}, + status: {}, changes: {} as MetadataResolver }; @@ -23,43 +26,10 @@ describe('Editor Reducer', () => { }); }); - describe('Editor Add Resolver', () => { - it('should update the status when a provider is saved', () => { - const action = new collectionActions.AddResolverRequest(changes); - const result = reducer(initialState, action); - expect(result).toEqual( - Object.assign({}, initialState, { - saving: true - }) - ); - }); - - it('should update the status on success', () => { - const action = new collectionActions.AddResolverSuccess(changes); - const result = reducer({...initialState, changes: {...changes, organization: {name: 'foo'}}}, action); - expect(result).toEqual( - Object.assign({}, initialState, { - saving: false, - changes: initialState.changes - }) - ); - }); - - it('should update the status on success', () => { - const action = new collectionActions.AddResolverFail(changes); - const result = reducer(initialState, action); - expect(result).toEqual( - Object.assign({}, initialState, { - saving: false - }) - ); - }); - }); - - describe('Editor Update Status', () => { + describe('Entity Update Status', () => { it('should update the status of the provided form', () => { const status = { organization: 'VALID' }; - const action = new actions.UpdateStatus(status); + const action = new UpdateStatus(status); const result = reducer(initialState, action); expect(result).toEqual( Object.assign({}, initialState, { @@ -71,7 +41,7 @@ describe('Editor Reducer', () => { describe('Editor Update Changes', () => { it('should add changes of the provided form', () => { - const action = new actions.UpdateChanges(changes); + const action = new UpdateChanges(changes); const result = reducer(initialState, action); expect(result).toEqual( Object.assign({}, initialState, { @@ -81,9 +51,9 @@ describe('Editor Reducer', () => { }); }); - describe('Editor Reset', () => { + describe('Editor Clear', () => { it('should remove changes', () => { - const action = new actions.ResetChanges(); + const action = new Clear(); const result = reducer(initialState, action); expect(result).toEqual( Object.assign({}, initialState, { @@ -95,19 +65,7 @@ describe('Editor Reducer', () => { describe('Editor Save', () => { it('should remove changes', () => { - const action = new actions.SaveChanges(changes); - const result = reducer(initialState, action); - expect(result).toEqual( - Object.assign({}, initialState, { - changes: initialState.changes - }) - ); - }); - }); - - describe('Editor Cancel', () => { - it('should remove changes', () => { - const action = new actions.CancelChanges(); + const action = new UpdateChanges(changes); const result = reducer(initialState, action); expect(result).toEqual( Object.assign({}, initialState, { @@ -119,10 +77,10 @@ describe('Editor Reducer', () => { describe('Selectors', () => { it('should aggregate the status', () => { - expect(fromEditor.isEditorValid({ + expect(fromEntity.isEditorValid({ saving: false, changes: {} as MetadataResolver, - formStatus: { + status: { organization: 'INVALID', foo: 'VALID' } @@ -130,38 +88,38 @@ describe('Editor Reducer', () => { }); it('should calculate a saved status based on changes', () => { - expect(fromEditor.isEditorSaved({ + expect(fromEntity.isEditorSaving({ saving: false, changes: {} as MetadataResolver, - formStatus: {} + status: {} })).toBe(true); - expect(fromEditor.isEditorSaved({ + expect(fromEntity.isEditorSaving({ saving: false, changes: {organization: {}, entityId: 'bar'} as MetadataResolver, - formStatus: {} + status: {} })).toBe(false); }); it('should return current changes', () => { - expect(fromEditor.getChanges({ + expect(fromEntity.getChanges({ saving: false, changes: {} as MetadataResolver, - formStatus: {} + status: {} })).toEqual({} as MetadataResolver); }); it('should return `saving` status', () => { - expect(fromEditor.isEditorSaving({ + expect(fromEntity.isEditorSaving({ saving: false, changes: {} as MetadataResolver, - formStatus: {} + status: {} })).toBe(false); - expect(fromEditor.isEditorSaving({ + expect(fromEntity.isEditorSaving({ saving: true, changes: {} as MetadataResolver, - formStatus: {} + status: {} })).toBe(true); }); }); diff --git a/ui/src/app/metadata/resolver/reducer/entity.reducer.ts b/ui/src/app/metadata/resolver/reducer/entity.reducer.ts new file mode 100644 index 000000000..819eacd11 --- /dev/null +++ b/ui/src/app/metadata/resolver/reducer/entity.reducer.ts @@ -0,0 +1,56 @@ +import { MetadataResolver } from '../../domain/model'; +import * as entity from '../action/entity.action'; +import { ResolverEntityActionTypes, ResolverEntityActionUnion } from '../action/entity.action'; + +export interface EntityState { + saving: boolean; + status: { [key: string]: string }; + changes: MetadataResolver; +} + +export const initialState: EntityState = { + saving: false, + status: {}, + changes: {} as MetadataResolver +}; + +export function reducer(state = initialState, action: ResolverEntityActionUnion): EntityState { + switch (action.type) { + case ResolverEntityActionTypes.UPDATE_CHANGES: { + return { + ...state, + changes: { ...state.changes, ...action.payload } + }; + } + case ResolverEntityActionTypes.UPDATE_STATUS: { + return { + ...state, + status: { ...state.status, ...action.payload } + }; + } + case ResolverEntityActionTypes.UPDATE_SAVING: { + return { + ...state, + saving: action.payload + }; + } + case ResolverEntityActionTypes.CLEAR: + return { + ...state, + changes: { ...initialState.changes } + }; + default: { + return state; + } + } +} + +export const isEntitySaved = (state: EntityState) => !Object.keys(state.changes).length; +export const getChanges = (state: EntityState) => state.changes; +export const isEntitySaving = (state: EntityState) => state.saving; +export const getFormStatus = (state: EntityState) => state.status; + +export const isEntityValid = (state: EntityState) => + !Object.keys(state.status).some(key => state.status[key] === ('INVALID')); +export const getInvalidForms = (state: EntityState) => + Object.keys(state.status).filter(key => state.status[key] === 'INVALID'); diff --git a/ui/src/app/metadata/resolver/reducer/index.ts b/ui/src/app/metadata/resolver/reducer/index.ts index ca47726ba..bf38715d9 100644 --- a/ui/src/app/metadata/resolver/reducer/index.ts +++ b/ui/src/app/metadata/resolver/reducer/index.ts @@ -1,6 +1,6 @@ import { createSelector, createFeatureSelector } from '@ngrx/store'; import * as fromRoot from '../../../app.reducer'; -import * as fromEditor from './editor.reducer'; +import * as fromEntity from './entity.reducer'; import * as fromSearch from './search.reducer'; import * as fromCopy from './copy.reducer'; import * as fromDraft from './draft.reducer'; @@ -9,7 +9,7 @@ import * as fromCollection from './collection.reducer'; import { combineAllFn, getEntityIdsFn, getInCollectionFn, doesExistFn } from '../../domain/domain.util'; export interface ResolverState { - editor: fromEditor.EditorState; + entity: fromEntity.EntityState; copy: fromCopy.CopyState; search: fromSearch.SearchState; draft: fromDraft.DraftState; @@ -18,10 +18,10 @@ export interface ResolverState { export const reducers = { copy: fromCopy.reducer, - search: fromSearch.reducer, - editor: fromEditor.reducer, + entity: fromEntity.reducer, collection: fromCollection.reducer, - draft: fromDraft.reducer + draft: fromDraft.reducer, + search: fromSearch.reducer }; export interface State extends fromRoot.State { @@ -32,26 +32,26 @@ export const getResolverState = createFeatureSelector('resolver') export const getCollectionStateFn = (state: ResolverState) => state.collection; export const getDraftStateFn = (state: ResolverState) => state.draft; -export const getEditorStateFn = (state: ResolverState) => state.editor; +export const getEntityStateFn = (state: ResolverState) => state.entity; export const getCopyStateFn = (state: ResolverState) => state.copy; export const getSearchStateFn = (state: ResolverState) => state.search; export const getCollectionState = createSelector(getResolverState, getCollectionStateFn); export const getDraftState = createSelector(getResolverState, getDraftStateFn); -export const getEditorState = createSelector(getResolverState, getEditorStateFn); +export const getEntityState = createSelector(getResolverState, getEntityStateFn); export const getCopyState = createSelector(getResolverState, getCopyStateFn); export const getSearchState = createSelector(getResolverState, getSearchStateFn); /* -Editor State +Entity State */ -export const getEditorIsValid = createSelector(getEditorState, fromEditor.isEditorValid); -export const getEditorIsSaved = createSelector(getEditorState, fromEditor.isEditorSaved); -export const getEditorChanges = createSelector(getEditorState, fromEditor.getChanges); -export const getEditorIsSaving = createSelector(getEditorState, fromEditor.isEditorSaving); -export const getFormStatus = createSelector(getEditorState, fromEditor.getFormStatus); -export const getInvalidEditorForms = createSelector(getEditorState, fromEditor.getInvalidForms); +export const getEntityIsValid = createSelector(getEntityState, fromEntity.isEntityValid); +export const getEntityIsSaved = createSelector(getEntityState, fromEntity.isEntitySaved); +export const getEntityChanges = createSelector(getEntityState, fromEntity.getChanges); +export const getEntityIsSaving = createSelector(getEntityState, fromEntity.isEntitySaving); +export const getFormStatus = createSelector(getEntityState, fromEntity.getFormStatus); +export const getInvalidEntityForms = createSelector(getEntityState, fromEntity.getInvalidForms); /* Copy State diff --git a/ui/src/app/metadata/resolver/resolver.module.ts b/ui/src/app/metadata/resolver/resolver.module.ts index f086c6f44..356fae5b1 100644 --- a/ui/src/app/metadata/resolver/resolver.module.ts +++ b/ui/src/app/metadata/resolver/resolver.module.ts @@ -23,14 +23,16 @@ import { CopyResolverEffects } from './effect/copy.effect'; import { DomainModule } from '../domain/domain.module'; import { DraftComponent } from './container/draft.component'; import { EditorComponent } from './container/editor.component'; -import { WizardComponent } from './container/wizard.component'; +import { ResolverWizardComponent } from './container/resolver-wizard.component'; import { WizardNavComponent } from './component/wizard-nav.component'; import { ResolverCollectionEffects } from './effect/collection.effects'; import { DraftCollectionEffects } from './effect/draft-collection.effects'; import { WizardEffects } from './effect/wizard.effect'; -import { EditorEffects } from './effect/editor.effect'; import { UnsavedDialogComponent } from './component/unsaved-dialog.component'; import { I18nModule } from '../../i18n/i18n.module'; +import { MetadataSourceWizard } from '../domain/model/wizards/metadata-source-wizard'; +import { METADATA_SOURCE_WIZARD } from './wizard-definition'; +import { EntityEffects } from './effect/entity.effect'; @NgModule({ declarations: [ @@ -42,7 +44,7 @@ import { I18nModule } from '../../i18n/i18n.module'; ResolverComponent, DraftComponent, EditorComponent, - WizardComponent, + ResolverWizardComponent, WizardNavComponent, UnsavedDialogComponent ], @@ -88,8 +90,11 @@ export class ResolverModule { ResolverCollectionEffects, DraftCollectionEffects, WizardEffects, - EditorEffects + EntityEffects ]) ], + providers: [ + { provide: METADATA_SOURCE_WIZARD, useClass: MetadataSourceWizard } + ] }) export class RootResolverModule { } diff --git a/ui/src/app/metadata/resolver/resolver.routing.ts b/ui/src/app/metadata/resolver/resolver.routing.ts index 9aa4ab730..3e8cc3b47 100644 --- a/ui/src/app/metadata/resolver/resolver.routing.ts +++ b/ui/src/app/metadata/resolver/resolver.routing.ts @@ -3,7 +3,7 @@ import { Routes } from '@angular/router'; import { ResolverComponent } from './container/resolver.component'; import { EditorComponent } from './container/editor.component'; import { DraftComponent } from './container/draft.component'; -import { WizardComponent } from './container/wizard.component'; +import { ResolverWizardComponent } from './container/resolver-wizard.component'; import { BlankResolverComponent } from './container/blank-resolver.component'; import { NewResolverComponent } from './container/new-resolver.component'; @@ -66,7 +66,7 @@ export const ResolverRoutes: Routes = [ { path: 'wizard', redirectTo: 'wizard/2' }, { path: 'wizard/:index', - component: WizardComponent, + component: ResolverWizardComponent, canDeactivate: [CanDeactivateGuard] } ] diff --git a/ui/src/app/metadata/resolver/wizard-definition.ts b/ui/src/app/metadata/resolver/wizard-definition.ts new file mode 100644 index 000000000..74e15df28 --- /dev/null +++ b/ui/src/app/metadata/resolver/wizard-definition.ts @@ -0,0 +1,5 @@ +import { InjectionToken } from '@angular/core'; +import { Wizard } from '../../wizard/model'; +import { MetadataResolver } from '../domain/model'; + +export const METADATA_SOURCE_WIZARD = new InjectionToken>('METADATA_SOURCE_WIZARD'); diff --git a/ui/src/app/wizard/action/wizard.action.ts b/ui/src/app/wizard/action/wizard.action.ts index ecb507cb4..83a5f08a5 100644 --- a/ui/src/app/wizard/action/wizard.action.ts +++ b/ui/src/app/wizard/action/wizard.action.ts @@ -12,7 +12,14 @@ export enum WizardActionTypes { NEXT = '[Wizard] Next Page', PREVIOUS = '[Wizard] Previous Page', - CLEAR = '[Wizard] Clear' + CLEAR = '[Wizard] Clear', + + LOAD_SCHEMA_REQUEST = '[Wizard] Load Schema Request', + LOAD_SCHEMA_SUCCESS = '[Wizard] Load Schema Success', + LOAD_SCHEMA_FAIL = '[Wizard] Load Schema Fail', + + LOCK = '[Wizard] Lock', + UNLOCK = '[Wizard] Unlock' } export class SetIndex implements Action { @@ -61,6 +68,32 @@ export class ClearWizard implements Action { readonly type = WizardActionTypes.CLEAR; } +export class LoadSchemaRequest implements Action { + readonly type = WizardActionTypes.LOAD_SCHEMA_REQUEST; + + constructor(public payload: string) { } +} + +export class LoadSchemaSuccess implements Action { + readonly type = WizardActionTypes.LOAD_SCHEMA_SUCCESS; + + constructor(public payload: any) { } +} + +export class LoadSchemaFail implements Action { + readonly type = WizardActionTypes.LOAD_SCHEMA_FAIL; + + constructor(public payload: Error) { } +} + +export class LockEditor implements Action { + readonly type = WizardActionTypes.LOCK; +} + +export class UnlockEditor implements Action { + readonly type = WizardActionTypes.UNLOCK; +} + export type WizardActionUnion = | SetIndex | SetDefinition @@ -69,4 +102,9 @@ export type WizardActionUnion = | Next | Previous | ClearWizard - | AddSchema; + | AddSchema + | LoadSchemaRequest + | LoadSchemaSuccess + | LoadSchemaFail + | LockEditor + | UnlockEditor; diff --git a/ui/src/app/wizard/model/form-definition.ts b/ui/src/app/wizard/model/form-definition.ts index 9cd42184b..f14fd2f23 100644 --- a/ui/src/app/wizard/model/form-definition.ts +++ b/ui/src/app/wizard/model/form-definition.ts @@ -2,9 +2,7 @@ export interface FormDefinition { label: string; type: string; schema?: string; - translate: { - parser(changes: Partial, schema?: any), - formatter(changes: Partial, schema?: any) - }; + parser(changes: Partial, schema?: any); + formatter(changes: Partial, schema?: any); getValidators?(...args: any[]): { [key: string]: any }; } diff --git a/ui/src/app/wizard/model/wizard.ts b/ui/src/app/wizard/model/wizard.ts index 71389c7c0..b07e98da4 100644 --- a/ui/src/app/wizard/model/wizard.ts +++ b/ui/src/app/wizard/model/wizard.ts @@ -11,6 +11,13 @@ export interface WizardStep { schema?: string; index: number; locked?: boolean; + fields?: (string | WizardFieldset)[]; + summary?: boolean; +} + +export interface WizardFieldset { + type: string; + fields: string[]; } export interface WizardValue { diff --git a/ui/src/app/wizard/reducer/index.ts b/ui/src/app/wizard/reducer/index.ts index a1a1e1c9b..e2c15147b 100644 --- a/ui/src/app/wizard/reducer/index.ts +++ b/ui/src/app/wizard/reducer/index.ts @@ -15,6 +15,28 @@ export interface State extends fromRoot.State { 'wizard': WizardState; } +export function getSchemaParseFn(schema, locked): any { + if (!schema) { + return null; + } + return { + ...schema, + properties: Object.keys(schema.properties).reduce((prev, current) => { + return { + ...prev, + [current]: { + ...schema.properties[current], + readOnly: locked, + ...(schema.properties[current].hasOwnProperty('properties') ? + getSchemaParseFn(schema.properties[current], locked) : + {} + ) + } + }; + }, {}) + }; +} + export const getWizardState = createFeatureSelector('wizard'); export const getWizardStateFn = (state: WizardState) => state.wizard; export const getState = createSelector(getWizardState, getWizardStateFn); @@ -67,3 +89,10 @@ export const getLast = createSelector(getWizardIndex, getWizardDefinition, getLa export const getModel = createSelector(getCurrent, getModelFn); export const getRoutes = createSelector(getWizardDefinition, d => d ? d.steps.map(step => ({ path: step.id, label: step.label })) : [] ); + +export const getLockedStatus = createSelector(getState, fromWizard.getLocked); +export const getSchemaLockedFn = (step, locked) => step ? step.locked ? locked : false : false; +export const getLocked = createSelector(getCurrent, getLockedStatus, getSchemaLockedFn); + +export const getSchemaObject = createSelector(getState, fromWizard.getSchema); +export const getParsedSchema = createSelector(getSchemaObject, getLocked, getSchemaParseFn); diff --git a/ui/src/app/wizard/reducer/wizard.reducer.ts b/ui/src/app/wizard/reducer/wizard.reducer.ts index 9ecae96ce..5b9e390b5 100644 --- a/ui/src/app/wizard/reducer/wizard.reducer.ts +++ b/ui/src/app/wizard/reducer/wizard.reducer.ts @@ -6,17 +6,63 @@ export interface State { disabled: boolean; definition: Wizard; schemaCollection: { [id: string]: any }; + + schemaPath: string; + loading: boolean; + schema: any; + locked: boolean; } export const initialState: State = { index: null, disabled: false, definition: null, - schemaCollection: {} + schemaCollection: {}, + + schemaPath: null, + loading: false, + schema: null, + locked: false }; export function reducer(state = initialState, action: WizardActionUnion): State { switch (action.type) { + case WizardActionTypes.LOAD_SCHEMA_REQUEST: { + return { + ...state, + loading: true, + schemaPath: action.payload + }; + } + case WizardActionTypes.LOAD_SCHEMA_SUCCESS: { + return { + ...state, + loading: false, + schema: action.payload + }; + } + case WizardActionTypes.LOAD_SCHEMA_FAIL: { + return { + ...state, + loading: false, + schema: initialState.schema + }; + } + + case WizardActionTypes.LOCK: { + return { + ...state, + locked: true + }; + } + + case WizardActionTypes.UNLOCK: { + return { + ...state, + locked: false + }; + } + case WizardActionTypes.ADD_SCHEMA: { return { ...state, @@ -64,6 +110,9 @@ export function reducer(state = initialState, action: WizardActionUnion): State } } +export const getSchema = (state: State) => state.schema; +export const getLocked = (state: State) => state.locked; + export const getIndex = (state: State) => state.index; export const getDisabled = (state: State) => state.disabled; export const getDefinition = (state: State) => state.definition; diff --git a/ui/src/assets/schema/source/metadata-source.json b/ui/src/assets/schema/source/metadata-source.json new file mode 100644 index 000000000..5766e298f --- /dev/null +++ b/ui/src/assets/schema/source/metadata-source.json @@ -0,0 +1,442 @@ +{ + "type": "object", + "properties": { + "entityId": { + "title": "label.entity-id", + "description": "tooltip.entity-id", + "type": "string" + }, + "serviceProviderName": { + "title": "label.service-provider-name", + "description": "tooltip.service-provider-name", + "type": "string" + }, + "serviceEnabled": { + "title": "label.enable-this-service-opon-saving", + "description": "tooltip.enable-this-service-upon-saving", + "type": "boolean" + }, + "organization": { + "type": "object", + "properties": { + "name": { + "title": "label.organization-name", + "description": "tooltip.organization-name", + "type": "string" + }, + "displayName": { + "title": "label.organization-display-name", + "description": "tooltip.organization-display-name", + "type": "string" + }, + "url": { + "title": "label.organization-display-name", + "description": "tooltip.organization-display-name", + "type": "string" + } + }, + "dependencies": { + "name": [ + "displayName", + "url" + ], + "displayName": [ + "name", + "url" + ], + "url": [ + "name", + "displayName" + ] + } + }, + "contacts": { + "title": "label.contact-information", + "description": "tooltip.contact-information", + "type": "array", + "items": { + "$ref": "#/definitions/Contact" + } + }, + "mdui": { + "type": "object", + "properties": { + "displayName": { + "title": "label.display-name", + "description": "tooltip.mdui-display-name", + "type": "string" + }, + "informationUrl": { + "title": "label.information-url", + "description": "tooltip.mdui-information-url", + "type": "string" + }, + "privacyStatementUrl": { + "title": "label.privacy-statement-url", + "description": "tooltip.mdui-privacy-statement-url", + "type": "string" + }, + "description": { + "title": "label.description", + "description": "tooltip.mdui-description", + "type": "string" + }, + "logoUrl": { + "title": "label.logo-url", + "description": "tooltip.mdui-logo-url", + "type": "string" + }, + "logoHeight": { + "title": "label.logo-height", + "description": "tooltip.mdui-logo-height", + "min": 0, + "type": "integer" + }, + "logoWidth": { + "title": "label.logo-width", + "description": "tooltip.mdui-logo-width", + "min": 0, + "type": "integer" + } + } + }, + "securityInfo": { + "type": "object", + "properties": { + "x509CertificateAvailable": { + "title": "label.is-there-a-x509-certificate", + "description": "tooltip.is-there-a-x509-certificate", + "type": "boolean", + "default": false + }, + "authenticationRequestsSigned": { + "title": "label.authentication-requests-signed", + "description": "tooltip.authentication-requests-signed", + "type": "boolean", + "default": false + }, + "wantAssertionsSigned": { + "title": "label.want-assertions-signed", + "description": "tooltip.want-assertions-signed", + "type": "boolean", + "default": false + }, + "x509Certificates": { + "title": "label.x509-certificates", + "type": "array", + "items": { + "$ref": "#/definitions/Certificate" + } + } + } + }, + "assertionConsumerServices": { + "title": "label.assertion-consumer-service-endpoints", + "description": "", + "type": "array", + "items": { + "$ref": "#/definitions/AssertionConsumerService" + } + }, + "serviceProviderSsoDescriptor": { + "type": "object", + "properties": { + "protocolSupportEnum": { + "title": "label.protocol-support-enumeration", + "description": "tooltip.protocol-support-enumeration", + "type": "string", + "placeholder": "label.select-protocol", + "oneOf": [ + { + "enum": [ + "SAML 2" + ], + "description": "SAML 2" + }, + { + "enum": [ + "SAML 1.1" + ], + "description": "SAML 1.1" + } + ] + } + }, + "nameIdFormats": { + "$ref": "#/definitions/NameIdFormatList" + } + }, + "logoutEndpoints": { + "title": "label.logout-endpoints", + "description": "tooltip.logout-endpoints", + "type": "array", + "items": { + "$ref": "#/definitions/LogoutEndpoint" + } + }, + "relyingPartyOverrides": { + "type": "object", + "properties": { + "signAssertion": { + "title": "label.sign-the-assertion", + "description": "tooltip.sign-assertion", + "type": "boolean", + "default": false + }, + "dontSignResponse": { + "title": "label.dont-sign-the-response", + "description": "tooltip.dont-sign-response", + "type": "boolean", + "default": false + }, + "turnOffEncryption": { + "title": "label.turn-off-encryption-of-response", + "description": "tooltip.turn-off-encryption", + "type": "boolean", + "default": false + }, + "useSha": { + "title": "label.use-sha1-signing-algorithm", + "description": "tooltip.usa-sha-algorithm", + "type": "boolean", + "default": false + }, + "ignoreAuthenticationMethod": { + "title": "label.ignore-any-sp-requested-authentication-method", + "description": "tooltip.ignore-auth-method", + "type": "boolean", + "default": false + }, + "forceAuthn": { + "title": "label.force-authn", + "description": "tooltip.force-authn", + "type": "boolean", + "default": false + }, + "omitNotBefore": { + "title": "label.omit-not-before-condition", + "type": "boolean", + "description": "tooltip.omit-not-before-condition", + "default": false + }, + "responderId": { + "title": "label.responder-id", + "description": "tooltip.responder-id", + "type": "string" + }, + "nameIdFormats": { + "$ref": "#/definitions/NameIdFormatList" + }, + "authenticationMethods": { + "$ref": "#/definitions/AuthenticationMethodList" + } + } + }, + "attributeRelease": { + "type": "array", + "description": "Attribute release table - select the attributes you want to release (default unchecked)", + "widget": { + "id": "checklist", + "dataUrl": "/customAttributes" + }, + "items": { + "type": "string" + } + } + }, + "definitions": { + "Contact": { + "type": "object", + "properties": { + "name": { + "title": "label.contact-name", + "description": "tooltip.contact-name", + "type": "string" + }, + "type": { + "title": "label.contact-type", + "description": "tooltip.contact-type", + "type": "string", + "oneOf": [ + { + "enum": [ + "support" + ], + "description": "value.support" + }, + { + "enum": [ + "technical" + ], + "description": "value.technical" + }, + { + "enum": [ + "administrative" + ], + "description": "value.administrative" + }, + { + "enum": [ + "other" + ], + "description": "value.other" + } + ] + }, + "emailAddress": { + "title": "label.contact-email-address", + "description": "tooltip.contact-email", + "type": "string", + "pattern": "^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$" + } + } + }, + "Certificate": { + "name": { + "title": "label.certificate-name-display-only", + "description": "tooltip.certificate-name", + "type": "string" + }, + "type": { + "title": "label.type", + "description": "tooltip.certificate-type", + "type": "string", + "oneOf": [ + { + "enum": [ + "signing" + ], + "description": "value.signing" + }, + { + "enum": [ + "encryption" + ], + "description": "value.encryption" + }, + { + "enum": [ + "both" + ], + "description": "value.both" + } + ], + "default": "both" + }, + "value": { + "title": "label.certificate", + "description": "tooltip.certificate", + "type": "string" + } + }, + "AssertionConsumerService": { + "type": "object", + "properties": { + "locationUrl": { + "title": "label.assertion-consumer-services-location", + "description": "tooltip.assertion-consumer-service-location", + "type": "string", + "widget": { + "id": "string", + "help": "message.valid-url" + } + }, + "binding": { + "title": "label.assertion-consumer-service-location-binding", + "description": "tooltip.assertion-consumer-service-location-binding", + "type": "string", + "oneOf": [ + { + "enum": [ + "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + ], + "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + }, + { + "enum": [ + "urn:oasis:names:tc:SAML:1.0:profiles:browser-post" + ], + "description": "urn:oasis:names:tc:SAML:1.0:profiles:browser-post" + } + ] + }, + "makeDefault": { + "title": "label.mark-as-default", + "description": "tooltip.mark-as-default", + "type": "boolean" + } + } + }, + "NameIdFormatList": { + "title": "label.nameid-format-to-send", + "placeholder": "label.nameid-format", + "description": "tooltip.nameid-format", + "type": "array", + "uniqueItems": true, + "items": { + "type": "string", + "widget": "datalist", + "data": [ + "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", + "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", + "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", + "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" + ] + }, + "default": null + }, + "AuthenticationMethodList": { + "title": "label.authentication-methods-to-use", + "description": "tooltip.authentication-methods-to-use", + "type": "array", + "placeholder": "label.authentication-method", + "uniqueItems": true, + "items": { + "type": "string", + "title": "label.authentication-method", + "widget": { + "id": "datalist", + "data": [ + "https://refeds.org/profile/mfa", + "urn:oasis:names:tc:SAML:2.0:ac:classes:TimeSyncToken", + "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" + ] + } + }, + "default": null + }, + "LogoutEndpoint": { + "title": "label.new-endpoint", + "description": "tooltip.new-endpoint", + "type": "object", + "properties": { + "url": { + "title": "label.url", + "description": "tooltip.url", + "type": "string" + }, + "bindingType": { + "title": "label.binding-type", + "description": "tooltip.binding-type", + "type": "string", + "oneOf": [ + { + "enum": [ + "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + ], + "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + }, + { + "enum": [ + "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + ], + "description": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + } + ] + + } + } + } + } +} \ No newline at end of file