blob: ca98675d1525f82c1c1b52f24505283554902f45 [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 {Component, ViewChild, OnInit, OnDestroy} from '@angular/core';
import {SearchService, SearchDefinition, SearchAttribute, SearchLayout} from './search.service';
import {of as observableOf, combineLatest as observableCombineLatest} from 'rxjs';
import {defaultIfEmpty, mergeMap, tap} from 'rxjs/operators';
import {FilterService, SearchFilter, Condition} from './filter.service';
import {NodeService} from '../navigator/node.service';
import {BasketService} from '../basket/basket.service';
import {QueryService, SearchResult } from '../tableview/query.service';
import {LocalizationService} from '../localization/localization.service';
import {MDMNotificationService} from '../core/mdm-notification.service';
import {Node} from '../navigator/node';
import {TableviewComponent} from '../tableview/tableview.component';
import {ViewComponent} from '../tableview/view.component';
import {ModalDirective} from 'ngx-bootstrap';
import {AccordionPanelComponent} from 'ngx-bootstrap/accordion';
import {MenuItem} from 'primeng/primeng';
import {EditSearchFieldsComponent} from './edit-searchFields.component';
import {OverwriteDialogComponent} from '../core/overwrite-dialog.component';
import {classToClass, serialize, deserialize} from 'class-transformer';
import {SelectItem} from 'primeng/primeng';
import { TranslateService } from '@ngx-translate/core';
import { streamTranslate, TRANSLATE } from '../core/mdm-core.module';
@Component({
selector: 'mdm-search',
templateUrl: 'mdm-search.component.html',
})
export class MDMSearchComponent implements OnInit, OnDestroy {
maxResults = 1000;
filters: SearchFilter[] = [];
currentFilter: SearchFilter;
filterName = '';
environments: Node[];
selectedEnvironments: Node[] = [];
definitions: SearchDefinition[] = [];
results: SearchResult = new SearchResult();
allSearchAttributes: { [type: string]: { [env: string]: SearchAttribute[] }} = {};
allSearchAttributesForCurrentResultType: { [env: string]: SearchAttribute[] } = {};
isAdvancedSearchOpen = false;
isAdvancedSearchActive = true;
isSearchResultsOpen = false;
layout: SearchLayout = new SearchLayout;
public dropdownModel: SelectItem[] = [];
public selectedEnvs: string[] = [];
searchFields: { group: string, attribute: string }[] = [];
subscription: any;
searchExecuted = false;
selectedRow: SearchFilter;
lazySelectedRow: SearchFilter;
loading = false;
contextMenuItems: MenuItem[] = [
{ label: 'Add to shopping basket', icon: 'fa fa-shopping-cart', command: (event) => this.addSelectionToBasket() }
];
@ViewChild(ViewComponent)
viewComponent: ViewComponent;
@ViewChild(TableviewComponent)
tableViewComponent: TableviewComponent;
@ViewChild('lgSaveModal')
childSaveModal: ModalDirective;
@ViewChild(EditSearchFieldsComponent)
editSearchFieldsComponent: EditSearchFieldsComponent;
@ViewChild(OverwriteDialogComponent)
overwriteDialogComponent: OverwriteDialogComponent;
@ViewChild('advancedSearch')
advancedSearchPanel: AccordionPanelComponent;
@ViewChild('searchResults')
searchResultsPanel: AccordionPanelComponent;
constructor(private searchService: SearchService,
private queryService: QueryService,
private filterService: FilterService,
private nodeService: NodeService,
private localService: LocalizationService,
private notificationService: MDMNotificationService,
private basketService: BasketService,
private translateService: TranslateService) { }
ngOnInit() {
this.currentFilter = this.filterService.EMPTY_FILTER;
streamTranslate(this.translateService, TRANSLATE('search.mdm-search.add-to-shopping-basket')).subscribe(
(msg: string) => this.contextMenuItems[0].label = msg);
this.nodeService.getNodes().pipe(
mergeMap(envs => observableCombineLatest([
observableOf(envs),
this.searchService.loadSearchAttributesStructured(envs.map(env => env.sourceName)),
this.filterService.getFilters().pipe(defaultIfEmpty([this.currentFilter])),
this.searchService.getDefinitionsSimple()
]))).subscribe(
([envs, attrs, filters, definitions]) => this.init(envs, attrs, filters, definitions),
error => this.notificationService.notifyError(
this.translateService.instant('search.mdm-search.err-cannot-load-data-sources'), error)
);
// event handlers
this.viewComponent.viewChanged$.subscribe(
() => this.onViewChanged(),
error => this.notificationService.notifyError(this.translateService.instant('search.mdm-search.err-cannot-update-view'), error)
);
}
ngOnDestroy() {
this.saveState();
}
init(envs: Node[], attrs: { [type: string]: { [env: string]: SearchAttribute[] }},
filters: SearchFilter[], definitions: SearchDefinition[]) {
this.environments = envs;
this.allSearchAttributes = attrs;
this.filters = filters;
this.definitions = definitions;
this.dropdownModel = envs.map(env => <SelectItem>{ value: env.sourceName, label: env.name });
this.selectedEnvs = envs.map(env => env.sourceName);
this.updateSearchAttributesForCurrentResultType();
this.selectedEnvironmentsChanged();
this.loadState();
}
loadState() {
this.results = deserialize(SearchResult, sessionStorage.getItem('mdm-search.searchResult')) || new SearchResult();
this.selectFilter(deserialize(SearchFilter, sessionStorage.getItem('mdm-search.currentFilter')) || this.filterService.EMPTY_FILTER);
this.isAdvancedSearchActive = !('false' === sessionStorage.getItem('mdm-search.isAdvancedSearchActive'));
this.advancedSearchPanel.isOpen = !('false' === sessionStorage.getItem('mdm-search.isAdvancedSearchOpen'));
this.searchResultsPanel.isOpen = !('false' === sessionStorage.getItem('mdm-search.isSearchResultsOpen'));
}
saveState() {
sessionStorage.setItem('mdm-search.isSearchResultsOpen', serialize(this.searchResultsPanel.isOpen));
sessionStorage.setItem('mdm-search.isAdvancedSearchOpen', serialize(this.advancedSearchPanel.isOpen));
sessionStorage.setItem('mdm-search.currentFilter', serialize(this.currentFilter));
sessionStorage.setItem('mdm-search.searchResult', serialize(this.results));
sessionStorage.setItem('mdm-search.isAdvancedSearchActive', this.isAdvancedSearchActive.toString());
}
onViewClick(e: Event) {
e.stopPropagation();
}
onCheckBoxClick(event: any) {
event.stopPropagation();
this.isAdvancedSearchActive = event.target.checked;
}
onViewChanged() {
if (this.searchExecuted) {
this.onSearch();
}
}
selectedEnvironmentsChanged() {
this.currentFilter.environments = this.selectedEnvs;
if (this.environments) {
let envs = this.environments.filter(env =>
this.currentFilter.environments.find(envName => envName === env.sourceName));
if (envs.length === 0) {
this.selectedEnvironments = this.environments;
} else {
this.selectedEnvironments = envs;
}
}
this.calcCurrentSearch();
}
loadFilters() {
this.filters = [];
this.filterService.getFilters().pipe(
defaultIfEmpty([this.currentFilter]))
.subscribe(
filters => this.filters = this.filters.concat(filters),
error => this.notificationService.notifyError(
this.translateService.instant('search.mdm-search.err-cannot-load-search-filter'), error)
);
}
selectFilterByName(defaultFilterName: string) {
this.selectFilter(this.filters.find(f => f.name === defaultFilterName));
}
removeSearchField(searchField: { group: string, attribute: string }) {
let index = this.searchFields.findIndex(sf => sf.group === searchField.group && sf.attribute === searchField.attribute);
this.searchFields.splice(index, 1);
}
onResultTypeChane(e: any) {
this.selectResultType(e.value);
}
selectResultType(type: any) {
this.currentFilter.resultType = type.type;
this.updateSearchAttributesForCurrentResultType();
}
updateSearchAttributesForCurrentResultType() {
if (this.allSearchAttributes.hasOwnProperty(this.getSelectedDefinition())) {
this.allSearchAttributesForCurrentResultType = this.allSearchAttributes[this.getSelectedDefinition()];
}
}
getSearchDefinition(type: string) {
return this.definitions.find(def => def.type === type);
}
getSelectedDefinition() {
let def = this.getSearchDefinition(this.currentFilter.resultType);
if (def) {
return def.value;
}
}
onSearch() {
let query;
this.loading = true;
this.isSearchResultsOpen = true;
if (this.isAdvancedSearchActive) {
query = this.searchService.convertToQuery(this.currentFilter, this.allSearchAttributes, this.viewComponent.selectedView);
} else {
let filter = classToClass(this.currentFilter);
filter.conditions = [];
query = this.searchService.convertToQuery(filter, this.allSearchAttributes, this.viewComponent.selectedView);
}
this.queryService.query(query).pipe(
tap(result => this.generateWarningsIfMaxResultsReached(result)))
.subscribe(
result => {
this.results = result;
this.isSearchResultsOpen = true;
this.searchExecuted = true;
this.loading = false;
},
error => this.notificationService.notifyError(
this.translateService.instant('search.mdm-search.err-cannot-process-search-query'), error)
);
}
generateWarningsIfMaxResultsReached(result: SearchResult) {
let resultsPerSource = result.rows
.map(r => r.source)
.reduce((prev, item) => { (prev[item]) ? prev[item] += 1 : prev[item] = 1; return prev; }, {});
Object.keys(resultsPerSource)
.filter(source => resultsPerSource[source] > this.maxResults)
.forEach(source => this.notificationService.notifyWarn(
this.translateService.instant('search.mdm-search.errheading-too-many-search-results'),
this.translateService.instant('search.mdm-search.err-too-many-search-results', {'numresults': this.maxResults, 'source': source})));
}
calcCurrentSearch() {
let environments = this.currentFilter.environments;
let conditions = this.currentFilter.conditions;
let type = this.getSelectedDefinition();
this.layout = SearchLayout.createSearchLayout(environments, this.allSearchAttributesForCurrentResultType, conditions);
}
onFilterChange(e: any) {
this.selectFilter(e.value);
}
selectFilter(filter: SearchFilter) {
this.currentFilter = classToClass(filter);
this.selectedEnvs = this.currentFilter.environments;
this.updateSearchAttributesForCurrentResultType();
this.selectedEnvironmentsChanged();
this.calcCurrentSearch();
}
resetConditions(e: Event) {
e.stopPropagation();
this.currentFilter.conditions.forEach(cond => cond.value = []);
this.selectFilter(this.currentFilter);
}
clearResultlist(e: Event) {
e.stopPropagation();
this.results = new SearchResult();
}
deleteFilter(e: Event) {
e.stopPropagation();
if (this.currentFilter.name === this.filterService.NO_FILTER_NAME
|| this.currentFilter.name === this.filterService.NEW_FILTER_NAME) {
this.notificationService
.notifyInfo(this.translateService.instant('search.mdm-search.errheading-cannot-delete-search-filter-none-selected'),
this.translateService.instant('search.mdm-search.err-cannot-delete-search-filter-none-selected'));
} else {
this.layout = new SearchLayout;
this.filterService.deleteFilter(this.currentFilter.name).subscribe(
() => {
this.loadFilters();
this.selectFilter(new SearchFilter(this.filterService.NO_FILTER_NAME, [], 'Test', '', []));
},
error => this.notificationService.notifyError(
this.translateService.instant('search.mdm-search.err-cannot-delete-search-filter'), error)
);
}
}
saveFilter(e: Event) {
e.stopPropagation();
if (this.filters.find(f => f.name === this.filterName) != undefined) {
this.childSaveModal.hide();
this.overwriteDialogComponent.showOverwriteModal(this.translateService.instant('search.mdm-search.a-filter')).subscribe(
needSave => this.saveFilter2(needSave),
error => {
this.saveFilter2(false);
this.notificationService.notifyError(this.translateService.instant('search.mdm-search.err-cannot-save-search-filter'), error);
}
);
} else {
this.saveFilter2(true);
}
}
saveFilter2(save: boolean) {
if (save) {
let filter = this.currentFilter;
filter.name = this.filterName;
this.filterService.saveFilter(filter).subscribe(
() => {
this.loadFilters();
this.selectFilter(filter);
},
error => this.notificationService.notifyError(
this.translateService.instant('search.mdm-search.err-cannot-save-search-filter'), error)
);
this.childSaveModal.hide();
} else {
this.childSaveModal.show();
}
}
removeCondition(condition: Condition) {
this.currentFilter.conditions = this.currentFilter.conditions
.filter(c => !(c.type === condition.type && c.attribute === condition.attribute));
this.calcCurrentSearch();
}
selected2Basket(e: Event) {
e.stopPropagation();
this.tableViewComponent.selectedViewRows.forEach(row => this.basketService.add(row.getItem()));
}
showSaveModal(e: Event) {
e.stopPropagation();
if (this.currentFilter.name === this.filterService.NO_FILTER_NAME
|| this.currentFilter.name === this.filterService.NEW_FILTER_NAME) {
this.filterName = '';
} else {
this.filterName = this.currentFilter.name;
}
this.childSaveModal.show();
}
showSearchFieldsEditor(e: Event, conditions?: Condition[]) {
e.stopPropagation();
this.editSearchFieldsComponent.show(conditions).subscribe(
conds => {
if (!conditions) {
let filter = new SearchFilter(this.filterService.NEW_FILTER_NAME, this.currentFilter.environments, 'Test', '', conds);
this.selectFilter(filter);
}
this.currentFilter.conditions = conds;
this.calcCurrentSearch();
},
error => this.notificationService.notifyError(
this.translateService.instant('search.mdm-search.err-cannot-display-search-field-editor'), error)
);
}
addSelectionToBasket() {
this.basketService.add(this.tableViewComponent.menuSelectedRow.getItem());
}
mapSourceNameToName(sourceName: string) {
return NodeService.mapSourceNameToName(this.environments, sourceName);
}
getSaveFilterBtnTitle () {
return this.filterName
? TRANSLATE('search.mdm-search.tooltip-save-search-filter')
: TRANSLATE('search.mdm-search.tooltip-no-name-set') ;
}
getAdvancedSearchCbxTitle() {
return this.isAdvancedSearchActive
? TRANSLATE('search.mdm-search.tooltip-disable-advanced-search')
: TRANSLATE('search.mdm-search.tooltip-enable-advanced-search');
}
/*
private loadSearchAttributes(environments: string[]) {
this.searchService.loadSearchAttributesStructured(environments)
.subscribe(
attrs => { this.allSearchAttributes = attrs; this.updateSearchAttributesForCurrentResultType(); },
error => this.notificationService.notifyError(
this.translateService.instant('search.mdm-search.err-cannot-load-attributes'), error));
}
*/
onRowSelect(e: any) {
if (this.lazySelectedRow !== e.data) {
this.selectedRow = e.data;
this.filterName = e.data.name;
} else {
this.selectedRow = undefined;
this.filterName = '';
}
this.lazySelectedRow = this.selectedRow;
}
}