[ACA-3542] - added sorting for filters (#5932)

* [ACA-3542] - added sorting for filters

* [ACA-3542] - removed wrong parameter

* [ACA-3542] - fixed test with fixed sorting mode parameter

* Update content-node-selector-panel.component.html

* fix e2e

* fix delete site

Co-authored-by: Eugenio Romano <eromano@users.noreply.github.com>
Co-authored-by: Eugenio Romano <eugenio.romano@alfresco.com>
This commit is contained in:
Vito 2020-08-03 10:30:14 +01:00 committed by GitHub
parent bfbb66ea8e
commit 85183ead0d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 114 additions and 27 deletions

View File

@ -567,7 +567,7 @@
"ascending": true "ascending": true
}, },
{ {
"key": "createdByUser", "key": "createdByUser.displayName",
"label": "Author", "label": "Author",
"type": "FIELD", "type": "FIELD",
"field": "cm:creator", "field": "cm:creator",

View File

@ -229,7 +229,7 @@
[sortingMode]="sortingMode" [sortingMode]="sortingMode"
[showHeader]="showHeader" [showHeader]="showHeader"
[thumbnails]="thumbnails" [thumbnails]="thumbnails"
[stickyHeader]="stickyHeader" [stickyHeader]="stickyHeader"
(error)="onNavigationError($event)" (error)="onNavigationError($event)"
(success)="resetError()" (success)="resetError()"
(ready)="emitReadyEvent($event)" (ready)="emitReadyEvent($event)"
@ -242,6 +242,7 @@
<adf-search-header [col]="col" <adf-search-header [col]="col"
[value]="paramValues? paramValues[col.key] : null" [value]="paramValues? paramValues[col.key] : null"
[currentFolderNodeId]="currentFolderId" [currentFolderNodeId]="currentFolderId"
[sorting]="filterSorting"
[maxItems]="pagination?.maxItems" [maxItems]="pagination?.maxItems"
[skipCount]="pagination?.skipCount" [skipCount]="pagination?.skipCount"
(update)="onFilterUpdate($event)" (update)="onFilterUpdate($event)"

View File

@ -88,7 +88,7 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
processId; processId;
@Input() @Input()
sorting = ['nodeType', 'DESC']; sorting = ['name', 'ASC'];
@Input() @Input()
sortingMode = 'server'; sortingMode = 'server';
@ -163,7 +163,10 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
enableCustomHeaderFilter = false; enableCustomHeaderFilter = false;
@Input() @Input()
paramValues: Map <any, any> = null; paramValues: Map<any, any> = null;
@Input()
filterSorting: string = null;
@Output() @Output()
documentListReady: EventEmitter<any> = new EventEmitter(); documentListReady: EventEmitter<any> = new EventEmitter();
@ -208,7 +211,7 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
enableMediumTimeFormat = false; enableMediumTimeFormat = false;
displayEmptyMetadata = false; displayEmptyMetadata = false;
hyperlinkNavigation = false; hyperlinkNavigation = false;
filtersStates: any[] = []; filteredSorting: string[] = null;
constructor(private notificationService: NotificationService, constructor(private notificationService: NotificationService,
private uploadService: UploadService, private uploadService: UploadService,

View File

@ -4,6 +4,8 @@
[showSettingsPanel]="false" [showSettingsPanel]="false"
[navigationRoute]="navigationRoute" [navigationRoute]="navigationRoute"
[currentFolderId]="currentFolderId" [currentFolderId]="currentFolderId"
[filterSorting]="filterSorting"
[enableCustomHeaderFilter]="true" [enableCustomHeaderFilter]="true"
[paramValues]="queryParams"> [paramValues]="queryParams"
(sorting-changed)="onSortingChanged($event)">
</app-files-component> </app-files-component>

View File

@ -30,6 +30,7 @@ export class FilteredSearchComponent {
currentFolderId = '-my-'; currentFolderId = '-my-';
queryParams = null; queryParams = null;
filterSorting = null;
constructor(@Optional() private route: ActivatedRoute) { constructor(@Optional() private route: ActivatedRoute) {
@ -46,4 +47,8 @@ export class FilteredSearchComponent {
} }
} }
onSortingChanged(event) {
this.filterSorting = event.detail.column + '-' + event.detail.direction;
}
} }

View File

@ -19,7 +19,8 @@ import { by, element, ElementFinder } from 'protractor';
import { BrowserVisibility, BrowserActions } from '@alfresco/adf-testing'; import { BrowserVisibility, BrowserActions } from '@alfresco/adf-testing';
export class FolderDialogPage { export class FolderDialogPage {
folderDialog = element(by.css('adf-folder-dialog'));
folderDialog = element.all(by.css('adf-folder-dialog')).first();
folderNameField = this.folderDialog.element(by.id('adf-folder-name-input')); folderNameField = this.folderDialog.element(by.id('adf-folder-name-input'));
folderDescriptionField = this.folderDialog.element(by.id('adf-folder-description-input')); folderDescriptionField = this.folderDialog.element(by.id('adf-folder-description-input'));
createUpdateButton = this.folderDialog.element(by.id('adf-folder-create-button')); createUpdateButton = this.folderDialog.element(by.id('adf-folder-create-button'));

View File

@ -64,6 +64,7 @@ describe('Viewer', () => {
}); });
afterAll(async () => { afterAll(async () => {
await apiService.getInstance().login(browser.params.testConfig.admin.email, browser.params.testConfig.admin.password);
await apiService.getInstance().core.sitesApi.deleteSite(site.entry.id, { permanent: true }); await apiService.getInstance().core.sitesApi.deleteSite(site.entry.id, { permanent: true });
await navigationBarPage.clickLogoutButton(); await navigationBarPage.clickLogoutButton();
}); });

View File

@ -76,6 +76,7 @@ describe('Viewer', () => {
}); });
afterAll(async () => { afterAll(async () => {
await apiService.getInstance().login(browser.params.testConfig.admin.email, browser.params.testConfig.admin.password);
await apiService.getInstance().core.sitesApi.deleteSite(site.entry.id, { permanent: true }); await apiService.getInstance().core.sitesApi.deleteSite(site.entry.id, { permanent: true });
await navigationBarPage.clickLogoutButton(); await navigationBarPage.clickLogoutButton();
}); });

View File

@ -64,6 +64,7 @@ describe('Viewer', () => {
}); });
afterAll(async () => { afterAll(async () => {
await apiService.getInstance().login(browser.params.testConfig.admin.email, browser.params.testConfig.admin.password);
await apiService.getInstance().core.sitesApi.deleteSite(site.entry.id, { permanent: true }); await apiService.getInstance().core.sitesApi.deleteSite(site.entry.id, { permanent: true });
}); });

View File

@ -70,6 +70,7 @@ describe('Viewer', () => {
}); });
afterAll(async () => { afterAll(async () => {
await apiService.getInstance().login(browser.params.testConfig.admin.email, browser.params.testConfig.admin.password);
await apiService.getInstance().core.sitesApi.deleteSite(site.entry.id, { permanent: true }); await apiService.getInstance().core.sitesApi.deleteSite(site.entry.id, { permanent: true });
}); });

View File

@ -66,6 +66,7 @@ describe('Viewer', () => {
}); });
afterAll(async () => { afterAll(async () => {
await apiService.getInstance().login(browser.params.testConfig.admin.email, browser.params.testConfig.admin.password);
await apiService.getInstance().core.sitesApi.deleteSite(site.entry.id, { permanent: true }); await apiService.getInstance().core.sitesApi.deleteSite(site.entry.id, { permanent: true });
}); });

View File

@ -64,6 +64,7 @@ describe('Viewer', () => {
}); });
afterAll(async () => { afterAll(async () => {
await apiService.getInstance().login(browser.params.testConfig.admin.email, browser.params.testConfig.admin.password);
await apiService.getInstance().core.sitesApi.deleteSite(site.entry.id, { permanent: true }); await apiService.getInstance().core.sitesApi.deleteSite(site.entry.id, { permanent: true });
}); });

View File

@ -66,6 +66,7 @@ describe('Viewer', () => {
}); });
afterAll(async () => { afterAll(async () => {
await apiService.getInstance().login(browser.params.testConfig.admin.email, browser.params.testConfig.admin.password);
await apiService.getInstance().core.sitesApi.deleteSite(site.entry.id, { permanent: true }); await apiService.getInstance().core.sitesApi.deleteSite(site.entry.id, { permanent: true });
await navigationBarPage.clickLogoutButton(); await navigationBarPage.clickLogoutButton();
}); });

View File

@ -14,6 +14,9 @@
"paths": { "paths": {
"@alfresco/adf-testing": [ "@alfresco/adf-testing": [
"../lib/testing" "../lib/testing"
],
"@alfresco/adf-process-services-cloud": [
"../lib/process-services-cloud"
] ]
} }
}, },

View File

@ -69,7 +69,7 @@
[contextMenuActions]="false" [contextMenuActions]="false"
[contentActions]="false" [contentActions]="false"
[allowDropFiles]="false" [allowDropFiles]="false"
[sorting]="'server'" sortingMode="server"
[where]="where" [where]="where"
(folderChange)="onFolderChange($event)" (folderChange)="onFolderChange($event)"
(ready)="onFolderLoaded()" (ready)="onFolderLoaded()"

View File

@ -131,7 +131,7 @@ describe('ContentNodeSelectorComponent', () => {
}); });
it('should the document list use the server ordering', () => { it('should the document list use the server ordering', () => {
expect(component.documentList.sorting).toBe('server'); expect(component.documentList.sortingMode).toBe('server');
}); });
it('should trigger the select event when selection has been made', (done) => { it('should trigger the select event when selection has been made', (done) => {

View File

@ -13,7 +13,7 @@
[noPermission]="noPermission" [noPermission]="noPermission"
[showHeader]="showHeader" [showHeader]="showHeader"
[rowMenuCacheEnabled]="false" [rowMenuCacheEnabled]="false"
[stickyHeader]="stickyHeader" [stickyHeader]="stickyHeader"
[allowFiltering]="allowFiltering" [allowFiltering]="allowFiltering"
(showRowContextMenu)="onShowRowContextMenu($event)" (showRowContextMenu)="onShowRowContextMenu($event)"
(showRowActionsMenu)="onShowRowActionsMenu($event)" (showRowActionsMenu)="onShowRowActionsMenu($event)"

View File

@ -1451,7 +1451,7 @@ describe('DocumentList', () => {
where: undefined, where: undefined,
maxItems: 25, maxItems: 25,
skipCount: 0, skipCount: 0,
orderBy: [ 'name ASC' ], orderBy: ['nodeType DESC', 'name asc' ],
rootFolderId: 'fake-id' rootFolderId: 'fake-id'
}, ['test-include']); }, ['test-include']);
}); });
@ -1467,14 +1467,14 @@ describe('DocumentList', () => {
where: '(isFolder=true)', where: '(isFolder=true)',
maxItems: 25, maxItems: 25,
skipCount: 0, skipCount: 0,
orderBy: [ 'name ASC' ], orderBy: ['nodeType DESC', 'name asc' ],
rootFolderId: 'fake-id' rootFolderId: 'fake-id'
}, ['test-include']); }, ['test-include']);
}); });
it('should add orderBy in the server request', () => { it('should add orderBy in the server request', () => {
documentList.includeFields = ['test-include']; documentList.includeFields = ['test-include'];
documentList.orderBy = ['size DESC']; documentList.sorting = ['size', 'DESC'];
documentList.currentFolderId = 'fake-id'; documentList.currentFolderId = 'fake-id';
documentList.where = null; documentList.where = null;
@ -1484,7 +1484,7 @@ describe('DocumentList', () => {
maxItems: 25, maxItems: 25,
skipCount: 0, skipCount: 0,
where: undefined, where: undefined,
orderBy: ['size DESC'], orderBy: ['nodeType DESC', 'size DESC'],
rootFolderId: 'fake-id' rootFolderId: 'fake-id'
}, ['test-include']); }, ['test-include']);
}); });
@ -1502,7 +1502,7 @@ describe('DocumentList', () => {
expect(documentListService.getFolder).toHaveBeenCalledWith(null, { expect(documentListService.getFolder).toHaveBeenCalledWith(null, {
maxItems: 25, maxItems: 25,
skipCount: 0, skipCount: 0,
orderBy: [ 'name ASC' ], orderBy: ['nodeType DESC', 'name asc' ],
rootFolderId: 'folder-id', rootFolderId: 'folder-id',
where: undefined where: undefined
}, undefined); }, undefined);

View File

@ -183,7 +183,14 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
* override the default sorting detected by the component based on columns. * override the default sorting detected by the component based on columns.
*/ */
@Input() @Input()
sorting = ['nodeType', 'DESC']; sorting = ['name', 'asc'];
/** Defines default sorting. The format is an array of strings `[key direction, otherKey otherDirection]`
* i.e. `['name desc', 'nodeType asc']` or `['name asc']`. Set this value if you want a base
* rule to be added to the sorting apart from the one driven by the header.
*/
@Input()
additionalSorting = ['nodeType DESC'];
/** Defines sorting mode. Can be either `client` (items in the list /** Defines sorting mode. Can be either `client` (items in the list
* are sorted client-side) or `server` (the ordering supplied by the * are sorted client-side) or `server` (the ordering supplied by the
@ -255,6 +262,10 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
set currentFolderId(currentFolderId: string) { set currentFolderId(currentFolderId: string) {
if (this._currentFolderId !== currentFolderId) { if (this._currentFolderId !== currentFolderId) {
this._currentFolderId = currentFolderId; this._currentFolderId = currentFolderId;
if (this.sorting) {
const [key, direction] = this.sorting;
this.orderBy = this.buildOrderByArray(key, direction);
}
if (this.data) { if (this.data) {
this.data.loadPage(null, false); this.data.loadPage(null, false);
this.resetNewFolderPagination(); this.resetNewFolderPagination();
@ -456,10 +467,16 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
ngOnChanges(changes: SimpleChanges) { ngOnChanges(changes: SimpleChanges) {
this.resetSelection(); this.resetSelection();
if (changes.sorting && changes.sorting.currentValue) {
const [key, direction] = changes.sorting.currentValue;
this.orderBy = this.buildOrderByArray(key, direction);
}
if (this.data) { if (this.data) {
this.data.thumbnails = this.thumbnails; this.data.thumbnails = this.thumbnails;
} }
if (changes.sortingMode && !changes.sortingMode.firstChange && this.data) { if (changes.sortingMode && !changes.sortingMode.firstChange && this.data) {
this.data.sortingMode = changes.sortingMode.currentValue; this.data.sortingMode = changes.sortingMode.currentValue;
} }
@ -475,7 +492,6 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
if (this.data) { if (this.data) {
if (changes.node && changes.node.currentValue) { if (changes.node && changes.node.currentValue) {
const merge = this._pagination ? this._pagination.merge : false; const merge = this._pagination ? this._pagination.merge : false;
this.data.loadPage(changes.node.currentValue, merge); this.data.loadPage(changes.node.currentValue, merge);
this.onDataReady(changes.node.currentValue); this.onDataReady(changes.node.currentValue);
} else if (changes.imageResolver) { } else if (changes.imageResolver) {
@ -646,6 +662,7 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
} }
loadFolder() { loadFolder() {
if (!this._pagination.merge) { if (!this._pagination.merge) {
this.setLoadingState(true); this.setLoadingState(true);
} }
@ -685,13 +702,16 @@ export class DocumentListComponent implements OnInit, OnChanges, OnDestroy, Afte
} }
onSortingChanged(event: CustomEvent) { onSortingChanged(event: CustomEvent) {
const flattenExtraSortingOption = this.sorting.join(' '); this.orderBy = this.buildOrderByArray(event.detail.key, event.detail.direction);
const orderArray = [flattenExtraSortingOption];
orderArray.push(''.concat(event.detail.key, ' ', event.detail.direction));
this.orderBy = orderArray;
this.reload(); this.reload();
} }
private buildOrderByArray(currentKey: string, currentDirection: string ): string[] {
const orderArray = [...this.additionalSorting];
orderArray.push(''.concat(currentKey, ' ', currentDirection));
return orderArray;
}
/** /**
* Creates a set of predefined columns. * Creates a set of predefined columns.
*/ */

View File

@ -51,6 +51,7 @@ export abstract class BaseQueryBuilderService {
filterQueries: FilterQuery[] = []; filterQueries: FilterQuery[] = [];
paging: { maxItems?: number; skipCount?: number } = null; paging: { maxItems?: number; skipCount?: number } = null;
sorting: Array<SearchSortingDefinition> = []; sorting: Array<SearchSortingDefinition> = [];
sortingOptions: Array<SearchSortingDefinition> = [];
protected userFacetBuckets: { [key: string]: Array<FacetFieldBucket> } = {}; protected userFacetBuckets: { [key: string]: Array<FacetFieldBucket> } = {};
@ -93,6 +94,7 @@ export abstract class BaseQueryBuilderService {
this.userFacetBuckets = {}; this.userFacetBuckets = {};
if (this.config.sorting) { if (this.config.sorting) {
this.sorting = this.config.sorting.defaults || []; this.sorting = this.config.sorting.defaults || [];
this.sortingOptions = this.config.sorting.options || [];
} }
} }
} }

View File

@ -149,6 +149,20 @@ describe('SearchHeaderComponent', () => {
await fixture.whenStable(); await fixture.whenStable();
}); });
it('should execute a new query when a new sorting is requested', async (done) => {
spyOn(alfrescoApiService.searchApi, 'search').and.returnValue(Promise.resolve(fakeNodePaging));
spyOn(queryBuilder, 'buildQuery').and.returnValue({});
component.update.subscribe((newNodePaging) => {
expect(newNodePaging).toBe(fakeNodePaging);
done();
});
const skipCount = new SimpleChange(null, '123-asc', false);
component.ngOnChanges({ 'sorting': skipCount });
fixture.detectChanges();
await fixture.whenStable();
});
it('should emit the clear event when no filter has been selected', async (done) => { it('should emit the clear event when no filter has been selected', async (done) => {
spyOn(queryBuilder, 'isNoFilterActive').and.returnValue(true); spyOn(queryBuilder, 'isNoFilterActive').and.returnValue(true);
spyOn(alfrescoApiService.searchApi, 'search').and.returnValue(Promise.resolve(fakeNodePaging)); spyOn(alfrescoApiService.searchApi, 'search').and.returnValue(Promise.resolve(fakeNodePaging));

View File

@ -67,6 +67,9 @@ export class SearchHeaderComponent implements OnInit, OnChanges, OnDestroy {
@Input() @Input()
skipCount: number; skipCount: number;
@Input()
sorting: string = null;
/** Emitted when the result of the filter is received from the API. */ /** Emitted when the result of the filter is received from the API. */
@Output() @Output()
update: EventEmitter<NodePaging> = new EventEmitter(); update: EventEmitter<NodePaging> = new EventEmitter();
@ -136,6 +139,14 @@ export class SearchHeaderComponent implements OnInit, OnChanges, OnDestroy {
this.searchHeaderQueryBuilder.setupCurrentPagination(actualMaxItems, actualSkipCount); this.searchHeaderQueryBuilder.setupCurrentPagination(actualMaxItems, actualSkipCount);
} }
if (changes['sorting'] && changes['sorting'].currentValue) {
const [key, value] = changes['sorting'].currentValue.split('-');
if (key === this.col.key) {
this.searchHeaderQueryBuilder.setSorting(key, value);
}
}
} }
ngOnDestroy() { ngOnDestroy() {

View File

@ -23,6 +23,7 @@ import { SearchCategory } from './search-category.interface';
import { MinimalNode, QueryBody } from '@alfresco/js-api'; import { MinimalNode, QueryBody } from '@alfresco/js-api';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { SearchSortingDefinition } from './search-sorting-definition.interface';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -77,6 +78,22 @@ export class SearchHeaderQueryBuilderService extends BaseQueryBuilderService {
} }
} }
setSorting(column: string, direction: string) {
const optionAscending = direction.toLocaleLowerCase() === 'asc' ? true : false;
const fieldValue = this.getSortingFieldFromColumnName(column);
const currentSort: SearchSortingDefinition = { key: column, label: 'current', type: 'FIELD', field: fieldValue, ascending: optionAscending};
this.sorting = [currentSort];
this.execute();
}
private getSortingFieldFromColumnName(columnName: string) {
if (this.sortingOptions.length > 0) {
const sortOption: SearchSortingDefinition = this.sortingOptions.find((option: SearchSortingDefinition) => option.key === columnName);
return sortOption.field;
}
return '';
}
getCategoryForColumn(columnKey: string): SearchCategory { getCategoryForColumn(columnKey: string): SearchCategory {
let foundCategory = null; let foundCategory = null;
if (this.categories !== null) { if (this.categories !== null) {

View File

@ -543,11 +543,11 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck,
const current = this.data.getSorting(); const current = this.data.getSorting();
let newDirection = 'asc'; let newDirection = 'asc';
if (current && column.key === current.key) { if (current && column.key === current.key) {
newDirection = current.direction === 'asc' ? 'desc' : 'asc'; newDirection = current.direction?.toLowerCase() === 'asc' ? 'desc' : 'asc';
} }
this.sorting = [column.key, newDirection]; this.sorting = [column.key, newDirection];
this.data.setSorting(new DataSorting(column.key, newDirection)); this.data.setSorting(new DataSorting(column.key, newDirection));
this.emitSortingChangedEvent(column.sortingKey, newDirection); this.emitSortingChangedEvent(column.key, column.sortingKey, newDirection);
} }
this.keyManager.updateActiveItem(0); this.keyManager.updateActiveItem(0);
@ -639,7 +639,7 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck,
isColumnSorted(col: DataColumn, direction: string): boolean { isColumnSorted(col: DataColumn, direction: string): boolean {
if (col && direction) { if (col && direction) {
const sorting = this.data.getSorting(); const sorting = this.data.getSorting();
return sorting && sorting.key === col.key && sorting.direction === direction; return sorting && sorting.key === col.key && sorting.direction.toLocaleLowerCase() === direction;
} }
return false; return false;
} }
@ -771,9 +771,10 @@ export class DataTableComponent implements AfterContentInit, OnChanges, DoCheck,
this.elementRef.nativeElement.dispatchEvent(domEvent); this.elementRef.nativeElement.dispatchEvent(domEvent);
} }
private emitSortingChangedEvent(key: string, direction: string) { private emitSortingChangedEvent(column: string, key: string, direction: string) {
const domEvent = new CustomEvent('sorting-changed', { const domEvent = new CustomEvent('sorting-changed', {
detail: { detail: {
column,
key, key,
direction direction
}, },

View File

@ -51,7 +51,7 @@ export class TaskFiltersCloudComponentPage {
} }
getTaskFilterLocatorByFilterName(filterName: string): ElementFinder { getTaskFilterLocatorByFilterName(filterName: string): ElementFinder {
return element(by.css(`button[data-automation-id="${filterName}_filter"]`)); return element.all(by.css(`button[data-automation-id="${filterName}_filter"]`)).first();
} }
} }