mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-24 17:32:15 +00:00
[ADF-2189] new search facets (#3230)
* slider facet * number range facet * unit tests * remove fdescribe * remove old demo-only content * remove old tests * Support "include" and "field" values with app config * exclude deprecated demo content from export checks
This commit is contained in:
committed by
Eugenio Romano
parent
b7fc44d576
commit
3a51c27f66
@@ -53,6 +53,8 @@
|
||||
}
|
||||
],
|
||||
"search": {
|
||||
"include": ["path", "allowableOperations"],
|
||||
"fields": [],
|
||||
"filterQueries": [
|
||||
{ "query": "TYPE:'cm:folder' OR TYPE:'cm:content'" },
|
||||
{ "query": "NOT cm:creator:System" }
|
||||
@@ -78,85 +80,62 @@
|
||||
"query": {
|
||||
"categories": [
|
||||
{
|
||||
"id": "broken",
|
||||
"name": "Broken Facet",
|
||||
"enabled": false,
|
||||
"expanded": false,
|
||||
"component": {
|
||||
"selector": "text",
|
||||
"settings": {
|
||||
"field": "fieldname"
|
||||
"id": "queryName",
|
||||
"name": "Name",
|
||||
"enabled": true,
|
||||
"expanded": true,
|
||||
"component": {
|
||||
"selector": "text",
|
||||
"settings": {
|
||||
"pattern": "cm:name:'(.*?)'",
|
||||
"field": "cm:name",
|
||||
"placeholder": "Enter the name"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "contentSize",
|
||||
"name": "Content Size",
|
||||
"enabled": true,
|
||||
"component": {
|
||||
"selector": "slider",
|
||||
"settings": {
|
||||
"field": "cm:content.size",
|
||||
"min": 0,
|
||||
"max": 18,
|
||||
"step": 1,
|
||||
"thumbLabel": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "contentSizeRange",
|
||||
"name": "Content Size (range)",
|
||||
"enabled": true,
|
||||
"component": {
|
||||
"selector": "number-range",
|
||||
"settings": {
|
||||
"field": "cm:content.size"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "queryType",
|
||||
"name": "Type",
|
||||
"enabled": true,
|
||||
"component": {
|
||||
"selector": "radio",
|
||||
"settings": {
|
||||
"field": null,
|
||||
"options": [
|
||||
{ "name": "None", "value": "", "default": true },
|
||||
{ "name": "All", "value": "TYPE:'cm:folder' OR TYPE:'cm:content'" },
|
||||
{ "name": "Folder", "value": "TYPE:'cm:folder'" },
|
||||
{ "name": "Document", "value": "TYPE:'cm:content'" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "queryName",
|
||||
"name": "Name",
|
||||
"enabled": true,
|
||||
"expanded": true,
|
||||
"component": {
|
||||
"selector": "text",
|
||||
"settings": {
|
||||
"pattern": "cm:name:'(.*?)'",
|
||||
"field": "cm:name",
|
||||
"placeholder": "Enter the name"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "queryFields",
|
||||
"name": "Fields",
|
||||
"enabled": true,
|
||||
"expanded": false,
|
||||
"component": {
|
||||
"selector": "fields",
|
||||
"settings": {
|
||||
"field": null,
|
||||
"options": [
|
||||
{ "name": "Name", "value": "name", "fields": ["name"], "default": true },
|
||||
{ "name": "File Size", "value": "content.sizeInBytes", "fields": ["content"], "default": true },
|
||||
{ "name": "Modified On", "value": "modifiedAt", "fields": ["modifiedAt"], "default": true },
|
||||
{ "name": "Modified By", "value": "modifiedByUser.displayName", "fields": ["modifiedByUser"], "default": true }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "queryType",
|
||||
"name": "Type",
|
||||
"enabled": true,
|
||||
"expanded": false,
|
||||
"component": {
|
||||
"selector": "radio",
|
||||
"settings": {
|
||||
"field": null,
|
||||
"options": [
|
||||
{ "name": "None", "value": "", "default": true },
|
||||
{ "name": "All", "value": "TYPE:'cm:folder' OR TYPE:'cm:content'" },
|
||||
{ "name": "Folder", "value": "TYPE:'cm:folder'" },
|
||||
{ "name": "Document", "value": "TYPE:'cm:content'" }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "queryLocations",
|
||||
"name": "Locations",
|
||||
"enabled": true,
|
||||
"expanded": false,
|
||||
"component": {
|
||||
"selector": "scope-locations",
|
||||
"settings": {
|
||||
"field": null,
|
||||
"options": [
|
||||
{ "name": "Default", "value": "nodes", "default": true },
|
||||
{ "name": "Nodes", "value": "nodes" },
|
||||
{ "name": "Deleted Nodes", "value": "deleted-nodes" },
|
||||
{ "name": "Versions", "value": "versions" }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@@ -5109,7 +5109,8 @@
|
||||
"character": 8,
|
||||
"fileName": "lib/content-services/search/components/search-widget-container/search-widgets.module.ts"
|
||||
},
|
||||
"name": "SearchFieldsComponent"
|
||||
"name": "SearchFieldsComponent",
|
||||
"skipError": true
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
@@ -5173,7 +5174,8 @@
|
||||
"character": 8,
|
||||
"fileName": "lib/content-services/search/components/search-widget-container/search-widgets.module.ts"
|
||||
},
|
||||
"name": "SearchScopeLocationsComponent"
|
||||
"name": "SearchScopeLocationsComponent",
|
||||
"skipError": true
|
||||
},
|
||||
{
|
||||
"position": {
|
||||
@@ -6455,4 +6457,4 @@
|
||||
},
|
||||
"name": "WidgetComponent"
|
||||
}
|
||||
]
|
||||
]
|
||||
|
@@ -165,6 +165,16 @@
|
||||
"MODIFIED_AT": "Modified at"
|
||||
}
|
||||
},
|
||||
"FILTER": {
|
||||
"ACTIONS": {
|
||||
"CLEAR": "Clear",
|
||||
"APPLY": "Apply"
|
||||
},
|
||||
"RANGE": {
|
||||
"FROM": "From",
|
||||
"TO": "To"
|
||||
}
|
||||
},
|
||||
"ICONS": {
|
||||
"ft_ic_raster_image": "Image file",
|
||||
"ft_ic_pdf": "PDF document",
|
||||
|
@@ -34,7 +34,8 @@ import {
|
||||
MatCheckboxModule,
|
||||
MatDatepickerModule,
|
||||
MatSlideToggleModule,
|
||||
MatRadioModule
|
||||
MatRadioModule,
|
||||
MatSliderModule
|
||||
} from '@angular/material';
|
||||
|
||||
export function modules() {
|
||||
@@ -56,7 +57,8 @@ export function modules() {
|
||||
MatCheckboxModule,
|
||||
MatDatepickerModule,
|
||||
MatSlideToggleModule,
|
||||
MatRadioModule
|
||||
MatRadioModule,
|
||||
MatSliderModule
|
||||
];
|
||||
}
|
||||
|
||||
|
@@ -1,6 +0,0 @@
|
||||
<mat-checkbox
|
||||
*ngFor="let option of settings.options"
|
||||
[checked]="option.checked"
|
||||
(change)="changeHandler($event, option)">
|
||||
{{ option.name }}
|
||||
</mat-checkbox>
|
@@ -1,8 +0,0 @@
|
||||
.adf-search-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.mat-checkbox {
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
@@ -1,70 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, ViewEncapsulation, OnInit, Input } from '@angular/core';
|
||||
import { MatCheckboxChange } from '@angular/material';
|
||||
|
||||
import { SearchWidget } from '../../search-widget.interface';
|
||||
import { SearchWidgetSettings } from '../../search-widget-settings.interface';
|
||||
import { SearchQueryBuilderService } from '../../search-query-builder.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-fields',
|
||||
templateUrl: './search-fields.component.html',
|
||||
styleUrls: ['./search-fields.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'adf-search-fields' }
|
||||
})
|
||||
export class SearchFieldsComponent implements SearchWidget, OnInit {
|
||||
|
||||
@Input()
|
||||
value: string;
|
||||
|
||||
id: string;
|
||||
settings: SearchWidgetSettings;
|
||||
context: SearchQueryBuilderService;
|
||||
|
||||
ngOnInit() {
|
||||
const defaultOptions = (this.settings.options || [])
|
||||
.filter(opt => opt.default)
|
||||
.map(opt => {
|
||||
opt.checked = true;
|
||||
return opt;
|
||||
});
|
||||
|
||||
if (defaultOptions.length > 0) {
|
||||
this.flush(defaultOptions);
|
||||
}
|
||||
}
|
||||
|
||||
changeHandler(event: MatCheckboxChange, option: any) {
|
||||
option.checked = event.checked;
|
||||
this.flush(this.settings.options);
|
||||
}
|
||||
|
||||
flush(opts: any[] = []) {
|
||||
const checkedValues = opts
|
||||
.filter(v => v.checked)
|
||||
.map(v => v.fields)
|
||||
.reduce((prev, curr) => {
|
||||
return prev.concat(curr);
|
||||
}, []);
|
||||
|
||||
this.context.fields[this.id] = checkedValues;
|
||||
this.context.update();
|
||||
}
|
||||
}
|
@@ -18,8 +18,8 @@
|
||||
import { Injectable, Type } from '@angular/core';
|
||||
import { SearchTextComponent } from '../search-text/search-text.component';
|
||||
import { SearchRadioComponent } from '../search-radio/search-radio.component';
|
||||
import { SearchFieldsComponent } from '../search-fields/search-fields.component';
|
||||
import { SearchScopeLocationsComponent } from '../search-scope-locations/search-scope-locations.component';
|
||||
import { SearchSliderComponent } from '../search-slider/search-slider.component';
|
||||
import { SearchNumberRangeComponent } from '../search-number-range/search-number-range.component';
|
||||
|
||||
@Injectable()
|
||||
export class SearchFilterService {
|
||||
@@ -30,8 +30,8 @@ export class SearchFilterService {
|
||||
widgets: { [id: string]: Type<{}> } = {
|
||||
'text': SearchTextComponent,
|
||||
'radio': SearchRadioComponent,
|
||||
'fields': SearchFieldsComponent,
|
||||
'scope-locations': SearchScopeLocationsComponent
|
||||
'slider': SearchSliderComponent,
|
||||
'number-range': SearchNumberRangeComponent
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,27 @@
|
||||
<form [formGroup]="form" novalidate (ngSubmit)="apply(form.value, form.valid)">
|
||||
|
||||
<mat-form-field>
|
||||
<input
|
||||
matInput [formControl]="from" [errorStateMatcher]="matcher"
|
||||
placeholder="{{ 'SEARCH.FILTER.RANGE.FROM' | translate }}">
|
||||
<mat-error *ngIf="from.hasError('pattern')">Invalid format</mat-error>
|
||||
<mat-error *ngIf="from.hasError('required')">Required value</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-form-field >
|
||||
<input
|
||||
matInput [formControl]="to" [errorStateMatcher]="matcher"
|
||||
placeholder="{{ 'SEARCH.FILTER.RANGE.TO' | translate }}">
|
||||
<mat-error *ngIf="to.invalid">Invalid format</mat-error>
|
||||
</mat-form-field>
|
||||
|
||||
|
||||
<div>
|
||||
<button mat-button color="primary" (click)="reset()">
|
||||
{{ 'SEARCH.FILTER.ACTIONS.CLEAR' | translate }}
|
||||
</button>
|
||||
<button mat-button color="primary" type="submit" [disabled]="!form.valid">
|
||||
{{ 'SEARCH.FILTER.ACTIONS.APPLY' | translate }}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
@@ -0,0 +1,8 @@
|
||||
.adf-search-number-range > form {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
|
||||
.mat-button {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { SearchNumberRangeComponent } from './search-number-range.component';
|
||||
|
||||
describe('SearchNumberRangeComponent', () => {
|
||||
|
||||
let component: SearchNumberRangeComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
component = new SearchNumberRangeComponent();
|
||||
});
|
||||
|
||||
it('should setup form elements on init', () => {
|
||||
component.ngOnInit();
|
||||
expect(component.form).toBeDefined();
|
||||
expect(component.to).toBeDefined();
|
||||
expect(component.form).toBeDefined();
|
||||
});
|
||||
|
||||
it('should reset form', () => {
|
||||
component.ngOnInit();
|
||||
component.form.reset({ from: '10', to: '20' });
|
||||
component.reset();
|
||||
|
||||
expect(component.from.value).toEqual('');
|
||||
expect(component.to.value).toEqual('');
|
||||
expect(component.form.value).toEqual({ from: '', to: '' });
|
||||
});
|
||||
|
||||
it('should update query builder on reset', () => {
|
||||
const context: any = {
|
||||
queryFragments: {
|
||||
contentSize: 'query'
|
||||
},
|
||||
update() {}
|
||||
};
|
||||
|
||||
component.id = 'contentSize';
|
||||
component.context = context;
|
||||
|
||||
spyOn(context, 'update').and.stub();
|
||||
|
||||
component.ngOnInit();
|
||||
component.reset();
|
||||
|
||||
expect(context.queryFragments.contentSize).toEqual('');
|
||||
expect(context.update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should update query builder on value changes', () => {
|
||||
const context: any = {
|
||||
queryFragments: {},
|
||||
update() {}
|
||||
};
|
||||
|
||||
component.id = 'contentSize';
|
||||
component.context = context;
|
||||
component.settings = { field: 'cm:content.size' };
|
||||
|
||||
spyOn(context, 'update').and.stub();
|
||||
|
||||
component.ngOnInit();
|
||||
component.apply({
|
||||
from: '10',
|
||||
to: '20'
|
||||
}, true);
|
||||
|
||||
const expectedQuery = 'cm:content.size:[10 TO 20]';
|
||||
expect(context.queryFragments[component.id]).toEqual(expectedQuery);
|
||||
expect(context.update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,77 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { OnInit, Component, ViewEncapsulation } from '@angular/core';
|
||||
import { FormControl, Validators, FormGroup } from '@angular/forms';
|
||||
import { SearchWidget } from '../../search-widget.interface';
|
||||
import { SearchWidgetSettings } from '../../search-widget-settings.interface';
|
||||
import { SearchQueryBuilderService } from '../../search-query-builder.service';
|
||||
import { LiveErrorStateMatcher } from '../../forms/live-error-state-matcher';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-number-range',
|
||||
templateUrl: './search-number-range.component.html',
|
||||
styleUrls: ['./search-number-range.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'adf-search-number-range' }
|
||||
})
|
||||
export class SearchNumberRangeComponent implements SearchWidget, OnInit {
|
||||
|
||||
from: FormControl;
|
||||
to: FormControl;
|
||||
|
||||
form: FormGroup;
|
||||
matcher = new LiveErrorStateMatcher();
|
||||
|
||||
id: string;
|
||||
settings?: SearchWidgetSettings;
|
||||
context?: SearchQueryBuilderService;
|
||||
|
||||
ngOnInit(): void {
|
||||
const validators = Validators.compose([
|
||||
Validators.required,
|
||||
Validators.pattern(/^-?(0|[1-9]\d*)?$/)
|
||||
]);
|
||||
|
||||
this.from = new FormControl('', validators);
|
||||
this.to = new FormControl('', validators);
|
||||
|
||||
this.form = new FormGroup({
|
||||
from: this.from,
|
||||
to: this.to
|
||||
});
|
||||
}
|
||||
|
||||
apply(model: { from: string, to: string }, isValid: boolean) {
|
||||
if (isValid && this.id && this.context && this.settings && this.settings.field) {
|
||||
this.context.queryFragments[this.id] = `${this.settings.field}:[${model.from} TO ${model.to}]`;
|
||||
this.context.update();
|
||||
}
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.form.reset({
|
||||
from: '',
|
||||
to: ''
|
||||
});
|
||||
|
||||
if (this.id && this.context) {
|
||||
this.context.queryFragments[this.id] = '';
|
||||
this.context.update();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
<mat-form-field>
|
||||
<mat-select
|
||||
[(value)]="value"
|
||||
(selectionChange)="changeHandler($event)">
|
||||
<mat-option
|
||||
*ngFor="let option of settings.options"
|
||||
[value]="option.value">
|
||||
{{option.name}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
@@ -1,57 +0,0 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, ViewEncapsulation, OnInit, Input } from '@angular/core';
|
||||
import { MatSelectChange } from '@angular/material';
|
||||
|
||||
import { SearchWidget } from '../../search-widget.interface';
|
||||
import { SearchWidgetSettings } from '../../search-widget-settings.interface';
|
||||
import { SearchQueryBuilderService } from '../../search-query-builder.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-scope-locations',
|
||||
templateUrl: './search-scope-locations.component.html',
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'adf-search-scope-locations' }
|
||||
})
|
||||
export class SearchScopeLocationsComponent implements SearchWidget, OnInit {
|
||||
|
||||
@Input()
|
||||
value: string;
|
||||
|
||||
id: string;
|
||||
settings: SearchWidgetSettings;
|
||||
context: SearchQueryBuilderService;
|
||||
|
||||
ngOnInit() {
|
||||
|
||||
const defaultSelection = (this.settings.options || []).find(opt => opt.default);
|
||||
if (defaultSelection) {
|
||||
this.flush(defaultSelection.value);
|
||||
}
|
||||
}
|
||||
|
||||
changeHandler(event: MatSelectChange) {
|
||||
this.flush(event.value);
|
||||
}
|
||||
|
||||
flush(value: string) {
|
||||
this.value = value;
|
||||
this.context.scope.locations = value;
|
||||
this.context.update();
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
<mat-slider
|
||||
[value]="value"
|
||||
[min]="min"
|
||||
[max]="max"
|
||||
[step]="step"
|
||||
[thumbLabel]="thumbLabel"
|
||||
(change)="onChangedHandler($event)">
|
||||
</mat-slider>
|
@@ -0,0 +1,5 @@
|
||||
.adf-search-slider {
|
||||
.mat-slider {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { MatSliderChange } from '@angular/material';
|
||||
import { SearchSliderComponent } from './search-slider.component';
|
||||
|
||||
describe('SearchSliderComponent', () => {
|
||||
|
||||
let component: SearchSliderComponent;
|
||||
|
||||
beforeEach(() => {
|
||||
component = new SearchSliderComponent();
|
||||
});
|
||||
|
||||
it('should setup slider from settings', () => {
|
||||
const settings: any = {
|
||||
min: 10,
|
||||
max: 100,
|
||||
step: 2,
|
||||
thumbLabel: true
|
||||
};
|
||||
|
||||
component.settings = settings;
|
||||
component.ngOnInit();
|
||||
|
||||
expect(component.min).toEqual(settings.min);
|
||||
expect(component.max).toEqual(settings.max);
|
||||
expect(component.step).toEqual(settings.step);
|
||||
expect(component.thumbLabel).toEqual(settings.thumbLabel);
|
||||
});
|
||||
|
||||
it('should update value on slider change', () => {
|
||||
component.onChangedHandler(<MatSliderChange> { value: 10 });
|
||||
expect(component.value).toEqual(10);
|
||||
|
||||
component.onChangedHandler(<MatSliderChange> { value: 20 });
|
||||
expect(component.value).toEqual(20);
|
||||
});
|
||||
|
||||
it('should update its query part on slider change', () => {
|
||||
const context: any = {
|
||||
queryFragments: {},
|
||||
update() {}
|
||||
};
|
||||
|
||||
spyOn(context, 'update').and.stub();
|
||||
|
||||
component.context = context;
|
||||
component.id = 'contentSize';
|
||||
component.settings = { field: 'cm:content.size' };
|
||||
|
||||
component.onChangedHandler(<MatSliderChange> { value: 10 });
|
||||
|
||||
const expectedQuery = 'cm:content.size:[0 TO 10]';
|
||||
expect(context.queryFragments[component.id]).toEqual(expectedQuery);
|
||||
expect(context.update).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
});
|
@@ -0,0 +1,69 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Component, ViewEncapsulation, OnInit } from '@angular/core';
|
||||
import { SearchWidget } from '../../search-widget.interface';
|
||||
import { SearchWidgetSettings } from '../../search-widget-settings.interface';
|
||||
import { SearchQueryBuilderService } from '../../search-query-builder.service';
|
||||
import { MatSliderChange } from '@angular/material';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-search-slider',
|
||||
templateUrl: './search-slider.component.html',
|
||||
styleUrls: ['./search-slider.component.scss'],
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
host: { class: 'adf-search-slider' }
|
||||
})
|
||||
export class SearchSliderComponent implements SearchWidget, OnInit {
|
||||
|
||||
id: string;
|
||||
settings: SearchWidgetSettings;
|
||||
context: SearchQueryBuilderService;
|
||||
step: number;
|
||||
min: number;
|
||||
max: number;
|
||||
thumbLabel = false;
|
||||
value: number;
|
||||
|
||||
ngOnInit() {
|
||||
if (this.settings) {
|
||||
if (this.settings.hasOwnProperty('min')) {
|
||||
this.min = this.settings['min'];
|
||||
}
|
||||
|
||||
if (this.settings.hasOwnProperty('max')) {
|
||||
this.max = this.settings['max'];
|
||||
}
|
||||
|
||||
if (this.settings.hasOwnProperty('step')) {
|
||||
this.step = this.settings['step'];
|
||||
}
|
||||
|
||||
this.thumbLabel = this.settings['thumbLabel'] ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
onChangedHandler(event: MatSliderChange) {
|
||||
this.value = event.value;
|
||||
|
||||
if (this.id && this.context && this.settings && this.settings.field) {
|
||||
this.context.queryFragments[this.id] = `${this.settings.field}:[0 TO ${this.value}]`;
|
||||
this.context.update();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
/*!
|
||||
* @license
|
||||
* Copyright 2016 Alfresco Software, Ltd.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { ErrorStateMatcher } from '@angular/material';
|
||||
import { FormControl, FormGroupDirective, NgForm } from '@angular/forms';
|
||||
|
||||
export class LiveErrorStateMatcher implements ErrorStateMatcher {
|
||||
|
||||
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
|
||||
const isSubmitted = form && form.submitted;
|
||||
return !!(control && control.invalid && (control.dirty || control.touched || isSubmitted));
|
||||
}
|
||||
|
||||
}
|
@@ -21,13 +21,11 @@ import { FacetField } from './facet-field.interface';
|
||||
import { SearchCategory } from './search-category.interface';
|
||||
|
||||
export interface SearchConfiguration {
|
||||
include?: Array<string>;
|
||||
fields?: Array<string>;
|
||||
query?: {
|
||||
categories: Array<SearchCategory>
|
||||
};
|
||||
limits?: {
|
||||
permissionEvaluationTime?: number;
|
||||
permissionEvaluationCount?: number;
|
||||
};
|
||||
filterQueries?: Array<FilterQuery>;
|
||||
facetQueries?: Array<FacetQuery>;
|
||||
facetFields?: {
|
||||
|
@@ -69,13 +69,6 @@ describe('SearchQueryBuilder', () => {
|
||||
expect(builder.filterQueries[1].query).toBe('query2');
|
||||
});
|
||||
|
||||
it('should setup default location scope', () => {
|
||||
const builder = new SearchQueryBuilderService(buildConfig({}), null);
|
||||
|
||||
expect(builder.scope).toBeDefined();
|
||||
expect(builder.scope.locations).toBeNull();
|
||||
});
|
||||
|
||||
it('should add new filter query', () => {
|
||||
const builder = new SearchQueryBuilderService(buildConfig({}), null);
|
||||
|
||||
@@ -237,6 +230,7 @@ describe('SearchQueryBuilder', () => {
|
||||
|
||||
it('should build query with custom fields', () => {
|
||||
const config: SearchConfiguration = {
|
||||
fields: ['field1', 'field2'],
|
||||
query: {
|
||||
categories: [
|
||||
<any> { id: 'cat1', enabled: true },
|
||||
@@ -247,15 +241,14 @@ describe('SearchQueryBuilder', () => {
|
||||
const builder = new SearchQueryBuilderService(buildConfig(config), null);
|
||||
|
||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||
builder.fields['cat1'] = ['field1', 'field3'];
|
||||
builder.fields['cat2'] = ['field2', 'field3'];
|
||||
|
||||
const compiled = builder.buildQuery();
|
||||
expect(compiled.fields).toEqual(['field1', 'field3', 'field2']);
|
||||
expect(compiled.fields).toEqual(['field1', 'field2']);
|
||||
});
|
||||
|
||||
it('should build query with empty custom fields', () => {
|
||||
const config: SearchConfiguration = {
|
||||
fields: [],
|
||||
query: {
|
||||
categories: [
|
||||
<any> { id: 'cat1', enabled: true },
|
||||
@@ -266,8 +259,6 @@ describe('SearchQueryBuilder', () => {
|
||||
const builder = new SearchQueryBuilderService(buildConfig(config), null);
|
||||
|
||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||
builder.fields['cat1'] = [];
|
||||
builder.fields['cat2'] = null;
|
||||
|
||||
const compiled = builder.buildQuery();
|
||||
expect(compiled.fields).toEqual([]);
|
||||
@@ -330,41 +321,6 @@ describe('SearchQueryBuilder', () => {
|
||||
expect(compiled.facetFields).toEqual(config.facetFields);
|
||||
});
|
||||
|
||||
it('should build query with custom limits', () => {
|
||||
const config: SearchConfiguration = {
|
||||
query: {
|
||||
categories: [
|
||||
<any> { id: 'cat1', enabled: true }
|
||||
]
|
||||
},
|
||||
limits: {
|
||||
permissionEvaluationCount: 100,
|
||||
permissionEvaluationTime: 100
|
||||
}
|
||||
};
|
||||
const builder = new SearchQueryBuilderService(buildConfig(config), null);
|
||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||
|
||||
const compiled = builder.buildQuery();
|
||||
expect(compiled.limits).toEqual(config.limits);
|
||||
});
|
||||
|
||||
it('should build query with custom scope', () => {
|
||||
const config: SearchConfiguration = {
|
||||
query: {
|
||||
categories: [
|
||||
<any> { id: 'cat1', enabled: true }
|
||||
]
|
||||
}
|
||||
};
|
||||
const builder = new SearchQueryBuilderService(buildConfig(config), null);
|
||||
builder.queryFragments['cat1'] = 'cm:name:test';
|
||||
builder.scope.locations = 'custom';
|
||||
|
||||
const compiled = builder.buildQuery();
|
||||
expect(compiled.scope.locations).toEqual('custom');
|
||||
});
|
||||
|
||||
it('should use pagination settings', () => {
|
||||
const config: SearchConfiguration = {
|
||||
query: {
|
||||
|
@@ -33,8 +33,6 @@ export class SearchQueryBuilderService {
|
||||
|
||||
categories: Array<SearchCategory> = [];
|
||||
queryFragments: { [id: string]: string } = {};
|
||||
fields: { [id: string]: string[] } = {};
|
||||
scope: { locations?: string };
|
||||
filterQueries: FilterQuery[] = [];
|
||||
ranges: { [id: string]: SearchRange } = {};
|
||||
paging: { maxItems?: number; skipCount?: number } = null;
|
||||
@@ -52,9 +50,6 @@ export class SearchQueryBuilderService {
|
||||
}
|
||||
|
||||
this.filterQueries = this.config.filterQueries || [];
|
||||
this.scope = {
|
||||
locations: null
|
||||
};
|
||||
}
|
||||
|
||||
addFilterQuery(query: string): void {
|
||||
@@ -93,7 +88,6 @@ export class SearchQueryBuilderService {
|
||||
|
||||
buildQuery(): QueryBody {
|
||||
let query = '';
|
||||
const fields: string[] = [];
|
||||
|
||||
this.categories.forEach(facet => {
|
||||
const customQuery = this.queryFragments[facet.id];
|
||||
@@ -103,17 +97,13 @@ export class SearchQueryBuilderService {
|
||||
}
|
||||
query += `(${customQuery})`;
|
||||
}
|
||||
|
||||
const customFields = this.fields[facet.id];
|
||||
if (customFields && customFields.length > 0) {
|
||||
for (const field of customFields) {
|
||||
if (!fields.includes(field)) {
|
||||
fields.push(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const include = this.config.include || [];
|
||||
if (include.length === 0) {
|
||||
include.push('path', 'allowableOperations');
|
||||
}
|
||||
|
||||
if (query) {
|
||||
|
||||
const result: QueryBody = {
|
||||
@@ -121,14 +111,12 @@ export class SearchQueryBuilderService {
|
||||
query: query,
|
||||
language: 'afts'
|
||||
},
|
||||
include: ['path', 'allowableOperations'],
|
||||
fields: fields,
|
||||
include: include,
|
||||
paging: this.paging,
|
||||
fields: this.config.fields,
|
||||
filterQueries: this.filterQueries,
|
||||
facetQueries: this.config.facetQueries,
|
||||
facetFields: this.config.facetFields,
|
||||
limits: this.config.limits,
|
||||
scope: this.scope
|
||||
facetFields: this.config.facetFields
|
||||
};
|
||||
|
||||
return result;
|
||||
|
@@ -33,8 +33,8 @@ import { SearchFilterComponent } from './components/search-filter/search-filter.
|
||||
import { SearchChipListComponent } from './components/search-chip-list/search-chip-list.component';
|
||||
import { SearchTextComponent } from './components/search-text/search-text.component';
|
||||
import { SearchRadioComponent } from './components/search-radio/search-radio.component';
|
||||
import { SearchFieldsComponent } from './components/search-fields/search-fields.component';
|
||||
import { SearchScopeLocationsComponent } from './components/search-scope-locations/search-scope-locations.component';
|
||||
import { SearchSliderComponent } from './components/search-slider/search-slider.component';
|
||||
import { SearchNumberRangeComponent } from './components/search-number-range/search-number-range.component';
|
||||
|
||||
export const ALFRESCO_SEARCH_DIRECTIVES: any[] = [
|
||||
SearchComponent,
|
||||
@@ -59,23 +59,23 @@ export const ALFRESCO_SEARCH_DIRECTIVES: any[] = [
|
||||
SearchWidgetContainerComponent,
|
||||
SearchTextComponent,
|
||||
SearchRadioComponent,
|
||||
SearchFieldsComponent,
|
||||
SearchScopeLocationsComponent
|
||||
SearchSliderComponent,
|
||||
SearchNumberRangeComponent
|
||||
],
|
||||
exports: [
|
||||
...ALFRESCO_SEARCH_DIRECTIVES,
|
||||
SearchWidgetContainerComponent,
|
||||
SearchTextComponent,
|
||||
SearchRadioComponent,
|
||||
SearchFieldsComponent,
|
||||
SearchScopeLocationsComponent
|
||||
SearchSliderComponent,
|
||||
SearchNumberRangeComponent
|
||||
],
|
||||
entryComponents: [
|
||||
SearchWidgetContainerComponent,
|
||||
SearchTextComponent,
|
||||
SearchRadioComponent,
|
||||
SearchFieldsComponent,
|
||||
SearchScopeLocationsComponent
|
||||
SearchSliderComponent,
|
||||
SearchNumberRangeComponent
|
||||
]
|
||||
})
|
||||
export class SearchModule {}
|
||||
|
@@ -481,12 +481,16 @@
|
||||
"description": "Search configuration parameters",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"limits": {
|
||||
"description": "The limits element limits the time and resources used for query execution. Limits applied to the query go to the database.",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"permissionEvaluationTime": { "type": "integer" },
|
||||
"permissionEvaluationCount": { "type": "integer" }
|
||||
"include": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"fields": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"filterQueries": {
|
||||
|
Reference in New Issue
Block a user