diff --git a/ui/src/app/metadata/provider/action/editor.action.ts b/ui/src/app/metadata/provider/action/editor.action.ts index 85ea00edd..3c82c0c19 100644 --- a/ui/src/app/metadata/provider/action/editor.action.ts +++ b/ui/src/app/metadata/provider/action/editor.action.ts @@ -8,7 +8,10 @@ export enum EditorActionTypes { SELECT_PROVIDER_TYPE = '[Provider Editor] Select Provider Type', - CLEAR = '[Provider Editor] Clear' + CLEAR = '[Provider Editor] Clear', + + LOCK = '[Provider Editor] Lock', + UNLOCK = '[Provider Editor] Unlock' } export class UpdateStatus implements Action { @@ -45,10 +48,20 @@ export class ClearEditor implements Action { readonly type = EditorActionTypes.CLEAR; } +export class LockEditor implements Action { + readonly type = EditorActionTypes.LOCK; +} + +export class UnlockEditor implements Action { + readonly type = EditorActionTypes.UNLOCK; +} + export type EditorActionUnion = | UpdateStatus | LoadSchemaRequest | LoadSchemaSuccess | LoadSchemaFail | SelectProviderType - | ClearEditor; + | ClearEditor + | LockEditor + | UnlockEditor; diff --git a/ui/src/app/metadata/provider/component/summary-property.component.html b/ui/src/app/metadata/provider/component/summary-property.component.html index 8f4781e47..664dcb82a 100644 --- a/ui/src/app/metadata/provider/component/summary-property.component.html +++ b/ui/src/app/metadata/provider/component/summary-property.component.html @@ -3,7 +3,7 @@ {{ property.name }} - {{ property.value || '-' }} + {{ property.value || property.value === false ? property.value : '-' }} diff --git a/ui/src/app/metadata/provider/container/provider-edit-step.component.html b/ui/src/app/metadata/provider/container/provider-edit-step.component.html index 5d07730fd..bab3e5b90 100644 --- a/ui/src/app/metadata/provider/container/provider-edit-step.component.html +++ b/ui/src/app/metadata/provider/container/provider-edit-step.component.html @@ -1,4 +1,11 @@ +
+ + + {{ lock.value ? 'Locked' : 'Unlocked' }} + + For Advanced Knowledge Only +
{ app.currentPage = 'common'; app.updateStatus({value: 'common'}); app.updateStatus({value: 'foo'}); - expect(store.dispatch).toHaveBeenCalledTimes(2); + expect(store.dispatch).toHaveBeenCalledTimes(3); }); }); diff --git a/ui/src/app/metadata/provider/container/provider-edit-step.component.ts b/ui/src/app/metadata/provider/container/provider-edit-step.component.ts index 63256e8a6..7811590ed 100644 --- a/ui/src/app/metadata/provider/container/provider-edit-step.component.ts +++ b/ui/src/app/metadata/provider/container/provider-edit-step.component.ts @@ -3,20 +3,20 @@ import { Observable, Subject } from 'rxjs'; import { Store } from '@ngrx/store'; import * as fromProvider from '../reducer'; -import { UpdateStatus } from '../action/editor.action'; -import { Wizard } from '../../../wizard/model'; +import { UpdateStatus, LockEditor, UnlockEditor } from '../action/editor.action'; +import { Wizard, WizardStep } from '../../../wizard/model'; import { MetadataProvider } from '../../domain/model'; import * as fromWizard from '../../../wizard/reducer'; -import { withLatestFrom, map, skipWhile, distinctUntilChanged } from 'rxjs/operators'; +import { withLatestFrom, map, skipWhile, distinctUntilChanged, startWith, combineLatest } from 'rxjs/operators'; import { UpdateProvider } from '../action/entity.action'; +import { FormControl } from '@angular/forms'; @Component({ selector: 'provider-edit-step', templateUrl: './provider-edit-step.component.html', styleUrls: [] }) - export class ProviderEditStepComponent implements OnDestroy { valueChangeSubject = new Subject>(); private valueChangeEmitted$ = this.valueChangeSubject.asObservable(); @@ -33,16 +33,30 @@ export class ProviderEditStepComponent implements OnDestroy { model$: Observable; definition$: Observable>; changes$: Observable; + step$: Observable; validators$: Observable<{ [key: string]: any }>; + lock: FormControl = new FormControl(true); + constructor( private store: Store ) { - this.schema$ = this.store.select(fromProvider.getSchema); this.definition$ = this.store.select(fromWizard.getWizardDefinition); this.changes$ = this.store.select(fromProvider.getEntityChanges); this.provider$ = this.store.select(fromProvider.getSelectedProvider); + this.step$ = this.store.select(fromWizard.getCurrent); + this.schema$ = this.store.select(fromProvider.getSchema); + + this.step$.subscribe(s => { + if (s && s.locked) { + this.store.dispatch(new LockEditor()); + } else { + this.store.dispatch(new UnlockEditor()); + } + }); + + this.lock.valueChanges.subscribe(locked => this.store.dispatch(locked ? new LockEditor() : new UnlockEditor())); this.validators$ = this.store.select(fromProvider.getProviderNames).pipe( withLatestFrom(this.definition$, this.provider$), @@ -76,7 +90,11 @@ export class ProviderEditStepComponent implements OnDestroy { ) .subscribe(changes => this.store.dispatch(new UpdateProvider(changes))); - this.statusChangeEmitted$.pipe(distinctUntilChanged()).subscribe(errors => this.updateStatus(errors)); + this.statusChangeEmitted$ + .pipe(distinctUntilChanged()) + .subscribe(errors => { + this.updateStatus(errors); + }); this.store.select(fromWizard.getWizardIndex).subscribe(i => this.currentPage = i); } 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 014061305..f7f958db8 100644 --- a/ui/src/app/metadata/provider/container/provider-wizard.component.ts +++ b/ui/src/app/metadata/provider/container/provider-wizard.component.ts @@ -10,7 +10,6 @@ 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'; import { AddProviderRequest } from '../action/collection.action'; import { MetadataProviderWizard } from '../model'; @@ -36,9 +35,7 @@ export class ProviderWizardComponent implements OnDestroy { provider: MetadataProvider; constructor( - private store: Store, - private router: Router, - private route: ActivatedRoute + private store: Store ) { this.store .select(fromWizard.getCurrentWizardSchema) diff --git a/ui/src/app/metadata/provider/effect/collection.effect.ts b/ui/src/app/metadata/provider/effect/collection.effect.ts index d4eb5ff0f..a3f8aa723 100644 --- a/ui/src/app/metadata/provider/effect/collection.effect.ts +++ b/ui/src/app/metadata/provider/effect/collection.effect.ts @@ -108,7 +108,7 @@ export class CollectionEffects { ); @Effect() - addResolverSuccessReload$ = this.actions$.pipe( + addProviderSuccessReload$ = this.actions$.pipe( ofType(ProviderCollectionActionTypes.ADD_PROVIDER_SUCCESS), map(action => action.payload), map(provider => new LoadProviderRequest()) 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 5af8642bb..b4faa696f 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 @@ -77,6 +77,7 @@ export const FileBackedHttpMetadataProviderEditor: Wizard { describe('undefined action', () => { @@ -15,4 +24,40 @@ describe('Provider Editor Reducer', () => { expect(reducer(snapshot, new ClearEditor())).toEqual(snapshot); }); }); + + describe(`${EditorActionTypes.LOCK}`, () => { + it('should reset to initial state', () => { + expect(reducer(snapshot, new LockEditor())).toEqual({ ...snapshot, locked: true }); + }); + }); + + describe(`${EditorActionTypes.UNLOCK}`, () => { + it('should reset to initial state', () => { + expect(reducer(snapshot, new UnlockEditor())).toEqual({ ...snapshot, locked: false }); + }); + }); + + describe(`${EditorActionTypes.LOAD_SCHEMA_REQUEST}`, () => { + it('should reset to initial state', () => { + expect(reducer(snapshot, new LoadSchemaRequest('foo'))).toEqual({ ...snapshot, schemaPath: 'foo', loading: true }); + }); + }); + + describe(`${EditorActionTypes.LOAD_SCHEMA_FAIL}`, () => { + it('should reset to initial state', () => { + expect(reducer(snapshot, new LoadSchemaFail(new Error('fail')))).toEqual({ ...snapshot }); + }); + }); + + describe(`${EditorActionTypes.LOAD_SCHEMA_REQUEST}`, () => { + it('should reset to initial state', () => { + expect(reducer(snapshot, new LoadSchemaSuccess({}))).toEqual({ ...snapshot, schema: {} }); + }); + }); + + describe(`${EditorActionTypes.SELECT_PROVIDER_TYPE}`, () => { + it('should reset to initial state', () => { + expect(reducer(snapshot, new SelectProviderType('foo'))).toEqual({ ...snapshot, type: 'foo' }); + }); + }); }); diff --git a/ui/src/app/metadata/provider/reducer/editor.reducer.ts b/ui/src/app/metadata/provider/reducer/editor.reducer.ts index 609f707f0..32c96c86c 100644 --- a/ui/src/app/metadata/provider/reducer/editor.reducer.ts +++ b/ui/src/app/metadata/provider/reducer/editor.reducer.ts @@ -6,6 +6,7 @@ export interface EditorState { loading: boolean; schema: any; type: string; + locked: boolean; } export const initialState: EditorState = { @@ -13,7 +14,8 @@ export const initialState: EditorState = { schemaPath: null, loading: false, schema: null, - type: null + type: null, + locked: false }; export function reducer(state = initialState, action: EditorActionUnion): EditorState { @@ -59,6 +61,20 @@ export function reducer(state = initialState, action: EditorActionUnion): Editor schema: initialState.schema }; } + + case EditorActionTypes.LOCK: { + return { + ...state, + locked: true + }; + } + + case EditorActionTypes.UNLOCK: { + return { + ...state, + locked: false + }; + } default: { return state; } @@ -66,6 +82,7 @@ export function reducer(state = initialState, action: EditorActionUnion): Editor } export const getSchema = (state: EditorState) => state.schema; +export const getLocked = (state: EditorState) => state.locked; export const isEditorValid = (state: EditorState) => !Object.keys(state.status).some(key => state.status[key] === ('INVALID')); diff --git a/ui/src/app/metadata/provider/reducer/index.ts b/ui/src/app/metadata/provider/reducer/index.ts index ee98fec25..4b7e4d45b 100644 --- a/ui/src/app/metadata/provider/reducer/index.ts +++ b/ui/src/app/metadata/provider/reducer/index.ts @@ -4,7 +4,11 @@ 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 * as fromWizard from '../../../wizard/reducer'; + import { MetadataProvider } from '../../domain/model'; +import { WizardStep } from '../../../wizard/model'; export interface ProviderState { editor: fromEditor.EditorState; @@ -36,13 +40,41 @@ export const getCollectionState = createSelector(getProviderState, getCollection Editor State */ -export const getSchema = createSelector(getEditorState, fromEditor.getSchema); +export function getSchemaParseFn(schema, locked): any { + if (!schema) { + return null; + } + return { + ...schema, + properties: Object.keys(schema.properties).reduce((prev, current) => { + return { + ...prev, + [current]: { + ...schema.properties[current], + readOnly: locked, + ...(schema.properties[current].hasOwnProperty('properties') ? + getSchemaParseFn(schema.properties[current], locked) : + {} + ) + } + }; + }, {}) + }; +} + +export const getSchemaLockedFn = (step, locked) => step ? step.locked ? locked : false : false; +export const getLockedStatus = createSelector(getEditorState, fromEditor.getLocked); +export const getLocked = createSelector(fromWizard.getCurrent, getLockedStatus, getSchemaLockedFn); + +export const getSchemaObject = createSelector(getEditorState, fromEditor.getSchema); +export const getSchema = createSelector(getSchemaObject, getLocked, getSchemaParseFn); export const getEditorIsValid = createSelector(getEditorState, fromEditor.isEditorValid); export const getFormStatus = createSelector(getEditorState, fromEditor.getFormStatus); export const getInvalidEditorForms = createSelector(getEditorState, fromEditor.getInvalidForms); + /* Entity State */ diff --git a/ui/src/app/schema-form/registry.ts b/ui/src/app/schema-form/registry.ts index f99f9eece..44a20841a 100644 --- a/ui/src/app/schema-form/registry.ts +++ b/ui/src/app/schema-form/registry.ts @@ -36,6 +36,8 @@ export class CustomWidgetRegistry extends WidgetRegistry { this.register('boolean-radio', BooleanRadioComponent); this.register('fieldset', FieldsetComponent); + this.register('object', FieldsetComponent); + this.register('array', CustomArrayComponent); this.register('select', CustomSelectComponent); @@ -50,7 +52,6 @@ export class CustomWidgetRegistry extends WidgetRegistry { this.register('datalist', DatalistComponent); /* NGX-Form */ - this.register('object', ObjectWidget); this.register('range', RangeWidget); this.register('file', FileWidget); diff --git a/ui/src/app/schema-form/widget/boolean-radio/boolean-radio.component.ts b/ui/src/app/schema-form/widget/boolean-radio/boolean-radio.component.ts index bd5ffa7df..d4ad09d6f 100644 --- a/ui/src/app/schema-form/widget/boolean-radio/boolean-radio.component.ts +++ b/ui/src/app/schema-form/widget/boolean-radio/boolean-radio.component.ts @@ -1,4 +1,4 @@ -import { Component } from '@angular/core'; +import { Component, AfterViewInit } from '@angular/core'; import { ControlWidget } from 'ngx-schema-form'; @Component({ @@ -6,6 +6,13 @@ import { ControlWidget } from 'ngx-schema-form'; templateUrl: './boolean-radio.component.html', styleUrls: ['./boolean-radio.component.scss'] }) -export class BooleanRadioComponent extends ControlWidget { - +export class BooleanRadioComponent extends ControlWidget implements AfterViewInit { + ngAfterViewInit(): void { + super.ngAfterViewInit(); + if (this.schema.readOnly) { + this.control.disable(); + } else { + this.control.enable(); + } + } } 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 d47aa2d4c..db1385630 100644 --- a/ui/src/app/schema-form/widget/check/checkbox.component.html +++ b/ui/src/app/schema-form/widget/check/checkbox.component.html @@ -7,7 +7,7 @@ [attr.name]="name" [indeterminate]="control.value !== false && control.value !== true ? true :null" type="checkbox" - [attr.disabled]="schema.readOnly" + [attr.disabled]="schema.readOnly?true:null" id="{{ name }}" [disableValidation]="true">