[ADF-3677] Add highlight configuration to SearchQueryBuilder (#4358)

* [ADF-3677] Add highlight configuration to SearchQueryBuilder

* highlight property in search

* Update files.component.ts

* remove comma

* highlight missspell

* fix schhema json highilight

* fix test

* fix lint

* fix lint

* fix lint

* fix search sorting tests

* fix search sorting

* fix lint

* remove useless test

* check for null nodes

* remove duplicated test

* lint

* fix sorting tests

* remove test not search component related
This commit is contained in:
davidcanonieto 2019-03-17 17:23:07 +00:00 committed by Eugenio Romano
parent d6f391c40e
commit 8dc9eba4c7
19 changed files with 401 additions and 444 deletions

View File

@ -249,7 +249,23 @@
}
}
}
]
],
"highlight": {
"prefix": " ",
"postfix": " ",
"mergeContiguous": true,
"fields": [
{
"field": "cm:title"
},
{
"field": "description",
"prefix": "(",
"postfix": ")"
}
]
}
},
"pagination": {
"size": 20,

View File

@ -1,10 +1,11 @@
<div class="adf-container">
<mat-accordion *ngIf="showRecentFiles" class="adf-container-recent">
<mat-expansion-panel hideToggle="true">
<mat-expansion-panel-header >
<mat-expansion-panel hideToggle="true">
<mat-expansion-panel-header>
<mat-panel-title>
{{ 'DOCUMENT_LIST.RECENT.TITLE' | translate }}<mat-icon>history</mat-icon>
{{ 'DOCUMENT_LIST.RECENT.TITLE' | translate }}
<mat-icon>history</mat-icon>
</mat-panel-title>
</mat-expansion-panel-header>
@ -31,7 +32,8 @@
</adf-sites-dropdown>
</div>
<div id="document-list-container" class="adf-document-list-container" fxLayout="row" fxLayoutAlign="start stretch" fxLayoutGap="16px">
<div id="document-list-container" class="adf-document-list-container" fxLayout="row" fxLayoutAlign="start stretch"
fxLayoutGap="16px">
<adf-upload-drag-area fxFlex="1 1 auto"
[disabled]="disableDragArea"
[acceptedFilesType]="getFileFiltering()"
@ -67,8 +69,12 @@
data-automation-id="document-list-grid-view"
title="{{ 'DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}"
(click)="toggleGalleryView()">
<mat-icon *ngIf="displayMode === 'list'" matTooltip="{{ 'DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}">view_comfy</mat-icon>
<mat-icon *ngIf="displayMode === 'gallery'" matTooltip="{{ 'DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list</mat-icon>
<mat-icon *ngIf="displayMode === 'list'"
matTooltip="{{ 'DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}">view_comfy
</mat-icon>
<mat-icon *ngIf="displayMode === 'gallery'"
matTooltip="{{ 'DOCUMENT_LIST.TOOLBAR.LISTVIEW' | translate }}">list
</mat-icon>
</button>
<button
data-automation-id="create-new-folder"
@ -136,7 +142,8 @@
</button>
</div>
<button fxFlex="1 0 auto" mat-icon-button [matMenuTriggerFor]="themePicker" matTooltip="{{ 'DOCUMENT_LIST.TOOLBAR.THEME' | translate }}">
<button fxFlex="1 0 auto" mat-icon-button [matMenuTriggerFor]="themePicker"
matTooltip="{{ 'DOCUMENT_LIST.TOOLBAR.THEME' | translate }}">
<mat-icon>format_color_fill</mat-icon>
</button>
@ -148,8 +155,12 @@
</mat-menu>
<button mat-icon-button (click)="showVersions = !showVersions" class="adf-show-versions-button">
<mat-icon *ngIf="!showVersions" matTooltip="{{ 'DOCUMENT_LIST.TOOLBAR.SHOW_VERSION' | translate }}">chevron_left</mat-icon>
<mat-icon *ngIf="showVersions" matTooltip="{{ 'DOCUMENT_LIST.TOOLBAR.HIDE_VERSION' | translate }}">chevron_right</mat-icon>
<mat-icon *ngIf="!showVersions" matTooltip="{{ 'DOCUMENT_LIST.TOOLBAR.SHOW_VERSION' | translate }}">
chevron_left
</mat-icon>
<mat-icon *ngIf="showVersions" matTooltip="{{ 'DOCUMENT_LIST.TOOLBAR.HIDE_VERSION' | translate }}">
chevron_right
</mat-icon>
</button>
<adf-toolbar-divider fxFlex="0 0 auto" fxHide fxShow.lt-sm="true"></adf-toolbar-divider>
@ -160,7 +171,7 @@
<mat-menu #menu="matMenu">
<button mat-menu-item
(click)="toggleGalleryView()">
<mat-icon *ngIf="displayMode === 'list'" >view_comfy</mat-icon>
<mat-icon *ngIf="displayMode === 'list'">view_comfy</mat-icon>
<mat-icon *ngIf="displayMode === 'gallery'">list</mat-icon>
<span>{{ 'DOCUMENT_LIST.TOOLBAR.CARDVIEW' | translate }}</span>
</button>
@ -225,12 +236,12 @@
(permissionError)="handlePermissionError($event)"
(name-click)="documentList.onNodeDblClick($event.detail?.node)">
<adf-custom-no-permission-template *ngIf="enableCustomPermissionMessage">
<h1>You don't have permissions</h1>
<h1>You don't have permissions</h1>
</adf-custom-no-permission-template>
<adf-custom-empty-content-template *ngIf="disableDragArea">
<div class="adf-empty_template">
<div class="adf-no-result-message">{{ 'SEARCH.NO_RESULT' | translate }}</div>
</div>
<div class="adf-empty_template">
<div class="adf-no-result-message">{{ 'SEARCH.NO_RESULT' | translate }}</div>
</div>
</adf-custom-empty-content-template>
<data-columns>
<data-column
@ -271,6 +282,14 @@
title="{{'DOCUMENT_LIST.COLUMNS.SIZE' | translate}}"
type="fileSize">
</data-column>
<data-column
key="search"
title="Search">
<ng-template let-entry="$implicit">
<div *ngIf="searchTerm" [innerHTML]="searchResultsHighlight(entry.row.node.entry.search) | highlight:searchTerm">
</div>
</ng-template>
</data-column>
<!-- Notes: has performance overhead due to multiple files/folders causing separate HTTP calls to get tags -->
<!--
<data-column
@ -422,7 +441,7 @@
<adf-info-drawer-layout *ngIf="showVersions" class="adf-manage-versions-sidebar" fxFlex="0 0 auto">
<div info-drawer-content>
<adf-info-drawer [title]="'Details'" *ngIf="documentList.selection[0]" >
<adf-info-drawer [title]="'Details'" *ngIf="documentList.selection[0]">
<adf-info-drawer-tab [label]="'Properties'">
<adf-content-metadata-card
[node]="documentList.selection[0].entry"
@ -514,7 +533,8 @@
</section>
<section>
<mat-slide-toggle id="adf-extension-filter-upload-switch" [color]="'primary'" [(ngModel)]="acceptedFilesTypeShow">
<mat-slide-toggle id="adf-extension-filter-upload-switch" [color]="'primary'"
[(ngModel)]="acceptedFilesTypeShow">
{{'DOCUMENT_LIST.CUSTOM_FILTER' | translate}}
</mat-slide-toggle>
</section>
@ -532,13 +552,14 @@
</section>
<section>
<mat-slide-toggle id="adf-document-list-enable-drop-files" [color]="'primary'" [(ngModel)]="allowDropFiles" (click)="toggleAllowDropFiles()" >
<mat-slide-toggle id="adf-document-list-enable-drop-files" [color]="'primary'" [(ngModel)]="allowDropFiles"
(click)="toggleAllowDropFiles()">
{{'DOCUMENT_LIST.ALLOW_DROP_FILES' | translate}}
</mat-slide-toggle>
</section>
<section>
<mat-slide-toggle id="adf-version-upload-switch" [color]="'primary'" [(ngModel)]="versioning">
<mat-slide-toggle id="adf-version-upload-switch" [color]="'primary'" [(ngModel)]="versioning">
{{'DOCUMENT_LIST.ENABLE_VERSIONING' | translate}}
</mat-slide-toggle>
</section>
@ -656,4 +677,4 @@
</div>
</div>
<adf-file-uploading-dialog #fileDialog (error)="openSnackMessage($event)" ></adf-file-uploading-dialog>
<adf-file-uploading-dialog #fileDialog (error)="openSnackMessage($event)"></adf-file-uploading-dialog>

View File

@ -28,7 +28,7 @@ import {
AlfrescoApiService, AuthenticationService, AppConfigService, AppConfigValues, ContentService, TranslationService,
FileUploadEvent, FolderCreatedEvent, LogService, NotificationService,
UploadService, DataColumn, DataRow, UserPreferencesService,
PaginationComponent, FormValues, DisplayMode, InfinitePaginationComponent
PaginationComponent, FormValues, DisplayMode, InfinitePaginationComponent, HighlightDirective
} from '@alfresco/adf-core';
import {
@ -46,6 +46,7 @@ import { MetadataDialogAdapterComponent } from './metadata-dialog-adapter.compon
import { Subscription } from 'rxjs';
import { PreviewService } from '../../services/preview.service';
import { debounceTime } from 'rxjs/operators';
import { SearchEntry } from '@alfresco/js-api';
const DEFAULT_FOLDER_TO_SHOW = '-my-';
@ -150,6 +151,9 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
@Input()
showNameColumn = true;
@Input()
searchTerm = '';
@Output()
documentListReady: EventEmitter<any> = new EventEmitter();
@ -180,6 +184,9 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
@ViewChild(InfinitePaginationComponent)
infinitePaginationComponent: InfinitePaginationComponent;
@ViewChild(HighlightDirective)
highlighter: HighlightDirective;
permissionsStyle: PermissionStyleModel[] = [];
infiniteScrolling: boolean;
warnOnMultipleUploads = false;
@ -581,4 +588,10 @@ export class FilesComponent implements OnInit, OnChanges, OnDestroy {
);
});
}
searchResultsHighlight(search: SearchEntry): string {
if (search && search.highlight) {
return search.highlight.map((currentHighlight) => currentHighlight.snippets).join(', ');
}
}
}

View File

@ -21,6 +21,7 @@
[nodeResult]="data"
[disableDragArea]="true"
[pagination]="pagination"
[searchTerm]="searchedWord"
(changedPageSize)="onRefreshPagination($event)"
(changedPageNumber)="onRefreshPagination($event)"
(turnedNextPage)="onRefreshPagination($event)"

View File

@ -19,6 +19,7 @@ Represents a main container component for custom search and faceted search setti
- [Categories and widgets](#categories-and-widgets)
- [Facet Fields](#facet-fields)
- [Facet Queries](#facet-queries)
- [highlight](#highlight)
- [Facet Intervals](#facet-intervals)
- [See also](#see-also)
@ -420,6 +421,59 @@ Each `intervals` item defined is collected into its collapsible category identif
![Facet Intervals](../../docassets/images/search-facet-intervals.png)
### Highlight
You can configure the Highlight using the `search` entry in the `app.config.json` file.
An example query for search highlighting could look like this:
```json
{
"search": {
"highlight": {
"prefix": "¿",
"postfix": "?",
"mergeContiguous": true,
"fields": [
{
"field": "cm:title"
},
{
"field": "description",
"prefix": "(",
"postfix": ")"
}
]
}
}
}
```
The example above changes the highlighting prefix and postfix from the default for all fields to ¿? and just for the "description" field to (). The highlight information will then be added in each node entry response; here is an example partial response:
```json
"entry": {
"createdAt": "2016-10-12T15:24:31.202+0000",
"isFolder": true,
"search": {
"score": 1,
"highlight": [
{
"field": "cm:title",
"snippets": [
"Customized ¿Workflow? Process Definitions"
]
},
{
"field": "description",
"snippets": [
"Customized (Workflow) Process Definitions"
]
}
]
},
```
## See also
- [Search Query Builder service](../services/search-query-builder.service.md)

View File

@ -30,8 +30,10 @@ export class NodeActions {
let promises = [];
let nodeList;
for (let i = 0; i < (numberOfElements - 1) ; i++ ) {
promises.push(alfrescoJsApi.core.nodesApi.getNode(idList[i]));
for (let i = 0; i < (numberOfElements - 1); i++) {
if (idList[i] && idList[i].trim() !== '') {
promises.push(alfrescoJsApi.core.nodesApi.getNode(idList[i]));
}
}
nodeList = await Promise.all(promises);
return nodeList;

View File

@ -25,6 +25,7 @@ import { DropActions } from '../../actions/drop.actions';
import { by, element, protractor, $$, browser } from 'protractor';
import path = require('path');
import { DateUtil } from '../../util/dateUtil';
export class ContentServicesPage {
@ -163,10 +164,6 @@ export class ContentServicesPage {
return this;
}
getElementsDisplayedCreated() {
return this.contentList.dataTablePage().getAllRowsColumnValues('Created');
}
getElementsDisplayedSize() {
return this.contentList.dataTablePage().getAllRowsColumnValues('Size');
}
@ -202,31 +199,32 @@ export class ContentServicesPage {
checkElementsSortedAsc(elements) {
let sorted = true;
let i = 0;
let compareNumbers = false;
if (elements && elements[0] && typeof elements[0] === 'number') {
compareNumbers = true;
}
while (elements.length > 1 && sorted === true && i < (elements.length - 1)) {
const left = compareNumbers ? elements[i] : JSON.stringify(elements[i]);
const right = compareNumbers ? elements[i + 1] : JSON.stringify(elements[i + 1]);
const left = DateUtil.parse(elements[i], 'DD-MM-YY');
const right = DateUtil.parse(elements[i + 1], 'DD-MM-YY');
if (left > right) {
sorted = false;
}
i++;
}
return sorted;
}
checkElementsSortedDesc(elements) {
let sorted = true;
let i = 0;
while (elements.length > 1 && sorted === true && i < (elements.length - 1)) {
if (elements[i] < elements[i + 1]) {
const left = DateUtil.parse(elements[i], 'DD-MM-YY');
const right = DateUtil.parse(elements[i + 1], 'DD-MM-YY');
if (left < right) {
sorted = false;
}
i++;
}
return sorted;
}

View File

@ -194,6 +194,10 @@ export class DataTableComponentPage {
return firstNode.getText();
}
geCellElementDetail(detail) {
return element.all(by.css(`adf-datatable div[title="${detail}"] span`));
}
sortByColumn(sortOrder, column) {
let locator = by.css(`div[data-automation-id="auto_id_${column}"]`);
Util.waitUntilElementIsVisible(element(locator));

View File

@ -18,7 +18,7 @@
import { Util } from '../../util/util';
import { DataTableComponentPage } from './dataTableComponentPage';
import { SearchSortingPickerPage } from './content-services/search/components/search-sortingPicker.page';
import { element, by, protractor } from 'protractor';
import { element, by } from 'protractor';
import { ContentServicesPage } from './contentServicesPage';
export class SearchResultsPage {
@ -95,22 +95,6 @@ export class SearchResultsPage {
return this;
}
sortAndCheckListIsOrderedByName(sortOrder) {
let deferred = protractor.promise.defer();
this.sortByName(sortOrder);
this.dataTable.waitForTableBody();
if (sortOrder === true) {
this.checkListIsOrderedByNameAsc().then((result) => {
deferred.fulfill(result);
});
} else {
this.checkListIsOrderedByNameDesc().then((result) => {
deferred.fulfill(result);
});
}
return deferred.promise;
}
async checkListIsOrderedByNameAsc() {
let list = await this.contentServices.getElementsDisplayedName();
return this.contentServices.checkElementsSortedAsc(list);
@ -121,60 +105,14 @@ export class SearchResultsPage {
return this.contentServices.checkElementsSortedDesc(list);
}
sortAndCheckListIsOrderedByAuthor(alfrescoJsApi, sortOrder) {
let deferred = protractor.promise.defer();
this.sortByAuthor(sortOrder);
this.dataTable.waitForTableBody();
if (sortOrder === true) {
this.checkListIsOrderedByAuthorAsc(alfrescoJsApi).then((result) => {
deferred.fulfill(result);
});
} else {
this.checkListIsOrderedByAuthorDesc(alfrescoJsApi).then((result) => {
deferred.fulfill(result);
});
}
return deferred.promise;
async checkListIsOrderedByAuthorAsc() {
let authorList = await this.dataTable.geCellElementDetail('Created by');
return this.contentServices.checkElementsSortedAsc(authorList);
}
async checkListIsOrderedByAuthorAsc(alfrescoJsApi) {
let list = await this.contentServices.getElementsDisplayedAuthor(alfrescoJsApi);
return this.contentServices.checkElementsSortedAsc(list);
}
async checkListIsOrderedByAuthorDesc(alfrescoJsApi) {
let list = await this.contentServices.getElementsDisplayedAuthor(alfrescoJsApi);
return this.contentServices.checkElementsSortedDesc(list);
}
sortAndCheckListIsOrderedByCreated(sortOrder) {
let deferred = protractor.promise.defer();
this.sortByCreated(sortOrder);
this.dataTable.waitForTableBody();
if (sortOrder === true) {
this.checkListIsOrderedByCreatedAsc().then((result) => {
deferred.fulfill(result);
});
} else {
this.checkListIsOrderedByCreatedDesc().then((result) => {
deferred.fulfill(result);
});
}
return deferred.promise;
}
async checkListIsOrderedByCreatedAsc() {
let stringList = await this.contentServices.getElementsDisplayedCreated();
let list;
await stringList.forEach((stringDate) => {
list.push(new Date(stringDate));
});
return this.contentServices.checkElementsSortedAsc(list);
}
async checkListIsOrderedByCreatedDesc() {
let list = await this.contentServices.getElementsDisplayedCreated();
return this.contentServices.checkElementsSortedDesc(list);
async checkListIsOrderedByAuthorDesc() {
let authorList = await this.dataTable.geCellElementDetail('Created by');
return this.contentServices.checkElementsSortedDesc(authorList);
}
async checkListIsOrderedBySizeAsc() {

View File

@ -120,7 +120,7 @@ describe('Search Date Range Filter', () => {
.selectTodayDate()
.checkDatePickerIsNotDisplayed();
dateRangeFilter.getFromDate().then((date) => {
fromDate = DateUtil.formatDate('DD-MM-YY', date);
fromDate = DateUtil.formatDate('DD-MM-YY', DateUtil.parse(date, 'DD-MMM-YY'));
});
dateRangeFilter.checkApplyButtonIsDisabled();
@ -129,34 +129,26 @@ describe('Search Date Range Filter', () => {
.selectTodayDate()
.checkDatePickerIsNotDisplayed();
dateRangeFilter.getToDate().then((date) => {
toDate = DateUtil.formatDate('DD-MM-YY', date);
toDate = DateUtil.formatDate('DD-MM-YY', DateUtil.parse(date, 'DD-MMM-YY'), 1);
});
dateRangeFilter.checkApplyButtonIsEnabled()
.clickApplyButton();
searchResults.sortByCreated(true);
browser.controlFlow().execute(async () => {
let firstResult = await dataTable.getFirstElementDetail('Node id');
await this.alfrescoJsApi.login(TestConfig.adf.adminEmail, TestConfig.adf.adminPassword);
await this.alfrescoJsApi.core.nodesApi.getNode(firstResult).then(async (node) => {
let nodeCreation = new Date(node.entry.createdAt);
nodeCreation.setHours(0, 0, 0, 0);
await expect(nodeCreation.getTime() >= DateUtil.parse(fromDate).getTime()).toBe(true);
await expect(nodeCreation.getTime() <= DateUtil.parse(toDate).getTime()).toBe(true);
});
});
searchResults.sortByCreated(false);
browser.controlFlow().execute(async () => {
let firstResult = await dataTable.getFirstElementDetail('Node id');
await this.alfrescoJsApi.login(TestConfig.adf.adminEmail, TestConfig.adf.adminPassword);
await this.alfrescoJsApi.core.nodesApi.getNode(firstResult).then(async (node) => {
let nodeCreation = new Date(node.entry.createdAt);
nodeCreation.setHours(0, 0, 0, 0);
await expect(nodeCreation.getTime() >= DateUtil.parse(fromDate).getTime()).toBe(true);
await expect(nodeCreation.getTime() <= DateUtil.parse(toDate).getTime()).toBe(true);
});
let results = await dataTable.geCellElementDetail('Created');
for (let currentResult of results) {
currentResult.getAttribute('title').then(async (currentDate) => {
let currentDateFormatted = DateUtil.parse(currentDate, 'MMM DD, YYYY, h:mm:ss a');
await expect(currentDateFormatted <= DateUtil.parse(toDate, 'DD-MM-YY')).toBe(true);
await expect(currentDateFormatted >= DateUtil.parse(fromDate, 'DD-MM-YY')).toBe(true);
});
}
});
});

View File

@ -32,6 +32,7 @@ import { FileModel } from '../../models/ACS/fileModel';
import { browser } from 'protractor';
import resources = require('../../util/resources');
import { SearchConfiguration } from '../search.config';
import { DateUtil } from '../../util/dateUtil';
describe('Search Number Range Filter', () => {
@ -171,10 +172,16 @@ describe('Search Number Range Filter', () => {
searchResults.sortBySize(false);
browser.controlFlow().execute(async () => {
let firstResult = await dataTable.getFirstElementDetail('Node id');
await this.alfrescoJsApi.core.nodesApi.getNode(firstResult).then(async (node) => {
await expect(node.entry.content.sizeInBytes <= toSize).toBe(true);
});
let results = await dataTable.geCellElementDetail('Size');
for (let currentResult of results) {
try {
let currentSize = await currentResult.getAttribute('title');
if (currentSize && currentSize.trim() !== '') {
await expect(parseInt(currentSize, 10) <= toSize).toBe(true);
}
} catch (e) {
}
}
});
});
@ -196,10 +203,16 @@ describe('Search Number Range Filter', () => {
searchResults.sortBySize(false);
browser.controlFlow().execute(async () => {
let firstResult = await dataTable.getFirstElementDetail('Node id');
await this.alfrescoJsApi.core.nodesApi.getNode(firstResult).then(async (node) => {
await expect(node.entry.content.sizeInBytes <= toSize).toBe(true);
});
let results = await dataTable.geCellElementDetail('Size');
for (let currentResult of results) {
try {
let currentSize = await currentResult.getAttribute('title');
if (currentSize && currentSize.trim() !== '') {
await expect(parseInt(currentSize, 10) <= toSize).toBe(true);
}
} catch (e) {
}
}
});
searchFilters.checkNameFilterIsDisplayed()
@ -208,63 +221,30 @@ describe('Search Number Range Filter', () => {
searchResults.sortBySize(false);
browser.controlFlow().execute(async () => {
let firstResult = await dataTable.getFirstElementDetail('Node id');
await this.alfrescoJsApi.core.nodesApi.getNode(firstResult).then(async (node) => {
await expect(node.entry.content.sizeInBytes <= toSize).toBe(true);
let name = node.entry.name;
await expect(/z*/i.test(name)).toBe(true);
});
let results = await dataTable.geCellElementDetail('Size');
for (let currentResult of results) {
try {
let currentSize = await currentResult.getAttribute('title');
if (currentSize && currentSize.trim() !== '') {
await expect(parseInt(currentSize, 10) <= toSize).toBe(true);
}
} catch (e) {
}
}
});
});
it('[C276950] Should be able to filter by size (slider) when size range filter is applied', () => {
let sizeSliderFilter = searchFilters.sizeSliderFilterPage();
let toSize = 20;
let sliderSize = 18;
searchFilters.checkSizeSliderFilterIsDisplayed()
.clickSizeSliderFilterHeader()
.checkSizeSliderFilterIsExpanded();
sizeSliderFilter.checkSliderIsDisplayed().setValue(sliderSize);
sizeRangeFilter.checkFromFieldIsDisplayed()
.putFromNumber(0)
.putToNumber(toSize);
expect(sizeRangeFilter.checkApplyButtonIsEnabled()).toBe(true);
sizeRangeFilter.clickApplyButton();
searchResults.sortBySize(false);
searchResults.tableIsLoaded();
browser.controlFlow().execute(async () => {
let firstResult = await dataTable.getFirstElementDetail('Node id');
await this.alfrescoJsApi.core.nodesApi.getNode(firstResult).then(async (node) => {
await expect(node.entry.content.sizeInBytes <= sliderSize).toBe(true);
});
let results = await dataTable.geCellElementDetail('Display name');
for (let currentResult of results) {
try {
let name = await currentResult.getAttribute('title');
if (name && name.trim() !== '') {
await expect(/z*/i.test(name)).toBe(true);
}
} catch (e) {
}
}
});
sizeRangeFilter.checkFromFieldIsDisplayed()
.putFromNumber(1);
expect(sizeRangeFilter.checkApplyButtonIsEnabled()).toBe(true);
sizeRangeFilter.clickApplyButton();
searchResults.sortBySize(true);
searchResults.tableIsLoaded();
browser.controlFlow().execute(async () => {
let firstResult = await dataTable.getFirstElementDetail('Node id');
await this.alfrescoJsApi.core.nodesApi.getNode(firstResult).then(async (node) => {
await expect(node.entry.content.sizeInBytes >= 1).toBe(true);
await expect(node.entry.content.sizeInBytes <= sliderSize).toBe(true);
});
});
sizeRangeFilter.checkFromFieldIsDisplayed()
.putFromNumber(19);
expect(sizeRangeFilter.checkApplyButtonIsEnabled()).toBe(true);
sizeRangeFilter.clickApplyButton();
searchResults.checkNoResultMessageIsDisplayed();
});
it('[C276951] Should not display folders when Size range is applied', () => {
@ -297,11 +277,18 @@ describe('Search Number Range Filter', () => {
searchResults.sortBySize(false);
browser.controlFlow().execute(async () => {
let firstResult = await dataTable.getFirstElementDetail('Node id');
await this.alfrescoJsApi.core.nodesApi.getNode(firstResult).then(async (node) => {
await expect(node.entry.content.sizeInBytes).toEqual(0);
});
let results = await dataTable.geCellElementDetail('Size');
for (let currentResult of results) {
try {
let currentSize = await currentResult.getAttribute('title');
if (currentSize && currentSize.trim() !== '') {
await expect(currentSize === '0').toBe(true);
}
} catch (e) {
}
}
});
});
it('[C277092] Should disable apply button when from field value equal/is bigger than to field value', () => {
@ -328,10 +315,17 @@ describe('Search Number Range Filter', () => {
searchResults.sortBySize(false);
browser.controlFlow().execute(async () => {
let firstResult = await dataTable.getFirstElementDetail('Node id');
await this.alfrescoJsApi.core.nodesApi.getNode(firstResult).then(async (node) => {
await expect(node.entry.content.sizeInBytes <= 1).toBe(true);
});
let results = await dataTable.geCellElementDetail('Size');
for (let currentResult of results) {
try {
let currentSize = await currentResult.getAttribute('title');
if (currentSize && currentSize.trim() !== '') {
await expect(parseInt(currentSize, 10) <= 1000).toBe(true);
}
} catch (e) {
}
}
});
sizeRangeFilter.clickClearButton();
@ -340,10 +334,17 @@ describe('Search Number Range Filter', () => {
expect(sizeRangeFilter.getToNumber()).toEqual('');
browser.controlFlow().execute(async () => {
let firstResult = await dataTable.getFirstElementDetail('Node id');
await this.alfrescoJsApi.core.nodesApi.getNode(firstResult).then(async (node) => {
await expect(node.entry.content.sizeInBytes >= 1).toBe(true);
});
let results = await dataTable.geCellElementDetail('Size');
for (let currentResult of results) {
try {
let currentSize = await currentResult.getAttribute('title');
if (currentSize && currentSize.trim() !== '') {
await expect(parseInt(currentSize, 10) >= 1000).toBe(true);
}
} catch (e) {
}
}
});
});
@ -426,20 +427,18 @@ describe('Search Number Range Filter', () => {
searchResults.sortByCreated(false);
browser.controlFlow().execute(async () => {
let firstResult = await dataTable.getFirstElementDetail('Node id');
await this.alfrescoJsApi.core.nodesApi.getNode(firstResult).then(async (node) => {
await expect((node.entry.createdAt.getFullYear()) <= toYear).toBe(true);
});
let results = await dataTable.geCellElementDetail('Created');
for (let currentResult of results) {
currentResult.getAttribute('title').then(async (currentDate) => {
let currentDateFormatted = DateUtil.parse(currentDate, 'MMM DD, YYYY, h:mm:ss a');
await expect(currentDateFormatted.getFullYear() <= toYear).toBe(true);
await expect(currentDateFormatted.getFullYear() >= fromYear).toBe(true);
});
}
});
searchResults.sortByCreated(true);
browser.controlFlow().execute(async () => {
let firstResult = await dataTable.getFirstElementDetail('Node id');
await this.alfrescoJsApi.core.nodesApi.getNode(firstResult).then(async (node) => {
await expect((node.entry.createdAt.getFullYear()) >= fromYear).toBe(true);
});
});
});
it('[C277139] Should be able to set To field to be exclusive', () => {

View File

@ -123,10 +123,16 @@ describe('Search Number Range Filter', () => {
.tableIsLoaded();
browser.controlFlow().execute(async () => {
let firstResult = await dataTable.getFirstElementDetail('Node id');
await this.alfrescoJsApi.core.nodesApi.getNode(firstResult).then(async (node) => {
await expect(node.entry.content.sizeInBytes <= size).toBe(true);
});
let results = await dataTable.geCellElementDetail('Size');
for (let currentResult of results) {
try {
let currentSize = await currentResult.getAttribute('title');
if (currentSize && currentSize.trim() !== '') {
await expect(parseInt(currentSize, 10) <= 5000).toBe(true);
}
} catch (e) {
}
}
});
sizeSliderFilter.checkSliderIsDisplayed()
@ -136,10 +142,17 @@ describe('Search Number Range Filter', () => {
.tableIsLoaded();
browser.controlFlow().execute(async () => {
let firstResult = await dataTable.getFirstElementDetail('Node id');
await this.alfrescoJsApi.core.nodesApi.getNode(firstResult).then(async (node) => {
await expect(node.entry.content.sizeInBytes >= size).toBe(true);
});
let results = await dataTable.geCellElementDetail('Size');
for (let currentResult of results) {
try {
let currentSize = await currentResult.getAttribute('title');
if (currentSize && currentSize.trim() !== '') {
await expect(parseInt(currentSize, 10) >= 5000).toBe(true);
}
} catch (e) {
}
}
});
});

View File

@ -176,110 +176,40 @@ describe('Search Sorting Picker', () => {
it('[C277280] Should be able to sort the search results by "Name" ASC', () => {
searchFilters.checkSearchFiltersIsDisplayed();
searchFilters.creatorCheckListFiltersPage().filterBy(`${acsUser.firstName} ${acsUser.lastName}`);
expect(searchResults.sortAndCheckListIsOrderedByName(true)).toBe(true);
searchResults.sortByName(true);
expect(searchResults.checkListIsOrderedByNameAsc()).toBe(true);
});
it('[C277281] Should be able to sort the search results by "Name" DESC', () => {
searchFilters.checkSearchFiltersIsDisplayed();
searchFilters.creatorCheckListFiltersPage().filterBy(`${acsUser.firstName} ${acsUser.lastName}`);
expect(searchResults.sortAndCheckListIsOrderedByName(false)).toBe(true);
searchResults.sortByName(false);
expect(searchResults.checkListIsOrderedByNameDesc()).toBe(true);
});
it('[C277282] Should be able to sort the search results by "Author" ASC', () => {
expect(searchResults.sortAndCheckListIsOrderedByAuthor(this.alfrescoJsApi, true)).toBe(true);
searchResults.sortByAuthor(true);
expect(searchResults.checkListIsOrderedByAuthorAsc()).toBe(true);
});
it('[C277283] Should be able to sort the search results by "Author" DESC', () => {
expect(searchResults.sortAndCheckListIsOrderedByAuthor(this.alfrescoJsApi, false)).toBe(true);
});
it('[C277284] Should be able to sort the search results by "Modifier" ASC', () => {
let searchConfiguration = new SearchConfiguration();
jsonFile = searchConfiguration.getConfiguration();
navigationBar.clickConfigEditorButton();
configEditor.clickSearchConfiguration();
configEditor.clickClearButton();
jsonFile.sorting.options.push({ 'key': 'Modifier', 'label': 'Modifier', 'type': 'FIELD', 'field': 'cm:modifier', 'ascending': true });
configEditor.enterBigConfigurationText(JSON.stringify(jsonFile));
configEditor.clickSaveButton();
searchDialog.checkSearchIconIsVisible()
.clickOnSearchIcon()
.enterTextAndPressEnter(search);
searchSortingPicker.checkSortingSelectorIsDisplayed()
.sortBy(true, 'Modifier');
browser.controlFlow().execute(async () => {
let idList = await contentServices.getElementsDisplayedId();
let numberOfElements = await contentServices.numberOfResultsDisplayed();
let nodeList = await nodeActions.getNodesDisplayed(this.alfrescoJsApi, idList, numberOfElements);
let modifierList = [];
for (let i = 0; i < nodeList.length; i++) {
modifierList.push(nodeList[i].entry.modifiedByUser.id);
}
expect(contentServices.checkElementsSortedAsc(modifierList)).toBe(true);
});
});
it('[C277285] Should be able to sort the search results by "Modifier" DESC', () => {
let searchConfiguration = new SearchConfiguration();
jsonFile = searchConfiguration.getConfiguration();
navigationBar.clickConfigEditorButton();
configEditor.clickSearchConfiguration();
configEditor.clickClearButton();
jsonFile.sorting.options.push({ 'key': 'Modifier', 'label': 'Modifier', 'type': 'FIELD', 'field': 'cm:modifier', 'ascending': true });
configEditor.enterBigConfigurationText(JSON.stringify(jsonFile));
configEditor.clickSaveButton();
searchDialog.checkSearchIconIsVisible()
.clickOnSearchIcon()
.enterTextAndPressEnter(search);
searchSortingPicker.checkSortingSelectorIsDisplayed()
.sortBy(false, 'Modifier');
browser.controlFlow().execute(async () => {
let idList = await contentServices.getElementsDisplayedId();
let numberOfElements = await contentServices.numberOfResultsDisplayed();
let nodeList = await nodeActions.getNodesDisplayed(this.alfrescoJsApi, idList, numberOfElements);
let modifierList = [];
for (let i = 0; i < nodeList.length; i++) {
modifierList.push(nodeList[i].entry.modifiedByUser.id);
}
expect(contentServices.checkElementsSortedDesc(modifierList)).toBe(true);
});
searchResults.sortByAuthor(false);
expect(searchResults.checkListIsOrderedByAuthorDesc()).toBe(true);
});
it('[C277286] Should be able to sort the search results by "Created Date" ASC', () => {
searchResults.sortByCreated(true);
browser.controlFlow().execute(async () => {
let idList = await contentServices.getElementsDisplayedId();
let numberOfElements = await contentServices.numberOfResultsDisplayed();
let nodeList = await nodeActions.getNodesDisplayed(this.alfrescoJsApi, idList, numberOfElements);
let dateList = [];
for (let i = 0; i < nodeList.length; i++) {
dateList.push(new Date(nodeList[i].entry.createdAt));
}
expect(contentServices.checkElementsSortedAsc(dateList)).toBe(true);
let results = await searchResults. dataTable.geCellElementDetail('Created');
expect(contentServices.checkElementsSortedAsc(results)).toBe(true);
});
});
it('[C277287] Should be able to sort the search results by "Created Date" DESC', () => {
searchResults.sortByCreated(false);
browser.controlFlow().execute(async () => {
let idList = await contentServices.getElementsDisplayedId();
let numberOfElements = await contentServices.numberOfResultsDisplayed();
let nodeList = await nodeActions.getNodesDisplayed(this.alfrescoJsApi, idList, numberOfElements);
let dateList = [];
for (let i = 0; i < nodeList.length; i++) {
dateList.push(new Date(nodeList[i].entry.createdAt));
}
expect(contentServices.checkElementsSortedDesc(dateList)).toBe(true);
let results = await searchResults. dataTable.geCellElementDetail('Created');
expect(contentServices.checkElementsSortedDesc(results)).toBe(true);
});
});
@ -313,36 +243,6 @@ describe('Search Sorting Picker', () => {
});
});
it('[C277289] Should be able to sort the search results by "Modified Date" DESC', () => {
let searchConfiguration = new SearchConfiguration();
jsonFile = searchConfiguration.getConfiguration();
navigationBar.clickConfigEditorButton();
configEditor.clickSearchConfiguration();
configEditor.clickClearButton();
jsonFile.sorting.options.push({ 'key': 'Modified Date', 'label': 'Modified Date', 'type': 'FIELD', 'field': 'cm:modified', 'ascending': true });
configEditor.enterBigConfigurationText(JSON.stringify(jsonFile));
configEditor.clickSaveButton();
searchDialog.checkSearchIconIsVisible()
.clickOnSearchIcon()
.enterTextAndPressEnter(search);
searchSortingPicker.checkSortingSelectorIsDisplayed()
.sortBy(false, 'Modified Date');
browser.controlFlow().execute(async () => {
let idList = await contentServices.getElementsDisplayedId();
let numberOfElements = await contentServices.numberOfResultsDisplayed();
let nodeList = await nodeActions.getNodesDisplayed(this.alfrescoJsApi, idList, numberOfElements);
let modifiedDateList = [];
for (let i = 0; i < nodeList.length; i++) {
modifiedDateList.push(new Date(nodeList[i].entry.modifiedAt));
}
expect(contentServices.checkElementsSortedAsc(modifiedDateList)).toBe(false);
});
});
it('[C277290] Should be able to sort the search results by "Size" ASC', () => {
searchResults.sortBySize(true);
expect(searchResults.checkListIsOrderedBySizeAsc()).toBe(true);

View File

@ -179,101 +179,6 @@ describe('Search component - Search Page', () => {
searchResultPage.checkNoResultMessageIsDisplayed();
});
describe('Sorting', () => {
beforeEach(() => {
searchDialog
.clickOnSearchIcon()
.enterTextAndPressEnter(search.active.base);
});
afterEach(async (done) => {
await browser.refresh();
done();
});
it('[C272803] Should be able to sort results by name (Ascending)', () => {
searchResultPage.checkContentIsDisplayed(search.active.secondFile);
searchResultPage.sortAndCheckListIsOrderedByName(true).then((result) => {
expect(result).toEqual(true);
});
});
it('[C272804] Should be able to sort results by name (Descending)', () => {
searchResultPage.checkContentIsDisplayed(search.active.secondFile);
searchResultPage.sortAndCheckListIsOrderedByName(false).then((result) => {
expect(result).toEqual(true);
});
});
it('[C272805] Should be able to sort results by author (Ascending)', () => {
searchResultPage.checkContentIsDisplayed(search.active.secondFile);
searchResultPage.sortAndCheckListIsOrderedByAuthor(this.alfrescoJsApi, true).then((result) => {
expect(result).toEqual(true);
});
});
it('[C272806] Should be able to sort results by author (Descending)', () => {
searchResultPage.checkContentIsDisplayed(search.active.secondFile);
searchResultPage.sortAndCheckListIsOrderedByAuthor(this.alfrescoJsApi, false).then((result) => {
expect(result).toEqual(true);
});
});
it('[C272807] Should be able to sort results by date (Ascending)', () => {
searchResultPage.checkContentIsDisplayed(search.active.secondFile);
searchResultPage.sortAndCheckListIsOrderedByCreated(true).then((result) => {
expect(result).toEqual(true);
});
});
it('[C260260] Should be able to sort results by date (Descending)', () => {
searchResultPage.checkContentIsDisplayed(search.active.secondFile);
searchResultPage.sortAndCheckListIsOrderedByCreated(false).then((result) => {
expect(result).toEqual(true);
});
});
});
it('[C260262] Should not be able to delete a file from search results without rights', () => {
searchDialog
.clickOnSearchIcon()
.enterTextAndPressEnter(search.no_permission.noPermFile);
searchResultPage.checkContentIsDisplayed(search.no_permission.noPermFile);
searchResultPage.checkDeleteIsDisabled(search.no_permission.noPermFile);
searchResultPage.checkContentIsDisplayed(search.no_permission.noPermFile);
searchDialog.checkSearchBarIsNotVisible().checkSearchIconIsVisible().clickOnSearchIcon()
.enterTextAndPressEnter(search.no_permission.noPermFile);
searchResultPage.checkNoResultMessageIsNotDisplayed();
searchResultPage.checkContentIsDisplayed(search.no_permission.noPermFile);
});
it('[C272808] Should not be able to delete a folder from search results without rights', () => {
searchDialog
.clickOnSearchIcon()
.enterTextAndPressEnter(search.no_permission.noPermFolder);
searchResultPage.checkContentIsDisplayed(search.no_permission.noPermFolder);
searchResultPage.checkDeleteIsDisabled(search.no_permission.noPermFolder);
searchResultPage.checkContentIsDisplayed(search.no_permission.noPermFolder);
searchDialog
.clickOnSearchIcon()
.enterTextAndPressEnter(search.no_permission.noPermFolder);
searchResultPage.checkNoResultMessageIsNotDisplayed();
searchResultPage.checkContentIsDisplayed(search.no_permission.noPermFolder);
});
it('[C286675] Should display results when searching for all elements', () => {
searchDialog
.clickOnSearchIcon()

View File

@ -20,11 +20,12 @@ import { FacetQuery } from './facet-query.interface';
import { FacetField } from './facet-field.interface';
import { SearchCategory } from './search-category.interface';
import { SearchSortingDefinition } from './search-sorting-definition.interface';
import { RequestHighlight } from '@alfresco/js-api';
export interface SearchConfiguration {
include?: string[];
fields?: string[];
categories: SearchCategory[];
categories?: SearchCategory[];
filterQueries?: FilterQuery[];
filterWithContains?: boolean;
resetButton?: boolean;
@ -47,4 +48,5 @@ export interface SearchConfiguration {
options: SearchSortingDefinition[];
defaults: SearchSortingDefinition[];
};
highlight?: RequestHighlight;
}

View File

@ -190,10 +190,12 @@ describe('SearchQueryBuilder', () => {
it('should fetch facet from the config by label', () => {
const config: SearchConfiguration = {
categories: [],
facetFields: { 'fields': [
{ 'field': 'content.mimetype', 'mincount': 1, 'label': 'Type' },
{ 'field': 'content.size', 'mincount': 1, 'label': 'Size' }
]}
facetFields: {
'fields': [
{ 'field': 'content.mimetype', 'mincount': 1, 'label': 'Type' },
{ 'field': 'content.size', 'mincount': 1, 'label': 'Size' }
]
}
};
const builder = new SearchQueryBuilderService(buildConfig(config), null);
const field = builder.getFacetField('Size');
@ -205,10 +207,12 @@ describe('SearchQueryBuilder', () => {
it('should not fetch facet from the config by label', () => {
const config: SearchConfiguration = {
categories: [],
facetFields: { 'fields': [
{ 'field': 'content.mimetype', 'mincount': 1, 'label': 'Type' },
{ 'field': 'content.size', 'mincount': 1, 'label': 'Size' }
]}
facetFields: {
'fields': [
{ 'field': 'content.mimetype', 'mincount': 1, 'label': 'Type' },
{ 'field': 'content.size', 'mincount': 1, 'label': 'Size' }
]
}
};
const builder = new SearchQueryBuilderService(buildConfig(config), null);
const field = builder.getFacetField('Missing');
@ -373,10 +377,12 @@ describe('SearchQueryBuilder', () => {
categories: [
<any> { id: 'cat1', enabled: true }
],
facetFields: { fields: [
{ field: 'field1', label: 'field1', mincount: 1, limit: null, offset: 0, prefix: null },
{ field: 'field2', label: 'field2', mincount: 1, limit: null, offset: 0, prefix: null }
]}
facetFields: {
fields: [
{ field: 'field1', label: 'field1', mincount: 1, limit: null, offset: 0, prefix: null },
{ field: 'field2', label: 'field2', mincount: 1, limit: null, offset: 0, prefix: null }
]
}
};
const builder = new SearchQueryBuilderService(buildConfig(config), null);
builder.queryFragments['cat1'] = 'cm:name:test';
@ -588,4 +594,23 @@ describe('SearchQueryBuilder', () => {
expect(compiledQuery.query.query).toBe(expectedResult);
});
it('should use highlight in the queries', () => {
const config: SearchConfiguration = {
highlight: {
'prefix': 'my-prefix',
'postfix': 'my-postfix',
'mergeContiguous': true
}
};
const builder = new SearchQueryBuilderService(buildConfig(config), null);
builder.userQuery = 'my query';
builder.queryFragments['cat1'] = 'cm:name:test';
const compiled = builder.buildQuery();
expect(compiled.highlight.prefix).toBe('my-prefix');
expect(compiled.highlight.postfix).toBe('my-postfix');
expect(compiled.highlight.mergeContiguous).toBe(true);
});
});

View File

@ -23,7 +23,8 @@ import {
RequestFacetFields,
RequestFacetField,
RequestSortDefinitionInner,
ResultSetPaging
ResultSetPaging,
RequestHighlight
} from '@alfresco/js-api';
import { SearchCategory } from './search-category.interface';
import { FilterQuery } from './filter-query.interface';
@ -227,7 +228,8 @@ export class SearchQueryBuilderService {
facetQueries: this.facetQueries,
facetIntervals: this.facetIntervals,
facetFields: this.facetFields,
sort: this.sort
sort: this.sort,
highlight: this.highlight
};
result['facetFormat'] = 'V2';
@ -296,6 +298,10 @@ export class SearchQueryBuilderService {
return false;
}
get hasFacetHighlight(): boolean {
return this.config && this.config.highlight ? true : false;
}
protected get sort(): RequestSortDefinitionInner[] {
return this.sorting.map((def) => {
return new RequestSortDefinitionInner({
@ -339,6 +345,10 @@ export class SearchQueryBuilderService {
return null;
}
protected get highlight(): RequestHighlight {
return this.hasFacetHighlight ? this.config.highlight : null;
}
protected getFinalQuery(): string {
let query = '';

View File

@ -1117,7 +1117,6 @@
],
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
@ -1148,6 +1147,68 @@
}
}
},
"highlight": {
"description": " Request that highlight fragments to be added to result set rows The properties reflect SOLR highlighting parameters.",
"properties": {
"prefix": {
"description": "The string used to mark the start of a highlight in a fragment",
"type": "string"
},
"postfix": {
"description": "The string used to mark the end of a highlight in a fragment",
"type": "string"
},
"snippetCount": {
"description": "The maximum number of distinct highlight snippets to return for each highlight field",
"type": "number"
},
"fragmentSize": {
"description": "The character length of each snippet",
"type": "number"
},
"maxAnalyzedChars": {
"description": "The number of characters to be considered for highlighting. Matches after this count will not be shown",
"type": "number"
},
"mergeContiguous": {
"description": "If fragments over lap they can be merged into one larger fragment",
"type": "boolean"
},
"usePhraseHighlighter": {
"description": "Should phrases be identified",
"type": "boolean"
},
"fields": {
"type": "array",
"minItems": 1,
"items": {
"description": "The fields to highlight and field specific configuration properties for each field",
"type": "object",
"properties": {
"field": {
"description": "The name of the field to highlight",
"type": "string"
},
"snippetCount": {
"type": "number"
},
"fragmentSize": {
"type": "number"
},
"mergeContiguous": {
"type": "boolean"
},
"prefix": {
"type": "string"
},
"postfix": {
"type": "string"
}
}
}
}
}
},
"sorting": {
"description": "Sorting options and defaults",
"required": [

View File

@ -96,10 +96,13 @@ describe('EditProcessFilterCloudComponent', () => {
fixture.detectChanges();
const title = fixture.debugElement.nativeElement.querySelector('#adf-edit-process-filter-title-id');
const subTitle = fixture.debugElement.nativeElement.querySelector('#adf-edit-process-filter-sub-title-id');
expect(title).toBeDefined();
expect(subTitle).toBeDefined();
expect(title.innerText).toEqual('FakeRunningProcess');
expect(subTitle.innerText.trim()).toEqual('ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE');
fixture.whenStable().then(() => {
expect(title).toBeDefined();
expect(subTitle).toBeDefined();
expect(title.innerText).toEqual('FakeRunningProcess');
expect(subTitle.innerText.trim()).toEqual('ADF_CLOUD_EDIT_PROCESS_FILTER.TITLE');
});
});
describe('EditProcessFilter form', () => {