Skip to content

Commit

Permalink
Showing 18 changed files with 120 additions and 57 deletions.
@@ -1,9 +1,11 @@
package edu.internet2.tier.shibboleth.admin.ui.controller.support;

import com.google.common.collect.ImmutableMap;
import edu.internet2.tier.shibboleth.admin.ui.controller.ErrorResponse;
import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver;
import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@@ -30,12 +32,18 @@ public MetadataResolver findResolverOrThrowHttp404(String resolverResourceId) {
return resolver;
}


@ExceptionHandler
//TODO: Review this handler and update accordingly. Do we still need it?
@ExceptionHandler(HttpClientErrorException.class)
public ResponseEntity<?> notFoundHandler(HttpClientErrorException ex) {
if(ex.getStatusCode() == NOT_FOUND) {
return ResponseEntity.status(NOT_FOUND).body(ex.getStatusText());
}
throw ex;
}

@ExceptionHandler(Exception.class)
public final ResponseEntity<ErrorResponse> handleAllOtherExceptions(Exception ex) {
ErrorResponse errorResponse = new ErrorResponse("400", ex.getLocalizedMessage());
return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
}
}
@@ -85,7 +85,6 @@ export class MetadataSourceBase implements Wizard<MetadataResolver> {
errors.push(error);
}
});
console.log(errors, form_current);
return errors;
},
'/entityId': (value, property, form) => {
4 changes: 4 additions & 0 deletions ui/src/app/metadata/domain/service/draft.service.ts
@@ -29,6 +29,10 @@ export class EntityDraftService {
);
}

exists(id: string, attr: string = 'id'): boolean {
return this.storage.query().some(entity => entity[attr] === id);
}

save(provider: MetadataResolver): Observable<MetadataResolver> {
this.storage.add(provider);
return of(provider);
@@ -127,7 +127,7 @@ describe('Dashboard Resolvers List Page', () => {
it('should route to the wizard page', () => {
spyOn(router, 'navigate');
instance.edit(draft);
expect(router.navigate).toHaveBeenCalledWith(['metadata', 'resolver', 'new', 'blank', 'org-info'], {
expect(router.navigate).toHaveBeenCalledWith(['metadata', 'resolver', 'new', 'blank', 'common'], {
queryParams: { id: '1' }
});
});
@@ -72,7 +72,7 @@ export class DashboardResolversListComponent implements OnInit {

edit(entity: MetadataEntity): void {
if (entity.isDraft()) {
this.router.navigate(['metadata', 'resolver', 'new', 'blank', 'org-info'], {
this.router.navigate(['metadata', 'resolver', 'new', 'blank', 'common'], {
queryParams: {
id: entity.getId()
}
@@ -19,7 +19,8 @@ <h3 translate="label.how-are-you-adding-the-metadata-information">How are you ad
class="btn btn-lg btn-block btn-secondary"
aria-label="Upload local metadata file or use a metadata URL"
role="button"
routerLink="upload"
[routerLink]="['./', 'upload']"
queryParamsHandling="merge"
routerLinkActive="btn-success">
<translate-i18n key="label.upload-url">Upload/URL</translate-i18n>
<i class="fa fa-link fa-2x d-block"></i>
@@ -33,7 +34,8 @@ <h3 translate="label.how-are-you-adding-the-metadata-information">How are you ad
class="btn btn-lg btn-block btn-secondary"
aria-label="Create metadata source using the wizard"
role="button"
[routerLink]="['./']"
[routerLink]="['./', 'blank', 'common']"
queryParamsHandling="merge"
routerLinkActive="btn-info">
<translate-i18n key="action.create">Create</translate-i18n>
<i class="fa fa-plus-square fa-2x d-block"></i>
@@ -47,7 +49,8 @@ <h3 translate="label.how-are-you-adding-the-metadata-information">How are you ad
class="btn btn-lg btn-block btn-secondary"
aria-label="Copy a metadata source"
role="button"
routerLink="copy"
[routerLink]="['./', 'copy']"
queryParamsHandling="merge"
routerLinkActive="btn-warning">
<translate-i18n key="action.copy">Copy</translate-i18n>
<i class="fa fa-copy fa-2x d-block"></i>
@@ -23,6 +23,7 @@ describe('New Resolver Page', () => {
let instance: NewResolverComponent;
let activatedRoute: ActivatedRouteStub = new ActivatedRouteStub();
activatedRoute.testParamMap = { id: 'foo', events: of({}) };
activatedRoute.data = of('foo');

beforeEach(() => {
TestBed.configureTestingModule({
@@ -27,13 +27,13 @@ export class NewResolverComponent {
debounceTime(10),
map(url => {
let child = this.route.snapshot.firstChild;
return child.routeConfig.path.match('blank').length === 0 || child.params.index === 'common';
return !child.routeConfig.path.match('blank') || child.params.index === 'common';
})
);

this.actionsSubscription = this.route.queryParams.pipe(
this.actionsSubscription = this.route.data.pipe(
distinctUntilChanged(),
map(params => new SelectDraftRequest(params.id))
map(data => new SelectDraftRequest(data.draft))
).subscribe(this.store);
}
}
@@ -77,14 +77,10 @@ export class ResolverWizardStepComponent implements OnDestroy {
this.valueChangeEmitted$.pipe(
withLatestFrom(this.definition$),
filter(([ changes, definition ]) => (!!definition && !!changes)),
map(([ changes, definition ]) => definition.parser(changes.value)),
withLatestFrom(this.store.select(fromResolver.getSelectedDraft)),
map(([changes, original]) => ({ ...original, ...changes }))
map(([ changes, definition ]) => definition.parser(changes.value))
)
.subscribe(changes => {
if (changes.id) {
this.store.dispatch(new UpdateChanges(changes));
}
this.store.dispatch(new UpdateChanges(changes));
});

this.statusChangeEmitted$.pipe(distinctUntilChanged()).subscribe(errors => this.updateStatus(errors));
@@ -2,24 +2,22 @@ import { Component, ViewChild } from '@angular/core';
import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { StoreModule, Store, combineReducers } from '@ngrx/store';

import { NgbDropdownModule, NgbPopoverModule, NgbModal, NgbModalModule } from '@ng-bootstrap/ng-bootstrap';
import { RouterStateSnapshot } from '@angular/router';
import { NgbDropdownModule, NgbPopoverModule, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { of } from 'rxjs';

import { ResolverWizardComponent } from './resolver-wizard.component';
import * as fromRoot from '../reducer';
import { WizardModule } from '../../../wizard/wizard.module';
import { WizardSummaryComponent } from '../../domain/component/wizard-summary.component';
import { SummaryPropertyComponent } from '../../domain/component/summary-property.component';
import * as fromWizard from '../../../wizard/reducer';
import { MockI18nModule } from '../../../../testing/i18n.stub';
import { METADATA_SOURCE_WIZARD } from '../wizard-definition';
import { MetadataSourceWizard } from '../../domain/model/wizards/metadata-source-wizard';
import { initialState } from '../reducer/entity.reducer';
import { MockWizardModule } from '../../../../testing/wizard.stub';
import { RouterStateSnapshot, ActivatedRouteSnapshot } from '@angular/router';

import { NgbModalStub } from '../../../../testing/modal.stub';
import { of } from 'rxjs';
import { MetadataResolver } from '../../domain/model';
import { DifferentialService } from '../../../core/service/differential.service';

@Component({
template: `
@@ -94,6 +92,7 @@ describe('Resolver Wizard Component', () => {
TestHostComponent
],
providers: [
DifferentialService,
{ provide: NgbModal, useClass: NgbModalStub },
{ provide: METADATA_SOURCE_WIZARD, useValue: MetadataSourceWizard }
]
@@ -12,6 +12,7 @@ import {
import { Observable, Subject, of, combineLatest as combine } from 'rxjs';
import { skipWhile, startWith, distinctUntilChanged, map, takeUntil, combineLatest } from 'rxjs/operators';
import { Store } from '@ngrx/store';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';

import { MetadataResolver } from '../../domain/model/metadata-resolver';
import * as fromCollections from '../reducer';
@@ -26,8 +27,8 @@ import { SetDefinition, SetIndex, SetDisabled, ClearWizard } from '../../../wiza
import * as fromWizard from '../../../wizard/reducer';
import { LoadSchemaRequest } from '../../../wizard/action/wizard.action';
import { UnsavedEntityComponent } from '../../domain/component/unsaved-entity.dialog';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { Clear } from '../action/entity.action';
import { DifferentialService } from '../../../core/service/differential.service';

@Component({
selector: 'resolver-wizard-page',
@@ -66,6 +67,7 @@ export class ResolverWizardComponent implements OnDestroy, CanComponentDeactivat
private route: ActivatedRoute,
private router: Router,
private modalService: NgbModal,
private diffService: DifferentialService,
@Inject(METADATA_SOURCE_WIZARD) private sourceWizard: Wizard<MetadataResolver>
) {
this.store
@@ -113,6 +115,8 @@ export class ResolverWizardComponent implements OnDestroy, CanComponentDeactivat
combineLatest(this.resolver$, (changes, base) => ({ ...base, ...changes }))
).subscribe(latest => this.latest = latest);

// this.changes$.subscribe(c => console.log(c));

this.summary$ = combine(
this.store.select(fromWizard.getWizardDefinition),
this.store.select(fromWizard.getSchemaCollection),
@@ -128,6 +132,7 @@ export class ResolverWizardComponent implements OnDestroy, CanComponentDeactivat
);

this.changes$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(c => this.changes = c);
this.resolver$.pipe(takeUntil(this.ngUnsubscribe)).subscribe(r => this.resolver = r);
}

next(): void {
@@ -160,6 +165,18 @@ export class ResolverWizardComponent implements OnDestroy, CanComponentDeactivat
this.store.dispatch(new SetIndex(page));
}

hasChanges(changes: MetadataResolver): boolean {
// const updated = this.diffService.updatedDiff(this.resolver, changes);
// const deleted = this.diffService.deletedDiff(this.resolver, changes);
let blacklist = ['id', 'resourceId'];
return Object.keys(changes).filter(key => !(blacklist.indexOf(key) > -1)).length > 0;
}

isNew(changes: MetadataResolver): boolean {
let blacklist = ['id', 'resourceId'];
return Object.keys(changes).filter(key => !(blacklist.indexOf(key) > -1)).length === 0;
}

ngOnDestroy(): void {
this.ngUnsubscribe.next();
this.ngUnsubscribe.complete();
@@ -171,18 +188,23 @@ export class ResolverWizardComponent implements OnDestroy, CanComponentDeactivat
currentState: RouterStateSnapshot,
nextState: RouterStateSnapshot
): Observable<boolean> {
if (nextState.url.match('blank') && !!nextState.root.queryParams.id) { return of(true); }
if (Object.keys(this.changes).length > 0) {
if (nextState.url.match('blank') && !!nextState.root.queryParams.id) {
return of(true);
}
if (this.hasChanges(this.changes)) {
let modal = this.modalService.open(UnsavedEntityComponent);
modal.componentInstance.message = 'resolver';
modal.result.then(
() => {
this.store.dispatch(new Clear());
this.router.navigate([nextState.url]);
this.router.navigateByUrl(nextState.url);
},
() => console.warn('denied')
);
}
if (this.isNew(this.latest)) {
return of(true);
}
return this.store.select(fromResolver.getEntityIsSaved);
}
}
5 changes: 4 additions & 1 deletion ui/src/app/metadata/resolver/effect/collection.effects.ts
@@ -111,7 +111,10 @@ export class ResolverCollectionEffects {
addResolverSuccessRemoveDraft$ = this.actions$.pipe(
ofType<providerActions.AddResolverSuccess>(ResolverCollectionActionTypes.ADD_RESOLVER_SUCCESS),
map(action => action.payload),
map(provider => new draftActions.RemoveDraftRequest(provider))
map(provider => {
console.log(provider);
return new draftActions.RemoveDraftRequest(provider);
})
);

@Effect()
25 changes: 0 additions & 25 deletions ui/src/app/metadata/resolver/effect/draft-collection.effects.ts
@@ -63,13 +63,6 @@ export class DraftCollectionEffects {
)
);

@Effect({ dispatch: false })
addDraftSuccessRedirect$ = this.actions$.pipe(
ofType<actions.AddDraftSuccess>(DraftActionTypes.ADD_DRAFT_SUCCESS),
map(getPayload),
tap(provider => this.router.navigate(['metadata', 'resolver', provider.entityId, 'wizard']))
);

@Effect()
updateDraft$ = this.actions$.pipe(
ofType<actions.UpdateDraftRequest>(DraftActionTypes.UPDATE_DRAFT_REQUEST),
@@ -107,24 +100,6 @@ export class DraftCollectionEffects {
map(id => new actions.LoadDraftRequest())
);

@Effect()
selectDraftError$ = this.actions$.pipe(
ofType<SelectDraftError>(DraftActionTypes.SELECT_ERROR),
map(getPayload),
switchMap(id =>
this.draftService
.save({ id: `r-${ Date.now() }`, serviceProviderName: '' })
.pipe(
map(p => new SelectDraftRequest(p.id)),
catchError(e => of(new SelectDraftError()))
)
),
tap(() => {
// this.store.dispatch(new ClearWizard());
this.store.dispatch(new Clear());
})
);

@Effect()
removeDraft$ = this.actions$.pipe(
ofType<actions.RemoveDraftRequest>(DraftActionTypes.REMOVE_DRAFT),
5 changes: 3 additions & 2 deletions ui/src/app/metadata/resolver/effect/wizard.effect.ts
@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { Effect, Actions, ofType } from '@ngrx/effects';
import { ActivatedRoute, Router } from '@angular/router';
import { map, filter, tap } from 'rxjs/operators';
import { map, filter, tap, withLatestFrom } from 'rxjs/operators';
import { Store } from '@ngrx/store';

import {
@@ -29,7 +29,8 @@ export class WizardEffects {
ofType<UpdateChanges>(ResolverEntityActionTypes.UPDATE_CHANGES),
map(action => action.payload),
filter(provider => !provider.createdDate),
map(provider => new UpdateDraftRequest(provider))
withLatestFrom(this.store.select(fromResolver.getSelectedDraft)),
map(([provider, draft]) => new UpdateDraftRequest({ ...draft, ...provider }))
);

@Effect()
4 changes: 3 additions & 1 deletion ui/src/app/metadata/resolver/resolver.module.ts
@@ -35,6 +35,7 @@ import { ResolverSelectComponent } from './container/resolver-select.component';
import { MetadataSourceEditor } from '../domain/model/wizards/metadata-source-editor';
import { FinishFormComponent } from './component/finish-form.component';
import { ProviderFormFragmentComponent } from './component/provider-form-fragment.component';
import { CreateDraftResolverService } from './service/create-draft.resolver';

@NgModule({
declarations: [
@@ -75,7 +76,8 @@ export class ResolverModule {
return {
ngModule: RootResolverModule,
providers: [
CopyIsSetGuard
CopyIsSetGuard,
CreateDraftResolverService
]
};
}
4 changes: 4 additions & 0 deletions ui/src/app/metadata/resolver/resolver.routing.ts
@@ -13,6 +13,7 @@ import { ResolverWizardStepComponent } from './container/resolver-wizard-step.co
import { ResolverEditComponent } from './container/resolver-edit.component';
import { ResolverEditStepComponent } from './container/resolver-edit-step.component';
import { ResolverSelectComponent } from './container/resolver-select.component';
import { CreateDraftResolverService } from './service/create-draft.resolver';

export const ResolverRoutes: Routes = [
{
@@ -21,6 +22,9 @@ export const ResolverRoutes: Routes = [
{
path: 'new',
component: NewResolverComponent,
resolve: {
draft: CreateDraftResolverService
},
children: [
{ path: '', redirectTo: 'blank/common', pathMatch: 'prefix' },
{
35 changes: 35 additions & 0 deletions ui/src/app/metadata/resolver/service/create-draft.resolver.ts
@@ -0,0 +1,35 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { Observable, of } from 'rxjs';
import { Store } from '@ngrx/store';
import { MetadataResolver } from '../../domain/model';
import { AddDraftRequest } from '../action/draft.action';
import * as fromResolver from '../reducer';
import { EntityDraftService } from '../../domain/service/draft.service';

@Injectable()
export class CreateDraftResolverService {
constructor(
private store: Store<fromResolver.State>,
private draftService: EntityDraftService
) { }

resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<string> | Observable<never> {
let id = route.queryParamMap.get('id');
if (id) {
let exists = this.draftService.exists(id);
if (!exists) {
let resolver = <MetadataResolver>{id};
this.store.dispatch(new AddDraftRequest(resolver));
}
}
if (!id) {
let resolver = <MetadataResolver>{
id: `r-${Date.now()}`
};
id = resolver.id;
this.store.dispatch(new AddDraftRequest(resolver));
}
return of(id);
}
}
11 changes: 11 additions & 0 deletions ui/src/testing/activated-route.stub.ts
@@ -3,6 +3,7 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { convertToParamMap, ParamMap, ActivatedRoute } from '@angular/router';
import { Observable, of } from 'rxjs';

@Injectable()
export class ActivatedRouteStub {
@@ -16,6 +17,8 @@ export class ActivatedRouteStub {

private _firstChild: ActivatedRouteStub;

private _data: Observable<any>;

get testParamMap() { return this._testParamMap; }
set testParamMap(params: {}) {
this._testParamMap = convertToParamMap(params);
@@ -44,4 +47,12 @@ export class ActivatedRouteStub {
set firstChild(stub: ActivatedRouteStub) {
this._firstChild = stub;
}

get data(): Observable<any> {
return this._data;
}

set data(d: Observable<any>) {
this._data = d;
}
}

0 comments on commit 73f068c

Please sign in to comment.