From 2eb54a05657872a327f58ea1d69496bb3b1a77a7 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 15 Aug 2019 10:07:11 -0700 Subject: [PATCH 01/11] Refactored version display --- .../main/resources/i18n/messages.properties | 1 + .../configuration/action/restore.action.ts | 43 +++--------- .../configuration/action/version.action.ts | 40 +++++++++++ .../filter-configuration-list.component.ts | 6 +- .../component/history-list.component.html | 12 ++-- .../metadata-configuration.component.html | 3 +- .../component/metadata-header.component.html | 5 +- .../component/metadata-header.component.ts | 6 +- .../configuration/configuration.module.ts | 12 +++- .../configuration/configuration.routing.ts | 16 ++++- .../container/configuration.component.ts | 33 ++------- .../metadata-comparison.component.ts | 6 +- .../container/metadata-history.component.ts | 37 +++++++--- .../container/metadata-options.component.html | 5 +- .../container/metadata-options.component.ts | 46 +++--------- .../container/restore.component.html | 8 +-- .../container/restore.component.ts | 37 +++------- .../container/version-options.component.html | 43 ++++++++++++ .../container/version-options.component.ts | 70 +++++++++++++++++++ .../container/version.component.html | 1 + .../container/version.component.ts | 42 +++++++++++ .../configuration/effect/restore.effect.ts | 68 +++++++++++------- .../configuration/effect/version.effect.ts | 32 +++++++++ .../metadata/configuration/model/request.ts | 5 ++ .../metadata/configuration/reducer/index.ts | 33 +++++---- ...educer.spec.ts => version.reducer.spec.ts} | 0 ...{restore.reducer.ts => version.reducer.ts} | 14 ++-- .../configuration/service/history.service.ts | 15 +--- 28 files changed, 421 insertions(+), 218 deletions(-) create mode 100644 ui/src/app/metadata/configuration/action/version.action.ts create mode 100644 ui/src/app/metadata/configuration/container/version-options.component.html create mode 100644 ui/src/app/metadata/configuration/container/version-options.component.ts create mode 100644 ui/src/app/metadata/configuration/container/version.component.html create mode 100644 ui/src/app/metadata/configuration/container/version.component.ts create mode 100644 ui/src/app/metadata/configuration/effect/version.effect.ts create mode 100644 ui/src/app/metadata/configuration/model/request.ts rename ui/src/app/metadata/configuration/reducer/{restore.reducer.spec.ts => version.reducer.spec.ts} (100%) rename ui/src/app/metadata/configuration/reducer/{restore.reducer.ts => version.reducer.ts} (65%) diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 182a2491e..ef48de5a2 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -60,6 +60,7 @@ action.restore=Restore value.enabled=Enabled value.disabled=Disabled value.current=Current +value.not-current=Not Current value.none=None value.file=File value.memory=Memory diff --git a/ui/src/app/metadata/configuration/action/restore.action.ts b/ui/src/app/metadata/configuration/action/restore.action.ts index 290367054..7febcd2e8 100644 --- a/ui/src/app/metadata/configuration/action/restore.action.ts +++ b/ui/src/app/metadata/configuration/action/restore.action.ts @@ -1,27 +1,19 @@ import { Action } from '@ngrx/store'; import { Metadata } from '../../domain/domain.type'; +import { VersionRequest } from '../model/request'; export enum RestoreActionTypes { - SELECT_VERSION_SUCCESS = '[Restore Version] Select Version Success', - SELECT_VERSION_ERROR = '[Restore Version] Select Version Error', - SELECT_VERSION_REQUEST = '[Restore Version] Select Version Request', - RESTORE_VERSION_REQUEST = '[Restore Version] Restore Version Request', RESTORE_VERSION_SUCCESS = '[Restore Version] Restore Version Success', RESTORE_VERSION_ERROR = '[Restore Version] Restore Version Error', - CLEAR_VERSION = '[Restore Version] Clear Versions' -} - -export interface VersionRequest { - type: string; - id: string; - version: string; + CLEAR_VERSION = '[Restore Version] Clear Versions', + CANCEL_RESTORE = '[Restore Version] Cancel Restore' } export class RestoreVersionRequest implements Action { readonly type = RestoreActionTypes.RESTORE_VERSION_REQUEST; - constructor(public payload: VersionRequest) { } + constructor() { } } export class RestoreVersionSuccess implements Action { @@ -34,32 +26,13 @@ export class RestoreVersionError implements Action { constructor(public payload: any) { } } -export class SelectVersionRestoreRequest implements Action { - readonly type = RestoreActionTypes.SELECT_VERSION_REQUEST; - - constructor(public payload: VersionRequest) { } -} - -export class SelectVersionRestoreSuccess implements Action { - readonly type = RestoreActionTypes.SELECT_VERSION_SUCCESS; - - constructor(public payload: Metadata) { } -} -export class SelectVersionRestoreError implements Action { - readonly type = RestoreActionTypes.SELECT_VERSION_ERROR; - - constructor(public payload: any) { } -} - -export class ClearVersionRestore implements Action { - readonly type = RestoreActionTypes.CLEAR_VERSION; +export class CancelRestore implements Action { + readonly type = RestoreActionTypes.CANCEL_RESTORE; + constructor() { } } export type RestoreActionsUnion = - | SelectVersionRestoreRequest - | SelectVersionRestoreError - | SelectVersionRestoreSuccess | RestoreVersionRequest | RestoreVersionSuccess | RestoreVersionError - | ClearVersionRestore; + | CancelRestore; diff --git a/ui/src/app/metadata/configuration/action/version.action.ts b/ui/src/app/metadata/configuration/action/version.action.ts new file mode 100644 index 000000000..0379a240b --- /dev/null +++ b/ui/src/app/metadata/configuration/action/version.action.ts @@ -0,0 +1,40 @@ +import { Action } from '@ngrx/store'; +import { Metadata } from '../../domain/domain.type'; +import { VersionRequest } from '../model/request'; + +export enum VersionActionTypes { + SELECT_VERSION_SUCCESS = '[Version] Select Version Success', + SELECT_VERSION_ERROR = '[Version] Select Version Error', + SELECT_VERSION_REQUEST = '[Version] Select Version Request', + + CLEAR_VERSION = '[Version] Clear Versions' +} + +export class SelectVersionRequest implements Action { + readonly type = VersionActionTypes.SELECT_VERSION_REQUEST; + + constructor(public payload: VersionRequest) { } +} + +export class SelectVersionSuccess implements Action { + readonly type = VersionActionTypes.SELECT_VERSION_SUCCESS; + + constructor(public payload: Metadata) { } +} +export class SelectVersionError implements Action { + readonly type = VersionActionTypes.SELECT_VERSION_ERROR; + + constructor(public payload: any) { } +} + +export class ClearVersion implements Action { + readonly type = VersionActionTypes.CLEAR_VERSION; + + constructor() { } +} + +export type VersionActionsUnion = + | SelectVersionRequest + | SelectVersionError + | SelectVersionSuccess + | ClearVersion; diff --git a/ui/src/app/metadata/configuration/component/filter-configuration-list.component.ts b/ui/src/app/metadata/configuration/component/filter-configuration-list.component.ts index 5208a7f4c..17f377480 100644 --- a/ui/src/app/metadata/configuration/component/filter-configuration-list.component.ts +++ b/ui/src/app/metadata/configuration/component/filter-configuration-list.component.ts @@ -1,8 +1,10 @@ -import { Component } from '@angular/core'; +import { Component, Input } from '@angular/core'; import { FilterListComponent } from '../../filter/component/filter-list.component'; @Component({ selector: 'filter-configuration-list', templateUrl: './filter-configuration-list.component.html' }) -export class FilterConfigurationListComponent extends FilterListComponent { } +export class FilterConfigurationListComponent extends FilterListComponent { + @Input() editable = true; +} diff --git a/ui/src/app/metadata/configuration/component/history-list.component.html b/ui/src/app/metadata/configuration/component/history-list.component.html index 168d0cf19..668308311 100644 --- a/ui/src/app/metadata/configuration/component/history-list.component.html +++ b/ui/src/app/metadata/configuration/component/history-list.component.html @@ -21,13 +21,11 @@ - - - {{ version.date | date:DATE_FORMAT }}  - - (Current) - - + + {{ version.date | date:DATE_FORMAT }} (Current) + + + {{ version.date | date:DATE_FORMAT }} {{ version.creator }} diff --git a/ui/src/app/metadata/configuration/component/metadata-configuration.component.html b/ui/src/app/metadata/configuration/component/metadata-configuration.component.html index f5ab2814b..f4826cfe6 100644 --- a/ui/src/app/metadata/configuration/component/metadata-configuration.component.html +++ b/ui/src/app/metadata/configuration/component/metadata-configuration.component.html @@ -1,8 +1,7 @@
-
+

diff --git a/ui/src/app/metadata/configuration/component/metadata-header.component.html b/ui/src/app/metadata/configuration/component/metadata-header.component.html index 64a1a2a2c..fee1cbae3 100644 --- a/ui/src/app/metadata/configuration/component/metadata-header.component.html +++ b/ui/src/app/metadata/configuration/component/metadata-header.component.html @@ -2,16 +2,17 @@
Saved:  - {{ version.date | date:'medium' }} + {{ version.modifiedDate | date:'medium' }}
By:  - {{ version.creator }} + {{ version.createdBy }}

Enabled Disabled   Current + Not Current

\ No newline at end of file diff --git a/ui/src/app/metadata/configuration/component/metadata-header.component.ts b/ui/src/app/metadata/configuration/component/metadata-header.component.ts index 8d0dcc3db..558353304 100644 --- a/ui/src/app/metadata/configuration/component/metadata-header.component.ts +++ b/ui/src/app/metadata/configuration/component/metadata-header.component.ts @@ -1,6 +1,5 @@ import { Component, Input } from '@angular/core'; -import { Metadata, MetadataTypes } from '../../domain/domain.type'; -import { MetadataVersion } from '../model/version'; +import { Metadata } from '../../domain/domain.type'; @Component({ selector: 'metadata-header', @@ -10,8 +9,7 @@ import { MetadataVersion } from '../model/version'; export class MetadataHeaderComponent { @Input() isEnabled: boolean; - @Input() version: MetadataVersion; - @Input() versionNumber: number; + @Input() version: Metadata; @Input() isCurrent: boolean; constructor() {} diff --git a/ui/src/app/metadata/configuration/configuration.module.ts b/ui/src/app/metadata/configuration/configuration.module.ts index 906bed24d..7294632dd 100644 --- a/ui/src/app/metadata/configuration/configuration.module.ts +++ b/ui/src/app/metadata/configuration/configuration.module.ts @@ -31,7 +31,10 @@ import { FilterConfigurationListItemComponent } from './component/filter-configu import { SharedModule } from '../../shared/shared.module'; import { FilterTargetPropertyComponent } from './component/filter-target-property.component'; import { RestoreComponent } from './container/restore.component'; -import { RestoreVersionEffects } from './effect/restore.effect'; +import { RestoreEffects } from './effect/restore.effect'; +import { VersionComponent } from './container/version.component'; +import { VersionOptionsComponent } from './container/version-options.component'; +import { VersionEffects } from './effect/version.effect'; @NgModule({ declarations: [ @@ -50,7 +53,9 @@ import { RestoreVersionEffects } from './effect/restore.effect'; FilterConfigurationListComponent, FilterConfigurationListItemComponent, FilterTargetPropertyComponent, - RestoreComponent + RestoreComponent, + VersionComponent, + VersionOptionsComponent ], entryComponents: [], imports: [ @@ -88,7 +93,8 @@ export class MetadataConfigurationModule { MetadataConfigurationEffects, MetadataHistoryEffects, CompareVersionEffects, - RestoreVersionEffects + RestoreEffects, + VersionEffects ]) ], providers: [] diff --git a/ui/src/app/metadata/configuration/configuration.routing.ts b/ui/src/app/metadata/configuration/configuration.routing.ts index dc3f5df99..ddf6c5c3c 100644 --- a/ui/src/app/metadata/configuration/configuration.routing.ts +++ b/ui/src/app/metadata/configuration/configuration.routing.ts @@ -5,6 +5,8 @@ import { MetadataXmlComponent } from './container/metadata-xml.component'; import { MetadataHistoryComponent } from './container/metadata-history.component'; import { MetadataComparisonComponent } from './container/metadata-comparison.component'; import { RestoreComponent } from './container/restore.component'; +import { VersionComponent } from './container/version.component'; +import { VersionOptionsComponent } from './container/version-options.component'; export const ConfigurationRoutes: Routes = [ { @@ -32,8 +34,18 @@ export const ConfigurationRoutes: Routes = [ component: MetadataComparisonComponent }, { - path: 'restore', - component: RestoreComponent + path: 'version/:version', + component: VersionComponent, + children: [ + { + path: 'options', + component: VersionOptionsComponent + }, + { + path: 'restore', + component: RestoreComponent + } + ] } ] } diff --git a/ui/src/app/metadata/configuration/container/configuration.component.ts b/ui/src/app/metadata/configuration/container/configuration.component.ts index 295f90257..c1e2daf35 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.ts +++ b/ui/src/app/metadata/configuration/container/configuration.component.ts @@ -1,13 +1,12 @@ -import { Component, ChangeDetectionStrategy, OnDestroy, HostListener } from '@angular/core'; -import { ActivatedRoute, Router, Scroll, Event } from '@angular/router'; -import { takeUntil, map, withLatestFrom, filter, timeout, delay } from 'rxjs/operators'; +import { Component, ChangeDetectionStrategy, OnDestroy } from '@angular/core'; +import { ActivatedRoute } from '@angular/router'; +import { takeUntil, map } from 'rxjs/operators'; import { Store } from '@ngrx/store'; -import { Observable, Subject, interval, combineLatest } from 'rxjs'; +import { Observable, Subject } from 'rxjs'; import * as fromConfiguration from '../reducer'; import { ClearConfiguration, SetMetadata } from '../action/configuration.action'; -import { LoadHistoryRequest, ClearHistory, SelectVersion } from '../action/history.action'; import * as fromReducer from '../reducer'; @Component({ @@ -22,39 +21,20 @@ export class ConfigurationComponent implements OnDestroy { name$: Observable; type$: Observable; - hasVersion = ([collection, params]) => params.version || collection && collection.length > 0 ? collection[0].id : null; - constructor( private store: Store, private routerState: ActivatedRoute ) { - combineLatest( - this.routerState.params, - this.routerState.queryParams - ).pipe( + this.routerState.params.pipe( takeUntil(this.ngUnsubscribe), - map(([{ id, type }, { version }]) => new SetMetadata({ + map(({ id, type, version }) => new SetMetadata({ id, type, version })) ).subscribe(this.store); - this.routerState.params.pipe( - takeUntil(this.ngUnsubscribe), - map(params => new LoadHistoryRequest({ id: params.id, type: params.type })) - ).subscribe(store); - - this.store.select(fromReducer.getVersionCollection).pipe( - filter(collection => collection && collection.length > 0), - takeUntil(this.ngUnsubscribe), - withLatestFrom( - this.routerState.queryParams - ), - map(this.hasVersion) - ).subscribe(version => this.store.dispatch(new SelectVersion(version))); - this.name$ = this.store.select(fromReducer.getConfigurationModelName); this.type$ = this.store.select(fromReducer.getConfigurationModelType); } @@ -63,6 +43,5 @@ export class ConfigurationComponent implements OnDestroy { this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); this.store.dispatch(new ClearConfiguration()); - this.store.dispatch(new ClearHistory()); } } diff --git a/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts b/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts index cec29c2a6..dff593acd 100644 --- a/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts +++ b/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts @@ -3,7 +3,7 @@ import { Observable } from 'rxjs'; import { Store } from '@ngrx/store'; import { ActivatedRoute } from '@angular/router'; import { map } from 'rxjs/operators'; -import { ConfigurationState, getVersionConfigurations, getVersionConfigurationCount } from '../reducer'; +import { ConfigurationState, getComparisonConfigurations, getComparisonConfigurationCount } from '../reducer'; import { CompareVersionRequest } from '../action/compare.action'; import { MetadataConfiguration } from '../model/metadata-configuration'; import * as fromReducer from '../reducer'; @@ -29,8 +29,8 @@ export class MetadataComparisonComponent { map(versions => new CompareVersionRequest(versions)) ).subscribe(this.store); - this.versions$ = this.store.select(getVersionConfigurations); - this.numVersions$ = this.store.select(getVersionConfigurationCount); + this.versions$ = this.store.select(getComparisonConfigurations); + this.numVersions$ = this.store.select(getComparisonConfigurationCount); this.type$ = this.store.select(fromReducer.getConfigurationModelType); } } diff --git a/ui/src/app/metadata/configuration/container/metadata-history.component.ts b/ui/src/app/metadata/configuration/container/metadata-history.component.ts index 7d817b94f..ab3df19ee 100644 --- a/ui/src/app/metadata/configuration/container/metadata-history.component.ts +++ b/ui/src/app/metadata/configuration/container/metadata-history.component.ts @@ -1,11 +1,16 @@ -import { Component, ChangeDetectionStrategy } from '@angular/core'; -import { Observable } from 'rxjs'; +import { Component, ChangeDetectionStrategy, OnDestroy } from '@angular/core'; +import { Observable, combineLatest, Subject } from 'rxjs'; import { Store } from '@ngrx/store'; -import { ConfigurationState, getVersionCollection } from '../reducer'; +import { + ConfigurationState, + getVersionCollection, + getConfigurationModelId, + getConfigurationModelKind +} from '../reducer'; import { MetadataVersion } from '../model/version'; -import { CompareVersionRequest } from '../action/compare.action'; import { Router, ActivatedRoute } from '@angular/router'; -import { map } from 'rxjs/operators'; +import { map, takeUntil } from 'rxjs/operators'; +import { LoadHistoryRequest, ClearHistory } from '../action/history.action'; @Component({ selector: 'metadata-history', @@ -13,7 +18,9 @@ import { map } from 'rxjs/operators'; templateUrl: './metadata-history.component.html', styleUrls: [] }) -export class MetadataHistoryComponent { +export class MetadataHistoryComponent implements OnDestroy { + + private ngUnsubscribe: Subject = new Subject(); history$: Observable; @@ -22,6 +29,15 @@ export class MetadataHistoryComponent { private router: Router, private route: ActivatedRoute ) { + combineLatest( + this.store.select(getConfigurationModelId), + this.store.select(getConfigurationModelKind) + ).pipe( + takeUntil(this.ngUnsubscribe), + map(([id, kind]) => ({ id, type: kind })), + map(request => new LoadHistoryRequest(request)) + ).subscribe(store); + this.history$ = this.store.select(getVersionCollection) .pipe(map(versions => this.sortVersionsByDate(versions))); } @@ -47,11 +63,16 @@ export class MetadataHistoryComponent { restoreVersion(version: MetadataVersion): void { this.router.navigate( - [ '../', 'restore' ], + [ '../', 'version', version.id, 'restore' ], { - queryParams: { version: version.id }, relativeTo: this.route } ); } + + ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + this.store.dispatch(new ClearHistory()); + } } diff --git a/ui/src/app/metadata/configuration/container/metadata-options.component.html b/ui/src/app/metadata/configuration/container/metadata-options.component.html index ebed36bf3..e201fcb62 100644 --- a/ui/src/app/metadata/configuration/container/metadata-options.component.html +++ b/ui/src/app/metadata/configuration/container/metadata-options.component.html @@ -6,9 +6,8 @@

+ diff --git a/ui/src/app/metadata/configuration/container/restore-edit.component.ts b/ui/src/app/metadata/configuration/container/restore-edit.component.ts new file mode 100644 index 000000000..a50c0a52b --- /dev/null +++ b/ui/src/app/metadata/configuration/container/restore-edit.component.ts @@ -0,0 +1,45 @@ +import { Component } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { Store } from '@ngrx/store'; +import { + ConfigurationState, getConfigurationModelKind +} from '../../configuration/reducer'; +import { RestoreVersionRequest, CancelRestore } from '../action/restore.action'; +import { NAV_FORMATS } from '../../domain/component/editor-nav.component'; +import { Metadata } from '../../domain/domain.type'; +import { getVersionModel } from '../reducer'; + + +@Component({ + selector: 'restore-edit', + templateUrl: './restore-edit.component.html', + styleUrls: [] +}) + +export class RestoreEditComponent { + + model$: Observable = this.store.select(getVersionModel); + kind$: Observable = this.store.select(getConfigurationModelKind); + + isInvalid$: Observable = of(false); + canFilter$: Observable = of(false); + status$: Observable = of('VALID'); + isSaving$: Observable = of(false); + + validators$: Observable; + + formats = NAV_FORMATS; + + constructor( + private store: Store + ) {} + + save() { + this.store.dispatch(new RestoreVersionRequest()); + } + + cancel() { + this.store.dispatch(new CancelRestore()); + } +} + diff --git a/ui/src/app/metadata/configuration/container/restore.component.ts b/ui/src/app/metadata/configuration/container/restore.component.ts index e48e52c38..d87302ff6 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.ts @@ -7,6 +7,7 @@ import { CONFIG_DATE_FORMAT } from '../configuration.values'; import { RestoreVersionRequest, CancelRestore } from '../action/restore.action'; import { map } from 'rxjs/operators'; import { DatePipe } from '@angular/common'; +import { Router, ActivatedRoute } from '@angular/router'; @Component({ selector: 'restore-component', @@ -21,13 +22,15 @@ export class RestoreComponent { constructor( private store: Store, - private datePipe: DatePipe + private datePipe: DatePipe, + private router: Router, + private route: ActivatedRoute ) { this.date$ = this.dateString$.pipe(map((date) => this.datePipe.transform(date, CONFIG_DATE_FORMAT))); } restore() { - this.store.dispatch(new RestoreVersionRequest()); + this.router.navigate(['../', 'edit'], { relativeTo: this.route }); } cancel() { diff --git a/ui/src/app/metadata/configuration/effect/configuration.effect.ts b/ui/src/app/metadata/configuration/effect/configuration.effect.ts index b9719469d..91ce3d37a 100644 --- a/ui/src/app/metadata/configuration/effect/configuration.effect.ts +++ b/ui/src/app/metadata/configuration/effect/configuration.effect.ts @@ -9,10 +9,10 @@ import { MetadataConfigurationService } from '../service/configuration.service'; import { ConfigurationActionTypes, SetMetadata, - SetDefinition, + SetConfigurationDefinition, LoadSchemaRequest, LoadSchemaSuccess, - SetSchema, + SetConfigurationSchema, LoadSchemaError, LoadXmlSuccess, LoadXmlError, @@ -34,6 +34,11 @@ import { import { MetadataHistoryService } from '../service/history.service'; import { Metadata } from '../../domain/domain.type'; import { SelectVersion } from '../action/history.action'; +import { + SetDefinition, + LoadSchemaSuccess as LoadWizardSchemaSuccess, + LoadSchemaRequest as LoadWizardSchemaRequest +} from '../../../wizard/action/wizard.action'; @Injectable() export class MetadataConfigurationEffects { @@ -79,21 +84,33 @@ export class MetadataConfigurationEffects { @Effect() setDefinitionOnResolverDataLoad$ = this.actions$.pipe( ofType(ResolverCollectionActionTypes.SELECT_SUCCESS), - map(action => new SetDefinition(this.configService.getDefinition('resolver'))) + map(action => new SetConfigurationDefinition(this.configService.getDefinition('resolver'))) ); @Effect() setDefinitionOnProviderLoad$ = this.actions$.pipe( ofType(ProviderCollectionActionTypes.SELECT_PROVIDER_SUCCESS), - map(action => new SetDefinition(this.configService.getDefinition(action.payload['@type']))) + map(action => new SetConfigurationDefinition(this.configService.getDefinition(action.payload['@type']))) ); @Effect() loadSchemaOnDefinitionSet$ = this.actions$.pipe( - ofType(ConfigurationActionTypes.SET_DEFINITION), + ofType(ConfigurationActionTypes.SET_DEFINITION), map(action => new LoadSchemaRequest(action.payload.schema)) ); + @Effect() + setWizardDefinition$ = this.actions$.pipe( + ofType(ConfigurationActionTypes.SET_DEFINITION), + map(action => new SetDefinition(action.payload)) + ); + + @Effect() + setWizardSchema$ = this.actions$.pipe( + ofType(ConfigurationActionTypes.LOAD_SCHEMA_SUCCESS), + map(action => new LoadWizardSchemaSuccess(action.payload)) + ); + @Effect() loadSchemaData$ = this.actions$.pipe( ofType(ConfigurationActionTypes.LOAD_SCHEMA_REQUEST), @@ -110,7 +127,7 @@ export class MetadataConfigurationEffects { @Effect() setSchema$ = this.actions$.pipe( ofType(ConfigurationActionTypes.LOAD_SCHEMA_SUCCESS), - map(action => new SetSchema(action.payload)) + map(action => new SetConfigurationSchema(action.payload)) ); @Effect({dispatch: false}) diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts index 4a5e0b491..bacb422f6 100644 --- a/ui/src/app/metadata/configuration/effect/restore.effect.ts +++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts @@ -7,7 +7,9 @@ import { RestoreVersionRequest, RestoreVersionSuccess, RestoreVersionError, - CancelRestore + CancelRestore, + UpdateRestorationChangesRequest, + UpdateRestorationChangesSuccess } from '../action/restore.action'; import { MetadataHistoryService } from '../service/history.service'; import { of } from 'rxjs'; @@ -16,7 +18,14 @@ import { Router } from '@angular/router'; import { AddNotification } from '../../../notification/action/notification.action'; import { Notification, NotificationType } from '../../../notification/model/notification'; import { Store } from '@ngrx/store'; -import { ConfigurationState, getConfigurationModel, getVersionModel, getConfigurationModelId, getConfigurationModelKind } from '../reducer'; +import { + ConfigurationState, + getConfigurationModel, + getVersionModel, + getConfigurationModelId, + getConfigurationModelKind, + getConfigurationDefinition +} from '../reducer'; import { SetMetadata } from '../action/configuration.action'; @@ -86,6 +95,31 @@ export class RestoreEffects { ) ); + @Effect() + updateRestorationChanges$ = this.actions$.pipe( + ofType(RestoreActionTypes.UPDATE_RESTORATION_REQUEST), + map(action => action.payload), + withLatestFrom( + this.store.select(getConfigurationDefinition), + this.store.select(getConfigurationModelKind), + this.store.select(getConfigurationModel) + ), + map(([changes, definition, kind, original]) => { + let parsed = definition.parser(changes); + if (kind === 'provider') { + parsed = { + ...parsed, + metadataFilters: [ + ...original.metadataFilters, + ...(parsed.metadataFilters || []) + ] + }; + } + return (parsed); + }), + map(changes => new UpdateRestorationChangesSuccess(changes)) + ); + constructor( private store: Store, private historyService: MetadataHistoryService, diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index 43638ba72..7bd043a2c 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -5,6 +5,7 @@ import * as fromConfiguration from './configuration.reducer'; import * as fromHistory from './history.reducer'; import * as fromCompare from './compare.reducer'; import * as fromVersion from './version.reducer'; +import * as fromRestore from './restore.reducer'; import { WizardStep } from '../../../wizard/model'; import * as utils from '../../domain/utility/configuration'; @@ -21,13 +22,15 @@ export interface ConfigurationState { history: fromHistory.HistoryState; compare: fromCompare.State; version: fromVersion.State; + restore: fromRestore.RestoreState; } export const reducers = { configuration: fromConfiguration.reducer, history: fromHistory.reducer, compare: fromCompare.reducer, - version: fromVersion.reducer + version: fromVersion.reducer, + restore: fromRestore.reducer }; export interface State extends fromRoot.State { @@ -40,6 +43,7 @@ export const getConfigurationStateFn = (state: ConfigurationState) => state.conf export const getHistoryStateFn = (state: ConfigurationState) => state.history; export const getCompareStateFn = (state: ConfigurationState) => state.compare; export const getVersionStateFn = (state: ConfigurationState) => state.version; +export const getRestoreStateFn = (state: ConfigurationState) => state.restore; export const getConfigurationState = createSelector(getState, getConfigurationStateFn); export const getConfigurationModelKind = createSelector(getConfigurationState, fromConfiguration.getModelKind); @@ -151,6 +155,9 @@ export const getComparisonConfigurations = createSelector( export const getComparisonConfigurationCount = createSelector(getComparisonConfigurations, (config) => config ? config.dates.length : 0); // Version Restoration + +export const getRestoreState = createSelector(getState, getRestoreStateFn); + export const filterPluginTypes = ['RequiredValidUntil', 'SignatureValidation', 'EntityRoleWhiteList']; export const isAdditionalFilter = (type) => filterPluginTypes.indexOf(type) === -1; @@ -175,6 +182,19 @@ export const getVersionModelFilters = createSelector( getVersionModelFiltersFn ); +export const getRestorationIsValid = createSelector(getRestoreState, fromRestore.isRestorationValid); +export const getRestorationIsSaved = createSelector(getRestoreState, fromRestore.isRestorationSaved); +export const getRestorationChanges = createSelector(getRestoreState, fromRestore.getChanges); +export const getRestorationIsSaving = createSelector(getRestoreState, fromRestore.isRestorationSaving); +export const getRestorationFormStatus = createSelector(getRestoreState, fromRestore.getFormStatus); +export const getInvalidRestorationForms = createSelector(getRestoreState, fromRestore.getInvalidRestorationForms); + +export const getFormattedModel = createSelector( + getVersionModel, + getConfigurationDefinition, + (model, definition) => definition.formatter(model) +); + // Mixed states export const getConfigurationModelFn = (kind, version, provider, resolver) => { diff --git a/ui/src/app/metadata/configuration/reducer/restore.reducer.ts b/ui/src/app/metadata/configuration/reducer/restore.reducer.ts new file mode 100644 index 000000000..4ca626459 --- /dev/null +++ b/ui/src/app/metadata/configuration/reducer/restore.reducer.ts @@ -0,0 +1,40 @@ +import { Metadata } from '../../domain/domain.type'; +import { RestoreActionTypes, RestoreActionsUnion } from '../action/restore.action'; + +export interface RestoreState { + saving: boolean; + status: { [key: string]: string }; + changes: Metadata; +} + +export const initialState: RestoreState = { + saving: false, + status: {}, + changes: {} as Metadata +}; + +export function reducer(state = initialState, action: RestoreActionsUnion): RestoreState { + switch (action.type) { + case RestoreActionTypes.UPDATE_RESTORATION_SUCCESS: + return { + ...state, + changes: { + ...state.changes, + ...action.payload + } + }; + default: { + return state; + } + } +} + +export const isRestorationSaved = (state: RestoreState) => !Object.keys(state.changes).length; +export const getChanges = (state: RestoreState) => state.changes; +export const isRestorationSaving = (state: RestoreState) => state.saving; +export const getFormStatus = (state: RestoreState) => state.status; + +export const isRestorationValid = (state: RestoreState) => + !Object.keys(state.status).some(key => state.status[key] === ('INVALID')); +export const getInvalidRestorationForms = (state: RestoreState) => + Object.keys(state.status).filter(key => state.status[key] === 'INVALID'); diff --git a/ui/src/app/metadata/configuration/service/index-resolver.service.ts b/ui/src/app/metadata/configuration/service/index-resolver.service.ts new file mode 100644 index 000000000..4bbc5e07e --- /dev/null +++ b/ui/src/app/metadata/configuration/service/index-resolver.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; +import { Resolve, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { WizardState } from '../../../wizard/reducer'; +import { SetIndex } from '../../../wizard/action/wizard.action'; + +@Injectable() +export class IndexResolver implements Resolve { + constructor(private store: Store) { } + + resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) { + this.store.dispatch(new SetIndex(route.params.index)); + } +} diff --git a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts index 02b1a2625..106ebcf1d 100644 --- a/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts +++ b/ui/src/app/metadata/domain/model/wizards/metadata-source-base.ts @@ -3,6 +3,7 @@ import { MetadataResolver } from '../metadata-resolver'; import { FormProperty } from 'ngx-schema-form/lib/model/formproperty'; import { ArrayProperty } from 'ngx-schema-form/lib/model/arrayproperty'; import { ObjectProperty } from 'ngx-schema-form/lib/model/objectproperty'; +import { getAllOtherIds } from '../../../resolver/reducer'; /*istanbul ignore next */ export class MetadataSourceBase implements Wizard { @@ -11,6 +12,8 @@ export class MetadataSourceBase implements Wizard { steps: WizardStep[] = []; schema = ''; + validatorParams = [getAllOtherIds]; + bindings = { '/securityInfo/x509CertificateAvailable': [ { @@ -86,7 +89,6 @@ export class MetadataSourceBase implements Wizard { const checkOrg = (value, property, form) => { const org = property.parent; const orgValue = org.value || {}; - console.log(orgValue); const err = Object.keys(orgValue) && !value ? { code: 'ORG_INCOMPLETE', path: `#${property.path}`, 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 78ee24e43..a2642ddc9 100644 --- a/ui/src/app/metadata/filter/model/entity-attributes.filter.ts +++ b/ui/src/app/metadata/filter/model/entity-attributes.filter.ts @@ -2,6 +2,7 @@ import { FormDefinition } from '../../../wizard/model'; import { MetadataFilter } from '../../domain/model'; import { removeNulls } from '../../../shared/util'; import { EntityAttributesFilterEntity } from '../../domain/entity'; +import { getFilterNames } from '../reducer'; export const EntityAttributesFilter: FormDefinition = { label: 'EntityAttributes', @@ -10,6 +11,7 @@ export const EntityAttributesFilter: FormDefinition = { getEntity(filter: MetadataFilter): EntityAttributesFilterEntity { return new EntityAttributesFilterEntity(filter); }, + validatorParams: [getFilterNames], getValidators(namesList: string[] = []): any { const validators = { '/': (value, property, form_current) => { diff --git a/ui/src/app/metadata/filter/model/nameid.filter.ts b/ui/src/app/metadata/filter/model/nameid.filter.ts index f3cf67960..9751d4ce9 100644 --- a/ui/src/app/metadata/filter/model/nameid.filter.ts +++ b/ui/src/app/metadata/filter/model/nameid.filter.ts @@ -1,6 +1,7 @@ import { FormDefinition } from '../../../wizard/model'; import { MetadataFilter } from '../../domain/model'; import { NameIDFormatFilterEntity } from '../../domain/entity/filter/nameid-format-filter'; +import { getFilterNames } from '../reducer'; export const NameIDFilter: FormDefinition = { label: 'NameIDFormat', @@ -9,6 +10,7 @@ export const NameIDFilter: FormDefinition = { getEntity(filter: MetadataFilter): NameIDFormatFilterEntity { return new NameIDFormatFilterEntity(filter); }, + validatorParams: [getFilterNames], getValidators(namesList: string[] = []): any { const validators = { '/': (value, property, form_current) => { diff --git a/ui/src/app/metadata/provider/container/provider-edit-step.component.spec.ts b/ui/src/app/metadata/provider/container/provider-edit-step.component.spec.ts index 093dc85c1..a9013176a 100644 --- a/ui/src/app/metadata/provider/container/provider-edit-step.component.spec.ts +++ b/ui/src/app/metadata/provider/container/provider-edit-step.component.spec.ts @@ -52,8 +52,7 @@ describe('Provider Edit Step Component', () => { } } }, - locked: false, - schemaCollection: [] + locked: false } }) }) diff --git a/ui/src/app/metadata/provider/container/provider-edit.component.spec.ts b/ui/src/app/metadata/provider/container/provider-edit.component.spec.ts index bd6ed6698..a5601d594 100644 --- a/ui/src/app/metadata/provider/container/provider-edit.component.spec.ts +++ b/ui/src/app/metadata/provider/container/provider-edit.component.spec.ts @@ -62,8 +62,7 @@ describe('Provider Edit Component', () => { schemaPath: '', loading: false, schema: {}, - locked: false, - schemaCollection: [] + locked: false } }) }), 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 125966878..f1957718e 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard.component.ts +++ b/ui/src/app/metadata/provider/container/provider-wizard.component.ts @@ -63,7 +63,7 @@ export class ProviderWizardComponent implements OnDestroy { this.summary$ = combineLatest( this.store.select(fromWizard.getWizardDefinition), - this.store.select(fromWizard.getSchemaCollection), + this.store.select(fromWizard.getSchema), this.store.select(fromProvider.getEntityChanges) ).pipe( map(([ definition, schema, model ]) => ({ definition, schema, model })) 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 a27a1cbc6..645e21f23 100644 --- a/ui/src/app/metadata/provider/model/base.provider.form.ts +++ b/ui/src/app/metadata/provider/model/base.provider.form.ts @@ -1,11 +1,13 @@ import { Wizard } from '../../../wizard/model'; import { BaseMetadataProvider } from '../../domain/model/providers'; +import { getProviderNames, getProviderXmlIds } from '../reducer'; export const BaseMetadataProviderEditor: Wizard = { label: 'BaseMetadataProvider', type: 'BaseMetadataResolver', schema: '', - getValidators(namesList: string[]): any { + validatorParams: [getProviderNames, getProviderXmlIds], + getValidators(namesList: string[], xmlIdList: string[]): any { const validators = { '/': (value, property, form_current) => { let errors; @@ -29,6 +31,15 @@ export const BaseMetadataProviderEditor: Wizard = { params: [value] } : null; return err; + }, + '/xmlId': (value, property, form) => { + const err = xmlIdList.indexOf(value) > -1 ? { + code: 'INVALID_ID', + path: `#${property.path}`, + message: 'message.id-unique', + params: [value] + } : null; + return err; } }; return validators; diff --git a/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts b/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts index 2583e3c69..9ef3bf2bb 100644 --- a/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts +++ b/ui/src/app/metadata/provider/model/dynamic-http.provider.form.ts @@ -17,16 +17,7 @@ export const DynamicHttpMetadataProviderWizard: Wizard { - const err = xmlIdList.indexOf(value) > -1 ? { - code: 'INVALID_ID', - path: `#${property.path}`, - message: 'message.id-unique', - params: [value] - } : null; - return err; - }; + const validators = BaseMetadataProviderEditor.getValidators(namesList, xmlIdList); validators['/metadataRequestURLConstructionScheme'] = (value, property, form) => { let errors; diff --git a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts index 0df10c0ea..dde0ec3f1 100644 --- a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts +++ b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts @@ -18,16 +18,7 @@ export const FileBackedHttpMetadataProviderWizard: Wizard { - const err = xmlIdList.indexOf(value) > -1 ? { - code: 'INVALID_ID', - path: `#${property.path}`, - message: 'message.id-unique', - params: [value] - } : null; - return err; - }; + const validators = BaseMetadataProviderEditor.getValidators(namesList, xmlIdList); validators['/metadataURL'] = (value, property, form) => { return !UriValidator.isUri(value) ? { code: 'INVALID_URI', diff --git a/ui/src/app/metadata/provider/model/file-system.provider.form.ts b/ui/src/app/metadata/provider/model/file-system.provider.form.ts index 331a53c99..fcf780ac5 100644 --- a/ui/src/app/metadata/provider/model/file-system.provider.form.ts +++ b/ui/src/app/metadata/provider/model/file-system.provider.form.ts @@ -16,19 +16,6 @@ export const FileSystemMetadataProviderWizard: Wizard { - const err = xmlIdList.indexOf(value) > -1 ? { - code: 'INVALID_ID', - path: `#${property.path}`, - message: 'message.id-unique', - params: [value] - } : null; - return err; - }; - return validators; - }, schema: '/api/ui/MetadataResolver/FilesystemMetadataResolver', steps: [ { diff --git a/ui/src/app/metadata/provider/model/local-dynamic.provider.form.ts b/ui/src/app/metadata/provider/model/local-dynamic.provider.form.ts index aeeef302a..c53562873 100644 --- a/ui/src/app/metadata/provider/model/local-dynamic.provider.form.ts +++ b/ui/src/app/metadata/provider/model/local-dynamic.provider.form.ts @@ -17,19 +17,6 @@ export const LocalDynamicMetadataProviderWizard: Wizard { - const err = xmlIdList.indexOf(value) > -1 ? { - code: 'INVALID_ID', - path: `#${property.path}`, - message: 'message.id-unique', - params: [value] - } : null; - return err; - }; - return validators; - }, schema: '/api/ui/MetadataResolver/LocalDynamicMetadataResolver', steps: [ { diff --git a/ui/src/app/metadata/resolver/container/resolver-wizard-step.component.spec.ts b/ui/src/app/metadata/resolver/container/resolver-wizard-step.component.spec.ts index 56c4784a7..c64445dd4 100644 --- a/ui/src/app/metadata/resolver/container/resolver-wizard-step.component.spec.ts +++ b/ui/src/app/metadata/resolver/container/resolver-wizard-step.component.spec.ts @@ -66,11 +66,6 @@ describe('Resolver Wizard Step Component', () => { index: 'common', disabled: false, definition: new MetadataSourceWizard(), - schemaCollection: { - common: { - ...schema - } - }, schemaPath: '/foo/bar', loading: false, schema: { diff --git a/ui/src/app/metadata/resolver/container/resolver-wizard.component.spec.ts b/ui/src/app/metadata/resolver/container/resolver-wizard.component.spec.ts index 1b6c5df32..3cec9bf25 100644 --- a/ui/src/app/metadata/resolver/container/resolver-wizard.component.spec.ts +++ b/ui/src/app/metadata/resolver/container/resolver-wizard.component.spec.ts @@ -72,11 +72,6 @@ describe('Resolver Wizard Component', () => { index: 'page', disabled: false, definition: new MetadataSourceWizard(), - schemaCollection: { - page: { - ...schema - } - }, schemaPath: '/foo/bar', loading: false, schema: { diff --git a/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts b/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts index 3ccddc90e..ffabac236 100644 --- a/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts +++ b/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts @@ -117,7 +117,7 @@ export class ResolverWizardComponent implements OnDestroy, CanComponentDeactivat this.summary$ = combine( this.store.select(fromWizard.getWizardDefinition), - this.store.select(fromWizard.getSchemaCollection), + this.store.select(fromWizard.getSchema), this.store.select(fromResolver.getEntityChanges) ).pipe( map(([definition, schema, model]) => ( diff --git a/ui/src/app/metadata/resolver/reducer/index.ts b/ui/src/app/metadata/resolver/reducer/index.ts index bf38715d9..43bc2fbc7 100644 --- a/ui/src/app/metadata/resolver/reducer/index.ts +++ b/ui/src/app/metadata/resolver/reducer/index.ts @@ -103,3 +103,9 @@ export const getAllResolvers = createSelector(getDraftCollection, getResolverCol export const getAllResolverIds = createSelector(getDraftIds, getResolverIds, combineAllFn); export const getAllEntityIds = createSelector(getAllResolvers, getEntityIdsFn); + +export const getAllOtherIds = createSelector( + getAllResolvers, + getSelectedResolverId, + (ids, selected) => ids.filter(id => id !== selected) +); diff --git a/ui/src/app/wizard/model/form-definition.ts b/ui/src/app/wizard/model/form-definition.ts index 3f03957c0..4402aa354 100644 --- a/ui/src/app/wizard/model/form-definition.ts +++ b/ui/src/app/wizard/model/form-definition.ts @@ -1,8 +1,11 @@ +import { Selector } from '@ngrx/store'; + export interface FormDefinition { label: string; type: string; schema: string; bindings?: any; + validatorParams: any[]; getEntity?(entity: any): any; parser(changes: Partial, schema?: any); formatter(changes: Partial, schema?: any); diff --git a/ui/src/app/wizard/reducer/index.ts b/ui/src/app/wizard/reducer/index.ts index 4c3321c62..d85d930be 100644 --- a/ui/src/app/wizard/reducer/index.ts +++ b/ui/src/app/wizard/reducer/index.ts @@ -2,8 +2,6 @@ import * as fromRoot from '../../app.reducer'; import * as fromWizard from './wizard.reducer'; import { createFeatureSelector, createSelector } from '@ngrx/store'; import { Wizard, WizardStep } from '../model'; -import { diff } from 'deep-object-diff'; -import { SchemaService } from '../../schema-form/service/schema.service'; export interface WizardState { wizard: fromWizard.State; @@ -46,7 +44,6 @@ export const getState = createSelector(getWizardState, getWizardStateFn); export const getWizardIndex = createSelector(getState, fromWizard.getIndex); export const getWizardIsDisabled = createSelector(getState, fromWizard.getDisabled); export const getWizardDefinition = createSelector(getState, fromWizard.getDefinition); -export const getSchemaCollection = createSelector(getState, fromWizard.getCollection); export const getSchemaPath = (wizard: Wizard) => wizard ? wizard.schema : null; @@ -135,3 +132,9 @@ export const getSchemaObject = createSelector(getState, fromWizard.getSchema); export const getParsedSchema = createSelector(getSchemaObject, getLocked, getSchemaParseFn); export const getSchema = createSelector(getParsedSchema, getCurrent, getSplitSchema); + +export const getWizardDefinitionValidationParams = createSelector(getWizardDefinition, def => def.validatorParams); + +export const getValidators = (params: any) => createSelector(getWizardDefinition, (definition) => { + return definition.getValidators(...params); +}); diff --git a/ui/src/app/wizard/reducer/wizard.reducer.spec.ts b/ui/src/app/wizard/reducer/wizard.reducer.spec.ts index 8a50eb116..d43ad334e 100644 --- a/ui/src/app/wizard/reducer/wizard.reducer.spec.ts +++ b/ui/src/app/wizard/reducer/wizard.reducer.spec.ts @@ -34,12 +34,6 @@ describe('Wizard Reducer', () => { }); }); - describe(`${WizardActionTypes.ADD_SCHEMA}`, () => { - it('should add the payload to the schema collection', () => { - expect(reducer(snapshot, new AddSchema({id: 'foo', schema: SCHEMA })).schemaCollection).toEqual({ 'foo': SCHEMA }); - }); - }); - describe(`${WizardActionTypes.SET_DISABLED}`, () => { it('should set the disabled property on the wizard', () => { expect(reducer(snapshot, new SetDisabled(true)).disabled).toBe(true); @@ -108,7 +102,6 @@ describe('Wizard Reducer', () => { describe('selector functions', () => { it('should return pieces of state', () => { - expect(selectors.getCollection(snapshot)).toEqual(snapshot.schemaCollection); expect(selectors.getDefinition(snapshot)).toEqual(snapshot.definition); expect(selectors.getDisabled(snapshot)).toEqual(snapshot.disabled); expect(selectors.getIndex(snapshot)).toEqual(snapshot.index); diff --git a/ui/src/app/wizard/reducer/wizard.reducer.ts b/ui/src/app/wizard/reducer/wizard.reducer.ts index 603fbaeda..11205afb3 100644 --- a/ui/src/app/wizard/reducer/wizard.reducer.ts +++ b/ui/src/app/wizard/reducer/wizard.reducer.ts @@ -5,7 +5,6 @@ export interface State { index: string; disabled: boolean; definition: Wizard; - schemaCollection: { [id: string]: any }; schemaPath: string; loading: boolean; @@ -17,8 +16,6 @@ export const initialState: State = { index: null, disabled: false, definition: null, - schemaCollection: {}, - schemaPath: null, loading: false, schema: null, @@ -62,16 +59,6 @@ export function reducer(state = initialState, action: WizardActionUnion): State locked: false }; } - - case WizardActionTypes.ADD_SCHEMA: { - return { - ...state, - schemaCollection: { - ...state.schemaCollection, - [action.payload.id]: action.payload.schema - } - }; - } case WizardActionTypes.SET_DISABLED: { return { ...state, @@ -121,4 +108,3 @@ 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; -export const getCollection = (state: State) => state.schemaCollection; From e85b71cc9b19b84cb82cbb1462cf38a973cd554d Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 20 Aug 2019 07:53:34 -0700 Subject: [PATCH 04/11] SHIBUI-1385 Fixed issue with wizard-summary --- .../metadata/configuration/effect/restore.effect.ts | 5 +++-- ui/src/app/metadata/configuration/reducer/index.ts | 6 ++++++ .../domain/component/wizard-summary.component.ts | 6 +----- .../provider/container/provider-wizard.component.ts | 2 +- ui/src/app/metadata/provider/effect/editor.effect.ts | 11 +---------- ui/src/app/wizard/action/wizard.action.ts | 7 ------- ui/src/app/wizard/reducer/wizard.reducer.spec.ts | 1 - 7 files changed, 12 insertions(+), 26 deletions(-) diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts index bacb422f6..4825c32dc 100644 --- a/ui/src/app/metadata/configuration/effect/restore.effect.ts +++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts @@ -24,7 +24,8 @@ import { getVersionModel, getConfigurationModelId, getConfigurationModelKind, - getConfigurationDefinition + getConfigurationDefinition, + getRestorationModel } from '../reducer'; import { SetMetadata } from '../action/configuration.action'; @@ -39,7 +40,7 @@ export class RestoreEffects { this.store.select(getConfigurationModelId), this.store.select(getConfigurationModelKind), this.store.select(getConfigurationModel), - this.store.select(getVersionModel) + this.store.select(getRestorationModel) ), switchMap(([action, id, kind, current, version]) => this.historyService.updateVersion(id, kind, { diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index 7bd043a2c..b26766af9 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -195,6 +195,12 @@ export const getFormattedModel = createSelector( (model, definition) => definition.formatter(model) ); +export const getRestorationModel = createSelector( + getVersionModel, + getRestorationChanges, + (model, changes) => model +); + // Mixed states export const getConfigurationModelFn = (kind, version, provider, resolver) => { diff --git a/ui/src/app/metadata/domain/component/wizard-summary.component.ts b/ui/src/app/metadata/domain/component/wizard-summary.component.ts index e8b8deb34..82cd86c8d 100644 --- a/ui/src/app/metadata/domain/component/wizard-summary.component.ts +++ b/ui/src/app/metadata/domain/component/wizard-summary.component.ts @@ -34,15 +34,11 @@ export class WizardSummaryComponent implements OnChanges { ngOnChanges(changes: SimpleChanges): void { if (changes.summary && this.summary) { - const schemas = this.summary.schema; + const schema = this.summary.schema; const model = this.summary.model; const def = this.summary.definition; const steps = def.steps; - const schema = Object.keys(schemas).reduce((coll, key) => ({ - ...merge(coll, schemas[key]) - }), {} as any); - this.sections = steps .filter(step => step.id !== 'summary') .map( 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 f1957718e..f501f7cb8 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard.component.ts +++ b/ui/src/app/metadata/provider/container/provider-wizard.component.ts @@ -63,7 +63,7 @@ export class ProviderWizardComponent implements OnDestroy { this.summary$ = combineLatest( this.store.select(fromWizard.getWizardDefinition), - this.store.select(fromWizard.getSchema), + this.store.select(fromWizard.getSchemaObject), this.store.select(fromProvider.getEntityChanges) ).pipe( map(([ definition, schema, model ]) => ({ definition, schema, model })) diff --git a/ui/src/app/metadata/provider/effect/editor.effect.ts b/ui/src/app/metadata/provider/effect/editor.effect.ts index f9ac13baf..60ec2d719 100644 --- a/ui/src/app/metadata/provider/effect/editor.effect.ts +++ b/ui/src/app/metadata/provider/effect/editor.effect.ts @@ -12,8 +12,7 @@ import { LoadSchemaSuccess, LoadSchemaFail, SetDefinition, - WizardActionTypes, - AddSchema + WizardActionTypes } from '../../../wizard/action/wizard.action'; import { ResetChanges } from '../action/entity.action'; @@ -38,14 +37,6 @@ export class EditorEffects { ) ); - @Effect() - $loadSchemaSuccess = this.actions$.pipe( - ofType(WizardActionTypes.LOAD_SCHEMA_SUCCESS), - map(action => action.payload), - withLatestFrom(this.store.select(fromWizard.getWizardIndex)), - map(([schema, id]) => new AddSchema({ id, schema })) - ); - @Effect() $resetChanges = this.actions$.pipe( ofType(WizardActionTypes.SET_DEFINITION), diff --git a/ui/src/app/wizard/action/wizard.action.ts b/ui/src/app/wizard/action/wizard.action.ts index 83a5f08a5..3e630f1a1 100644 --- a/ui/src/app/wizard/action/wizard.action.ts +++ b/ui/src/app/wizard/action/wizard.action.ts @@ -58,12 +58,6 @@ export class Previous implements Action { constructor(public payload: string) { } } -export class AddSchema implements Action { - readonly type = WizardActionTypes.ADD_SCHEMA; - - constructor(public payload: { id: string, schema: any }) { } -} - export class ClearWizard implements Action { readonly type = WizardActionTypes.CLEAR; } @@ -102,7 +96,6 @@ export type WizardActionUnion = | Next | Previous | ClearWizard - | AddSchema | LoadSchemaRequest | LoadSchemaSuccess | LoadSchemaFail diff --git a/ui/src/app/wizard/reducer/wizard.reducer.spec.ts b/ui/src/app/wizard/reducer/wizard.reducer.spec.ts index d43ad334e..8276a2d68 100644 --- a/ui/src/app/wizard/reducer/wizard.reducer.spec.ts +++ b/ui/src/app/wizard/reducer/wizard.reducer.spec.ts @@ -3,7 +3,6 @@ import * as selectors from './wizard.reducer'; import { WizardActionTypes, ClearWizard, - AddSchema, SetDisabled, SetDefinition, SetIndex, From f778a743ac4c5c17f73737ff0fc18bd06082c80b Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 29 Aug 2019 15:28:26 -0700 Subject: [PATCH 05/11] SHIBUI-1385 Implemented version restore editing --- .../configuration/action/restore.action.ts | 17 +++++ .../component/editor.component.html | 2 +- .../component/editor.component.ts | 5 +- .../metadata-header.component.spec.ts | 2 - .../container/configuration.component.spec.ts | 8 --- .../restore-edit-step.component.html | 3 + .../container/restore-edit-step.component.ts | 70 +++++++++++-------- .../container/restore-edit.component.html | 17 +---- .../container/restore-edit.component.ts | 18 +++-- .../container/restore.component.spec.ts | 11 +-- .../configuration/effect/restore.effect.ts | 22 +++++- .../reducer/configuration.reducer.spec.ts | 4 +- .../metadata/configuration/reducer/index.ts | 29 ++++++-- .../reducer/restore.reducer.spec.ts | 34 +++++++++ .../configuration/reducer/restore.reducer.ts | 14 ++++ .../service/history.service.spec.ts | 17 ----- .../configuration/service/history.service.ts | 2 +- .../wizard-summary.component.spec.ts | 5 +- .../component/wizard-summary.component.ts | 19 ++--- .../container/provider-edit-step.component.ts | 4 +- .../provider/model/base.provider.form.ts | 4 +- ui/src/app/metadata/provider/reducer/index.ts | 10 +++ .../container/resolver-wizard.component.ts | 6 +- ui/src/app/metadata/resolver/reducer/index.ts | 15 ++++ ui/src/app/wizard/model/form-definition.ts | 2 +- ui/src/app/wizard/reducer/wizard.reducer.ts | 1 - 26 files changed, 230 insertions(+), 111 deletions(-) create mode 100644 ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts diff --git a/ui/src/app/metadata/configuration/action/restore.action.ts b/ui/src/app/metadata/configuration/action/restore.action.ts index 04a15fca7..0407e830b 100644 --- a/ui/src/app/metadata/configuration/action/restore.action.ts +++ b/ui/src/app/metadata/configuration/action/restore.action.ts @@ -10,6 +10,9 @@ export enum RestoreActionTypes { UPDATE_RESTORATION_REQUEST = '[Restore Version] Update Changes Request', UPDATE_RESTORATION_SUCCESS = '[Restore Version] Update Changes Success', + UPDATE_STATUS = '[Restore Version] Update Restore Form Status', + SET_SAVING_STATUS = '[Restore Version] Set Saving Status', + CLEAR_VERSION = '[Restore Version] Clear Versions', CANCEL_RESTORE = '[Restore Version] Cancel Restore' } @@ -39,6 +42,18 @@ export class UpdateRestorationChangesSuccess implements Action { constructor(public payload: any) { } } +export class UpdateRestoreFormStatus implements Action { + readonly type = RestoreActionTypes.UPDATE_STATUS; + + constructor(public payload: { [key: string]: string }) { } +} + +export class SetSavingStatus implements Action { + readonly type = RestoreActionTypes.SET_SAVING_STATUS; + + constructor(public payload: boolean) { } +} + export class CancelRestore implements Action { readonly type = RestoreActionTypes.CANCEL_RESTORE; constructor() { } @@ -50,4 +65,6 @@ export type RestoreActionsUnion = | RestoreVersionError | UpdateRestorationChangesRequest | UpdateRestorationChangesSuccess + | UpdateRestoreFormStatus + | SetSavingStatus | CancelRestore; diff --git a/ui/src/app/metadata/configuration/component/editor.component.html b/ui/src/app/metadata/configuration/component/editor.component.html index 53414fcf6..fd71771aa 100644 --- a/ui/src/app/metadata/configuration/component/editor.component.html +++ b/ui/src/app/metadata/configuration/component/editor.component.html @@ -11,4 +11,4 @@ [validators]="validators" [bindings]="bindings" (onChange)="change.emit($event.value)" - (onError)="status.emit($event)"> \ No newline at end of file + (onErrorChange)="status.emit($event)"> \ No newline at end of file diff --git a/ui/src/app/metadata/configuration/component/editor.component.ts b/ui/src/app/metadata/configuration/component/editor.component.ts index 8e0ab78dd..59f3ef375 100644 --- a/ui/src/app/metadata/configuration/component/editor.component.ts +++ b/ui/src/app/metadata/configuration/component/editor.component.ts @@ -18,9 +18,12 @@ export class MetadataEditorComponent { @Output() change: EventEmitter = new EventEmitter(); @Output() status: EventEmitter = new EventEmitter(); + @Output() onLockChange: EventEmitter = new EventEmitter(); lock: FormControl = new FormControl(true); - constructor() {} + constructor() { + this.lock.valueChanges.subscribe(locked => this.onLockChange.emit(locked)); + } } diff --git a/ui/src/app/metadata/configuration/component/metadata-header.component.spec.ts b/ui/src/app/metadata/configuration/component/metadata-header.component.spec.ts index e8db42c36..f3cfd0d45 100644 --- a/ui/src/app/metadata/configuration/component/metadata-header.component.spec.ts +++ b/ui/src/app/metadata/configuration/component/metadata-header.component.spec.ts @@ -9,7 +9,6 @@ import { MetadataHeaderComponent } from './metadata-header.component'; ` @@ -25,7 +24,6 @@ class TestHostComponent { creator: 'foobar', date: new Date().toDateString() }; - versionNumber = 1; isCurrent = false; } diff --git a/ui/src/app/metadata/configuration/container/configuration.component.spec.ts b/ui/src/app/metadata/configuration/container/configuration.component.spec.ts index 0d2fca0d3..71bb05ab0 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/configuration.component.spec.ts @@ -59,12 +59,4 @@ describe('Metadata Configuration Page Component', () => { it('should load metadata objects', async(() => { expect(app).toBeTruthy(); })); - - describe('hasVersion function', () => { - it('should determine if a version is defined', () => { - expect(app.hasVersion([[{id: 'foo'}], { version: 'foo' }])).toBe('foo'); - expect(app.hasVersion([[{ id: 'foo' }], {}])).toBe('foo'); - expect(app.hasVersion([[], {}])).toBeNull(); - }); - }); }); diff --git a/ui/src/app/metadata/configuration/container/restore-edit-step.component.html b/ui/src/app/metadata/configuration/container/restore-edit-step.component.html index 496aa29f0..be953b86b 100644 --- a/ui/src/app/metadata/configuration/container/restore-edit-step.component.html +++ b/ui/src/app/metadata/configuration/container/restore-edit-step.component.html @@ -2,5 +2,8 @@ [schema]="schema$ | async" [model]="model$ | async" [validators]="validators$ | async" + [lockable]="lockable$ | async" (change)="onChange($event)" + (status)="statusChange$.next($event)" + (onLockChange)="lockChange$.next($event)" > \ No newline at end of file diff --git a/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts b/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts index 2e066a3d2..3d93f5ebd 100644 --- a/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts +++ b/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts @@ -1,18 +1,18 @@ -import { Component } from '@angular/core'; -import { Observable, combineLatest, of } from 'rxjs'; +import { Component, OnDestroy } from '@angular/core'; +import { Observable, combineLatest, of, Subject } from 'rxjs'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; import { - ConfigurationState, - getFormattedModel, - getRestorationChanges + ConfigurationState, getFormattedModel } from '../reducer'; -import { getWizardDefinition, getSchema, getValidators } from '../../../wizard/reducer'; -import { Wizard } from '../../../wizard/model'; +import { getWizardDefinition, getSchema, getValidators, getCurrent, getWizardIndex } from '../../../wizard/reducer'; +import { Wizard, WizardStep } from '../../../wizard/model'; import { Metadata } from '../../domain/domain.type'; -import { map, switchMap } from 'rxjs/operators'; +import { map, switchMap, withLatestFrom } from 'rxjs/operators'; import { NAV_FORMATS } from '../../domain/component/editor-nav.component'; -import { UpdateRestorationChangesRequest } from '../action/restore.action'; +import { UpdateRestorationChangesRequest, UpdateRestoreFormStatus } from '../action/restore.action'; +import { LockEditor, UnlockEditor } from '../../../wizard/action/wizard.action'; +import { takeUntil } from 'rxjs/operators'; @Component({ selector: 'restore-edit-step', @@ -20,11 +20,19 @@ import { UpdateRestorationChangesRequest } from '../action/restore.action'; styleUrls: [] }) -export class RestoreEditStepComponent { +export class RestoreEditStepComponent implements OnDestroy { + + private ngUnsubscribe: Subject = new Subject(); + + readonly lockChange$: Subject = new Subject(); + readonly statusChange$: Subject = new Subject(); definition$: Observable> = this.store.select(getWizardDefinition); schema$: Observable = this.store.select(getSchema); model$: Observable = this.store.select(getFormattedModel); + step$: Observable = this.store.select(getCurrent); + + lockable$: Observable = this.step$.pipe(map(step => step.locked)); validators$: Observable; @@ -40,29 +48,31 @@ export class RestoreEditStepComponent { switchMap(selections => this.store.select(getValidators(selections))) ); - this.store.select(getRestorationChanges).subscribe(console.log); + this.step$ + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe(s => this.lockChange$.next(s && s.locked ? true : false)); + + this.lockChange$ + .pipe(takeUntil(this.ngUnsubscribe)) + .subscribe(locked => this.store.dispatch(locked ? new LockEditor() : new UnlockEditor())); + + this.statusChange$ + .pipe( + takeUntil(this.ngUnsubscribe), + withLatestFrom(this.store.select(getWizardIndex)) + ) + .subscribe(([errors, currentPage]) => { + const status = { [currentPage]: !(errors.value) ? 'VALID' : 'INVALID' }; + this.store.dispatch(new UpdateRestoreFormStatus(status)); + }); } onChange(changes: any): void { this.store.dispatch(new UpdateRestorationChangesRequest(changes)); } -} -/* -this.valueChangeEmitted$.pipe( - map(changes => changes.value), - withLatestFrom(this.definition$, this.store.select(fromProvider.getSelectedProvider)), - filter(([changes, definition, provider]) => definition && changes && provider), - map(([changes, definition, provider]) => { - const parsed = definition.parser(changes); - return ({ - ...parsed, - metadataFilters: [ - ...provider.metadataFilters, - ...(parsed.metadataFilters || []) - ] - }); - }) - ) - .subscribe(changes => this.store.dispatch(new UpdateProvider(changes))); -*/ + ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + } +} diff --git a/ui/src/app/metadata/configuration/container/restore-edit.component.html b/ui/src/app/metadata/configuration/container/restore-edit.component.html index 6199dc016..7b1569f9a 100644 --- a/ui/src/app/metadata/configuration/container/restore-edit.component.html +++ b/ui/src/app/metadata/configuration/container/restore-edit.component.html @@ -4,14 +4,6 @@ - - -
@@ -46,15 +38,8 @@
diff --git a/ui/src/app/metadata/configuration/container/restore-edit.component.ts b/ui/src/app/metadata/configuration/container/restore-edit.component.ts index a50c0a52b..7bd939ee4 100644 --- a/ui/src/app/metadata/configuration/container/restore-edit.component.ts +++ b/ui/src/app/metadata/configuration/container/restore-edit.component.ts @@ -2,12 +2,17 @@ import { Component } from '@angular/core'; import { Observable, of } from 'rxjs'; import { Store } from '@ngrx/store'; import { - ConfigurationState, getConfigurationModelKind + ConfigurationState, + getConfigurationModelKind, + getRestorationIsSaving, + getRestorationIsValid, + getInvalidRestorationForms } from '../../configuration/reducer'; import { RestoreVersionRequest, CancelRestore } from '../action/restore.action'; import { NAV_FORMATS } from '../../domain/component/editor-nav.component'; import { Metadata } from '../../domain/domain.type'; import { getVersionModel } from '../reducer'; +import { map } from 'rxjs/operators'; @Component({ @@ -21,10 +26,9 @@ export class RestoreEditComponent { model$: Observable = this.store.select(getVersionModel); kind$: Observable = this.store.select(getConfigurationModelKind); - isInvalid$: Observable = of(false); - canFilter$: Observable = of(false); - status$: Observable = of('VALID'); - isSaving$: Observable = of(false); + isInvalid$: Observable = this.store.select(getRestorationIsValid).pipe(map(v => !v)); + status$: Observable = this.store.select(getInvalidRestorationForms); + isSaving$: Observable = this.store.select(getRestorationIsSaving); validators$: Observable; @@ -32,7 +36,9 @@ export class RestoreEditComponent { constructor( private store: Store - ) {} + ) { + // this.status$.subscribe(console.log); + } save() { this.store.dispatch(new RestoreVersionRequest()); diff --git a/ui/src/app/metadata/configuration/container/restore.component.spec.ts b/ui/src/app/metadata/configuration/container/restore.component.spec.ts index e79cbcac9..ef3b0f3fa 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.spec.ts @@ -11,6 +11,7 @@ import { MockI18nModule } from '../../../../testing/i18n.stub'; import { RestoreComponent } from './restore.component'; import { of } from 'rxjs'; import { DatePipe } from '@angular/common'; +import { Router } from '@angular/router'; @Component({ template: ` @@ -28,6 +29,7 @@ describe('Metadata Restore Page Component', () => { let instance: TestHostComponent; let app: RestoreComponent; let store: Store; + let router: Router; beforeEach(async(() => { TestBed.configureTestingModule({ @@ -51,6 +53,7 @@ describe('Metadata Restore Page Component', () => { }).compileComponents(); store = TestBed.get(Store); + router = TestBed.get(Router); spyOn(store, 'dispatch'); spyOn(store, 'select').and.callFake(() => of(new Date().toDateString())); @@ -62,15 +65,15 @@ describe('Metadata Restore Page Component', () => { it('should load metadata objects', async(() => { expect(app).toBeTruthy(); - expect(store.select).toHaveBeenCalledTimes(4); + expect(store.select).toHaveBeenCalledTimes(1); expect(store.dispatch).not.toHaveBeenCalled(); })); describe('restore method', () => { - it('should emit a value from the restore subject', () => { - spyOn(app.subj, 'next').and.callThrough(); + it('should navigate to the restore edit page', () => { + spyOn(router, 'navigate').and.callThrough(); app.restore(); - expect(app.subj.next).toHaveBeenCalled(); + expect(router.navigate).toHaveBeenCalled(); }); }); }); diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts index b34cfe997..7be47103e 100644 --- a/ui/src/app/metadata/configuration/effect/restore.effect.ts +++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts @@ -9,7 +9,8 @@ import { RestoreVersionError, CancelRestore, UpdateRestorationChangesRequest, - UpdateRestorationChangesSuccess + UpdateRestorationChangesSuccess, + SetSavingStatus } from '../action/restore.action'; import { MetadataHistoryService } from '../service/history.service'; import { of } from 'rxjs'; @@ -28,6 +29,7 @@ import { } from '../reducer'; import { SetMetadata } from '../action/configuration.action'; import { removeNulls } from '../../../shared/util'; +import { getModel } from '../../../wizard/reducer'; @Injectable() @@ -53,6 +55,24 @@ export class RestoreEffects { ) ); + @Effect() + restoreVersionSaving$ = this.actions$.pipe( + ofType(RestoreActionTypes.RESTORE_VERSION_REQUEST), + map(() => new SetSavingStatus(true)) + ); + + @Effect() + restoreVersionSaved$ = this.actions$.pipe( + ofType(RestoreActionTypes.RESTORE_VERSION_SUCCESS), + map(() => new SetSavingStatus(false)) + ); + + @Effect() + restoreVersionError$ = this.actions$.pipe( + ofType(RestoreActionTypes.RESTORE_VERSION_ERROR), + map(() => new SetSavingStatus(false)) + ); + @Effect({ dispatch: false }) restoreVersionCancel$ = this.actions$.pipe( ofType(RestoreActionTypes.CANCEL_RESTORE), diff --git a/ui/src/app/metadata/configuration/reducer/configuration.reducer.spec.ts b/ui/src/app/metadata/configuration/reducer/configuration.reducer.spec.ts index a0dd05d58..714da74b4 100644 --- a/ui/src/app/metadata/configuration/reducer/configuration.reducer.spec.ts +++ b/ui/src/app/metadata/configuration/reducer/configuration.reducer.spec.ts @@ -27,7 +27,7 @@ describe('Configuration Reducer', () => { describe('SET_DEFINITION action', () => { it('should set the state definition', () => { - const action = new actions.SetDefinition(definition); + const action = new actions.SetConfigurationDefinition(definition); const result = reducer(initialState, action); expect(result).toEqual({ ...initialState, definition }); @@ -36,7 +36,7 @@ describe('Configuration Reducer', () => { describe('SET_SCHEMA action', () => { it('should set the state schema', () => { - const action = new actions.SetSchema(schema); + const action = new actions.SetConfigurationSchema(schema); const result = reducer(initialState, action); expect(result).toEqual({ ...initialState, schema }); diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index fe961cfc3..b64b6e070 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -9,7 +9,7 @@ import * as fromRestore from './restore.reducer'; import { WizardStep } from '../../../wizard/model'; import * as utils from '../../domain/utility/configuration'; -import { getSplitSchema } from '../../../wizard/reducer'; +import { getSplitSchema, getModel } from '../../../wizard/reducer'; import { getInCollectionFn } from '../../domain/domain.util'; import { MetadataConfiguration } from '../model/metadata-configuration'; import { Metadata } from '../../domain/domain.type'; @@ -192,13 +192,34 @@ export const getInvalidRestorationForms = createSelector(getRestoreState, fromRe export const getFormattedModel = createSelector( getVersionModel, getConfigurationDefinition, - (model, definition) => definition.formatter(model) + (model, definition) => definition ? definition.formatter(model) : null +); + +export const getFormattedChanges = createSelector( + getRestorationChanges, + getConfigurationDefinition, + (model, definition) => definition ? definition.formatter(model) : null +); + +export const getFormattedModelWithChanges = createSelector( + getVersionModel, + getRestorationChanges, + getConfigurationDefinition, + (model, changes, definition) => definition ? definition.formatter({ + ...model, + ...changes + }) : null ); export const getRestorationModel = createSelector( getVersionModel, getRestorationChanges, - (model, changes) => model + getModel, + (model, changes, empty) => ({ + ...model, + ...empty, + ...changes + }) ); // Mixed states @@ -229,4 +250,4 @@ export const getConfigurationModelType = createSelector(getConfigurationModel, g export const getConfigurationHasXml = createSelector(getConfigurationXml, xml => !!xml); export const getConfigurationFilters = createSelector(getConfigurationModel, model => model.metadataFilters); -export const getConfigurationVersionDate = createSelector(getRestoreModel, version => version && version.modifiedDate); +export const getConfigurationVersionDate = createSelector(getVersionModel, version => version && version.modifiedDate); diff --git a/ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts b/ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts new file mode 100644 index 000000000..9aa588b98 --- /dev/null +++ b/ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts @@ -0,0 +1,34 @@ +import { reducer } from './restore.reducer'; +import * as fromRestore from './restore.reducer'; +import * as actions from '../action/restore.action'; + +describe('Restore Reducer', () => { + + const baseState = fromRestore.initialState; + + describe('undefined action', () => { + it('should return the default state', () => { + const result = reducer(undefined, {} as any); + + expect(result).toEqual(fromRestore.initialState); + }); + }); + + describe('SET_HISTORY action', () => { + it('should set the state metadata model', () => { + const serviceEnabled = true; + const action = new actions.UpdateRestorationChangesSuccess({ serviceEnabled }); + const result = reducer(fromRestore.initialState, action); + + expect(Object.keys(result.changes)).toEqual({serviceEnabled: true}); + }); + }); + + describe('selector function', () => { + describe('getChanges', () => { + it('should return the selected version id', () => { + expect(fromRestore.getChanges({ ...baseState, serviceEnabled: false })).toEqual({ serviceEnabled: false }); + }); + }); + }); +}); diff --git a/ui/src/app/metadata/configuration/reducer/restore.reducer.ts b/ui/src/app/metadata/configuration/reducer/restore.reducer.ts index 4ca626459..f2e000728 100644 --- a/ui/src/app/metadata/configuration/reducer/restore.reducer.ts +++ b/ui/src/app/metadata/configuration/reducer/restore.reducer.ts @@ -23,6 +23,20 @@ export function reducer(state = initialState, action: RestoreActionsUnion): Rest ...action.payload } }; + case RestoreActionTypes.SET_SAVING_STATUS: + return { + ...state, + saving: action.payload + }; + case RestoreActionTypes.UPDATE_STATUS: { + return { + ...state, + status: { + ...state.status, + ...action.payload + } + }; + } default: { return state; } diff --git a/ui/src/app/metadata/configuration/service/history.service.spec.ts b/ui/src/app/metadata/configuration/service/history.service.spec.ts index 0927f8e70..e4fa72dac 100644 --- a/ui/src/app/metadata/configuration/service/history.service.spec.ts +++ b/ui/src/app/metadata/configuration/service/history.service.spec.ts @@ -80,21 +80,4 @@ describe(`Attributes Service`, () => { } ))); }); - - describe('restoreVersion method', () => { - it(`should send a put request`, async(inject([MetadataHistoryService, HttpTestingController], - (service: MetadataHistoryService, backend: HttpTestingController) => { - const resourceId = 'foo'; - const type = 'resource'; - const versionId = '1'; - const response = {version: 'bar'} as Metadata; - spyOn(service, 'getVersions').and.returnValue(of([response, { ...response, version: 'foo' }])); - spyOn(service, 'updateVersion').and.returnValue(of(response)); - service.restoreVersion(resourceId, type, versionId).subscribe(updated => { - expect(service.getVersions).toHaveBeenCalled(); - expect(service.updateVersion).toHaveBeenCalled(); - }); - } - ))); - }); }); diff --git a/ui/src/app/metadata/configuration/service/history.service.ts b/ui/src/app/metadata/configuration/service/history.service.ts index 4f480f916..9d3edc212 100644 --- a/ui/src/app/metadata/configuration/service/history.service.ts +++ b/ui/src/app/metadata/configuration/service/history.service.ts @@ -32,7 +32,7 @@ export class MetadataHistoryService { )); } - getVersion(resourceId: string, type: string, versionId: string): Observable { + getVersion(resourceId: string, type: string, versionId: string = null): Observable { const api = versionId ? `/${this.base}/${PATHS[type]}/${resourceId}/${this.path}/${versionId}` : diff --git a/ui/src/app/metadata/domain/component/wizard-summary.component.spec.ts b/ui/src/app/metadata/domain/component/wizard-summary.component.spec.ts index 107ef7241..ecd51b1b9 100644 --- a/ui/src/app/metadata/domain/component/wizard-summary.component.spec.ts +++ b/ui/src/app/metadata/domain/component/wizard-summary.component.spec.ts @@ -12,6 +12,8 @@ import { SummaryPropertyComponent } from './summary-property.component'; import { SCHEMA } from '../../../../testing/form-schema.stub'; import { MockI18nModule } from '../../../../testing/i18n.stub'; import { MetadataProviderWizard } from '../../provider/model'; +import { AttributesService } from '../service/attributes.service'; +import { MockAttributeService } from '../../../../testing/attributes.stub'; @Component({ template: ` @@ -54,7 +56,8 @@ describe('Provider Wizard Summary Component', () => { TestHostComponent ], providers: [ - { provide: WidgetRegistry, useClass: DefaultWidgetRegistry } + { provide: WidgetRegistry, useClass: DefaultWidgetRegistry }, + { provide: AttributesService, useClass: MockAttributeService } ] }).compileComponents(); diff --git a/ui/src/app/metadata/domain/component/wizard-summary.component.ts b/ui/src/app/metadata/domain/component/wizard-summary.component.ts index 82cd86c8d..80f246c83 100644 --- a/ui/src/app/metadata/domain/component/wizard-summary.component.ts +++ b/ui/src/app/metadata/domain/component/wizard-summary.component.ts @@ -43,16 +43,19 @@ export class WizardSummaryComponent implements OnChanges { .filter(step => step.id !== 'summary') .map( (step: WizardStep, num: number) => { + const { id, index, label } = step; + const split = getSplitSchema(schema, step); + const properties = getStepProperties( + split, + def.formatter(model), + schema.definitions || {} + ); return ({ - id: step.id, + id, pageNumber: num + 1, - index: step.index, - label: step.label, - properties: getStepProperties( - getSplitSchema(schema, step), - def.formatter(model), - schema.definitions || {} - ) + index, + label, + properties }); } ); 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 be79bb1fa..c869c8e9c 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 @@ -1,6 +1,7 @@ import { Component, OnDestroy } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import { Store } from '@ngrx/store'; +import { FormControl } from '@angular/forms'; import * as fromProvider from '../reducer'; import { UpdateStatus } from '../action/editor.action'; @@ -9,9 +10,8 @@ import { MetadataProvider } from '../../domain/model'; import { LockEditor, UnlockEditor } from '../../../wizard/action/wizard.action'; import * as fromWizard from '../../../wizard/reducer'; -import { withLatestFrom, map, skipWhile, distinctUntilChanged, takeUntil, filter } from 'rxjs/operators'; +import { withLatestFrom, map, distinctUntilChanged, filter } from 'rxjs/operators'; import { UpdateProvider } from '../action/entity.action'; -import { FormControl } from '@angular/forms'; @Component({ selector: 'provider-edit-step', 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 645e21f23..1fce5263d 100644 --- a/ui/src/app/metadata/provider/model/base.provider.form.ts +++ b/ui/src/app/metadata/provider/model/base.provider.form.ts @@ -1,12 +1,12 @@ import { Wizard } from '../../../wizard/model'; import { BaseMetadataProvider } from '../../domain/model/providers'; -import { getProviderNames, getProviderXmlIds } from '../reducer'; +import { getFilteredProviderNames, getFilteredProviderXmlIds } from '../reducer'; export const BaseMetadataProviderEditor: Wizard = { label: 'BaseMetadataProvider', type: 'BaseMetadataResolver', schema: '', - validatorParams: [getProviderNames, getProviderXmlIds], + validatorParams: [getFilteredProviderNames, getFilteredProviderXmlIds], getValidators(namesList: string[], xmlIdList: string[]): any { const validators = { '/': (value, property, form_current) => { diff --git a/ui/src/app/metadata/provider/reducer/index.ts b/ui/src/app/metadata/provider/reducer/index.ts index d40313299..562a4ab44 100644 --- a/ui/src/app/metadata/provider/reducer/index.ts +++ b/ui/src/app/metadata/provider/reducer/index.ts @@ -66,9 +66,19 @@ export const getProviderIds = createSelector(getCollectionState, fromCollection. export const getProviderCollectionIsLoaded = createSelector(getCollectionState, fromCollection.getIsLoaded); export const getProviderNames = createSelector(getAllProviders, (providers: MetadataProvider[]) => providers.map(p => p.name)); +export const getFilteredProviderNames = createSelector( + getProviderNames, + getSelectedProvider, (names, provider) => names.filter(name => name !== provider.name) +); + export const getProviderFilters = createSelector(getSelectedProvider, provider => provider.metadataFilters); export const getProviderXmlIds = createSelector(getAllProviders, (providers: MetadataProvider[]) => providers.map(p => p.xmlId)); export const getOrderedProviders = createSelector(getAllProviders, getProviderOrder, utils.mergeOrderFn); export const getOrderedProvidersInSearch = createSelector(getAllProviders, getProviderOrder, utils.mergeOrderFn); + +export const getFilteredProviderXmlIds = createSelector( + getProviderXmlIds, + getSelectedProvider, (ids, provider) => ids.filter(id => id !== provider.xmlId) +); diff --git a/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts b/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts index ffabac236..56848ad5e 100644 --- a/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts +++ b/ui/src/app/metadata/resolver/container/resolver-wizard.component.ts @@ -117,13 +117,13 @@ export class ResolverWizardComponent implements OnDestroy, CanComponentDeactivat this.summary$ = combine( this.store.select(fromWizard.getWizardDefinition), - this.store.select(fromWizard.getSchema), - this.store.select(fromResolver.getEntityChanges) + this.store.select(fromWizard.getSchemaObject), + this.store.select(fromResolver.getDraftModelWithChanges) ).pipe( map(([definition, schema, model]) => ( { definition, - schema, + schema: schema || {}, model } )) diff --git a/ui/src/app/metadata/resolver/reducer/index.ts b/ui/src/app/metadata/resolver/reducer/index.ts index 43bc2fbc7..a1cc5256d 100644 --- a/ui/src/app/metadata/resolver/reducer/index.ts +++ b/ui/src/app/metadata/resolver/reducer/index.ts @@ -5,6 +5,7 @@ import * as fromSearch from './search.reducer'; import * as fromCopy from './copy.reducer'; import * as fromDraft from './draft.reducer'; import * as fromCollection from './collection.reducer'; +import * as fromWizard from '../../../wizard/reducer'; import { combineAllFn, getEntityIdsFn, getInCollectionFn, doesExistFn } from '../../domain/domain.util'; @@ -109,3 +110,17 @@ export const getAllOtherIds = createSelector( getSelectedResolverId, (ids, selected) => ids.filter(id => id !== selected) ); + +export const getDraftModelWithChanges = createSelector( + fromWizard.getSchema, + fromWizard.getModel, + getSelectedDraft, + getEntityChanges, + fromWizard.getWizardDefinition, + (schema, wizardModel, selectedDraft, changes, definition) => definition.formatter({ + ...wizardModel, + ...selectedDraft, + ...changes + }) +); + diff --git a/ui/src/app/wizard/model/form-definition.ts b/ui/src/app/wizard/model/form-definition.ts index 4402aa354..bde8e10ad 100644 --- a/ui/src/app/wizard/model/form-definition.ts +++ b/ui/src/app/wizard/model/form-definition.ts @@ -5,7 +5,7 @@ export interface FormDefinition { type: string; schema: string; bindings?: any; - validatorParams: any[]; + validatorParams: any; getEntity?(entity: any): any; parser(changes: Partial, schema?: any); formatter(changes: Partial, schema?: any); diff --git a/ui/src/app/wizard/reducer/wizard.reducer.ts b/ui/src/app/wizard/reducer/wizard.reducer.ts index 11205afb3..819a82533 100644 --- a/ui/src/app/wizard/reducer/wizard.reducer.ts +++ b/ui/src/app/wizard/reducer/wizard.reducer.ts @@ -5,7 +5,6 @@ export interface State { index: string; disabled: boolean; definition: Wizard; - schemaPath: string; loading: boolean; schema: any; From 51bfa3c688dbd7f838a3642b4e3efbafd244f120 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 30 Aug 2019 12:20:13 -0700 Subject: [PATCH 06/11] SHIBUI-1385 Added unit tests --- .../configuration/action/restore.action.ts | 4 +- .../container/configuration.component.spec.ts | 8 +- .../restore-edit-step.component.spec.ts | 107 +++++++++++++++++ .../container/restore-edit-step.component.ts | 28 +++-- .../container/restore-edit.component.spec.ts | 79 +++++++++++++ .../version-options.component.spec.ts | 82 +++++++++++++ .../container/version.component.spec.ts | 56 +++++++++ .../reducer/restore.reducer.spec.ts | 74 +++++++++++- .../configuration/reducer/restore.reducer.ts | 2 +- .../reducer/version.reducer.spec.ts | 108 ++++++++++++++++++ 10 files changed, 522 insertions(+), 26 deletions(-) create mode 100644 ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts create mode 100644 ui/src/app/metadata/configuration/container/restore-edit.component.spec.ts create mode 100644 ui/src/app/metadata/configuration/container/version-options.component.spec.ts create mode 100644 ui/src/app/metadata/configuration/container/version.component.spec.ts diff --git a/ui/src/app/metadata/configuration/action/restore.action.ts b/ui/src/app/metadata/configuration/action/restore.action.ts index 0407e830b..978e9eee4 100644 --- a/ui/src/app/metadata/configuration/action/restore.action.ts +++ b/ui/src/app/metadata/configuration/action/restore.action.ts @@ -34,12 +34,12 @@ export class RestoreVersionError implements Action { export class UpdateRestorationChangesRequest implements Action { readonly type = RestoreActionTypes.UPDATE_RESTORATION_REQUEST; - constructor(public payload: any) { } + constructor(public payload: Partial) { } } export class UpdateRestorationChangesSuccess implements Action { readonly type = RestoreActionTypes.UPDATE_RESTORATION_SUCCESS; - constructor(public payload: any) { } + constructor(public payload: Partial) { } } export class UpdateRestoreFormStatus implements Action { diff --git a/ui/src/app/metadata/configuration/container/configuration.component.spec.ts b/ui/src/app/metadata/configuration/container/configuration.component.spec.ts index 71bb05ab0..b1d9bc2f2 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/configuration.component.spec.ts @@ -1,10 +1,9 @@ -import { Component, ViewChild, Input } from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; import { TestBed, async, ComponentFixture } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; import { StoreModule, combineReducers } from '@ngrx/store'; import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; -import { MetadataConfiguration } from '../model/metadata-configuration'; import { ConfigurationComponent } from './configuration.component'; import * as fromConfiguration from '../reducer'; import * as fromProviders from '../../provider/reducer'; @@ -19,11 +18,6 @@ import { MockI18nModule } from '../../../../testing/i18n.stub'; class TestHostComponent { @ViewChild(ConfigurationComponent) public componentUnderTest: ConfigurationComponent; - - configuration: MetadataConfiguration = { - dates: [], - sections: [] - }; } describe('Metadata Configuration Page Component', () => { diff --git a/ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts b/ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts new file mode 100644 index 000000000..7d468b5af --- /dev/null +++ b/ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts @@ -0,0 +1,107 @@ +import { Component, ViewChild, NO_ERRORS_SCHEMA } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule, combineReducers, Store } from '@ngrx/store'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; + +import { RestoreEditStepComponent } from './restore-edit-step.component'; +import * as fromConfiguration from '../reducer'; +import * as fromProviders from '../../provider/reducer'; +import * as fromResolvers from '../../resolver/reducer'; +import * as fromWizard from '../../../wizard/reducer'; +import { MockI18nModule } from '../../../../testing/i18n.stub'; + +import { + RestoreActionTypes +} from '../action/restore.action'; +import { WizardActionTypes } from '../../../wizard/action/wizard.action'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(RestoreEditStepComponent) + public componentUnderTest: RestoreEditStepComponent; +} + +describe('Restore Version Edit Step Component', () => { + + let fixture: ComponentFixture; + let instance: TestHostComponent; + let app: RestoreEditStepComponent; + let store: Store; + let dispatchSpy; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + StoreModule.forRoot({ + 'metadata-configuration': combineReducers(fromConfiguration.reducers), + 'provider': combineReducers(fromProviders.reducers), + 'resolver': combineReducers(fromResolvers.reducers), + 'wizard': combineReducers(fromWizard.reducers) + }), + MockI18nModule, + RouterTestingModule + ], + declarations: [ + RestoreEditStepComponent, + TestHostComponent + ], + schemas: [ + NO_ERRORS_SCHEMA + ] + }).compileComponents(); + + store = TestBed.get(Store); + dispatchSpy = spyOn(store, 'dispatch'); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should compile', () => { + expect(app).toBeTruthy(); + }); + + describe('onChange', () => { + it('should dispatch an update changes event', () => { + app.onChange({ name: 'test' }); + expect(store.dispatch).toHaveBeenCalled(); + expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(RestoreActionTypes.UPDATE_RESTORATION_REQUEST); + }); + }); + + describe('updateStatus', () => { + it('should dispatch an update form status event', () => { + app.updateStatus([{ value: 'foo' }, 'common']); + expect(store.dispatch).toHaveBeenCalled(); + expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(RestoreActionTypes.UPDATE_STATUS); + }); + + it('should dispatch an update form status event', () => { + app.updateStatus([{}, 'common']); + expect(store.dispatch).toHaveBeenCalled(); + expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(RestoreActionTypes.UPDATE_STATUS); + }); + }); + + describe('updateLock', () => { + it('should dispatch a LockEditor event when passed a locked status', () => { + app.updateLock(true); + expect(store.dispatch).toHaveBeenCalled(); + expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(WizardActionTypes.LOCK); + }); + + it('should dispatch a UnlockEditor event when passed a locked status', () => { + app.updateLock(false); + expect(store.dispatch).toHaveBeenCalled(); + expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(WizardActionTypes.UNLOCK); + }); + }); +}); diff --git a/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts b/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts index 3d93f5ebd..904cf43f6 100644 --- a/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts +++ b/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts @@ -8,7 +8,7 @@ import { import { getWizardDefinition, getSchema, getValidators, getCurrent, getWizardIndex } from '../../../wizard/reducer'; import { Wizard, WizardStep } from '../../../wizard/model'; import { Metadata } from '../../domain/domain.type'; -import { map, switchMap, withLatestFrom } from 'rxjs/operators'; +import { map, switchMap, withLatestFrom, filter } from 'rxjs/operators'; import { NAV_FORMATS } from '../../domain/component/editor-nav.component'; import { UpdateRestorationChangesRequest, UpdateRestoreFormStatus } from '../action/restore.action'; import { LockEditor, UnlockEditor } from '../../../wizard/action/wizard.action'; @@ -32,45 +32,51 @@ export class RestoreEditStepComponent implements OnDestroy { model$: Observable = this.store.select(getFormattedModel); step$: Observable = this.store.select(getCurrent); - lockable$: Observable = this.step$.pipe(map(step => step.locked)); + lockable$: Observable = this.step$.pipe(filter(s => !!s), map(step => step.locked)); validators$: Observable; formats = NAV_FORMATS; constructor( - private store: Store, - private route: ActivatedRoute + private store: Store ) { this.validators$ = this.definition$.pipe( + filter(def => !!def), map(def => def.validatorParams), switchMap(params => combineLatest(params.map(p => this.store.select(p)))), switchMap(selections => this.store.select(getValidators(selections))) ); this.step$ - .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe(s => this.lockChange$.next(s && s.locked ? true : false)); + .pipe(takeUntil(this.ngUnsubscribe), filter(step => !!step)) + .subscribe(s => this.lockChange$.next(s.locked ? true : false)); this.lockChange$ .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe(locked => this.store.dispatch(locked ? new LockEditor() : new UnlockEditor())); + .subscribe(this.updateLock); this.statusChange$ .pipe( takeUntil(this.ngUnsubscribe), withLatestFrom(this.store.select(getWizardIndex)) ) - .subscribe(([errors, currentPage]) => { - const status = { [currentPage]: !(errors.value) ? 'VALID' : 'INVALID' }; - this.store.dispatch(new UpdateRestoreFormStatus(status)); - }); + .subscribe(this.updateStatus); } onChange(changes: any): void { this.store.dispatch(new UpdateRestorationChangesRequest(changes)); } + updateStatus([errors, currentPage]) { + const status = { [currentPage]: !(errors.value) ? 'VALID' : 'INVALID' }; + this.store.dispatch(new UpdateRestoreFormStatus(status)); + } + + updateLock(locked: boolean) { + this.store.dispatch(locked ? new LockEditor() : new UnlockEditor()); + } + ngOnDestroy() { this.ngUnsubscribe.next(); this.ngUnsubscribe.complete(); diff --git a/ui/src/app/metadata/configuration/container/restore-edit.component.spec.ts b/ui/src/app/metadata/configuration/container/restore-edit.component.spec.ts new file mode 100644 index 000000000..847ab5623 --- /dev/null +++ b/ui/src/app/metadata/configuration/container/restore-edit.component.spec.ts @@ -0,0 +1,79 @@ +import { Component, ViewChild, NO_ERRORS_SCHEMA } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule, combineReducers, Store } from '@ngrx/store'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; + +import { RestoreEditComponent } from './restore-edit.component'; +import * as fromConfiguration from '../reducer'; +import * as fromProviders from '../../provider/reducer'; +import * as fromResolvers from '../../resolver/reducer'; +import { MockI18nModule } from '../../../../testing/i18n.stub'; +import { RestoreActionTypes } from '../action/restore.action'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(RestoreEditComponent) + public componentUnderTest: RestoreEditComponent; +} + +describe('Restore Version Edit Component', () => { + + let fixture: ComponentFixture; + let instance: TestHostComponent; + let app: RestoreEditComponent; + let store: Store; + let dispatchSpy; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + StoreModule.forRoot({ + 'metadata-configuration': combineReducers(fromConfiguration.reducers), + 'provider': combineReducers(fromProviders.reducers), + 'resolver': combineReducers(fromResolvers.reducers) + }), + MockI18nModule, + RouterTestingModule + ], + declarations: [ + RestoreEditComponent, + TestHostComponent + ], + schemas: [ NO_ERRORS_SCHEMA ] + }).compileComponents(); + + store = TestBed.get(Store); + dispatchSpy = spyOn(store, 'dispatch'); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should compile', () => { + expect(app).toBeTruthy(); + }); + + describe('save', () => { + it('should dispatch a save request event', () => { + app.save(); + expect(store.dispatch).toHaveBeenCalled(); + expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(RestoreActionTypes.RESTORE_VERSION_REQUEST); + }); + }); + + describe('cancel', () => { + it('should dispatch a cancel request event', () => { + app.cancel(); + expect(store.dispatch).toHaveBeenCalled(); + expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(RestoreActionTypes.CANCEL_RESTORE); + }); + }); +}); diff --git a/ui/src/app/metadata/configuration/container/version-options.component.spec.ts b/ui/src/app/metadata/configuration/container/version-options.component.spec.ts new file mode 100644 index 000000000..86f78f839 --- /dev/null +++ b/ui/src/app/metadata/configuration/container/version-options.component.spec.ts @@ -0,0 +1,82 @@ +import { Component, ViewChild, NO_ERRORS_SCHEMA } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule, combineReducers } from '@ngrx/store'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; + +import { VersionOptionsComponent } from './version-options.component'; +import * as fromConfiguration from '../reducer'; +import * as fromProviders from '../../provider/reducer'; +import * as fromResolvers from '../../resolver/reducer'; +import { MockI18nModule } from '../../../../testing/i18n.stub'; +import { Metadata } from '../../domain/domain.type'; +import { ViewportScroller } from '@angular/common'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(VersionOptionsComponent) + public componentUnderTest: VersionOptionsComponent; +} + +describe('Metadata Version Options Page Component', () => { + + let fixture: ComponentFixture; + let instance: TestHostComponent; + let app: VersionOptionsComponent; + let scroller: ViewportScroller; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + StoreModule.forRoot({ + 'metadata-configuration': combineReducers(fromConfiguration.reducers), + 'provider': combineReducers(fromProviders.reducers), + 'resolver': combineReducers(fromResolvers.reducers) + }), + MockI18nModule, + RouterTestingModule + ], + declarations: [ + VersionOptionsComponent, + TestHostComponent + ], + schemas: [ NO_ERRORS_SCHEMA ] + }).compileComponents(); + + scroller = TestBed.get(ViewportScroller); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should compile', () => { + expect(app).toBeTruthy(); + }); + + describe('setModel method', () => { + it('should set attributes based on the passed data', () => { + app.setModel({ id: 'foo', '@type': 'bar' } as Metadata); + expect(app.id).toBe('foo'); + expect(app.kind).toBe('provider'); + + app.setModel({ resourceId: 'baz' } as Metadata); + expect(app.id).toBe('baz'); + expect(app.kind).toBe('resolver'); + }); + }); + + describe('onScrollTo method', () => { + it('should set attributes based on the passed data', () => { + spyOn(scroller, 'scrollToAnchor'); + app.onScrollTo('foo'); + expect(scroller.scrollToAnchor).toHaveBeenCalledWith('foo'); + }); + }); +}); diff --git a/ui/src/app/metadata/configuration/container/version.component.spec.ts b/ui/src/app/metadata/configuration/container/version.component.spec.ts new file mode 100644 index 000000000..ae745f1b4 --- /dev/null +++ b/ui/src/app/metadata/configuration/container/version.component.spec.ts @@ -0,0 +1,56 @@ +import { Component, ViewChild } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule, combineReducers } from '@ngrx/store'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; + +import { VersionComponent } from './version.component'; +import * as fromConfiguration from '../reducer'; +import * as fromProviders from '../../provider/reducer'; +import * as fromResolvers from '../../resolver/reducer'; +import { MockI18nModule } from '../../../../testing/i18n.stub'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(VersionComponent) + public componentUnderTest: VersionComponent; +} + +describe('Metadata Version Page Component', () => { + + let fixture: ComponentFixture; + let instance: TestHostComponent; + let app: VersionComponent; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + StoreModule.forRoot({ + 'metadata-configuration': combineReducers(fromConfiguration.reducers), + 'provider': combineReducers(fromProviders.reducers), + 'resolver': combineReducers(fromResolvers.reducers) + }), + MockI18nModule, + RouterTestingModule + ], + declarations: [ + VersionComponent, + TestHostComponent + ], + }).compileComponents(); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should compile', () => { + expect(app).toBeTruthy(); + }); +}); diff --git a/ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts b/ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts index 9aa588b98..97795549b 100644 --- a/ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts +++ b/ui/src/app/metadata/configuration/reducer/restore.reducer.spec.ts @@ -1,6 +1,13 @@ import { reducer } from './restore.reducer'; import * as fromRestore from './restore.reducer'; -import * as actions from '../action/restore.action'; +import { + RestoreActionTypes, + RestoreActionsUnion, + UpdateRestorationChangesSuccess, + SetSavingStatus, + UpdateRestoreFormStatus +} from '../action/restore.action'; +import { Metadata } from '../../domain/domain.type'; describe('Restore Reducer', () => { @@ -14,20 +21,77 @@ describe('Restore Reducer', () => { }); }); - describe('SET_HISTORY action', () => { + describe(`${RestoreActionTypes.UPDATE_RESTORATION_SUCCESS} action`, () => { it('should set the state metadata model', () => { const serviceEnabled = true; - const action = new actions.UpdateRestorationChangesSuccess({ serviceEnabled }); + const action = new UpdateRestorationChangesSuccess({ serviceEnabled }); const result = reducer(fromRestore.initialState, action); - expect(Object.keys(result.changes)).toEqual({serviceEnabled: true}); + expect(result.changes).toEqual({serviceEnabled} as Metadata); + }); + }); + + describe(`${RestoreActionTypes.SET_SAVING_STATUS} action`, () => { + it('should set the state saving status', () => { + const action = new SetSavingStatus(true); + const result = reducer(fromRestore.initialState, action); + + expect(result.saving).toBe(true); + }); + }); + + describe(`${RestoreActionTypes.UPDATE_STATUS} action`, () => { + it('should set the state saving status', () => { + const action = new UpdateRestoreFormStatus({foo: 'INVALID'}); + const result = reducer(fromRestore.initialState, action); + + expect(result.status.foo).toBe('INVALID'); }); }); describe('selector function', () => { describe('getChanges', () => { it('should return the selected version id', () => { - expect(fromRestore.getChanges({ ...baseState, serviceEnabled: false })).toEqual({ serviceEnabled: false }); + expect(fromRestore.getChanges({ ...baseState, changes: { serviceEnabled: false } })).toEqual({ serviceEnabled: false }); + }); + }); + + describe('isRestorationSaved', () => { + it('should return false if there are outstanding changes', () => { + expect(fromRestore.isRestorationSaved({ ...baseState, changes: { name: 'too' } })).toBe(false); + }); + + it('should return true if there are no outstanding changes', () => { + expect(fromRestore.isRestorationSaved({ ...baseState, changes: {} })).toBe(true); + }); + }); + + describe('getFormStatus', () => { + it('should return the current form status', () => { + expect(fromRestore.getFormStatus({ ...baseState, status: { common: 'INVALID' } })).toEqual({ common: 'INVALID' }); + }); + }); + + describe('isRestorationSaving', () => { + it('should return the saving status', () => { + expect(fromRestore.isRestorationSaving({ ...baseState })).toBe(false); + expect(fromRestore.isRestorationSaving({ ...baseState, saving: true })).toBe(true); + }); + }); + + describe('isRestorationValid', () => { + it('should return false if any forms have an invalid status', () => { + expect(fromRestore.isRestorationValid({ ...baseState, status: { common: 'INVALID' } })).toBe(false); + }); + + it('should return true if all forms have a valid status', () => { + expect(fromRestore.isRestorationValid({ ...baseState, status: { common: 'VALID' } })).toBe(true); + }); + }); + + describe('getInvalidRestorationForms', () => { + it('should return the form names that are invalid', () => { + expect(fromRestore.getInvalidRestorationForms({ ...baseState, status: { common: 'INVALID' } })).toEqual(['common']); }); }); }); diff --git a/ui/src/app/metadata/configuration/reducer/restore.reducer.ts b/ui/src/app/metadata/configuration/reducer/restore.reducer.ts index f2e000728..e0c58c2b7 100644 --- a/ui/src/app/metadata/configuration/reducer/restore.reducer.ts +++ b/ui/src/app/metadata/configuration/reducer/restore.reducer.ts @@ -4,7 +4,7 @@ import { RestoreActionTypes, RestoreActionsUnion } from '../action/restore.actio export interface RestoreState { saving: boolean; status: { [key: string]: string }; - changes: Metadata; + changes: Partial; } export const initialState: RestoreState = { diff --git a/ui/src/app/metadata/configuration/reducer/version.reducer.spec.ts b/ui/src/app/metadata/configuration/reducer/version.reducer.spec.ts index e69de29bb..a564df384 100644 --- a/ui/src/app/metadata/configuration/reducer/version.reducer.spec.ts +++ b/ui/src/app/metadata/configuration/reducer/version.reducer.spec.ts @@ -0,0 +1,108 @@ +import { + reducer, + getVersionModel, + getVersionModelLoaded, + getSelectedMetadataId, + getSelectedVersionId, + getSelectedVersionType +} from './version.reducer'; +import * as fromVersion from './version.reducer'; +import { + VersionActionTypes, + SelectVersionRequest, + ClearVersion, + SelectVersionSuccess +} from '../action/version.action'; +import { Metadata } from '../../domain/domain.type'; +import { VersionRequest } from '../model/request'; + +describe('Restore Reducer', () => { + + let baseState; + + const req: VersionRequest = { + type: 'provider', + version: 'foo', + id: 'bar' + }; + + const model: Metadata = { + id: 'bar', + name: 'foo', + '@type': 'MetadataProvider', + type: 'provider', + resourceId: 'foo', + createdBy: 'bar' + }; + + beforeEach(() => { + baseState = { ...fromVersion.initialState }; + }); + + describe('undefined action', () => { + it('should return the default state', () => { + const result = reducer(undefined, {} as any); + + expect(result).toEqual(baseState); + }); + }); + + describe(`${VersionActionTypes.SELECT_VERSION_REQUEST} action`, () => { + it('should set the needed metadata properties', () => { + const action = new SelectVersionRequest(req); + const result = reducer(baseState, action); + + expect(result.selectedMetadataId).toEqual(req.id); + }); + }); + + describe(`${VersionActionTypes.SELECT_VERSION_SUCCESS} action`, () => { + it('should set the needed metadata properties', () => { + const action = new SelectVersionSuccess(model as Metadata); + const result = reducer(baseState, action); + + expect(result).toEqual({ ...baseState, model, loaded: true }); + }); + }); + + describe(`${VersionActionTypes.CLEAR_VERSION} action`, () => { + it('should set the needed metadata properties', () => { + const action = new ClearVersion(); + const result = reducer(baseState, action); + + expect(result).toEqual(baseState); + }); + }); + + describe('selector function', () => { + describe('getSelectedMetadataId', () => { + it('should return the selected version id', () => { + expect(getVersionModel({ ...baseState, model })).toEqual(model); + }); + }); + + describe('getSelectedMetadataVersion', () => { + it('should return the selected version id', () => { + expect(getVersionModelLoaded({ ...baseState, loaded: true })).toBe(true); + }); + }); + + describe('getSelectedMetadataId', () => { + it('should return the selected resource id', () => { + expect(getSelectedMetadataId({ ...baseState, selectedMetadataId: req.id})).toEqual(req.id); + }); + }); + + describe('getSelectedMetadataType', () => { + it('should return the selected version type', () => { + expect(getSelectedVersionType({ ...baseState, selectedVersionType: req.type })).toEqual(req.type); + }); + }); + + describe('getSelectedMetadataType', () => { + it('should return the selected version id', () => { + expect(getSelectedVersionId({ ...baseState, selectedVersionId: req.version })).toEqual(req.version); + }); + }); + }); +}); From 8cb1d49279e1328220bf190790b9d0fc85b5d0c1 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 30 Aug 2019 13:36:35 -0700 Subject: [PATCH 07/11] SHIBUI-1385 Added loading indicator --- .../restore-edit-step.component.spec.ts | 4 +- .../container/restore-edit-step.component.ts | 6 +-- .../container/restore.component.html | 42 +++++++++++-------- .../container/restore.component.spec.ts | 2 +- .../container/restore.component.ts | 2 + .../metadata/configuration/reducer/index.ts | 3 ++ .../configuration/reducer/version.reducer.ts | 18 ++++++-- .../widget/datalist/datalist.component.html | 2 +- .../autocomplete/autocomplete.component.html | 7 +++- 9 files changed, 57 insertions(+), 29 deletions(-) diff --git a/ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts b/ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts index 7d468b5af..d42f0be51 100644 --- a/ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/restore-edit-step.component.spec.ts @@ -79,13 +79,13 @@ describe('Restore Version Edit Step Component', () => { describe('updateStatus', () => { it('should dispatch an update form status event', () => { - app.updateStatus([{ value: 'foo' }, 'common']); + app.updateStatus({ value: 'foo' }, 'common'); expect(store.dispatch).toHaveBeenCalled(); expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(RestoreActionTypes.UPDATE_STATUS); }); it('should dispatch an update form status event', () => { - app.updateStatus([{}, 'common']); + app.updateStatus({}, 'common'); expect(store.dispatch).toHaveBeenCalled(); expect(dispatchSpy.calls.mostRecent().args[0].type).toBe(RestoreActionTypes.UPDATE_STATUS); }); diff --git a/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts b/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts index 904cf43f6..2a9a73a0e 100644 --- a/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts +++ b/ui/src/app/metadata/configuration/container/restore-edit-step.component.ts @@ -54,21 +54,21 @@ export class RestoreEditStepComponent implements OnDestroy { this.lockChange$ .pipe(takeUntil(this.ngUnsubscribe)) - .subscribe(this.updateLock); + .subscribe(locked => this.updateLock(locked)); this.statusChange$ .pipe( takeUntil(this.ngUnsubscribe), withLatestFrom(this.store.select(getWizardIndex)) ) - .subscribe(this.updateStatus); + .subscribe(([errors, currentPage]) => this.updateStatus(errors, currentPage)); } onChange(changes: any): void { this.store.dispatch(new UpdateRestorationChangesRequest(changes)); } - updateStatus([errors, currentPage]) { + updateStatus(errors, currentPage) { const status = { [currentPage]: !(errors.value) ? 'VALID' : 'INVALID' }; this.store.dispatch(new UpdateRestoreFormStatus(status)); } diff --git a/ui/src/app/metadata/configuration/container/restore.component.html b/ui/src/app/metadata/configuration/container/restore.component.html index 855426142..3072dbfc3 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.html +++ b/ui/src/app/metadata/configuration/container/restore.component.html @@ -1,20 +1,26 @@ -

- - Restore Version ( date ) - -

-
-
-
-

- Create New Version from Previous Settings -

-

- Restoring this version will copy the Version ( date ) configuration and create a new Version from the selected version settings. You can then edit the configuration before saving the new version. -

-   - + +

+ + Restore Version ( date ) + +

+
+
+
+

+ Create New Version from Previous Settings +

+

+ Restoring this version will copy the Version ( date ) configuration and create a new Version from the selected version settings. You can then edit the configuration before saving the new version. +

+   + +
-
+ +
+ + Loading... +
\ No newline at end of file diff --git a/ui/src/app/metadata/configuration/container/restore.component.spec.ts b/ui/src/app/metadata/configuration/container/restore.component.spec.ts index ef3b0f3fa..36420e2f6 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.spec.ts @@ -65,7 +65,7 @@ describe('Metadata Restore Page Component', () => { it('should load metadata objects', async(() => { expect(app).toBeTruthy(); - expect(store.select).toHaveBeenCalledTimes(1); + expect(store.select).toHaveBeenCalledTimes(2); expect(store.dispatch).not.toHaveBeenCalled(); })); diff --git a/ui/src/app/metadata/configuration/container/restore.component.ts b/ui/src/app/metadata/configuration/container/restore.component.ts index 6fc4181c1..bb8ffc2cf 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.ts @@ -19,6 +19,8 @@ import { Router, ActivatedRoute } from '@angular/router'; export class RestoreComponent { dateString$ = this.store.select(fromConfiguration.getConfigurationVersionDate); + loading$ = this.store.select(fromConfiguration.getVersionLoading); + loaded$ = this.loading$.pipe(map(loading => !loading)); date$: Observable; constructor( diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index b64b6e070..a9485de94 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -167,6 +167,9 @@ export const getVersionModelFiltersFn = null; export const getVersionState = createSelector(getState, getVersionStateFn); + +export const getVersionLoading = createSelector(getVersionState, fromVersion.isVersionLoading); + export const getVersionModel = createSelector(getVersionState, fromVersion.getVersionModel); export const getVersionModels = createSelector(getVersionModel, (model) => model ? [model] : null); export const getVersionConfigurationSections = createSelector( diff --git a/ui/src/app/metadata/configuration/reducer/version.reducer.ts b/ui/src/app/metadata/configuration/reducer/version.reducer.ts index 9d2dd9986..ae14c33a4 100644 --- a/ui/src/app/metadata/configuration/reducer/version.reducer.ts +++ b/ui/src/app/metadata/configuration/reducer/version.reducer.ts @@ -7,6 +7,7 @@ export interface State { selectedVersionType: string; selectedMetadataId: string; loaded: Boolean; + loading: Boolean; } export const initialState: State = { @@ -14,7 +15,8 @@ export const initialState: State = { selectedVersionId: null, selectedMetadataId: null, selectedVersionType: null, - loaded: false + loaded: false, + loading: false }; export function reducer(state = initialState, action: VersionActionsUnion): State { @@ -24,13 +26,22 @@ export function reducer(state = initialState, action: VersionActionsUnion): Stat ...state, selectedMetadataId: action.payload.id, selectedVersionId: action.payload.version, - selectedVersionType: action.payload.type + selectedVersionType: action.payload.type, + loading: true }; case VersionActionTypes.SELECT_VERSION_SUCCESS: return { ...state, model: action.payload, - loaded: true + loaded: true, + loading: false + }; + case VersionActionTypes.SELECT_VERSION_ERROR: + return { + ...state, + model: null, + loaded: false, + loading: false }; case VersionActionTypes.CLEAR_VERSION: return { @@ -44,6 +55,7 @@ export function reducer(state = initialState, action: VersionActionsUnion): Stat export const getVersionModel = (state: State) => state.model; export const getVersionModelLoaded = (state: State) => state.loaded; +export const isVersionLoading = (state: State) => state.loading; export const getSelectedMetadataId = (state: State) => state.selectedMetadataId; export const getSelectedVersionId = (state: State) => state.selectedVersionId; diff --git a/ui/src/app/schema-form/widget/datalist/datalist.component.html b/ui/src/app/schema-form/widget/datalist/datalist.component.html index a4f377035..58cf01c46 100644 --- a/ui/src/app/schema-form/widget/datalist/datalist.component.html +++ b/ui/src/app/schema-form/widget/datalist/datalist.component.html @@ -1,5 +1,5 @@
-