-
Loading...
diff --git a/ui/src/app/metadata/configuration/effect/restore.effect.ts b/ui/src/app/metadata/configuration/effect/restore.effect.ts
index 7be47103e..f15f21569 100644
--- a/ui/src/app/metadata/configuration/effect/restore.effect.ts
+++ b/ui/src/app/metadata/configuration/effect/restore.effect.ts
@@ -30,6 +30,8 @@ import {
import { SetMetadata } from '../action/configuration.action';
import { removeNulls } from '../../../shared/util';
import { getModel } from '../../../wizard/reducer';
+import { ClearProviderSelection } from '../../provider/action/collection.action';
+import { ClearResolverSelection } from '../../resolver/action/collection.action';
@Injectable()
@@ -107,6 +109,15 @@ export class RestoreEffects {
)
);
+ @Effect()
+ restoreVersionSuccessClear$ = this.actions$.pipe(
+ ofType(RestoreActionTypes.RESTORE_VERSION_SUCCESS),
+ map(action => action.payload),
+ map(({ id, type }) =>
+ type === 'provider' ? new ClearProviderSelection() : new ClearResolverSelection()
+ )
+ );
+
@Effect()
restoreVersionSuccessReload$ = this.actions$.pipe(
ofType(RestoreActionTypes.RESTORE_VERSION_SUCCESS),
diff --git a/ui/src/app/metadata/domain/entity/filter/entity-attributes-filter.spec.ts b/ui/src/app/metadata/domain/entity/filter/entity-attributes-filter.spec.ts
index 22eae4cda..bb544cf0d 100644
--- a/ui/src/app/metadata/domain/entity/filter/entity-attributes-filter.spec.ts
+++ b/ui/src/app/metadata/domain/entity/filter/entity-attributes-filter.spec.ts
@@ -14,8 +14,8 @@ describe('EntityAttributesFilter Entity', () => {
expect(entity.resourceId).toBe('foo');
expect(entity.enabled).toBe(entity.filterEnabled);
expect(entity.id).toBe(entity.resourceId);
- expect(entity.getId()).toBe(entity.entityId);
- expect(entity.getDisplayId()).toBe(entity.entityId);
+ expect(entity.getId()).toBe(entity.resourceId);
+ expect(entity.getDisplayId()).toBe(entity.resourceId);
expect(entity.isDraft()).toBe(false);
});
});
diff --git a/ui/src/app/metadata/domain/entity/filter/entity-attributes-filter.ts b/ui/src/app/metadata/domain/entity/filter/entity-attributes-filter.ts
index 839324879..f536a5b38 100644
--- a/ui/src/app/metadata/domain/entity/filter/entity-attributes-filter.ts
+++ b/ui/src/app/metadata/domain/entity/filter/entity-attributes-filter.ts
@@ -31,11 +31,11 @@ export class EntityAttributesFilterEntity implements MetadataFilter, MetadataEnt
}
getId(): string {
- return this.entityId;
+ return this.resourceId;
}
getDisplayId(): string {
- return this.entityId;
+ return this.resourceId;
}
isDraft(): boolean {
diff --git a/ui/src/app/metadata/domain/entity/filter/nameid-format-filter.spec.ts b/ui/src/app/metadata/domain/entity/filter/nameid-format-filter.spec.ts
new file mode 100644
index 000000000..ba39ffbe2
--- /dev/null
+++ b/ui/src/app/metadata/domain/entity/filter/nameid-format-filter.spec.ts
@@ -0,0 +1,21 @@
+import { NameIDFormatFilterEntity } from './nameid-format-filter';
+
+describe('NameIDFormatFilterEntity Entity', () => {
+ let entity: NameIDFormatFilterEntity;
+ beforeEach(() => {
+ entity = new NameIDFormatFilterEntity({
+ resourceId: 'foo',
+ filterEnabled: false
+ });
+ });
+
+ it('should be an instance', () => {
+ expect(entity).toBeDefined();
+ expect(entity.resourceId).toBe('foo');
+ expect(entity.enabled).toBe(entity.filterEnabled);
+ expect(entity.id).toBe(entity.resourceId);
+ expect(entity.getId()).toBe(entity.resourceId);
+ expect(entity.getDisplayId()).toBe(entity.resourceId);
+ expect(entity.isDraft()).toBe(false);
+ });
+});
diff --git a/ui/src/app/metadata/filter/model/entity-attributes.filter.spec.ts b/ui/src/app/metadata/filter/model/entity-attributes.filter.spec.ts
index 53e3630c8..a5505284d 100644
--- a/ui/src/app/metadata/filter/model/entity-attributes.filter.spec.ts
+++ b/ui/src/app/metadata/filter/model/entity-attributes.filter.spec.ts
@@ -5,7 +5,8 @@ describe('Entity Attributes filter form', () => {
it('should return an empty object for validators', () => {
expect(Object.keys(EntityAttributesFilter.getValidators())).toEqual([
'/',
- '/name'
+ '/name',
+ '/entityAttributesFilterTarget'
]);
});
diff --git a/ui/src/app/metadata/filter/model/entity-attributes.filter.ts b/ui/src/app/metadata/filter/model/entity-attributes.filter.ts
index a2642ddc9..f1d58d915 100644
--- a/ui/src/app/metadata/filter/model/entity-attributes.filter.ts
+++ b/ui/src/app/metadata/filter/model/entity-attributes.filter.ts
@@ -2,7 +2,11 @@ import { FormDefinition } from '../../../wizard/model';
import { MetadataFilter } from '../../domain/model';
import { removeNulls } from '../../../shared/util';
import { EntityAttributesFilterEntity } from '../../domain/entity';
+import { RegexValidator } from '../../../shared/validation/regex.validator';
import { getFilterNames } from '../reducer';
+import { memoize } from '../../../shared/memo';
+
+const checkRegex = memoize(RegexValidator.isValidRegex);
export const EntityAttributesFilter: FormDefinition = {
label: 'EntityAttributes',
@@ -37,6 +41,18 @@ export const EntityAttributesFilter: FormDefinition = {
params: [value]
} : null;
return err;
+ },
+ '/entityAttributesFilterTarget': (value, property, form) => {
+ if (!form || !form.value || !form.value.entityAttributesFilterTarget ||
+ form.value.entityAttributesFilterTarget.entityAttributesFilterTargetType !== 'REGEX') {
+ return null;
+ }
+ return checkRegex(value.value[0]) ? null : {
+ code: 'INVALID_REGEX',
+ path: `#${property.path}`,
+ message: 'message.invalid-regex-pattern',
+ params: [value.value[0]]
+ };
}
};
return validators;
diff --git a/ui/src/app/metadata/filter/model/nameid.filter.spec.ts b/ui/src/app/metadata/filter/model/nameid.filter.spec.ts
index f366ca90a..e3d3aca36 100644
--- a/ui/src/app/metadata/filter/model/nameid.filter.spec.ts
+++ b/ui/src/app/metadata/filter/model/nameid.filter.spec.ts
@@ -5,7 +5,8 @@ describe('NameID Format filter form', () => {
it('should return an empty object for validators', () => {
expect(Object.keys(NameIDFilter.getValidators())).toEqual([
'/',
- '/name'
+ '/name',
+ '/nameIdFormatFilterTarget'
]);
});
diff --git a/ui/src/app/metadata/filter/model/nameid.filter.ts b/ui/src/app/metadata/filter/model/nameid.filter.ts
index 9751d4ce9..ee48f4f89 100644
--- a/ui/src/app/metadata/filter/model/nameid.filter.ts
+++ b/ui/src/app/metadata/filter/model/nameid.filter.ts
@@ -1,7 +1,11 @@
import { FormDefinition } from '../../../wizard/model';
import { MetadataFilter } from '../../domain/model';
import { NameIDFormatFilterEntity } from '../../domain/entity/filter/nameid-format-filter';
+import { RegexValidator } from '../../../shared/validation/regex.validator';
import { getFilterNames } from '../reducer';
+import { memoize } from '../../../shared/memo';
+
+const checkRegex = memoize(RegexValidator.isValidRegex);
export const NameIDFilter: FormDefinition = {
label: 'NameIDFormat',
@@ -36,6 +40,18 @@ export const NameIDFilter: FormDefinition = {
params: [value]
} : null;
return err;
+ },
+ '/nameIdFormatFilterTarget': (value, property, form) => {
+ if (!form || !form.value || !form.value.nameIdFormatFilterTarget ||
+ form.value.nameIdFormatFilterTarget.nameIdFormatFilterTargetType !== 'REGEX') {
+ return null;
+ }
+ return checkRegex(value.value[0]) ? null : {
+ code: 'INVALID_REGEX',
+ path: `#${property.path}`,
+ message: 'message.invalid-regex-pattern',
+ params: [value.value[0]]
+ };
}
};
return validators;
diff --git a/ui/src/app/metadata/provider/reducer/index.spec.ts b/ui/src/app/metadata/provider/reducer/index.spec.ts
new file mode 100644
index 000000000..7ffb3e9db
--- /dev/null
+++ b/ui/src/app/metadata/provider/reducer/index.spec.ts
@@ -0,0 +1,24 @@
+import { getFilteredListFn } from './';
+
+describe('Provider Reducer selectors', () => {
+ describe(`getFilteredListFn method`, () => {
+ it('should return a list without the provider`s property', () => {
+
+ const fn = getFilteredListFn('name');
+ const name = 'foo';
+ const collection = ['foo', 'bar', 'baz'];
+ const provider = { name };
+
+ expect(fn(collection, provider)).toEqual(['bar', 'baz']);
+ });
+
+ it('should return the list if the provider passed is null', () => {
+
+ const fn = getFilteredListFn('name');
+ const name = 'foo';
+ const collection = ['foo', 'bar', 'baz'];
+
+ expect(fn(collection, null)).toEqual(['foo', 'bar', 'baz']);
+ });
+ });
+});
diff --git a/ui/src/app/metadata/provider/reducer/index.ts b/ui/src/app/metadata/provider/reducer/index.ts
index 562a4ab44..8005d63e9 100644
--- a/ui/src/app/metadata/provider/reducer/index.ts
+++ b/ui/src/app/metadata/provider/reducer/index.ts
@@ -66,11 +66,11 @@ export const getProviderIds = createSelector(getCollectionState, fromCollection.
export const getProviderCollectionIsLoaded = createSelector(getCollectionState, fromCollection.getIsLoaded);
export const getProviderNames = createSelector(getAllProviders, (providers: MetadataProvider[]) => providers.map(p => p.name));
-export const getFilteredProviderNames = createSelector(
- getProviderNames,
- getSelectedProvider, (names, provider) => names.filter(name => name !== provider.name)
-);
+export const getFilteredListFn = (property: string) =>
+ (collection, provider) => !provider ? collection : collection.filter(val => val !== provider[property]);
+
+export const getFilteredProviderNames = createSelector(getProviderNames, getSelectedProvider, getFilteredListFn('name'));
export const getProviderFilters = createSelector(getSelectedProvider, provider => provider.metadataFilters);
@@ -78,7 +78,4 @@ export const getProviderXmlIds = createSelector(getAllProviders, (providers: Met
export const getOrderedProviders = createSelector(getAllProviders, getProviderOrder, utils.mergeOrderFn);
export const getOrderedProvidersInSearch = createSelector(getAllProviders, getProviderOrder, utils.mergeOrderFn);
-export const getFilteredProviderXmlIds = createSelector(
- getProviderXmlIds,
- getSelectedProvider, (ids, provider) => ids.filter(id => id !== provider.xmlId)
-);
+export const getFilteredProviderXmlIds = createSelector(getProviderXmlIds, getSelectedProvider, getFilteredListFn('xmlId'));
diff --git a/ui/src/app/metadata/resolver/action/collection.action.ts b/ui/src/app/metadata/resolver/action/collection.action.ts
index dc3843319..57861b17c 100644
--- a/ui/src/app/metadata/resolver/action/collection.action.ts
+++ b/ui/src/app/metadata/resolver/action/collection.action.ts
@@ -7,6 +7,8 @@ export enum ResolverCollectionActionTypes {
SELECT = '[Metadata Resolver] Select',
SELECT_SUCCESS = '[Metadata Resolver] Select Success',
+ CLEAR_SELECTION = '[Metadata Resolver] Selection Clear',
+
UPDATE_RESOLVER_REQUEST = '[Metadata Resolver] Update Request',
UPDATE_RESOLVER_SUCCESS = '[Metadata Resolver] Update Success',
UPDATE_RESOLVER_FAIL = '[Metadata Resolver] Update Fail',
@@ -143,6 +145,10 @@ export class CreateResolverFromUrlRequest implements Action {
constructor(public payload: { name: string, url: string }) { }
}
+export class ClearResolverSelection implements Action {
+ readonly type = ResolverCollectionActionTypes.CLEAR_SELECTION;
+}
+
export type ResolverCollectionActionsUnion =
| LoadResolverRequest
| LoadResolverSuccess
@@ -162,4 +168,5 @@ export type ResolverCollectionActionsUnion =
| UpdateResolverFail
| UpdateResolverConflict
| UploadResolverRequest
- | CreateResolverFromUrlRequest;
+ | CreateResolverFromUrlRequest
+ | ClearResolverSelection;
diff --git a/ui/src/app/metadata/resolver/effect/collection.effects.ts b/ui/src/app/metadata/resolver/effect/collection.effects.ts
index ff5c5ea9c..2a92b0242 100644
--- a/ui/src/app/metadata/resolver/effect/collection.effects.ts
+++ b/ui/src/app/metadata/resolver/effect/collection.effects.ts
@@ -35,6 +35,7 @@ import { I18nService } from '../../../i18n/service/i18n.service';
import * as fromRoot from '../../../app.reducer';
import * as fromI18n from '../../../i18n/reducer';
import { FileBackedHttpMetadataResolver } from '../../domain/entity';
+import { UpdateSaving } from '../action/entity.action';
/* istanbul ignore next */
@@ -79,6 +80,21 @@ export class ResolverCollectionEffects {
})
);
+ @Effect()
+ updateResolverSavingOnRequest$ = this.actions$.pipe(
+ ofType(ResolverCollectionActionTypes.UPDATE_RESOLVER_REQUEST),
+ map(action => new UpdateSaving(true)),
+ );
+
+ @Effect()
+ updateResolverSavingOnResult$ = this.actions$.pipe(
+ ofType(
+ ResolverCollectionActionTypes.UPDATE_RESOLVER_SUCCESS,
+ ResolverCollectionActionTypes.UPDATE_RESOLVER_FAIL
+ ),
+ map(action => new UpdateSaving(false)),
+ );
+
@Effect({ dispatch: false })
updateResolverSuccessRedirect$ = this.actions$.pipe(
ofType(ResolverCollectionActionTypes.UPDATE_RESOLVER_SUCCESS),
diff --git a/ui/src/app/metadata/resolver/reducer/collection.reducer.ts b/ui/src/app/metadata/resolver/reducer/collection.reducer.ts
index 93118aec3..425e2dee6 100644
--- a/ui/src/app/metadata/resolver/reducer/collection.reducer.ts
+++ b/ui/src/app/metadata/resolver/reducer/collection.reducer.ts
@@ -51,6 +51,13 @@ export function reducer(state = initialState, action: ResolverCollectionActionsU
});
}
+ case ResolverCollectionActionTypes.CLEAR_SELECTION: {
+ return {
+ ...state,
+ selectedResolverId: null
+ };
+ }
+
default: {
return state;
}
diff --git a/ui/src/app/schema-form/widget/array/array.component.ts b/ui/src/app/schema-form/widget/array/array.component.ts
index 18f93566e..82a089635 100644
--- a/ui/src/app/schema-form/widget/array/array.component.ts
+++ b/ui/src/app/schema-form/widget/array/array.component.ts
@@ -9,9 +9,9 @@ export interface FormError {
code: string;
description: string;
message: string;
- params: any[];
+ params?: any[];
path: string;
- schemaId: any;
+ schemaId?: any;
}
@Component({
diff --git a/ui/src/app/schema-form/widget/filter-target/filter-target.component.html b/ui/src/app/schema-form/widget/filter-target/filter-target.component.html
index ef3a7be4a..fcbd80ab8 100644
--- a/ui/src/app/schema-form/widget/filter-target/filter-target.component.html
+++ b/ui/src/app/schema-form/widget/filter-target/filter-target.component.html
@@ -69,11 +69,21 @@
-
-
+
+
Required for Regex
+
+
+ ,
+ {{ error.message }}
+
+
diff --git a/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts b/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts
index 4107ebfd3..7af0c8b0d 100644
--- a/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts
+++ b/ui/src/app/schema-form/widget/filter-target/filter-target.component.ts
@@ -2,8 +2,8 @@ import { Component, OnDestroy, AfterViewInit } from '@angular/core';
import { FormControl, Validators, AbstractControl, ValidatorFn } from '@angular/forms';
import { ObjectWidget } from 'ngx-schema-form';
import { Store } from '@ngrx/store';
-import { Observable } from 'rxjs';
-import { distinctUntilChanged, skipWhile, map } from 'rxjs/operators';
+import { Observable, Subject } from 'rxjs';
+import { distinctUntilChanged, skipWhile, takeUntil, map } from 'rxjs/operators';
import * as fromRoot from '../../../app.reducer';
import * as fromFilters from '../../../metadata/filter/reducer';
@@ -17,7 +17,7 @@ import { QueryEntityIds, ClearSearch } from '../../../metadata/filter/action/sea
styleUrls: ['./filter-target.component.scss']
})
export class FilterTargetComponent extends ObjectWidget implements OnDestroy, AfterViewInit {
-
+ private ngUnsubscribe: Subject
= new Subject();
ids$: Observable;
ids: string[];
@@ -32,6 +32,9 @@ export class FilterTargetComponent extends ObjectWidget implements OnDestroy, Af
[Validators.required]
);
+ errors$: Observable;
+ hasErrors$: Observable;
+
constructor(
private store: Store
) {
@@ -42,6 +45,7 @@ export class FilterTargetComponent extends ObjectWidget implements OnDestroy, Af
this.search
.valueChanges
.pipe(
+ takeUntil(this.ngUnsubscribe),
distinctUntilChanged()
)
.subscribe(query => this.searchEntityIds(query));
@@ -49,6 +53,7 @@ export class FilterTargetComponent extends ObjectWidget implements OnDestroy, Af
this.script
.valueChanges
.pipe(
+ takeUntil(this.ngUnsubscribe),
distinctUntilChanged(),
skipWhile(() => this.targetType === 'ENTITY')
)
@@ -61,6 +66,31 @@ export class FilterTargetComponent extends ObjectWidget implements OnDestroy, Af
super.ngAfterViewInit();
this.script.setValue(this.targets[0]);
this.search.setValidators(this.unique());
+
+ this.errors$ = this.formProperty.errorsChanges.pipe(
+ map(errors =>
+ errors && errors.length > 1 ?
+ Array
+ .from(new Set(errors.filter(e => e.code !== 'ARRAY_LENGTH_SHORT').map(e => e.code)))
+ .map(id => ({ ...errors.find(e => e.code === id) }))
+ : []
+ ));
+
+ this.errors$
+ .pipe(
+ takeUntil(this.ngUnsubscribe),
+ map(errors => errors.reduce((collection, e) => ({ ...collection, [e.code]: e.message }), {})),
+ map(errors => Object.keys(errors).length > 0 ? errors : null)
+ )
+ .subscribe(errors => this.script.setErrors(
+ errors,
+ {
+ emitEvent: true
+ }
+ )
+ );
+
+ this.hasErrors$ = this.errors$.pipe(map(e => e && e.length > 0));
}
unique(): ValidatorFn {
@@ -138,5 +168,7 @@ export class FilterTargetComponent extends ObjectWidget implements OnDestroy, Af
ngOnDestroy(): void {
this.store.dispatch(new ClearSearch());
+ this.ngUnsubscribe.next();
+ this.ngUnsubscribe.complete();
}
}
diff --git a/ui/src/app/shared/memo.spec.ts b/ui/src/app/shared/memo.spec.ts
new file mode 100644
index 000000000..01fcd9898
--- /dev/null
+++ b/ui/src/app/shared/memo.spec.ts
@@ -0,0 +1,21 @@
+import { memoize } from './memo';
+
+const fns = {
+ square(n) {
+ return n * n;
+ }
+};
+
+describe('memoize function', () => {
+ it('should return a memoized function', () => {
+ spyOn(fns, 'square').and.callThrough();
+ const memoized = memoize(fns.square);
+ const call1 = memoized(1);
+ const call2 = memoized(2);
+ const call3 = memoized(2);
+ expect(call1).toBe(1);
+ expect(call2).toBe(4);
+ expect(call3).toBe(4);
+ expect(fns.square).toHaveBeenCalledTimes(2);
+ });
+});
diff --git a/ui/src/app/shared/memo.ts b/ui/src/app/shared/memo.ts
new file mode 100644
index 000000000..3f719142a
--- /dev/null
+++ b/ui/src/app/shared/memo.ts
@@ -0,0 +1,15 @@
+export function memoize(func) {
+ const cache = {};
+ return function (...args: any[]) {
+ const key = JSON.stringify(args);
+ if (cache[key]) {
+ return cache[key];
+ } else {
+ const val = func.apply(null, args);
+ cache[key] = val;
+ return val;
+ }
+ };
+}
+
+export default { memoize };
diff --git a/ui/src/app/shared/validation/regex.validator.spec.ts b/ui/src/app/shared/validation/regex.validator.spec.ts
new file mode 100644
index 000000000..ce8af17ee
--- /dev/null
+++ b/ui/src/app/shared/validation/regex.validator.spec.ts
@@ -0,0 +1,18 @@
+import { RegexValidator } from './regex.validator';
+
+describe('RegexValidator', () => {
+ describe('isValidRegex method', () => {
+ it('should return true if no error is thrown', () => {
+ expect(RegexValidator.isValidRegex('/abc/')).toBe(true);
+ expect(RegexValidator.isValidRegex('/*123/')).toBe(true);
+ });
+
+ it('should return false if an error is thrown trying to construct a regex', () => {
+ expect(RegexValidator.isValidRegex(')')).toBe(false);
+ });
+
+ it('should return false if the regex doesnt begin and end with slashes', () => {
+ expect(RegexValidator.isValidRegex('abc')).toBe(false);
+ });
+ });
+});
diff --git a/ui/src/app/shared/validation/regex.validator.ts b/ui/src/app/shared/validation/regex.validator.ts
new file mode 100644
index 000000000..be567a0f4
--- /dev/null
+++ b/ui/src/app/shared/validation/regex.validator.ts
@@ -0,0 +1,18 @@
+const regexChecker = new RegExp('^\/|\/$', 'g');
+
+export class RegexValidator {
+ static isValidRegex(pattern: string): boolean {
+ if (!pattern) {
+ return true;
+ }
+ let regex;
+ try {
+ regex = new RegExp(pattern);
+ } catch (err) {
+ return false;
+ }
+ return regexChecker.test(pattern);
+ }
+}
+
+export default RegexValidator;