diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index ff6eb36d2..87b0e707d 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -280,6 +280,7 @@ label.filter-enable=Enable this Filter? label.search-criteria=Search Criteria label.metadata-filter=Metadata Filter label.metadata-filter-type=Metadata Filter Type +label.filter-versions=Filter Versions label.http-connection-attributes=HTTP Connection Attributes label.http-security-attributes=HTTP Security Attributes diff --git a/ui/src/app/metadata/configuration/action/compare.action.ts b/ui/src/app/metadata/configuration/action/compare.action.ts index be687d142..da012bea7 100644 --- a/ui/src/app/metadata/configuration/action/compare.action.ts +++ b/ui/src/app/metadata/configuration/action/compare.action.ts @@ -1,6 +1,4 @@ import { Action } from '@ngrx/store'; -import { MetadataHistory } from '../model/history'; -import { MetadataVersion } from '../model/version'; import { Metadata } from '../../domain/domain.type'; export enum CompareActionTypes { diff --git a/ui/src/app/metadata/configuration/action/filter.action.ts b/ui/src/app/metadata/configuration/action/filter.action.ts new file mode 100644 index 000000000..6567fd0e2 --- /dev/null +++ b/ui/src/app/metadata/configuration/action/filter.action.ts @@ -0,0 +1,62 @@ +import { Action } from '@ngrx/store'; +import { Metadata } from '../../domain/domain.type'; +import { FormDefinition } from '../../../wizard/model/form-definition'; +import { FilterComparison } from '../model/compare'; +import { Schema } from '../model/schema'; + +export enum FilterCompareActionTypes { + LOAD_SCHEMA_REQUEST = '[Filter Compare Version] Compare Version Request', + LOAD_SCHEMA_SUCCESS = '[Filter Compare Version] Compare Version Success', + LOAD_SCHEMA_ERROR = '[Filter Compare Version] Compare Version Error', + SET_SCHEMA = '[Filter Compare Version] Set Schema', + SET_DEFINITION = '[Filter Compare Version] Set Definition', + COMPARE_FILTERS = '[Filter Compare Version] Compare Filters', + CLEAR = '[Filter Compare Version] Clear Filter Comparison' +} + +export class LoadFilterSchemaRequest implements Action { + readonly type = FilterCompareActionTypes.LOAD_SCHEMA_REQUEST; + + constructor(public payload: string) { } +} + +export class LoadFilterSchemaSuccess implements Action { + readonly type = FilterCompareActionTypes.LOAD_SCHEMA_SUCCESS; + + constructor(public payload: Schema) { } +} + +export class LoadFilterSchemaError implements Action { + readonly type = FilterCompareActionTypes.LOAD_SCHEMA_ERROR; + + constructor(public payload: any) { } +} + +export class SetFilterComparisonSchema implements Action { + readonly type = FilterCompareActionTypes.SET_SCHEMA; + constructor(public payload: any) { } +} + +export class SetFilterComparisonDefinition implements Action { + readonly type = FilterCompareActionTypes.SET_DEFINITION; + constructor(public payload: FormDefinition) { } +} + +export class ClearFilterComparison implements Action { + readonly type = FilterCompareActionTypes.CLEAR; +} + +export class CompareFilterVersions implements Action { + readonly type = FilterCompareActionTypes.COMPARE_FILTERS; + + constructor(public payload: FilterComparison) { } +} + +export type FilterCompareActionsUnion = + | LoadFilterSchemaRequest + | LoadFilterSchemaSuccess + | LoadFilterSchemaError + | SetFilterComparisonSchema + | SetFilterComparisonDefinition + | CompareFilterVersions + | ClearFilterComparison; diff --git a/ui/src/app/metadata/configuration/component/filter-version-list.component.html b/ui/src/app/metadata/configuration/component/filter-version-list.component.html new file mode 100644 index 000000000..0ca03398d --- /dev/null +++ b/ui/src/app/metadata/configuration/component/filter-version-list.component.html @@ -0,0 +1,51 @@ + +
+ Order + Option + + {{ date | date:DATE_FORMAT }} + +
+
+ +
+
+ {{ i + 1 }} +
+
+
-
+
+
+

{{ filter.name }}

+

{{ filter['@type'] }}

+
+ +
+
+ +
+
+
+
+ +
+
+
+

No Filters

+

No filters have been added to this Metadata Provider

+
\ No newline at end of file diff --git a/ui/src/app/metadata/configuration/component/filter-version-list.component.spec.ts b/ui/src/app/metadata/configuration/component/filter-version-list.component.spec.ts new file mode 100644 index 000000000..c555ea9fc --- /dev/null +++ b/ui/src/app/metadata/configuration/component/filter-version-list.component.spec.ts @@ -0,0 +1,80 @@ +import { Component, ViewChild } from '@angular/core'; +import { TestBed, ComponentFixture } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; + +import { MockI18nModule } from '../../../../testing/i18n.stub'; +import { FilterVersionListComponent } from './filter-version-list.component'; +import { FilterComparison } from '../model/compare'; +import { FilterConfiguration } from '../model/metadata-configuration'; + +export const TestData: FilterConfiguration = { + dates: ['2019-09-23T20:54:31.081Z', '2019-10-23T20:54:31.081Z'], + filters: [ + [ + { + resourceId: 'foo', + name: 'Test 1', + type: 'EntityAttributesFilter', + comparable: false + }, + { + resourceId: 'bar', + name: 'Test 2', + type: 'EntityAttributesFilter', + comparable: false + } + ] + ] +}; + +@Component({ + template: `` +}) +class TestHostComponent { + @ViewChild(FilterVersionListComponent) + public componentUnderTest: FilterVersionListComponent; + + filters = TestData; + + compare(versions: FilterComparison): void { } +} + +describe('Filter Version List Component', () => { + let fixture: ComponentFixture; + let instance: TestHostComponent; + let list: FilterVersionListComponent; + + beforeEach(() => { + TestBed.configureTestingModule({ + providers: [], + imports: [ + MockI18nModule, + RouterTestingModule + ], + declarations: [ + FilterVersionListComponent, + TestHostComponent + ], + }); + + fixture = TestBed.createComponent(TestHostComponent); + instance = fixture.componentInstance; + list = instance.componentUnderTest; + fixture.detectChanges(); + }); + + it('should compile', () => { + expect(list).toBeDefined(); + }); + + describe('compareSelected', () => { + it('should emit an event with the selected filter data', () => { + spyOn(instance, 'compare'); + list.selected = 'foo'; + fixture.detectChanges(); + list.compareSelected(); + fixture.detectChanges(); + expect(instance.compare).toHaveBeenCalled(); + }); + }); +}); diff --git a/ui/src/app/metadata/configuration/component/filter-version-list.component.ts b/ui/src/app/metadata/configuration/component/filter-version-list.component.ts new file mode 100644 index 000000000..2d2865d00 --- /dev/null +++ b/ui/src/app/metadata/configuration/component/filter-version-list.component.ts @@ -0,0 +1,40 @@ +import { Component, Input, Output, EventEmitter } from '@angular/core'; +import { FilterConfiguration, MetadataConfiguration } from '../model/metadata-configuration'; +import { CONFIG_DATE_FORMAT } from '../configuration.values'; +import { Observable } from 'rxjs'; +import { FilterComparison } from '../model/compare'; + +@Component({ + selector: 'filter-version-list', + templateUrl: './filter-version-list.component.html' +}) +export class FilterVersionListComponent { + + @Input() filters: FilterConfiguration; + @Output() compare: EventEmitter = new EventEmitter(); + + selected: string; + comparing: string; + selectedFilters$: Observable; + + DATE_FORMAT = CONFIG_DATE_FORMAT; + + constructor() {} + + compareSelected() { + const reduced = this.filters.filters.reduce((acc, l) => acc.concat(l), []); + const filtered = reduced.filter(f => f && f.resourceId === this.selected); + const type = filtered[0]['@type']; + + this.compare.emit({ + modelId: this.selected, + modelType: type, + models: filtered + }); + } + + get width(): string { + const columns = this.filters.dates.length; + return `${Math.floor(100 / (columns + 1))}`; + } +} diff --git a/ui/src/app/metadata/configuration/component/property/primitive-property.component.html b/ui/src/app/metadata/configuration/component/property/primitive-property.component.html index 5e582350d..58e114311 100644 --- a/ui/src/app/metadata/configuration/component/property/primitive-property.component.html +++ b/ui/src/app/metadata/configuration/component/property/primitive-property.component.html @@ -8,8 +8,12 @@ [translate]="property.name" [ngStyle]="{'width': width}">{{ property.name }} {{ v ? v : (v === false) ? v : '-' }} \ No newline at end of file diff --git a/ui/src/app/metadata/configuration/component/property/primitive-property.component.spec.ts b/ui/src/app/metadata/configuration/component/property/primitive-property.component.spec.ts index dbc3eed4f..bf893396e 100644 --- a/ui/src/app/metadata/configuration/component/property/primitive-property.component.spec.ts +++ b/ui/src/app/metadata/configuration/component/property/primitive-property.component.spec.ts @@ -2,7 +2,7 @@ import { Component, ViewChild, Input } from '@angular/core'; import { TestBed, async, ComponentFixture } from '@angular/core/testing'; import { RouterTestingModule } from '@angular/router/testing'; -import { NgbDropdownModule } from '@ng-bootstrap/ng-bootstrap'; +import { NgbDropdownModule, NgbPopoverModule } from '@ng-bootstrap/ng-bootstrap'; import { Property } from '../../../domain/model/property'; import { MockI18nModule } from '../../../../../testing/i18n.stub'; import { PrimitivePropertyComponent } from './primitive-property.component'; @@ -39,6 +39,7 @@ describe('Primitive Property Component', () => { TestBed.configureTestingModule({ imports: [ NgbDropdownModule, + NgbPopoverModule, MockI18nModule, RouterTestingModule ], diff --git a/ui/src/app/metadata/configuration/configuration.module.ts b/ui/src/app/metadata/configuration/configuration.module.ts index ae332ddf3..338c39ccb 100644 --- a/ui/src/app/metadata/configuration/configuration.module.ts +++ b/ui/src/app/metadata/configuration/configuration.module.ts @@ -44,6 +44,8 @@ import { RestoreEditComponent } from './container/restore-edit.component'; import { RestoreEditStepComponent } from './container/restore-edit-step.component'; import { IndexResolver } from './service/index-resolver.service'; +import { FilterVersionListComponent } from './component/filter-version-list.component'; +import { FilterCompareVersionEffects } from './effect/filter.effect'; @NgModule({ declarations: [ @@ -67,7 +69,8 @@ import { IndexResolver } from './service/index-resolver.service'; VersionOptionsComponent, MetadataEditorComponent, RestoreEditComponent, - RestoreEditStepComponent + RestoreEditStepComponent, + FilterVersionListComponent ], entryComponents: [], imports: [ @@ -109,6 +112,7 @@ export class MetadataConfigurationModule { MetadataHistoryEffects, CompareVersionEffects, RestoreEffects, + FilterCompareVersionEffects, VersionEffects ]) ], diff --git a/ui/src/app/metadata/configuration/container/metadata-comparison.component.html b/ui/src/app/metadata/configuration/container/metadata-comparison.component.html index 5c93f5ec7..effbefb0a 100644 --- a/ui/src/app/metadata/configuration/container/metadata-comparison.component.html +++ b/ui/src/app/metadata/configuration/container/metadata-comparison.component.html @@ -25,6 +25,32 @@

+ +
+

+ Metadata Filter +

+
+ + + + + +
+ +
+ +
+
+
diff --git a/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts b/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts index e080558e1..e8a905b0d 100644 --- a/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts +++ b/ui/src/app/metadata/configuration/container/metadata-comparison.component.ts @@ -1,12 +1,14 @@ import { Component, ChangeDetectionStrategy, OnDestroy } from '@angular/core'; -import { Observable, BehaviorSubject, Subscription, combineLatest } from 'rxjs'; +import { Observable, BehaviorSubject, Subscription } from 'rxjs'; import { Store } from '@ngrx/store'; import { ActivatedRoute } from '@angular/router'; import { map, withLatestFrom } from 'rxjs/operators'; import { ConfigurationState, getComparisonConfigurationCount } from '../reducer'; import { CompareVersionRequest, ClearVersions, ViewChanged } from '../action/compare.action'; -import { MetadataConfiguration } from '../model/metadata-configuration'; +import { MetadataConfiguration, FilterConfiguration } from '../model/metadata-configuration'; import * as fromReducer from '../reducer'; +import { CompareFilterVersions, ClearFilterComparison } from '../action/filter.action'; +import { FilterComparison } from '../model/compare'; @Component({ selector: 'metadata-comparison', @@ -24,6 +26,8 @@ export class MetadataComparisonComponent implements OnDestroy { loading$: Observable = this.store.select(fromReducer.getComparisonLoading); limited$: Observable = this.store.select(fromReducer.getViewChangedOnly); sub: Subscription; + filters$: Observable = this.store.select(fromReducer.getComparisonFilterConfiguration); + filterCompare$: Observable = this.store.select(fromReducer.getLimitedFilterComparisonConfiguration); constructor( private store: Store, @@ -45,8 +49,17 @@ export class MetadataComparisonComponent implements OnDestroy { ).subscribe(this.store); } + compareFilters (comparison: FilterComparison) { + this.store.dispatch(new CompareFilterVersions(comparison)); + } + + resetCompareFilters () { + this.store.dispatch(new ClearFilterComparison()); + } + ngOnDestroy(): void { this.sub.unsubscribe(); this.store.dispatch(new ClearVersions()); + this.resetCompareFilters(); } } diff --git a/ui/src/app/metadata/configuration/effect/filter.effect.ts b/ui/src/app/metadata/configuration/effect/filter.effect.ts new file mode 100644 index 000000000..215ac755e --- /dev/null +++ b/ui/src/app/metadata/configuration/effect/filter.effect.ts @@ -0,0 +1,63 @@ +import { Injectable } from '@angular/core'; +import { Effect, Actions, ofType } from '@ngrx/effects'; +import { + LoadFilterSchemaRequest, + CompareFilterVersions, + SetFilterComparisonDefinition, + LoadFilterSchemaSuccess, + LoadFilterSchemaError, + SetFilterComparisonSchema +} from '../action/filter.action'; +import { Store } from '@ngrx/store'; +import { State } from '../reducer'; +import { FilterCompareActionTypes } from '../action/filter.action'; +import { MetadataConfigurationService } from '../service/configuration.service'; +import { switchMap, map, catchError } from 'rxjs/operators'; +import { of } from 'rxjs'; + +@Injectable() +export class FilterCompareVersionEffects { + + @Effect() + setDefinition$ = this.actions$.pipe( + ofType(FilterCompareActionTypes.COMPARE_FILTERS), + map(action => action.payload), + map(comparison => { + const def = this.configService.getDefinition(comparison.modelType); + return new SetFilterComparisonDefinition(def); + }) + ); + + @Effect() + loadSchemaOnDefinitionSet$ = this.actions$.pipe( + ofType(FilterCompareActionTypes.SET_DEFINITION), + map(action => action.payload), + map(def => new LoadFilterSchemaRequest(def.schema)) + ); + + @Effect() + loadSchemaData$ = this.actions$.pipe( + ofType(FilterCompareActionTypes.LOAD_SCHEMA_REQUEST), + switchMap(action => + this.configService + .loadSchema(action.payload) + .pipe( + map(schema => new LoadFilterSchemaSuccess(schema)), + catchError(error => of(new LoadFilterSchemaError(error))) + ) + ) + ); + + @Effect() + loadSchemaSuccess$ = this.actions$.pipe( + ofType(FilterCompareActionTypes.LOAD_SCHEMA_SUCCESS), + map(action => action.payload), + map(schema => new SetFilterComparisonSchema(schema)) + ); + + constructor( + private configService: MetadataConfigurationService, + private store: Store, + private actions$: Actions + ) {} +} diff --git a/ui/src/app/metadata/configuration/model/compare.ts b/ui/src/app/metadata/configuration/model/compare.ts new file mode 100644 index 000000000..28038806a --- /dev/null +++ b/ui/src/app/metadata/configuration/model/compare.ts @@ -0,0 +1,8 @@ +import { Metadata } from '../../domain/domain.type'; +import { FilterVersion } from './version'; + +export interface FilterComparison { + modelId: string; + modelType: string; + models: FilterVersion[]; +} diff --git a/ui/src/app/metadata/configuration/model/history.ts b/ui/src/app/metadata/configuration/model/history.ts index 2fec132da..180502423 100644 --- a/ui/src/app/metadata/configuration/model/history.ts +++ b/ui/src/app/metadata/configuration/model/history.ts @@ -1,4 +1,4 @@ -import { MetadataVersion } from './version'; +import { MetadataVersion, FilterVersion } from './version'; export interface MetadataHistory { versions: MetadataVersion[]; diff --git a/ui/src/app/metadata/configuration/model/metadata-configuration.ts b/ui/src/app/metadata/configuration/model/metadata-configuration.ts index 13886d1a3..57316cdde 100644 --- a/ui/src/app/metadata/configuration/model/metadata-configuration.ts +++ b/ui/src/app/metadata/configuration/model/metadata-configuration.ts @@ -1,7 +1,12 @@ import { Section } from './section'; -import { Metadata } from '../../domain/domain.type'; +import { FilterVersion } from './version'; export interface MetadataConfiguration { sections: Section[]; dates: String[]; } + +export interface FilterConfiguration { + dates: string[]; + filters: FilterVersion[][]; +} diff --git a/ui/src/app/metadata/configuration/model/version.ts b/ui/src/app/metadata/configuration/model/version.ts index 0eb7cb815..bba3c7ced 100644 --- a/ui/src/app/metadata/configuration/model/version.ts +++ b/ui/src/app/metadata/configuration/model/version.ts @@ -3,3 +3,10 @@ export interface MetadataVersion { date: string; creator: string; } + +export interface FilterVersion { + resourceId: string; + name: string; + type: string; + comparable: boolean; +} diff --git a/ui/src/app/metadata/configuration/reducer/compare.reducer.ts b/ui/src/app/metadata/configuration/reducer/compare.reducer.ts index 84eec1558..ea0ef1598 100644 --- a/ui/src/app/metadata/configuration/reducer/compare.reducer.ts +++ b/ui/src/app/metadata/configuration/reducer/compare.reducer.ts @@ -6,13 +6,15 @@ export interface State { loaded: boolean; loading: boolean; compareChangedOnly: boolean; + filter: string; } export const initialState: State = { models: [], loaded: false, loading: false, - compareChangedOnly: false + compareChangedOnly: false, + filter: null }; export function reducer(state = initialState, action: CompareActionsUnion): State { @@ -53,3 +55,4 @@ export const getVersionModels = (state: State) => state.models; export const getVersionModelsLoaded = (state: State) => state.loaded; export const getComparisonLoading = (state: State) => state.loading; export const getViewChangedOnly = (state: State) => state.compareChangedOnly; +export const getFilterId = (state: State) => state.filter; diff --git a/ui/src/app/metadata/configuration/reducer/filter.reducer.spec.ts b/ui/src/app/metadata/configuration/reducer/filter.reducer.spec.ts new file mode 100644 index 000000000..ed9685cac --- /dev/null +++ b/ui/src/app/metadata/configuration/reducer/filter.reducer.spec.ts @@ -0,0 +1,93 @@ +import { reducer } from './filter.reducer'; +import * as fromFilterCompare from './filter.reducer'; +import { + SetFilterComparisonSchema, + ClearFilterComparison, + SetFilterComparisonDefinition, + CompareFilterVersions +} from '../action/filter.action'; +import { NameIDFilterConfiguration } from '../../filter/model/nameid-configuration.filter'; +import { FilterVersion } from '../model/version'; + +describe('Filter Comparison Reducer', () => { + const initialState: fromFilterCompare.State = { ...fromFilterCompare.initialState }; + + describe('undefined action', () => { + it('should return the default state', () => { + const result = reducer(undefined, {} as any); + + expect(result).toEqual(initialState); + }); + }); + + describe('SET_SCHEMA action', () => { + it('should add the provided schema to the state', () => { + const schema = {type: 'object', properties: { foo: { type: 'string' } }}; + const action = new SetFilterComparisonSchema(schema); + const result = reducer(initialState, action); + expect(result.schema).toEqual(schema); + }); + }); + + describe('SET_DEFINITION action', () => { + it('should add the provided definition to the state', () => { + const definition = NameIDFilterConfiguration; + const action = new SetFilterComparisonDefinition(definition); + const result = reducer(initialState, action); + expect(result.definition).toEqual(definition); + }); + }); + + describe('COMPARE_FILTERS action', () => { + it('should add model information to the state', () => { + const request = { + modelId: 'foo', + modelType: 'EntityAttributesFilter', + models: [{}, {}] as FilterVersion[] + }; + const action = new CompareFilterVersions(request); + const result = reducer(initialState, action); + expect(result.modelId).toEqual(request.modelId); + expect(result.modelType).toEqual(request.modelType); + expect(result.models).toEqual(request.models); + }); + }); + + describe('CLEAR action', () => { + it('should reset to the initial state', () => { + const action = new ClearFilterComparison(); + const result = reducer(initialState, action); + expect(result).toEqual(initialState); + }); + }); + + describe('selector functions', () => { + describe('getModel', () => { + it('should retrieve the model from state', () => { + const models = [{}, {}] as FilterVersion[]; + expect(fromFilterCompare.getModelId({ ...initialState, modelId: 'foo' })).toBe('foo'); + expect(fromFilterCompare.getModelType({ ...initialState, modelType: 'foo' })).toBe('foo'); + expect(fromFilterCompare.getModels({ ...initialState, models })).toBe(models); + }); + }); + describe('getLoading', () => { + it('should retrieve the loading state', () => { + expect(fromFilterCompare.getLoading({ ...initialState, loading: true })).toBe(true); + }); + }); + + describe('getSchema', () => { + it('should retrieve the schema from state', () => { + const schema = {}; + expect(fromFilterCompare.getSchema({ ...initialState, schema })).toBe(schema); + }); + }); + + describe('getDefinition', () => { + it('should retrieve the definition from state', () => { + const definition = NameIDFilterConfiguration; + expect(fromFilterCompare.getDefinition({ ...initialState, definition })).toBe(NameIDFilterConfiguration); + }); + }); + }); +}); diff --git a/ui/src/app/metadata/configuration/reducer/filter.reducer.ts b/ui/src/app/metadata/configuration/reducer/filter.reducer.ts new file mode 100644 index 000000000..445038ab8 --- /dev/null +++ b/ui/src/app/metadata/configuration/reducer/filter.reducer.ts @@ -0,0 +1,57 @@ +import { FilterCompareActionTypes, FilterCompareActionsUnion } from '../action/filter.action'; +import { Metadata } from '../../domain/domain.type'; +import { FormDefinition } from '../../../wizard/model'; +import { Schema } from '../model/schema'; +import { FilterVersion } from '../model/version'; + +export interface State { + models: FilterVersion[]; + modelType: string; + modelId: string; + schema: Schema; + definition: FormDefinition; + loading: boolean; +} + +export const initialState: State = { + models: null, + modelType: null, + modelId: null, + schema: null, + definition: null, + loading: false +}; + +export function reducer(state = initialState, action: FilterCompareActionsUnion): State { + switch (action.type) { + case FilterCompareActionTypes.SET_SCHEMA: + return { + ...state, + schema: action.payload + }; + case FilterCompareActionTypes.SET_DEFINITION: + return { + ...state, + definition: action.payload + }; + case FilterCompareActionTypes.COMPARE_FILTERS: + return { + ...state, + ...action.payload + }; + case FilterCompareActionTypes.CLEAR: + return { + ...initialState + }; + default: { + return state; + } + } +} + +export const getModels = (state: State) => state.models; +export const getModelType = (state: State) => state.modelType; +export const getModelId = (state: State) => state.modelId; +export const getDefinition = (state: State) => state.definition; +export const getSchema = (state: State) => state.schema; +export const getLoading = (state: State) => state.loading; diff --git a/ui/src/app/metadata/configuration/reducer/index.ts b/ui/src/app/metadata/configuration/reducer/index.ts index 2b67b7a7a..13daf9b34 100644 --- a/ui/src/app/metadata/configuration/reducer/index.ts +++ b/ui/src/app/metadata/configuration/reducer/index.ts @@ -6,6 +6,7 @@ import * as fromHistory from './history.reducer'; import * as fromCompare from './compare.reducer'; import * as fromVersion from './version.reducer'; import * as fromRestore from './restore.reducer'; +import * as fromFilter from './filter.reducer'; import { WizardStep } from '../../../wizard/model'; import * as utils from '../../domain/utility/configuration'; @@ -24,6 +25,7 @@ export interface ConfigurationState { compare: fromCompare.State; version: fromVersion.State; restore: fromRestore.RestoreState; + filter: fromFilter.State; } export const reducers = { @@ -31,7 +33,8 @@ export const reducers = { history: fromHistory.reducer, compare: fromCompare.reducer, version: fromVersion.reducer, - restore: fromRestore.reducer + restore: fromRestore.reducer, + filter: fromFilter.reducer }; export interface State extends fromRoot.State { @@ -45,6 +48,7 @@ export const getHistoryStateFn = (state: ConfigurationState) => state.history; export const getCompareStateFn = (state: ConfigurationState) => state.compare; export const getVersionStateFn = (state: ConfigurationState) => state.version; export const getRestoreStateFn = (state: ConfigurationState) => state.restore; +export const getFilterStateFn = (state: ConfigurationState) => state.filter; export const getConfigurationState = createSelector(getState, getConfigurationStateFn); export const getConfigurationModelKind = createSelector(getConfigurationState, fromConfiguration.getModelKind); @@ -180,12 +184,14 @@ export const getSelectedIsCurrent = createSelector( getSelectedIsCurrentFn ); + // Version Comparison export const getCompareState = createSelector(getState, getCompareStateFn); export const getComparisonLoading = createSelector(getCompareState, fromCompare.getComparisonLoading); export const getComparisonModels = createSelector(getCompareState, fromCompare.getVersionModels); export const getComparisonModelsLoaded = createSelector(getCompareState, fromCompare.getVersionModelsLoaded); +export const getComparisonFilterId = createSelector(getCompareState, fromCompare.getFilterId); export const getComparisonModelsFilteredFn = (models) => models.map((model) => ({ ...model, @@ -240,14 +246,59 @@ export const getLimitedComparisonConfigurations = createSelector( getLimitedConfigurationsFn ); -// Version Restoration - -export const getRestoreState = createSelector(getState, getRestoreStateFn); +export const getComparisonFilterListFn = (models) => models.map(m => getVersionModelFiltersFn(m, 'provider')); +export const getComparisonFilterList = createSelector(getComparisonModels, getComparisonFilterListFn); + +export const getComparisonDatesFn = (config) => config.map(m => m ? m.modifiedDate : null); +export const getComparisonDates = createSelector(getComparisonModels, getComparisonDatesFn); + +export const getComparisonFilterOrderedFn = (list) => + list.map(models => + models.map(filter => + ({ + ...filter, + comparable: list + .reduce((acc, v) => acc.concat(v), []) + .map(v => v.resourceId) + .some((id, index, coll) => { + return coll.indexOf(filter.resourceId) !== coll.lastIndexOf(filter.resourceId); + }) + }) +)); + +export const getComparisonFilterOrdered = createSelector(getComparisonFilterList, getComparisonFilterOrderedFn); + +export const getComparisonFilterConfiguration = createSelector( + getComparisonFilterOrdered, + getComparisonDates, + (filters, dates) => { + const rows = filters.reduce((num, version) => version.length > num ? version.length : num, 0); + const range = [...Array(rows).keys()]; + return { + dates, + filters: range.reduce((collection, index) => { + const val = filters.map(version => version[index]); + collection[index] = val; + return collection; + }, []) + }; + } +); +export const getComparisonSelectedFilters = createSelector( + getComparisonModels, + getComparisonDates, + getComparisonFilterId, + (models, dates, id) => ({ + dates, + sections: [] + }) +); +// Version Restoration +export const getRestoreState = createSelector(getState, getRestoreStateFn); export const getVersionState = createSelector(getState, getVersionStateFn); - export const getVersionLoading = createSelector(getVersionState, fromVersion.isVersionLoading); export const getVersionModel = createSelector(getVersionState, fromVersion.getVersionModel); @@ -305,6 +356,25 @@ export const getRestorationModel = createSelector( }) ); +// Filter Comparison State + +export const getFilterState = createSelector(getState, getFilterStateFn); +export const getFilterComparisonDefinition = createSelector(getFilterState, fromFilter.getDefinition); +export const getFilterComparisonSchema = createSelector(getFilterState, fromFilter.getSchema); +export const getFilterComparisonModels = createSelector(getFilterState, fromFilter.getModels); +export const getFilterComparisonConfigurations = createSelector( + getFilterComparisonModels, + getFilterComparisonDefinition, + getFilterComparisonSchema, + getConfigurationSectionsFn +); + +export const getLimitedFilterComparisonConfiguration = createSelector( + getFilterComparisonConfigurations, + getViewChangedOnly, + getLimitedConfigurationsFn +); + // Mixed states export const getConfigurationModelFn = (kind, version, provider, resolver) => { diff --git a/ui/src/theme/utility.scss b/ui/src/theme/utility.scss index 0f8bea642..4fc7539cc 100644 --- a/ui/src/theme/utility.scss +++ b/ui/src/theme/utility.scss @@ -6,6 +6,10 @@ background: #FAFAFA !important; } +.bg-primary-light { + background: lighten($brand-primary, 75%); +} + .w-15 { width: 15%; }