diff --git a/ui/src/app/metadata/configuration/action/configuration.action.ts b/ui/src/app/metadata/configuration/action/configuration.action.ts index 6c71b61a6..1aa442601 100644 --- a/ui/src/app/metadata/configuration/action/configuration.action.ts +++ b/ui/src/app/metadata/configuration/action/configuration.action.ts @@ -12,9 +12,17 @@ export enum ConfigurationActionTypes { LOAD_SCHEMA_SUCCESS = '[Metadata Configuration] Load Schema Success', LOAD_SCHEMA_ERROR = '[Metadata Configuration] Load Schema Error', + LOAD_XML_REQUEST = '[Metadata Configuration] Load XML Request', + LOAD_XML_SUCCESS = '[Metadata Configuration] Load XML Success', + LOAD_XML_ERROR = '[Metadata Configuration] Load XML Error', + SET_METADATA = '[Metadata Configuration] Set Metadata Model', SET_DEFINITION = '[Metadata Configuration] Set Metadata Definition', SET_SCHEMA = '[Metadata Configuration] Set Metadata Schema', + SET_XML = '[Metadata Configuration] Set Metadata Xml', + + DOWNLOAD_XML = '[Metadata Configuration] Download Metadata Xml', + CLEAR = '[Metadata Configuration] Clear' } @@ -54,6 +62,24 @@ export class LoadSchemaError implements Action { constructor(public payload: any) { } } +export class LoadXmlRequest implements Action { + readonly type = ConfigurationActionTypes.LOAD_XML_REQUEST; + + constructor(public payload: string) { } +} + +export class LoadXmlSuccess implements Action { + readonly type = ConfigurationActionTypes.LOAD_XML_SUCCESS; + + constructor(public payload: string) { } +} + +export class LoadXmlError implements Action { + readonly type = ConfigurationActionTypes.LOAD_XML_ERROR; + + constructor(public payload: any) { } +} + export class SetMetadata implements Action { readonly type = ConfigurationActionTypes.SET_METADATA; @@ -72,6 +98,16 @@ export class SetSchema implements Action { constructor(public payload: Schema) { } } +export class SetXml implements Action { + readonly type = ConfigurationActionTypes.SET_XML; + + constructor(public payload: string) { } +} + +export class DownloadXml implements Action { + readonly type = ConfigurationActionTypes.DOWNLOAD_XML; +} + export class ClearConfiguration implements Action { readonly type = ConfigurationActionTypes.CLEAR; } @@ -83,7 +119,12 @@ export type ConfigurationActionsUnion = | LoadSchemaRequest | LoadSchemaSuccess | LoadSchemaError + | LoadXmlRequest + | LoadXmlSuccess + | LoadXmlError | SetMetadata | SetDefinition | SetSchema + | SetXml + | DownloadXml | ClearConfiguration; diff --git a/ui/src/app/metadata/configuration/component/metadata-configuration.component.html b/ui/src/app/metadata/configuration/component/metadata-configuration.component.html index 136e625d7..258b9aa77 100644 --- a/ui/src/app/metadata/configuration/component/metadata-configuration.component.html +++ b/ui/src/app/metadata/configuration/component/metadata-configuration.component.html @@ -1,4 +1,4 @@ -
+
diff --git a/ui/src/app/metadata/configuration/configuration.module.ts b/ui/src/app/metadata/configuration/configuration.module.ts index eb69b14af..2cf56580d 100644 --- a/ui/src/app/metadata/configuration/configuration.module.ts +++ b/ui/src/app/metadata/configuration/configuration.module.ts @@ -17,10 +17,14 @@ import { PrimitivePropertyComponent } from './component/primitive-property.compo import { ObjectPropertyComponent } from './component/object-property.component'; import { ArrayPropertyComponent } from './component/array-property.component'; import { RouterModule } from '@angular/router'; +import { MetadataOptionsComponent } from './container/metadata-options.component'; +import { MetadataXmlComponent } from './container/metadata-xml.component'; @NgModule({ declarations: [ MetadataConfigurationComponent, + MetadataOptionsComponent, + MetadataXmlComponent, ConfigurationPropertyComponent, PrimitivePropertyComponent, ObjectPropertyComponent, diff --git a/ui/src/app/metadata/configuration/configuration.routing.ts b/ui/src/app/metadata/configuration/configuration.routing.ts index c1e6e138d..8e7327792 100644 --- a/ui/src/app/metadata/configuration/configuration.routing.ts +++ b/ui/src/app/metadata/configuration/configuration.routing.ts @@ -1,9 +1,25 @@ import { Routes } from '@angular/router'; import { ConfigurationComponent } from './container/configuration.component'; +import { MetadataOptionsComponent } from './container/metadata-options.component'; +import { MetadataXmlComponent } from './container/metadata-xml.component'; export const ConfigurationRoutes: Routes = [ { path: ':type/:id/configuration', - component: ConfigurationComponent + component: ConfigurationComponent, + children: [ + { + path: '', + redirectTo: 'options' + }, + { + path: 'options', + component: MetadataOptionsComponent + }, + { + path: 'xml', + component: MetadataXmlComponent + } + ] } ]; diff --git a/ui/src/app/metadata/configuration/container/configuration.component.html b/ui/src/app/metadata/configuration/container/configuration.component.html index 1e9f67486..36626f240 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.html +++ b/ui/src/app/metadata/configuration/container/configuration.component.html @@ -11,7 +11,7 @@
- +
\ No newline at end of file diff --git a/ui/src/app/metadata/configuration/container/configuration.component.spec.ts b/ui/src/app/metadata/configuration/container/configuration.component.spec.ts index a61db7b7c..c399fbacf 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.spec.ts +++ b/ui/src/app/metadata/configuration/container/configuration.component.spec.ts @@ -9,14 +9,6 @@ import { ConfigurationComponent } from './configuration.component'; import * as fromConfiguration from '../reducer'; import { MockI18nModule } from '../../../../testing/i18n.stub'; -@Component({ - selector: 'metadata-configuration', - template: `` -}) -class MetadataConfigurationComponent { - @Input() configuration: MetadataConfiguration; -} - @Component({ template: ` @@ -47,7 +39,6 @@ describe('Metadata Configuration Page Component', () => { ], declarations: [ ConfigurationComponent, - MetadataConfigurationComponent, TestHostComponent ], }).compileComponents(); diff --git a/ui/src/app/metadata/configuration/container/configuration.component.ts b/ui/src/app/metadata/configuration/container/configuration.component.ts index e22f23861..d18e5ab34 100644 --- a/ui/src/app/metadata/configuration/container/configuration.component.ts +++ b/ui/src/app/metadata/configuration/container/configuration.component.ts @@ -1,7 +1,7 @@ import { Store } from '@ngrx/store'; import { Component, ChangeDetectionStrategy, OnDestroy } from '@angular/core'; import { Observable, Subject } from 'rxjs'; -import { ActivatedRoute } from '@angular/router'; +import { ActivatedRoute, Params } from '@angular/router'; import * as fromConfiguration from '../reducer'; import { MetadataConfiguration } from '../model/metadata-configuration'; @@ -17,14 +17,10 @@ import { LoadMetadataRequest, ClearConfiguration } from '../action/configuration export class ConfigurationComponent implements OnDestroy { private ngUnsubscribe: Subject = new Subject(); - configuration$: Observable; - constructor( private store: Store, private routerState: ActivatedRoute ) { - this.configuration$ = this.store.select(fromConfiguration.getConfigurationSections); - this.routerState.params.pipe( takeUntil(this.ngUnsubscribe), map(params => new LoadMetadataRequest({id: params.id, type: params.type})) diff --git a/ui/src/app/metadata/configuration/container/metadata-options.component.html b/ui/src/app/metadata/configuration/container/metadata-options.component.html new file mode 100644 index 000000000..5e6b85384 --- /dev/null +++ b/ui/src/app/metadata/configuration/container/metadata-options.component.html @@ -0,0 +1,3 @@ +
+ +
\ No newline at end of file diff --git a/ui/src/app/metadata/configuration/container/metadata-options.component.spec.ts b/ui/src/app/metadata/configuration/container/metadata-options.component.spec.ts new file mode 100644 index 000000000..2e35d8c75 --- /dev/null +++ b/ui/src/app/metadata/configuration/container/metadata-options.component.spec.ts @@ -0,0 +1,70 @@ +import { Component, ViewChild, Input } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule, combineReducers, Store } from '@ngrx/store'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; + +import { MetadataConfiguration } from '../model/metadata-configuration'; +import * as fromConfiguration from '../reducer'; +import { MockI18nModule } from '../../../../testing/i18n.stub'; +import { MetadataOptionsComponent } from './metadata-options.component'; + +@Component({ + selector: 'metadata-configuration', + template: `` +}) +class MetadataConfigurationComponent { + @Input() configuration: MetadataConfiguration; +} + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(MetadataOptionsComponent) + public componentUnderTest: MetadataOptionsComponent; + + configuration: MetadataConfiguration = { sections: [] }; +} + +describe('Metadata Options Page Component', () => { + + let fixture: ComponentFixture; + let instance: TestHostComponent; + let app: MetadataOptionsComponent; + let store: Store; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + StoreModule.forRoot({ + 'metadata-configuration': combineReducers(fromConfiguration.reducers), + }), + MockI18nModule, + RouterTestingModule + ], + declarations: [ + MetadataOptionsComponent, + MetadataConfigurationComponent, + TestHostComponent + ], + }).compileComponents(); + + store = TestBed.get(Store); + spyOn(store, 'dispatch'); + spyOn(store, 'select'); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should load metadata objects', async(() => { + expect(app).toBeTruthy(); + expect(store.select).toHaveBeenCalled(); + })); +}); diff --git a/ui/src/app/metadata/configuration/container/metadata-options.component.ts b/ui/src/app/metadata/configuration/container/metadata-options.component.ts new file mode 100644 index 000000000..bc2d0bb0d --- /dev/null +++ b/ui/src/app/metadata/configuration/container/metadata-options.component.ts @@ -0,0 +1,23 @@ +import { Store } from '@ngrx/store'; +import { Component, ChangeDetectionStrategy } from '@angular/core'; +import { Observable } from 'rxjs'; + +import * as fromConfiguration from '../reducer'; +import { MetadataConfiguration } from '../model/metadata-configuration'; + +@Component({ + selector: 'metadata-options-page', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './metadata-options.component.html', + styleUrls: [] +}) +export class MetadataOptionsComponent { + + configuration$: Observable; + + constructor( + private store: Store + ) { + this.configuration$ = this.store.select(fromConfiguration.getConfigurationSections); + } +} diff --git a/ui/src/app/metadata/configuration/container/metadata-xml.component.html b/ui/src/app/metadata/configuration/container/metadata-xml.component.html new file mode 100644 index 000000000..c76b4627b --- /dev/null +++ b/ui/src/app/metadata/configuration/container/metadata-xml.component.html @@ -0,0 +1,7 @@ +
+
{{ xml$ | async }}
+ +
\ No newline at end of file diff --git a/ui/src/app/metadata/configuration/container/metadata-xml.component.spec.ts b/ui/src/app/metadata/configuration/container/metadata-xml.component.spec.ts new file mode 100644 index 000000000..81fbd836f --- /dev/null +++ b/ui/src/app/metadata/configuration/container/metadata-xml.component.spec.ts @@ -0,0 +1,61 @@ +import { Component, ViewChild, Input } from '@angular/core'; +import { TestBed, async, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { StoreModule, combineReducers, Store } from '@ngrx/store'; +import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; + +import { MetadataConfiguration } from '../model/metadata-configuration'; +import * as fromConfiguration from '../reducer'; +import { MockI18nModule } from '../../../../testing/i18n.stub'; +import { MetadataXmlComponent } from './metadata-xml.component'; + +@Component({ + template: ` + + ` +}) +class TestHostComponent { + @ViewChild(MetadataXmlComponent) + public componentUnderTest: MetadataXmlComponent; + + configuration: MetadataConfiguration = { sections: [] }; +} + +describe('Metadata Xml Page Component', () => { + + let fixture: ComponentFixture; + let instance: TestHostComponent; + let app: MetadataXmlComponent; + let store: Store; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + NgbDropdownModule, + StoreModule.forRoot({ + 'metadata-configuration': combineReducers(fromConfiguration.reducers), + }), + MockI18nModule, + RouterTestingModule + ], + declarations: [ + MetadataXmlComponent, + TestHostComponent + ], + }).compileComponents(); + + store = TestBed.get(Store); + spyOn(store, 'dispatch'); + spyOn(store, 'select'); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + app = instance.componentUnderTest; + fixture.detectChanges(); + })); + + it('should load metadata objects', async(() => { + expect(app).toBeTruthy(); + expect(store.select).toHaveBeenCalledTimes(2); + })); +}); diff --git a/ui/src/app/metadata/configuration/container/metadata-xml.component.ts b/ui/src/app/metadata/configuration/container/metadata-xml.component.ts new file mode 100644 index 000000000..26b62323e --- /dev/null +++ b/ui/src/app/metadata/configuration/container/metadata-xml.component.ts @@ -0,0 +1,33 @@ +import { Store } from '@ngrx/store'; +import { Component } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; + +import * as fromConfiguration from '../reducer'; +import { Metadata } from '../../domain/domain.type'; +import { DownloadXml } from '../action/configuration.action'; + +@Component({ + selector: 'metadata-xml-page', + templateUrl: './metadata-xml.component.html', + styleUrls: [] +}) +export class MetadataXmlComponent { + + private ngUnsubscribe: Subject = new Subject(); + + entity: Metadata; + entity$: Observable; + xml: string; + xml$: Observable; + + constructor( + private store: Store + ) { + this.xml$ = this.store.select(fromConfiguration.getConfigurationXml); + this.entity$ = this.store.select(fromConfiguration.getConfigurationModel); + } + + preview(): void { + this.store.dispatch(new DownloadXml()); + } +} diff --git a/ui/src/app/metadata/configuration/effect/configuration.effect.ts b/ui/src/app/metadata/configuration/effect/configuration.effect.ts index 781d9a7b9..ff76c8125 100644 --- a/ui/src/app/metadata/configuration/effect/configuration.effect.ts +++ b/ui/src/app/metadata/configuration/effect/configuration.effect.ts @@ -1,7 +1,9 @@ import { Injectable } from '@angular/core'; import { Effect, Actions, ofType } from '@ngrx/effects'; -import { switchMap, catchError, map } from 'rxjs/operators'; -import { of } from 'rxjs'; +import { switchMap, catchError, map, tap, withLatestFrom } from 'rxjs/operators'; +import { of, Observable } from 'rxjs'; +import * as FileSaver from 'file-saver'; +import { Store } from '@ngrx/store'; import { MetadataConfigurationService } from '../service/configuration.service'; import { @@ -14,8 +16,17 @@ import { LoadSchemaRequest, LoadSchemaSuccess, SetSchema, - LoadSchemaError + LoadSchemaError, + LoadXmlSuccess, + LoadXmlError, + SetXml, + DownloadXml } from '../action/configuration.action'; +import { ResolverService } from '../../domain/service/resolver.service'; +import { EntityIdService } from '../../domain/service/entity-id.service'; +import { State } from '../reducer/configuration.reducer'; +import { getConfigurationModel, getConfigurationXml } from '../reducer'; +import { MetadataResolver } from '../../domain/model'; @Injectable() export class MetadataConfigurationEffects { @@ -33,6 +44,33 @@ export class MetadataConfigurationEffects { ) ); + @Effect() + loadMetadataXml$ = this.actions$.pipe( + ofType(ConfigurationActionTypes.LOAD_METADATA_REQUEST), + switchMap(action => { + let loader: Observable; + switch (action.payload.type) { + case 'filter': + loader = this.entityService.preview(action.payload.id); + break; + default: + loader = this.providerService.preview(action.payload.id); + break; + } + + return loader.pipe( + map(xml => new LoadXmlSuccess(xml)), + catchError(error => of(new LoadXmlError(error))) + ); + }) + ); + + @Effect() + setXmlOnLoad$ = this.actions$.pipe( + ofType(ConfigurationActionTypes.LOAD_XML_SUCCESS), + map(action => new SetXml(action.payload)) + ); + @Effect() setMetadataOnLoad$ = this.actions$.pipe( ofType(ConfigurationActionTypes.LOAD_METADATA_SUCCESS), @@ -70,8 +108,25 @@ export class MetadataConfigurationEffects { map(action => new SetSchema(action.payload)) ); + @Effect({dispatch: false}) + downloadXml$ = this.actions$.pipe( + ofType(ConfigurationActionTypes.DOWNLOAD_XML), + withLatestFrom( + this.store.select(getConfigurationModel), + this.store.select(getConfigurationXml) + ), + tap(([action, entity, xml]) => { + const name = entity.name ? entity.name : (entity as MetadataResolver).serviceProviderName; + const blob = new Blob([xml], { type: 'text/xml;charset=utf-8' }); + FileSaver.saveAs(blob, `${name}.xml`); + }) + ); + constructor( private configService: MetadataConfigurationService, - private actions$: Actions + private actions$: Actions, + private providerService: ResolverService, + private entityService: EntityIdService, + private store: Store ) { } } diff --git a/ui/src/app/metadata/configuration/reducer/configuration.reducer.spec.ts b/ui/src/app/metadata/configuration/reducer/configuration.reducer.spec.ts index d02b99560..fed39ff3f 100644 --- a/ui/src/app/metadata/configuration/reducer/configuration.reducer.spec.ts +++ b/ui/src/app/metadata/configuration/reducer/configuration.reducer.spec.ts @@ -14,6 +14,7 @@ describe('Configuration Reducer', () => { serviceProviderName: 'foo', '@type': 'MetadataResolver' }; + const xml = ``; describe('undefined action', () => { it('should return the default state', () => { @@ -50,6 +51,15 @@ describe('Configuration Reducer', () => { }); }); + describe('SET_XML action', () => { + it('should set the state metadata model', () => { + const action = new actions.SetXml(xml); + const result = reducer(initialState, action); + + expect(result).toEqual({ ...initialState, xml }); + }); + }); + describe('CLEAR action', () => { it('should clear the state and reset to initial state', () => { const action = new actions.ClearConfiguration(); @@ -63,4 +73,33 @@ describe('Configuration Reducer', () => { expect(result).toEqual(initialState); }); }); + + describe('selector functions', () => { + /* + export const getModel = (state: State) => state.model; + export const getDefinition = (state: State) => state.definition; + export const getSchema = (state: State) => state.schema; + export const getXml = (state: State) => state.xml; + */ + describe('getModel', () => { + it('should retrieve the model from state', () => { + expect(fromConfig.getModel({...initialState, model})).toBe(model); + }); + }); + describe('getDefinition', () => { + it('should retrieve the definition from state', () => { + expect(fromConfig.getDefinition({ ...initialState, definition })).toBe(definition); + }); + }); + describe('getSchema', () => { + it('should retrieve the schema from state', () => { + expect(fromConfig.getSchema({ ...initialState, schema })).toBe(schema); + }); + }); + describe('getXml', () => { + it('should retrieve the schema from state', () => { + expect(fromConfig.getXml({ ...initialState, xml })).toBe(xml); + }); + }); + }); }); diff --git a/ui/src/app/metadata/configuration/reducer/configuration.reducer.ts b/ui/src/app/metadata/configuration/reducer/configuration.reducer.ts index 966cf634c..a35e20dd7 100644 --- a/ui/src/app/metadata/configuration/reducer/configuration.reducer.ts +++ b/ui/src/app/metadata/configuration/reducer/configuration.reducer.ts @@ -7,12 +7,14 @@ export interface State { model: Metadata; schema: Schema; definition: Wizard; + xml: string; } export const initialState: State = { model: null, schema: null, - definition: null + definition: null, + xml: '' }; export function reducer(state = initialState, action: ConfigurationActionsUnion): State { @@ -32,6 +34,11 @@ export function reducer(state = initialState, action: ConfigurationActionsUnion) ...state, model: action.payload }; + case ConfigurationActionTypes.SET_XML: + return { + ...state, + xml: action.payload + }; case ConfigurationActionTypes.CLEAR: return { ...initialState @@ -45,3 +52,4 @@ export function reducer(state = initialState, action: ConfigurationActionsUnion) export const getModel = (state: State) => state.model; export const getDefinition = (state: State) => state.definition; export const getSchema = (state: State) => state.schema; +export const getXml = (state: State) => state.xml; diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index e8ba98467..d4c2fcc9c 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -28,6 +28,7 @@ export const getConfigurationState = createSelector(getState, getConfigurationSt export const getConfigurationModel = createSelector(getConfigurationState, fromConfiguration.getModel); export const getConfigurationDefinition = createSelector(getConfigurationState, fromConfiguration.getDefinition); export const getConfigurationSchema = createSelector(getConfigurationState, fromConfiguration.getSchema); +export const getConfigurationXml = createSelector(getConfigurationState, fromConfiguration.getXml); export const getConfigurationSectionsFn = (model, definition, schema) => !definition || !schema ? null : ({ diff --git a/ui/src/app/metadata/manager/container/dashboard-resolvers-list.component.ts b/ui/src/app/metadata/manager/container/dashboard-resolvers-list.component.ts index 0c20310c7..4de58be3a 100644 --- a/ui/src/app/metadata/manager/container/dashboard-resolvers-list.component.ts +++ b/ui/src/app/metadata/manager/container/dashboard-resolvers-list.component.ts @@ -87,7 +87,8 @@ export class DashboardResolversListComponent implements OnInit { } viewConfiguration(entity: MetadataEntity): void { - this.router.navigate(['metadata', 'resolver', entity.getId(), 'configuration']); + // this.store.dispatch(new PreviewEntity({ id: entity.getId(), entity })); + this.router.navigate(['metadata', 'resolver', entity.getId(), 'configuration', 'options']); } viewMetadataHistory(entity: MetadataEntity): void {