diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html
index 8361b6f80..4de6ae629 100644
--- a/ui/src/app/app.component.html
+++ b/ui/src/app/app.component.html
@@ -70,7 +70,7 @@
- {{ version }}
+ {{ formatted$ | async }}
|
Copyright © {{ today | date:'yyyy' }} Internet2
diff --git a/ui/src/app/app.component.spec.ts b/ui/src/app/app.component.spec.ts
index 4b1943f20..1d3703df5 100644
--- a/ui/src/app/app.component.spec.ts
+++ b/ui/src/app/app.component.spec.ts
@@ -4,25 +4,27 @@ import { StoreModule, Store, combineReducers } from '@ngrx/store';
import { AppComponent } from './app.component';
import { User } from './core/model/user';
-import * as fromUser from './core/reducer/user.reducer';
+import * as fromRoot from './core/reducer';
import { NotificationModule } from './notification/notification.module';
import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap';
describe('AppComponent', () => {
+ let store: Store;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [
NgbDropdownModule.forRoot(),
RouterTestingModule,
- StoreModule.forRoot({
- user: combineReducers(fromUser.reducer),
- }),
+ StoreModule.forRoot({}),
NotificationModule
],
declarations: [
AppComponent
],
}).compileComponents();
+
+ store = TestBed.get(Store);
+ spyOn(store, 'dispatch');
}));
it('should create the app', async(() => {
diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts
index 851158697..a832e3713 100644
--- a/ui/src/app/app.component.ts
+++ b/ui/src/app/app.component.ts
@@ -1,7 +1,7 @@
import { Component, OnInit, ChangeDetectionStrategy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Store } from '@ngrx/store';
-import 'rxjs/add/operator/takeWhile';
+import 'rxjs/add/operator/map';
import * as fromRoot from './core/reducer';
import { VersionInfo } from './core/model/version';
@@ -17,21 +17,17 @@ export class AppComponent implements OnInit {
title = 'Shib UI';
version$: Observable;
version: string;
+ formatted$: Observable;
today = new Date();
constructor(private store: Store) {
this.version$ = this.store.select(fromRoot.getVersionInfo);
+ this.formatted$ = this.version$.map(v => v && v.build ? `${v.build.version}-${v.git.commit.id}` : '');
}
ngOnInit(): void {
this.store.dispatch(new LoadProviderRequest());
this.store.dispatch(new LoadDraftRequest());
this.store.dispatch(new VersionInfoLoadRequestAction());
-
- this.version$.subscribe(v => {
- if (v && v.build) {
- this.version = `${v.build.version}-${v.git.commit.id}`;
- }
- });
}
}
diff --git a/ui/src/app/core/reducer/index.spec.ts b/ui/src/app/core/reducer/index.spec.ts
new file mode 100644
index 000000000..b6336a397
--- /dev/null
+++ b/ui/src/app/core/reducer/index.spec.ts
@@ -0,0 +1,22 @@
+import * as fromIndex from './index';
+import * as fromUser from './user.reducer';
+import * as fromVersion from './version.reducer';
+import { VersionInfo } from '../model/version';
+
+describe('Core index reducers', () => {
+ const state: fromIndex.CoreState = {
+ user: fromUser.initialState as fromUser.UserState,
+ version: fromVersion.initialState as fromVersion.VersionState
+ };
+
+ describe('getUserStateFn function', () => {
+ it('should return the user state', () => {
+ expect(fromIndex.getUserStateFn(state)).toEqual(state.user);
+ });
+ });
+ describe('getVersionStateFn function', () => {
+ it('should return the version state', () => {
+ expect(fromIndex.getVersionStateFn(state)).toEqual(state.version);
+ });
+ });
+});
diff --git a/ui/src/app/core/reducer/index.ts b/ui/src/app/core/reducer/index.ts
index 16d5ae45f..de0686765 100644
--- a/ui/src/app/core/reducer/index.ts
+++ b/ui/src/app/core/reducer/index.ts
@@ -27,8 +27,10 @@ export const reducers = {
};
export const getCoreFeature = createFeatureSelector('core');
+export const getUserStateFn = (state: CoreState) => state.user;
+export const getVersionStateFn = (state: CoreState) => state.version;
-export const getUserState = createSelector(getCoreFeature, (state: CoreState) => state.user);
+export const getUserState = createSelector(getCoreFeature, getUserStateFn);
export const getUser = createSelector(getUserState, fromUser.getUser);
export const isFetching = createSelector(getUserState, fromUser.isFetching);
export const getUserError = createSelector(getUserState, fromUser.getError);
diff --git a/ui/src/app/core/reducer/version.reducer.spec.ts b/ui/src/app/core/reducer/version.reducer.spec.ts
new file mode 100644
index 000000000..1536eeaa6
--- /dev/null
+++ b/ui/src/app/core/reducer/version.reducer.spec.ts
@@ -0,0 +1,98 @@
+import { reducer } from './version.reducer';
+import * as fromVersion from './version.reducer';
+import * as actions from '../action/version.action';
+import { VersionInfo } from '../model/version';
+
+describe('Version Reducer', () => {
+ const initialState: fromVersion.VersionState = {
+ info: {},
+ loading: false,
+ error: null
+ };
+
+ const version: VersionInfo = {
+ git: {
+ commit: {
+ time: '2018-03-28T20:14:36Z',
+ id: '40aff48'
+ },
+ branch: 'feature/SHIBUI-285'
+ },
+ build: {
+ version: '1.0.0',
+ artifact: 'shibui',
+ name: 'master',
+ group: 'foo',
+ time: '2018-03-29T14:51:38.975Z'
+ }
+ };
+
+ describe('undefined action', () => {
+ it('should return the default state', () => {
+ const result = reducer(undefined, {} as any);
+ expect(result).toEqual(initialState);
+ });
+ });
+
+ describe('Version Load Request', () => {
+ it('should set loading to true', () => {
+ const action = new actions.VersionInfoLoadRequestAction();
+ const result = reducer(initialState, action);
+ expect(result.loading).toBe(true);
+ });
+ });
+
+ describe('Version Load Success', () => {
+ it('should set loading to false', () => {
+ const action = new actions.VersionInfoLoadSuccessAction(version);
+ const result = reducer(initialState, action);
+ expect(result.loading).toBe(false);
+ });
+
+ it('should set the version data to the payload', () => {
+ const action = new actions.VersionInfoLoadSuccessAction(version);
+ const result = reducer(initialState, action);
+ expect(result.info).toEqual(version);
+ });
+ });
+
+ describe('Version Load Success', () => {
+ it('should set loading to false', () => {
+ const action = new actions.VersionInfoLoadErrorAction(new Error());
+ const result = reducer(initialState, action);
+ expect(result.loading).toBe(false);
+ });
+
+ it('should add an error to state', () => {
+ const err = new Error('fail!');
+ const action = new actions.VersionInfoLoadErrorAction(err);
+ const result = reducer(initialState, action);
+ expect(result.error).toEqual(err);
+ });
+ });
+
+ describe('getVersionInfo selector', () => {
+ it('should return the version info from state', () => {
+ const action = new actions.VersionInfoLoadSuccessAction(version);
+ const result = reducer(initialState, action);
+ expect(fromVersion.getVersionInfo(result)).toEqual(version);
+ });
+ });
+
+ describe('getError selector', () => {
+ it('should return the version error from state', () => {
+ const err = new Error('fail');
+ const action = new actions.VersionInfoLoadErrorAction(err);
+ const result = reducer(initialState, action);
+ expect(fromVersion.getVersionError(result)).toEqual(err);
+ });
+ });
+
+ describe('getLoading selector', () => {
+ it('should return the version loading status from state', () => {
+ const action = new actions.VersionInfoLoadRequestAction();
+ const result = reducer(initialState, action);
+ expect(fromVersion.getVersionIsLoading(result)).toBe(true);
+ });
+ });
+});
diff --git a/ui/src/app/edit-provider/container/editor.component.html b/ui/src/app/edit-provider/container/editor.component.html
index 7c7853a0e..767f8f177 100644
--- a/ui/src/app/edit-provider/container/editor.component.html
+++ b/ui/src/app/edit-provider/container/editor.component.html
@@ -88,8 +88,12 @@
-
-
+
+
+
+
diff --git a/ui/src/app/edit-provider/container/wizard.component.html b/ui/src/app/edit-provider/container/wizard.component.html
index 3cc781ccc..7034db1c8 100644
--- a/ui/src/app/edit-provider/container/wizard.component.html
+++ b/ui/src/app/edit-provider/container/wizard.component.html
@@ -27,8 +27,12 @@
-
-
+
+
+
+
diff --git a/ui/src/app/metadata-filter/action/filter.action.ts b/ui/src/app/metadata-filter/action/filter.action.ts
index 15791c2c4..f1fa4afde 100644
--- a/ui/src/app/metadata-filter/action/filter.action.ts
+++ b/ui/src/app/metadata-filter/action/filter.action.ts
@@ -1,25 +1,28 @@
import { Action } from '@ngrx/store';
import { QueryParams } from '../../core/model/query';
+import { MetadataProvider } from '../../metadata-provider/model/metadata-provider';
export const QUERY_ENTITY_IDS = '[Filter] Query Entity Ids';
export const VIEW_MORE_IDS = '[Filter] View More Ids Modal';
export const CANCEL_VIEW_MORE = '[Filter] Cancel View More';
export const SELECT_ID = '[Filter] Select Entity ID';
-export const CANCEL_CREATE_FILTER = '[Filter] Cancel Create Filter';
export const LOAD_ENTITY_IDS_SUCCESS = '[Entity ID Collection] Load Entity Ids Success';
export const LOAD_ENTITY_IDS_ERROR = '[Entity ID Collection] Load Entity Ids Error';
+export const CREATE_FILTER = '[Filter] Create Filter';
+export const UPDATE_FILTER = '[Filter] Update Filter';
+export const SAVE_FILTER = '[Filter] Save Filter';
+export const SAVE_FILTER_SUCCESS = '[Filter] Save Filter Success';
+export const SAVE_FILTER_ERROR = '[Filter] Save Filter Error';
+export const CANCEL_CREATE_FILTER = '[Filter] Cancel Create Filter';
+
export class QueryEntityIds implements Action {
readonly type = QUERY_ENTITY_IDS;
constructor(public payload: QueryParams) { }
}
-export class CancelCreateFilter implements Action {
- readonly type = CANCEL_CREATE_FILTER;
-}
-
export class ViewMoreIds implements Action {
readonly type = VIEW_MORE_IDS;
@@ -48,11 +51,50 @@ export class LoadEntityIdsError implements Action {
constructor(public payload: Error) { }
}
+export class CreateFilter implements Action {
+ readonly type = CREATE_FILTER;
+
+ constructor(public payload: MetadataProvider) { }
+}
+
+export class UpdateFilter implements Action {
+ readonly type = UPDATE_FILTER;
+
+ constructor(public payload: Partial) { }
+}
+
+export class SaveFilter implements Action {
+ readonly type = SAVE_FILTER;
+
+ constructor(public payload: Partial) { }
+}
+
+export class SaveFilterSuccess implements Action {
+ readonly type = SAVE_FILTER_SUCCESS;
+
+ constructor(public payload: MetadataProvider) { }
+}
+
+export class SaveFilterError implements Action {
+ readonly type = SAVE_FILTER_ERROR;
+
+ constructor(public payload: Error) { }
+}
+
+export class CancelCreateFilter implements Action {
+ readonly type = CANCEL_CREATE_FILTER;
+}
+
export type Actions =
| ViewMoreIds
| CancelViewMore
- | CancelCreateFilter
| SelectId
| LoadEntityIdsSuccess
| LoadEntityIdsError
- | QueryEntityIds;
+ | QueryEntityIds
+ | CreateFilter
+ | UpdateFilter
+ | SaveFilter
+ | SaveFilterSuccess
+ | SaveFilterError
+ | CancelCreateFilter;
diff --git a/ui/src/app/metadata-filter/component/filter-form.component.html b/ui/src/app/metadata-filter/component/filter-form.component.html
new file mode 100644
index 000000000..4609c6f85
--- /dev/null
+++ b/ui/src/app/metadata-filter/component/filter-form.component.html
@@ -0,0 +1,143 @@
+
diff --git a/ui/src/app/metadata-filter/component/filter-form.component.scss b/ui/src/app/metadata-filter/component/filter-form.component.scss
new file mode 100644
index 000000000..e69de29bb
diff --git a/ui/src/app/metadata-filter/component/filter-form.component.spec.ts b/ui/src/app/metadata-filter/component/filter-form.component.spec.ts
new file mode 100644
index 000000000..30790e2cc
--- /dev/null
+++ b/ui/src/app/metadata-filter/component/filter-form.component.spec.ts
@@ -0,0 +1,73 @@
+import { TestBed, ComponentFixture } from '@angular/core/testing';
+import { ReactiveFormsModule, FormBuilder } from '@angular/forms';
+import { StoreModule, Store, combineReducers } from '@ngrx/store';
+import { FilterFormComponent } from './filter-form.component';
+import * as fromFilter from '../reducer';
+import { ProviderEditorFormModule } from '../../metadata-provider/component';
+import { ProviderStatusEmitter, ProviderValueEmitter } from '../../metadata-provider/service/provider-change-emitter.service';
+import { NgbPopoverModule, NgbPopoverConfig, NgbModalModule } from '@ng-bootstrap/ng-bootstrap';
+import { NavigatorService } from '../../core/service/navigator.service';
+import { SharedModule } from '../../shared/shared.module';
+
+describe('Metadata Filter Form Component', () => {
+ let fixture: ComponentFixture;
+ let store: Store;
+ let instance: FilterFormComponent;
+
+ beforeEach(() => {
+ TestBed.configureTestingModule({
+ providers: [
+ ProviderStatusEmitter,
+ ProviderValueEmitter,
+ FormBuilder,
+ NgbPopoverConfig,
+ NavigatorService
+ ],
+ imports: [
+ StoreModule.forRoot({
+ 'metadata-filter': combineReducers(fromFilter.reducers),
+ }),
+ ReactiveFormsModule,
+ ProviderEditorFormModule,
+ NgbPopoverModule,
+ NgbModalModule,
+ SharedModule
+ ],
+ declarations: [FilterFormComponent],
+ });
+
+ fixture = TestBed.createComponent(FilterFormComponent);
+ instance = fixture.componentInstance;
+ store = TestBed.get(Store);
+
+ spyOn(store, 'dispatch').and.callThrough();
+ });
+
+ it('should compile', () => {
+ fixture.detectChanges();
+
+ expect(fixture).toBeDefined();
+ });
+
+ describe('searchEntityIds method', () => {
+ it('should NOT dispatch a loadEntityIds action until there are 4 or more characters', () => {
+ fixture.detectChanges();
+ instance.searchEntityIds('foo');
+ expect(store.dispatch).not.toHaveBeenCalled();
+ });
+
+ it('should dispatch a loadEntityIds action', () => {
+ fixture.detectChanges();
+ instance.searchEntityIds('foo-');
+ expect(store.dispatch).toHaveBeenCalled();
+ });
+ });
+
+ describe('onViewMore method', () => {
+ it('should dispatch a viewMoreEntityIds action', () => {
+ fixture.detectChanges();
+ instance.onViewMore('foo');
+ expect(store.dispatch).toHaveBeenCalled();
+ });
+ });
+});
diff --git a/ui/src/app/metadata-filter/component/filter-form.component.ts b/ui/src/app/metadata-filter/component/filter-form.component.ts
new file mode 100644
index 000000000..d00213d74
--- /dev/null
+++ b/ui/src/app/metadata-filter/component/filter-form.component.ts
@@ -0,0 +1,93 @@
+import { Component, OnInit, OnChanges, OnDestroy, Input, Output, EventEmitter, SimpleChanges } from '@angular/core';
+import { FormBuilder, FormArray, Validators } from '@angular/forms';
+import { Store } from '@ngrx/store';
+import { Observable } from 'rxjs/Observable';
+import 'rxjs/add/observable/of';
+import 'rxjs/add/operator/takeWhile';
+
+import * as fromFilter from '../reducer';
+import { ProviderFormFragmentComponent } from '../../metadata-provider/component/forms/provider-form-fragment.component';
+import { ProviderStatusEmitter, ProviderValueEmitter } from '../../metadata-provider/service/provider-change-emitter.service';
+import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+import { SearchDialogComponent } from '../component/search-dialog.component';
+import { ViewMoreIds, CancelCreateFilter, QueryEntityIds } from '../action/filter.action';
+import { EntityValidators } from '../../metadata-provider/service/entity-validators.service';
+import { MetadataProvider } from '../../metadata-provider/model/metadata-provider';
+
+@Component({
+ selector: 'filter-form',
+ templateUrl: './filter-form.component.html'
+})
+export class FilterFormComponent extends ProviderFormFragmentComponent implements OnInit, OnDestroy {
+
+ @Input() provider: MetadataProvider;
+
+ @Output() onSave: EventEmitter = new EventEmitter();
+ @Output() onCancel: EventEmitter = new EventEmitter();
+ @Output() onPreview: EventEmitter = new EventEmitter();
+
+ ids: string[];
+ entityIds$: Observable;
+ showMore$: Observable;
+ selected$: Observable;
+ loading$: Observable;
+ processing$: Observable;
+
+ nameIdFormatList: FormArray;
+ authenticationMethodList: FormArray;
+
+ constructor(
+ private store: Store,
+ protected statusEmitter: ProviderStatusEmitter,
+ protected valueEmitter: ProviderValueEmitter,
+ protected fb: FormBuilder
+ ) {
+ super(fb, statusEmitter, valueEmitter);
+
+ this.showMore$ = this.store.select(fromFilter.getViewingMore);
+ this.selected$ = this.store.select(fromFilter.getSelected);
+ this.entityIds$ = this.store.select(fromFilter.getEntityCollection);
+ this.loading$ = this.store.select(fromFilter.getIsLoading);
+ this.processing$ = this.loading$.withLatestFrom(this.showMore$, (l, s) => !s && l);
+
+ this.entityIds$.subscribe(ids => this.ids = ids);
+
+ this.selected$.subscribe(s => {
+ this.form.patchValue({
+ entityId: s
+ });
+ });
+ }
+
+ createForm(): void {
+ this.form = this.fb.group({
+ entityId: ['', [Validators.required]],
+ serviceProviderName: ['', [Validators.required]],
+ filterEnabled: [false]
+ });
+ }
+
+ ngOnInit(): void {
+ super.ngOnInit();
+ let id = this.form.get('entityId');
+ id.setAsyncValidators([EntityValidators.existsInCollection(this.entityIds$)]);
+ id.valueChanges
+ .distinctUntilChanged()
+ .subscribe(query => this.searchEntityIds(query));
+ }
+
+ ngOnDestroy(): void { }
+
+ searchEntityIds(term: string): void {
+ if (term.length >= 4 && this.ids.indexOf(term) < 0) {
+ this.store.dispatch(new QueryEntityIds({
+ term,
+ limit: 10
+ }));
+ }
+ }
+
+ onViewMore(query: string): void {
+ this.store.dispatch(new ViewMoreIds(query));
+ }
+}
diff --git a/ui/src/app/metadata-filter/component/preview-filter.component.html b/ui/src/app/metadata-filter/component/preview-filter.component.html
new file mode 100644
index 000000000..ecf5de875
--- /dev/null
+++ b/ui/src/app/metadata-filter/component/preview-filter.component.html
@@ -0,0 +1,14 @@
+
+
+
{{ filter$ | async | json }}
+
+
\ No newline at end of file
diff --git a/ui/src/app/metadata-filter/component/preview-filter.component.ts b/ui/src/app/metadata-filter/component/preview-filter.component.ts
new file mode 100644
index 000000000..560321a9c
--- /dev/null
+++ b/ui/src/app/metadata-filter/component/preview-filter.component.ts
@@ -0,0 +1,23 @@
+import { Component, AfterViewInit, Input, OnInit, SimpleChange, SimpleChanges } from '@angular/core';
+import { NgbModal, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
+import { Observable } from 'rxjs/Observable';
+import { Store } from '@ngrx/store';
+
+import * as fromFilter from '../reducer';
+import { QueryEntityIds } from '../action/filter.action';
+import { FormBuilder, FormGroup } from '@angular/forms';
+import { MetadataProvider } from '../../metadata-provider/model/metadata-provider';
+
+@Component({
+ selector: 'preview-filter',
+ templateUrl: './preview-filter.component.html'
+})
+export class PreviewFilterComponent {
+ filter$: Observable;
+ constructor(
+ public activeModal: NgbActiveModal,
+ private store: Store
+ ) {
+ this.filter$ = this.store.select(fromFilter.getFilter);
+ }
+}
diff --git a/ui/src/app/metadata-filter/component/search-dialog.component.html b/ui/src/app/metadata-filter/component/search-dialog.component.html
index 447f7312e..ea20d5ce0 100644
--- a/ui/src/app/metadata-filter/component/search-dialog.component.html
+++ b/ui/src/app/metadata-filter/component/search-dialog.component.html
@@ -19,8 +19,12 @@
/>
-
@@ -31,7 +35,7 @@
*ngFor="let item of matches$ | async"
class="list-group-item list-group-item-action p-1"
[class.active]="item === selected"
- (click)="select($event, item)"
+ (click)="$event.preventDefault(); selected = item;"
role="menuitem"
[innerHTML]="item | highlight:this.form.get('search').value">
diff --git a/ui/src/app/metadata-filter/component/search-dialog.component.spec.ts b/ui/src/app/metadata-filter/component/search-dialog.component.spec.ts
index 617a4c302..aa86d0289 100644
--- a/ui/src/app/metadata-filter/component/search-dialog.component.spec.ts
+++ b/ui/src/app/metadata-filter/component/search-dialog.component.spec.ts
@@ -9,6 +9,7 @@ import { SharedModule } from '../../shared/shared.module';
describe('Search Dialog', () => {
let fixture: ComponentFixture;
+ let store: Store;
let instance: SearchDialogComponent;
beforeEach(() => {
@@ -31,6 +32,10 @@ describe('Search Dialog', () => {
fixture = TestBed.createComponent(SearchDialogComponent);
instance = fixture.componentInstance;
+
+ store = TestBed.get(Store);
+
+ spyOn(store, 'dispatch').and.callThrough();
});
it('should compile', () => {
@@ -38,4 +43,18 @@ describe('Search Dialog', () => {
expect(fixture).toBeDefined();
});
+
+ describe('search method', () => {
+ it('should dispatch a search action', () => {
+ fixture.detectChanges();
+ instance.search('foo');
+ expect(store.dispatch).toHaveBeenCalled();
+ });
+
+ it('should dispatch a search action with the default string if not provided', () => {
+ fixture.detectChanges();
+ instance.search();
+ expect(store.dispatch).toHaveBeenCalled();
+ });
+ });
});
diff --git a/ui/src/app/metadata-filter/component/search-dialog.component.ts b/ui/src/app/metadata-filter/component/search-dialog.component.ts
index 1059a7383..21de7fb4a 100644
--- a/ui/src/app/metadata-filter/component/search-dialog.component.ts
+++ b/ui/src/app/metadata-filter/component/search-dialog.component.ts
@@ -18,6 +18,7 @@ export class SearchDialogComponent implements OnInit, AfterViewInit {
@Input() source = 'InCommon';
matches$: Observable;
+ loading$: Observable;
selected: string;
@@ -34,21 +35,12 @@ export class SearchDialogComponent implements OnInit, AfterViewInit {
private store: Store,
private fb: FormBuilder
) {
- // this.query$ = this.store.select(fromFilter.getQuery);
this.matches$ = this.store.select(fromFilter.getEntityCollection);
}
ngOnInit(): void {
let search = this.form.get('search');
search.setValue(this.term);
-
- search.valueChanges
- .debounceTime(this.dbounce)
- .subscribe(val =>
- this.store.dispatch(
- new QueryEntityIds({ term: val, limit: this.limit })
- )
- );
}
ngAfterViewInit(): void {
@@ -56,8 +48,7 @@ export class SearchDialogComponent implements OnInit, AfterViewInit {
this.store.dispatch(new QueryEntityIds({ term, limit }));
}
- select($event: MouseEvent, id: string): void {
- $event.preventDefault();
- this.selected = id;
+ search(term: string = ''): void {
+ this.store.dispatch(new QueryEntityIds({ term, limit: this.limit }));
}
} /* istanbul ignore next */
diff --git a/ui/src/app/metadata-filter/container/new-filter.component.html b/ui/src/app/metadata-filter/container/new-filter.component.html
index 999fcf63a..036f09d25 100644
--- a/ui/src/app/metadata-filter/container/new-filter.component.html
+++ b/ui/src/app/metadata-filter/container/new-filter.component.html
@@ -11,104 +11,12 @@
-
+
diff --git a/ui/src/app/metadata-filter/container/new-filter.component.spec.ts b/ui/src/app/metadata-filter/container/new-filter.component.spec.ts
index 1786ada5f..6d77525ac 100644
--- a/ui/src/app/metadata-filter/container/new-filter.component.spec.ts
+++ b/ui/src/app/metadata-filter/container/new-filter.component.spec.ts
@@ -5,9 +5,10 @@ import { NewFilterComponent } from './new-filter.component';
import * as fromFilter from '../reducer';
import { ProviderEditorFormModule } from '../../metadata-provider/component';
import { ProviderStatusEmitter, ProviderValueEmitter } from '../../metadata-provider/service/provider-change-emitter.service';
-import { NgbPopoverModule, NgbPopoverConfig } from '@ng-bootstrap/ng-bootstrap';
+import { NgbPopoverModule, NgbPopoverConfig, NgbModalModule, NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { NavigatorService } from '../../core/service/navigator.service';
import { SharedModule } from '../../shared/shared.module';
+import { FilterFormComponent } from '../component/filter-form.component';
describe('New Metadata Filter Page', () => {
let fixture: ComponentFixture;
@@ -32,7 +33,10 @@ describe('New Metadata Filter Page', () => {
NgbPopoverModule,
SharedModule
],
- declarations: [NewFilterComponent],
+ declarations: [
+ NewFilterComponent,
+ FilterFormComponent
+ ],
});
fixture = TestBed.createComponent(NewFilterComponent);
@@ -55,26 +59,4 @@ describe('New Metadata Filter Page', () => {
expect(store.dispatch).toHaveBeenCalled();
});
});
-
- describe('searchEntityIds method', () => {
- it('should NOT dispatch a loadEntityIds action until there are 4 or more characters', () => {
- fixture.detectChanges();
- instance.searchEntityIds('foo');
- expect(store.dispatch).not.toHaveBeenCalled();
- });
-
- it('should dispatch a loadEntityIds action', () => {
- fixture.detectChanges();
- instance.searchEntityIds('foo-');
- expect(store.dispatch).toHaveBeenCalled();
- });
- });
-
- describe('onViewMore method', () => {
- it('should dispatch a viewMoreEntityIds action', () => {
- fixture.detectChanges();
- instance.onViewMore('foo');
- expect(store.dispatch).toHaveBeenCalled();
- });
- });
});
diff --git a/ui/src/app/metadata-filter/container/new-filter.component.ts b/ui/src/app/metadata-filter/container/new-filter.component.ts
index e3756855c..ad4ec547b 100644
--- a/ui/src/app/metadata-filter/container/new-filter.component.ts
+++ b/ui/src/app/metadata-filter/container/new-filter.component.ts
@@ -4,87 +4,71 @@ import { Store } from '@ngrx/store';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/takeWhile';
+import 'rxjs/add/observable/fromPromise';
import * as fromFilter from '../reducer';
import { ProviderFormFragmentComponent } from '../../metadata-provider/component/forms/provider-form-fragment.component';
import { ProviderStatusEmitter, ProviderValueEmitter } from '../../metadata-provider/service/provider-change-emitter.service';
-import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
+import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { SearchDialogComponent } from '../component/search-dialog.component';
-import { ViewMoreIds, CancelCreateFilter, QueryEntityIds } from '../action/filter.action';
+import { ViewMoreIds, CancelCreateFilter, QueryEntityIds, CreateFilter, UpdateFilter, SaveFilter } from '../action/filter.action';
import { EntityValidators } from '../../metadata-provider/service/entity-validators.service';
+import { MetadataProvider } from '../../metadata-provider/model/metadata-provider';
+import { PreviewFilterComponent } from '../component/preview-filter.component';
@Component({
selector: 'new-filter-page',
templateUrl: './new-filter.component.html'
})
-export class NewFilterComponent extends ProviderFormFragmentComponent implements OnInit, OnChanges, OnDestroy {
-
- ids: string[];
- entityIds$: Observable;
- showMore$: Observable;
- selected$: Observable;
- loading$: Observable;
- processing$: Observable;
+export class NewFilterComponent implements OnInit, OnChanges, OnDestroy {
+
+ changes$: Observable;
+ changes: MetadataProvider;
+ filter: MetadataProvider = {
+ entityId: '',
+ serviceProviderName: '',
+ relyingPartyOverrides: {
+ signAssertion: false,
+ dontSignResponse: false,
+ turnOffEncryption: false,
+ useSha: false,
+ ignoreAuthenticationMethod: false,
+ omitNotBefore: false,
+ responderId: '',
+ nameIdFormats: [],
+ authenticationMethods: []
+ },
+ attributeRelease: []
+ };
constructor(
private store: Store,
- protected statusEmitter: ProviderStatusEmitter,
- protected valueEmitter: ProviderValueEmitter,
- protected fb: FormBuilder
+ private valueEmitter: ProviderValueEmitter
) {
- super(fb, statusEmitter, valueEmitter);
-
- this.showMore$ = this.store.select(fromFilter.getViewingMore);
- this.selected$ = this.store.select(fromFilter.getSelected);
- this.entityIds$ = this.store.select(fromFilter.getEntityCollection);
- this.loading$ = this.store.select(fromFilter.getIsLoading);
- this.processing$ = this.loading$.withLatestFrom(this.showMore$, (l, s) => !s && l);
-
- this.entityIds$.subscribe(ids => this.ids = ids);
-
- this.selected$.subscribe(s => {
- this.form.patchValue({
- entityId: s
- });
- });
- }
-
- createForm(): void {
- this.form = this.fb.group({
- entityId: ['', [Validators.required]],
- filterName: ['', [Validators.required]]
- });
+ this.changes$ = this.store.select(fromFilter.getFilter);
+ this.changes$.subscribe(c => this.changes = c);
}
ngOnInit(): void {
- super.ngOnInit();
+ this.store.dispatch(new CreateFilter(this.filter));
- this.form.get('entityId').setAsyncValidators([EntityValidators.existsInCollection(this.entityIds$)]);
+ this.valueEmitter.changeEmitted$.subscribe((changes) => {
+ this.store.dispatch(new UpdateFilter(changes));
+ });
}
ngOnChanges(): void {}
ngOnDestroy(): void {}
- searchEntityIds(term: string): void {
- if (term.length >= 4 && this.ids.indexOf(term) < 0) {
- this.store.dispatch(new QueryEntityIds({
- term,
- limit: 10
- }));
- }
- }
-
- onViewMore(query: string): void {
- this.store.dispatch(new ViewMoreIds(query));
- }
-
save(): void {
- console.log('Save!');
+ this.store.dispatch(new SaveFilter(this.changes));
}
cancel(): void {
this.store.dispatch(new CancelCreateFilter());
}
+
+ preview(): void {}
}
diff --git a/ui/src/app/metadata-filter/effect/filter.effect.ts b/ui/src/app/metadata-filter/effect/filter.effect.ts
index 5c5431dee..8eb70ba88 100644
--- a/ui/src/app/metadata-filter/effect/filter.effect.ts
+++ b/ui/src/app/metadata-filter/effect/filter.effect.ts
@@ -16,6 +16,8 @@ import * as fromFilter from '../reducer';
import { SearchDialogComponent } from '../component/search-dialog.component';
import { EntityIdService } from '../../metadata-provider/service/entity-id.service';
+import { MetadataFilterService } from '../service/filter.service';
+import { MetadataProvider } from '../../metadata-provider/model/metadata-provider';
@Injectable()
export class FilterEffects {
@@ -52,24 +54,29 @@ export class FilterEffects {
.map(id => new filter.SelectId(id))
.catch(() => Observable.of(new filter.CancelViewMore()));
});
- /*
+
@Effect()
- viewMoreQuery$ = this.actions$
- .ofType(filter.VIEW_MORE_IDS)
+ saveFilter$ = this.actions$
+ .ofType(filter.SAVE_FILTER)
.map(action => action.payload)
- .switchMap(query =>
- this.idService
- .query(query)
- .map(ids => new filter.LoadEntityIdsSuccess(ids))
- .catch(error => Observable.of(new filter.LoadEntityIdsError(error)))
+ .switchMap(unsaved =>
+ this.filterService
+ .save(unsaved as MetadataProvider)
+ .map(saved => new filter.SaveFilterSuccess(saved))
+ .catch(error => Observable.of(new filter.SaveFilterError(error)))
);
- */
+
+ @Effect({ dispatch: false })
+ saveFilterSuccess$ = this.actions$
+ .ofType(filter.SAVE_FILTER_SUCCESS)
+ .switchMap(() => this.router.navigate(['/dashboard']));
constructor(
private actions$: Actions,
private router: Router,
private modalService: NgbModal,
private idService: EntityIdService,
+ private filterService: MetadataFilterService,
private store: Store
) { }
}
diff --git a/ui/src/app/metadata-filter/filter.module.ts b/ui/src/app/metadata-filter/filter.module.ts
index fad0b9c2f..7249d6434 100644
--- a/ui/src/app/metadata-filter/filter.module.ts
+++ b/ui/src/app/metadata-filter/filter.module.ts
@@ -12,7 +12,10 @@ import { ProviderEditorFormModule } from '../metadata-provider/component';
import { FilterEffects } from './effect/filter.effect';
import { NgbPopoverModule, NgbModalModule } from '@ng-bootstrap/ng-bootstrap';
import { SearchDialogComponent } from './component/search-dialog.component';
+import { FilterFormComponent } from './component/filter-form.component';
import { SharedModule } from '../shared/shared.module';
+import { PreviewFilterComponent } from './component/preview-filter.component';
+import { MetadataFilterService } from './service/filter.service';
export const routes: Routes = [
{
@@ -25,10 +28,13 @@ export const routes: Routes = [
@NgModule({
declarations: [
NewFilterComponent,
- SearchDialogComponent
+ FilterFormComponent,
+ SearchDialogComponent,
+ PreviewFilterComponent
],
entryComponents: [
- SearchDialogComponent
+ SearchDialogComponent,
+ PreviewFilterComponent
],
imports: [
CommonModule,
@@ -42,6 +48,8 @@ export const routes: Routes = [
NgbModalModule,
SharedModule
],
- providers: []
+ providers: [
+ MetadataFilterService
+ ]
})
export class FilterModule { }
diff --git a/ui/src/app/metadata-filter/reducer/collection.reducer.ts b/ui/src/app/metadata-filter/reducer/collection.reducer.ts
new file mode 100644
index 000000000..b7301da83
--- /dev/null
+++ b/ui/src/app/metadata-filter/reducer/collection.reducer.ts
@@ -0,0 +1,30 @@
+import { createSelector, createFeatureSelector } from '@ngrx/store';
+import { MetadataProvider } from '../../metadata-provider/model/metadata-provider';
+import * as filter from '../action/filter.action';
+import * as fromRoot from '../../core/reducer';
+
+export interface CollectionState {
+ filters: MetadataProvider[];
+}
+
+export const initialState: CollectionState = {
+ filters: []
+};
+
+export function reducer(state = initialState, action: filter.Actions): CollectionState {
+ switch (action.type) {
+ case filter.SAVE_FILTER_SUCCESS: {
+ return {
+ filters: [
+ ...state.filters,
+ action.payload
+ ]
+ };
+ }
+ default: {
+ return state;
+ }
+ }
+}
+
+export const getFilters = (state: CollectionState) => state.filters;
diff --git a/ui/src/app/metadata-filter/reducer/filter.reducer.spec.ts b/ui/src/app/metadata-filter/reducer/filter.reducer.spec.ts
index 54ea59a6a..e1cd2ef5e 100644
--- a/ui/src/app/metadata-filter/reducer/filter.reducer.spec.ts
+++ b/ui/src/app/metadata-filter/reducer/filter.reducer.spec.ts
@@ -8,7 +8,8 @@ const snapshot: fromFilter.FilterState = {
viewMore: false,
loading: false,
error: null,
- term: ''
+ term: '',
+ filter: null
};
describe('Filter Reducer', () => {
diff --git a/ui/src/app/metadata-filter/reducer/filter.reducer.ts b/ui/src/app/metadata-filter/reducer/filter.reducer.ts
index ef435a7c7..e21d1a945 100644
--- a/ui/src/app/metadata-filter/reducer/filter.reducer.ts
+++ b/ui/src/app/metadata-filter/reducer/filter.reducer.ts
@@ -10,6 +10,7 @@ export interface FilterState {
error: Error | null;
selected: string | null;
term: string;
+ filter: MetadataProvider | null;
}
export const initialState: FilterState = {
@@ -18,7 +19,8 @@ export const initialState: FilterState = {
viewMore: false,
loading: false,
error: null,
- term: ''
+ term: '',
+ filter: null
};
export function reducer(state = initialState, action: filter.Actions): FilterState {
@@ -42,11 +44,6 @@ export function reducer(state = initialState, action: filter.Actions): FilterSta
viewMore: false
};
}
- case filter.CANCEL_CREATE_FILTER: {
- return {
- ...initialState
- };
- }
case filter.QUERY_ENTITY_IDS: {
return {
...state,
@@ -69,6 +66,26 @@ export function reducer(state = initialState, action: filter.Actions): FilterSta
error: action.payload
};
}
+ case filter.CREATE_FILTER: {
+ return {
+ ...state,
+ filter: action.payload
+ };
+ }
+ case filter.UPDATE_FILTER: {
+ return {
+ ...state,
+ filter: {
+ ...state.filter,
+ ...action.payload
+ }
+ };
+ }
+ case filter.CANCEL_CREATE_FILTER: {
+ return {
+ ...initialState
+ };
+ }
default: {
return state;
}
@@ -81,3 +98,4 @@ export const getEntityIds = (state: FilterState) => state.entityIds;
export const getError = (state: FilterState) => state.error;
export const getLoading = (state: FilterState) => state.loading;
export const getTerm = (state: FilterState) => state.term;
+export const getFilter = (state: FilterState) => state.filter;
diff --git a/ui/src/app/metadata-filter/reducer/index.ts b/ui/src/app/metadata-filter/reducer/index.ts
index 230cb42ce..65dfd5966 100644
--- a/ui/src/app/metadata-filter/reducer/index.ts
+++ b/ui/src/app/metadata-filter/reducer/index.ts
@@ -1,13 +1,16 @@
import { createSelector, createFeatureSelector } from '@ngrx/store';
import * as fromRoot from '../../core/reducer';
import * as fromFilter from './filter.reducer';
+import * as fromCollection from './collection.reducer';
export interface FilterState {
filter: fromFilter.FilterState;
+ collection: fromCollection.CollectionState;
}
export const reducers = {
- filter: fromFilter.reducer
+ filter: fromFilter.reducer,
+ collection: fromCollection.reducer
};
export interface State extends fromRoot.State {
@@ -15,13 +18,17 @@ export interface State extends fromRoot.State {
}
export const getFiltersFromStateFn = (state: FilterState) => state.filter;
-
+export const getCollectionStateFn = (state: FilterState) => state.collection;
export const getFilterState = createFeatureSelector('metadata-filter');
-export const getFilterFromState = createSelector(getFilterState, getFiltersFromStateFn);
+export const getFilterFromState = createSelector(getFilterState, getFiltersFromStateFn);
export const getViewingMore = createSelector(getFilterFromState, fromFilter.getViewMore);
export const getSelected = createSelector(getFilterFromState, fromFilter.getSelected);
export const getEntityCollection = createSelector(getFilterFromState, fromFilter.getEntityIds);
export const getIsLoading = createSelector(getFilterFromState, fromFilter.getLoading);
export const getError = createSelector(getFilterFromState, fromFilter.getError);
export const getTerm = createSelector(getFilterFromState, fromFilter.getTerm);
+export const getFilter = createSelector(getFilterFromState, fromFilter.getFilter);
+
+export const getCollectionFromState = createSelector(getFilterState, getCollectionStateFn);
+export const getCollection = createSelector(getCollectionFromState, fromCollection.getFilters);
diff --git a/ui/src/app/metadata-filter/service/filter.service.ts b/ui/src/app/metadata-filter/service/filter.service.ts
new file mode 100644
index 000000000..468003ff3
--- /dev/null
+++ b/ui/src/app/metadata-filter/service/filter.service.ts
@@ -0,0 +1,18 @@
+import 'rxjs/add/observable/of';
+import 'rxjs/add/operator/concat';
+import { Injectable } from '@angular/core';
+import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
+import { Observable } from 'rxjs/Observable';
+import { MetadataProvider } from '../../metadata-provider/model/metadata-provider';
+
+@Injectable()
+export class MetadataFilterService {
+
+ constructor(
+ private http: HttpClient
+ ) { }
+ save(provider: MetadataProvider): Observable {
+ const saved = { ...provider, id: Date.now() + '_' + Math.random().toString(36).substr(2, 9) };
+ return Observable.of(saved);
+ }
+}
diff --git a/ui/src/app/metadata-provider/component/forms/attribute-release-form.component.html b/ui/src/app/metadata-provider/component/forms/attribute-release-form.component.html
index b202cf0c4..2bde67d72 100644
--- a/ui/src/app/metadata-provider/component/forms/attribute-release-form.component.html
+++ b/ui/src/app/metadata-provider/component/forms/attribute-release-form.component.html
@@ -1,6 +1,6 @@
+
+
\ No newline at end of file
diff --git a/ui/src/app/metadata-provider/component/forms/attribute-release-form.component.ts b/ui/src/app/metadata-provider/component/forms/attribute-release-form.component.ts
index d1e80f9af..0e085941e 100644
--- a/ui/src/app/metadata-provider/component/forms/attribute-release-form.component.ts
+++ b/ui/src/app/metadata-provider/component/forms/attribute-release-form.component.ts
@@ -1,6 +1,8 @@
import { Component, Output, Input, EventEmitter, OnInit, OnChanges, OnDestroy, ChangeDetectionStrategy } from '@angular/core';
import { FormBuilder, FormGroup, FormControl, Validators } from '@angular/forms';
import { Observable } from 'rxjs/Observable';
+import 'rxjs/add/operator/last';
+
import { ProviderFormFragmentComponent } from './provider-form-fragment.component';
import { ProviderStatusEmitter, ProviderValueEmitter } from '../../service/provider-change-emitter.service';
import { MetadataProvider, Organization, Contact } from '../../model/metadata-provider';
@@ -49,7 +51,7 @@ export class AttributeReleaseFormComponent extends ProviderFormFragmentComponent
}
isChecked(attr): boolean {
- return this.provider.attributeRelease.indexOf(attr) > -1;
+ return this.attributeRelease.controls.findIndex(control => control.value === attr) > -1;
}
setAttributes(list: string[] = []): void {
@@ -59,13 +61,27 @@ export class AttributeReleaseFormComponent extends ProviderFormFragmentComponent
}
onCheck($event, attr: string): void {
- const checked = $event.target.checked;
+ const checked = $event ? $event.target.checked : true;
if (checked) {
this.attributeRelease.push(this.fb.control(attr));
} else {
const index = this.attributeRelease.controls.findIndex(control => control.value === attr);
this.attributeRelease.removeAt(index);
}
- this.attributeRelease.updateValueAndValidity();
+ }
+
+ onCheckAll(): void {
+ this.onCheckNone();
+ this.listOfAttributes$.last().subscribe(attrs => {
+ attrs.forEach(attr => this.onCheck(null, attr.key));
+ });
+ }
+ onCheckNone(event: Event | null = null): void {
+ if (event) {
+ event.preventDefault();
+ }
+ while (this.attributeRelease.controls.length !== 0) {
+ this.attributeRelease.removeAt(0);
+ }
}
} /* istanbul ignore next */
diff --git a/ui/src/app/metadata-provider/component/forms/descriptor-info-form.component.html b/ui/src/app/metadata-provider/component/forms/descriptor-info-form.component.html
index 5d7d72809..971780ba3 100644
--- a/ui/src/app/metadata-provider/component/forms/descriptor-info-form.component.html
+++ b/ui/src/app/metadata-provider/component/forms/descriptor-info-form.component.html
@@ -43,7 +43,6 @@
[formControlName]="i"
[matches]="nameIds$ | async"
[required]="true"
- [allowCustom]="true"
(onChange)="updateOptions($event)"
role="textbox"
aria-label="Name ID Format (type for auto-complete)">
diff --git a/ui/src/app/metadata-provider/component/forms/relying-party-form.component.html b/ui/src/app/metadata-provider/component/forms/relying-party-form.component.html
index b3f3fab4c..b21a1e955 100644
--- a/ui/src/app/metadata-provider/component/forms/relying-party-form.component.html
+++ b/ui/src/app/metadata-provider/component/forms/relying-party-form.component.html
@@ -1,167 +1,155 @@