diff --git a/ui/src/app/metadata/domain/entity/filter/entity-attributes-filter.ts b/ui/src/app/metadata/domain/entity/filter/entity-attributes-filter.ts index 82b5ae9d6..c3b9edc98 100644 --- a/ui/src/app/metadata/domain/entity/filter/entity-attributes-filter.ts +++ b/ui/src/app/metadata/domain/entity/filter/entity-attributes-filter.ts @@ -73,7 +73,7 @@ export class EntityAttributesFilter implements MetadataFilter, MetadataEntity { return { attributeRelease: this.attributeRelease, relyingPartyOverrides: this.relyingPartyOverrides, - entityAttributesFilterTarget: { ...this.entityAttributesFilterTarget }, + entityAttributesFilterTarget: this.entityAttributesFilterTarget, filterEnabled: this.filterEnabled, name: this.name, '@type': 'EntityAttributes' diff --git a/ui/src/app/metadata/domain/service/filter.service.spec.ts b/ui/src/app/metadata/domain/service/filter.service.spec.ts index 1410f6723..571940935 100644 --- a/ui/src/app/metadata/domain/service/filter.service.spec.ts +++ b/ui/src/app/metadata/domain/service/filter.service.spec.ts @@ -6,6 +6,8 @@ import { EntityAttributesFilter } from '../entity'; describe(`Metadata Filter Service`, () => { + const provider = 'foo'; + beforeEach(() => { TestBed.configureTestingModule({ imports: [ @@ -21,53 +23,53 @@ describe(`Metadata Filter Service`, () => { describe('query method', () => { it(`should send an expected GET[] request`, async(inject([MetadataFilterService, HttpTestingController], (service: MetadataFilterService, backend: HttpTestingController) => { - service.query().subscribe(); + service.query(provider).subscribe(); backend.expectOne((req: HttpRequest) => { - return req.url === `${service.base}${service.endpoint}` + return req.url === `${service.base}${service.endpoint}/${provider}/Filters` && req.method === 'GET'; - }, `GET MetadataResolvers collection`); + }, `GET MetadataFilter collection`); } ))); }); describe('find method', () => { it(`should send an expected GET request`, async(inject([MetadataFilterService, HttpTestingController], (service: MetadataFilterService, backend: HttpTestingController) => { - const id = 'foo'; - service.find(id).subscribe(); + const id = 'bar'; + service.find(provider, id).subscribe(); backend.expectOne((req: HttpRequest) => { - return req.url === `${service.base}${service.endpoint}/${id}` + return req.url === `${service.base}${service.endpoint}/${provider}/Filters/${id}` && req.method === 'GET'; - }, `GET MetadataResolvers collection`); + }, `GET MetadataFilter`); } ))); }); describe('update method', () => { it(`should send an expected PUT request`, async(inject([MetadataFilterService, HttpTestingController], (service: MetadataFilterService, backend: HttpTestingController) => { - const id = 'foo'; - const filter = new EntityAttributesFilter({ id }); - service.update(filter).subscribe(); + const id = 'bar'; + const filter = new EntityAttributesFilter({ resourceId: id }); + service.update(provider, filter).subscribe(); backend.expectOne((req: HttpRequest) => { - return req.url === `${service.base}${service.endpoint}/${id}` + return req.url === `${service.base}${service.endpoint}/${provider}/Filters/${ id }` && req.method === 'PUT'; - }, `PUT (update) MetadataResolvers collection`); + }, `PUT (update) MetadataFilter`); } ))); }); describe('save method', () => { it(`should send an expected POST request`, async(inject([MetadataFilterService, HttpTestingController], (service: MetadataFilterService, backend: HttpTestingController) => { - const id = 'foo'; + const id = 'bar'; const filter = new EntityAttributesFilter({ id }); - service.save(filter).subscribe(); + service.save(provider, filter).subscribe(); backend.expectOne((req: HttpRequest) => { - return req.url === `${service.base}${service.endpoint}` + return req.url === `${service.base}${service.endpoint}/${provider}/Filters` && req.method === 'POST'; - }, `POST MetadataResolvers collection`); + }, `POST MetadataFilter`); } ))); }); diff --git a/ui/src/app/metadata/domain/service/filter.service.ts b/ui/src/app/metadata/domain/service/filter.service.ts index b96907eca..ac9bc8ce1 100644 --- a/ui/src/app/metadata/domain/service/filter.service.ts +++ b/ui/src/app/metadata/domain/service/filter.service.ts @@ -13,20 +13,20 @@ export class MetadataFilterService { constructor( private http: HttpClient ) { } - query(): Observable { - return this.http.get(`${this.base}${this.endpoint}`, {}); + query(providerId: string): Observable { + return this.http.get(`${this.base}${this.endpoint}/${providerId}/Filters`); } - find(id: string): Observable { + find(providerId: string, filterId: string): Observable { // console.log(id); - return this.http.get(`${this.base}${this.endpoint}/${id}`); + return this.http.get(`${this.base}${this.endpoint}/${providerId}/Filters/${ filterId }`); } - update(filter: MetadataFilter): Observable { - return this.http.put(`${this.base}${this.endpoint}/${filter.id}`, filter); + update(providerId: string, filter: MetadataFilter): Observable { + return this.http.put(`${this.base}${this.endpoint}/${providerId}/Filters/${ filter.resourceId }`, filter); } - save(filter: MetadataFilter): Observable { - return this.http.post(`${this.base}${this.endpoint}`, filter); + save(providerId: string, filter: MetadataFilter): Observable { + return this.http.post(`${this.base}${this.endpoint}/${providerId}/Filters`, filter); } } diff --git a/ui/src/app/metadata/filter/action/collection.action.ts b/ui/src/app/metadata/filter/action/collection.action.ts index 9ed93aaff..f220d84bb 100644 --- a/ui/src/app/metadata/filter/action/collection.action.ts +++ b/ui/src/app/metadata/filter/action/collection.action.ts @@ -53,7 +53,7 @@ export class SelectFilterFail implements Action { export class LoadFilterRequest implements Action { readonly type = FilterCollectionActionTypes.LOAD_FILTER_REQUEST; - constructor() { } + constructor(public payload: string) { } } export class LoadFilterSuccess implements Action { diff --git a/ui/src/app/metadata/filter/container/edit-filter.component.ts b/ui/src/app/metadata/filter/container/edit-filter.component.ts index 41d10a3e9..d2921013f 100644 --- a/ui/src/app/metadata/filter/container/edit-filter.component.ts +++ b/ui/src/app/metadata/filter/container/edit-filter.component.ts @@ -63,12 +63,14 @@ export class EditFilterComponent implements OnInit, OnDestroy { private valueEmitter: ProviderValueEmitter, private fb: FormBuilder ) { - this.changes$ = this.store.select(fromFilter.getFilter); + this.changes$ = this.store.select(fromFilter.getFilterWithChanges); this.changes$ .pipe( distinctUntilChanged() ) - .subscribe(c => this.changes = new EntityAttributesFilter(c)); + .subscribe(c => { + this.changes = new EntityAttributesFilter(c); + }); this.showMore$ = this.store.select(fromFilter.getViewingMore); this.selected$ = this.store.select(fromFilter.getSelected); diff --git a/ui/src/app/metadata/filter/container/filter.component.html b/ui/src/app/metadata/filter/container/filter.component.html index 90c6b6463..3793ba3bf 100644 --- a/ui/src/app/metadata/filter/container/filter.component.html +++ b/ui/src/app/metadata/filter/container/filter.component.html @@ -1 +1,3 @@ - \ No newline at end of file + + + \ No newline at end of file diff --git a/ui/src/app/metadata/filter/effect/collection.effect.ts b/ui/src/app/metadata/filter/effect/collection.effect.ts index 5422f2d99..788e51329 100644 --- a/ui/src/app/metadata/filter/effect/collection.effect.ts +++ b/ui/src/app/metadata/filter/effect/collection.effect.ts @@ -4,11 +4,12 @@ import { Store } from '@ngrx/store'; import { Router } from '@angular/router'; import { of } from 'rxjs'; -import { switchMap, map, catchError, tap } from 'rxjs/operators'; +import { switchMap, map, catchError, tap, combineLatest, skipWhile } from 'rxjs/operators'; import * as actions from '../action/collection.action'; import { FilterCollectionActionTypes } from '../action/collection.action'; import * as fromFilter from '../reducer'; +import * as fromProvider from '../../provider/reducer'; import { MetadataFilter } from '../../domain/model'; import { removeNulls } from '../../../shared/util'; import { EntityAttributesFilter } from '../../domain/entity/filter/entity-attributes-filter'; @@ -21,9 +22,11 @@ export class FilterCollectionEffects { @Effect() loadFilters$ = this.actions$.pipe( ofType(FilterCollectionActionTypes.LOAD_FILTER_REQUEST), - switchMap(() => + map(action => action.payload), + skipWhile(providerId => !providerId), + switchMap(providerId => this.filterService - .query() + .query(providerId) .pipe( map(filters => new actions.LoadFilterSuccess(filters)), catchError(error => of(new actions.LoadFilterError(error))) @@ -34,9 +37,10 @@ export class FilterCollectionEffects { selectFilterRequest$ = this.actions$.pipe( ofType(FilterCollectionActionTypes.SELECT_FILTER), map(action => action.payload), - switchMap(id => { + combineLatest(this.store.select(fromProvider.getSelectedProviderId).pipe(skipWhile(id => !id))), + switchMap(([filterId, providerId]) => { return this.filterService - .find(id) + .find(providerId, filterId) .pipe( map(p => new actions.SelectFilterSuccess(p)), catchError(error => of(new actions.SelectFilterFail(error))) @@ -55,41 +59,37 @@ export class FilterCollectionEffects { relyingPartyOverrides: removeNulls(new EntityAttributesFilter(filter).relyingPartyOverrides) }; }), - switchMap(unsaved => - this.filterService - .save(unsaved as MetadataFilter) + combineLatest(this.store.select(fromProvider.getSelectedProviderId).pipe(skipWhile(id => !id))), + switchMap(([unsaved, providerId]) => { + return this.filterService + .save(providerId, unsaved as MetadataFilter) .pipe( map(saved => new actions.AddFilterSuccess(saved)), catchError(error => of(new actions.AddFilterFail(error))) - ) - ) + ); + }) ); @Effect({ dispatch: false }) addFilterSuccessRedirect$ = this.actions$.pipe( ofType(FilterCollectionActionTypes.ADD_FILTER_SUCCESS), map(action => action.payload), - tap(filter => this.router.navigate(['/dashboard'])) - ); - - @Effect() - addFilterSuccessReload$ = this.actions$.pipe( - ofType(FilterCollectionActionTypes.ADD_FILTER_SUCCESS), - map(action => action.payload), - map(filter => new actions.LoadFilterRequest()) + combineLatest(this.store.select(fromProvider.getSelectedProviderId).pipe(skipWhile(id => !id))), + tap(([filter, provider]) => this.router.navigate(['/', 'metadata', 'provider', provider, 'filters'])) ); @Effect() updateFilter$ = this.actions$.pipe( ofType(FilterCollectionActionTypes.UPDATE_FILTER_REQUEST), map(action => action.payload), - switchMap(filter => { + combineLatest(this.store.select(fromProvider.getSelectedProviderId).pipe(skipWhile(id => !id))), + switchMap(([filter, providerId]) => { delete filter.modifiedDate; delete filter.createdDate; return this.filterService - .update(filter) + .update(providerId, filter) .pipe( map(p => new actions.UpdateFilterSuccess({ - id: p.id, + id: p.resourceId, changes: p })), catchError(err => of(new actions.UpdateFilterFail(filter))) @@ -100,13 +100,8 @@ export class FilterCollectionEffects { updateFilterSuccessRedirect$ = this.actions$.pipe( ofType(FilterCollectionActionTypes.UPDATE_FILTER_SUCCESS), map(action => action.payload), - tap(filter => this.router.navigate(['/dashboard'])) - ); - @Effect() - updateFilterSuccessReload$ = this.actions$.pipe( - ofType(FilterCollectionActionTypes.UPDATE_FILTER_SUCCESS), - map(action => action.payload), - map(filter => new actions.LoadFilterRequest()) + combineLatest(this.store.select(fromProvider.getSelectedProviderId).pipe(skipWhile(id => !id))), + tap(([filter, provider]) => this.router.navigate(['/', 'metadata', 'provider', provider, 'filters'])) ); constructor( diff --git a/ui/src/app/metadata/filter/effect/filter.effect.ts b/ui/src/app/metadata/filter/effect/filter.effect.ts index 87b5a7f1c..1698333dd 100644 --- a/ui/src/app/metadata/filter/effect/filter.effect.ts +++ b/ui/src/app/metadata/filter/effect/filter.effect.ts @@ -58,18 +58,19 @@ export class FilterEffects { ) ); + /* @Effect({ dispatch: false }) saveFilterSuccess$ = this.actions$.pipe( ofType(FilterCollectionActionTypes.ADD_FILTER_SUCCESS), switchMap(() => this.router.navigate(['/dashboard'])) ); - @Effect() cancelChanges$ = this.actions$.pipe( ofType(FilterActionTypes.CANCEL_CREATE_FILTER), map(() => new LoadFilterRequest()), tap(() => this.router.navigate(['/dashboard'])) ); + */ constructor( private store: Store, diff --git a/ui/src/app/metadata/filter/filter.module.ts b/ui/src/app/metadata/filter/filter.module.ts index 22a7ed992..24111a113 100644 --- a/ui/src/app/metadata/filter/filter.module.ts +++ b/ui/src/app/metadata/filter/filter.module.ts @@ -19,27 +19,7 @@ import { SearchIdEffects } from './effect/search.effect'; import { FilterExistsGuard } from './guard/filter-exists.guard'; import { DomainModule } from '../domain/domain.module'; import { ModuleWithProviders } from '@angular/compiler/src/core'; - - -export const routes: Routes = [ - { - path: 'new', - component: NewFilterComponent, - canActivate: [] - }, - { - path: ':id', - component: FilterComponent, - canActivate: [FilterExistsGuard], - children: [ - { - path: 'edit', - component: EditFilterComponent, - canDeactivate: [] - } - ] - } -]; +import { FilterCollectionEffects } from './effect/collection.effect'; @NgModule({ declarations: [ @@ -77,9 +57,8 @@ export class FilterModule { @NgModule({ imports: [ FilterModule, - RouterModule.forChild(routes), StoreModule.forFeature('filter', reducers), - EffectsModule.forFeature([FilterEffects, SearchIdEffects]), + EffectsModule.forFeature([FilterEffects, SearchIdEffects, FilterCollectionEffects]), ], }) export class RootFilterModule { } diff --git a/ui/src/app/metadata/filter/reducer/collection.reducer.spec.ts b/ui/src/app/metadata/filter/reducer/collection.reducer.spec.ts index 0a5d9f74f..abd35ab48 100644 --- a/ui/src/app/metadata/filter/reducer/collection.reducer.spec.ts +++ b/ui/src/app/metadata/filter/reducer/collection.reducer.spec.ts @@ -1,20 +1,19 @@ -import { reducer } from './collection.reducer'; +import { reducer, initialState as snapshot } from './collection.reducer'; import * as fromFilter from './collection.reducer'; import { FilterCollectionActionTypes, LoadFilterSuccess, UpdateFilterSuccess, - SelectFilter + SelectFilter, + SelectFilterSuccess, + AddFilterRequest, + UpdateFilterRequest, + AddFilterSuccess, + AddFilterFail, + UpdateFilterFail } from '../action/collection.action'; import { EntityAttributesFilter } from '../../domain/entity/filter/entity-attributes-filter'; -const snapshot: fromFilter.CollectionState = { - ids: [], - entities: {}, - selectedFilterId: null, - loaded: false -}; - describe('Filter Reducer', () => { describe('undefined action', () => { it('should return the default state', () => { @@ -37,8 +36,8 @@ describe('Filter Reducer', () => { it('should add the loaded filters to the collection', () => { spyOn(fromFilter.adapter, 'addAll').and.callThrough(); const filters = [ - new EntityAttributesFilter({ id: 'foo', createdDate: new Date().toLocaleDateString() }), - new EntityAttributesFilter({ id: 'bar', createdDate: new Date().toLocaleDateString() }) + new EntityAttributesFilter({ resourceId: 'foo', createdDate: new Date().toLocaleDateString() }), + new EntityAttributesFilter({ resourceId: 'bar', createdDate: new Date().toLocaleDateString() }) ]; const action = new LoadFilterSuccess(filters); const result = reducer(snapshot, action); @@ -46,12 +45,60 @@ describe('Filter Reducer', () => { }); }); + describe(`${FilterCollectionActionTypes.SELECT_FILTER_SUCCESS}`, () => { + it('should add the loaded filter to the collection', () => { + spyOn(fromFilter.adapter, 'addOne').and.callThrough(); + const filter = new EntityAttributesFilter({ resourceId: 'foo', createdDate: new Date().toLocaleDateString() }); + const action = new SelectFilterSuccess(filter); + const result = reducer(snapshot, action); + expect(fromFilter.adapter.addOne).toHaveBeenCalled(); + }); + }); + + describe(`${FilterCollectionActionTypes.ADD_FILTER}`, () => { + it('should set saving to true', () => { + const filter = new EntityAttributesFilter({ resourceId: 'foo', createdDate: new Date().toLocaleDateString() }); + const action = new AddFilterRequest(filter); + expect(reducer(snapshot, action).saving).toBe(true); + }); + }); + describe(`${FilterCollectionActionTypes.UPDATE_FILTER_REQUEST}`, () => { + it('should set saving to true', () => { + const filter = new EntityAttributesFilter({ resourceId: 'foo', createdDate: new Date().toLocaleDateString() }); + const action = new UpdateFilterRequest(filter); + expect(reducer(snapshot, action).saving).toBe(true); + }); + }); + + describe(`${FilterCollectionActionTypes.ADD_FILTER_SUCCESS}`, () => { + it('should set saving to false', () => { + const filter = new EntityAttributesFilter({ resourceId: 'foo', createdDate: new Date().toLocaleDateString() }); + const action = new AddFilterSuccess(filter); + expect(reducer(snapshot, action).saving).toBe(false); + }); + }); + + describe(`${FilterCollectionActionTypes.ADD_FILTER_FAIL}`, () => { + it('should set saving to false', () => { + const action = new AddFilterFail(new Error('error')); + expect(reducer(snapshot, action).saving).toBe(false); + }); + }); + + describe(`${FilterCollectionActionTypes.UPDATE_FILTER_FAIL}`, () => { + it('should set saving to false', () => { + const filter = new EntityAttributesFilter({ resourceId: 'foo', createdDate: new Date().toLocaleDateString() }); + const action = new UpdateFilterFail(filter); + expect(reducer(snapshot, action).saving).toBe(false); + }); + }); + describe(`${FilterCollectionActionTypes.UPDATE_FILTER_SUCCESS}`, () => { - it('should add the loaded filters to the collection', () => { + it('should update the filter in the collection', () => { spyOn(fromFilter.adapter, 'updateOne').and.callThrough(); const update = { id: 'foo', - changes: new EntityAttributesFilter({ id: 'foo', name: 'bar', createdDate: new Date().toLocaleDateString() }), + changes: new EntityAttributesFilter({ resourceId: 'foo', name: 'bar', createdDate: new Date().toLocaleDateString() }), }; const action = new UpdateFilterSuccess(update); const result = reducer(snapshot, action); diff --git a/ui/src/app/metadata/filter/reducer/collection.reducer.ts b/ui/src/app/metadata/filter/reducer/collection.reducer.ts index 2eeff6ff1..e71f0947c 100644 --- a/ui/src/app/metadata/filter/reducer/collection.reducer.ts +++ b/ui/src/app/metadata/filter/reducer/collection.reducer.ts @@ -5,6 +5,7 @@ import { MetadataFilter } from '../../domain/model'; export interface CollectionState extends EntityState { selectedFilterId: string | null; loaded: boolean; + saving: boolean; } export function sortByDate(a: MetadataFilter, b: MetadataFilter): number { @@ -18,7 +19,8 @@ export const adapter: EntityAdapter = createEntityAdapter state.selectedFilterId; export const getIsLoaded = (state: CollectionState) => state.loaded; +export const getIsSaving = (state: CollectionState) => state.saving; export const { selectIds: selectFilterIds, selectEntities: selectFilterEntities, diff --git a/ui/src/app/metadata/filter/reducer/index.ts b/ui/src/app/metadata/filter/reducer/index.ts index 5ae10da51..6724af3a7 100644 --- a/ui/src/app/metadata/filter/reducer/index.ts +++ b/ui/src/app/metadata/filter/reducer/index.ts @@ -50,8 +50,26 @@ export const getViewingMore = createSelector(getSearchFromState, fromSearch.getV */ export const getCollectionState = createSelector(getFilterState, getCollectionFromStateFn); export const getAllFilters = createSelector(getCollectionState, fromCollection.selectAllFilters); +export const getFiltersSaving = createSelector(getCollectionState, fromCollection.getIsSaving); + +export const notAddtlFilters = ['RequiredValidUntil', 'SignatureValidation', 'EntityRoleWhiteList']; + +export const getAdditionalFilters = createSelector( + getAllFilters, + filters => filters.filter(f => notAddtlFilters.indexOf(f['@type']) === -1) +); + + export const getFilterEntities = createSelector(getCollectionState, fromCollection.selectFilterEntities); export const getSelectedFilterId = createSelector(getCollectionState, fromCollection.getSelectedFilterId); export const getSelectedFilter = createSelector(getFilterEntities, getSelectedFilterId, utils.getInCollectionFn); export const getFilterIds = createSelector(getCollectionState, fromCollection.selectFilterIds); export const getFilterCollectionIsLoaded = createSelector(getCollectionState, fromCollection.getIsLoaded); + +/* + * Combine pieces of State +*/ + +export const mergeFn = (changes, filter) => ({ ...filter, ...changes }); + +export const getFilterWithChanges = createSelector(getFilter, getSelectedFilter, mergeFn); diff --git a/ui/src/app/metadata/manager/component/provider-item.component.html b/ui/src/app/metadata/manager/component/provider-item.component.html index 32cfd1d21..537ca75af 100644 --- a/ui/src/app/metadata/manager/component/provider-item.component.html +++ b/ui/src/app/metadata/manager/component/provider-item.component.html @@ -28,7 +28,7 @@
- + +
+ + +
+ +
+ diff --git a/ui/src/app/metadata/provider/component/provider-editor-nav.component.ts b/ui/src/app/metadata/provider/component/provider-editor-nav.component.ts new file mode 100644 index 000000000..fd244a18f --- /dev/null +++ b/ui/src/app/metadata/provider/component/provider-editor-nav.component.ts @@ -0,0 +1,50 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { Store } from '@ngrx/store'; + +import { Observable } from 'rxjs'; +import { skipWhile, combineLatest, map } from 'rxjs/operators'; + +import * as fromProvider from '../reducer'; +import { WizardStep, Wizard } from '../../../wizard/model'; +import * as fromWizard from '../../../wizard/reducer'; +import { MetadataProvider } from '../../domain/model'; + +export enum NAV_FORMATS { + DROPDOWN = 'NAV_DROPDOWN', + TABS = 'NAV_TABS' +} + +@Component({ + selector: 'provider-editor-nav', + templateUrl: './provider-editor-nav.component.html', + styleUrls: [] +}) + +export class ProviderEditorNavComponent { + @Input() format: string; + + @Output() onPageSelect: EventEmitter = new EventEmitter(); + + formats = NAV_FORMATS; + + currentPage$: Observable; + index$: Observable; + invalidForms$: Observable; + routes$: Observable<{ path: string, label: string }[]>; + + constructor( + private store: Store + ) { + this.index$ = this.store.select(fromWizard.getWizardIndex).pipe(skipWhile(i => !i)); + this.routes$ = this.store.select(fromWizard.getRoutes); + this.currentPage$ = this.store.select(fromWizard.getCurrent).pipe( + map(p => p ? p.id : 'filters') + ); + this.invalidForms$ = this.store.select(fromProvider.getInvalidEditorForms); + } + + gotoPage(page: string = ''): void { + this.onPageSelect.emit(page); + } +} + diff --git a/ui/src/app/metadata/provider/container/provider-edit.component.html b/ui/src/app/metadata/provider/container/provider-edit.component.html index 98c8b0f64..6f15638b7 100644 --- a/ui/src/app/metadata/provider/container/provider-edit.component.html +++ b/ui/src/app/metadata/provider/container/provider-edit.component.html @@ -10,31 +10,10 @@
- + +