From 3a2cee53a09fa306f2399cef7f83874cc92dffc1 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 9 Aug 2019 11:05:10 -0700 Subject: [PATCH 01/13] SHIBUI-1382 implementation for restore page --- .../configuration/action/restore.action.ts | 31 ++++++++++++++++- .../container/configuration.component.ts | 2 +- .../container/metadata-history.component.ts | 1 + .../container/restore.component.html | 10 +++--- .../container/restore.component.ts | 33 ++++++++++++++----- .../effect/configuration.effect.ts | 8 ++--- .../configuration/effect/restore.effect.ts | 17 ++++++++-- .../metadata/configuration/reducer/index.ts | 1 + 8 files changed, 82 insertions(+), 21 deletions(-) diff --git a/ui/src/app/metadata/configuration/action/restore.action.ts b/ui/src/app/metadata/configuration/action/restore.action.ts index 48861c35e..b05594fab 100644 --- a/ui/src/app/metadata/configuration/action/restore.action.ts +++ b/ui/src/app/metadata/configuration/action/restore.action.ts @@ -5,13 +5,39 @@ 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 Request', + RESTORE_VERSION_ERROR = '[Restore Version] Restore Version Request', + CLEAR_VERSION = '[Restore Version] Clear Versions' } +export interface VersionRequest { + type: string; + id: string; + version: string; +} + +export class RestoreVersionRequest implements Action { + readonly type = RestoreActionTypes.RESTORE_VERSION_REQUEST; + constructor(public payload: VersionRequest) { } +} + +export class RestoreVersionSuccess implements Action { + readonly type = RestoreActionTypes.RESTORE_VERSION_SUCCESS; + constructor(public payload: Metadata) { } +} + +export class RestoreVersionError implements Action { + readonly type = RestoreActionTypes.RESTORE_VERSION_ERROR; + constructor(public payload: any) { } +} + export class SelectVersionRestoreRequest implements Action { readonly type = RestoreActionTypes.SELECT_VERSION_REQUEST; - constructor(public payload: { type: string, id: string, version: string }) { } + constructor(public payload: VersionRequest) { } } export class SelectVersionRestoreSuccess implements Action { @@ -33,4 +59,7 @@ export type RestoreActionsUnion = | SelectVersionRestoreRequest | SelectVersionRestoreError | SelectVersionRestoreSuccess + | RestoreVersionRequest + | RestoreVersionSuccess + | RestoreVersionError | ClearVersionRestore; diff --git a/ui/src/app/metadata/configuration/container/configuration.component.ts b/ui/src/app/metadata/configuration/container/configuration.component.ts index d8c0234c6..7e8d6f443 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.ts +++ b/ui/src/app/metadata/configuration/container/configuration.component.ts @@ -37,7 +37,7 @@ export class ConfigurationComponent implements OnDestroy { type, version })) - ).subscribe(store); + ).subscribe(this.store); this.routerState.params.pipe( takeUntil(this.ngUnsubscribe), 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 17f2c3caa..7d817b94f 100644 --- a/ui/src/app/metadata/configuration/container/metadata-history.component.ts +++ b/ui/src/app/metadata/configuration/container/metadata-history.component.ts @@ -49,6 +49,7 @@ export class MetadataHistoryComponent { this.router.navigate( [ '../', 'restore' ], { + queryParams: { version: version.id }, relativeTo: this.route } ); diff --git a/ui/src/app/metadata/configuration/container/restore.component.html b/ui/src/app/metadata/configuration/container/restore.component.html index c842aa47d..0b5a2f1ac 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.html +++ b/ui/src/app/metadata/configuration/container/restore.component.html @@ -1,12 +1,14 @@

- Restore Version + Restore Version ({{ date$ | async | date:DATE_FORMAT }})

-

Create new version from {{ date | date:DATE_FORMAT }} settings

-

Restoring this version will copy the configuration from the selected version and create a new version from these settings. You can then edit the configuration before saving the new version.

- Cancel Restore +

Create New Version from Version ({{ date$ | async | date:DATE_FORMAT }}) Settings

+

Restoring this version will copy the Version ({{ date$ | async | date:DATE_FORMAT }}) configuration and create a new Version from the + selected version settings. You can then edit the configuration before saving the new version.

+ Cancel  +
diff --git a/ui/src/app/metadata/configuration/container/restore.component.ts b/ui/src/app/metadata/configuration/container/restore.component.ts index 53f3573ec..83d3cb98f 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.ts @@ -5,6 +5,8 @@ import { Subject } from 'rxjs'; import * as fromConfiguration from '../reducer'; import { CONFIG_DATE_FORMAT } from '../configuration.values'; +import { RestoreVersionRequest } from '../action/restore.action'; +import { withLatestFrom, map, takeUntil } from 'rxjs/operators'; @Component({ selector: 'restore-component', @@ -13,19 +15,32 @@ import { CONFIG_DATE_FORMAT } from '../configuration.values'; styleUrls: [] }) export class RestoreComponent implements OnDestroy { - private ngUnsubscribe: Subject = new Subject(); - DATE_FORMAT = CONFIG_DATE_FORMAT; + private subj = new Subject(); + restore$ = this.subj.asObservable(); + + date$ = this.store.select(fromConfiguration.getConfigurationVersionDate); - date = new Date(); + DATE_FORMAT = CONFIG_DATE_FORMAT; constructor( - private store: Store, - private routerState: ActivatedRoute - ) {} + private store: Store + ) { + this.restore$.pipe( + withLatestFrom( + this.store.select(fromConfiguration.getSelectedVersionId), + this.store.select(fromConfiguration.getConfigurationModelType), + this.store.select(fromConfiguration.getConfigurationModelId) + ), + map(([restore, version, type, id]) => new RestoreVersionRequest({ id, type, version })) + ).subscribe(this.store); + } + + restore() { + this.subj.next(); + } - ngOnDestroy() { - this.ngUnsubscribe.next(); - this.ngUnsubscribe.complete(); + ngOnDestroy(): void { + this.subj.complete(); } } diff --git a/ui/src/app/metadata/configuration/effect/configuration.effect.ts b/ui/src/app/metadata/configuration/effect/configuration.effect.ts index 924d61919..b9719469d 100644 --- a/ui/src/app/metadata/configuration/effect/configuration.effect.ts +++ b/ui/src/app/metadata/configuration/effect/configuration.effect.ts @@ -44,11 +44,11 @@ export class MetadataConfigurationEffects { map(action => action.payload), switchMap(payload => this.historyService.getVersion(payload.id, payload.type, payload.version).pipe( - map((response: Metadata) => - (payload.type === 'resolver') ? + map((response: Metadata) => { + return (payload.type === 'resolver') ? new SelectResolverSuccess(response as MetadataResolver) : - new SelectProviderSuccess(response as MetadataProvider) - ) + new SelectProviderSuccess(response as MetadataProvider); + }) ) ) ); diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts index b7f928f88..3a7f10002 100644 --- a/ui/src/app/metadata/configuration/effect/restore.effect.ts +++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts @@ -6,7 +6,8 @@ import { RestoreActionTypes, SelectVersionRestoreRequest, SelectVersionRestoreError, - SelectVersionRestoreSuccess + SelectVersionRestoreSuccess, + RestoreVersionRequest } from '../action/restore.action'; import { MetadataHistoryService } from '../service/history.service'; import { of } from 'rxjs'; @@ -16,7 +17,7 @@ import { of } from 'rxjs'; export class RestoreVersionEffects { @Effect() - restoreVersionFromId$ = this.actions$.pipe( + selectVersionFromId$ = this.actions$.pipe( ofType(RestoreActionTypes.SELECT_VERSION_REQUEST), map(action => action.payload), switchMap(({ type, id, version }) => { @@ -27,6 +28,18 @@ export class RestoreVersionEffects { }) ); + @Effect() + restoreVersion$ = this.actions$.pipe( + ofType(RestoreActionTypes.RESTORE_VERSION_REQUEST), + map(action => action.payload), + switchMap(({ id, type, version }) => { + return this.historyService.getVersion(id, version, type).pipe( + map(v => new SelectVersionRestoreSuccess(v)), + catchError(err => of(new SelectVersionRestoreError(err))) + ); + }) + ); + constructor( private historyService: MetadataHistoryService, private actions$: Actions diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index 76140bfca..469fe740e 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -183,3 +183,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(getConfigurationModel, version => version.modifiedDate); From f302e901b73902fb5a1a5a6baa4ba1c2507c56c0 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 12 Aug 2019 09:40:45 -0700 Subject: [PATCH 02/13] SHIBUI-1382 Implemented restore call --- .../configuration/action/restore.action.ts | 6 +-- .../container/restore.component.ts | 2 +- .../configuration/effect/restore.effect.ts | 46 +++++++++++++++---- .../configuration/service/history.service.ts | 15 +++++- .../model/wizards/metadata-source-base.ts | 1 + 5 files changed, 57 insertions(+), 13 deletions(-) diff --git a/ui/src/app/metadata/configuration/action/restore.action.ts b/ui/src/app/metadata/configuration/action/restore.action.ts index b05594fab..290367054 100644 --- a/ui/src/app/metadata/configuration/action/restore.action.ts +++ b/ui/src/app/metadata/configuration/action/restore.action.ts @@ -7,8 +7,8 @@ export enum RestoreActionTypes { SELECT_VERSION_REQUEST = '[Restore Version] Select Version Request', RESTORE_VERSION_REQUEST = '[Restore Version] Restore Version Request', - RESTORE_VERSION_SUCCESS = '[Restore Version] Restore Version Request', - RESTORE_VERSION_ERROR = '[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' } @@ -26,7 +26,7 @@ export class RestoreVersionRequest implements Action { export class RestoreVersionSuccess implements Action { readonly type = RestoreActionTypes.RESTORE_VERSION_SUCCESS; - constructor(public payload: Metadata) { } + constructor(public payload: { id: string, type: string, model: Metadata }) { } } export class RestoreVersionError implements Action { diff --git a/ui/src/app/metadata/configuration/container/restore.component.ts b/ui/src/app/metadata/configuration/container/restore.component.ts index 83d3cb98f..feffeb71b 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.ts @@ -29,7 +29,7 @@ export class RestoreComponent implements OnDestroy { this.restore$.pipe( withLatestFrom( this.store.select(fromConfiguration.getSelectedVersionId), - this.store.select(fromConfiguration.getConfigurationModelType), + this.store.select(fromConfiguration.getConfigurationModelKind), this.store.select(fromConfiguration.getConfigurationModelId) ), map(([restore, version, type, id]) => new RestoreVersionRequest({ id, type, version })) diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts index 3a7f10002..d91682c61 100644 --- a/ui/src/app/metadata/configuration/effect/restore.effect.ts +++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts @@ -7,10 +7,16 @@ import { SelectVersionRestoreRequest, SelectVersionRestoreError, SelectVersionRestoreSuccess, - RestoreVersionRequest + RestoreVersionRequest, + RestoreVersionSuccess, + RestoreVersionError } from '../action/restore.action'; import { MetadataHistoryService } from '../service/history.service'; import { of } from 'rxjs'; +import { Router, ActivatedRoute } from '@angular/router'; + +import { AddNotification } from '../../../notification/action/notification.action'; +import { Notification, NotificationType } from '../../../notification/model/notification'; @Injectable() @@ -32,16 +38,40 @@ export class RestoreVersionEffects { restoreVersion$ = this.actions$.pipe( ofType(RestoreActionTypes.RESTORE_VERSION_REQUEST), map(action => action.payload), - switchMap(({ id, type, version }) => { - return this.historyService.getVersion(id, version, type).pipe( - map(v => new SelectVersionRestoreSuccess(v)), - catchError(err => of(new SelectVersionRestoreError(err))) - ); - }) + switchMap(({ id, type, version }) => + this.historyService.restoreVersion(id, type, version).pipe( + map(v => new RestoreVersionSuccess({ id, type, model: v })), + catchError(err => of(new RestoreVersionError(err))) + ) + ) + ); + + @Effect() + restoreVersionSuccessNotification$ = this.actions$.pipe( + ofType(RestoreActionTypes.RESTORE_VERSION_SUCCESS), + map(action => action.payload), + map((data) => + new AddNotification(new Notification( + NotificationType.Success, + `Version Restored!`, + 5000 + )) + ) + ); + + @Effect({dispatch: false}) + restoreVersionSuccessRedirect$ = this.actions$.pipe( + ofType(RestoreActionTypes.RESTORE_VERSION_SUCCESS), + map(action => action.payload), + switchMap((data) => + this.router.navigate(['/metadata', data.type, data.id, 'configuration', 'options']) + ) ); constructor( private historyService: MetadataHistoryService, - private actions$: Actions + private actions$: Actions, + private router: Router, + private route: ActivatedRoute ) { } } diff --git a/ui/src/app/metadata/configuration/service/history.service.ts b/ui/src/app/metadata/configuration/service/history.service.ts index 700e88e6a..02529cde5 100644 --- a/ui/src/app/metadata/configuration/service/history.service.ts +++ b/ui/src/app/metadata/configuration/service/history.service.ts @@ -5,8 +5,9 @@ import { MetadataHistory } from '../model/history'; import { PATHS } from '../../configuration/configuration.values'; import { MetadataVersion } from '../model/version'; -import { map } from 'rxjs/operators'; +import { map, catchError, switchMap } from 'rxjs/operators'; import { Metadata } from '../../domain/domain.type'; +import { withLatestFrom } from 'rxjs-compat/operator/withLatestFrom'; @Injectable() export class MetadataHistoryService { @@ -39,4 +40,16 @@ export class MetadataHistoryService { `/${this.base}/${PATHS[type]}/${resourceId}`; return this.http.get(api); } + + updateVersion(resourceId: string, type: string, model: Metadata): Observable { + return this.http.put(`/${this.base}/${PATHS[type]}/${resourceId}`, model); + } + + restoreVersion(resourceId: string, type: string, versionId: string): Observable { + return this.getVersions(resourceId, [null, versionId], type).pipe( + switchMap(([current, toRestore]) => + this.updateVersion(resourceId, type, { ...toRestore, version: current.version }) + ) + ); + } } 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 aab844d5e..02b1a2625 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 @@ -86,6 +86,7 @@ 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}`, From 0a601273d998c076a789dbc06abd5b7c46845f69 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 13 Aug 2019 08:02:03 -0700 Subject: [PATCH 03/13] Implemented unit tests --- .../container/configuration.component.spec.ts | 8 ++ .../container/configuration.component.ts | 8 +- .../container/metadata-xml.component.spec.ts | 7 ++ .../container/restore.component.spec.ts | 72 ++++++++++++++++++ .../container/restore.component.ts | 2 +- .../service/history.service.spec.ts | 74 ++++++++++++++++++- 6 files changed, 165 insertions(+), 6 deletions(-) create mode 100644 ui/src/app/metadata/configuration/container/restore.component.spec.ts 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..0d2fca0d3 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/configuration.component.spec.ts @@ -59,4 +59,12 @@ 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/configuration.component.ts b/ui/src/app/metadata/configuration/container/configuration.component.ts index 7e8d6f443..295f90257 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.ts +++ b/ui/src/app/metadata/configuration/container/configuration.component.ts @@ -22,6 +22,8 @@ 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 @@ -50,10 +52,8 @@ export class ConfigurationComponent implements OnDestroy { withLatestFrom( this.routerState.queryParams ), - map(([collection, params]) => params.version || collection && collection.length ? collection[0].id : null) - ).subscribe(version => { - this.store.dispatch(new SelectVersion(version)); - }); + 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); diff --git a/ui/src/app/metadata/configuration/container/metadata-xml.component.spec.ts b/ui/src/app/metadata/configuration/container/metadata-xml.component.spec.ts index c9129dfaa..5e5815c06 100644 --- a/ui/src/app/metadata/configuration/container/metadata-xml.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/metadata-xml.component.spec.ts @@ -56,4 +56,11 @@ describe('Metadata Xml Page Component', () => { expect(app).toBeTruthy(); expect(store.select).toHaveBeenCalledTimes(3); })); + + describe('preview method', () => { + it('should dispatch an action', () => { + app.preview(); + expect(store.dispatch).toHaveBeenCalled(); + }); + }); }); diff --git a/ui/src/app/metadata/configuration/container/restore.component.spec.ts b/ui/src/app/metadata/configuration/container/restore.component.spec.ts new file mode 100644 index 000000000..433a11ebd --- /dev/null +++ b/ui/src/app/metadata/configuration/container/restore.component.spec.ts @@ -0,0 +1,72 @@ +import { Component, ViewChild, Input } 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 * as fromConfiguration from '../reducer'; +import * as fromProviders from '../../provider/reducer'; +import * as fromResolvers from '../../resolver/reducer'; +import { MockI18nModule } from '../../../../testing/i18n.stub'; +import { RestoreComponent } from './restore.component'; +import { of } from 'rxjs'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(RestoreComponent) + public componentUnderTest: RestoreComponent; +} + +describe('Metadata Restore Page Component', () => { + + let fixture: ComponentFixture; + let instance: TestHostComponent; + let app: RestoreComponent; + let store: Store; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + StoreModule.forRoot({ + 'metadata-configuration': combineReducers(fromConfiguration.reducers), + 'provider': combineReducers(fromProviders.reducers), + 'resolver': combineReducers(fromResolvers.reducers) + }), + MockI18nModule, + RouterTestingModule + ], + declarations: [ + RestoreComponent, + TestHostComponent + ], + }).compileComponents(); + + store = TestBed.get(Store); + spyOn(store, 'dispatch'); + spyOn(store, 'select').and.callFake(() => of(new Date().toDateString())); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should load metadata objects', async(() => { + expect(app).toBeTruthy(); + expect(store.select).toHaveBeenCalledTimes(4); + expect(store.dispatch).not.toHaveBeenCalled(); + })); + + describe('restore method', () => { + it('should emit a value from the restore subject', () => { + spyOn(app.subj, 'next').and.callThrough(); + app.restore(); + expect(app.subj.next).toHaveBeenCalled(); + }); + }); +}); diff --git a/ui/src/app/metadata/configuration/container/restore.component.ts b/ui/src/app/metadata/configuration/container/restore.component.ts index feffeb71b..69c6a6f18 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.ts @@ -16,7 +16,7 @@ import { withLatestFrom, map, takeUntil } from 'rxjs/operators'; }) export class RestoreComponent implements OnDestroy { - private subj = new Subject(); + readonly subj = new Subject(); restore$ = this.subj.asObservable(); date$ = this.store.select(fromConfiguration.getConfigurationVersionDate); 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 0e8bc77a4..0927f8e70 100644 --- a/ui/src/app/metadata/configuration/service/history.service.spec.ts +++ b/ui/src/app/metadata/configuration/service/history.service.spec.ts @@ -1,7 +1,10 @@ import { TestBed, async, inject } from '@angular/core/testing'; -import { HttpClientModule } from '@angular/common/http'; +import { HttpClientModule, HttpRequest } from '@angular/common/http'; import { HttpTestingController, HttpClientTestingModule } from '@angular/common/http/testing'; import { MetadataHistoryService } from './history.service'; +import { of } from 'rxjs'; +import { PATHS } from '../configuration.values'; +import { Metadata } from '../../domain/domain.type'; describe(`Attributes Service`, () => { beforeEach(() => { @@ -25,4 +28,73 @@ describe(`Attributes Service`, () => { } ))); }); + + describe('getVersions method', () => { + it(`should join a list of observables`, async(inject([MetadataHistoryService, HttpTestingController], + (service: MetadataHistoryService) => { + spyOn(service, 'getVersion').and.returnValue(of()); + service.getVersions('foo', ['abc', 'def'], 'resolver').subscribe(history => { + expect(service.getVersion).toHaveBeenCalledTimes(2); + }); + } + ))); + }); + + describe('getVersion method', () => { + it(`should get the primary version of the resource`, async(inject([MetadataHistoryService, HttpTestingController], + (service: MetadataHistoryService, backend: HttpTestingController) => { + const resourceId = 'foo'; + const type = 'resource'; + service.getVersion(resourceId, type).subscribe(); + backend.expectOne((req: HttpRequest) => { + return req.url === `/${service.base}/${PATHS[type]}/${resourceId}` + && req.method === 'GET'; + }, `GET schema by path`); + } + ))); + it(`should get the provided version of the resource`, async(inject([MetadataHistoryService, HttpTestingController], + (service: MetadataHistoryService, backend: HttpTestingController) => { + const resourceId = 'foo'; + const type = 'resource'; + const versionId = '1'; + service.getVersion(resourceId, type, versionId).subscribe(); + backend.expectOne((req: HttpRequest) => { + return req.url === `/${service.base}/${PATHS[type]}/${resourceId}/${service.path}/${versionId}` + && req.method === 'GET'; + }, `GET schema by path`); + } + ))); + }); + + describe('updateVersion method', () => { + it(`should send a put request`, async(inject([MetadataHistoryService, HttpTestingController], + (service: MetadataHistoryService, backend: HttpTestingController) => { + const resourceId = 'foo'; + const type = 'resource'; + const versionId = '1'; + service.updateVersion(resourceId, type, {} as Metadata).subscribe(); + backend.expectOne((req: HttpRequest) => { + return req.url === `/${service.base}/${PATHS[type]}/${resourceId}` + && req.method === 'PUT'; + }, `PUT schema by path`); + } + ))); + }); + + 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(); + }); + } + ))); + }); }); From 76dcaa6d8dbbfb16803ea3b0b04d4c4ac8e08df3 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 13 Aug 2019 08:27:44 -0700 Subject: [PATCH 04/13] Fixed unit test --- .../service/configuration.service.spec.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/ui/src/app/metadata/configuration/service/configuration.service.spec.ts b/ui/src/app/metadata/configuration/service/configuration.service.spec.ts index 40bfb39ac..43fd99c05 100644 --- a/ui/src/app/metadata/configuration/service/configuration.service.spec.ts +++ b/ui/src/app/metadata/configuration/service/configuration.service.spec.ts @@ -7,10 +7,14 @@ import { MetadataSourceEditor } from '../../domain/model/wizards/metadata-source import { ResolverService } from '../../domain/service/resolver.service'; import { of } from 'rxjs'; import { MetadataProviderService } from '../../domain/service/provider.service'; +import { Metadata } from '../../domain/domain.type'; +import { SCHEMA } from '../../../../testing/form-schema.stub'; +import { getConfigurationSectionsFn } from '../reducer'; describe(`Configuration Service`, () => { let resolverService: any; + let providerService: any; let mockService = { find: () => of([]) @@ -36,11 +40,12 @@ describe(`Configuration Service`, () => { }); resolverService = TestBed.get(ResolverService); + providerService = TestBed.get(MetadataProviderService); }); describe('find method', () => { - it(`should send an expected GET request`, async(inject([MetadataConfigurationService, HttpTestingController], + it(`should call the resolver service when type is resolver`, async(inject([MetadataConfigurationService, HttpTestingController], (service: MetadataConfigurationService, backend: HttpTestingController) => { spyOn(resolverService, 'find').and.callThrough(); const type = 'resolver'; @@ -49,6 +54,25 @@ describe(`Configuration Service`, () => { expect(resolverService.find).toHaveBeenCalledWith(id); } ))); + it(`should call the provider service when type is resolver`, async(inject([MetadataConfigurationService, HttpTestingController], + (service: MetadataConfigurationService, backend: HttpTestingController) => { + spyOn(providerService, 'find').and.callThrough(); + const type = 'provider'; + const id = 'foo'; + service.find(id, type).subscribe(); + expect(providerService.find).toHaveBeenCalledWith(id); + } + ))); + it(`should throw an error when a type is not found`, async(inject([MetadataConfigurationService, HttpTestingController], + (service: MetadataConfigurationService, backend: HttpTestingController) => { + spyOn(providerService, 'find').and.callThrough(); + const type = 'bar'; + const id = 'foo'; + service.find(id, type).subscribe(null, (err) => { + expect(err).toEqual(new Error('Type not supported')); + }); + } + ))); }); describe('loadSchema method', () => { @@ -79,4 +103,15 @@ describe(`Configuration Service`, () => { } ))); }); + + describe('getMetadataConfiguration method', () => { + it('should return the parsed configuration', async(inject([MetadataConfigurationService], + (service: MetadataConfigurationService) => { + const model = {} as Metadata; + const definition = {steps: []}; + const expected = getConfigurationSectionsFn([model], definition, SCHEMA); + expect(service.getMetadataConfiguration(model, definition, SCHEMA)).toEqual(expected); + } + ))); + }); }); From be608918c13217ef5f73ff7209f77d9d0e669ca2 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 14 Aug 2019 13:28:57 -0700 Subject: [PATCH 05/13] Updated i18n tags --- .../main/resources/i18n/messages.properties | 5 +++++ .../component/history-list.component.html | 2 +- .../configuration/configuration.module.ts | 6 ++++-- .../container/restore.component.html | 20 +++++++++++++------ .../container/restore.component.ts | 11 +++++++--- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index f82153ce6..182a2491e 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -55,6 +55,7 @@ action.xml=XML action.manage=Manage action.close=Close action.back-to-top=Back to Top +action.restore=Restore value.enabled=Enabled value.disabled=Disabled @@ -416,6 +417,7 @@ label.check-to-select=Check to select label.current=Current label.restore=Restore label.compare-selected=Compare Selected +label.restore-version=Restore Version ({ date }) label.saved=Saved label.by=By @@ -471,6 +473,9 @@ message.database-constraint=There was a database constraint problem processing t message.no-filters=No Filters message.no-filters-added=No filters have been added to this Metadata Provider +message.create-new-version-from-version=Create New Version from Version ({ date }) Settings +message.restoring-this-version-will-copy=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. + tooltip.entity-id=Entity ID tooltip.service-provider-name=Service Provider Name (Dashboard Display Only) tooltip.force-authn=Disallows use (or reuse) of authentication results and login flows that don\u0027t provide a real-time proof of user presence in the login process 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 ff4599b91..168d0cf19 100644 --- a/ui/src/app/metadata/configuration/component/history-list.component.html +++ b/ui/src/app/metadata/configuration/component/history-list.component.html @@ -34,7 +34,7 @@ diff --git a/ui/src/app/metadata/configuration/configuration.module.ts b/ui/src/app/metadata/configuration/configuration.module.ts index c55e24051..906bed24d 100644 --- a/ui/src/app/metadata/configuration/configuration.module.ts +++ b/ui/src/app/metadata/configuration/configuration.module.ts @@ -1,5 +1,5 @@ import { NgModule, ModuleWithProviders } from '@angular/core'; -import { CommonModule } from '@angular/common'; +import { CommonModule, DatePipe } from '@angular/common'; import { StoreModule } from '@ngrx/store'; import { EffectsModule } from '@ngrx/effects'; import { RouterModule } from '@angular/router'; @@ -63,7 +63,9 @@ import { RestoreVersionEffects } from './effect/restore.effect'; SharedModule ], exports: [], - providers: [] + providers: [ + DatePipe + ] }) export class MetadataConfigurationModule { static forRoot(): ModuleWithProviders { diff --git a/ui/src/app/metadata/configuration/container/restore.component.html b/ui/src/app/metadata/configuration/container/restore.component.html index 0b5a2f1ac..66f994d1d 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.html +++ b/ui/src/app/metadata/configuration/container/restore.component.html @@ -1,14 +1,22 @@

- Restore Version ({{ date$ | async | date:DATE_FORMAT }}) + + Restore Version ( date ) +

-

Create New Version from Version ({{ date$ | async | date:DATE_FORMAT }}) Settings

-

Restoring this version will copy the Version ({{ date$ | async | date:DATE_FORMAT }}) configuration and create a new Version from the - selected version settings. You can then edit the configuration before saving the new version.

- Cancel  - +

+ Create New Version from Version ( date ) 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. +

+ Cancel  +
diff --git a/ui/src/app/metadata/configuration/container/restore.component.ts b/ui/src/app/metadata/configuration/container/restore.component.ts index 69c6a6f18..388c5f8d8 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 * as fromConfiguration from '../reducer'; import { CONFIG_DATE_FORMAT } from '../configuration.values'; import { RestoreVersionRequest } from '../action/restore.action'; import { withLatestFrom, map, takeUntil } from 'rxjs/operators'; +import { DatePipe } from '@angular/common'; @Component({ selector: 'restore-component', @@ -20,11 +21,11 @@ export class RestoreComponent implements OnDestroy { restore$ = this.subj.asObservable(); date$ = this.store.select(fromConfiguration.getConfigurationVersionDate); - - DATE_FORMAT = CONFIG_DATE_FORMAT; + date: string; constructor( - private store: Store + private store: Store, + private datePipe: DatePipe ) { this.restore$.pipe( withLatestFrom( @@ -34,6 +35,10 @@ export class RestoreComponent implements OnDestroy { ), map(([restore, version, type, id]) => new RestoreVersionRequest({ id, type, version })) ).subscribe(this.store); + + this.date$.pipe(takeUntil(this.subj)).subscribe( + (date) => this.date = this.datePipe.transform(date, CONFIG_DATE_FORMAT) + ); } restore() { From 2e39d4f5e5dff006fff7a32e8442b9b7429430dd Mon Sep 17 00:00:00 2001 From: Jj! Date: Fri, 16 Aug 2019 10:38:51 -0500 Subject: [PATCH 06/13] [SHIBUI-1414] WIP --- .../resources/metadata-sources-ui-schema.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/main/resources/metadata-sources-ui-schema.json b/backend/src/main/resources/metadata-sources-ui-schema.json index 9d64e75b4..8743acd12 100644 --- a/backend/src/main/resources/metadata-sources-ui-schema.json +++ b/backend/src/main/resources/metadata-sources-ui-schema.json @@ -26,7 +26,7 @@ "default": false }, "organization": { - "type": "object", + "type": ["object", "null"], "properties": { "name": { "title": "label.organization-name", @@ -68,13 +68,13 @@ "contacts": { "title": "label.contact-information", "description": "tooltip.contact-information", - "type": "array", + "type": ["array", "null"], "items": { "$ref": "#/definitions/Contact" } }, "mdui": { - "type": "object", + "type": ["object", "null"], "widget": { "id": "fieldset" }, @@ -143,7 +143,7 @@ } }, "securityInfo": { - "type": "object", + "type": ["object", "null"], "widget": { "id": "fieldset" }, @@ -245,13 +245,13 @@ "assertionConsumerServices": { "title": "label.assertion-consumer-service-endpoints", "description": "", - "type": "array", + "type": ["array", "null"], "items": { "$ref": "#/definitions/AssertionConsumerService" } }, "serviceProviderSsoDescriptor": { - "type": "object", + "type": ["object", "null"], "widget": { "id": "fieldset" }, @@ -299,17 +299,17 @@ "logoutEndpoints": { "title": "label.logout-endpoints", "description": "tooltip.logout-endpoints", - "type": "array", + "type": ["array", "null"], "items": { "$ref": "#/definitions/LogoutEndpoint" } }, "relyingPartyOverrides": { - "type": "object", + "type": ["object", "null"], "properties": {} }, "attributeRelease": { - "type": "array", + "type": ["array", "null"], "title": "label.attribute-release", "description": "Attribute release table - select the attributes you want to release (default unchecked)", "widget": { From 8da94bd8c2c1cfd9b0c8c5d5c85e26914fa63959 Mon Sep 17 00:00:00 2001 From: Jj! Date: Mon, 19 Aug 2019 09:25:16 -0500 Subject: [PATCH 07/13] [SHIBUI-1414] update tests --- ...lerVersionEndpointsIntegrationTests.groovy | 51 +++++++++++++++++++ ...efinitionControllerIntegrationTests.groovy | 4 +- 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerVersionEndpointsIntegrationTests.groovy b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerVersionEndpointsIntegrationTests.groovy index 99b5810ce..82b143c00 100644 --- a/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerVersionEndpointsIntegrationTests.groovy +++ b/backend/src/enversTest/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorControllerVersionEndpointsIntegrationTests.groovy @@ -1,11 +1,22 @@ package edu.internet2.tier.shibboleth.admin.ui.controller import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor +import edu.internet2.tier.shibboleth.admin.ui.domain.Organization +import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationDisplayName +import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationName +import edu.internet2.tier.shibboleth.admin.ui.domain.OrganizationURL import edu.internet2.tier.shibboleth.admin.ui.domain.frontend.EntityDescriptorRepresentation import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository +import groovy.json.JsonOutput +import groovy.json.JsonSlurper import org.springframework.beans.factory.annotation.Autowired import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.web.client.TestRestTemplate +import org.springframework.http.HttpEntity +import org.springframework.http.HttpHeaders +import org.springframework.http.HttpMethod +import org.springframework.http.MediaType +import org.springframework.test.annotation.DirtiesContext import org.springframework.test.context.ActiveProfiles import spock.lang.Specification @@ -100,6 +111,46 @@ class EntityDescriptorControllerVersionEndpointsIntegrationTests extends Specifi edv2.body.serviceProviderName == 'SP2' } + @DirtiesContext(methodMode = DirtiesContext.MethodMode.AFTER_METHOD) + def 'SHIBUI-1414'() { + given: + def ed = new EntityDescriptor(entityID: 'testme', serviceProviderName: 'testme').with { + entityDescriptorRepository.save(it) + }.with { + it.setOrganization(new Organization().with { + it.organizationNames = [new OrganizationName(value: 'testme', XMLLang: 'en')] + it.organizationDisplayNames = [new OrganizationDisplayName(value: 'testme', XMLLang: 'en')] + it.organizationURLs = [new OrganizationURL(value: 'http://testme.org', XMLLang: 'en')] + it + }) + entityDescriptorRepository.save(it) + } + + when: + def headers = new HttpHeaders().with { + it.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + it + } + + def allVersions = getAllEntityDescriptorVersions(ed.resourceId, List) + def edv1 = getEntityDescriptorForVersion(ed.resourceId, allVersions.body[0].id, String).body + def tedv2 = getEntityDescriptorForVersion(ed.resourceId, allVersions.body[1].id, EntityDescriptorRepresentation).body + + def aedv1 = new JsonSlurper().parseText(edv1).with { + it.put('version', tedv2.version) + it + }.with { + JsonOutput.toJson(it) + } + + def request = new HttpEntity(aedv1, headers) + def response = this.restTemplate.exchange("/api/EntityDescriptor/${ed.resourceId}", HttpMethod.PUT, request, String) + + then: + response.statusCodeValue != 400 + noExceptionThrown() + } + private getAllEntityDescriptorVersions(String resourceId, responseType) { this.restTemplate.getForEntity(resourceUriFor(ALL_VERSIONS_URI, resourceId), responseType) } diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy index af758f131..b9789201f 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests.groovy @@ -7,6 +7,7 @@ import org.springframework.boot.test.context.SpringBootTest import org.springframework.boot.test.context.TestConfiguration import org.springframework.boot.test.web.client.TestRestTemplate import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Profile import org.springframework.core.io.ResourceLoader import org.springframework.test.context.ActiveProfiles import spock.lang.Specification @@ -23,7 +24,7 @@ import static edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaResour * @author Dmitriy Kopylenko */ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@ActiveProfiles("no-auth") +@ActiveProfiles(["no-auth", "badjson"]) class BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests extends Specification { @Autowired @@ -42,6 +43,7 @@ class BadJSONMetadataSourcesUiDefinitionControllerIntegrationTests extends Speci } @TestConfiguration + @Profile('badjson') static class Config { @Bean JsonSchemaResourceLocationRegistry jsonSchemaResourceLocationRegistry(ResourceLoader resourceLoader, From 643b12ba36fbecec9bdf962a53b4a3f7ba3b0c25 Mon Sep 17 00:00:00 2001 From: Jj! Date: Mon, 19 Aug 2019 09:30:38 -0500 Subject: [PATCH 08/13] Revert "[SHIBUI-1414] WIP" This reverts commit 2e39d4f5 --- .../resources/metadata-sources-ui-schema.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/backend/src/main/resources/metadata-sources-ui-schema.json b/backend/src/main/resources/metadata-sources-ui-schema.json index 8743acd12..9d64e75b4 100644 --- a/backend/src/main/resources/metadata-sources-ui-schema.json +++ b/backend/src/main/resources/metadata-sources-ui-schema.json @@ -26,7 +26,7 @@ "default": false }, "organization": { - "type": ["object", "null"], + "type": "object", "properties": { "name": { "title": "label.organization-name", @@ -68,13 +68,13 @@ "contacts": { "title": "label.contact-information", "description": "tooltip.contact-information", - "type": ["array", "null"], + "type": "array", "items": { "$ref": "#/definitions/Contact" } }, "mdui": { - "type": ["object", "null"], + "type": "object", "widget": { "id": "fieldset" }, @@ -143,7 +143,7 @@ } }, "securityInfo": { - "type": ["object", "null"], + "type": "object", "widget": { "id": "fieldset" }, @@ -245,13 +245,13 @@ "assertionConsumerServices": { "title": "label.assertion-consumer-service-endpoints", "description": "", - "type": ["array", "null"], + "type": "array", "items": { "$ref": "#/definitions/AssertionConsumerService" } }, "serviceProviderSsoDescriptor": { - "type": ["object", "null"], + "type": "object", "widget": { "id": "fieldset" }, @@ -299,17 +299,17 @@ "logoutEndpoints": { "title": "label.logout-endpoints", "description": "tooltip.logout-endpoints", - "type": ["array", "null"], + "type": "array", "items": { "$ref": "#/definitions/LogoutEndpoint" } }, "relyingPartyOverrides": { - "type": ["object", "null"], + "type": "object", "properties": {} }, "attributeRelease": { - "type": ["array", "null"], + "type": "array", "title": "label.attribute-release", "description": "Attribute release table - select the attributes you want to release (default unchecked)", "widget": { From 10d03e702d630250b76d9499a14f099f288ba8e3 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 20 Aug 2019 09:34:33 -0700 Subject: [PATCH 09/13] SHIBUI-1382 Fixed issue with restoration --- .../configuration/container/restore.component.spec.ts | 4 ++++ ui/src/app/metadata/configuration/effect/restore.effect.ts | 1 + ui/src/app/metadata/configuration/service/history.service.ts | 3 ++- ui/src/app/metadata/filter/effect/collection.effect.ts | 3 +-- .../filter/model/entity-attributes-configuration.filter.ts | 1 - ui/src/app/schema-form/service/schema.service.ts | 3 ++- 6 files changed, 10 insertions(+), 5 deletions(-) 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 433a11ebd..e79cbcac9 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.spec.ts @@ -10,6 +10,7 @@ import * as fromResolvers from '../../resolver/reducer'; import { MockI18nModule } from '../../../../testing/i18n.stub'; import { RestoreComponent } from './restore.component'; import { of } from 'rxjs'; +import { DatePipe } from '@angular/common'; @Component({ template: ` @@ -44,6 +45,9 @@ describe('Metadata Restore Page Component', () => { RestoreComponent, TestHostComponent ], + providers: [ + DatePipe + ] }).compileComponents(); store = TestBed.get(Store); diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts index d91682c61..cfdf45057 100644 --- a/ui/src/app/metadata/configuration/effect/restore.effect.ts +++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts @@ -17,6 +17,7 @@ import { Router, ActivatedRoute } from '@angular/router'; import { AddNotification } from '../../../notification/action/notification.action'; import { Notification, NotificationType } from '../../../notification/model/notification'; +import { removeNulls } from '../../../shared/util'; @Injectable() diff --git a/ui/src/app/metadata/configuration/service/history.service.ts b/ui/src/app/metadata/configuration/service/history.service.ts index 02529cde5..6a5f79607 100644 --- a/ui/src/app/metadata/configuration/service/history.service.ts +++ b/ui/src/app/metadata/configuration/service/history.service.ts @@ -8,6 +8,7 @@ import { MetadataVersion } from '../model/version'; import { map, catchError, switchMap } from 'rxjs/operators'; import { Metadata } from '../../domain/domain.type'; import { withLatestFrom } from 'rxjs-compat/operator/withLatestFrom'; +import { removeNulls } from '../../../shared/util'; @Injectable() export class MetadataHistoryService { @@ -48,7 +49,7 @@ export class MetadataHistoryService { restoreVersion(resourceId: string, type: string, versionId: string): Observable { return this.getVersions(resourceId, [null, versionId], type).pipe( switchMap(([current, toRestore]) => - this.updateVersion(resourceId, type, { ...toRestore, version: current.version }) + this.updateVersion(resourceId, type, { ...removeNulls(toRestore), version: current.version }) ) ); } diff --git a/ui/src/app/metadata/filter/effect/collection.effect.ts b/ui/src/app/metadata/filter/effect/collection.effect.ts index 58ecd41f7..dcf67b246 100644 --- a/ui/src/app/metadata/filter/effect/collection.effect.ts +++ b/ui/src/app/metadata/filter/effect/collection.effect.ts @@ -35,8 +35,7 @@ import { FilterCollectionActionTypes } from '../action/collection.action'; import * as fromFilter from '../reducer'; import * as fromProvider from '../../provider/reducer'; import { MetadataFilter } from '../../domain/model'; -import { removeNulls, array_move } from '../../../shared/util'; -import { EntityAttributesFilterEntity } from '../../domain/entity/filter/entity-attributes-filter'; +import { array_move } from '../../../shared/util'; import { MetadataFilterService } from '../../domain/service/filter.service'; import { SelectProviderRequest } from '../../provider/action/collection.action'; import { UpdateFilterChanges, ClearFilter } from '../action/filter.action'; diff --git a/ui/src/app/metadata/filter/model/entity-attributes-configuration.filter.ts b/ui/src/app/metadata/filter/model/entity-attributes-configuration.filter.ts index 5333e08f1..06a6609b6 100644 --- a/ui/src/app/metadata/filter/model/entity-attributes-configuration.filter.ts +++ b/ui/src/app/metadata/filter/model/entity-attributes-configuration.filter.ts @@ -1,6 +1,5 @@ import { Wizard } from '../../../wizard/model'; import { MetadataFilter } from '../../domain/model'; -import { removeNulls } from '../../../shared/util'; import { EntityAttributesFilter } from './entity-attributes.filter'; export const EntityAttributesFilterConfiguration: Wizard = { diff --git a/ui/src/app/schema-form/service/schema.service.ts b/ui/src/app/schema-form/service/schema.service.ts index 617769de6..0e5905a8a 100644 --- a/ui/src/app/schema-form/service/schema.service.ts +++ b/ui/src/app/schema-form/service/schema.service.ts @@ -28,12 +28,13 @@ export class SchemaService { const conditions = formProperty.parent.schema.anyOf || []; const values = formProperty.parent.value; const currentConditions = conditions.filter(condition => - Object + 'properties' in condition ? Object .keys(condition.properties) .some( key => values.hasOwnProperty(key) && condition.properties[key].enum ? condition.properties[key].enum[0] === values[key] : false ) + : false ); currentConditions.forEach(el => { requiredFields = el.required || []; From 4863ee6f5e3938d2902ba257a2357dbe43787bdb Mon Sep 17 00:00:00 2001 From: Dmitriy Kopylenko Date: Wed, 21 Aug 2019 09:15:31 -0400 Subject: [PATCH 10/13] Do not expose null values in JSON payloads --- .../src/main/resources/application.properties | 1 + .../EntitiesControllerIntegrationTests.groovy | 17 ++++------------- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 559a6c082..909b2943a 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -20,6 +20,7 @@ spring.jpa.database-platform=org.hibernate.dialect.H2Dialect spring.h2.console.enabled=true # spring.jackson.default-property-inclusion=non_absent +spring.jackson.default-property-inclusion=NON_NULL # Database Configuration PostgreSQL #spring.datasource.url=jdbc:postgresql://localhost:5432/shibui diff --git a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy index 8bf12484a..d8d9e7afb 100644 --- a/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy +++ b/backend/src/test/groovy/edu/internet2/tier/shibboleth/admin/ui/controller/EntitiesControllerIntegrationTests.groovy @@ -42,25 +42,16 @@ class EntitiesControllerIntegrationTests extends Specification { def "GET /api/entities returns the proper json"() { given: def expectedBody = ''' - { - "id":null, - "serviceProviderName":null, - "entityId":"http://test.scaldingspoon.org/test1", - "organization":null, - "contacts":null, - "mdui":null, + { + "entityId":"http://test.scaldingspoon.org/test1", "serviceProviderSsoDescriptor": { "protocolSupportEnum":"SAML 2", "nameIdFormats":["urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"] - }, - "logoutEndpoints":null, - "securityInfo":null, + }, "assertionConsumerServices":[ {"locationUrl":"https://test.scaldingspoon.org/test1/acs","binding":"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST","makeDefault":false} ], - "serviceEnabled":false, - "createdDate":null, - "modifiedDate":null, + "serviceEnabled":false, "relyingPartyOverrides":{}, "attributeRelease":["givenName","employeeNumber"] } From 58b0c2577d01559c8ecf79dc67d83270ca335609 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Wed, 21 Aug 2019 10:06:58 -0700 Subject: [PATCH 11/13] SHIBUI-1382 Fixed issue with date display --- .../container/restore.component.html | 8 ++--- .../container/restore.component.ts | 31 +++++++++++++------ .../configuration/effect/restore.effect.ts | 2 +- .../metadata/configuration/reducer/index.ts | 2 +- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/ui/src/app/metadata/configuration/container/restore.component.html b/ui/src/app/metadata/configuration/container/restore.component.html index 66f994d1d..c2ac6f75e 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.html +++ b/ui/src/app/metadata/configuration/container/restore.component.html @@ -1,5 +1,5 @@ -

- +

+ Restore Version ( date )

@@ -8,11 +8,11 @@

+ [translateParams]="{ 'date': date$ | async }"> Create New Version from Version ( date ) Settings

+ [translateParams]="{ 'date': date$ | async }"> 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.

Cancel  diff --git a/ui/src/app/metadata/configuration/container/restore.component.ts b/ui/src/app/metadata/configuration/container/restore.component.ts index 388c5f8d8..0b6faff29 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.ts +++ b/ui/src/app/metadata/configuration/container/restore.component.ts @@ -1,11 +1,11 @@ import { Component, ChangeDetectionStrategy, OnDestroy } from '@angular/core'; import { ActivatedRoute, } from '@angular/router'; import { Store } from '@ngrx/store'; -import { Subject } from 'rxjs'; +import { Subject, Observable } from 'rxjs'; import * as fromConfiguration from '../reducer'; import { CONFIG_DATE_FORMAT } from '../configuration.values'; -import { RestoreVersionRequest } from '../action/restore.action'; +import { RestoreVersionRequest, SelectVersionRestoreRequest } from '../action/restore.action'; import { withLatestFrom, map, takeUntil } from 'rxjs/operators'; import { DatePipe } from '@angular/common'; @@ -20,25 +20,36 @@ export class RestoreComponent implements OnDestroy { readonly subj = new Subject(); restore$ = this.subj.asObservable(); - date$ = this.store.select(fromConfiguration.getConfigurationVersionDate); - date: string; + date$: Observable; + kind$: Observable = this.store.select(fromConfiguration.getConfigurationModelKind); + id$: Observable = this.store.select(fromConfiguration.getConfigurationModelId); constructor( private store: Store, - private datePipe: DatePipe + private datePipe: DatePipe, + private route: ActivatedRoute ) { this.restore$.pipe( withLatestFrom( this.store.select(fromConfiguration.getSelectedVersionId), - this.store.select(fromConfiguration.getConfigurationModelKind), - this.store.select(fromConfiguration.getConfigurationModelId) + this.kind$, + this.id$ ), map(([restore, version, type, id]) => new RestoreVersionRequest({ id, type, version })) ).subscribe(this.store); - this.date$.pipe(takeUntil(this.subj)).subscribe( - (date) => this.date = this.datePipe.transform(date, CONFIG_DATE_FORMAT) - ); + this.date$ = this.store + .select(fromConfiguration.getConfigurationVersionDate) + .pipe( + map((date) => this.datePipe.transform(date, CONFIG_DATE_FORMAT)) + ); + + this.route.queryParams.pipe( + takeUntil(this.subj), + map(params => params.version), + withLatestFrom(this.id$, this.kind$), + map(([version, id, type]) => new SelectVersionRestoreRequest({ version, id, type })) + ).subscribe(this.store); } restore() { diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts index cfdf45057..df5272ced 100644 --- a/ui/src/app/metadata/configuration/effect/restore.effect.ts +++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts @@ -28,7 +28,7 @@ export class RestoreVersionEffects { ofType(RestoreActionTypes.SELECT_VERSION_REQUEST), map(action => action.payload), switchMap(({ type, id, version }) => { - return this.historyService.getVersion(id, version, type).pipe( + return this.historyService.getVersion(id, type, version).pipe( map(v => new SelectVersionRestoreSuccess(v)), catchError(err => of(new SelectVersionRestoreError(err))) ); diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index 469fe740e..ab217de6d 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -183,4 +183,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(getConfigurationModel, version => version.modifiedDate); +export const getConfigurationVersionDate = createSelector(getRestoreModel, version => version && version.modifiedDate); From d010d6957e535349b15ef02e0c2b29e3f35b0940 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 29 Aug 2019 08:34:05 -0700 Subject: [PATCH 12/13] SHIBUI-1382 Fixed translation --- .../metadata/configuration/container/restore.component.html | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ui/src/app/metadata/configuration/container/restore.component.html b/ui/src/app/metadata/configuration/container/restore.component.html index c2ac6f75e..77fc2c7cb 100644 --- a/ui/src/app/metadata/configuration/container/restore.component.html +++ b/ui/src/app/metadata/configuration/container/restore.component.html @@ -6,10 +6,8 @@

-

- Create New Version from Version ( date ) Settings +

+ Create New Version from Previous Settings

From 483a446c0a98f450911c50c9aac1bd2f3ae0cef5 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 29 Aug 2019 08:34:40 -0700 Subject: [PATCH 13/13] SHIBUI-1382 Fixed translation --- backend/src/main/resources/i18n/messages.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 182a2491e..621dc4e63 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -473,7 +473,7 @@ message.database-constraint=There was a database constraint problem processing t message.no-filters=No Filters message.no-filters-added=No filters have been added to this Metadata Provider -message.create-new-version-from-version=Create New Version from Version ({ date }) Settings +message.create-new-version-from-version=Create New Version from Previous Settings message.restoring-this-version-will-copy=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. tooltip.entity-id=Entity ID