diff --git a/ui/src/app/metadata/configuration/action/configuration.action.ts b/ui/src/app/metadata/configuration/action/configuration.action.ts new file mode 100644 index 000000000..42d1dddaa --- /dev/null +++ b/ui/src/app/metadata/configuration/action/configuration.action.ts @@ -0,0 +1,61 @@ +import { Action } from '@ngrx/store'; +import { Metadata } from '../../domain/domain.type'; +import { Schema } from '../model/schema'; +import { Wizard } from '../../../wizard/model'; + +export enum ConfigurationActionTypes { + LOAD_METADATA_REQUEST = '[Metadata Configuration] Load Metadata Request', + LOAD_METADATA_SUCCESS = '[Metadata Configuration] Load Metadata Success', + LOAD_METADATA_ERROR = '[Metadata Configuration] Load Metadata Error', + + SET_METADATA = '[Metadata Configuration] Set Metadata Model', + SET_DEFINITION = '[Metadata Configuration] Set Metadata Definition', + SET_SCHEMA = '[Metadata Configuration] Set Metadata Schema', + CLEAR = '[Metadata Configuration] Clear' +} + +export class LoadMetadataRequest implements Action { + readonly type = ConfigurationActionTypes.LOAD_METADATA_REQUEST; + + constructor(public payload: { id: string, type: string }) { } +} + +export class LoadMetadataSuccess implements Action { + readonly type = ConfigurationActionTypes.LOAD_METADATA_SUCCESS; + + constructor(public payload: Metadata) { } +} + +export class LoadMetadataError implements Action { + readonly type = ConfigurationActionTypes.LOAD_METADATA_ERROR; + + constructor(public payload: any) { } +} + +export class SetMetadata implements Action { + readonly type = ConfigurationActionTypes.SET_METADATA; + + constructor(public payload: Metadata) { } +} + +export class SetDefinition implements Action { + readonly type = ConfigurationActionTypes.SET_DEFINITION; + + constructor(public payload: Wizard) { } +} + +export class SetSchema implements Action { + readonly type = ConfigurationActionTypes.SET_SCHEMA; + + constructor(public payload: Schema) { } +} + +export class ClearConfiguration implements Action { + readonly type = ConfigurationActionTypes.CLEAR; +} + +export type ConfigurationActionsUnion = + | SetMetadata + | SetDefinition + | SetSchema + | 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 new file mode 100644 index 000000000..f2034d06b --- /dev/null +++ b/ui/src/app/metadata/configuration/component/metadata-configuration.component.html @@ -0,0 +1,10 @@ +
+
+
+ {{ i + 1 }}: {{ section.label }} + + {{ prop | json }} + +
+
+
\ No newline at end of file diff --git a/ui/src/app/metadata/configuration/component/metadata-configuration.component.spec.ts b/ui/src/app/metadata/configuration/component/metadata-configuration.component.spec.ts new file mode 100644 index 000000000..e69de29bb diff --git a/ui/src/app/metadata/configuration/component/metadata-configuration.component.ts b/ui/src/app/metadata/configuration/component/metadata-configuration.component.ts new file mode 100644 index 000000000..578b7abd3 --- /dev/null +++ b/ui/src/app/metadata/configuration/component/metadata-configuration.component.ts @@ -0,0 +1,16 @@ +import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; +import { WizardStep } from '../../../wizard/model'; +import Section from '../model/section'; +import { MetadataConfiguration } from '../model/metadata-configuration'; + +@Component({ + selector: 'metadata-configuration', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './metadata-configuration.component.html', + styleUrls: [] +}) +export class MetadataConfigurationComponent { + @Input() configuration: MetadataConfiguration; + + constructor() { } +} diff --git a/ui/src/app/metadata/configuration/configuration.module.ts b/ui/src/app/metadata/configuration/configuration.module.ts new file mode 100644 index 000000000..c04ab7add --- /dev/null +++ b/ui/src/app/metadata/configuration/configuration.module.ts @@ -0,0 +1,45 @@ +import { NgModule, ModuleWithProviders } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { StoreModule } from '@ngrx/store'; +import { EffectsModule } from '@ngrx/effects'; + +import { I18nModule } from '../../i18n/i18n.module'; +import { MetadataConfigurationComponent } from './component/metadata-configuration.component'; +import { ConfigurationComponent } from './container/configuration.component'; +import { MetadataConfigurationService } from './service/configuration.service'; +import * as fromConfig from './reducer'; +import { MetadataConfigurationEffects } from './effect/configuration.effect'; + +@NgModule({ + declarations: [ + MetadataConfigurationComponent, + ConfigurationComponent + ], + entryComponents: [], + imports: [ + CommonModule, + I18nModule + ], + exports: [], + providers: [] +}) +export class MetadataConfigurationModule { + static forRoot(): ModuleWithProviders { + return { + ngModule: RootMetadataConfigurationModule, + providers: [ + MetadataConfigurationService + ] + }; + } +} + +@NgModule({ + imports: [ + MetadataConfigurationModule, + StoreModule.forFeature('metadata-configuration', fromConfig.reducers), + EffectsModule.forFeature([MetadataConfigurationEffects]) + ], + providers: [] +}) +export class RootMetadataConfigurationModule { } diff --git a/ui/src/app/metadata/configuration/configuration.routing.ts b/ui/src/app/metadata/configuration/configuration.routing.ts new file mode 100644 index 000000000..c1e6e138d --- /dev/null +++ b/ui/src/app/metadata/configuration/configuration.routing.ts @@ -0,0 +1,9 @@ +import { Routes } from '@angular/router'; +import { ConfigurationComponent } from './container/configuration.component'; + +export const ConfigurationRoutes: Routes = [ + { + path: ':type/:id/configuration', + component: ConfigurationComponent + } +]; diff --git a/ui/src/app/metadata/configuration/container/configuration.component.html b/ui/src/app/metadata/configuration/container/configuration.component.html new file mode 100644 index 000000000..21e8a3311 --- /dev/null +++ b/ui/src/app/metadata/configuration/container/configuration.component.html @@ -0,0 +1,17 @@ +
+
+
+
+
+ + + Metadata resolver history + +
+
+
+
+ +
+
+
\ 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 new file mode 100644 index 000000000..e69de29bb diff --git a/ui/src/app/metadata/configuration/container/configuration.component.ts b/ui/src/app/metadata/configuration/container/configuration.component.ts new file mode 100644 index 000000000..3c47e0b93 --- /dev/null +++ b/ui/src/app/metadata/configuration/container/configuration.component.ts @@ -0,0 +1,44 @@ +import { Store } from '@ngrx/store'; +import { Component, ChangeDetectionStrategy, OnDestroy } from '@angular/core'; +import { Observable, Subject } from 'rxjs'; +import { ActivatedRoute } from '@angular/router'; + +import * as fromConfiguration from '../reducer'; +import { MetadataConfiguration } from '../model/metadata-configuration'; +import { METADATA_SOURCE_EDITOR } from '../../resolver/wizard-definition'; +import { takeUntil, map } from 'rxjs/operators'; +import { LoadMetadataRequest, ClearConfiguration } from '../action/configuration.action'; + +@Component({ + selector: 'configuration-page', + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './configuration.component.html', + styleUrls: [] +}) +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.getConfigurationColumns); + + this.routerState.params.pipe( + takeUntil(this.ngUnsubscribe), + map(params => new LoadMetadataRequest({id: params.id, type: params.type})) + ).subscribe(store); + + this.configuration$.subscribe(c => console.log(c)); + + // this.resolver$ = this.store.select(fromResolvers.getSelectedResolver).pipe(skipWhile(p => !p)); + } + + ngOnDestroy() { + this.ngUnsubscribe.next(); + this.ngUnsubscribe.complete(); + this.store.dispatch(new ClearConfiguration()); + } +} diff --git a/ui/src/app/metadata/configuration/effect/configuration.effect.ts b/ui/src/app/metadata/configuration/effect/configuration.effect.ts new file mode 100644 index 000000000..b3c8f0515 --- /dev/null +++ b/ui/src/app/metadata/configuration/effect/configuration.effect.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@angular/core'; +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { switchMap, catchError, map } from 'rxjs/operators'; +import { of } from 'rxjs'; + +import { MetadataConfigurationService } from '../service/configuration.service'; +import { + LoadMetadataRequest, + LoadMetadataSuccess, + LoadMetadataError, + ConfigurationActionTypes, + SetMetadata, + SetDefinition +} from '../action/configuration.action'; + +@Injectable() +export class MetadataConfigurationEffects { + + @Effect() + loadMetadata$ = this.actions$.pipe( + ofType(ConfigurationActionTypes.LOAD_METADATA_REQUEST), + switchMap(action => + this.configService + .find(action.payload.id, action.payload.type) + .pipe( + map(md => new LoadMetadataSuccess(md)), + catchError(error => of(new LoadMetadataError(error))) + ) + ) + ); + + @Effect() + setMetadataOnLoad$ = this.actions$.pipe( + ofType(ConfigurationActionTypes.LOAD_METADATA_SUCCESS), + map(action => new SetMetadata(action.payload)) + ); + + @Effect() + setDefinitionOnMetadataSet$ = this.actions$.pipe( + ofType(ConfigurationActionTypes.SET_METADATA), + map(action => new SetDefinition(this.configService.getDefinition(action.payload))) + ); + + constructor( + private configService: MetadataConfigurationService, + private actions$: Actions + ) { } +} diff --git a/ui/src/app/metadata/configuration/model/metadata-configuration.ts b/ui/src/app/metadata/configuration/model/metadata-configuration.ts new file mode 100644 index 000000000..10768f14e --- /dev/null +++ b/ui/src/app/metadata/configuration/model/metadata-configuration.ts @@ -0,0 +1,5 @@ +import Section from './section'; + +export interface MetadataConfiguration { + columns: Array
[]; +} diff --git a/ui/src/app/metadata/configuration/model/schema.ts b/ui/src/app/metadata/configuration/model/schema.ts new file mode 100644 index 000000000..469650111 --- /dev/null +++ b/ui/src/app/metadata/configuration/model/schema.ts @@ -0,0 +1,3 @@ +export interface Schema { + [prop: string]: any; +} diff --git a/ui/src/app/metadata/configuration/model/section.ts b/ui/src/app/metadata/configuration/model/section.ts new file mode 100644 index 000000000..089a1953a --- /dev/null +++ b/ui/src/app/metadata/configuration/model/section.ts @@ -0,0 +1,11 @@ +import { Property } from '../../domain/model/property'; + +export interface Section { + id: string; + index: number; + label: string; + pageNumber: number; + properties: Property[]; +} + +export default Section; diff --git a/ui/src/app/metadata/configuration/reducer/configuration.reducer.ts b/ui/src/app/metadata/configuration/reducer/configuration.reducer.ts new file mode 100644 index 000000000..3acc42338 --- /dev/null +++ b/ui/src/app/metadata/configuration/reducer/configuration.reducer.ts @@ -0,0 +1,51 @@ +import { ConfigurationActionTypes, ConfigurationActionsUnion } from '../action/configuration.action'; +import { Metadata } from '../../domain/domain.type'; +import { Wizard, WizardStep } from '../../../wizard/model'; +import { Schema } from '../model/schema'; + +export interface State { + model: Metadata; + schema: Schema; + definition: Wizard; +} + +export const initialState: State = { + model: null, + schema: null, + definition: null +}; + +export function reducer(state = initialState, action: ConfigurationActionsUnion): State { + switch (action.type) { + case ConfigurationActionTypes.SET_SCHEMA: + return { + ...state, + schema: action.payload + }; + break; + case ConfigurationActionTypes.SET_DEFINITION: + return { + ...state, + definition: action.payload + }; + break; + case ConfigurationActionTypes.SET_METADATA: + return { + ...state, + model: action.payload + }; + break; + case ConfigurationActionTypes.CLEAR: + return { + ...initialState + }; + break; + default: { + return state; + } + } +} + +export const getModel = (state: State) => state.model; +export const getDefinition = (state: State) => state.definition; +export const getSchema = (state: State) => state.schema; diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts new file mode 100644 index 000000000..13b5c627f --- /dev/null +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -0,0 +1,75 @@ +import { createSelector, createFeatureSelector } from '@ngrx/store'; +import merge from 'deepmerge'; + +import * as fromRoot from '../../../app.reducer'; +import * as fromConfiguration from './configuration.reducer'; +import { WizardStep } from '../../../wizard/model'; + +import * as utils from '../service/utility'; +import { getSplitSchema } from '../../../wizard/reducer'; + +export interface ConfigurationState { + configuration: fromConfiguration.State; +} + +export const reducers = { + configuration: fromConfiguration.reducer +}; + +export interface State extends fromRoot.State { + 'metadata-configuration': ConfigurationState; +} + +export const getState = createFeatureSelector('metadata-configuration'); + +export const getConfigurationStateFn = (state: ConfigurationState) => state.configuration; + +export const getConfigurationState = createSelector(getState, getConfigurationStateFn); +export const getConfigurationModel = createSelector(getConfigurationState, fromConfiguration.getModel); +export const getConfigurationDefinition = createSelector(getConfigurationState, fromConfiguration.getDefinition); +export const getConfigurationSchema = createSelector(getConfigurationState, fromConfiguration.getSchema); + +export const mergedSchema = createSelector(getConfigurationSchema, schema => !schema ? null : Object.keys(schema).reduce((coll, key) => ({ + ...merge(coll, schema[key]) +}), {} as any)); + +export const getConfigurationSectionsFn = (model, definition, schema) => !definition || !schema ? null : + definition.steps + .filter(step => step.id !== 'summary') + .map( + (step: WizardStep, num: number) => { + return ({ + id: step.id, + pageNumber: num + 1, + index: step.index, + label: step.label, + properties: utils.getStepProperties( + getSplitSchema(schema, step), + definition.formatter(model), + schema.definitions || {} + ) + }); + } + ); + +export const getConfigurationColumnsFn = sections => !sections ? null : + sections.reduce((resultArray, item, index) => { + const chunkIndex = Math.floor(index / Math.round(this.sections.length / 2)); + + if (!resultArray[chunkIndex]) { + resultArray[chunkIndex] = []; + } + + resultArray[chunkIndex].push(item); + + return resultArray; + }, []); + +export const getConfigurationSections = createSelector( + getConfigurationModel, + getConfigurationDefinition, + mergedSchema, + getConfigurationSectionsFn +); + +export const getConfigurationColumns = createSelector(getConfigurationSections, getConfigurationColumnsFn); diff --git a/ui/src/app/metadata/configuration/service/configuration.service.ts b/ui/src/app/metadata/configuration/service/configuration.service.ts new file mode 100644 index 000000000..9ea95a79a --- /dev/null +++ b/ui/src/app/metadata/configuration/service/configuration.service.ts @@ -0,0 +1,35 @@ +import { HttpClient } from '@angular/common/http'; +import { Injectable } from '@angular/core'; +import { Observable, of } from 'rxjs'; +import { Metadata } from '../../domain/domain.type'; +import { Wizard } from '../../../wizard/model'; +import { MetadataSourceEditor } from '../../domain/model/wizards/metadata-source-editor'; +import { MetadataProviderEditorTypes } from '../../provider/model'; + +export enum PATHS { + resolver = 'EntityDescriptor', + provider = 'MetadataResolvers' +} + +export const DEFINITIONS = { + resolver: MetadataSourceEditor +}; + +@Injectable() +export class MetadataConfigurationService { + + readonly base = '/api'; + + constructor( + private http: HttpClient + ) {} + + find(id: string, type: string): Observable { + return this.http.get(`${this.base}/${PATHS[type]}/${id}`); + } + + getDefinition(model: Metadata): Wizard { + return MetadataProviderEditorTypes.find(def => def.type === model['@type']) || new MetadataSourceEditor(); + } +} + diff --git a/ui/src/app/metadata/configuration/service/utility.ts b/ui/src/app/metadata/configuration/service/utility.ts new file mode 100644 index 000000000..ee329c3b1 --- /dev/null +++ b/ui/src/app/metadata/configuration/service/utility.ts @@ -0,0 +1,40 @@ +import { Property } from '../../domain/model/property'; + +export function getDefinition(path: string, definitions: any): any { + let def = path.split('/').pop(); + return definitions[def]; +} +export function getPropertyItemSchema(items: any, definitions: any): any { + if (!items) { return null; } + return items.$ref ? getDefinition(items.$ref, definitions) : items; +} + +export function getStepProperty(property, model, definitions): Property { + if (!property) { return null; } + property = property.$ref ? { ...property, ...getDefinition(property.$ref, definitions) } : property; + return { + name: property.title, + value: model, + type: property.type, + items: getPropertyItemSchema(property.items, definitions), + properties: getStepProperties( + property, + model, + definitions + ), + widget: property.widget instanceof String ? { id: property.widget } : { ...property.widget } + }; +} + +export function getStepProperties(schema: any, model: any, definitions: any = {}): Property[] { + if (!schema || !schema.properties) { return []; } + return Object + .keys(schema.properties) + .map(property => { + return getStepProperty( + schema.properties[property], + model && model.hasOwnProperty(property) ? model[property] : null, + definitions + ); + }); +} diff --git a/ui/src/app/metadata/domain/model/wizards/metadata-source-editor.ts b/ui/src/app/metadata/domain/model/wizards/metadata-source-editor.ts index cbe98d5de..2c5c66a59 100644 --- a/ui/src/app/metadata/domain/model/wizards/metadata-source-editor.ts +++ b/ui/src/app/metadata/domain/model/wizards/metadata-source-editor.ts @@ -2,7 +2,7 @@ import { Wizard, WizardStep } from '../../../../wizard/model'; import { MetadataResolver } from '../metadata-resolver'; import { MetadataSourceBase } from './metadata-source-base'; -export class MetadataSourceEditor extends MetadataSourceBase implements Wizard { +export class MetadataSourceEditor extends MetadataSourceBase implements Wizard { steps: WizardStep[] = [ { index: 1, diff --git a/ui/src/app/metadata/manager/container/dashboard-resolvers-list.component.html b/ui/src/app/metadata/manager/container/dashboard-resolvers-list.component.html index e2136c4a1..e9aea1c2b 100644 --- a/ui/src/app/metadata/manager/container/dashboard-resolvers-list.component.html +++ b/ui/src/app/metadata/manager/container/dashboard-resolvers-list.component.html @@ -24,7 +24,7 @@ [isOpen]="(entitiesOpen$ | async)[resolver.getId()]" (select)="edit(resolver)" (toggle)="toggleEntity(resolver)" - (preview)="openPreviewDialog(resolver)" + (preview)="viewConfiguration(resolver)" (delete)="deleteResolver(resolver)" (history)="viewMetadataHistory(resolver)" [allowDelete]="resolver.isDraft()"> 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 540d5b694..0c20310c7 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 @@ -86,8 +86,8 @@ export class DashboardResolversListComponent implements OnInit { this.store.dispatch(new ToggleEntityDisplay(entity.getId())); } - openPreviewDialog(entity: MetadataEntity): void { - this.store.dispatch(new PreviewEntity({ id: entity.getId(), entity })); + viewConfiguration(entity: MetadataEntity): void { + this.router.navigate(['metadata', 'resolver', entity.getId(), 'configuration']); } viewMetadataHistory(entity: MetadataEntity): void { diff --git a/ui/src/app/metadata/metadata.module.ts b/ui/src/app/metadata/metadata.module.ts index 80f663b2a..231f39f5f 100644 --- a/ui/src/app/metadata/metadata.module.ts +++ b/ui/src/app/metadata/metadata.module.ts @@ -12,7 +12,7 @@ import { CustomWidgetRegistry } from '../schema-form/registry'; import { WidgetRegistry, SchemaValidatorFactory } from 'ngx-schema-form'; import { CustomSchemaValidatorFactory } from '../schema-form/service/schema-validator'; import { MetadataVersionModule } from './version/version.module'; - +import { MetadataConfigurationModule } from './configuration/configuration.module'; @NgModule({ imports: [ @@ -22,6 +22,7 @@ import { MetadataVersionModule } from './version/version.module'; ManagerModule.forRoot(), ProviderModule.forRoot(), MetadataVersionModule.forRoot(), + MetadataConfigurationModule.forRoot(), MetadataRoutingModule, I18nModule ], diff --git a/ui/src/app/metadata/metadata.routing.ts b/ui/src/app/metadata/metadata.routing.ts index a1d8964f2..35902415c 100644 --- a/ui/src/app/metadata/metadata.routing.ts +++ b/ui/src/app/metadata/metadata.routing.ts @@ -6,6 +6,7 @@ import { ResolverRoutes } from './resolver/resolver.routing'; import { ProviderRoutes } from './provider/provider.routing'; import { ManagerRoutes } from './manager/manager.routing'; import { VersionRoutes } from './version/version.routing'; +import { ConfigurationRoutes } from './configuration/configuration.routing'; const routes: Routes = [ { @@ -15,7 +16,8 @@ const routes: Routes = [ ...ManagerRoutes, ...ResolverRoutes, ...ProviderRoutes, - ...VersionRoutes + ...VersionRoutes, + ...ConfigurationRoutes ], }, ];