From f3b97a7c29410107ad33e4e4265e72080c03f6ad Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 2 Jul 2018 11:06:34 -0700 Subject: [PATCH 01/15] Adding checkbox component --- ui/src/app/schema-form/registry.ts | 5 ++-- ui/src/app/schema-form/schema-form.module.ts | 4 ++- .../widget/check/checkbox.component.html | 28 +++++++++++++++++++ .../widget/check/checkbox.component.ts | 9 ++++++ 4 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 ui/src/app/schema-form/widget/check/checkbox.component.html create mode 100644 ui/src/app/schema-form/widget/check/checkbox.component.ts diff --git a/ui/src/app/schema-form/registry.ts b/ui/src/app/schema-form/registry.ts index 85e7d3a7d..904377313 100644 --- a/ui/src/app/schema-form/registry.ts +++ b/ui/src/app/schema-form/registry.ts @@ -15,6 +15,7 @@ import { RangeWidget } from 'ngx-schema-form'; import { TextAreaWidget } from 'ngx-schema-form'; import { CustomSelectComponent } from './widget/select/select.component'; import { DatalistComponent } from './widget/datalist/datalist.component'; +import { CustomCheckboxComponent } from './widget/check/checkbox.component'; export class CustomWidgetRegistry extends WidgetRegistry { @@ -37,6 +38,8 @@ export class CustomWidgetRegistry extends WidgetRegistry { this.register('fieldset', FieldsetComponent); this.register('select', CustomSelectComponent); + this.register('boolean', CustomCheckboxComponent); + this.register('checkbox', CustomCheckboxComponent); this.register('datalist', DatalistComponent); @@ -52,8 +55,6 @@ export class CustomWidgetRegistry extends WidgetRegistry { this.register('file', FileWidget); this.register('radio', RadioWidget); - this.register('boolean', CheckboxWidget); - this.register('checkbox', CheckboxWidget); this.register('button', ButtonWidget); diff --git a/ui/src/app/schema-form/schema-form.module.ts b/ui/src/app/schema-form/schema-form.module.ts index 95549b7de..89dc8ffc3 100644 --- a/ui/src/app/schema-form/schema-form.module.ts +++ b/ui/src/app/schema-form/schema-form.module.ts @@ -11,13 +11,15 @@ import { NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'; import { SharedModule } from '../shared/shared.module'; import { CustomSelectComponent } from './widget/select/select.component'; import { DatalistComponent } from './widget/datalist/datalist.component'; +import { CustomCheckboxComponent } from './widget/check/checkbox.component'; export const COMPONENTS = [ BooleanRadioComponent, FieldsetComponent, CustomStringComponent, CustomSelectComponent, - DatalistComponent + DatalistComponent, + CustomCheckboxComponent ]; @NgModule({ diff --git a/ui/src/app/schema-form/widget/check/checkbox.component.html b/ui/src/app/schema-form/widget/check/checkbox.component.html new file mode 100644 index 000000000..7ce7eba62 --- /dev/null +++ b/ui/src/app/schema-form/widget/check/checkbox.component.html @@ -0,0 +1,28 @@ +
+ +
+ +
+ +
+ +
+
+
\ No newline at end of file diff --git a/ui/src/app/schema-form/widget/check/checkbox.component.ts b/ui/src/app/schema-form/widget/check/checkbox.component.ts new file mode 100644 index 000000000..ebb784d71 --- /dev/null +++ b/ui/src/app/schema-form/widget/check/checkbox.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +import { CheckboxWidget } from 'ngx-schema-form'; + +@Component({ + selector: 'checkbox-component', + templateUrl: `./checkbox.component.html` +}) +export class CustomCheckboxComponent extends CheckboxWidget { } From 26828df2376a8989ac829e94a6c530cd9704f076 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Mon, 2 Jul 2018 15:15:23 -0700 Subject: [PATCH 02/15] SHIBUI-579 Added metadata filters --- ui/src/app/schema-form/registry.ts | 12 ++--- ui/src/app/schema-form/schema-form.module.ts | 6 ++- .../widget/array/array.component.html | 23 ++++++++ .../widget/array/array.component.ts | 9 ++++ .../widget/check/checkbox.component.html | 53 +++++++++++++------ .../widget/select/select.component.html | 4 +- .../widget/textarea/textarea.component.html | 21 ++++++++ .../widget/textarea/textarea.component.ts | 9 ++++ .../filebacked-http-filters.schema.json | 15 +++--- 9 files changed, 121 insertions(+), 31 deletions(-) create mode 100644 ui/src/app/schema-form/widget/array/array.component.html create mode 100644 ui/src/app/schema-form/widget/array/array.component.ts create mode 100644 ui/src/app/schema-form/widget/textarea/textarea.component.html create mode 100644 ui/src/app/schema-form/widget/textarea/textarea.component.ts diff --git a/ui/src/app/schema-form/registry.ts b/ui/src/app/schema-form/registry.ts index 904377313..d197dcf2c 100644 --- a/ui/src/app/schema-form/registry.ts +++ b/ui/src/app/schema-form/registry.ts @@ -4,18 +4,17 @@ import { CustomStringComponent } from './widget/text/string.component'; import { WidgetRegistry } from 'ngx-schema-form'; -import { ArrayWidget } from 'ngx-schema-form'; import { ButtonWidget } from 'ngx-schema-form'; -import { CheckboxWidget } from 'ngx-schema-form'; import { FileWidget } from 'ngx-schema-form'; import { IntegerWidget } from 'ngx-schema-form'; import { ObjectWidget } from 'ngx-schema-form'; import { RadioWidget } from 'ngx-schema-form'; import { RangeWidget } from 'ngx-schema-form'; -import { TextAreaWidget } from 'ngx-schema-form'; import { CustomSelectComponent } from './widget/select/select.component'; import { DatalistComponent } from './widget/datalist/datalist.component'; import { CustomCheckboxComponent } from './widget/check/checkbox.component'; +import { CustomTextAreaComponent } from './widget/textarea/textarea.component'; +import { CustomArrayComponent } from './widget/array/array.component'; export class CustomWidgetRegistry extends WidgetRegistry { @@ -35,24 +34,25 @@ export class CustomWidgetRegistry extends WidgetRegistry { this.register('time', CustomStringComponent); this.register('boolean-radio', BooleanRadioComponent); + this.register('fieldset', FieldsetComponent); + this.register('array', CustomArrayComponent); this.register('select', CustomSelectComponent); this.register('boolean', CustomCheckboxComponent); this.register('checkbox', CustomCheckboxComponent); + this.register('textarea', CustomTextAreaComponent); + this.register('datalist', DatalistComponent); /* NGX-Form */ - this.register('array', ArrayWidget); this.register('object', ObjectWidget); this.register('integer', IntegerWidget); this.register('number', IntegerWidget); this.register('range', RangeWidget); - this.register('textarea', TextAreaWidget); - this.register('file', FileWidget); this.register('radio', RadioWidget); diff --git a/ui/src/app/schema-form/schema-form.module.ts b/ui/src/app/schema-form/schema-form.module.ts index 89dc8ffc3..d51ae2269 100644 --- a/ui/src/app/schema-form/schema-form.module.ts +++ b/ui/src/app/schema-form/schema-form.module.ts @@ -12,6 +12,8 @@ import { SharedModule } from '../shared/shared.module'; import { CustomSelectComponent } from './widget/select/select.component'; import { DatalistComponent } from './widget/datalist/datalist.component'; import { CustomCheckboxComponent } from './widget/check/checkbox.component'; +import { CustomTextAreaComponent } from './widget/textarea/textarea.component'; +import { CustomArrayComponent } from './widget/array/array.component'; export const COMPONENTS = [ BooleanRadioComponent, @@ -19,7 +21,9 @@ export const COMPONENTS = [ CustomStringComponent, CustomSelectComponent, DatalistComponent, - CustomCheckboxComponent + CustomCheckboxComponent, + CustomTextAreaComponent, + CustomArrayComponent ]; @NgModule({ diff --git a/ui/src/app/schema-form/widget/array/array.component.html b/ui/src/app/schema-form/widget/array/array.component.html new file mode 100644 index 000000000..577c084f9 --- /dev/null +++ b/ui/src/app/schema-form/widget/array/array.component.html @@ -0,0 +1,23 @@ +
+
+ +   + +
+
+
+ +
+ +
+
+
+ {{schema.description}} +
\ No newline at end of file diff --git a/ui/src/app/schema-form/widget/array/array.component.ts b/ui/src/app/schema-form/widget/array/array.component.ts new file mode 100644 index 000000000..52987cda7 --- /dev/null +++ b/ui/src/app/schema-form/widget/array/array.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +import { ArrayWidget } from 'ngx-schema-form'; + +@Component({ + selector: 'array-component', + templateUrl: `./array.component.html` +}) +export class CustomArrayComponent extends ArrayWidget {} diff --git a/ui/src/app/schema-form/widget/check/checkbox.component.html b/ui/src/app/schema-form/widget/check/checkbox.component.html index 7ce7eba62..4c2745660 100644 --- a/ui/src/app/schema-form/widget/check/checkbox.component.html +++ b/ui/src/app/schema-form/widget/check/checkbox.component.html @@ -1,18 +1,27 @@
- -
- -
- + +
+
+ + + +
+
+ {{ schema.description }} + +
+
+
+
-
\ No newline at end of file + + \ No newline at end of file diff --git a/ui/src/app/schema-form/widget/select/select.component.html b/ui/src/app/schema-form/widget/select/select.component.html index 831a7e852..7b4bf8173 100644 --- a/ui/src/app/schema-form/widget/select/select.component.html +++ b/ui/src/app/schema-form/widget/select/select.component.html @@ -1,7 +1,7 @@
-
\ No newline at end of file diff --git a/ui/src/app/schema-form/widget/textarea/textarea.component.ts b/ui/src/app/schema-form/widget/textarea/textarea.component.ts new file mode 100644 index 000000000..6586d7d1f --- /dev/null +++ b/ui/src/app/schema-form/widget/textarea/textarea.component.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +import { TextAreaWidget } from 'ngx-schema-form'; + +@Component({ + selector: 'textarea-component', + templateUrl: `./textarea.component.html` +}) +export class CustomTextAreaComponent extends TextAreaWidget {} diff --git a/ui/src/assets/schema/provider/filebacked-http-filters.schema.json b/ui/src/assets/schema/provider/filebacked-http-filters.schema.json index 2b3d32846..884b8b89e 100644 --- a/ui/src/assets/schema/provider/filebacked-http-filters.schema.json +++ b/ui/src/assets/schema/provider/filebacked-http-filters.schema.json @@ -15,7 +15,7 @@ "properties": { "maxValidityInterval": { "title": "Max Validity Interval", - "description": "", + "description": "Max Validity Interval", "type": "number" } } @@ -29,14 +29,15 @@ "properties": { "requireSignedRoot": { "title": "Require Signed Root", - "description": "", + "description": "Require Signed Root", "type": "boolean", "default": true }, "certificateFile": { "title": "Certificate File", - "description": "", - "type": "string" + "description": "Certificate File", + "type": "string", + "widget": "textarea" } } }, @@ -49,7 +50,7 @@ "properties": { "retainedRoles": { "title": "Retained Roles", - "description": "", + "description": "Retained Roles", "type": "array", "items": { "widget": { @@ -74,13 +75,13 @@ }, "removeRolelessEntityDescriptors": { "title": "Remove Roleless Entity Descriptors?", - "description": "", + "description": "Remove Roleless Entity Descriptors?", "type": "boolean", "default": true }, "removeEmptyEntitiesDescriptors": { "title": "Remove Empty Entities Descriptors?", - "description": "", + "description": "Remove Empty Entities Descriptors?", "type": "boolean", "default": true } From 15cd44411adb8640fd67a9865a1707ec8bc11e74 Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 3 Jul 2018 09:35:58 -0700 Subject: [PATCH 03/15] SHIBUI-579 Implemented metadata filters page in wizard --- ui/src/app/metadata/domain/domain.type.ts | 6 +- ui/src/app/metadata/domain/entity/index.ts | 1 - ...file-backed-http-metadata-provider.spec.ts | 104 ------------------ .../file-backed-http-metadata-provider.ts | 21 ---- .../file-backed-http-metadata-provider.ts | 5 + .../metadata/domain/model/providers/index.ts | 1 + .../metadata/provider/action/editor.action.ts | 11 +- .../metadata/provider/action/entity.action.ts | 8 +- .../container/provider-wizard.component.html | 8 +- .../container/provider-wizard.component.ts | 56 ++++++---- .../model/file-backed-http.provider.form.ts | 34 +++++- .../provider/reducer/editor.reducer.ts | 5 + .../provider/reducer/entity.reducer.ts | 9 +- ui/src/app/wizard/action/wizard.action.ts | 11 +- ui/src/app/wizard/model/wizard.ts | 7 +- ui/src/app/wizard/reducer/index.ts | 10 +- 16 files changed, 128 insertions(+), 169 deletions(-) delete mode 100644 ui/src/app/metadata/domain/entity/provider/file-backed-http-metadata-provider.spec.ts delete mode 100644 ui/src/app/metadata/domain/entity/provider/file-backed-http-metadata-provider.ts create mode 100644 ui/src/app/metadata/domain/model/providers/file-backed-http-metadata-provider.ts create mode 100644 ui/src/app/metadata/domain/model/providers/index.ts diff --git a/ui/src/app/metadata/domain/domain.type.ts b/ui/src/app/metadata/domain/domain.type.ts index 78def16d7..3cda36b83 100644 --- a/ui/src/app/metadata/domain/domain.type.ts +++ b/ui/src/app/metadata/domain/domain.type.ts @@ -6,9 +6,11 @@ import { import { EntityAttributesFilter, - FileBackedHttpMetadataResolver, - FileBackedHttpMetadataProvider + FileBackedHttpMetadataResolver } from './entity'; +import { + FileBackedHttpMetadataProvider +} from './model/providers'; export type Filter = | EntityAttributesFilter; diff --git a/ui/src/app/metadata/domain/entity/index.ts b/ui/src/app/metadata/domain/entity/index.ts index 1adb3735e..648255369 100644 --- a/ui/src/app/metadata/domain/entity/index.ts +++ b/ui/src/app/metadata/domain/entity/index.ts @@ -1,3 +1,2 @@ export * from './filter/entity-attributes-filter'; -export * from './provider/file-backed-http-metadata-provider'; export * from './resolver/file-backed-http-metadata-resolver'; diff --git a/ui/src/app/metadata/domain/entity/provider/file-backed-http-metadata-provider.spec.ts b/ui/src/app/metadata/domain/entity/provider/file-backed-http-metadata-provider.spec.ts deleted file mode 100644 index 70b0245a4..000000000 --- a/ui/src/app/metadata/domain/entity/provider/file-backed-http-metadata-provider.spec.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { FileBackedHttpMetadataProvider } from './file-backed-http-metadata-provider'; - -describe('FileBackedHttmMetadataProvider construct', () => { - - const config = { - id: 'foo', - entityId: 'string', - serviceProviderName: 'string', - organization: { - 'name': 'string', - 'displayName': 'string', - 'url': 'string' - }, - contacts: [ - { - 'name': 'string', - 'type': 'string', - 'emailAddress': 'string' - } - ], - mdui: { - 'displayName': 'string', - 'informationUrl': 'string', - 'privacyStatementUrl': 'string', - 'logoUrl': 'string', - 'logoHeight': 100, - 'logoWidth': 100, - 'description': 'string' - }, - securityInfo: { - 'x509CertificateAvailable': true, - 'authenticationRequestsSigned': true, - 'wantAssertionsSigned': true, - 'x509Certificates': [ - { - 'name': 'string', - 'type': 'string', - 'value': 'string' - } - ] - }, - assertionConsumerServices: [ - { - 'binding': 'string', - 'locationUrl': 'string', - 'makeDefault': true - } - ], - serviceProviderSsoDescriptor: { - 'protocolSupportEnum': 'string', - 'nameIdFormats': [ - 'string' - ] - }, - - logoutEndpoints: [ - { - 'url': 'string', - 'bindingType': 'string' - } - ], - serviceEnabled: true, - createdDate: new Date().toDateString(), - modifiedDate: new Date().toDateString(), - relyingPartyOverrides: { - 'signAssertion': true, - 'dontSignResponse': true, - 'turnOffEncryption': true, - 'useSha': true, - 'ignoreAuthenticationMethod': true, - 'omitNotBefore': true, - 'responderId': 'string', - 'nameIdFormats': [ - 'string' - ], - 'authenticationMethods': [ - 'string' - ] - }, - attributeRelease: [ - 'eduPersonPrincipalName', - 'uid', - 'mail' - ] - }; - - let entity; - - beforeEach(() => { - entity = new FileBackedHttpMetadataProvider(config); - }); - - it('should populate its own values', () => { - Object.keys(config).forEach(key => { - expect(entity[key]).toEqual(config[key]); - }); - }); - - describe('interface methods', () => { - it('should return a date object from getCreationDate', () => { - expect(entity.getCreationDate()).toEqual(new Date(config.createdDate)); - }); - }); -}); diff --git a/ui/src/app/metadata/domain/entity/provider/file-backed-http-metadata-provider.ts b/ui/src/app/metadata/domain/entity/provider/file-backed-http-metadata-provider.ts deleted file mode 100644 index 2f64a98ba..000000000 --- a/ui/src/app/metadata/domain/entity/provider/file-backed-http-metadata-provider.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { - MetadataProvider -} from '../../model'; - -export class FileBackedHttpMetadataProvider implements MetadataProvider { - id: string; - name: string; - '@type': string; - - createdDate?: string; - modifiedDate?: string; - version: string; - - constructor(descriptor?: Partial) { - Object.assign(this, descriptor); - } - - getCreationDate(): Date { - return new Date(this.createdDate); - } -} diff --git a/ui/src/app/metadata/domain/model/providers/file-backed-http-metadata-provider.ts b/ui/src/app/metadata/domain/model/providers/file-backed-http-metadata-provider.ts new file mode 100644 index 000000000..7d107036b --- /dev/null +++ b/ui/src/app/metadata/domain/model/providers/file-backed-http-metadata-provider.ts @@ -0,0 +1,5 @@ +import { MetadataProvider } from '../metadata-provider'; + +export interface FileBackedHttpMetadataProvider extends MetadataProvider { + metadataFilters: any[]; +} diff --git a/ui/src/app/metadata/domain/model/providers/index.ts b/ui/src/app/metadata/domain/model/providers/index.ts new file mode 100644 index 000000000..0cd02d47d --- /dev/null +++ b/ui/src/app/metadata/domain/model/providers/index.ts @@ -0,0 +1 @@ +export * from './file-backed-http-metadata-provider'; \ No newline at end of file diff --git a/ui/src/app/metadata/provider/action/editor.action.ts b/ui/src/app/metadata/provider/action/editor.action.ts index ca195716f..85ea00edd 100644 --- a/ui/src/app/metadata/provider/action/editor.action.ts +++ b/ui/src/app/metadata/provider/action/editor.action.ts @@ -6,7 +6,9 @@ export enum EditorActionTypes { LOAD_SCHEMA_SUCCESS = '[Provider Editor] Load Schema Success', LOAD_SCHEMA_FAIL = '[Provider Editor] Load Schema Fail', - SELECT_PROVIDER_TYPE = '[Provider Editor] Select Provider Type' + SELECT_PROVIDER_TYPE = '[Provider Editor] Select Provider Type', + + CLEAR = '[Provider Editor] Clear' } export class UpdateStatus implements Action { @@ -39,9 +41,14 @@ export class SelectProviderType implements Action { constructor(public payload: string) { } } +export class ClearEditor implements Action { + readonly type = EditorActionTypes.CLEAR; +} + export type EditorActionUnion = | UpdateStatus | LoadSchemaRequest | LoadSchemaSuccess | LoadSchemaFail - | SelectProviderType; + | SelectProviderType + | ClearEditor; diff --git a/ui/src/app/metadata/provider/action/entity.action.ts b/ui/src/app/metadata/provider/action/entity.action.ts index bff45b5f4..12c2efa5f 100644 --- a/ui/src/app/metadata/provider/action/entity.action.ts +++ b/ui/src/app/metadata/provider/action/entity.action.ts @@ -9,7 +9,8 @@ export enum EntityActionTypes { SAVE_PROVIDER_SUCCESS = '[Provider Entity] Save Provider Success', SAVE_PROVIDER_FAIL = '[Provider Entity] Save Provider Fail', - RESET_CHANGES = '[Provider Entity] Reset Provider Changes' + CLEAR_PROVIDER = '[Provider Entity] Clear', + RESET_CHANGES = '[Provider Entity] Reset Changes' } export class SelectProvider implements Action { @@ -48,6 +49,10 @@ export class SaveProviderFail implements Action { constructor(public payload: Error) { } } +export class ClearProvider implements Action { + readonly type = EntityActionTypes.CLEAR_PROVIDER; +} + export class ResetChanges implements Action { readonly type = EntityActionTypes.RESET_CHANGES; } @@ -59,4 +64,5 @@ export type EntityActionUnion = | SaveProviderSuccess | SaveProviderFail | CreateProvider + | ClearProvider | ResetChanges; diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.html b/ui/src/app/metadata/provider/container/provider-wizard.component.html index 58adb7221..085756e5d 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard.component.html +++ b/ui/src/app/metadata/provider/container/provider-wizard.component.html @@ -5,14 +5,12 @@
- + --> diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.ts b/ui/src/app/metadata/provider/container/provider-wizard.component.ts index 36861d3cf..3209bd051 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard.component.ts +++ b/ui/src/app/metadata/provider/container/provider-wizard.component.ts @@ -1,20 +1,18 @@ - import { Component, OnDestroy } from '@angular/core'; -import { ActivatedRoute, Router } from '@angular/router'; -import { Subscription, Observable, Subject } from 'rxjs'; -import { distinctUntilChanged, map, withLatestFrom } from 'rxjs/operators'; +import { Observable, Subject } from 'rxjs'; +import { map, withLatestFrom } from 'rxjs/operators'; import { Store } from '@ngrx/store'; import * as fromProvider from '../reducer'; import * as fromWizard from '../../../wizard/reducer'; -import { SetIndex, SetDisabled, UpdateDefinition, WizardActionTypes, Next, SetDefinition } from '../../../wizard/action/wizard.action'; -import { LoadSchemaRequest, UpdateStatus } from '../action/editor.action'; +import { SetIndex, SetDisabled, SetDefinition, ClearWizard } from '../../../wizard/action/wizard.action'; +import { LoadSchemaRequest, UpdateStatus, ClearEditor } from '../action/editor.action'; import { startWith } from 'rxjs/operators'; import { Wizard, WizardStep } from '../../../wizard/model'; import { MetadataProvider } from '../../domain/model'; import { MetadataProviderTypes, MetadataProviderWizard } from '../model'; -import { UpdateProvider } from '../action/entity.action'; +import { UpdateProvider, ClearProvider } from '../action/entity.action'; import { pick } from '../../../shared/util'; @Component({ @@ -24,8 +22,6 @@ import { pick } from '../../../shared/util'; }) export class ProviderWizardComponent implements OnDestroy { - actionsSubscription: Subscription; - changeSubject = new Subject>(); private changeEmitted$ = this.changeSubject.asObservable(); @@ -33,11 +29,10 @@ export class ProviderWizardComponent implements OnDestroy { schema: any; definition$: Observable>; changes$: Observable; + model$: Observable; currentPage: string; valid$: Observable; - formModel: any; - nextStep: WizardStep; previousStep: WizardStep; @@ -57,6 +52,26 @@ export class ProviderWizardComponent implements OnDestroy { this.store.select(fromWizard.getNext).subscribe(n => this.nextStep = n); this.store.select(fromWizard.getPrevious).subscribe(p => this.previousStep = p); + this.model$ = this.schema$.pipe( + withLatestFrom( + this.store.select(fromWizard.getModel), + this.changes$, + this.definition$ + ), + map(([schema, model, changes, definition]) => { + return ({ + model: { + ...model, + ...changes + }, + definition + }); + }), + map(({ model, definition }) => { + return definition.translate ? definition.translate.formatter(model) : model; + }) + ); + this.valid$ .pipe(startWith(false)) .subscribe((valid) => { @@ -65,12 +80,9 @@ export class ProviderWizardComponent implements OnDestroy { this.schema$.subscribe(s => this.schema = s); - this.changeEmitted$ - .pipe( + this.changeEmitted$.pipe( withLatestFrom(this.schema$, this.definition$), - ) - .subscribe( - ([changes, schema, definition]) => { + map(([changes, schema, definition]) => { const type = changes.value['@type']; if (type && type !== definition.type) { const newDefinition = MetadataProviderTypes.find(def => def.type === type); @@ -86,13 +98,17 @@ export class ProviderWizardComponent implements OnDestroy { changes = { value: pick(Object.keys(schema.properties))(changes.value) }; } } - this.store.dispatch(new UpdateProvider(changes.value)); - } - ); + return { changes: changes.value, definition }; + }), + map(({ changes, definition }) => definition.translate ? definition.translate.parser(changes) : changes) + ) + .subscribe(changes => this.store.dispatch(new UpdateProvider(changes))); } ngOnDestroy() { - this.actionsSubscription.unsubscribe(); + this.store.dispatch(new ClearProvider()); + this.store.dispatch(new ClearWizard()); + this.store.dispatch(new ClearEditor()); this.changeSubject.complete(); } diff --git a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts index 4f77f9e35..9b31fa220 100644 --- a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts +++ b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts @@ -1,17 +1,39 @@ import { Wizard } from '../../../wizard/model'; -import { MetadataProvider } from '../../domain/model'; +import { FileBackedHttpMetadataProvider } from '../../domain/model/providers/file-backed-http-metadata-provider'; -export const FileBackedHttpMetadataProviderWizard: Wizard = { +export const FileBackedHttpMetadataProviderWizard: Wizard = { label: 'FileBackedHttpMetadataProvider', type: '@FileBackedHttpMetadataProvider', + translate: { + parser: (changes) => ({ + ...changes, + metadataFilters: [ + ...Object.keys(changes.metadataFilters || {}).reduce((collection, filterName) => ([ + ...collection, + { + ...changes.metadataFilters[filterName], + '@type': filterName + } + ]), []) + ] + }), + formatter: (changes) => ({ + ...changes, + metadataFilters: { + ...(changes.metadataFilters || []).reduce((collection, filter) => ({ + ...collection, + [filter['@type']]: filter + }), {}) + } + }) + }, steps: [ { id: 'common', label: 'Common Attributes', index: 2, initialValues: [], - schema: 'assets/schema/provider/filebacked-http-common.schema.json', - parser: (changes: Partial, schema: any) => ({ name: '', '@type': '' }) + schema: 'assets/schema/provider/filebacked-http-common.schema.json' }, { id: 'reloading', @@ -24,7 +46,9 @@ export const FileBackedHttpMetadataProviderWizard: Wizard = { id: 'filters', label: 'Metadata Filter Plugins', index: 4, - initialValues: [], + initialValues: [ + { key: 'metadataFilters', value: [] } + ], schema: 'assets/schema/provider/filebacked-http-filters.schema.json' } ] diff --git a/ui/src/app/metadata/provider/reducer/editor.reducer.ts b/ui/src/app/metadata/provider/reducer/editor.reducer.ts index 351a17917..609f707f0 100644 --- a/ui/src/app/metadata/provider/reducer/editor.reducer.ts +++ b/ui/src/app/metadata/provider/reducer/editor.reducer.ts @@ -24,6 +24,11 @@ export function reducer(state = initialState, action: EditorActionUnion): Editor type: action.payload }; } + case EditorActionTypes.CLEAR: { + return { + ...initialState + }; + } case EditorActionTypes.UPDATE_STATUS: { return { ...state, diff --git a/ui/src/app/metadata/provider/reducer/entity.reducer.ts b/ui/src/app/metadata/provider/reducer/entity.reducer.ts index 101674749..4207c42a4 100644 --- a/ui/src/app/metadata/provider/reducer/entity.reducer.ts +++ b/ui/src/app/metadata/provider/reducer/entity.reducer.ts @@ -15,10 +15,17 @@ export const initialState: EntityState = { export function reducer(state = initialState, action: EntityActionUnion): EntityState { switch (action.type) { + case EntityActionTypes.CLEAR_PROVIDER: { + return { + ...initialState + }; + } case EntityActionTypes.RESET_CHANGES: { return { ...state, - changes: initialState.changes + changes: { + ...initialState.changes + } }; } case EntityActionTypes.SELECT_PROVIDER: diff --git a/ui/src/app/wizard/action/wizard.action.ts b/ui/src/app/wizard/action/wizard.action.ts index b144601ae..55bb9b404 100644 --- a/ui/src/app/wizard/action/wizard.action.ts +++ b/ui/src/app/wizard/action/wizard.action.ts @@ -8,7 +8,9 @@ export enum WizardActionTypes { SET_DISABLED = '[Wizard] Set Disabled', NEXT = '[Wizard] Next Page', - PREVIOUS = '[Wizard] Previous Page' + PREVIOUS = '[Wizard] Previous Page', + + CLEAR = '[Wizard] Clear' } export class SetIndex implements Action { @@ -47,10 +49,15 @@ export class Previous implements Action { constructor(public payload: string) { } } +export class ClearWizard implements Action { + readonly type = WizardActionTypes.CLEAR; +} + export type WizardActionUnion = | SetIndex | SetDefinition | UpdateDefinition | SetDisabled | Next - | Previous; + | Previous + | ClearWizard; diff --git a/ui/src/app/wizard/model/wizard.ts b/ui/src/app/wizard/model/wizard.ts index bcceb9f00..b95f18cd9 100644 --- a/ui/src/app/wizard/model/wizard.ts +++ b/ui/src/app/wizard/model/wizard.ts @@ -1,9 +1,11 @@ -import { MetadataProvider } from '../../metadata/domain/model'; - export interface Wizard { label: string; type: string; steps: WizardStep[]; + translate?: { + parser(changes: Partial, schema?: any), + formatter(changes: Partial, schema?: any) + }; } export interface WizardStep { @@ -12,7 +14,6 @@ export interface WizardStep { initialValues?: WizardValue[]; schema: string; index: number; - parser?(changes: Partial, schema: any); } export interface WizardValue { diff --git a/ui/src/app/wizard/reducer/index.ts b/ui/src/app/wizard/reducer/index.ts index 1119d5d14..bde6660fb 100644 --- a/ui/src/app/wizard/reducer/index.ts +++ b/ui/src/app/wizard/reducer/index.ts @@ -1,7 +1,7 @@ import * as fromRoot from '../../app.reducer'; import * as fromWizard from './wizard.reducer'; import { createFeatureSelector, createSelector } from '@ngrx/store'; -import { Wizard } from '../model'; +import { Wizard, WizardStep } from '../model'; export interface WizardState { wizard: fromWizard.State; @@ -28,7 +28,6 @@ export const getSchema = (index: string, wizard: Wizard) => { return step ? step.schema : null; }; - export const getCurrentWizardSchema = createSelector(getWizardIndex, getWizardDefinition, getSchema); export const getPreviousFn = (index: string, wizard: Wizard) => { @@ -54,7 +53,14 @@ export const getLastFn = (index: string, wizard: Wizard) => { return index === step.id ? step : null; }; +export const getModelFn = (currentStep: WizardStep) => { + const model = currentStep.initialValues || []; + return model.reduce((m, property) => m[property.key] = property.value, {}); +}; + export const getPrevious = createSelector(getWizardIndex, getWizardDefinition, getPreviousFn); export const getCurrent = createSelector(getWizardIndex, getWizardDefinition, getCurrentFn); export const getNext = createSelector(getWizardIndex, getWizardDefinition, getNextFn); export const getLast = createSelector(getWizardIndex, getWizardDefinition, getLastFn); +export const getModel = createSelector(getCurrent, getModelFn); + From 44146a8b1a2db529d1b143c17fef8f1ef4b4882c Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 3 Jul 2018 09:51:54 -0700 Subject: [PATCH 04/15] SHIBUI-646 Fixed default route with redirect --- ui/src/app/app.routing.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/src/app/app.routing.ts b/ui/src/app/app.routing.ts index 3de190469..9903039c9 100644 --- a/ui/src/app/app.routing.ts +++ b/ui/src/app/app.routing.ts @@ -3,6 +3,7 @@ import { Routes, RouterModule } from '@angular/router'; const routes: Routes = [ { path: '', redirectTo: 'metadata', pathMatch: 'full' }, + { path: 'dashboard', redirectTo: 'metadata', pathMatch: 'full' }, { path: 'metadata', loadChildren: './metadata/metadata.module#MetadataModule' From cba08fc77fcd1962e369818528b49b34f6cfc59b Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Tue, 3 Jul 2018 11:31:12 -0700 Subject: [PATCH 05/15] SHIBUI-580 Re-organized code for provider wizard containers --- .../container/new-provider.component.scss | 5 - .../provider-wizard-step.component.html | 5 + .../provider-wizard-step.component.ts | 92 +++++++++++++++++++ ...=> provider-wizard-summary.component.html} | 0 .../provider-wizard-summary.component.ts | 21 +++++ .../container/provider-wizard.component.html | 6 +- .../container/provider-wizard.component.ts | 63 ++----------- ...component.html => provider.component.html} | 0 ...ponent.scss => provider.component.spec.ts} | 0 ...der.component.ts => provider.component.ts} | 8 +- .../app/metadata/provider/provider.module.ts | 10 +- .../app/metadata/provider/provider.routing.ts | 21 ++++- 12 files changed, 153 insertions(+), 78 deletions(-) delete mode 100644 ui/src/app/metadata/provider/container/new-provider.component.scss create mode 100644 ui/src/app/metadata/provider/container/provider-wizard-step.component.html create mode 100644 ui/src/app/metadata/provider/container/provider-wizard-step.component.ts rename ui/src/app/metadata/provider/container/{new-provider.component.spec.ts => provider-wizard-summary.component.html} (100%) create mode 100644 ui/src/app/metadata/provider/container/provider-wizard-summary.component.ts rename ui/src/app/metadata/provider/container/{new-provider.component.html => provider.component.html} (100%) rename ui/src/app/metadata/provider/container/{provider-wizard.component.scss => provider.component.spec.ts} (100%) rename ui/src/app/metadata/provider/container/{new-provider.component.ts => provider.component.ts} (81%) diff --git a/ui/src/app/metadata/provider/container/new-provider.component.scss b/ui/src/app/metadata/provider/container/new-provider.component.scss deleted file mode 100644 index e3d0c671e..000000000 --- a/ui/src/app/metadata/provider/container/new-provider.component.scss +++ /dev/null @@ -1,5 +0,0 @@ -// :host { -// .provider-nav-option { -// width: 160px; -// } -// } diff --git a/ui/src/app/metadata/provider/container/provider-wizard-step.component.html b/ui/src/app/metadata/provider/container/provider-wizard-step.component.html new file mode 100644 index 000000000..842a4a2c2 --- /dev/null +++ b/ui/src/app/metadata/provider/container/provider-wizard-step.component.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/ui/src/app/metadata/provider/container/provider-wizard-step.component.ts b/ui/src/app/metadata/provider/container/provider-wizard-step.component.ts new file mode 100644 index 000000000..b4c553f0a --- /dev/null +++ b/ui/src/app/metadata/provider/container/provider-wizard-step.component.ts @@ -0,0 +1,92 @@ +import { Component, OnDestroy } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { withLatestFrom } from 'rxjs/operators'; +import { Store } from '@ngrx/store'; + +import * as fromProvider from '../reducer'; +import * as fromWizard from '../../../wizard/reducer'; + +import { SetDisabled, SetDefinition } from '../../../wizard/action/wizard.action'; +import { LoadSchemaRequest, UpdateStatus } from '../action/editor.action'; +import { startWith } from 'rxjs/operators'; +import { Wizard } from '../../../wizard/model'; +import { MetadataProvider } from '../../domain/model'; +import { MetadataProviderTypes, MetadataProviderWizard } from '../model'; +import { UpdateProvider } from '../action/entity.action'; +import { pick } from '../../../shared/util'; + +@Component({ + selector: 'provider-wizard-step', + templateUrl: './provider-wizard-step.component.html', + styleUrls: [] +}) + +export class ProviderWizardStepComponent implements OnDestroy { + changeSubject = new Subject>(); + private changeEmitted$ = this.changeSubject.asObservable(); + + schema$: Observable; + schema: any; + definition$: Observable>; + changes$: Observable; + currentPage: string; + valid$: Observable; + + constructor( + private store: Store + ) { + this.store + .select(fromWizard.getCurrentWizardSchema) + .subscribe(s => { + this.store.dispatch(new LoadSchemaRequest(s)); + }); + + this.schema$ = this.store.select(fromProvider.getSchema); + this.valid$ = this.store.select(fromProvider.getEditorIsValid); + this.definition$ = this.store.select(fromWizard.getWizardDefinition); + this.changes$ = this.store.select(fromProvider.getEntityChanges); + + this.valid$ + .pipe(startWith(false)) + .subscribe((valid) => { + this.store.dispatch(new SetDisabled(!valid)); + }); + + this.schema$.subscribe(s => this.schema = s); + + this.changeEmitted$ + .pipe( + withLatestFrom(this.schema$, this.definition$), + ) + .subscribe( + ([changes, schema, definition]) => { + const type = changes.value['@type']; + if (type && type !== definition.type) { + const newDefinition = MetadataProviderTypes.find(def => def.type === type); + if (newDefinition) { + this.store.dispatch(new SetDefinition({ + ...MetadataProviderWizard, + ...newDefinition, + steps: [ + ...MetadataProviderWizard.steps, + ...newDefinition.steps + ] + })); + changes = { value: pick(Object.keys(schema.properties))(changes.value) }; + } + } + this.store.dispatch(new UpdateProvider(changes.value)); + } + ); + } + + ngOnDestroy() { + this.changeSubject.complete(); + } + + onStatusChange(value): void { + const status = { [this.currentPage]: value ? 'VALID' : 'INVALID' }; + this.store.dispatch(new UpdateStatus(status)); + } +} + diff --git a/ui/src/app/metadata/provider/container/new-provider.component.spec.ts b/ui/src/app/metadata/provider/container/provider-wizard-summary.component.html similarity index 100% rename from ui/src/app/metadata/provider/container/new-provider.component.spec.ts rename to ui/src/app/metadata/provider/container/provider-wizard-summary.component.html diff --git a/ui/src/app/metadata/provider/container/provider-wizard-summary.component.ts b/ui/src/app/metadata/provider/container/provider-wizard-summary.component.ts new file mode 100644 index 000000000..d16ee2498 --- /dev/null +++ b/ui/src/app/metadata/provider/container/provider-wizard-summary.component.ts @@ -0,0 +1,21 @@ +import { Component } from '@angular/core'; +import { Store } from '@ngrx/store'; + +import * as fromProvider from '../reducer'; + +@Component({ + selector: 'provider-wizard-summary', + templateUrl: './provider-wizard-summary.component.html', + styleUrls: [] +}) + +export class ProviderWizardSummaryComponent { + constructor( + private store: Store + ) {} + + save(): void { + console.log('Save!'); + } +} + diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.html b/ui/src/app/metadata/provider/container/provider-wizard.component.html index 58adb7221..cbeb7c675 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard.component.html +++ b/ui/src/app/metadata/provider/container/provider-wizard.component.html @@ -3,11 +3,7 @@
- +
+ + diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.ts b/ui/src/app/metadata/provider/container/provider-wizard.component.ts index 248efe68b..a9383bd64 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard.component.ts +++ b/ui/src/app/metadata/provider/container/provider-wizard.component.ts @@ -1,5 +1,5 @@ import { Component, OnDestroy } from '@angular/core'; -import { Observable } from 'rxjs'; +import { Observable, combineLatest } from 'rxjs'; import { Store } from '@ngrx/store'; import * as fromProvider from '../reducer'; @@ -10,6 +10,8 @@ import { startWith } from 'rxjs/operators'; import { Wizard, WizardStep } from '../../../wizard/model'; import { MetadataProvider } from '../../domain/model'; import { ClearProvider } from '../action/entity.action'; +import { Router, ActivatedRoute } from '@angular/router'; +import { map } from 'rxjs/operators'; @Component({ @@ -19,9 +21,6 @@ import { ClearProvider } from '../action/entity.action'; }) export class ProviderWizardComponent implements OnDestroy { - - schema$: Observable; - schema: any; definition$: Observable>; changes$: Observable; model$: Observable; @@ -31,8 +30,12 @@ export class ProviderWizardComponent implements OnDestroy { nextStep: WizardStep; previousStep: WizardStep; + summary$: Observable<{ definition: Wizard, schema: { [id: string]: any }, model: any }>; + constructor( - private store: Store + private store: Store, + private router: Router, + private route: ActivatedRoute ) { this.store .select(fromWizard.getCurrentWizardSchema) @@ -41,8 +44,10 @@ export class ProviderWizardComponent implements OnDestroy { }); this.valid$ = this.store.select(fromProvider.getEditorIsValid); this.changes$ = this.store.select(fromProvider.getEntityChanges); + this.store.select(fromWizard.getNext).subscribe(n => this.nextStep = n); this.store.select(fromWizard.getPrevious).subscribe(p => this.previousStep = p); + this.store.select(fromWizard.getWizardIndex).subscribe(i => this.currentPage = i); this.valid$ .pipe(startWith(false)) @@ -50,8 +55,13 @@ export class ProviderWizardComponent implements OnDestroy { this.store.dispatch(new SetDisabled(!valid)); }); - this.schema$.subscribe(s => this.schema = s); - + this.summary$ = combineLatest( + this.store.select(fromWizard.getWizardDefinition), + this.store.select(fromWizard.getSchemaCollection), + this.store.select(fromProvider.getEntityChanges) + ).pipe( + map(([ definition, schema, model ]) => ({ definition, schema, model })) + ); } ngOnDestroy() { @@ -73,5 +83,9 @@ export class ProviderWizardComponent implements OnDestroy { save(): void { console.log('Save!'); } + + gotoPage(page: string): void { + this.store.dispatch(new SetIndex(page)); + } } diff --git a/ui/src/app/metadata/provider/container/provider.component.ts b/ui/src/app/metadata/provider/container/provider.component.ts index c2955ca7a..afaf48a27 100644 --- a/ui/src/app/metadata/provider/container/provider.component.ts +++ b/ui/src/app/metadata/provider/container/provider.component.ts @@ -1,10 +1,5 @@ import { Component } from '@angular/core'; -import { FormGroup, Validators, FormBuilder } from '@angular/forms'; import { Store } from '@ngrx/store'; -import { ActivatedRoute } from '@angular/router'; - - -import { MetadataProviderTypes } from '../model'; import * as fromProvider from '../reducer'; import { SetDefinition, SetIndex } from '../../../wizard/action/wizard.action'; @@ -16,10 +11,7 @@ import { MetadataProviderWizard } from '../model'; styleUrls: [] }) export class ProviderComponent { - types = MetadataProviderTypes; - constructor( - private fb: FormBuilder, private store: Store ) { this.store.dispatch(new SetDefinition(MetadataProviderWizard)); diff --git a/ui/src/app/metadata/provider/effect/editor.effect.ts b/ui/src/app/metadata/provider/effect/editor.effect.ts index 7fc04f3bc..bbf1bcc48 100644 --- a/ui/src/app/metadata/provider/effect/editor.effect.ts +++ b/ui/src/app/metadata/provider/effect/editor.effect.ts @@ -8,11 +8,14 @@ import { LoadSchemaFail, EditorActionTypes } from '../action/editor.action'; -import { map, switchMap, catchError } from 'rxjs/operators'; +import { map, switchMap, catchError, withLatestFrom } from 'rxjs/operators'; import { of } from 'rxjs'; -import { SetDefinition, WizardActionTypes } from '../../../wizard/action/wizard.action'; +import { SetDefinition, WizardActionTypes, AddSchema } from '../../../wizard/action/wizard.action'; import { ResetChanges } from '../action/entity.action'; +import * as fromWizard from '../../../wizard/reducer'; +import { Store } from '@ngrx/store'; + @Injectable() export class EditorEffects { @@ -30,6 +33,14 @@ export class EditorEffects { ) ); + @Effect() + $loadSchemaSuccess = this.actions$.pipe( + ofType(EditorActionTypes.LOAD_SCHEMA_SUCCESS), + map(action => action.payload), + withLatestFrom(this.store.select(fromWizard.getWizardIndex)), + map(([schema, id]) => new AddSchema({ id, schema })) + ); + @Effect() $resetChanges = this.actions$.pipe( ofType(WizardActionTypes.SET_DEFINITION), @@ -38,6 +49,7 @@ export class EditorEffects { constructor( private schemaService: SchemaService, + private store: Store, private actions$: Actions ) { } } /* istanbul ignore next */ diff --git a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts index 9b31fa220..d4bfd143d 100644 --- a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts +++ b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts @@ -5,27 +5,27 @@ export const FileBackedHttpMetadataProviderWizard: Wizard ({ - ...changes, - metadataFilters: [ - ...Object.keys(changes.metadataFilters || {}).reduce((collection, filterName) => ([ - ...collection, - { - ...changes.metadataFilters[filterName], - '@type': filterName - } - ]), []) - ] - }), - formatter: (changes) => ({ - ...changes, - metadataFilters: { - ...(changes.metadataFilters || []).reduce((collection, filter) => ({ - ...collection, - [filter['@type']]: filter - }), {}) - } - }) + parser: (changes) => changes.metadataFilters ? ({ + ...changes, + metadataFilters: [ + ...Object.keys(changes.metadataFilters).reduce((collection, filterName) => ([ + ...collection, + { + ...changes.metadataFilters[filterName], + '@type': filterName + } + ]), []) + ] + }) : changes, + formatter: (changes) => changes.metadataFilters ? ({ + ...changes, + metadataFilters: { + ...(changes.metadataFilters || []).reduce((collection, filter) => ({ + ...collection, + [filter['@type']]: filter + }), {}) + } + }) : changes }, steps: [ { @@ -50,6 +50,13 @@ export const FileBackedHttpMetadataProviderWizard: Wizard - \ No newline at end of file diff --git a/ui/src/app/wizard/action/wizard.action.ts b/ui/src/app/wizard/action/wizard.action.ts index 55bb9b404..ecb507cb4 100644 --- a/ui/src/app/wizard/action/wizard.action.ts +++ b/ui/src/app/wizard/action/wizard.action.ts @@ -7,6 +7,8 @@ export enum WizardActionTypes { UPDATE_DEFINITION = '[Wizard] Update Definition', SET_DISABLED = '[Wizard] Set Disabled', + ADD_SCHEMA = '[Wizard] Add Schema', + NEXT = '[Wizard] Next Page', PREVIOUS = '[Wizard] Previous Page', @@ -49,6 +51,12 @@ export class Previous implements Action { constructor(public payload: string) { } } +export class AddSchema implements Action { + readonly type = WizardActionTypes.ADD_SCHEMA; + + constructor(public payload: { id: string, schema: any }) { } +} + export class ClearWizard implements Action { readonly type = WizardActionTypes.CLEAR; } @@ -60,4 +68,5 @@ export type WizardActionUnion = | SetDisabled | Next | Previous - | ClearWizard; + | ClearWizard + | AddSchema; diff --git a/ui/src/app/wizard/component/wizard.component.html b/ui/src/app/wizard/component/wizard.component.html index e5921c6ac..a4ae2b75c 100644 --- a/ui/src/app/wizard/component/wizard.component.html +++ b/ui/src/app/wizard/component/wizard.component.html @@ -8,21 +8,26 @@ Back - {{ (previous$ | async).index }}. {{ (previous$ | async).label }} + {{ (previous$ | async).index }}. + {{ (previous$ | async).label }} - diff --git a/ui/src/app/wizard/component/wizard.component.ts b/ui/src/app/wizard/component/wizard.component.ts index 5ab006519..da9cdafb0 100644 --- a/ui/src/app/wizard/component/wizard.component.ts +++ b/ui/src/app/wizard/component/wizard.component.ts @@ -13,6 +13,7 @@ import { Observable } from 'rxjs'; export class WizardComponent implements OnChanges { @Output() onNext = new EventEmitter(); @Output() onPrevious = new EventEmitter(); + @Output() onLast = new EventEmitter(); @Output() onSave = new EventEmitter(); currentPage: any = {}; diff --git a/ui/src/app/wizard/reducer/index.ts b/ui/src/app/wizard/reducer/index.ts index bde6660fb..bbf53caf4 100644 --- a/ui/src/app/wizard/reducer/index.ts +++ b/ui/src/app/wizard/reducer/index.ts @@ -22,6 +22,7 @@ export const getState = createSelector(getWizardState, getWizardStateFn); export const getWizardIndex = createSelector(getState, fromWizard.getIndex); export const getWizardIsDisabled = createSelector(getState, fromWizard.getDisabled); export const getWizardDefinition = createSelector(getState, fromWizard.getDefinition); +export const getSchemaCollection = createSelector(getState, fromWizard.getCollection); export const getSchema = (index: string, wizard: Wizard) => { const step = wizard.steps.find(s => s.id === index); @@ -63,4 +64,3 @@ export const getCurrent = createSelector(getWizardIndex, getWizardDefinition, ge export const getNext = createSelector(getWizardIndex, getWizardDefinition, getNextFn); export const getLast = createSelector(getWizardIndex, getWizardDefinition, getLastFn); export const getModel = createSelector(getCurrent, getModelFn); - diff --git a/ui/src/app/wizard/reducer/wizard.reducer.ts b/ui/src/app/wizard/reducer/wizard.reducer.ts index de40f86a3..9ecae96ce 100644 --- a/ui/src/app/wizard/reducer/wizard.reducer.ts +++ b/ui/src/app/wizard/reducer/wizard.reducer.ts @@ -5,16 +5,27 @@ export interface State { index: string; disabled: boolean; definition: Wizard; + schemaCollection: { [id: string]: any }; } export const initialState: State = { index: null, disabled: false, - definition: null + definition: null, + schemaCollection: {} }; export function reducer(state = initialState, action: WizardActionUnion): State { switch (action.type) { + case WizardActionTypes.ADD_SCHEMA: { + return { + ...state, + schemaCollection: { + ...state.schemaCollection, + [action.payload.id]: action.payload.schema + } + }; + } case WizardActionTypes.SET_DISABLED: { return { ...state, @@ -56,3 +67,4 @@ export function reducer(state = initialState, action: WizardActionUnion): State export const getIndex = (state: State) => state.index; export const getDisabled = (state: State) => state.disabled; export const getDefinition = (state: State) => state.definition; +export const getCollection = (state: State) => state.schemaCollection; diff --git a/ui/src/assets/schema/provider/metadata-provider-summary.schema.json b/ui/src/assets/schema/provider/metadata-provider-summary.schema.json new file mode 100644 index 000000000..b3cc3b73c --- /dev/null +++ b/ui/src/assets/schema/provider/metadata-provider-summary.schema.json @@ -0,0 +1,11 @@ +{ + "type": "object", + "properties": { + "enabled": { + "title": "Enable Metadata Provider", + "description": "Enable Metadata Provider", + "type": "boolean", + "default": true + } + } +} \ No newline at end of file From 382294aecf769613f924524e2a4609204433b0ee Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 5 Jul 2018 10:59:04 -0700 Subject: [PATCH 08/15] [SHIBUI-580] Added a name conflict check. --- .../admin/ui/controller/MetadataResolversController.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java index ae75b3f6d..c0ee64068 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java @@ -56,6 +56,10 @@ public ResponseEntity getOne(@PathVariable String resourceId) { @PostMapping("/MetadataResolvers") @Transactional public ResponseEntity create(@RequestBody MetadataResolver newResolver) { + if (resolverRepository.findByName(newResolver.getName()) != null) { + return ResponseEntity.status(HttpStatus.CONFLICT).build(); + } + //TODO: we are disregarding attached filters if any sent from UI. //Only deal with filters via filters endpoints? newResolver.clearAllFilters(); From 07c7e9ffe4992a914b8d235448e8f819a59e6053 Mon Sep 17 00:00:00 2001 From: Bill Smith Date: Thu, 5 Jul 2018 11:50:25 -0700 Subject: [PATCH 09/15] [SHIBUI-580] Added a simple POJO for transmitting errors from custom exception handlers. Added a custom exception handler for when bad things happen when parsing JSON Resolvers. Fixed a couple typos. --- .../admin/ui/controller/ErrorResponse.java | 18 ++++++++++++++++++ .../MetadataResolversController.java | 11 +++++++++-- .../MetadataResolverValidationService.java | 4 ++-- 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ErrorResponse.java 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 new file mode 100644 index 000000000..fa91aa3e6 --- /dev/null +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/ErrorResponse.java @@ -0,0 +1,18 @@ +package edu.internet2.tier.shibboleth.admin.ui.controller; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import lombok.ToString; + +/** + * @author Bill Smith (wsmith@unicon.net) + */ +@AllArgsConstructor +@Getter +@Setter +@ToString +public class ErrorResponse { + private String errorCode; + private String errorMessage; +} diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java index c0ee64068..5b24f5788 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/controller/MetadataResolversController.java @@ -1,15 +1,16 @@ package edu.internet2.tier.shibboleth.admin.ui.controller; +import com.fasterxml.jackson.databind.exc.InvalidTypeIdException; import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolver; import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolverValidationService; -import edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolverValidator; import edu.internet2.tier.shibboleth.admin.ui.repository.MetadataResolverRepository; - import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.http.converter.HttpMessageNotReadableException; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -19,6 +20,7 @@ import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import java.io.IOException; import java.net.URI; import static edu.internet2.tier.shibboleth.admin.ui.domain.resolvers.MetadataResolverValidator.ValidationResult; @@ -34,6 +36,11 @@ public class MetadataResolversController { @Autowired MetadataResolverValidationService metadataResolverValidationService; + @ExceptionHandler({InvalidTypeIdException.class, IOException.class, HttpMessageNotReadableException.class}) + public ResponseEntity unableToParseJson(Exception ex) { + return ResponseEntity.badRequest().body(new ErrorResponse(HttpStatus.BAD_REQUEST.toString(), ex.getMessage())); + } + @GetMapping("/MetadataResolvers") @Transactional(readOnly = true) public ResponseEntity getAll() { diff --git a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataResolverValidationService.java b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataResolverValidationService.java index 26039b81b..4151faeb6 100644 --- a/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataResolverValidationService.java +++ b/backend/src/main/java/edu/internet2/tier/shibboleth/admin/ui/domain/resolvers/MetadataResolverValidationService.java @@ -8,7 +8,7 @@ /** * A facade that aggregates {@link MetadataResolverValidator}s available to call just one of them supporting the type of a given resolver. - * If no {@link MetadataResolverValidator}s are configured, conciders provided MetadataResolver as valid. + * If no {@link MetadataResolverValidator}s are configured, considers provided MetadataResolver as valid. *

* Uses chain-of-responsibility design pattern * @@ -22,7 +22,7 @@ public MetadataResolverValidationService(List> vali this.validators = validators != null ? validators : new ArrayList<>(); } - @SuppressWarnings("Uncheked") + @SuppressWarnings("Unchecked") public ValidationResult validateIfNecessary(T metadataResolver) { Optional> validator = this.validators From f357ea488f28b5fa00bab7b041059cc73699829b Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 5 Jul 2018 11:54:45 -0700 Subject: [PATCH 10/15] SHIBUI-580 Fixed issues with saving provider --- ui/src/app/metadata/domain/domain.module.ts | 4 +- .../domain/model/metadata-provider.ts | 10 +- .../metadata/domain/service/filter.service.ts | 32 +++++ .../domain/service/provider.service.ts | 22 ++-- .../filter/effect/collection.effect.ts | 13 +- .../provider/action/collection.action.ts | 112 ++++++++++++++++++ .../metadata/provider/action/entity.action.ts | 33 ------ .../container/provider-wizard.component.ts | 17 ++- .../provider/effect/collection.effect.ts | 55 +++++++++ .../model/file-backed-http.provider.form.ts | 19 ++- .../app/metadata/provider/provider.module.ts | 3 +- .../provider/reducer/collection.reducer.ts | 52 ++++++++ .../provider/reducer/entity.reducer.ts | 20 +--- ui/src/app/metadata/provider/reducer/index.ts | 23 +++- .../wizard/component/wizard.component.html | 2 +- .../filebacked-http-common.schema.json | 7 +- .../filebacked-http-filters.schema.json | 12 +- .../filebacked-http-reloading.schema.json | 12 +- .../provider/metadata-provider.schema.json | 2 +- 19 files changed, 354 insertions(+), 96 deletions(-) create mode 100644 ui/src/app/metadata/domain/service/filter.service.ts create mode 100644 ui/src/app/metadata/provider/action/collection.action.ts create mode 100644 ui/src/app/metadata/provider/effect/collection.effect.ts create mode 100644 ui/src/app/metadata/provider/reducer/collection.reducer.ts diff --git a/ui/src/app/metadata/domain/domain.module.ts b/ui/src/app/metadata/domain/domain.module.ts index 3349342d9..c51bbb260 100644 --- a/ui/src/app/metadata/domain/domain.module.ts +++ b/ui/src/app/metadata/domain/domain.module.ts @@ -12,6 +12,7 @@ import { EntityDraftService } from './service/draft.service'; import { MetadataProviderService } from './service/provider.service'; import { EntityEffects } from './effect/entity.effect'; import { PreviewDialogComponent } from './component/preview-dialog.component'; +import { MetadataFilterService } from './service/filter.service'; export const COMPONENTS = [ PreviewDialogComponent @@ -42,7 +43,8 @@ export class DomainModule { ListValuesService, ProviderStatusEmitter, ProviderValueEmitter, - MetadataProviderService + MetadataProviderService, + MetadataFilterService ] }; } diff --git a/ui/src/app/metadata/domain/model/metadata-provider.ts b/ui/src/app/metadata/domain/model/metadata-provider.ts index 3239bd4b0..4dc283368 100644 --- a/ui/src/app/metadata/domain/model/metadata-provider.ts +++ b/ui/src/app/metadata/domain/model/metadata-provider.ts @@ -1,16 +1,10 @@ import { MetadataBase, - Organization, - Contact, - MDUI, - SecurityInfo, - SsoService, - IdpSsoDescriptor, - LogoutEndpoint, - RelyingPartyOverrides } from '../model'; export interface MetadataProvider extends MetadataBase { name: string; '@type': string; + enabled: boolean; + resourceId: string; } diff --git a/ui/src/app/metadata/domain/service/filter.service.ts b/ui/src/app/metadata/domain/service/filter.service.ts new file mode 100644 index 000000000..b96907eca --- /dev/null +++ b/ui/src/app/metadata/domain/service/filter.service.ts @@ -0,0 +1,32 @@ +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs'; + +import { MetadataFilter } from '../../domain/model'; + +@Injectable() +export class MetadataFilterService { + + readonly endpoint = '/MetadataResolvers'; + readonly base = '/api'; + + constructor( + private http: HttpClient + ) { } + query(): Observable { + return this.http.get(`${this.base}${this.endpoint}`, {}); + } + + find(id: string): Observable { + // console.log(id); + return this.http.get(`${this.base}${this.endpoint}/${id}`); + } + + update(filter: MetadataFilter): Observable { + return this.http.put(`${this.base}${this.endpoint}/${filter.id}`, filter); + } + + save(filter: MetadataFilter): Observable { + return this.http.post(`${this.base}${this.endpoint}`, filter); + } +} diff --git a/ui/src/app/metadata/domain/service/provider.service.ts b/ui/src/app/metadata/domain/service/provider.service.ts index 0644e4ab8..29cc32f36 100644 --- a/ui/src/app/metadata/domain/service/provider.service.ts +++ b/ui/src/app/metadata/domain/service/provider.service.ts @@ -2,31 +2,33 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; -import { MetadataFilter } from '../../domain/model'; +import { MetadataProvider } from '../../domain/model'; +import { FileBackedHttpMetadataProvider } from '../model/providers'; @Injectable() export class MetadataProviderService { - readonly endpoint = '/MetadataResolver/incommon/Filters'; + readonly endpoint = '/MetadataResolvers'; readonly base = '/api'; constructor( private http: HttpClient ) {} - query(): Observable { - return this.http.get(`${this.base}${this.endpoint}`, {}); + query(): Observable { + return this.http.get(`${this.base}${this.endpoint}`, {}); } - find(id: string): Observable { + find(id: string): Observable { // console.log(id); - return this.http.get(`${this.base}${this.endpoint}/${id}`); + return this.http.get(`${this.base}${this.endpoint}/${id}`); } - update(filter: MetadataFilter): Observable { - return this.http.put(`${this.base}${this.endpoint}/${filter.id}`, filter); + update(provider: MetadataProvider): Observable { + return this.http.put(`${this.base}${this.endpoint}/${provider.id}`, provider); } - save(filter: MetadataFilter): Observable { - return this.http.post(`${this.base}${this.endpoint}`, filter); + save(provider: MetadataProvider): Observable { + const { metadataFilters, id, ...pruned } = provider as FileBackedHttpMetadataProvider; + return this.http.post(`${this.base}${this.endpoint}`, pruned); } } diff --git a/ui/src/app/metadata/filter/effect/collection.effect.ts b/ui/src/app/metadata/filter/effect/collection.effect.ts index da2a08a84..5422f2d99 100644 --- a/ui/src/app/metadata/filter/effect/collection.effect.ts +++ b/ui/src/app/metadata/filter/effect/collection.effect.ts @@ -9,11 +9,10 @@ import { switchMap, map, catchError, tap } from 'rxjs/operators'; import * as actions from '../action/collection.action'; import { FilterCollectionActionTypes } from '../action/collection.action'; import * as fromFilter from '../reducer'; - -import { MetadataProviderService } from '../../domain/service/provider.service'; import { MetadataFilter } from '../../domain/model'; import { removeNulls } from '../../../shared/util'; import { EntityAttributesFilter } from '../../domain/entity/filter/entity-attributes-filter'; +import { MetadataFilterService } from '../../domain/service/filter.service'; /* istanbul ignore next */ @Injectable() @@ -23,7 +22,7 @@ export class FilterCollectionEffects { loadFilters$ = this.actions$.pipe( ofType(FilterCollectionActionTypes.LOAD_FILTER_REQUEST), switchMap(() => - this.resolverService + this.filterService .query() .pipe( map(filters => new actions.LoadFilterSuccess(filters)), @@ -36,7 +35,7 @@ export class FilterCollectionEffects { ofType(FilterCollectionActionTypes.SELECT_FILTER), map(action => action.payload), switchMap(id => { - return this.resolverService + return this.filterService .find(id) .pipe( map(p => new actions.SelectFilterSuccess(p)), @@ -57,7 +56,7 @@ export class FilterCollectionEffects { }; }), switchMap(unsaved => - this.resolverService + this.filterService .save(unsaved as MetadataFilter) .pipe( map(saved => new actions.AddFilterSuccess(saved)), @@ -86,7 +85,7 @@ export class FilterCollectionEffects { switchMap(filter => { delete filter.modifiedDate; delete filter.createdDate; - return this.resolverService + return this.filterService .update(filter) .pipe( map(p => new actions.UpdateFilterSuccess({ @@ -113,7 +112,7 @@ export class FilterCollectionEffects { constructor( private actions$: Actions, private router: Router, - private resolverService: MetadataProviderService, + private filterService: MetadataFilterService, private store: Store ) { } } diff --git a/ui/src/app/metadata/provider/action/collection.action.ts b/ui/src/app/metadata/provider/action/collection.action.ts new file mode 100644 index 000000000..934fafa46 --- /dev/null +++ b/ui/src/app/metadata/provider/action/collection.action.ts @@ -0,0 +1,112 @@ +import { Action } from '@ngrx/store'; +import { MetadataProvider } from '../../domain/model/metadata-provider'; +import { Update } from '@ngrx/entity'; + +export enum ProviderCollectionActionTypes { + FIND = '[Metadata Provider] Find', + SELECT_PROVIDER = '[Metadata Provider] Select Request', + SELECT_PROVIDER_SUCCESS = '[Metadata Provider] Select Success', + SELECT_PROVIDER_FAIL = '[Metadata Provider] Select Fail', + + UPDATE_PROVIDER_REQUEST = '[Metadata Provider] Update Request', + UPDATE_PROVIDER_SUCCESS = '[Metadata Provider] Update Success', + UPDATE_PROVIDER_FAIL = '[Metadata Provider] Update Fail', + + LOAD_PROVIDER_REQUEST = '[Metadata Provider Collection] Provider REQUEST', + LOAD_PROVIDER_SUCCESS = '[Metadata Provider Collection] Provider SUCCESS', + LOAD_PROVIDER_ERROR = '[Metadata Provider Collection] Provider ERROR', + + ADD_PROVIDER_REQUEST = '[Metadata Provider Collection] Add Provider', + ADD_PROVIDER_SUCCESS = '[Metadata Provider Collection] Add Provider Success', + ADD_PROVIDER_FAIL = '[Metadata Provider Collection] Add Provider Fail', + + REMOVE_PROVIDER_REQUEST = '[Metadata Provider Collection] Remove Provider Request', + REMOVE_PROVIDER_SUCCESS = '[Metadata Provider Collection] Remove Provider Success', + REMOVE_PROVIDER_FAIL = '[Metadata Provider Collection] Remove Provider Fail' +} + +export class LoadProviderRequest implements Action { + readonly type = ProviderCollectionActionTypes.LOAD_PROVIDER_REQUEST; + + constructor() { } +} + +export class LoadProviderSuccess implements Action { + readonly type = ProviderCollectionActionTypes.LOAD_PROVIDER_SUCCESS; + + constructor(public payload: MetadataProvider[]) { } +} + +export class LoadProviderError implements Action { + readonly type = ProviderCollectionActionTypes.LOAD_PROVIDER_ERROR; + + constructor(public payload: any) { } +} + +export class UpdateProviderRequest implements Action { + readonly type = ProviderCollectionActionTypes.UPDATE_PROVIDER_REQUEST; + + constructor(public payload: MetadataProvider) { } +} + +export class UpdateProviderSuccess implements Action { + readonly type = ProviderCollectionActionTypes.UPDATE_PROVIDER_SUCCESS; + + constructor(public payload: Update) { } +} + +export class UpdateProviderFail implements Action { + readonly type = ProviderCollectionActionTypes.UPDATE_PROVIDER_FAIL; + + constructor(public payload: MetadataProvider) { } +} + +export class AddProviderRequest implements Action { + readonly type = ProviderCollectionActionTypes.ADD_PROVIDER_REQUEST; + + constructor(public payload: MetadataProvider) { } +} + +export class AddProviderSuccess implements Action { + readonly type = ProviderCollectionActionTypes.ADD_PROVIDER_SUCCESS; + + constructor(public payload: MetadataProvider) { } +} + +export class AddProviderFail implements Action { + readonly type = ProviderCollectionActionTypes.ADD_PROVIDER_FAIL; + + constructor(public payload: any) { } +} + +export class RemoveProviderRequest implements Action { + readonly type = ProviderCollectionActionTypes.REMOVE_PROVIDER_REQUEST; + + constructor(public payload: MetadataProvider) { } +} + +export class RemoveProviderSuccess implements Action { + readonly type = ProviderCollectionActionTypes.REMOVE_PROVIDER_SUCCESS; + + constructor(public payload: MetadataProvider) { } +} + +export class RemoveProviderFail implements Action { + readonly type = ProviderCollectionActionTypes.REMOVE_PROVIDER_FAIL; + + constructor(public payload: MetadataProvider) { } +} + +export type ProviderCollectionActionsUnion = + | LoadProviderRequest + | LoadProviderSuccess + | LoadProviderError + | AddProviderRequest + | AddProviderSuccess + | AddProviderFail + | RemoveProviderRequest + | RemoveProviderSuccess + | RemoveProviderFail + | UpdateProviderRequest + | UpdateProviderSuccess + | UpdateProviderFail; diff --git a/ui/src/app/metadata/provider/action/entity.action.ts b/ui/src/app/metadata/provider/action/entity.action.ts index 12c2efa5f..84f7dbd4a 100644 --- a/ui/src/app/metadata/provider/action/entity.action.ts +++ b/ui/src/app/metadata/provider/action/entity.action.ts @@ -3,12 +3,7 @@ import { MetadataProvider } from '../../domain/model'; export enum EntityActionTypes { SELECT_PROVIDER = '[Provider Entity] Select Provider', - CREATE_PROVIDER = '[Provider Entity] Create Provider', UPDATE_PROVIDER = '[Provider Entity] Update Provider', - SAVE_PROVIDER_REQUEST = '[Provider Entity] Save Provider Request', - SAVE_PROVIDER_SUCCESS = '[Provider Entity] Save Provider Success', - SAVE_PROVIDER_FAIL = '[Provider Entity] Save Provider Fail', - CLEAR_PROVIDER = '[Provider Entity] Clear', RESET_CHANGES = '[Provider Entity] Reset Changes' } @@ -19,36 +14,12 @@ export class SelectProvider implements Action { constructor(public payload: MetadataProvider) { } } -export class CreateProvider implements Action { - readonly type = EntityActionTypes.CREATE_PROVIDER; - - constructor(public payload: MetadataProvider) { } -} - export class UpdateProvider implements Action { readonly type = EntityActionTypes.UPDATE_PROVIDER; constructor(public payload: Partial) { } } -export class SaveProviderRequest implements Action { - readonly type = EntityActionTypes.SAVE_PROVIDER_REQUEST; - - constructor(public payload: MetadataProvider) { } -} - -export class SaveProviderSuccess implements Action { - readonly type = EntityActionTypes.SAVE_PROVIDER_SUCCESS; - - constructor(public payload: MetadataProvider) { } -} - -export class SaveProviderFail implements Action { - readonly type = EntityActionTypes.SAVE_PROVIDER_FAIL; - - constructor(public payload: Error) { } -} - export class ClearProvider implements Action { readonly type = EntityActionTypes.CLEAR_PROVIDER; } @@ -60,9 +31,5 @@ export class ResetChanges implements Action { export type EntityActionUnion = | SelectProvider | UpdateProvider - | SaveProviderRequest - | SaveProviderSuccess - | SaveProviderFail - | CreateProvider | ClearProvider | ResetChanges; diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.ts b/ui/src/app/metadata/provider/container/provider-wizard.component.ts index a9383bd64..ad042590b 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard.component.ts +++ b/ui/src/app/metadata/provider/container/provider-wizard.component.ts @@ -12,6 +12,7 @@ import { MetadataProvider } from '../../domain/model'; import { ClearProvider } from '../action/entity.action'; import { Router, ActivatedRoute } from '@angular/router'; import { map } from 'rxjs/operators'; +import { AddProviderRequest } from '../action/collection.action'; @Component({ @@ -32,6 +33,16 @@ export class ProviderWizardComponent implements OnDestroy { summary$: Observable<{ definition: Wizard, schema: { [id: string]: any }, model: any }>; + provider: MetadataProvider; + + namesList: string[] = []; + + validators = { + '/name': (value, property, form) => { + return this.namesList.indexOf(value) > -1 ? { 'name': { 'expectedValue': 'unique' } } : null; + } + }; + constructor( private store: Store, private router: Router, @@ -62,6 +73,10 @@ export class ProviderWizardComponent implements OnDestroy { ).pipe( map(([ definition, schema, model ]) => ({ definition, schema, model })) ); + + this.changes$.subscribe(c => this.provider = c); + + this.store.select(fromProvider.getProviderNames).subscribe(list => this.namesList = list); } ngOnDestroy() { @@ -81,7 +96,7 @@ export class ProviderWizardComponent implements OnDestroy { } save(): void { - console.log('Save!'); + this.store.dispatch(new AddProviderRequest(this.provider)); } gotoPage(page: string): void { diff --git a/ui/src/app/metadata/provider/effect/collection.effect.ts b/ui/src/app/metadata/provider/effect/collection.effect.ts new file mode 100644 index 000000000..19e521f3c --- /dev/null +++ b/ui/src/app/metadata/provider/effect/collection.effect.ts @@ -0,0 +1,55 @@ +import { Injectable } from '@angular/core'; +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { Router } from '@angular/router'; + +import { of } from 'rxjs'; +import { map, catchError, switchMap, tap } from 'rxjs/operators'; +import { + ProviderCollectionActionsUnion, + ProviderCollectionActionTypes, + AddProviderRequest, + AddProviderSuccess, + AddProviderFail +} from '../action/collection.action'; +import { MetadataProviderService } from '../../domain/service/provider.service'; + +/* istanbul ignore next */ +@Injectable() +export class CollectionEffects { + + @Effect() + createProvider$ = this.actions$.pipe( + ofType(ProviderCollectionActionTypes.ADD_PROVIDER_REQUEST), + map(action => action.payload), + switchMap(provider => + this.providerService + .save(provider) + .pipe( + map(p => new AddProviderSuccess(p)), + catchError((e) => of(new AddProviderFail(e))) + ) + ) + ); + + @Effect({ dispatch: false }) + createProviderSuccessRedirect$ = this.actions$.pipe( + ofType(ProviderCollectionActionTypes.ADD_PROVIDER_SUCCESS), + map(action => action.payload), + tap(provider => this.router.navigate(['metadata'])) + ); + + /* + @Effect() + addResolverSuccessReload$ = this.actions$.pipe( + ofType(EntityActionTypes.CREATE_PROVIDER_SUCCESS), + map(action => action.payload), + map(provider => new LoadProviderRequest()) + ); + */ + + constructor( + private actions$: Actions, + private router: Router, + private providerService: MetadataProviderService + ) { } +} /* istanbul ignore next */ diff --git a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts index d4bfd143d..5208783c2 100644 --- a/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts +++ b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts @@ -3,10 +3,27 @@ import { FileBackedHttpMetadataProvider } from '../../domain/model/providers/fil export const FileBackedHttpMetadataProviderWizard: Wizard = { label: 'FileBackedHttpMetadataProvider', - type: '@FileBackedHttpMetadataProvider', + type: 'FileBackedHttpMetadataResolver', translate: { parser: (changes) => changes.metadataFilters ? ({ ...changes, + httpMetadataResolverAttributes: { + httpClientRef: null, + connectionRequestTimeout: null, + connectionTimeout: null, + socketTimeout: null, + disregardTLSCertificate: false, + tlsTrustEngineRef: null, + httpClientSecurityParametersRef: null, + proxyHost: null, + proxyPort: null, + proxyUser: null, + proxyPassword: null, + httpCaching: null, + httpCacheDirectory: null, + httpMaxCacheEntries: null, + httpMaxCacheEntrySize: null + }, metadataFilters: [ ...Object.keys(changes.metadataFilters).reduce((collection, filterName) => ([ ...collection, diff --git a/ui/src/app/metadata/provider/provider.module.ts b/ui/src/app/metadata/provider/provider.module.ts index 098c8baf1..7d60e47bb 100644 --- a/ui/src/app/metadata/provider/provider.module.ts +++ b/ui/src/app/metadata/provider/provider.module.ts @@ -17,6 +17,7 @@ import { WidgetRegistry} from 'ngx-schema-form'; import { FormModule } from '../../schema-form/schema-form.module'; import { CustomWidgetRegistry } from '../../schema-form/registry'; import { SummaryPropertyComponent } from './component/summary-property.component'; +import { CollectionEffects } from './effect/collection.effect'; @NgModule({ @@ -52,7 +53,7 @@ export class ProviderModule { imports: [ ProviderModule, StoreModule.forFeature('provider', fromProvider.reducers), - EffectsModule.forFeature([EditorEffects]) + EffectsModule.forFeature([EditorEffects, CollectionEffects]) ] }) export class RootProviderModule { } diff --git a/ui/src/app/metadata/provider/reducer/collection.reducer.ts b/ui/src/app/metadata/provider/reducer/collection.reducer.ts new file mode 100644 index 000000000..9a625fa79 --- /dev/null +++ b/ui/src/app/metadata/provider/reducer/collection.reducer.ts @@ -0,0 +1,52 @@ +import { EntityState, EntityAdapter, createEntityAdapter } from '@ngrx/entity'; +import { ProviderCollectionActionTypes, ProviderCollectionActionsUnion } from '../action/collection.action'; +import { MetadataProvider } from '../../domain/model'; + +export interface CollectionState extends EntityState { + selectedProviderId: string | null; + loaded: boolean; +} + +export function sortByDate(a: MetadataProvider, b: MetadataProvider): number { + return a.createdDate.localeCompare(b.createdDate); +} + +export const adapter: EntityAdapter = createEntityAdapter({ + sortComparer: sortByDate, + selectId: (model: MetadataProvider) => model.resourceId +}); + +export const initialState: CollectionState = adapter.getInitialState({ + selectedProviderId: null, + loaded: false +}); + +export function reducer(state = initialState, action: ProviderCollectionActionsUnion): CollectionState { + switch (action.type) { + case ProviderCollectionActionTypes.LOAD_PROVIDER_SUCCESS: { + let s = adapter.addAll(action.payload, { + ...state, + selectedProviderId: state.selectedProviderId, + loaded: true + }); + return s; + } + + case ProviderCollectionActionTypes.UPDATE_PROVIDER_SUCCESS: { + return adapter.updateOne(action.payload, state); + } + + default: { + return state; + } + } +} + +export const getSelectedProviderId = (state: CollectionState) => state.selectedProviderId; +export const getIsLoaded = (state: CollectionState) => state.loaded; +export const { + selectIds: selectProviderIds, + selectEntities: selectProviderEntities, + selectAll: selectAllProviders, + selectTotal: selectProviderTotal +} = adapter.getSelectors(); diff --git a/ui/src/app/metadata/provider/reducer/entity.reducer.ts b/ui/src/app/metadata/provider/reducer/entity.reducer.ts index 4207c42a4..5aa7eceb1 100644 --- a/ui/src/app/metadata/provider/reducer/entity.reducer.ts +++ b/ui/src/app/metadata/provider/reducer/entity.reducer.ts @@ -28,8 +28,7 @@ export function reducer(state = initialState, action: EntityActionUnion): Entity } }; } - case EntityActionTypes.SELECT_PROVIDER: - case EntityActionTypes.CREATE_PROVIDER: { + case EntityActionTypes.SELECT_PROVIDER: { return { ...state, base: { @@ -46,23 +45,6 @@ export function reducer(state = initialState, action: EntityActionUnion): Entity } }; } - case EntityActionTypes.SAVE_PROVIDER_REQUEST: { - return { - ...state, - saving: true - }; - } - case EntityActionTypes.SAVE_PROVIDER_SUCCESS: { - return { - ...initialState, - }; - } - case EntityActionTypes.SAVE_PROVIDER_FAIL: { - return { - ...state, - saving: false - }; - } default: { return state; } diff --git a/ui/src/app/metadata/provider/reducer/index.ts b/ui/src/app/metadata/provider/reducer/index.ts index c736201eb..ee98fec25 100644 --- a/ui/src/app/metadata/provider/reducer/index.ts +++ b/ui/src/app/metadata/provider/reducer/index.ts @@ -2,15 +2,20 @@ import { createSelector, createFeatureSelector } from '@ngrx/store'; import * as fromRoot from '../../../app.reducer'; import * as fromEditor from './editor.reducer'; import * as fromEntity from './entity.reducer'; +import * as fromCollection from './collection.reducer'; +import * as utils from '../../domain/domain.util'; +import { MetadataProvider } from '../../domain/model'; export interface ProviderState { editor: fromEditor.EditorState; entity: fromEntity.EntityState; + collection: fromCollection.CollectionState; } export const reducers = { editor: fromEditor.reducer, - entity: fromEntity.reducer + entity: fromEntity.reducer, + collection: fromCollection.reducer }; export interface State extends fromRoot.State { @@ -21,9 +26,11 @@ export const getProviderState = createFeatureSelector('provider') export const getEditorStateFn = (state: ProviderState) => state.editor; export const getEntityStateFn = (state: ProviderState) => state.entity; +export const getCollectionStateFn = (state: ProviderState) => state.collection; export const getEditorState = createSelector(getProviderState, getEditorStateFn); export const getEntityState = createSelector(getProviderState, getEntityStateFn); +export const getCollectionState = createSelector(getProviderState, getCollectionStateFn); /* Editor State @@ -43,4 +50,16 @@ Entity State export const getEntityIsSaved = createSelector(getEntityState, fromEntity.isEntitySaved); export const getEntityChanges = createSelector(getEntityState, fromEntity.getEntityChanges); export const getEntityIsSaving = createSelector(getEntityState, fromEntity.isEditorSaving); -export const getUpdatedEntity = createSelector(getEntityState, fromEntity.getUpdatedEntity); \ No newline at end of file +export const getUpdatedEntity = createSelector(getEntityState, fromEntity.getUpdatedEntity); + +/* + * Select pieces of Provider Collection +*/ +export const getAllProviders = createSelector(getCollectionState, fromCollection.selectAllProviders); +export const getProviderEntities = createSelector(getCollectionState, fromCollection.selectProviderEntities); +export const getSelectedProviderId = createSelector(getCollectionState, fromCollection.getSelectedProviderId); +export const getSelectedProvider = createSelector(getProviderEntities, getSelectedProviderId, utils.getInCollectionFn); +export const getProviderIds = createSelector(getCollectionState, fromCollection.selectProviderIds); +export const getProviderCollectionIsLoaded = createSelector(getCollectionState, fromCollection.getIsLoaded); + +export const getProviderNames = createSelector(getAllProviders, (providers: MetadataProvider[]) => providers.map(p => p.name)); diff --git a/ui/src/app/wizard/component/wizard.component.html b/ui/src/app/wizard/component/wizard.component.html index a4ae2b75c..3d62f7d7a 100644 --- a/ui/src/app/wizard/component/wizard.component.html +++ b/ui/src/app/wizard/component/wizard.component.html @@ -36,7 +36,7 @@