From 4949ebae16aacf6e96c80e2f31137f9f68dc92db Mon Sep 17 00:00:00 2001 From: Ryan Mathis Date: Thu, 28 Jun 2018 20:14:32 +0000 Subject: [PATCH] Merged in feature/SHIBUI-576 (pull request #97) SHIBUI-576 Implemented First page of wizard * SHIBUI-576 Initial draft of wizard module * SHIBUI-576 Initial draft of wizard module * SHIBUI-576 Initial implementation of schema-form component * SHIBUI-576 Implemented First page of wizard * SHIBUI-576 Approved-by: Shibui Jenkins Approved-by: Ryan Mathis --- ui/package-lock.json | 44 ++++- ui/package.json | 10 +- ui/src/app/app.component.html | 2 +- ui/src/app/app.module.ts | 4 + ...file-backed-http-metadata-provider.spec.ts | 2 +- .../file-backed-http-metadata-provider.ts | 77 +-------- .../domain/model/metadata-provider.ts | 13 +- ui/src/app/metadata/metadata.routing.ts | 89 +--------- .../metadata/provider/action/editor.action.ts | 47 +++++ .../metadata/provider/action/entity.action.ts | 55 ++++++ .../container/new-provider.component.html | 28 +-- .../container/new-provider.component.ts | 24 ++- .../container/provider-wizard.component.html | 18 ++ ...nt.scss => provider-wizard.component.scss} | 0 .../container/provider-wizard.component.ts | 93 ++++++++++ .../provider/container/wizard.component.html | 3 - .../provider/container/wizard.component.ts | 13 -- .../metadata/provider/effect/editor.effect.ts | 35 ++++ .../model/file-backed-http.provider.form.ts | 31 ++++ ui/src/app/metadata/provider/model/index.ts | 8 + .../metadata/provider/model/provider.form.ts | 17 ++ .../app/metadata/provider/provider.module.ts | 32 +++- .../app/metadata/provider/provider.routing.ts | 23 +++ .../provider/reducer/editor.reducer.ts | 69 ++++++++ .../provider/reducer/entity.reducer.ts | 62 +++++++ ui/src/app/metadata/provider/reducer/index.ts | 46 +++++ .../app/metadata/resolver/resolver.routing.ts | 76 +++++++++ .../notification-item.component.html | 2 +- .../component/notification-item.component.ts | 10 -- ui/src/app/notification/model/notification.ts | 8 +- ui/src/app/schema-form/registry.ts | 14 ++ ui/src/app/schema-form/schema-form.module.ts | 44 +++++ .../app/schema-form/service/schema.service.ts | 15 ++ .../boolean-radio.component.html | 17 ++ .../boolean-radio.component.scss | 5 + .../boolean-radio.component.spec.ts | 0 .../boolean-radio/boolean-radio.component.ts | 11 ++ .../widget/fieldset/fieldset.component.html | 9 + .../widget/fieldset/fieldset.component.scss | 7 + .../widget/fieldset/fieldset.component.ts | 10 ++ .../widget/text/string.component.html | 17 ++ .../widget/text/string.component.ts | 17 ++ ui/src/app/wizard/action/wizard.action.ts | 56 ++++++ .../wizard/component/wizard.component.html | 43 +++++ .../wizard/component/wizard.component.scss | 13 ++ .../wizard/component/wizard.component.spec.ts | 0 .../app/wizard/component/wizard.component.ts | 48 ++++++ ui/src/app/wizard/guard/step-exists.guard.ts | 29 ++++ ui/src/app/wizard/model/index.ts | 1 + ui/src/app/wizard/model/wizard.ts | 21 +++ ui/src/app/wizard/reducer/index.ts | 60 +++++++ ui/src/app/wizard/reducer/wizard.reducer.ts | 58 +++++++ ui/src/app/wizard/wizard.module.ts | 40 +++++ .../filebacked-http-common.schema.json | 161 ++++++++++++++++++ .../filebacked-http-filters.schema.json | 92 ++++++++++ .../filebacked-http-reloading.schema.json | 53 ++++++ .../provider/metadata-provider.schema.json | 44 +++++ ui/src/theme/forms.scss | 20 +++ ui/src/typings.d.ts | 5 + 59 files changed, 1607 insertions(+), 244 deletions(-) create mode 100644 ui/src/app/metadata/provider/action/editor.action.ts create mode 100644 ui/src/app/metadata/provider/action/entity.action.ts create mode 100644 ui/src/app/metadata/provider/container/provider-wizard.component.html rename ui/src/app/metadata/provider/container/{wizard.component.scss => provider-wizard.component.scss} (100%) create mode 100644 ui/src/app/metadata/provider/container/provider-wizard.component.ts delete mode 100644 ui/src/app/metadata/provider/container/wizard.component.html delete mode 100644 ui/src/app/metadata/provider/container/wizard.component.ts create mode 100644 ui/src/app/metadata/provider/effect/editor.effect.ts create mode 100644 ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts create mode 100644 ui/src/app/metadata/provider/model/index.ts create mode 100644 ui/src/app/metadata/provider/model/provider.form.ts create mode 100644 ui/src/app/metadata/provider/provider.routing.ts create mode 100644 ui/src/app/metadata/provider/reducer/editor.reducer.ts create mode 100644 ui/src/app/metadata/provider/reducer/entity.reducer.ts create mode 100644 ui/src/app/metadata/provider/reducer/index.ts create mode 100644 ui/src/app/metadata/resolver/resolver.routing.ts create mode 100644 ui/src/app/schema-form/registry.ts create mode 100644 ui/src/app/schema-form/schema-form.module.ts create mode 100644 ui/src/app/schema-form/service/schema.service.ts create mode 100644 ui/src/app/schema-form/widget/boolean-radio/boolean-radio.component.html create mode 100644 ui/src/app/schema-form/widget/boolean-radio/boolean-radio.component.scss create mode 100644 ui/src/app/schema-form/widget/boolean-radio/boolean-radio.component.spec.ts create mode 100644 ui/src/app/schema-form/widget/boolean-radio/boolean-radio.component.ts create mode 100644 ui/src/app/schema-form/widget/fieldset/fieldset.component.html create mode 100644 ui/src/app/schema-form/widget/fieldset/fieldset.component.scss create mode 100644 ui/src/app/schema-form/widget/fieldset/fieldset.component.ts create mode 100644 ui/src/app/schema-form/widget/text/string.component.html create mode 100644 ui/src/app/schema-form/widget/text/string.component.ts create mode 100644 ui/src/app/wizard/action/wizard.action.ts create mode 100644 ui/src/app/wizard/component/wizard.component.html create mode 100644 ui/src/app/wizard/component/wizard.component.scss create mode 100644 ui/src/app/wizard/component/wizard.component.spec.ts create mode 100644 ui/src/app/wizard/component/wizard.component.ts create mode 100644 ui/src/app/wizard/guard/step-exists.guard.ts create mode 100644 ui/src/app/wizard/model/index.ts create mode 100644 ui/src/app/wizard/model/wizard.ts create mode 100644 ui/src/app/wizard/reducer/index.ts create mode 100644 ui/src/app/wizard/reducer/wizard.reducer.ts create mode 100644 ui/src/app/wizard/wizard.module.ts create mode 100644 ui/src/assets/schema/provider/filebacked-http-common.schema.json create mode 100644 ui/src/assets/schema/provider/filebacked-http-filters.schema.json create mode 100644 ui/src/assets/schema/provider/filebacked-http-reloading.schema.json create mode 100644 ui/src/assets/schema/provider/metadata-provider.schema.json diff --git a/ui/package-lock.json b/ui/package-lock.json index 17cda4f97..0fd8aeb12 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -2997,8 +2997,7 @@ "commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" }, "commondir": { "version": "1.0.1", @@ -7905,6 +7904,16 @@ "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" + }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" + }, "lodash.mergewith": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", @@ -8420,6 +8429,21 @@ "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", "dev": true }, + "ngx-schema-form": { + "version": "2.0.0-beta.3", + "resolved": "https://registry.npmjs.org/ngx-schema-form/-/ngx-schema-form-2.0.0-beta.3.tgz", + "integrity": "sha512-t545QEm16G4kdHFq4VOfmn0ICVwE6v+WAeQ5Bw1ur21U6U6n5q1srImQtefmGdqx69kl1sNwLJLF7Zm8UhJZCg==", + "requires": { + "tslib": "1.9.3" + }, + "dependencies": { + "tslib": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", + "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==" + } + } + }, "no-case": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", @@ -11941,6 +11965,11 @@ "builtins": "1.0.3" } }, + "validator": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.4.0.tgz", + "integrity": "sha512-Q/wBy3LB1uOyssgNlXSRmaf22NxjvDNZM2MtIQ4jaEOAB61xsh1TQxsq1CgzUMBV1lDrVMogIh8GjG1DYW0zLg==" + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -13589,6 +13618,17 @@ "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", "dev": true }, + "z-schema": { + "version": "3.22.0", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.22.0.tgz", + "integrity": "sha512-Oq82unxX2PTcJ031gFGcksDHE5PNBs5CbcQ1tbre0Sl4Mu5habZTVmEAkuZS4cK//VgIdNg9UG9PMgMlN6KmiA==", + "requires": { + "commander": "2.15.1", + "lodash.get": "4.4.2", + "lodash.isequal": "4.5.0", + "validator": "10.4.0" + } + }, "zone.js": { "version": "0.8.26", "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.26.tgz", diff --git a/ui/package.json b/ui/package.json index 32284f887..c0199ee2d 100644 --- a/ui/package.json +++ b/ui/package.json @@ -31,15 +31,17 @@ "@ngrx/entity": "^5.2.0", "@ngrx/router-store": "^5.2.0", "@ngrx/store": "^5.2.0", - "rxjs": "^6.1.0", - "rxjs-compat": "^6.1.0", "bootstrap": "4.1.1", "core-js": "^2.4.1", + "deep-object-diff": "^1.1.0", "file-saver": "^1.3.3", "font-awesome": "^4.7.0", + "ngx-schema-form": "^2.0.0-beta.3", + "rxjs": "^6.1.0", + "rxjs-compat": "^6.1.0", "xml-formatter": "^1.0.1", - "zone.js": "^0.8.26", - "deep-object-diff": "^1.1.0" + "z-schema": "^3.22.0", + "zone.js": "^0.8.26" }, "devDependencies": { "@angular-devkit/build-angular": "~0.5.0", diff --git a/ui/src/app/app.component.html b/ui/src/app/app.component.html index 364388566..8f8d3b34f 100644 --- a/ui/src/app/app.component.html +++ b/ui/src/app/app.component.html @@ -33,7 +33,7 @@ Metadata Source diff --git a/ui/src/app/app.module.ts b/ui/src/app/app.module.ts index 85dd2dcf9..e51a919f2 100644 --- a/ui/src/app/app.module.ts +++ b/ui/src/app/app.module.ts @@ -20,6 +20,8 @@ import { ErrorInterceptor } from './core/service/error.interceptor'; import { NavigatorService } from './core/service/navigator.service'; import { ContentionModule } from './contention/contention.module'; import { SharedModule } from './shared/shared.module'; +import { WizardModule } from './wizard/wizard.module'; +import { FormModule } from './schema-form/schema-form.module'; @NgModule({ declarations: [ @@ -37,6 +39,8 @@ import { SharedModule } from './shared/shared.module'; NgbModalModule.forRoot(), NgbPopoverModule.forRoot(), NgbPaginationModule.forRoot(), + WizardModule.forRoot(), + FormModule.forRoot(), NotificationModule, HttpClientModule, ContentionModule, 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 index 37d74a6a8..70b0245a4 100644 --- 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 @@ -1,6 +1,6 @@ import { FileBackedHttpMetadataProvider } from './file-backed-http-metadata-provider'; -describe('Resolver construct', () => { +describe('FileBackedHttmMetadataProvider construct', () => { const config = { id: 'foo', 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 index 41898afa5..2f64a98ba 100644 --- 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 @@ -1,86 +1,21 @@ import { - MetadataProvider, - MetadataEntity, - Organization, - Contact, - MDUI, - LogoutEndpoint, - SecurityInfo, - Certificate, - SsoService, - IdpSsoDescriptor, - RelyingPartyOverrides + MetadataProvider } from '../../model'; -import { MetadataTypes } from '../../domain.type'; -export class FileBackedHttpMetadataProvider implements MetadataProvider, MetadataEntity { - id = ''; +export class FileBackedHttpMetadataProvider implements MetadataProvider { + id: string; + name: string; + '@type': string; + createdDate?: string; modifiedDate?: string; version: string; - entityId = ''; - serviceProviderName = ''; - organization = {} as Organization; - contacts = [] as Contact[]; - mdui = {} as MDUI; - - securityInfo = { - x509CertificateAvailable: false, - authenticationRequestsSigned: false, - wantAssertionsSigned: false, - x509Certificates: [] as Certificate[] - } as SecurityInfo; - - assertionConsumerServices = [] as SsoService[]; - serviceProviderSsoDescriptor = { - nameIdFormats: [] - } as IdpSsoDescriptor; - - logoutEndpoints = [] as LogoutEndpoint[]; - - serviceEnabled = false; - - relyingPartyOverrides = { - nameIdFormats: [] as string[], - authenticationMethods: [] as string[] - } as RelyingPartyOverrides; - - attributeRelease = [] as string[]; - constructor(descriptor?: Partial) { Object.assign(this, descriptor); } - getId(): string { - return this.id; - } - - getDisplayId(): string { - return this.id; - } - - isDraft(): boolean { - return false; - } - getCreationDate(): Date { return new Date(this.createdDate); } - - get name(): string { - return this.serviceProviderName; - } - - get enabled(): boolean { - return this.serviceEnabled; - } - - get kind(): string { - return MetadataTypes.PROVIDER; - } - - serialize(): any { - return this; - } } diff --git a/ui/src/app/metadata/domain/model/metadata-provider.ts b/ui/src/app/metadata/domain/model/metadata-provider.ts index 22da67257..3239bd4b0 100644 --- a/ui/src/app/metadata/domain/model/metadata-provider.ts +++ b/ui/src/app/metadata/domain/model/metadata-provider.ts @@ -11,15 +11,6 @@ import { } from '../model'; export interface MetadataProvider extends MetadataBase { - serviceProviderName: string; - organization?: Organization; - contacts?: Contact[]; - mdui?: MDUI; - securityInfo?: SecurityInfo; - assertionConsumerServices?: SsoService[]; - serviceProviderSsoDescriptor?: IdpSsoDescriptor; - logoutEndpoints?: LogoutEndpoint[]; - serviceEnabled?: boolean; - relyingPartyOverrides: RelyingPartyOverrides; - attributeRelease: string[]; + name: string; + '@type': string; } diff --git a/ui/src/app/metadata/metadata.routing.ts b/ui/src/app/metadata/metadata.routing.ts index d7e4d4b1a..31223e7c4 100644 --- a/ui/src/app/metadata/metadata.routing.ts +++ b/ui/src/app/metadata/metadata.routing.ts @@ -2,20 +2,10 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; import { ManagerComponent } from './manager/container/manager.component'; -import { BlankResolverComponent } from './resolver/container/blank-resolver.component'; -import { NewResolverComponent } from './resolver/container/new-resolver.component'; -import { UploadResolverComponent } from './resolver/container/upload-resolver.component'; -import { CopyResolverComponent } from './resolver/container/copy-resolver.component'; -import { ConfirmCopyComponent } from './resolver/container/confirm-copy.component'; -import { CopyIsSetGuard } from './resolver/guard/copy-isset.guard'; import { MetadataPageComponent } from './metadata.component'; -import { ResolverComponent } from './resolver/container/resolver.component'; -import { EditorComponent } from './resolver/container/editor.component'; -import { CanDeactivateGuard } from '../core/service/can-deactivate.guard'; -import { DraftComponent } from './resolver/container/draft.component'; -import { WizardComponent } from './resolver/container/wizard.component'; -import { NewProviderComponent } from './provider/container/new-provider.component'; -import { ProviderWizardComponent } from './provider/container/wizard.component'; + +import { ResolverRoutes } from './resolver/resolver.routing'; +import { ProviderRoutes } from './provider/provider.routing'; const routes: Routes = [ { @@ -23,79 +13,16 @@ const routes: Routes = [ component: MetadataPageComponent, children: [ { path: '', component: ManagerComponent }, - { - path: 'resolver', - children: [ - { - path: 'new', - component: NewResolverComponent, - children: [ - { path: '', redirectTo: 'blank', pathMatch: 'prefix' }, - { - path: 'blank', - component: BlankResolverComponent, - canDeactivate: [] - }, - { - path: 'upload', - component: UploadResolverComponent, - canDeactivate: [] - }, - { - path: 'copy', - component: CopyResolverComponent, - canDeactivate: [] - } - ] - }, - { - path: 'new/copy/confirm', - component: ConfirmCopyComponent, - canActivate: [CopyIsSetGuard] - }, - { - path: ':id', - component: ResolverComponent, - canActivate: [], - children: [ - { path: 'edit', redirectTo: 'edit/2' }, - { - path: 'edit/:index', - component: EditorComponent, - canDeactivate: [CanDeactivateGuard] - } - ] - }, - { - path: ':entityId', - component: DraftComponent, - canActivate: [], - children: [ - { path: 'wizard', redirectTo: 'wizard/2' }, - { - path: 'wizard/:index', - component: WizardComponent, - canDeactivate: [CanDeactivateGuard] - } - ] - } - ] - }, - { - path: 'provider', - children: [ - { - path: 'new', - component: NewProviderComponent - } - ] - } + ...ResolverRoutes, + ...ProviderRoutes ] }, ]; @NgModule({ - imports: [RouterModule.forChild(routes)], + imports: [ + RouterModule.forChild(routes), + ], exports: [RouterModule] }) export class MetadataRoutingModule { } diff --git a/ui/src/app/metadata/provider/action/editor.action.ts b/ui/src/app/metadata/provider/action/editor.action.ts new file mode 100644 index 000000000..ca195716f --- /dev/null +++ b/ui/src/app/metadata/provider/action/editor.action.ts @@ -0,0 +1,47 @@ +import { Action } from '@ngrx/store'; + +export enum EditorActionTypes { + UPDATE_STATUS = '[Provider Editor] Update Status', + LOAD_SCHEMA_REQUEST = '[Provider Editor] Load Schema Request', + LOAD_SCHEMA_SUCCESS = '[Provider Editor] Load Schema Success', + LOAD_SCHEMA_FAIL = '[Provider Editor] Load Schema Fail', + + SELECT_PROVIDER_TYPE = '[Provider Editor] Select Provider Type' +} + +export class UpdateStatus implements Action { + readonly type = EditorActionTypes.UPDATE_STATUS; + + constructor(public payload: { [key: string]: string }) { } +} + +export class LoadSchemaRequest implements Action { + readonly type = EditorActionTypes.LOAD_SCHEMA_REQUEST; + + constructor(public payload: string) { } +} + +export class LoadSchemaSuccess implements Action { + readonly type = EditorActionTypes.LOAD_SCHEMA_SUCCESS; + + constructor(public payload: any) { } +} + +export class LoadSchemaFail implements Action { + readonly type = EditorActionTypes.LOAD_SCHEMA_FAIL; + + constructor(public payload: Error) { } +} + +export class SelectProviderType implements Action { + readonly type = EditorActionTypes.SELECT_PROVIDER_TYPE; + + constructor(public payload: string) { } +} + +export type EditorActionUnion = + | UpdateStatus + | LoadSchemaRequest + | LoadSchemaSuccess + | LoadSchemaFail + | SelectProviderType; diff --git a/ui/src/app/metadata/provider/action/entity.action.ts b/ui/src/app/metadata/provider/action/entity.action.ts new file mode 100644 index 000000000..09398ce97 --- /dev/null +++ b/ui/src/app/metadata/provider/action/entity.action.ts @@ -0,0 +1,55 @@ +import { Action } from '@ngrx/store'; +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' +} + +export class SelectProvider implements Action { + readonly type = EntityActionTypes.SELECT_PROVIDER; + + 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 type EntityActionUnion = + | SelectProvider + | UpdateProvider + | SaveProviderRequest + | SaveProviderSuccess + | SaveProviderFail + | CreateProvider; diff --git a/ui/src/app/metadata/provider/container/new-provider.component.html b/ui/src/app/metadata/provider/container/new-provider.component.html index 0e15a2650..203223445 100644 --- a/ui/src/app/metadata/provider/container/new-provider.component.html +++ b/ui/src/app/metadata/provider/container/new-provider.component.html @@ -8,33 +8,7 @@
-
-
-
- -
-
-
+
\ No newline at end of file diff --git a/ui/src/app/metadata/provider/container/new-provider.component.ts b/ui/src/app/metadata/provider/container/new-provider.component.ts index e5023ac07..d7581c566 100644 --- a/ui/src/app/metadata/provider/container/new-provider.component.ts +++ b/ui/src/app/metadata/provider/container/new-provider.component.ts @@ -1,5 +1,14 @@ 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'; + +import { MetadataProviderWizard } from '../model'; @Component({ selector: 'new-provider-page', @@ -7,14 +16,13 @@ import { FormGroup, Validators, FormBuilder } from '@angular/forms'; styleUrls: ['./new-provider.component.scss'] }) export class NewProviderComponent { - form: FormGroup = this.fb.group({ - name: ['', [Validators.required]], - '@type': ['', [Validators.required]] - }); + types = MetadataProviderTypes; constructor( - private fb: FormBuilder - ) {} - - next(): void {} + private fb: FormBuilder, + private store: Store + ) { + this.store.dispatch(new SetDefinition(MetadataProviderWizard)); + this.store.dispatch(new SetIndex(MetadataProviderWizard.steps[0].id)); + } } diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.html b/ui/src/app/metadata/provider/container/provider-wizard.component.html new file mode 100644 index 000000000..83caacfd0 --- /dev/null +++ b/ui/src/app/metadata/provider/container/provider-wizard.component.html @@ -0,0 +1,18 @@ +
+ +
+
+
+ +
+ +
+
diff --git a/ui/src/app/metadata/provider/container/wizard.component.scss b/ui/src/app/metadata/provider/container/provider-wizard.component.scss similarity index 100% rename from ui/src/app/metadata/provider/container/wizard.component.scss rename to ui/src/app/metadata/provider/container/provider-wizard.component.scss diff --git a/ui/src/app/metadata/provider/container/provider-wizard.component.ts b/ui/src/app/metadata/provider/container/provider-wizard.component.ts new file mode 100644 index 000000000..0565a5336 --- /dev/null +++ b/ui/src/app/metadata/provider/container/provider-wizard.component.ts @@ -0,0 +1,93 @@ + +import { Component, OnDestroy } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { Subscription, Observable } from 'rxjs'; +import { distinctUntilChanged, map } 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 } from '../../../wizard/action/wizard.action'; +import { LoadSchemaRequest, UpdateStatus } from '../action/editor.action'; +import { startWith } from 'rxjs/operators'; +import { Wizard, WizardStep } from '../../../wizard/model'; +import { MetadataProvider } from '../../domain/model'; +import { MetadataProviderTypes } from '../model'; +import { UpdateProvider } from '../action/entity.action'; + +@Component({ + selector: 'provider-wizard-page', + templateUrl: './provider-wizard.component.html', + styleUrls: ['./provider-wizard.component.scss'] +}) + +export class ProviderWizardComponent implements OnDestroy { + actionsSubscription: Subscription; + + schema$: Observable; + definition$: Observable>; + changes$: Observable; + currentPage: string; + valid$: Observable; + + formModel: any; + + nextStep: WizardStep; + previousStep: WizardStep; + + constructor( + private store: Store, + private route: ActivatedRoute, + private router: Router + ) { + 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.store.select(fromWizard.getNext).subscribe(n => this.nextStep = n); + this.store.select(fromWizard.getPrevious).subscribe(p => this.previousStep = p); + + this.valid$ + .pipe(startWith(false)) + .subscribe((valid) => { + this.store.dispatch(new SetDisabled(!valid)); + }); + } + + ngOnDestroy() { + this.actionsSubscription.unsubscribe(); + } + + next(): void { + this.store.dispatch(new SetIndex(this.nextStep.id)); + } + + previous(): void { + this.store.dispatch(new SetIndex(this.previousStep.id)); + } + + save(): void { + console.log('Save!'); + } + + onValueChange(changes: any): void { + const type = changes.value['@type']; + if (type) { + this.store.dispatch(new UpdateDefinition(MetadataProviderTypes.find(def => def.type === type))); + } + this.store.dispatch(new UpdateProvider(changes.value)); + } + + 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/wizard.component.html b/ui/src/app/metadata/provider/container/wizard.component.html deleted file mode 100644 index a2dfdb02b..000000000 --- a/ui/src/app/metadata/provider/container/wizard.component.html +++ /dev/null @@ -1,3 +0,0 @@ -
- Hi. I'm the metadata provider wizard. -
diff --git a/ui/src/app/metadata/provider/container/wizard.component.ts b/ui/src/app/metadata/provider/container/wizard.component.ts deleted file mode 100644 index 8b5ec5510..000000000 --- a/ui/src/app/metadata/provider/container/wizard.component.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Component } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; - -@Component({ - selector: 'provider-wizard-page', - templateUrl: './wizard.component.html', - styleUrls: ['./wizard.component.scss'] -}) - -export class ProviderWizardComponent { - -} - diff --git a/ui/src/app/metadata/provider/effect/editor.effect.ts b/ui/src/app/metadata/provider/effect/editor.effect.ts new file mode 100644 index 000000000..f778a4ac6 --- /dev/null +++ b/ui/src/app/metadata/provider/effect/editor.effect.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@angular/core'; +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { SchemaService } from '../../../schema-form/service/schema.service'; + +import { + LoadSchemaRequest, + LoadSchemaSuccess, + LoadSchemaFail, + EditorActionTypes +} from '../action/editor.action'; +import { map, switchMap, catchError } from 'rxjs/operators'; +import { of } from 'rxjs'; + +@Injectable() +export class EditorEffects { + + @Effect() + $loadSchemaRequest = this.actions$.pipe( + ofType(EditorActionTypes.LOAD_SCHEMA_REQUEST), + map(action => action.payload), + switchMap((schemaPath: string) => + this.schemaService + .get(schemaPath) + .pipe( + map(schema => new LoadSchemaSuccess(schema)), + catchError(error => of(new LoadSchemaFail(error))) + ) + ) + ); + + constructor( + private schemaService: SchemaService, + 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 new file mode 100644 index 000000000..4f77f9e35 --- /dev/null +++ b/ui/src/app/metadata/provider/model/file-backed-http.provider.form.ts @@ -0,0 +1,31 @@ +import { Wizard } from '../../../wizard/model'; +import { MetadataProvider } from '../../domain/model'; + +export const FileBackedHttpMetadataProviderWizard: Wizard = { + label: 'FileBackedHttpMetadataProvider', + type: '@FileBackedHttpMetadataProvider', + 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': '' }) + }, + { + id: 'reloading', + label: 'Reloading Attributes', + index: 3, + initialValues: [], + schema: 'assets/schema/provider/filebacked-http-reloading.schema.json' + }, + { + id: 'filters', + label: 'Metadata Filter Plugins', + index: 4, + initialValues: [], + schema: 'assets/schema/provider/filebacked-http-filters.schema.json' + } + ] +}; diff --git a/ui/src/app/metadata/provider/model/index.ts b/ui/src/app/metadata/provider/model/index.ts new file mode 100644 index 000000000..508650ca7 --- /dev/null +++ b/ui/src/app/metadata/provider/model/index.ts @@ -0,0 +1,8 @@ +import { FileBackedHttpMetadataProviderWizard } from './file-backed-http.provider.form'; + +export const MetadataProviderTypes = [ + FileBackedHttpMetadataProviderWizard +]; + +export * from './file-backed-http.provider.form'; +export * from './provider.form'; diff --git a/ui/src/app/metadata/provider/model/provider.form.ts b/ui/src/app/metadata/provider/model/provider.form.ts new file mode 100644 index 000000000..133de467f --- /dev/null +++ b/ui/src/app/metadata/provider/model/provider.form.ts @@ -0,0 +1,17 @@ +import { Wizard, WizardStep } from '../../../wizard/model'; +import { MetadataProvider } from '../../domain/model'; +import { Metadata } from '../../domain/domain.type'; + +export const MetadataProviderWizard: Wizard = { + label: 'MetadataProvider', + type: '@MetadataProvider', + steps: [ + { + id: 'new', + label: 'Select Metadata Provider Type', + index: 1, + initialValues: [], + schema: 'assets/schema/provider/metadata-provider.schema.json' + } + ] as WizardStep[] +}; diff --git a/ui/src/app/metadata/provider/provider.module.ts b/ui/src/app/metadata/provider/provider.module.ts index aa31612dc..5379d9c94 100644 --- a/ui/src/app/metadata/provider/provider.module.ts +++ b/ui/src/app/metadata/provider/provider.module.ts @@ -1,8 +1,21 @@ import { NgModule, ModuleWithProviders } from '@angular/core'; -import { ProviderWizardComponent } from './container/wizard.component'; -import { NewProviderComponent } from './container/new-provider.component'; import { ReactiveFormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; +import { StoreModule } from '@ngrx/store'; + +import { ProviderWizardComponent } from './container/provider-wizard.component'; +import { NewProviderComponent } from './container/new-provider.component'; +import { WizardModule } from '../../wizard/wizard.module'; +import * as fromProvider from './reducer'; +import { EffectsModule } from '@ngrx/effects'; +import { EditorEffects } from './effect/editor.effect'; +// import { SchemaFormModule } from '../../schema-form/form.module'; + +import { SchemaFormModule, WidgetRegistry, DefaultWidgetRegistry } from 'ngx-schema-form'; +import { FormModule } from '../../schema-form/schema-form.module'; +import { CustomWidgetRegistry } from '../../schema-form/registry'; + @NgModule({ declarations: [ @@ -12,7 +25,10 @@ import { CommonModule } from '@angular/common'; entryComponents: [], imports: [ ReactiveFormsModule, - CommonModule + CommonModule, + WizardModule, + RouterModule, + FormModule ], exports: [] }) @@ -20,16 +36,18 @@ export class ProviderModule { static forRoot(): ModuleWithProviders { return { ngModule: RootProviderModule, - providers: [] + providers: [ + { provide: WidgetRegistry, useClass: CustomWidgetRegistry } + ] }; } } @NgModule({ imports: [ - ProviderModule - // StoreModule.forFeature('provider', fromResolver.reducers), - // EffectsModule.forFeature([]) + ProviderModule, + StoreModule.forFeature('provider', fromProvider.reducers), + EffectsModule.forFeature([EditorEffects]) ] }) export class RootProviderModule { } diff --git a/ui/src/app/metadata/provider/provider.routing.ts b/ui/src/app/metadata/provider/provider.routing.ts new file mode 100644 index 000000000..ad783dc6a --- /dev/null +++ b/ui/src/app/metadata/provider/provider.routing.ts @@ -0,0 +1,23 @@ +import { Routes } from '@angular/router'; + +import { NewProviderComponent } from './container/new-provider.component'; +import { ProviderWizardComponent } from './container/provider-wizard.component'; + +export const ProviderRoutes: Routes = [ + { + path: 'provider', + component: NewProviderComponent, + children: [ + { + path: 'wizard', + redirectTo: `wizard/new`, + pathMatch: 'prefix' + }, + { + path: 'wizard/new', + component: ProviderWizardComponent, + canActivate: [] + } + ] + } +]; diff --git a/ui/src/app/metadata/provider/reducer/editor.reducer.ts b/ui/src/app/metadata/provider/reducer/editor.reducer.ts new file mode 100644 index 000000000..351a17917 --- /dev/null +++ b/ui/src/app/metadata/provider/reducer/editor.reducer.ts @@ -0,0 +1,69 @@ +import { EditorActionTypes, EditorActionUnion } from '../action/editor.action'; + +export interface EditorState { + status: { [key: string]: string }; + schemaPath: string; + loading: boolean; + schema: any; + type: string; +} + +export const initialState: EditorState = { + status: {}, + schemaPath: null, + loading: false, + schema: null, + type: null +}; + +export function reducer(state = initialState, action: EditorActionUnion): EditorState { + switch (action.type) { + case EditorActionTypes.SELECT_PROVIDER_TYPE: { + return { + ...state, + type: action.payload + }; + } + case EditorActionTypes.UPDATE_STATUS: { + return { + ...state, + status: { + ...state.status, + ...action.payload + } + }; + } + case EditorActionTypes.LOAD_SCHEMA_REQUEST: { + return { + ...state, + loading: true, + schemaPath: action.payload + }; + } + case EditorActionTypes.LOAD_SCHEMA_SUCCESS: { + return { + ...state, + loading: false, + schema: action.payload + }; + } + case EditorActionTypes.LOAD_SCHEMA_FAIL: { + return { + ...state, + loading: false, + schema: initialState.schema + }; + } + default: { + return state; + } + } +} + +export const getSchema = (state: EditorState) => state.schema; + +export const isEditorValid = (state: EditorState) => + !Object.keys(state.status).some(key => state.status[key] === ('INVALID')); +export const getFormStatus = (state: EditorState) => state.status; +export const getInvalidForms = (state: EditorState) => + Object.keys(state.status).filter(key => state.status[key] === 'INVALID'); diff --git a/ui/src/app/metadata/provider/reducer/entity.reducer.ts b/ui/src/app/metadata/provider/reducer/entity.reducer.ts new file mode 100644 index 000000000..936370316 --- /dev/null +++ b/ui/src/app/metadata/provider/reducer/entity.reducer.ts @@ -0,0 +1,62 @@ +import { MetadataProvider } from '../../domain/model'; +import { EntityActionTypes, EntityActionUnion } from '../action/entity.action'; + +export interface EntityState { + saving: boolean; + base: MetadataProvider; + changes: MetadataProvider; +} + +export const initialState: EntityState = { + saving: false, + base: null, + changes: null +}; + +export function reducer(state = initialState, action: EntityActionUnion): EntityState { + switch (action.type) { + case EntityActionTypes.SELECT_PROVIDER: + case EntityActionTypes.CREATE_PROVIDER: { + return { + ...state, + base: { + ...action.payload + } + }; + } + case EntityActionTypes.UPDATE_PROVIDER: { + return { + ...state, + changes: { + ...state.changes, + ...action.payload + } + }; + } + 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; + } + } +} + +export const isEntitySaved = (state: EntityState) => !Object.keys(state.changes).length && !state.saving; +export const getEntityChanges = (state: EntityState) => state.changes; +export const isEditorSaving = (state: EntityState) => state.saving; +export const getUpdatedEntity = (state: EntityState) => ({ ...state.base, ...state.changes }); diff --git a/ui/src/app/metadata/provider/reducer/index.ts b/ui/src/app/metadata/provider/reducer/index.ts new file mode 100644 index 000000000..c736201eb --- /dev/null +++ b/ui/src/app/metadata/provider/reducer/index.ts @@ -0,0 +1,46 @@ +import { createSelector, createFeatureSelector } from '@ngrx/store'; +import * as fromRoot from '../../../app.reducer'; +import * as fromEditor from './editor.reducer'; +import * as fromEntity from './entity.reducer'; + +export interface ProviderState { + editor: fromEditor.EditorState; + entity: fromEntity.EntityState; +} + +export const reducers = { + editor: fromEditor.reducer, + entity: fromEntity.reducer +}; + +export interface State extends fromRoot.State { + provider: ProviderState; +} + +export const getProviderState = createFeatureSelector('provider'); + +export const getEditorStateFn = (state: ProviderState) => state.editor; +export const getEntityStateFn = (state: ProviderState) => state.entity; + +export const getEditorState = createSelector(getProviderState, getEditorStateFn); +export const getEntityState = createSelector(getProviderState, getEntityStateFn); + +/* +Editor State +*/ + +export const getSchema = createSelector(getEditorState, fromEditor.getSchema); + +export const getEditorIsValid = createSelector(getEditorState, fromEditor.isEditorValid); + +export const getFormStatus = createSelector(getEditorState, fromEditor.getFormStatus); +export const getInvalidEditorForms = createSelector(getEditorState, fromEditor.getInvalidForms); + +/* +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 diff --git a/ui/src/app/metadata/resolver/resolver.routing.ts b/ui/src/app/metadata/resolver/resolver.routing.ts new file mode 100644 index 000000000..9aa4ab730 --- /dev/null +++ b/ui/src/app/metadata/resolver/resolver.routing.ts @@ -0,0 +1,76 @@ +import { Routes } from '@angular/router'; + +import { ResolverComponent } from './container/resolver.component'; +import { EditorComponent } from './container/editor.component'; +import { DraftComponent } from './container/draft.component'; +import { WizardComponent } from './container/wizard.component'; + +import { BlankResolverComponent } from './container/blank-resolver.component'; +import { NewResolverComponent } from './container/new-resolver.component'; +import { UploadResolverComponent } from './container/upload-resolver.component'; +import { CopyResolverComponent } from './container/copy-resolver.component'; +import { ConfirmCopyComponent } from './container/confirm-copy.component'; +import { CopyIsSetGuard } from './guard/copy-isset.guard'; + +import { CanDeactivateGuard } from '../../core/service/can-deactivate.guard'; + +export const ResolverRoutes: Routes = [ + { + path: 'resolver', + children: [ + { + path: 'new', + component: NewResolverComponent, + children: [ + { path: '', redirectTo: 'blank', pathMatch: 'prefix' }, + { + path: 'blank', + component: BlankResolverComponent, + canDeactivate: [] + }, + { + path: 'upload', + component: UploadResolverComponent, + canDeactivate: [] + }, + { + path: 'copy', + component: CopyResolverComponent, + canDeactivate: [] + } + ] + }, + { + path: 'new/copy/confirm', + component: ConfirmCopyComponent, + canActivate: [CopyIsSetGuard] + }, + { + path: ':id', + component: ResolverComponent, + canActivate: [], + children: [ + { path: 'edit', redirectTo: 'edit/2' }, + { + path: 'edit/:index', + component: EditorComponent, + canDeactivate: [CanDeactivateGuard] + } + ] + }, + { + path: ':entityId', + component: DraftComponent, + canActivate: [], + children: [ + { path: 'wizard', redirectTo: 'wizard/2' }, + { + path: 'wizard/:index', + component: WizardComponent, + canDeactivate: [CanDeactivateGuard] + } + ] + } + ] + } +]; diff --git a/ui/src/app/notification/component/notification-item.component.html b/ui/src/app/notification/component/notification-item.component.html index e14cdf882..235941254 100644 --- a/ui/src/app/notification/component/notification-item.component.html +++ b/ui/src/app/notification/component/notification-item.component.html @@ -1,5 +1,5 @@