blob: 1a461e08b88cb1de8c9ac876393a3f4be7c335ac [file] [log] [blame]
/********************************************************************************
* Copyright (c) 2015-2020 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
********************************************************************************/
import {Injectable} from '@angular/core';
import {Http} from '@angular/http';
import {forkJoin as observableForkJoin, of as observableOf, Observable} from 'rxjs';
import {catchError, mergeMap, publishReplay, refCount, map} from 'rxjs/operators';
import {MDMNotificationService} from '../core/mdm-notification.service';
import {PropertyService} from '../core/property.service';
import {LocalizationService} from '../localization/localization.service';
import { Preference, PreferenceService, Scope } from '../core/preference.service';
import {HttpErrorHandler} from '../core/http-error-handler';
import {SearchFilter, Condition, OperatorUtil} from './filter.service';
import {Query, Filter} from '../tableview/query.service';
import {View} from '../tableview/tableview.service';
import { TranslateService } from '@ngx-translate/core';
export class SearchLayout {
map: { [sourceNames: string]: Condition[] } = {};
public static createSearchLayout(envs: string[], attributesPerEnv: { [env: string]: SearchAttribute[] }, conditions: Condition[]) {
let conditionsWithSortIndex = conditions.map((c, i) => { c.sortIndex = i; return c; });
let result = new SearchLayout();
let attribute2Envs = SearchLayout.mapAttribute2Environments(envs, attributesPerEnv);
let globalEnv = 'Global';
Object.keys(attribute2Envs).forEach(attr => {
let c = conditionsWithSortIndex.find(cond => cond.type + '.' + cond.attribute === attr);
if (c) {
if (attribute2Envs[attr].length === envs.length) {
result.add(globalEnv, c);
} else {
attribute2Envs[attr].forEach(e => result.add(e, c));
}
}
});
return result;
}
public static groupByEnv(attrs: SearchAttribute[]) {
let attributesPerEnv: { [environment: string]: SearchAttribute[] } = {};
attrs.forEach(attr => {
attributesPerEnv[attr.source] = attributesPerEnv[attr.source] || [];
attributesPerEnv[attr.source].push(attr);
});
return attributesPerEnv;
}
private static mapAttribute2Environments(envs: string[], attributesPerEnv: { [environment: string]: SearchAttribute[] }) {
let attribute2Envs: { [attribute: string]: string[] } = {};
Object.keys(attributesPerEnv)
.filter(env => envs.find(e => e === env))
.forEach(env =>
attributesPerEnv[env].forEach(sa => {
let attr = sa.boType + '.' + sa.attrName;
attribute2Envs[attr] = attribute2Envs[attr] || [];
attribute2Envs[attr].push(env);
})
);
return attribute2Envs;
}
getSourceNames() {
return Object.keys(this.map).sort((s1, s2) => {
if (s1 === 'Global') {
return -1;
} else if ( s2 === 'Global') {
return 1;
} else if (s1) {
return s1.localeCompare(s2);
} else {
return -1;
}
});
}
getConditions(sourceName: string) {
return this.map[sourceName].sort((c1, c2) => c1.sortIndex - c2.sortIndex) || [];
}
getSourceName(condition: Condition) {
if (condition) {
let sourceName;
Object.keys(this.map)
.forEach(env => {
if (this.map[env].find(c => c.type === condition.type && c.attribute === condition.attribute)) {
sourceName = env;
}
}
);
return sourceName;
}
}
set(sourceName: string, conditions: Condition[]) {
this.map[sourceName] = conditions;
}
add(sourceName: string, condition: Condition) {
this.map[sourceName] = this.map[sourceName] || [];
this.map[sourceName].push(condition);
}
}
export class SearchAttribute {
source: string;
boType: string;
attrName: string;
valueType: string;
criteria: string;
constructor(source: string, boType: string, attrName: string, valueType?: string, criteria?: string) {
this.source = source;
this.boType = boType;
this.attrName = attrName;
this.valueType = valueType || 'string';
this.criteria = criteria || '';
}
}
export class SearchDefinition {
key: string;
value: string;
type: string;
label: string;
}
@Injectable()
export class SearchService {
private _searchUrl: string;
private errorMessage: string;
private defs: SearchAttribute[];
ignoreAttributesPrefs: Preference[] = [];
private cachedAttributes: Observable<any>;
constructor(private http: Http,
private httpErrorHandler: HttpErrorHandler,
private localService: LocalizationService,
private _prop: PropertyService,
private preferenceService: PreferenceService,
private notificationService: MDMNotificationService,
private translateService: TranslateService) {
this.preferenceService.getPreference('ignoredAttributes').subscribe(
prefs => this.ignoreAttributesPrefs = this.ignoreAttributesPrefs.concat(prefs),
error => this.notificationService.notifyError(
this.translateService.instant('search.search.err-cannot-load-preference-for-attributes-to-ignore'), error)
);
}
loadSearchAttributes(type: string, env: string) {
return this.http.get(this._prop.getUrl('mdm/environments/' + env + '/' + type + '/searchattributes')).pipe(
map(response => <SearchAttribute[]>response.json().data),
map(sas => sas.map(sa => { sa.source = env; return sa; })),
map(sas => this.filterIgnoredAttributes(env, sas)),
catchError(this.httpErrorHandler.handleError));
}
// TODO TRANSLATION
getDefinitionsSimple() {
return observableOf([
<SearchDefinition>{ key: '1', value: 'tests', type: 'Test', label: 'Versuche' },
<SearchDefinition>{ key: '2', value: 'teststeps', type: 'TestStep', label: 'Versuchsschritte' },
<SearchDefinition>{ key: '3', value: 'measurements', type: 'Measurement', label: 'Messungen' }
]);
}
getSearchAttributesPerEnvs(envs: string[], type: string) {
return observableForkJoin(envs.map(env => this.loadSearchAttributes(type, env).pipe(
map(sas => sas.map(sa => { sa.source = env; return sa; }))))).pipe(
map(x => x.reduce(function(explored, toExplore) {
return explored.concat(toExplore);
}, [])));
}
loadSearchAttributesStructured(environments: string[]) {
if (!this.cachedAttributes) {
this.cachedAttributes = this.getDefinitionsSimple().pipe(
map(defs => defs.map(d => d.value)),
mergeMap(defs => this.loadSearchAttributesForAllDefs(defs, environments)),
publishReplay(1),
refCount());
}
return this.cachedAttributes;
}
loadSearchAttributesForAllDefs(types: string[], environments: string[]) {
return observableForkJoin(types.map(type => this.loadSearchAttributesForDef(type, environments))).pipe(
map(type2AttributesPerEnv =>
type2AttributesPerEnv.reduce(
function(acc, value) {
acc[value.type] = value.attributesPerEnv;
return acc; },
<{ [type: string]: { [env: string]: SearchAttribute[] }}> {})
));
}
loadSearchAttributesForDef(type: string, environments: string[]) {
return observableForkJoin(environments.map(env => this.loadSearchAttributes(type, env).pipe(
catchError(error => {
console.log('Could not load search attributes for type ' + type + ' in environment ' + env);
return observableOf(<SearchAttribute[]> []);
}),
map(attrs => { return { 'env': env, 'attributes': attrs}; })))).pipe(
map(attrsPerEnv => attrsPerEnv.reduce(
function(acc, value) {acc[value.env] = value.attributes; return acc; },
<{ [env: string]: SearchAttribute[] }> {})
),
map(attrsPerEnv => { return { 'type': type, 'attributesPerEnv': attrsPerEnv}; }));
}
convertToQuery(searchFilter: SearchFilter, attr: { [type: string]: { [env: string]: SearchAttribute[] }}, view: View) {
let q = new Query();
q.resultType = searchFilter.resultType;
if (attr['tests']) {
q.filters = this.convert(searchFilter.environments, searchFilter.conditions, attr['tests'], searchFilter.fulltextQuery); // TODO
}
q.columns = view.columns.map(c => c.type + '.' + c.name);
console.log('Query', q);
return q;
}
convert(envs: string[], conditions: Condition[], attr: { [env: string]: SearchAttribute[] }, fullTextQuery: string): Filter[] {
return envs.map(e => this.convertEnv(e, conditions, attr[e], fullTextQuery));
}
convertEnv(env: string, conditions: Condition[], attrs: SearchAttribute[], fullTextQuery: string): Filter {
let filterString = conditions
.map(c => c.value.map(value => c.type + '.' + c.attribute + ' ' + this.adjustOperator(OperatorUtil.toFilterString(c.operator),
this.getValueType(c, attrs)) + ' ' + this.quoteValue(value, this.getValueType(c, attrs))).join(' or '))
.filter(c => c.length > 0)
.join(' and ');
return new Filter(env, filterString, fullTextQuery);
}
quoteValue(value: string, valueType: string) {
if (valueType.toLowerCase() === 'string' || valueType.toLowerCase() === 'date') {
return '"' + value + '"';
} else {
return value;
}
}
getValueType(c: Condition, attrs: SearchAttribute[]) {
return attrs.find(a => a.boType === c.type && a.attrName === c.attribute).valueType;
}
adjustOperator(operator: string, valueType: string) {
if (valueType.toLowerCase() === 'string') {
return 'ci_' + operator;
} else {
return operator;
}
}
isAttributeIgnored(attributeName: string, sa: SearchAttribute) {
let x = attributeName.split('.', 2);
let fType = x[0];
let fName = x[1];
return ((fType === sa.boType || fType === '*') && (fName === sa.attrName || fName === '*'));
}
private filterIgnoredAttributes(environment: string, searchAttributes: SearchAttribute[]) {
let filters = this.getFilters(environment);
filters.forEach(f =>
searchAttributes = searchAttributes.filter(sa => !this.isAttributeIgnored(f, sa))
);
return searchAttributes;
}
getFilters(source: string): string[] {
return this.ignoreAttributesPrefs
.filter(p => p.scope !== Scope.SOURCE || p.source === source)
.sort(Preference.sortByScope)
.map(p => this.parsePreference(p))
.reduce((acc, value) => acc.concat(value), []);
}
private parsePreference(pref: Preference) {
try {
return <string[]> JSON.parse(pref.value);
} catch (e) {
this.notificationService.notifyError(
this.translateService.instant('search.search.err-faulty-preference-for-attributes-to-ignore'), e);
return [];
}
}
}