From 5b37eab657a0c2f32b48c81c6c1a821ba25e62c3 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 17 Jan 2019 14:41:57 -0700 Subject: [PATCH 01/12] Adding structure for source management --- ...on.action.ts => user-collection.action.ts} | 0 ui/src/app/admin/admin.component.ts | 2 +- ui/src/app/admin/admin.module.ts | 10 +- .../component/access-request.component.html | 107 ++++++++---------- .../component/enable-metadata.component.html | 14 +++ .../component/enable-metadata.component.ts | 75 ++++++++++++ .../component/user-management.component.html | 82 ++++++-------- .../component/user-management.component.ts | 2 +- .../container/action-required.component.html | 28 ++++- .../container/action-required.component.ts | 2 - .../container/admin-management.component.html | 16 ++- .../container/admin-management.component.ts | 2 - ...on.effect.ts => user-collection.effect.ts} | 2 +- ui/src/app/admin/reducer/index.ts | 2 +- ...pec.ts => user-collection.reducer.spec.ts} | 6 +- ....reducer.ts => user-collection.reducer.ts} | 2 +- ui/src/app/metadata/manager/manager.module.ts | 3 + 17 files changed, 230 insertions(+), 125 deletions(-) rename ui/src/app/admin/action/{collection.action.ts => user-collection.action.ts} (100%) create mode 100644 ui/src/app/admin/component/enable-metadata.component.html create mode 100644 ui/src/app/admin/component/enable-metadata.component.ts rename ui/src/app/admin/effect/{collection.effect.ts => user-collection.effect.ts} (98%) rename ui/src/app/admin/reducer/{collection.reducer.spec.ts => user-collection.reducer.spec.ts} (93%) rename ui/src/app/admin/reducer/{collection.reducer.ts => user-collection.reducer.ts} (97%) diff --git a/ui/src/app/admin/action/collection.action.ts b/ui/src/app/admin/action/user-collection.action.ts similarity index 100% rename from ui/src/app/admin/action/collection.action.ts rename to ui/src/app/admin/action/user-collection.action.ts diff --git a/ui/src/app/admin/admin.component.ts b/ui/src/app/admin/admin.component.ts index 038d281b8..e4944bad5 100644 --- a/ui/src/app/admin/admin.component.ts +++ b/ui/src/app/admin/admin.component.ts @@ -3,7 +3,7 @@ import { LoadRoleRequest } from '../core/action/configuration.action'; import * as fromRoot from '../app.reducer'; import { Store } from '@ngrx/store'; -import { LoadAdminRequest } from './action/collection.action'; +import { LoadAdminRequest } from './action/user-collection.action'; @Component({ selector: 'admin-page', diff --git a/ui/src/app/admin/admin.module.ts b/ui/src/app/admin/admin.module.ts index 476575d1b..6ccd53c87 100644 --- a/ui/src/app/admin/admin.module.ts +++ b/ui/src/app/admin/admin.module.ts @@ -11,13 +11,15 @@ import { AdminManagementPageComponent } from './container/admin-management.compo import { AdminComponent } from './admin.component'; import { reducers } from './reducer'; import { AdminService } from './service/admin.service'; -import { AdminCollectionEffects } from './effect/collection.effect'; +import { AdminCollectionEffects } from './effect/user-collection.effect'; import { EffectsModule } from '@ngrx/effects'; import { DeleteUserDialogComponent } from './component/delete-user-dialog.component'; import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap'; import { ActionRequiredPageComponent } from './container/action-required.component'; import { AccessRequestComponent } from './component/access-request.component'; import { UserManagementComponent } from './component/user-management.component'; +import { EnableMetadataComponent } from './component/enable-metadata.component'; +import { ManagerModule } from '../metadata/manager/manager.module'; @NgModule({ declarations: [ @@ -26,7 +28,8 @@ import { UserManagementComponent } from './component/user-management.component'; DeleteUserDialogComponent, UserManagementComponent, ActionRequiredPageComponent, - AccessRequestComponent + AccessRequestComponent, + EnableMetadataComponent ], entryComponents: [ DeleteUserDialogComponent @@ -41,7 +44,8 @@ import { UserManagementComponent } from './component/user-management.component'; HttpClientModule, SharedModule, I18nModule, - NgbModalModule + NgbModalModule, + ManagerModule ], providers: [ AdminService diff --git a/ui/src/app/admin/component/access-request.component.html b/ui/src/app/admin/component/access-request.component.html index a9b96b586..7beeee816 100644 --- a/ui/src/app/admin/component/access-request.component.html +++ b/ui/src/app/admin/component/access-request.component.html @@ -1,68 +1,53 @@ -
-
-
-
-
- User Access Request -
-
+ + +
+
+

There are no new user requests at this time.

- -
-
-

There are no new user requests at this time.

-
-
-
- -
-
-
-
-
-
- UserId -
-
{{ user.username }}
-
- Email -
-
{{ user.emailAddress }}
-
-
-
-
- Name -
-
{{ user.firstName }} {{ user.lastName }}
- -
- -
-
+
+ + +
+
+
+
+
+
+ UserId
-
- +
{{ user.username }}
+
+ Email +
+
{{ user.emailAddress }}
+
+
+
+
+ Name +
+
{{ user.firstName }} {{ user.lastName }}
+ +
+
+
+ +
- +
-
\ No newline at end of file + \ No newline at end of file diff --git a/ui/src/app/admin/component/enable-metadata.component.html b/ui/src/app/admin/component/enable-metadata.component.html new file mode 100644 index 000000000..2346caa2e --- /dev/null +++ b/ui/src/app/admin/component/enable-metadata.component.html @@ -0,0 +1,14 @@ + \ No newline at end of file diff --git a/ui/src/app/admin/component/enable-metadata.component.ts b/ui/src/app/admin/component/enable-metadata.component.ts new file mode 100644 index 000000000..d963a0a81 --- /dev/null +++ b/ui/src/app/admin/component/enable-metadata.component.ts @@ -0,0 +1,75 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; +import { Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { MetadataEntity, MetadataResolver } from '../../metadata/domain/model'; +import * as fromDashboard from '../../metadata/manager/reducer'; +import { ToggleEntityDisplay } from '../../metadata/manager/action/manager.action'; +import { DeleteDialogComponent } from '../../metadata/manager/component/delete-dialog.component'; +import { PreviewEntity } from '../../metadata/domain/action/entity.action'; +import { RemoveDraftRequest } from '../../metadata/resolver/action/draft.action'; +import { LoadResolverRequest } from '../../metadata/resolver/action/collection.action'; + +@Component({ + selector: 'enable-metadata', + templateUrl: './enable-metadata.component.html' +}) + +export class EnableMetadataComponent implements OnInit { + searchQuery$: Observable; + resolvers$: Observable; + loading$: Observable; + + total$: Observable; + page = 1; + limit = 8; + limited$: Observable; + + entitiesOpen$: Observable<{ [key: string]: boolean }>; + + constructor( + private store: Store, + private router: Router, + private modalService: NgbModal + ) { + this.resolvers$ = store.select(fromDashboard.getSearchResults); + this.searchQuery$ = store.select(fromDashboard.getSearchQuery); + this.loading$ = store.select(fromDashboard.getSearchLoading); + this.entitiesOpen$ = store.select(fromDashboard.getOpenProviders); + + this.total$ = this.resolvers$.pipe(map(list => list.length)); + } + + ngOnInit(): void { + this.store.dispatch(new LoadResolverRequest()); + } + + edit(entity: MetadataEntity): void { + this.router.navigate(['metadata', 'resolver', entity.getId(), 'edit']); + } + + toggleEntity(entity: MetadataEntity): void { + this.store.dispatch(new ToggleEntityDisplay(entity.getId())); + } + + openPreviewDialog(entity: MetadataEntity): void { + this.store.dispatch(new PreviewEntity({ id: entity.getId(), entity })); + } + + deleteResolver(entity: MetadataResolver): void { + this.modalService + .open(DeleteDialogComponent) + .result + .then( + success => { + this.store.dispatch(new RemoveDraftRequest(entity)); + }, + err => { + console.log('Cancelled'); + } + ); + } +} diff --git a/ui/src/app/admin/component/user-management.component.html b/ui/src/app/admin/component/user-management.component.html index 86ecfb6e0..14d1d52a6 100644 --- a/ui/src/app/admin/component/user-management.component.html +++ b/ui/src/app/admin/component/user-management.component.html @@ -1,47 +1,35 @@ -
-
-
-
-
- User Maintenance -
-
-
-
-
-

There are no users configured in the system for you to manage.

-
- - - - - - - - - - - - - - - - - - - -
UserIdNameEmailRoleDelete?
{{ user.username }}{{ user.firstName }} {{ user.lastName }}{{ user.emailAddress }} - - - -
-
-
-
\ No newline at end of file +
+

There are no users configured in the system for you to manage.

+
+ + + + + + + + + + + + + + + + + + + +
UserIdNameEmailRoleDelete?
{{ user.username }}{{ user.firstName }} {{ user.lastName }}{{ user.emailAddress }} + + + +
+ diff --git a/ui/src/app/admin/component/user-management.component.ts b/ui/src/app/admin/component/user-management.component.ts index 356f062ba..cecefa5ae 100644 --- a/ui/src/app/admin/component/user-management.component.ts +++ b/ui/src/app/admin/component/user-management.component.ts @@ -6,7 +6,7 @@ import * as fromRoot from '../../app.reducer'; import * as fromCore from '../../core/reducer'; import * as fromAdmin from '../reducer'; -import { UpdateAdminRequest, RemoveAdminRequest } from '../action/collection.action'; +import { UpdateAdminRequest, RemoveAdminRequest } from '../action/user-collection.action'; import { Admin } from '../model/admin'; import { DeleteUserDialogComponent } from '../component/delete-user-dialog.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; diff --git a/ui/src/app/admin/container/action-required.component.html b/ui/src/app/admin/container/action-required.component.html index 4bb706337..0d5c70823 100644 --- a/ui/src/app/admin/container/action-required.component.html +++ b/ui/src/app/admin/container/action-required.component.html @@ -1 +1,27 @@ - \ No newline at end of file +
+
+
+
+
+ Enable Metadata Sources +
+
+
+
+ +
+
+
+
+
+
+
+
+ User Access Request +
+
+
+ +
+
+ diff --git a/ui/src/app/admin/container/action-required.component.ts b/ui/src/app/admin/container/action-required.component.ts index 8f7aab548..f504f36e4 100644 --- a/ui/src/app/admin/container/action-required.component.ts +++ b/ui/src/app/admin/container/action-required.component.ts @@ -3,8 +3,6 @@ import { Store } from '@ngrx/store'; import * as fromRoot from '../../app.reducer'; -import { LoadNewUsersRequest } from '../action/collection.action'; - @Component({ selector: 'action-required-page', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/ui/src/app/admin/container/admin-management.component.html b/ui/src/app/admin/container/admin-management.component.html index 77864802b..8a07130d2 100644 --- a/ui/src/app/admin/container/admin-management.component.html +++ b/ui/src/app/admin/container/admin-management.component.html @@ -1 +1,15 @@ - \ No newline at end of file +
+
+
+
+
+ User Maintenance +
+
+
+
+ +
+
+
+ diff --git a/ui/src/app/admin/container/admin-management.component.ts b/ui/src/app/admin/container/admin-management.component.ts index 2773eadc2..1e29338bf 100644 --- a/ui/src/app/admin/container/admin-management.component.ts +++ b/ui/src/app/admin/container/admin-management.component.ts @@ -3,8 +3,6 @@ import { Store } from '@ngrx/store'; import * as fromRoot from '../../app.reducer'; -import { LoadAdminRequest } from '../action/collection.action'; - @Component({ selector: 'admin-management-page', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/ui/src/app/admin/effect/collection.effect.ts b/ui/src/app/admin/effect/user-collection.effect.ts similarity index 98% rename from ui/src/app/admin/effect/collection.effect.ts rename to ui/src/app/admin/effect/user-collection.effect.ts index 410166ed5..3fb2b966c 100644 --- a/ui/src/app/admin/effect/collection.effect.ts +++ b/ui/src/app/admin/effect/user-collection.effect.ts @@ -13,7 +13,7 @@ import { RemoveAdminRequest, RemoveAdminSuccess, LoadNewUsersRequest -} from '../action/collection.action'; +} from '../action/user-collection.action'; import { AdminService } from '../service/admin.service'; diff --git a/ui/src/app/admin/reducer/index.ts b/ui/src/app/admin/reducer/index.ts index 31a5265ea..f647e200e 100644 --- a/ui/src/app/admin/reducer/index.ts +++ b/ui/src/app/admin/reducer/index.ts @@ -1,6 +1,6 @@ import { createSelector, createFeatureSelector } from '@ngrx/store'; import * as fromRoot from '../../core/reducer'; -import * as fromCollection from './collection.reducer'; +import * as fromCollection from './user-collection.reducer'; export interface AdminState { collection: fromCollection.CollectionState; diff --git a/ui/src/app/admin/reducer/collection.reducer.spec.ts b/ui/src/app/admin/reducer/user-collection.reducer.spec.ts similarity index 93% rename from ui/src/app/admin/reducer/collection.reducer.spec.ts rename to ui/src/app/admin/reducer/user-collection.reducer.spec.ts index ac0349889..34b75defd 100644 --- a/ui/src/app/admin/reducer/collection.reducer.spec.ts +++ b/ui/src/app/admin/reducer/user-collection.reducer.spec.ts @@ -1,11 +1,11 @@ -import { reducer, initialState as snapshot } from './collection.reducer'; -import * as fromAdmin from './collection.reducer'; +import { reducer, initialState as snapshot } from './user-collection.reducer'; +import * as fromAdmin from './user-collection.reducer'; import { AdminCollectionActionTypes, LoadAdminSuccess, UpdateAdminSuccess, RemoveAdminSuccess -} from '../action/collection.action'; +} from '../action/user-collection.action'; import { Admin } from '../model/admin'; let users = [ diff --git a/ui/src/app/admin/reducer/collection.reducer.ts b/ui/src/app/admin/reducer/user-collection.reducer.ts similarity index 97% rename from ui/src/app/admin/reducer/collection.reducer.ts rename to ui/src/app/admin/reducer/user-collection.reducer.ts index 70d8ba9a4..e11be45ef 100644 --- a/ui/src/app/admin/reducer/collection.reducer.ts +++ b/ui/src/app/admin/reducer/user-collection.reducer.ts @@ -1,6 +1,6 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; import { Admin } from '../model/admin'; -import { AdminCollectionActionsUnion, AdminCollectionActionTypes } from '../action/collection.action'; +import { AdminCollectionActionsUnion, AdminCollectionActionTypes } from '../action/user-collection.action'; export interface CollectionState extends EntityState { selectedAdminId: string | null; diff --git a/ui/src/app/metadata/manager/manager.module.ts b/ui/src/app/metadata/manager/manager.module.ts index 778d18c2d..e4e447641 100644 --- a/ui/src/app/metadata/manager/manager.module.ts +++ b/ui/src/app/metadata/manager/manager.module.ts @@ -45,6 +45,9 @@ import { I18nModule } from '../../i18n/i18n.module'; HttpClientModule, SharedModule, I18nModule + ], + exports: [ + ResolverItemComponent ] }) export class ManagerModule { From 2a7ae3d9ae23e055aef8b2a6d59a115b98657ba8 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 21 Jan 2019 10:42:28 -0700 Subject: [PATCH 02/12] Implemented custom action for admin resolvers --- .../container/dashboard.component.ts | 2 +- .../domain/service/resolver.service.ts | 2 +- .../resolver/action/collection.action.ts | 16 +++- .../resolver/effect/collection.effects.ts | 91 +++++++++++++------ 4 files changed, 77 insertions(+), 34 deletions(-) diff --git a/ui/src/app/dashboard/container/dashboard.component.ts b/ui/src/app/dashboard/container/dashboard.component.ts index 56a7013fe..b0e71e770 100644 --- a/ui/src/app/dashboard/container/dashboard.component.ts +++ b/ui/src/app/dashboard/container/dashboard.component.ts @@ -4,7 +4,7 @@ import { Store } from '@ngrx/store'; import * as fromRoot from '../../app.reducer'; import * as fromAdmin from '../../admin/reducer'; import { Observable } from 'rxjs'; -import { LoadAdminRequest } from '../../admin/action/collection.action'; +import { LoadAdminRequest } from '../../admin/action/user-collection.action'; import { LoadRoleRequest } from '../../core/action/configuration.action'; import { map } from 'rxjs/operators'; diff --git a/ui/src/app/metadata/domain/service/resolver.service.ts b/ui/src/app/metadata/domain/service/resolver.service.ts index 895e086f0..3c3e48602 100644 --- a/ui/src/app/metadata/domain/service/resolver.service.ts +++ b/ui/src/app/metadata/domain/service/resolver.service.ts @@ -14,7 +14,7 @@ export class ResolverService { private http: HttpClient ) {} - query(): Observable { + query(opts: any = {}): Observable { return this.http.get(`${ this.base }${ this.endpoint }s`, {}) .pipe( catchError(err => throwError([])) diff --git a/ui/src/app/metadata/resolver/action/collection.action.ts b/ui/src/app/metadata/resolver/action/collection.action.ts index 9ce6c31d4..dc3843319 100644 --- a/ui/src/app/metadata/resolver/action/collection.action.ts +++ b/ui/src/app/metadata/resolver/action/collection.action.ts @@ -12,12 +12,15 @@ export enum ResolverCollectionActionTypes { UPDATE_RESOLVER_FAIL = '[Metadata Resolver] Update Fail', UPDATE_RESOLVER_CONFLICT = '[Metadata Resolver] Update Conflict', - LOAD_RESOLVER_REQUEST = '[Metadata Resolver Collection] Resolver REQUEST', - LOAD_RESOLVER_SUCCESS = '[Metadata Resolver Collection] Resolver SUCCESS', - LOAD_RESOLVER_ERROR = '[Metadata Resolver Collection] Resolver ERROR', + LOAD_RESOLVER_REQUEST = '[Metadata Resolver Collection] Load Resolver REQUEST', + LOAD_RESOLVER_SUCCESS = '[Metadata Resolver Collection] Load Resolver SUCCESS', + LOAD_RESOLVER_ERROR = '[Metadata Resolver Collection] Load Resolver ERROR', + LOAD_ADMIN_RESOLVERS_REQUEST = '[Metadata Resolver Collection] Load Admin Resolver REQUEST', + ADD_RESOLVER = '[Metadata Resolver Collection] Add Resolver', ADD_RESOLVER_SUCCESS = '[Metadata Resolver Collection] Add Resolver Success', ADD_RESOLVER_FAIL = '[Metadata Resolver Collection] Add Resolver Fail', + REMOVE_RESOLVER = '[Metadata Resolver Collection] Remove Resolver', REMOVE_RESOLVER_SUCCESS = '[Metadata Resolver Collection] Remove Resolver Success', REMOVE_RESOLVER_FAIL = '[Metadata Resolver Collection] Remove Resolver Fail', @@ -50,6 +53,12 @@ export class LoadResolverRequest implements Action { constructor() { } } +export class LoadAdminResolverRequest implements Action { + readonly type = ResolverCollectionActionTypes.LOAD_ADMIN_RESOLVERS_REQUEST; + + constructor() { } +} + export class LoadResolverSuccess implements Action { readonly type = ResolverCollectionActionTypes.LOAD_RESOLVER_SUCCESS; @@ -138,6 +147,7 @@ export type ResolverCollectionActionsUnion = | LoadResolverRequest | LoadResolverSuccess | LoadResolverError + | LoadAdminResolverRequest | AddResolverRequest | AddResolverSuccess | AddResolverFail diff --git a/ui/src/app/metadata/resolver/effect/collection.effects.ts b/ui/src/app/metadata/resolver/effect/collection.effects.ts index 608d1d442..ac5bc87c8 100644 --- a/ui/src/app/metadata/resolver/effect/collection.effects.ts +++ b/ui/src/app/metadata/resolver/effect/collection.effects.ts @@ -5,9 +5,29 @@ import { Store } from '@ngrx/store'; import { of } from 'rxjs'; import { map, catchError, switchMap, tap, withLatestFrom } from 'rxjs/operators'; -import * as providerActions from '../action/collection.action'; +import { + ResolverCollectionActionTypes, + LoadResolverRequest, + LoadResolverSuccess, + LoadResolverError, + LoadAdminResolverRequest, + AddResolverRequest, + AddResolverSuccess, + AddResolverFail, + RemoveResolverRequest, + RemoveResolverSuccess, + RemoveResolverFail, + SelectResolver, + SelectResolverSuccess, + UpdateResolverRequest, + UpdateResolverSuccess, + UpdateResolverFail, + UpdateResolverConflict, + UploadResolverRequest, + CreateResolverFromUrlRequest +} from '../action/collection.action'; import * as draftActions from '../action/draft.action'; -import { ResolverCollectionActionTypes } from '../action/collection.action'; +import { } from '../action/collection.action'; import { ResolverService } from '../../domain/service/resolver.service'; import { removeNulls } from '../../../shared/util'; import { AddNotification } from '../../../notification/action/notification.action'; @@ -23,35 +43,48 @@ export class ResolverCollectionEffects { @Effect() loadResolvers$ = this.actions$.pipe( - ofType(ResolverCollectionActionTypes.LOAD_RESOLVER_REQUEST), + ofType(ResolverCollectionActionTypes.LOAD_RESOLVER_REQUEST), switchMap(() => this.descriptorService .query() .pipe( - map(descriptors => new providerActions.LoadResolverSuccess(descriptors)), - catchError(error => of(new providerActions.LoadResolverError(error))) + map(descriptors => new LoadResolverSuccess(descriptors)), + catchError(error => of(new LoadResolverError(error))) + ) + ) + ); + + @Effect() + loadAdminResolvers$ = this.actions$.pipe( + ofType(ResolverCollectionActionTypes.LOAD_RESOLVER_REQUEST), + switchMap(() => + this.descriptorService + .query({admin: true}) + .pipe( + map(descriptors => new LoadResolverSuccess(descriptors)), + catchError(error => of(new LoadResolverError(error))) ) ) ); @Effect() updateResolver$ = this.actions$.pipe( - ofType(ResolverCollectionActionTypes.UPDATE_RESOLVER_REQUEST), + ofType(ResolverCollectionActionTypes.UPDATE_RESOLVER_REQUEST), map(action => action.payload), switchMap(provider => { return this.descriptorService .update(provider) .pipe( - map(p => new providerActions.UpdateResolverSuccess({ + map(p => new UpdateResolverSuccess({ id: p.id, changes: p })), catchError(err => { if (err.status === 409) { - return of(new providerActions.UpdateResolverConflict(provider)); + return of(new UpdateResolverConflict(provider)); } console.log(err); - return of(new providerActions.UpdateResolverFail({ + return of(new UpdateResolverFail({ errorCode: err.status, errorMessage: `${err.statusText} - ${err.message}` })); @@ -62,21 +95,21 @@ export class ResolverCollectionEffects { @Effect({ dispatch: false }) updateResolverSuccessRedirect$ = this.actions$.pipe( - ofType(ResolverCollectionActionTypes.UPDATE_RESOLVER_SUCCESS), + ofType(ResolverCollectionActionTypes.UPDATE_RESOLVER_SUCCESS), map(action => action.payload), tap(provider => this.router.navigate(['dashboard'])) ); @Effect() updateResolverSuccessReload$ = this.actions$.pipe( - ofType(ResolverCollectionActionTypes.UPDATE_RESOLVER_SUCCESS), + ofType(ResolverCollectionActionTypes.UPDATE_RESOLVER_SUCCESS), map(action => action.payload), - map(provider => new providerActions.LoadResolverRequest()) + map(provider => new LoadResolverRequest()) ); @Effect() updateResolverFailNotification$ = this.actions$.pipe( - ofType(ResolverCollectionActionTypes.UPDATE_RESOLVER_FAIL), + ofType(ResolverCollectionActionTypes.UPDATE_RESOLVER_FAIL), map(action => action.payload), withLatestFrom(this.store.select(fromI18n.getMessages)), map(([error, messages]) => new AddNotification( @@ -90,20 +123,20 @@ export class ResolverCollectionEffects { @Effect() selectResolver$ = this.actions$.pipe( - ofType(ResolverCollectionActionTypes.SELECT), + ofType(ResolverCollectionActionTypes.SELECT), map(action => action.payload), switchMap(id => this.descriptorService .find(id) .pipe( - map(p => new providerActions.SelectResolverSuccess(p)) + map(p => new SelectResolverSuccess(p)) ) ) ); @Effect() addResolverRequest$ = this.actions$.pipe( - ofType(ResolverCollectionActionTypes.ADD_RESOLVER), + ofType(ResolverCollectionActionTypes.ADD_RESOLVER), map(action => action.payload), map(provider => { return ({ @@ -115,28 +148,28 @@ export class ResolverCollectionEffects { this.descriptorService .save(provider) .pipe( - map(p => new providerActions.AddResolverSuccess(p)), - catchError(() => of(new providerActions.AddResolverFail(provider))) + map(p => new AddResolverSuccess(p)), + catchError(() => of(new AddResolverFail(provider))) ) ) ); @Effect({ dispatch: false }) addResolverSuccessRedirect$ = this.actions$.pipe( - ofType(ResolverCollectionActionTypes.ADD_RESOLVER_SUCCESS), + ofType(ResolverCollectionActionTypes.ADD_RESOLVER_SUCCESS), map(action => action.payload), tap(provider => this.router.navigate(['dashboard'])) ); @Effect() addResolverSuccessReload$ = this.actions$.pipe( - ofType(ResolverCollectionActionTypes.ADD_RESOLVER_SUCCESS), + ofType(ResolverCollectionActionTypes.ADD_RESOLVER_SUCCESS), map(action => action.payload), - map(provider => new providerActions.LoadResolverRequest()) + map(provider => new LoadResolverRequest()) ); @Effect() addResolverSuccessRemoveDraft$ = this.actions$.pipe( - ofType(ResolverCollectionActionTypes.ADD_RESOLVER_SUCCESS), + ofType(ResolverCollectionActionTypes.ADD_RESOLVER_SUCCESS), map(action => action.payload), map(provider => { return new draftActions.RemoveDraftRequest(provider); @@ -145,7 +178,7 @@ export class ResolverCollectionEffects { @Effect() addResolverFailNotification$ = this.actions$.pipe( - ofType(ResolverCollectionActionTypes.ADD_RESOLVER_FAIL), + ofType(ResolverCollectionActionTypes.ADD_RESOLVER_FAIL), map(action => action.payload), withLatestFrom(this.store.select(fromI18n.getMessages)), map(([error, messages]) => new AddNotification( @@ -159,28 +192,28 @@ export class ResolverCollectionEffects { @Effect() uploadResolverRequest$ = this.actions$.pipe( - ofType(ResolverCollectionActionTypes.UPLOAD_RESOLVER_REQUEST), + ofType(ResolverCollectionActionTypes.UPLOAD_RESOLVER_REQUEST), map(action => action.payload), switchMap(file => this.descriptorService .upload(file.name, file.body) .pipe( - map(p => new providerActions.AddResolverSuccess(p)), - catchError((error) => of(new providerActions.AddResolverFail(error))) + map(p => new AddResolverSuccess(p)), + catchError((error) => of(new AddResolverFail(error))) ) ) ); @Effect() createResolverFromUrlRequest$ = this.actions$.pipe( - ofType(ResolverCollectionActionTypes.CREATE_RESOLVER_FROM_URL_REQUEST), + ofType(ResolverCollectionActionTypes.CREATE_RESOLVER_FROM_URL_REQUEST), map(action => action.payload), switchMap(file => this.descriptorService .createFromUrl(file.name, file.url) .pipe( - map(p => new providerActions.AddResolverSuccess(p)), - catchError((error) => of(new providerActions.AddResolverFail(error))) + map(p => new AddResolverSuccess(p)), + catchError((error) => of(new AddResolverFail(error))) ) ) ); From 42d3e0dcc41e5244b445d717c3a45f42789f4b1e Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 21 Jan 2019 12:32:01 -0700 Subject: [PATCH 03/12] SHIBUI-1063 Loading resolvers into admin dashboard for approval --- ui/src/app/admin/admin.component.ts | 2 +- .../app/admin/component/enable-metadata.component.html | 2 +- .../app/admin/component/enable-metadata.component.ts | 10 ++++------ .../app/metadata/resolver/effect/collection.effects.ts | 5 +++-- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/ui/src/app/admin/admin.component.ts b/ui/src/app/admin/admin.component.ts index e4944bad5..fa51290b0 100644 --- a/ui/src/app/admin/admin.component.ts +++ b/ui/src/app/admin/admin.component.ts @@ -16,6 +16,6 @@ export class AdminComponent implements OnInit { ) { } ngOnInit(): void { - this.store.dispatch(new LoadAdminRequest()); + // this.store.dispatch(new LoadAdminRequest()); } } diff --git a/ui/src/app/admin/component/enable-metadata.component.html b/ui/src/app/admin/component/enable-metadata.component.html index 2346caa2e..6ab978649 100644 --- a/ui/src/app/admin/component/enable-metadata.component.html +++ b/ui/src/app/admin/component/enable-metadata.component.html @@ -1,5 +1,5 @@
    -
  • ; resolvers$: Observable; loading$: Observable; total$: Observable; page = 1; limit = 8; - limited$: Observable; entitiesOpen$: Observable<{ [key: string]: boolean }>; @@ -35,8 +34,7 @@ export class EnableMetadataComponent implements OnInit { private router: Router, private modalService: NgbModal ) { - this.resolvers$ = store.select(fromDashboard.getSearchResults); - this.searchQuery$ = store.select(fromDashboard.getSearchQuery); + this.resolvers$ = store.select(fromResolver.getAllResolvers); this.loading$ = store.select(fromDashboard.getSearchLoading); this.entitiesOpen$ = store.select(fromDashboard.getOpenProviders); @@ -44,7 +42,7 @@ export class EnableMetadataComponent implements OnInit { } ngOnInit(): void { - this.store.dispatch(new LoadResolverRequest()); + this.store.dispatch(new LoadAdminResolverRequest()); } edit(entity: MetadataEntity): void { diff --git a/ui/src/app/metadata/resolver/effect/collection.effects.ts b/ui/src/app/metadata/resolver/effect/collection.effects.ts index ac5bc87c8..e76d2ca98 100644 --- a/ui/src/app/metadata/resolver/effect/collection.effects.ts +++ b/ui/src/app/metadata/resolver/effect/collection.effects.ts @@ -35,6 +35,7 @@ import { Notification, NotificationType } from '../../../notification/model/noti import { I18nService } from '../../../i18n/service/i18n.service'; import * as fromRoot from '../../../app.reducer'; import * as fromI18n from '../../../i18n/reducer'; +import { FileBackedHttpMetadataResolver } from '../../domain/entity'; /* istanbul ignore next */ @@ -56,12 +57,12 @@ export class ResolverCollectionEffects { @Effect() loadAdminResolvers$ = this.actions$.pipe( - ofType(ResolverCollectionActionTypes.LOAD_RESOLVER_REQUEST), + ofType(ResolverCollectionActionTypes.LOAD_ADMIN_RESOLVERS_REQUEST), switchMap(() => this.descriptorService .query({admin: true}) .pipe( - map(descriptors => new LoadResolverSuccess(descriptors)), + map(descriptors => new LoadResolverSuccess(descriptors.map(d => new FileBackedHttpMetadataResolver(d)))), catchError(error => of(new LoadResolverError(error))) ) ) From 2489e4f368536bb78f209d940e7d4d9155067742 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Tue, 22 Jan 2019 15:22:52 -0700 Subject: [PATCH 04/12] [SHIBUI-1063] Added query/test/endpoint to get disabled EntityDescriptors not owned by admin. --- .../admin/ui/controller/EntityDescriptorController.java | 7 +++++++ .../admin/ui/repository/EntityDescriptorRepository.java | 7 +++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java index c3e76983e..c960e6ffd 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java @@ -144,6 +144,13 @@ public ResponseEntity getOneXml(@PathVariable String resourceId) throws Marsh return ResponseEntity.ok(xml); } + @GetMapping(value = "/EntityDescriptor/disabledNonAdmin") + public Iterable getDisabledAndNotOwnedByAdmin() { + return entityDescriptorRepository.findAllDisabledAndNotOwnedByAdmin() + .map(ed -> entityDescriptorService.createRepresentationFromDescriptor(ed)) + .collect(Collectors.toList()); + } + private static URI getResourceUriFor(EntityDescriptor ed) { return ServletUriComponentsBuilder .fromCurrentServletMapping().path("/api/EntityDescriptor") diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java index be729d489..321735b96 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/repository/EntityDescriptorRepository.java @@ -1,8 +1,8 @@ package edu.internet2.tier.shibboleth.admin.ui.repository; import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import java.util.stream.Stream; @@ -10,7 +10,7 @@ /** * Repository to manage {@link EntityDescriptor} instances. */ -public interface EntityDescriptorRepository extends CrudRepository { +public interface EntityDescriptorRepository extends JpaRepository { EntityDescriptor findByEntityID(String entityId); @@ -21,4 +21,7 @@ public interface EntityDescriptorRepository extends CrudRepository findAllByCustomQueryAndStream(); + @Query("select e from EntityDescriptor e, User u join u.roles r " + + "where e.createdBy = u.username and e.serviceEnabled = false and r.name in ('ROLE_USER', 'ROLE_NONE')") + Stream findAllDisabledAndNotOwnedByAdmin(); } From 8e67e9fe72db9351e248d9954a627dba010c5064 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 24 Jan 2019 13:37:08 -0700 Subject: [PATCH 05/12] [SHIBUI-1063] Set createdBy on our two resolver-seeding profiles to be the 'nonadmin' user from the dev profile. --- .../tier/shibboleth/admin/ui/configuration/DevConfig.groovy | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy index bcee9a94c..30091e8de 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy @@ -92,6 +92,7 @@ class DevConfig { @Bean MetadataResolver fbhmr(ModelRepresentationConversions modelRepresentationConversions) { return this.metadataResolverRepository.save(new FileBackedHttpMetadataResolver().with { + it.createdBy = 'nonadmin' // depends on dev profile enabled = true xmlId = 'test-fbhmr' name = 'test-fbhmr' @@ -120,6 +121,7 @@ class DevConfig { @Bean MetadataResolver dhmr(ModelRepresentationConversions modelRepresentationConversions) { return this.metadataResolverRepository.save(new DynamicHttpMetadataResolver().with { + it.createdBy = 'nonadmin' // depends on dev profile it.enabled = true it.xmlId = 'test-dhmr' it.name = 'test-dhmr' From 40998519fa6660b0258f3638206f79529ba953e7 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 24 Jan 2019 13:46:16 -0700 Subject: [PATCH 06/12] Fixed missing actions --- ui/src/app/admin/container/action-required.component.ts | 1 + ui/src/app/admin/container/admin-management.component.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/ui/src/app/admin/container/action-required.component.ts b/ui/src/app/admin/container/action-required.component.ts index 74ec7407e..b10d507ef 100644 --- a/ui/src/app/admin/container/action-required.component.ts +++ b/ui/src/app/admin/container/action-required.component.ts @@ -2,6 +2,7 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Store } from '@ngrx/store'; import * as fromRoot from '../../app.reducer'; +import { LoadNewUsersRequest } from '../action/user-collection.action'; @Component({ selector: 'action-required-page', diff --git a/ui/src/app/admin/container/admin-management.component.ts b/ui/src/app/admin/container/admin-management.component.ts index 95a2e94c8..937cd3c98 100644 --- a/ui/src/app/admin/container/admin-management.component.ts +++ b/ui/src/app/admin/container/admin-management.component.ts @@ -2,6 +2,7 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Store } from '@ngrx/store'; import * as fromRoot from '../../app.reducer'; +import { LoadAdminRequest } from '../action/user-collection.action'; @Component({ selector: 'admin-management-page', From d390b3ec8e99266a8ef8e5a9ab32470845dca02b Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 24 Jan 2019 13:50:57 -0700 Subject: [PATCH 07/12] [SHIBUI-1063] Added a new 'ed' profile with a test EntityDescriptor created by the 'nonadmin' ROLE_USER from the 'dev' profile. --- .../admin/ui/configuration/DevConfig.groovy | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy index 30091e8de..7b1c018da 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/configuration/DevConfig.groovy @@ -1,5 +1,6 @@ package edu.internet2.tier.shibboleth.admin.ui.configuration +import edu.internet2.tier.shibboleth.admin.ui.domain.EntityDescriptor import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilter import edu.internet2.tier.shibboleth.admin.ui.domain.filters.EntityAttributesFilterTarget import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.DynamicHttpMetadataResolver @@ -8,6 +9,7 @@ import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.HttpMetadataResol import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataQueryProtocolScheme import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.ReloadableMetadataResolverAttributes +import edu.internet2.tier.shibboleth.admin.ui.repository.EntityDescriptorRepository import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository import edu.internet2.tier.shibboleth.admin.ui.security.model.Role import edu.internet2.tier.shibboleth.admin.ui.security.model.User @@ -28,11 +30,13 @@ class DevConfig { private final RoleRepository roleRepository private final MetadataResolverRepository metadataResolverRepository + private final EntityDescriptorRepository entityDescriptorRepository - DevConfig(UserRepository adminUserRepository, MetadataResolverRepository metadataResolverRepository, RoleRepository roleRepository) { + DevConfig(UserRepository adminUserRepository, MetadataResolverRepository metadataResolverRepository, RoleRepository roleRepository, EntityDescriptorRepository entityDescriptorRepository) { this.adminUserRepository = adminUserRepository this.metadataResolverRepository = metadataResolverRepository this.roleRepository = roleRepository + this.entityDescriptorRepository = entityDescriptorRepository } @Transactional @@ -92,7 +96,6 @@ class DevConfig { @Bean MetadataResolver fbhmr(ModelRepresentationConversions modelRepresentationConversions) { return this.metadataResolverRepository.save(new FileBackedHttpMetadataResolver().with { - it.createdBy = 'nonadmin' // depends on dev profile enabled = true xmlId = 'test-fbhmr' name = 'test-fbhmr' @@ -121,7 +124,6 @@ class DevConfig { @Bean MetadataResolver dhmr(ModelRepresentationConversions modelRepresentationConversions) { return this.metadataResolverRepository.save(new DynamicHttpMetadataResolver().with { - it.createdBy = 'nonadmin' // depends on dev profile it.enabled = true it.xmlId = 'test-dhmr' it.name = 'test-dhmr' @@ -141,4 +143,17 @@ class DevConfig { return it }) } + + @Profile('ed') + @Transactional + @Bean + EntityDescriptor ed() { + return this.entityDescriptorRepository.save(new EntityDescriptor().with { + it.createdBy = 'nonadmin' + it.entityID = 'testID' + it.serviceEnabled = true + it.serviceProviderName = 'testSP' + it + }) + } } From 2105029fa24e784fa09720243c0cde1a0cc8778e Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 24 Jan 2019 14:29:31 -0700 Subject: [PATCH 08/12] [SHIBUI-1063] Added a forgotten @Transactional --- .../admin/ui/controller/EntityDescriptorController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java index c960e6ffd..e38d03499 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java @@ -144,6 +144,7 @@ public ResponseEntity getOneXml(@PathVariable String resourceId) throws Marsh return ResponseEntity.ok(xml); } + @Transactional @GetMapping(value = "/EntityDescriptor/disabledNonAdmin") public Iterable getDisabledAndNotOwnedByAdmin() { return entityDescriptorRepository.findAllDisabledAndNotOwnedByAdmin() From 3c9063f9e5193e8762566b1b3d77c78407090715 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 24 Jan 2019 15:37:49 -0700 Subject: [PATCH 09/12] [SHIBUI-1063] Added an @Secured delete method for metadata sources. Also pulled in the updated ErrorResponse from 1058. --- .../controller/EntityDescriptorController.java | 17 +++++++++++++++++ .../admin/ui/controller/ErrorResponse.java | 6 ++++++ 2 files changed, 23 insertions(+) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java index e38d03499..3fad62463 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/EntityDescriptorController.java @@ -13,7 +13,9 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.security.access.annotation.Secured; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -26,6 +28,7 @@ import org.springframework.web.servlet.support.ServletUriComponentsBuilder; import javax.annotation.PostConstruct; +import javax.xml.ws.Response; import java.net.URI; import java.util.stream.Collectors; @@ -152,6 +155,20 @@ public Iterable getDisabledAndNotOwnedByAdmin() .collect(Collectors.toList()); } + @Secured("ROLE_ADMIN") + @DeleteMapping(value = "/EntityDescriptor/{resourceId}") + public ResponseEntity deleteOne(@PathVariable String resourceId) { + EntityDescriptor ed = entityDescriptorRepository.findByResourceId(resourceId); + if (ed == null) { + return ResponseEntity.notFound().build(); + } else if (ed.isServiceEnabled()) { + return ResponseEntity.status(HttpStatus.FORBIDDEN).body(new ErrorResponse(HttpStatus.FORBIDDEN, "Deleting an enabled Metadata Source is not allowed. Disable the source and try again.")); + } else { + entityDescriptorRepository.delete(ed); + return ResponseEntity.noContent().build(); + } + } + private static URI getResourceUriFor(EntityDescriptor ed) { return ServletUriComponentsBuilder .fromCurrentServletMapping().path("/api/EntityDescriptor") diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ErrorResponse.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ErrorResponse.java index fa91aa3e6..f3f84169d 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ErrorResponse.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ErrorResponse.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.Setter; import lombok.ToString; +import org.springframework.http.HttpStatus; /** * @author Bill Smith (wsmith@unicon.net) @@ -15,4 +16,9 @@ public class ErrorResponse { private String errorCode; private String errorMessage; + + public ErrorResponse(HttpStatus httpStatus, String errorMessage) { + this.errorCode = String.valueOf(httpStatus.value()); + this.errorMessage = errorMessage; + } } From 67c33c134205a59aabc6dcac63acd76ebaf65722 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 24 Jan 2019 16:30:02 -0700 Subject: [PATCH 10/12] [SHIBUI-1063] Relocated the JsonSchemaValidationFailedException because it wasn't being called in the groovy controller advice class. The generic exception handler was handling it instead. Also fixed handling of the List -> List conversion. Json schema validation errors should return something more useful now. --- ...yOverridesJsonSchemaValidatingControllerAdvice.groovy | 5 ----- .../ui/controller/support/RestControllersSupport.java | 8 +++++++- .../jsonschema/JsonSchemaValidationFailedException.java | 9 ++++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy index e77097af6..9151e4ef4 100644 --- a/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy +++ b/backend/src/main/groovy/edu/internet2/tier/shibboleth/admin/ui/jsonschema/RelyingPartyOverridesJsonSchemaValidatingControllerAdvice.groovy @@ -55,11 +55,6 @@ class RelyingPartyOverridesJsonSchemaValidatingControllerAdvice extends RequestB ] as HttpInputMessage } - @ExceptionHandler(JsonSchemaValidationFailedException) - final ResponseEntity handleJsonSchemaValidationFailedException(JsonSchemaValidationFailedException ex) { - ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponse("400", String.join('\n', ex.errors))) - } - @PostConstruct void init() { this.jsonSchemaLocation = metadataSourcesSchema(this.jsonSchemaResourceLocationRegistry); diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/support/RestControllersSupport.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/support/RestControllersSupport.java index d918594db..ebaa4da08 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/support/RestControllersSupport.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/support/RestControllersSupport.java @@ -3,6 +3,7 @@ import edu.internet2.tier.shibboleth.admin.ui.controller.ErrorResponse; import edu.internet2.tier.shibboleth.admin.ui.domain.exceptions.MetadataFileNotFoundException; import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver; +import edu.internet2.tier.shibboleth.admin.ui.jsonschema.JsonSchemaValidationFailedException; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; @@ -12,8 +13,8 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.client.HttpClientErrorException; -import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; import static org.springframework.http.HttpStatus.BAD_REQUEST; +import static org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR; import static org.springframework.http.HttpStatus.NOT_FOUND; /** @@ -56,4 +57,9 @@ public final ResponseEntity metadataFileNotFoundHandler(MetadataF ErrorResponse errorResponse = new ErrorResponse(INTERNAL_SERVER_ERROR.toString(), ex.getLocalizedMessage()); return new ResponseEntity<>(errorResponse, INTERNAL_SERVER_ERROR); } + + @ExceptionHandler(JsonSchemaValidationFailedException.class) + public final ResponseEntity handleJsonSchemaValidationFailedException(JsonSchemaValidationFailedException ex) { + return ResponseEntity.status(BAD_REQUEST).body(new ErrorResponse("400", String.join("\n", ex.getErrors()))); + } } diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaValidationFailedException.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaValidationFailedException.java index eac2a6d2f..fac8d91fb 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaValidationFailedException.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/jsonschema/JsonSchemaValidationFailedException.java @@ -1,5 +1,7 @@ package edu.internet2.tier.shibboleth.admin.ui.jsonschema; +import lombok.Getter; + import java.util.List; /** @@ -8,11 +10,12 @@ * * @author Dmitriy Kopylenko */ -class JsonSchemaValidationFailedException extends RuntimeException { +@Getter +public class JsonSchemaValidationFailedException extends RuntimeException { List errors; - JsonSchemaValidationFailedException(List errors) { - this.errors = errors; + JsonSchemaValidationFailedException(List errors) { + this.errors = (List) errors; } } From 9fe20779c227c73b47300a9feaffa756e65a8c71 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Fri, 25 Jan 2019 11:07:24 -0700 Subject: [PATCH 11/12] Fixed issues in Enable Metadata Source tab --- .../resources/i18n/messages_en.properties | 4 + ...n.action.ts => admin-collection.action.ts} | 8 -- .../action/metadata-collection.action.ts | 90 ++++++++++++ ui/src/app/admin/admin.component.spec.ts | 12 +- ui/src/app/admin/admin.component.ts | 12 +- ui/src/app/admin/admin.module.ts | 5 +- .../component/enable-metadata.component.html | 5 +- .../component/enable-metadata.component.ts | 31 +++-- .../component/user-management.component.ts | 4 +- .../container/action-required.component.ts | 5 +- .../container/admin-management.component.ts | 6 +- ...n.effect.ts => admin-collection.effect.ts} | 13 +- .../effect/metadata-collection.effect.ts | 128 ++++++++++++++++++ ...ec.ts => admin-collection.reducer.spec.ts} | 6 +- ...reducer.ts => admin-collection.reducer.ts} | 2 +- ui/src/app/admin/reducer/index.spec.ts | 40 ++++++ ui/src/app/admin/reducer/index.ts | 50 +++++-- .../metadata-collection.reducer.spec.ts | 72 ++++++++++ .../reducer/metadata-collection.reducer.ts | 59 ++++++++ ui/src/app/core/model/user.ts | 9 +- ui/src/app/core/reducer/user.reducer.spec.ts | 18 +-- .../container/dashboard.component.ts | 1 - .../domain/service/resolver.service.ts | 11 +- .../component/entity-item.component.ts | 3 + .../component/resolver-item.component.html | 26 +++- .../component/resolver-item.component.ts | 5 +- .../dashboard-resolvers-list.component.html | 3 +- .../resolver/effect/collection.effects.ts | 30 ++-- .../resolver/reducer/collection.reducer.ts | 12 ++ ui/src/app/shared/util.ts | 3 +- 30 files changed, 564 insertions(+), 109 deletions(-) rename ui/src/app/admin/action/{user-collection.action.ts => admin-collection.action.ts} (94%) create mode 100644 ui/src/app/admin/action/metadata-collection.action.ts rename ui/src/app/admin/effect/{user-collection.effect.ts => admin-collection.effect.ts} (88%) create mode 100644 ui/src/app/admin/effect/metadata-collection.effect.ts rename ui/src/app/admin/reducer/{user-collection.reducer.spec.ts => admin-collection.reducer.spec.ts} (93%) rename ui/src/app/admin/reducer/{user-collection.reducer.ts => admin-collection.reducer.ts} (97%) create mode 100644 ui/src/app/admin/reducer/index.spec.ts create mode 100644 ui/src/app/admin/reducer/metadata-collection.reducer.spec.ts create mode 100644 ui/src/app/admin/reducer/metadata-collection.reducer.ts diff --git a/backend/src/main/resources/i18n/messages_en.properties b/backend/src/main/resources/i18n/messages_en.properties index 7a1efb615..81af2f217 100644 --- a/backend/src/main/resources/i18n/messages_en.properties +++ b/backend/src/main/resources/i18n/messages_en.properties @@ -387,6 +387,10 @@ label.role=Role label.delete=Delete? label.delete-request=Delete Request +label.enable=Enable +label.disable=Disable +label.enable-metadata-sources=Enable Metadata Sources + message.delete-user-title=Delete User? message.delete-user-body=You are requesting to delete a user. If you complete this process the user will be removed. This cannot be undone. Do you wish to continue? diff --git a/ui/src/app/admin/action/user-collection.action.ts b/ui/src/app/admin/action/admin-collection.action.ts similarity index 94% rename from ui/src/app/admin/action/user-collection.action.ts rename to ui/src/app/admin/action/admin-collection.action.ts index 8c9c3e420..96c2ae937 100644 --- a/ui/src/app/admin/action/user-collection.action.ts +++ b/ui/src/app/admin/action/admin-collection.action.ts @@ -11,7 +11,6 @@ export enum AdminCollectionActionTypes { UPDATE_ADMIN_SUCCESS = '[Admin Collection] Update Admin Success', UPDATE_ADMIN_FAIL = '[Admin Collection] Update Admin Fail', - LOAD_NEW_USERS_REQUEST = '[Admin Collection] Load New Users Request', LOAD_ADMIN_REQUEST = '[Admin Collection] Load Admin Request', LOAD_ADMIN_SUCCESS = '[Admin Collection] Load Admin Success', LOAD_ADMIN_ERROR = '[Admin Collection] Load Admin Error', @@ -52,12 +51,6 @@ export class LoadAdminRequest implements Action { constructor() { } } -export class LoadNewUsersRequest implements Action { - readonly type = AdminCollectionActionTypes.LOAD_NEW_USERS_REQUEST; - - constructor() { } -} - export class LoadAdminSuccess implements Action { readonly type = AdminCollectionActionTypes.LOAD_ADMIN_SUCCESS; @@ -133,7 +126,6 @@ export type AdminCollectionActionsUnion = | LoadAdminRequest | LoadAdminSuccess | LoadAdminError - | LoadNewUsersRequest | AddAdminRequest | AddAdminSuccess | AddAdminFail diff --git a/ui/src/app/admin/action/metadata-collection.action.ts b/ui/src/app/admin/action/metadata-collection.action.ts new file mode 100644 index 000000000..01795d49b --- /dev/null +++ b/ui/src/app/admin/action/metadata-collection.action.ts @@ -0,0 +1,90 @@ +import { Action } from '@ngrx/store'; +import { MetadataResolver } from '../../metadata/domain/model'; +import { Update } from '@ngrx/entity'; + +export enum MetadataCollectionActionTypes { + UPDATE_METADATA_REQUEST = '[Admin Metadata Collection] Update Request', + UPDATE_METADATA_SUCCESS = '[Admin Metadata Collection] Update Success', + UPDATE_METADATA_FAIL = '[Admin Metadata Collection] Update Fail', + UPDATE_METADATA_CONFLICT = '[Admin Metadata Collection] Update Conflict', + + LOAD_METADATA_REQUEST = '[Admin Metadata Collection] Load Metadata REQUEST', + LOAD_METADATA_SUCCESS = '[Admin Metadata Collection] Load Metadata SUCCESS', + LOAD_METADATA_ERROR = '[Admin Metadata Collection] Load Metadata ERROR', + + REMOVE_METADATA = '[Admin Metadata Collection] Remove Metadata', + REMOVE_METADATA_SUCCESS = '[Admin Metadata Collection] Remove Metadata Success', + REMOVE_METADATA_FAIL = '[Admin Metadata Collection] Remove Metadata Fail', +} + +export class LoadMetadataRequest implements Action { + readonly type = MetadataCollectionActionTypes.LOAD_METADATA_REQUEST; + + constructor() { } +} + +export class LoadMetadataSuccess implements Action { + readonly type = MetadataCollectionActionTypes.LOAD_METADATA_SUCCESS; + + constructor(public payload: MetadataResolver[]) { } +} + +export class LoadMetadataError implements Action { + readonly type = MetadataCollectionActionTypes.LOAD_METADATA_ERROR; + + constructor(public payload: any) { } +} + +export class UpdateMetadataRequest implements Action { + readonly type = MetadataCollectionActionTypes.UPDATE_METADATA_REQUEST; + + constructor(public payload: MetadataResolver) { } +} + +export class UpdateMetadataSuccess implements Action { + readonly type = MetadataCollectionActionTypes.UPDATE_METADATA_SUCCESS; + + constructor(public payload: Update) { } +} + +export class UpdateMetadataFail implements Action { + readonly type = MetadataCollectionActionTypes.UPDATE_METADATA_FAIL; + + constructor(public payload: any) { } +} + +export class UpdateMetadataConflict implements Action { + readonly type = MetadataCollectionActionTypes.UPDATE_METADATA_CONFLICT; + + constructor(public payload: MetadataResolver) { } +} + +export class RemoveMetadataRequest implements Action { + readonly type = MetadataCollectionActionTypes.REMOVE_METADATA; + + constructor(public payload: MetadataResolver) { } +} + +export class RemoveMetadataSuccess implements Action { + readonly type = MetadataCollectionActionTypes.REMOVE_METADATA_SUCCESS; + + constructor(public payload: MetadataResolver) { } +} + +export class RemoveMetadataFail implements Action { + readonly type = MetadataCollectionActionTypes.REMOVE_METADATA_FAIL; + + constructor(public payload: MetadataResolver) { } +} + +export type MetadataCollectionActionsUnion = + | LoadMetadataRequest + | LoadMetadataSuccess + | LoadMetadataError + | RemoveMetadataRequest + | RemoveMetadataSuccess + | RemoveMetadataFail + | UpdateMetadataRequest + | UpdateMetadataSuccess + | UpdateMetadataFail + | UpdateMetadataConflict; diff --git a/ui/src/app/admin/admin.component.spec.ts b/ui/src/app/admin/admin.component.spec.ts index 4cb5cb3e0..909e20c03 100644 --- a/ui/src/app/admin/admin.component.spec.ts +++ b/ui/src/app/admin/admin.component.spec.ts @@ -1,21 +1,30 @@ import { TestBed, ComponentFixture } from '@angular/core/testing'; import { AdminComponent } from './admin.component'; import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule, combineReducers, Store } from '@ngrx/store'; +import * as fromAdmin from './reducer'; describe('Admin Root Component', () => { let fixture: ComponentFixture; let instance: AdminComponent; + let store: Store; beforeEach(() => { TestBed.configureTestingModule({ imports: [ - RouterTestingModule + RouterTestingModule, + StoreModule.forRoot({ + admin: combineReducers(fromAdmin.reducers) + }) ], declarations: [ AdminComponent ], }); + store = TestBed.get(Store); + spyOn(store, 'dispatch'); + fixture = TestBed.createComponent(AdminComponent); instance = fixture.componentInstance; }); @@ -24,5 +33,6 @@ describe('Admin Root Component', () => { fixture.detectChanges(); expect(fixture).toBeDefined(); + expect(store.dispatch).toHaveBeenCalled(); }); }); diff --git a/ui/src/app/admin/admin.component.ts b/ui/src/app/admin/admin.component.ts index 6d95d1d23..f4670889d 100644 --- a/ui/src/app/admin/admin.component.ts +++ b/ui/src/app/admin/admin.component.ts @@ -1,4 +1,8 @@ -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; + +import * as fromRoot from '../app.reducer'; +import { Store } from '@ngrx/store'; +import { LoadAdminRequest } from './action/admin-collection.action'; @Component({ selector: 'admin-page', @@ -6,5 +10,9 @@ import { Component, OnInit } from '@angular/core'; styleUrls: [] }) export class AdminComponent { - constructor() { } + constructor( + private store: Store + ) { + this.store.dispatch(new LoadAdminRequest()); + } } diff --git a/ui/src/app/admin/admin.module.ts b/ui/src/app/admin/admin.module.ts index 6ccd53c87..fd25105a4 100644 --- a/ui/src/app/admin/admin.module.ts +++ b/ui/src/app/admin/admin.module.ts @@ -11,7 +11,7 @@ import { AdminManagementPageComponent } from './container/admin-management.compo import { AdminComponent } from './admin.component'; import { reducers } from './reducer'; import { AdminService } from './service/admin.service'; -import { AdminCollectionEffects } from './effect/user-collection.effect'; +import { AdminCollectionEffects } from './effect/admin-collection.effect'; import { EffectsModule } from '@ngrx/effects'; import { DeleteUserDialogComponent } from './component/delete-user-dialog.component'; import { NgbModalModule } from '@ng-bootstrap/ng-bootstrap'; @@ -20,6 +20,7 @@ import { AccessRequestComponent } from './component/access-request.component'; import { UserManagementComponent } from './component/user-management.component'; import { EnableMetadataComponent } from './component/enable-metadata.component'; import { ManagerModule } from '../metadata/manager/manager.module'; +import { MetadataCollectionEffects } from './effect/metadata-collection.effect'; @NgModule({ declarations: [ @@ -38,7 +39,7 @@ import { ManagerModule } from '../metadata/manager/manager.module'; CommonModule, I18nModule, StoreModule.forFeature('admin', reducers), - EffectsModule.forFeature([AdminCollectionEffects]), + EffectsModule.forFeature([AdminCollectionEffects, MetadataCollectionEffects]), FormsModule, RouterModule, HttpClientModule, diff --git a/ui/src/app/admin/component/enable-metadata.component.html b/ui/src/app/admin/component/enable-metadata.component.html index 6ab978649..cba54f177 100644 --- a/ui/src/app/admin/component/enable-metadata.component.html +++ b/ui/src/app/admin/component/enable-metadata.component.html @@ -8,7 +8,10 @@ (select)="edit(resolver)" (toggle)="toggleEntity(resolver)" (preview)="openPreviewDialog(resolver)" - (delete)="deleteResolver(resolver)"> + (delete)="deleteResolver(resolver)" + (toggleEnabled)="toggleResolverEnabled(resolver, !resolver.enabled)" + [showAdminFunctions]="true" + [allowDelete]="true"> \ No newline at end of file diff --git a/ui/src/app/admin/component/enable-metadata.component.ts b/ui/src/app/admin/component/enable-metadata.component.ts index 42032496b..3c463eb9d 100644 --- a/ui/src/app/admin/component/enable-metadata.component.ts +++ b/ui/src/app/admin/component/enable-metadata.component.ts @@ -7,12 +7,12 @@ import { map } from 'rxjs/operators'; import { MetadataEntity, MetadataResolver } from '../../metadata/domain/model'; import * as fromDashboard from '../../metadata/manager/reducer'; -import * as fromResolver from '../../metadata/resolver/reducer'; +import * as fromMetadata from '../reducer'; import { ToggleEntityDisplay } from '../../metadata/manager/action/manager.action'; import { DeleteDialogComponent } from '../../metadata/manager/component/delete-dialog.component'; import { PreviewEntity } from '../../metadata/domain/action/entity.action'; -import { RemoveDraftRequest } from '../../metadata/resolver/action/draft.action'; -import { LoadAdminResolverRequest } from '../../metadata/resolver/action/collection.action'; +import { FileBackedHttpMetadataResolver } from '../../metadata/domain/entity'; +import { RemoveMetadataRequest, UpdateMetadataRequest, LoadMetadataRequest } from '../action/metadata-collection.action'; @Component({ selector: 'enable-metadata', @@ -20,7 +20,7 @@ import { LoadAdminResolverRequest } from '../../metadata/resolver/action/collect }) export class EnableMetadataComponent implements OnInit { - resolvers$: Observable; + resolvers$: Observable; loading$: Observable; total$: Observable; @@ -34,16 +34,20 @@ export class EnableMetadataComponent implements OnInit { private router: Router, private modalService: NgbModal ) { - this.resolvers$ = store.select(fromResolver.getAllResolvers); - this.loading$ = store.select(fromDashboard.getSearchLoading); - this.entitiesOpen$ = store.select(fromDashboard.getOpenProviders); + this.store.dispatch(new LoadMetadataRequest()); + + this.resolvers$ = this.store + .select(fromMetadata.getMetadataCollection) + .pipe( + map(resolvers => resolvers.map(r => new FileBackedHttpMetadataResolver(r))) + ); + this.loading$ = this.store.select(fromDashboard.getSearchLoading); + this.entitiesOpen$ = this.store.select(fromDashboard.getOpenProviders); this.total$ = this.resolvers$.pipe(map(list => list.length)); } - ngOnInit(): void { - this.store.dispatch(new LoadAdminResolverRequest()); - } + ngOnInit(): void {} edit(entity: MetadataEntity): void { this.router.navigate(['metadata', 'resolver', entity.getId(), 'edit']); @@ -57,13 +61,18 @@ export class EnableMetadataComponent implements OnInit { this.store.dispatch(new PreviewEntity({ id: entity.getId(), entity })); } + toggleResolverEnabled(entity: MetadataResolver, enabled: boolean): void { + let update = { ...entity, serviceEnabled: enabled }; + this.store.dispatch(new UpdateMetadataRequest(update)); + } + deleteResolver(entity: MetadataResolver): void { this.modalService .open(DeleteDialogComponent) .result .then( success => { - this.store.dispatch(new RemoveDraftRequest(entity)); + this.store.dispatch(new RemoveMetadataRequest(entity)); }, err => { console.log('Cancelled'); diff --git a/ui/src/app/admin/component/user-management.component.ts b/ui/src/app/admin/component/user-management.component.ts index cecefa5ae..72a91ff62 100644 --- a/ui/src/app/admin/component/user-management.component.ts +++ b/ui/src/app/admin/component/user-management.component.ts @@ -6,7 +6,7 @@ import * as fromRoot from '../../app.reducer'; import * as fromCore from '../../core/reducer'; import * as fromAdmin from '../reducer'; -import { UpdateAdminRequest, RemoveAdminRequest } from '../action/user-collection.action'; +import { UpdateAdminRequest, RemoveAdminRequest } from '../action/admin-collection.action'; import { Admin } from '../model/admin'; import { DeleteUserDialogComponent } from '../component/delete-user-dialog.component'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; @@ -33,7 +33,7 @@ export class UserManagementComponent implements OnInit { } ngOnInit(): void { - this.users$ = this.store.select(fromAdmin.getAllConfiguredUsers); + this.users$ = this.store.select(fromAdmin.getAllConfiguredAdmins); this.hasUsers$ = this.users$.pipe(map(userList => userList.length > 0)); } diff --git a/ui/src/app/admin/container/action-required.component.ts b/ui/src/app/admin/container/action-required.component.ts index b10d507ef..f504f36e4 100644 --- a/ui/src/app/admin/container/action-required.component.ts +++ b/ui/src/app/admin/container/action-required.component.ts @@ -2,7 +2,6 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Store } from '@ngrx/store'; import * as fromRoot from '../../app.reducer'; -import { LoadNewUsersRequest } from '../action/user-collection.action'; @Component({ selector: 'action-required-page', @@ -14,7 +13,5 @@ export class ActionRequiredPageComponent { constructor( private store: Store - ) { - this.store.dispatch(new LoadNewUsersRequest()); - } + ) {} } diff --git a/ui/src/app/admin/container/admin-management.component.ts b/ui/src/app/admin/container/admin-management.component.ts index 937cd3c98..fa657d5b5 100644 --- a/ui/src/app/admin/container/admin-management.component.ts +++ b/ui/src/app/admin/container/admin-management.component.ts @@ -2,7 +2,7 @@ import { Component, ChangeDetectionStrategy } from '@angular/core'; import { Store } from '@ngrx/store'; import * as fromRoot from '../../app.reducer'; -import { LoadAdminRequest } from '../action/user-collection.action'; +import { LoadAdminRequest } from '../action/admin-collection.action'; @Component({ selector: 'admin-management-page', @@ -14,7 +14,5 @@ export class AdminManagementPageComponent { constructor( private store: Store - ) { - this.store.dispatch(new LoadAdminRequest()); - } + ) {} } diff --git a/ui/src/app/admin/effect/user-collection.effect.ts b/ui/src/app/admin/effect/admin-collection.effect.ts similarity index 88% rename from ui/src/app/admin/effect/user-collection.effect.ts rename to ui/src/app/admin/effect/admin-collection.effect.ts index 2e11979c7..36a71884a 100644 --- a/ui/src/app/admin/effect/user-collection.effect.ts +++ b/ui/src/app/admin/effect/admin-collection.effect.ts @@ -11,9 +11,8 @@ import { UpdateAdminRequest, UpdateAdminSuccess, RemoveAdminRequest, - RemoveAdminSuccess, - LoadNewUsersRequest -} from '../action/user-collection.action'; + RemoveAdminSuccess +} from '../action/admin-collection.action'; import { AdminService } from '../service/admin.service'; import { AddNotification } from '../../notification/action/notification.action'; import { Notification, NotificationType } from '../../notification/model/notification'; @@ -31,14 +30,6 @@ export class AdminCollectionEffects { )) ); - @Effect() - loadNewUsersRequest$ = this.actions$.pipe( - ofType(AdminCollectionActionTypes.LOAD_NEW_USERS_REQUEST), - switchMap(() => this.adminService.queryByRole('ROLE_NONE').pipe( - map(users => new LoadAdminSuccess(users)) - )) - ); - @Effect() updateAdminRequest$ = this.actions$.pipe( ofType(AdminCollectionActionTypes.UPDATE_ADMIN_REQUEST), diff --git a/ui/src/app/admin/effect/metadata-collection.effect.ts b/ui/src/app/admin/effect/metadata-collection.effect.ts new file mode 100644 index 000000000..bb9604916 --- /dev/null +++ b/ui/src/app/admin/effect/metadata-collection.effect.ts @@ -0,0 +1,128 @@ +import { Injectable } from '@angular/core'; +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { Router } from '@angular/router'; +import { Store } from '@ngrx/store'; +import { of } from 'rxjs'; +import { map, catchError, switchMap, tap, withLatestFrom } from 'rxjs/operators'; + +import { + MetadataCollectionActionTypes, + LoadMetadataRequest, + LoadMetadataSuccess, + LoadMetadataError, + RemoveMetadataRequest, + RemoveMetadataSuccess, + RemoveMetadataFail, + UpdateMetadataRequest, + UpdateMetadataSuccess, + UpdateMetadataFail, + UpdateMetadataConflict +} from '../action/metadata-collection.action'; +import { ResolverService } from '../../metadata/domain/service/resolver.service'; +import { removeNulls } from '../../shared/util'; +import { AddNotification } from '../../notification/action/notification.action'; +import { Notification, NotificationType } from '../../notification/model/notification'; +import { I18nService } from '../../i18n/service/i18n.service'; +import * as fromRoot from '../../app.reducer'; +import * as fromI18n from '../../i18n/reducer'; + + +/* istanbul ignore next */ +@Injectable() +export class MetadataCollectionEffects { + + @Effect() + loadMetadatas$ = this.actions$.pipe( + ofType(MetadataCollectionActionTypes.LOAD_METADATA_REQUEST), + switchMap(() => + this.descriptorService + .queryForAdmin() + .pipe( + map(descriptors => new LoadMetadataSuccess(descriptors)), + catchError(error => of(new LoadMetadataError(error))) + ) + ) + ); + + @Effect() + updateMetadata$ = this.actions$.pipe( + ofType(MetadataCollectionActionTypes.UPDATE_METADATA_REQUEST), + map(action => action.payload), + switchMap(provider => { + return this.descriptorService + .update(removeNulls(provider)) + .pipe( + map(p => new UpdateMetadataSuccess({ + id: p.id, + changes: p + })), + catchError(err => { + if (err.status === 409) { + return of(new UpdateMetadataConflict(provider)); + } + return of(new UpdateMetadataFail({ + errorCode: err.status, + errorMessage: `${err.statusText} - ${err.message}` + })); + }) + ); + }) + ); + + @Effect() + removeMetadataSuccessReload$ = this.actions$.pipe( + ofType(MetadataCollectionActionTypes.REMOVE_METADATA_SUCCESS), + map(action => action.payload), + map(provider => new LoadMetadataRequest()) + ); + + @Effect() + updateMetadataSuccessNotification$ = this.actions$.pipe( + ofType(MetadataCollectionActionTypes.UPDATE_METADATA_SUCCESS), + map(action => action.payload), + withLatestFrom(this.store.select(fromI18n.getMessages)), + map(([error, messages]) => new AddNotification( + new Notification( + NotificationType.Success, + `Metadata Source has been enabled`, + 8000 + ) + )) + ); + + @Effect() + updateMetadataFailNotification$ = this.actions$.pipe( + ofType(MetadataCollectionActionTypes.UPDATE_METADATA_FAIL), + map(action => action.payload), + withLatestFrom(this.store.select(fromI18n.getMessages)), + map(([error, messages]) => new AddNotification( + new Notification( + NotificationType.Danger, + `${error.errorCode}: ${this.i18nService.translate(error.errorMessage, null, messages)}`, + 8000 + ) + )) + ); + + @Effect() + removeMetadata$ = this.actions$.pipe( + ofType(MetadataCollectionActionTypes.REMOVE_METADATA), + map(action => action.payload), + switchMap(entity => + this.descriptorService + .remove(entity) + .pipe( + map(p => new RemoveMetadataSuccess(entity)), + catchError(err => of(new RemoveMetadataFail(err))) + ) + ) + ); + + constructor( + private descriptorService: ResolverService, + private actions$: Actions, + private router: Router, + private store: Store, + private i18nService: I18nService + ) { } +} /* istanbul ignore next */ diff --git a/ui/src/app/admin/reducer/user-collection.reducer.spec.ts b/ui/src/app/admin/reducer/admin-collection.reducer.spec.ts similarity index 93% rename from ui/src/app/admin/reducer/user-collection.reducer.spec.ts rename to ui/src/app/admin/reducer/admin-collection.reducer.spec.ts index 34b75defd..4c26df392 100644 --- a/ui/src/app/admin/reducer/user-collection.reducer.spec.ts +++ b/ui/src/app/admin/reducer/admin-collection.reducer.spec.ts @@ -1,11 +1,11 @@ -import { reducer, initialState as snapshot } from './user-collection.reducer'; -import * as fromAdmin from './user-collection.reducer'; +import { reducer, initialState as snapshot } from './admin-collection.reducer'; +import * as fromAdmin from './admin-collection.reducer'; import { AdminCollectionActionTypes, LoadAdminSuccess, UpdateAdminSuccess, RemoveAdminSuccess -} from '../action/user-collection.action'; +} from '../action/admin-collection.action'; import { Admin } from '../model/admin'; let users = [ diff --git a/ui/src/app/admin/reducer/user-collection.reducer.ts b/ui/src/app/admin/reducer/admin-collection.reducer.ts similarity index 97% rename from ui/src/app/admin/reducer/user-collection.reducer.ts rename to ui/src/app/admin/reducer/admin-collection.reducer.ts index e11be45ef..ce404d57d 100644 --- a/ui/src/app/admin/reducer/user-collection.reducer.ts +++ b/ui/src/app/admin/reducer/admin-collection.reducer.ts @@ -1,6 +1,6 @@ import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; import { Admin } from '../model/admin'; -import { AdminCollectionActionsUnion, AdminCollectionActionTypes } from '../action/user-collection.action'; +import { AdminCollectionActionsUnion, AdminCollectionActionTypes } from '../action/admin-collection.action'; export interface CollectionState extends EntityState { selectedAdminId: string | null; diff --git a/ui/src/app/admin/reducer/index.spec.ts b/ui/src/app/admin/reducer/index.spec.ts new file mode 100644 index 000000000..f93b0557e --- /dev/null +++ b/ui/src/app/admin/reducer/index.spec.ts @@ -0,0 +1,40 @@ +import * as fromIndex from './'; +import { MetadataResolver } from '../../metadata/domain/model'; +import { User } from '../../core/model/user'; + +// export const totalUserFn = (users) => users.length; +// export const totalMetadataFn = (md) => md.filter(obj => !obj.serviceEnabled).length; +// export const totalActionsFn = (users, md) => md + users; + +let resolvers: MetadataResolver[] = [ + { id: '1', entityId: 'foo', serviceEnabled: true, serviceProviderName: 'bar', createdDate: 'Date' } as MetadataResolver, + { id: '2', entityId: 'baz', serviceEnabled: false, serviceProviderName: 'fin', createdDate: 'Date' } as MetadataResolver +]; + +let users: User[] = [ + { + username: 'foo', + role: 'admin', + firstName: 'foo', + lastName: 'bar', + emailAddress: 'foo@bar.com' + } +]; + +describe('admin dashboard state selectors', () => { + describe('totalUserFn', () => { + it('should get the length of the provided array', () => { + expect(fromIndex.totalUserFn(users)).toBe(1); + }); + }); + describe('totalMetadataFn', () => { + it('should get the length of the provided list after filtering enabled resolvers', () => { + expect(fromIndex.totalMetadataFn(resolvers)).toBe(1); + }); + }); + describe('totalActionsFn', () => { + it('should return the sum of the total users and metadata', () => { + expect(fromIndex.totalActionsFn(1, 2)).toBe(3); + }); + }); +}); diff --git a/ui/src/app/admin/reducer/index.ts b/ui/src/app/admin/reducer/index.ts index f647e200e..651870b76 100644 --- a/ui/src/app/admin/reducer/index.ts +++ b/ui/src/app/admin/reducer/index.ts @@ -1,37 +1,61 @@ import { createSelector, createFeatureSelector } from '@ngrx/store'; import * as fromRoot from '../../core/reducer'; -import * as fromCollection from './user-collection.reducer'; +import * as fromAdminCollection from './admin-collection.reducer'; +import * as fromMetadataCollection from './metadata-collection.reducer'; +import { getInCollectionFn } from '../../metadata/domain/domain.util'; export interface AdminState { - collection: fromCollection.CollectionState; + admins: fromAdminCollection.CollectionState; + metadata: fromMetadataCollection.CollectionState; } export const reducers = { - collection: fromCollection.reducer + admins: fromAdminCollection.reducer, + metadata: fromMetadataCollection.reducer }; export interface State extends fromRoot.State { 'admin': AdminState; } -export const getCollectionFromStateFn = (state: AdminState) => state.collection; +export const getAdminsCollectionFromStateFn = (state: AdminState) => state.admins; +export const getMetadataCollectionFromStateFn = (state: AdminState) => state.metadata; -export const getAdminState = createFeatureSelector('admin'); +export const getFeatureState = createFeatureSelector('admin'); /* * Select pieces of Admin Collection */ -export const getCollectionState = createSelector(getAdminState, getCollectionFromStateFn); -export const getAllAdmins = createSelector(getCollectionState, fromCollection.selectAllAdmins); -export const getCollectionSaving = createSelector(getCollectionState, fromCollection.getIsSaving); +export const getAdminCollectionState = createSelector(getFeatureState, getAdminsCollectionFromStateFn); +export const getAllAdmins = createSelector(getAdminCollectionState, fromAdminCollection.selectAllAdmins); +export const getCollectionSaving = createSelector(getAdminCollectionState, fromAdminCollection.getIsSaving); -export const getAdminEntities = createSelector(getCollectionState, fromCollection.selectAdminEntities); -export const getSelectedAdminId = createSelector(getCollectionState, fromCollection.getSelectedAdminId); +export const getAdminEntities = createSelector(getAdminCollectionState, fromAdminCollection.selectAdminEntities); +export const getSelectedAdminId = createSelector(getAdminCollectionState, fromAdminCollection.getSelectedAdminId); export const getSelectedAdmin = createSelector(getAdminEntities, getSelectedAdminId, (entities, selectedId) => { return selectedId && entities[selectedId]; }); -export const getAdminIds = createSelector(getCollectionState, fromCollection.selectAdminIds); +export const getAdminIds = createSelector(getAdminCollectionState, fromAdminCollection.selectAdminIds); +export const getAllConfiguredAdmins = createSelector(getAllAdmins, (admins) => admins.filter(a => a.role !== 'ROLE_NONE')); export const getAllNewUsers = createSelector(getAllAdmins, (admins) => admins.filter(a => a.role === 'ROLE_NONE')); -export const getAllConfiguredUsers = createSelector(getAllAdmins, (admins) => admins.filter(a => a.role !== 'ROLE_NONE')); -export const getTotalActionsRequired = createSelector(getAllNewUsers, (users) => users.length); +/* + * Select pieces of Metadata Collection +*/ +export const getMetadataCollectionState = createSelector(getFeatureState, getMetadataCollectionFromStateFn); + +export const getMetadataEntities = createSelector(getMetadataCollectionState, fromMetadataCollection.selectMetadataEntities); +export const getSelectedMetadataId = createSelector(getMetadataCollectionState, fromMetadataCollection.getSelectedMetadataId); +export const getMetadataIds = createSelector(getMetadataCollectionState, fromMetadataCollection.selectMetadataIds); + +export const getMetadataCollection = createSelector(getMetadataCollectionState, getMetadataIds, fromMetadataCollection.selectAllMetadata); +export const getSelectedMetadata = createSelector(getMetadataEntities, getSelectedMetadataId, getInCollectionFn); + +export const totalUserFn = (users) => users.length; +export const totalMetadataFn = (md) => md.filter(obj => !obj.serviceEnabled).length; +export const totalActionsFn = (users, md) => md + users; + +export const getTotalNewUsers = createSelector(getAllNewUsers, totalUserFn); +export const getTotalNewMetadata = createSelector(getMetadataCollection, totalMetadataFn); + +export const getTotalActionsRequired = createSelector(getTotalNewUsers, getTotalNewMetadata, totalActionsFn); diff --git a/ui/src/app/admin/reducer/metadata-collection.reducer.spec.ts b/ui/src/app/admin/reducer/metadata-collection.reducer.spec.ts new file mode 100644 index 000000000..a1b5b0c70 --- /dev/null +++ b/ui/src/app/admin/reducer/metadata-collection.reducer.spec.ts @@ -0,0 +1,72 @@ +import { reducer } from './metadata-collection.reducer'; +import * as fromCollection from './metadata-collection.reducer'; +import * as resolverActions from '../action/metadata-collection.action'; +import { MetadataResolver } from '../../metadata/domain/model'; + +let resolvers: MetadataResolver[] = [ + { id: '1', entityId: 'foo', serviceProviderName: 'bar', createdDate: 'Tue Apr 17 2018 13:33:54 GMT-0700 (MST)' } as MetadataResolver, + { id: '2', entityId: 'baz', serviceProviderName: 'fin', createdDate: 'Tue Apr 17 2018 13:34:07 GMT-0700 (MST)' } as MetadataResolver +], +snapshot: fromCollection.CollectionState = { + ids: [resolvers[0].id, resolvers[1].id], + entities: { + [resolvers[0].id]: resolvers[0], + [resolvers[1].id]: resolvers[1] + }, + selectedMetadataId: null +}; + +describe('Resolver Reducer', () => { + const initialState: fromCollection.CollectionState = { + ids: [], + entities: {}, + selectedMetadataId: null, + }; + + describe('undefined action', () => { + it('should return the default state', () => { + const result = reducer(undefined, {} as any); + + expect(result).toEqual(initialState); + }); + }); + + describe('Load Providers: Success', () => { + it('should add the loaded resolvers to the collection', () => { + const action = new resolverActions.LoadMetadataSuccess(resolvers); + const result = reducer(initialState, action); + + expect(result).toEqual( + Object.assign({}, initialState, snapshot) + ); + }); + }); + + describe('Update Providers: Success', () => { + it('should update the draft of the specified id', () => { + let changes = { ...resolvers[1], serviceEnabled: true }, + expected = { + ids: [resolvers[0].id, resolvers[1].id], + entities: { + [resolvers[0].id]: resolvers[0], + [resolvers[1].id]: changes + }, + selectedMetadataId: null + }; + const action = new resolverActions.UpdateMetadataSuccess({ id: changes.id, changes }); + const result = reducer({ ...snapshot }, action); + + expect(result).toEqual( + Object.assign({}, initialState, expected) + ); + }); + + it('should return state if the entityId is not found', () => { + let changes = { ...resolvers[1], serviceEnabled: true, id: '4' }; + const action = new resolverActions.UpdateMetadataSuccess({ id: changes.id, changes }); + const result = reducer({ ...snapshot }, action); + + expect(result).toEqual(snapshot); + }); + }); +}); diff --git a/ui/src/app/admin/reducer/metadata-collection.reducer.ts b/ui/src/app/admin/reducer/metadata-collection.reducer.ts new file mode 100644 index 000000000..dc4006841 --- /dev/null +++ b/ui/src/app/admin/reducer/metadata-collection.reducer.ts @@ -0,0 +1,59 @@ +import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; +import { MetadataResolver } from '../../metadata/domain/model'; +import { MetadataCollectionActionsUnion, MetadataCollectionActionTypes } from '../action/metadata-collection.action'; + +export interface CollectionState extends EntityState { + selectedMetadataId: string | null; +} + +export function sortByDate(a: MetadataResolver, b: MetadataResolver): number { + return a.createdDate.localeCompare(b.createdDate); +} + +export const adapter: EntityAdapter = createEntityAdapter({ + sortComparer: sortByDate, + selectId: (model: MetadataResolver) => model.id +}); + +export const initialState: CollectionState = adapter.getInitialState({ + selectedMetadataId: null +}); + +export function reducer(state = initialState, action: MetadataCollectionActionsUnion): CollectionState { + switch (action.type) { + case MetadataCollectionActionTypes.LOAD_METADATA_SUCCESS: { + return adapter.addAll(action.payload, { + ...state, + selectedMetadataId: state.selectedMetadataId + }); + } + + case MetadataCollectionActionTypes.UPDATE_METADATA_SUCCESS: { + return adapter.updateOne(action.payload, state); + } + + case MetadataCollectionActionTypes.LOAD_METADATA_ERROR: { + return adapter.removeAll({ + ...state + }); + } + + case MetadataCollectionActionTypes.REMOVE_METADATA_SUCCESS: { + return adapter.removeOne(action.payload.id, { + ...state + }); + } + + default: { + return state; + } + } +} + +export const getSelectedMetadataId = (state: CollectionState) => state.selectedMetadataId; +export const { + selectIds: selectMetadataIds, + selectEntities: selectMetadataEntities, + selectAll: selectAllMetadata, + selectTotal: selectMetadataTotal +} = adapter.getSelectors(); diff --git a/ui/src/app/core/model/user.ts b/ui/src/app/core/model/user.ts index 37d05212e..27375c23e 100644 --- a/ui/src/app/core/model/user.ts +++ b/ui/src/app/core/model/user.ts @@ -1,8 +1,7 @@ export interface User { - id: string; + username: string; role: string; - name: { - first: string, - last: string - }; + firstName: string; + lastName: string; + emailAddress: string; } diff --git a/ui/src/app/core/reducer/user.reducer.spec.ts b/ui/src/app/core/reducer/user.reducer.spec.ts index 6dc2bed01..587acca0f 100644 --- a/ui/src/app/core/reducer/user.reducer.spec.ts +++ b/ui/src/app/core/reducer/user.reducer.spec.ts @@ -11,12 +11,11 @@ describe('User Reducer', () => { }; const user: User = { - id: '1', + username: 'foo', role: 'admin', - name: { - first: 'foo', - last: 'bar' - } + firstName: 'foo', + lastName: 'bar', + emailAddress: 'foo@bar.com' }; describe('undefined action', () => { @@ -59,14 +58,7 @@ describe('User Reducer', () => { describe('User Selectors', () => { const state = { - user: { - id: '1', - role: 'admin', - name: { - first: 'foo', - last: 'bar' - } - }, + user: { ...user }, fetching: true, error: { message: 'foo', type: 'bar' } } as fromUser.UserState; diff --git a/ui/src/app/dashboard/container/dashboard.component.ts b/ui/src/app/dashboard/container/dashboard.component.ts index 4c3aab787..543252e94 100644 --- a/ui/src/app/dashboard/container/dashboard.component.ts +++ b/ui/src/app/dashboard/container/dashboard.component.ts @@ -4,7 +4,6 @@ import { Store } from '@ngrx/store'; import * as fromRoot from '../../app.reducer'; import * as fromAdmin from '../../admin/reducer'; import { Observable } from 'rxjs'; -import { LoadAdminRequest } from '../../admin/action/user-collection.action'; import { LoadRoleRequest } from '../../core/action/configuration.action'; import { map } from 'rxjs/operators'; diff --git a/ui/src/app/metadata/domain/service/resolver.service.ts b/ui/src/app/metadata/domain/service/resolver.service.ts index 3c3e48602..2a158f75f 100644 --- a/ui/src/app/metadata/domain/service/resolver.service.ts +++ b/ui/src/app/metadata/domain/service/resolver.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; -import { Observable, throwError } from 'rxjs'; +import { Observable, throwError, of } from 'rxjs'; import { catchError } from 'rxjs/operators'; import { MetadataResolver } from '../model'; @@ -14,13 +14,20 @@ export class ResolverService { private http: HttpClient ) {} - query(opts: any = {}): Observable { + query(): Observable { return this.http.get(`${ this.base }${ this.endpoint }s`, {}) .pipe( catchError(err => throwError([])) ); } + queryForAdmin(): Observable { + return this.http.get(`${this.base}${this.endpoint}/disabledNonAdmin`, {}) + .pipe( + catchError(err => throwError([])) + ); + } + find(id: string): Observable { return this.http.get(`${ this.base }${ this.endpoint }/${ id }`) .pipe( diff --git a/ui/src/app/metadata/manager/component/entity-item.component.ts b/ui/src/app/metadata/manager/component/entity-item.component.ts index b2453c474..f9d1745c3 100644 --- a/ui/src/app/metadata/manager/component/entity-item.component.ts +++ b/ui/src/app/metadata/manager/component/entity-item.component.ts @@ -12,6 +12,9 @@ import { MetadataTypes } from '../../domain/domain.type'; export class EntityItemComponent { types = MetadataTypes; @Input() isOpen: boolean; + @Input() allowDelete: boolean; + @Input() showAdminFunctions: boolean; + @Output() toggleEnabled = new EventEmitter(); @Output() select = new EventEmitter(); @Output() toggle = new EventEmitter(); @Output() preview = new EventEmitter(); diff --git a/ui/src/app/metadata/manager/component/resolver-item.component.html b/ui/src/app/metadata/manager/component/resolver-item.component.html index ff734e4a6..0524c9d10 100644 --- a/ui/src/app/metadata/manager/component/resolver-item.component.html +++ b/ui/src/app/metadata/manager/component/resolver-item.component.html @@ -1,7 +1,7 @@
    -
    +
      @@ -21,11 +21,27 @@ {{ entity.getDisplayId() }}
    -
    - + -
    @@ -66,7 +82,7 @@
    -