[ACA-3507] [ACA-3523] Fix Filters (#5821)

* fix search one letter
fix navigation
add new property search text prefix and suffix

* fix unit test

* fix unit test

* fix failing test
This commit is contained in:
Eugenio Romano 2020-06-29 17:27:52 +01:00 committed by GitHub
parent 0183f9e7f7
commit 5180493aa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 102 additions and 64 deletions

View File

@ -459,7 +459,8 @@
"settings": { "settings": {
"pattern": "cm:name:'(.*?)'", "pattern": "cm:name:'(.*?)'",
"field": "cm:name", "field": "cm:name",
"placeholder": "Enter the name" "placeholder": "Enter the name",
"searchSuffix" : "*"
} }
} }
}, },

View File

@ -25,6 +25,8 @@ Implements a text input [widget](../../../lib/testing/src/lib/core/pages/form/wi
"component": { "component": {
"selector": "text", "selector": "text",
"settings": { "settings": {
"searchPrefix": "",
"searchSuffix": "",
"pattern": "cm:name:'(.*?)'", "pattern": "cm:name:'(.*?)'",
"field": "cm:name", "field": "cm:name",
"placeholder": "Enter the name" "placeholder": "Enter the name"
@ -43,6 +45,8 @@ Implements a text input [widget](../../../lib/testing/src/lib/core/pages/form/wi
| field | string | Field to apply the query fragment to. Required value | | field | string | Field to apply the query fragment to. Required value |
| pattern | string | Regular expression pattern to restrict the format of the input text | | pattern | string | Regular expression pattern to restrict the format of the input text |
| placeholder | string | Text displayed in the [widget](../../../lib/testing/src/lib/core/pages/form/widgets/widget.ts) when the input string is empty | | placeholder | string | Text displayed in the [widget](../../../lib/testing/src/lib/core/pages/form/widgets/widget.ts) when the input string is empty |
| searchSuffix | string | Text to append always in the search of a string|
| searchPrefix | string | Text to prepend always in the search of a string|
## Details ## Details

View File

@ -43,6 +43,7 @@ export class SearchCheckListComponent implements SearchWidget, OnInit {
options: SearchFilterList<SearchListOption>; options: SearchFilterList<SearchListOption>;
operator: string = 'OR'; operator: string = 'OR';
pageSize = 5; pageSize = 5;
isActive = false;
constructor() { constructor() {
this.options = new SearchFilterList<SearchListOption>(); this.options = new SearchFilterList<SearchListOption>();
@ -60,6 +61,7 @@ export class SearchCheckListComponent implements SearchWidget, OnInit {
} }
reset() { reset() {
this.isActive = false;
this.options.items.forEach((opt) => { this.options.items.forEach((opt) => {
opt.checked = false; opt.checked = false;
}); });
@ -80,6 +82,8 @@ export class SearchCheckListComponent implements SearchWidget, OnInit {
.filter((option) => option.checked) .filter((option) => option.checked)
.map((option) => option.value); .map((option) => option.value);
this.isActive = !!checkedValues.length;
const query = checkedValues.join(` ${this.operator} `); const query = checkedValues.join(` ${this.operator} `);
if (this.id && this.context) { if (this.id && this.context) {

View File

@ -56,6 +56,8 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit, OnDestroy
context?: SearchQueryBuilderService; context?: SearchQueryBuilderService;
datePickerDateFormat = DEFAULT_FORMAT_DATE; datePickerDateFormat = DEFAULT_FORMAT_DATE;
maxDate: any; maxDate: any;
isActive = false;
private onDestroy$ = new Subject<boolean>(); private onDestroy$ = new Subject<boolean>();
constructor(private dateAdapter: DateAdapter<Moment>, constructor(private dateAdapter: DateAdapter<Moment>,
@ -117,6 +119,8 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit, OnDestroy
apply(model: { from: string, to: string }, isValid: boolean) { apply(model: { from: string, to: string }, isValid: boolean) {
if (isValid && this.id && this.context && this.settings && this.settings.field) { if (isValid && this.id && this.context && this.settings && this.settings.field) {
this.isActive = true;
const start = moment(model.from).startOf('day').format(); const start = moment(model.from).startOf('day').format();
const end = moment(model.to).endOf('day').format(); const end = moment(model.to).endOf('day').format();
@ -130,6 +134,7 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit, OnDestroy
} }
reset() { reset() {
this.isActive = false;
this.form.reset({ this.form.reset({
from: '', from: '',
to: '' to: ''

View File

@ -7,10 +7,10 @@
class="adf-filter-button" class="adf-filter-button"
[matTooltip]="getTooltipTranslation(col?.title)"> [matTooltip]="getTooltipTranslation(col?.title)">
<adf-icon value="adf:filter" <adf-icon value="adf:filter"
[ngClass]="{ 'adf-icon-active': isActive || menuTrigger.menuOpen }" [ngClass]="{ 'adf-icon-active': isActive()|| menuTrigger.menuOpen }"
matBadge="filter" matBadge="filter"
matBadgeColor="warn" matBadgeColor="warn"
[matBadgeHidden]="!isActive"></adf-icon> [matBadgeHidden]="!isActive()"></adf-icon>
</button> </button>
<mat-menu #filter="matMenu" class="adf-filter-menu"> <mat-menu #filter="matMenu" class="adf-filter-menu">

View File

@ -95,6 +95,7 @@ describe('SearchHeaderComponent', () => {
menuButton.click(); menuButton.click();
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
component.widgetContainer.componentRef.instance.value = 'searchText';
const applyButton = fixture.debugElement.query(By.css('#apply-filter-button')); const applyButton = fixture.debugElement.query(By.css('#apply-filter-button'));
applyButton.triggerEventHandler('click', {}); applyButton.triggerEventHandler('click', {});
fixture.detectChanges(); fixture.detectChanges();

View File

@ -15,7 +15,19 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, Input, Output, OnInit, OnChanges, EventEmitter, SimpleChanges, ViewEncapsulation, ViewChild, Inject, OnDestroy } from '@angular/core'; import {
Component,
Input,
Output,
OnInit,
OnChanges,
EventEmitter,
SimpleChanges,
ViewEncapsulation,
ViewChild,
Inject,
OnDestroy
} from '@angular/core';
import { DataColumn, TranslationService } from '@alfresco/adf-core'; import { DataColumn, TranslationService } from '@alfresco/adf-core';
import { SearchWidgetContainerComponent } from '../search-widget-container/search-widget-container.component'; import { SearchWidgetContainerComponent } from '../search-widget-container/search-widget-container.component';
import { SearchHeaderQueryBuilderService } from '../../search-header-query-builder.service'; import { SearchHeaderQueryBuilderService } from '../../search-header-query-builder.service';
@ -60,8 +72,6 @@ export class SearchHeaderComponent implements OnInit, OnChanges, OnDestroy {
@ViewChild(SearchWidgetContainerComponent) @ViewChild(SearchWidgetContainerComponent)
widgetContainer: SearchWidgetContainerComponent; widgetContainer: SearchWidgetContainerComponent;
public isActive: boolean;
category: SearchCategory; category: SearchCategory;
isFilterServiceActive: boolean; isFilterServiceActive: boolean;
@ -81,19 +91,13 @@ export class SearchHeaderComponent implements OnInit, OnChanges, OnDestroy {
.pipe(takeUntil(this.onDestroy$)) .pipe(takeUntil(this.onDestroy$))
.subscribe((newNodePaging: NodePaging) => { .subscribe((newNodePaging: NodePaging) => {
this.update.emit(newNodePaging); this.update.emit(newNodePaging);
}); });
} }
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
if (changes['currentFolderNodeId'] && changes['currentFolderNodeId'].currentValue) { if (changes['currentFolderNodeId'] && changes['currentFolderNodeId'].currentValue !== changes['currentFolderNodeId'].previousValue) {
const currentIdValue = changes['currentFolderNodeId'].currentValue; this.searchHeaderQueryBuilder.setCurrentRootFolderId(changes['currentFolderNodeId'].currentValue);
const previousIdValue = changes['currentFolderNodeId'].previousValue; this.clearHeader();
this.searchHeaderQueryBuilder.setCurrentRootFolderId(
currentIdValue,
previousIdValue
);
this.isActive = false;
} }
if (changes['maxItems'] || changes['skipCount']) { if (changes['maxItems'] || changes['skipCount']) {
@ -125,7 +129,12 @@ export class SearchHeaderComponent implements OnInit, OnChanges, OnDestroy {
} }
onApplyButtonClick() { onApplyButtonClick() {
this.isActive = true; // TODO Move this piece of code in the search text widget
if (this.widgetContainer.selector === 'text' && this.widgetContainer.componentRef.instance.value === '') {
this.clearHeader();
return;
}
this.widgetContainer.applyInnerWidget(); this.widgetContainer.applyInnerWidget();
this.searchHeaderQueryBuilder.setActiveFilter(this.category.columnKey); this.searchHeaderQueryBuilder.setActiveFilter(this.category.columnKey);
this.searchHeaderQueryBuilder.execute(); this.searchHeaderQueryBuilder.execute();
@ -133,13 +142,14 @@ export class SearchHeaderComponent implements OnInit, OnChanges, OnDestroy {
onClearButtonClick(event: Event) { onClearButtonClick(event: Event) {
event.stopPropagation(); event.stopPropagation();
this.widgetContainer.resetInnerWidget(); this.clearHeader();
this.isActive = false; }
this.searchHeaderQueryBuilder.removeActiveFilter(this.category.columnKey);
if (this.searchHeaderQueryBuilder.isNoFilterActive()) { clearHeader() {
if (this.widgetContainer) {
this.widgetContainer.resetInnerWidget();
this.searchHeaderQueryBuilder.removeActiveFilter(this.category.columnKey);
this.clear.emit(); this.clear.emit();
} else {
this.searchHeaderQueryBuilder.execute();
} }
} }
@ -147,6 +157,10 @@ export class SearchHeaderComponent implements OnInit, OnChanges, OnDestroy {
if (!columnTitle) { if (!columnTitle) {
columnTitle = 'SEARCH.SEARCH_HEADER.TYPE'; columnTitle = 'SEARCH.SEARCH_HEADER.TYPE';
} }
return this.translationService.instant('SEARCH.SEARCH_HEADER.FILTER_BY', {category: this.translationService.instant(columnTitle)}); return this.translationService.instant('SEARCH.SEARCH_HEADER.FILTER_BY', { category: this.translationService.instant(columnTitle) });
}
isActive(): boolean {
return this.widgetContainer && this.widgetContainer.componentRef && this.widgetContainer.componentRef.instance.isActive;
} }
} }

View File

@ -44,6 +44,8 @@ export class SearchNumberRangeComponent implements SearchWidget, OnInit {
field: string; field: string;
format = '[{FROM} TO {TO}]'; format = '[{FROM} TO {TO}]';
isActive = false;
validators: Validators; validators: Validators;
ngOnInit(): void { ngOnInit(): void {
@ -74,6 +76,8 @@ export class SearchNumberRangeComponent implements SearchWidget, OnInit {
apply(model: { from: string, to: string }, isValid: boolean) { apply(model: { from: string, to: string }, isValid: boolean) {
if (isValid && this.id && this.context && this.field) { if (isValid && this.id && this.context && this.field) {
this.isActive = true;
const map = new Map<string, string>(); const map = new Map<string, string>();
map.set('FROM', model.from); map.set('FROM', model.from);
map.set('TO', model.to); map.set('TO', model.to);
@ -97,6 +101,8 @@ export class SearchNumberRangeComponent implements SearchWidget, OnInit {
} }
reset() { reset() {
this.isActive = false;
this.form.reset({ this.form.reset({
from: '', from: '',
to: '' to: ''

View File

@ -46,6 +46,7 @@ export class SearchRadioComponent implements SearchWidget, OnInit {
context: SearchQueryBuilderService; context: SearchQueryBuilderService;
options: SearchFilterList<SearchRadioOption>; options: SearchFilterList<SearchRadioOption>;
pageSize = 5; pageSize = 5;
isActive = false;
constructor() { constructor() {
this.options = new SearchFilterList<SearchRadioOption>(); this.options = new SearchFilterList<SearchRadioOption>();
@ -71,6 +72,8 @@ export class SearchRadioComponent implements SearchWidget, OnInit {
private getSelectedValue(): string { private getSelectedValue(): string {
const options: any[] = this.settings['options'] || []; const options: any[] = this.settings['options'] || [];
if (options && options.length > 0) { if (options && options.length > 0) {
this.isActive = true;
let selected = options.find((opt) => opt.default); let selected = options.find((opt) => opt.default);
if (!selected) { if (!selected) {
selected = options[0]; selected = options[0];
@ -91,6 +94,8 @@ export class SearchRadioComponent implements SearchWidget, OnInit {
} }
reset() { reset() {
this.isActive = false;
const initialValue = this.getSelectedValue(); const initialValue = this.getSelectedValue();
if (initialValue !== null) { if (initialValue !== null) {
this.setValue(initialValue); this.setValue(initialValue);

View File

@ -36,6 +36,7 @@ export class SearchTextComponent implements SearchWidget, OnInit {
id: string; id: string;
settings: SearchWidgetSettings; settings: SearchWidgetSettings;
context: SearchQueryBuilderService; context: SearchQueryBuilderService;
isActive = false;
ngOnInit() { ngOnInit() {
if (this.context && this.settings && this.settings.pattern) { if (this.context && this.settings && this.settings.pattern) {
@ -49,6 +50,8 @@ export class SearchTextComponent implements SearchWidget, OnInit {
} }
reset() { reset() {
this.isActive = false;
this.value = ''; this.value = '';
this.updateQuery(null); this.updateQuery(null);
} }
@ -59,10 +62,21 @@ export class SearchTextComponent implements SearchWidget, OnInit {
} }
private updateQuery(value: string) { private updateQuery(value: string) {
this.isActive = !!value;
if (this.context && this.settings && this.settings.field) { if (this.context && this.settings && this.settings.field) {
this.context.queryFragments[this.id] = value ? `${this.settings.field}:'${value}'` : ''; this.context.queryFragments[this.id] = value ? `${this.settings.field}:'${this.getSearchPrefix()}${value}${this.getSearchSuffix()}'` : '';
this.context.update(); this.context.update();
} }
}
private getSearchPrefix(): string {
return this.settings.searchPrefix ? this.settings.searchPrefix : '';
}
private getSearchSuffix(): string {
return this.settings.searchSuffix ? this.settings.searchSuffix : '';
} }
} }

View File

@ -41,7 +41,7 @@ export class SearchWidgetContainerComponent implements OnInit, OnDestroy {
@Input() @Input()
config: any; config: any;
private componentRef: ComponentRef<any>; componentRef: ComponentRef<any>;
constructor( constructor(
private searchFilterService: SearchFilterService, private searchFilterService: SearchFilterService,

View File

@ -94,8 +94,6 @@ describe('SearchHeaderQueryBuilder', () => {
}; };
const expectedResult = [ const expectedResult = [
{ query: 'query1' },
{ query: 'query2' },
{ query: 'PARENT:"workspace://SpacesStore/fake-node-id"' } { query: 'PARENT:"workspace://SpacesStore/fake-node-id"' }
]; ];
@ -105,7 +103,7 @@ describe('SearchHeaderQueryBuilder', () => {
null null
); );
searchHeaderService.setCurrentRootFolderId('fake-node-id', undefined); searchHeaderService.setCurrentRootFolderId('fake-node-id');
expect(searchHeaderService.filterQueries).toEqual(expectedResult, 'Filters are not as expected'); expect(searchHeaderService.filterQueries).toEqual(expectedResult, 'Filters are not as expected');
}); });
@ -113,9 +111,7 @@ describe('SearchHeaderQueryBuilder', () => {
it('should not add again the parent filter if that node is already added', () => { it('should not add again the parent filter if that node is already added', () => {
const expectedResult = [ const expectedResult = [
{ query: 'query1' }, { query: 'PARENT:"workspace://SpacesStore/fake-node-id"' }
{ query: 'query2' },
{ query: 'PARENT:"workspace://SpacesStore/fake-node-id' }
]; ];
const config: SearchConfiguration = { const config: SearchConfiguration = {
@ -132,7 +128,7 @@ describe('SearchHeaderQueryBuilder', () => {
null null
); );
searchHeaderService.setCurrentRootFolderId('fake-node-id', undefined); searchHeaderService.setCurrentRootFolderId('fake-node-id');
expect(searchHeaderService.filterQueries).toEqual( expect(searchHeaderService.filterQueries).toEqual(
expectedResult, expectedResult,
@ -142,8 +138,6 @@ describe('SearchHeaderQueryBuilder', () => {
it('should replace the new query filter for the old parent node with the new one', () => { it('should replace the new query filter for the old parent node with the new one', () => {
const expectedResult = [ const expectedResult = [
{ query: 'query1' },
{ query: 'query2' },
{ query: 'PARENT:"workspace://SpacesStore/fake-next-node-id"' } { query: 'PARENT:"workspace://SpacesStore/fake-next-node-id"' }
]; ];
@ -153,8 +147,6 @@ describe('SearchHeaderQueryBuilder', () => {
<any> { id: 'cat2', enabled: true } <any> { id: 'cat2', enabled: true }
], ],
filterQueries: [ filterQueries: [
{ query: 'query1' },
{ query: 'query2' },
{ query: 'PARENT:"workspace://SpacesStore/fake-node-id' } { query: 'PARENT:"workspace://SpacesStore/fake-node-id' }
] ]
}; };
@ -165,9 +157,10 @@ describe('SearchHeaderQueryBuilder', () => {
null null
); );
searchHeaderService.currentParentFolderId = 'fake-node-id';
searchHeaderService.setCurrentRootFolderId( searchHeaderService.setCurrentRootFolderId(
'fake-next-node-id', 'fake-next-node-id'
'fake-node-id'
); );
expect(searchHeaderService.filterQueries).toEqual( expect(searchHeaderService.filterQueries).toEqual(

View File

@ -30,7 +30,7 @@ export class SearchHeaderQueryBuilderService extends BaseQueryBuilderService {
private customSources = ['-trashcan-', '-sharedlinks-', '-sites-', '-mysites-', '-favorites-', '-recent-', '-my-']; private customSources = ['-trashcan-', '-sharedlinks-', '-sites-', '-mysites-', '-favorites-', '-recent-', '-my-'];
activeFilters: string[] = []; activeFilters: string[] = [];
currentParentFolderID: string; currentParentFolderId: string;
constructor(appConfig: AppConfigService, alfrescoApiService: AlfrescoApiService, private nodeApiService: NodesApiService) { constructor(appConfig: AppConfigService, alfrescoApiService: AlfrescoApiService, private nodeApiService: NodesApiService) {
super(appConfig, alfrescoApiService); super(appConfig, alfrescoApiService);
@ -78,41 +78,31 @@ export class SearchHeaderQueryBuilderService extends BaseQueryBuilderService {
return foundCategory; return foundCategory;
} }
setCurrentRootFolderId(currentFolderId: string, previousFolderId: string) { setCurrentRootFolderId(currentFolderId: string) {
if (this.customSources.includes(currentFolderId)) { if (currentFolderId !== this.currentParentFolderId) {
if (currentFolderId !== this.currentParentFolderID) { if (this.customSources.includes(currentFolderId)) {
this.nodeApiService.getNode(currentFolderId).subscribe((nodeEntity: MinimalNode) => { this.nodeApiService.getNode(currentFolderId).subscribe((nodeEntity: MinimalNode) => {
this.updateCurrentParentFilter(nodeEntity.id, previousFolderId); this.updateCurrentParentFilter(nodeEntity.id);
}); });
} else {
this.currentParentFolderId = currentFolderId;
this.updateCurrentParentFilter(currentFolderId);
} }
} else {
this.updateCurrentParentFilter(currentFolderId, previousFolderId);
} }
} }
private updateCurrentParentFilter(currentFolderId: string, previousFolderId: string) { private updateCurrentParentFilter(currentFolderId: string) {
const alreadyAddedFilter = this.filterQueries.find(filterQueries => const alreadyAddedFilter = this.filterQueries.find(filterQueries =>
filterQueries.query.includes(currentFolderId) filterQueries.query.includes(currentFolderId)
); );
if (!alreadyAddedFilter) { if (alreadyAddedFilter !== undefined) {
this.removeOldFolderFiltering(previousFolderId, this.currentParentFolderID); this.filterQueries = [];
this.currentParentFolderID = currentFolderId;
this.filterQueries.push({
query: `PARENT:"workspace://SpacesStore/${currentFolderId}"`
});
} }
this.filterQueries = [{
query: `PARENT:"workspace://SpacesStore/${currentFolderId}"`
}];
} }
private removeOldFolderFiltering(previousFolderId: string, actualSetFolderId: string) {
if (previousFolderId || actualSetFolderId) {
const folderIdToRetrieve = previousFolderId ? previousFolderId : actualSetFolderId;
const oldFilterIndex = this.filterQueries.findIndex(filterQueries =>
filterQueries.query.includes(folderIdToRetrieve)
);
if (oldFilterIndex) {
this.filterQueries.splice(oldFilterIndex, 1);
}
}
}
} }

View File

@ -22,5 +22,6 @@ export interface SearchWidget {
id: string; id: string;
settings?: SearchWidgetSettings; settings?: SearchWidgetSettings;
context?: SearchQueryBuilderService; context?: SearchQueryBuilderService;
isActive?: boolean;
reset(); reset();
} }