diff --git a/demo-shell/src/app.config.json b/demo-shell/src/app.config.json index 490fe38e22..21d4f81841 100644 --- a/demo-shell/src/app.config.json +++ b/demo-shell/src/app.config.json @@ -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" } - ] - } - } - } ] } }, diff --git a/lib/config/export-check/export-2.3.0.json b/lib/config/export-check/export-2.3.0.json index 3f05565614..8746233cc9 100644 --- a/lib/config/export-check/export-2.3.0.json +++ b/lib/config/export-check/export-2.3.0.json @@ -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" } -] \ No newline at end of file +] diff --git a/lib/content-services/i18n/en.json b/lib/content-services/i18n/en.json index abbb8eedc7..ace61f1fc2 100644 --- a/lib/content-services/i18n/en.json +++ b/lib/content-services/i18n/en.json @@ -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", diff --git a/lib/content-services/material.module.ts b/lib/content-services/material.module.ts index 272db886c1..c5ea70d2dc 100644 --- a/lib/content-services/material.module.ts +++ b/lib/content-services/material.module.ts @@ -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 ]; } diff --git a/lib/content-services/search/components/search-fields/search-fields.component.html b/lib/content-services/search/components/search-fields/search-fields.component.html deleted file mode 100644 index 8dd4a54b35..0000000000 --- a/lib/content-services/search/components/search-fields/search-fields.component.html +++ /dev/null @@ -1,6 +0,0 @@ - - {{ option.name }} - diff --git a/lib/content-services/search/components/search-fields/search-fields.component.scss b/lib/content-services/search/components/search-fields/search-fields.component.scss deleted file mode 100644 index bb65a956fb..0000000000 --- a/lib/content-services/search/components/search-fields/search-fields.component.scss +++ /dev/null @@ -1,8 +0,0 @@ -.adf-search-fields { - display: flex; - flex-direction: column; - - .mat-checkbox { - margin: 5px; - } -} diff --git a/lib/content-services/search/components/search-fields/search-fields.component.ts b/lib/content-services/search/components/search-fields/search-fields.component.ts deleted file mode 100644 index 688c915a96..0000000000 --- a/lib/content-services/search/components/search-fields/search-fields.component.ts +++ /dev/null @@ -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(); - } -} diff --git a/lib/content-services/search/components/search-filter/search-filter.service.ts b/lib/content-services/search/components/search-filter/search-filter.service.ts index d56c31b104..c1010939aa 100644 --- a/lib/content-services/search/components/search-filter/search-filter.service.ts +++ b/lib/content-services/search/components/search-filter/search-filter.service.ts @@ -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 }; } diff --git a/lib/content-services/search/components/search-number-range/search-number-range.component.html b/lib/content-services/search/components/search-number-range/search-number-range.component.html new file mode 100644 index 0000000000..6a25a88bed --- /dev/null +++ b/lib/content-services/search/components/search-number-range/search-number-range.component.html @@ -0,0 +1,27 @@ +
+ + + + Invalid format + Required value + + + + + Invalid format + + + +
+ + +
+
diff --git a/lib/content-services/search/components/search-number-range/search-number-range.component.scss b/lib/content-services/search/components/search-number-range/search-number-range.component.scss new file mode 100644 index 0000000000..c4437f1597 --- /dev/null +++ b/lib/content-services/search/components/search-number-range/search-number-range.component.scss @@ -0,0 +1,8 @@ +.adf-search-number-range > form { + display: inline-flex; + flex-direction: column; + + .mat-button { + text-transform: uppercase; + } +} diff --git a/lib/content-services/search/components/search-number-range/search-number-range.component.spec.ts b/lib/content-services/search/components/search-number-range/search-number-range.component.spec.ts new file mode 100644 index 0000000000..0a1f6e7893 --- /dev/null +++ b/lib/content-services/search/components/search-number-range/search-number-range.component.spec.ts @@ -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(); + }); + +}); diff --git a/lib/content-services/search/components/search-number-range/search-number-range.component.ts b/lib/content-services/search/components/search-number-range/search-number-range.component.ts new file mode 100644 index 0000000000..ca9a9229bd --- /dev/null +++ b/lib/content-services/search/components/search-number-range/search-number-range.component.ts @@ -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(); + } + } +} diff --git a/lib/content-services/search/components/search-scope-locations/search-scope-locations.component.html b/lib/content-services/search/components/search-scope-locations/search-scope-locations.component.html deleted file mode 100644 index 17cfe5077f..0000000000 --- a/lib/content-services/search/components/search-scope-locations/search-scope-locations.component.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - {{option.name}} - - - diff --git a/lib/content-services/search/components/search-scope-locations/search-scope-locations.component.ts b/lib/content-services/search/components/search-scope-locations/search-scope-locations.component.ts deleted file mode 100644 index 06051ce4ec..0000000000 --- a/lib/content-services/search/components/search-scope-locations/search-scope-locations.component.ts +++ /dev/null @@ -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(); - } -} diff --git a/lib/content-services/search/components/search-slider/search-slider.component.html b/lib/content-services/search/components/search-slider/search-slider.component.html new file mode 100644 index 0000000000..adb5aecf52 --- /dev/null +++ b/lib/content-services/search/components/search-slider/search-slider.component.html @@ -0,0 +1,8 @@ + + diff --git a/lib/content-services/search/components/search-slider/search-slider.component.scss b/lib/content-services/search/components/search-slider/search-slider.component.scss new file mode 100644 index 0000000000..2f05890816 --- /dev/null +++ b/lib/content-services/search/components/search-slider/search-slider.component.scss @@ -0,0 +1,5 @@ +.adf-search-slider { + .mat-slider { + width: 100%; + } +} diff --git a/lib/content-services/search/components/search-slider/search-slider.component.spec.ts b/lib/content-services/search/components/search-slider/search-slider.component.spec.ts new file mode 100644 index 0000000000..cdad747c2c --- /dev/null +++ b/lib/content-services/search/components/search-slider/search-slider.component.spec.ts @@ -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( { value: 10 }); + expect(component.value).toEqual(10); + + component.onChangedHandler( { 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( { value: 10 }); + + const expectedQuery = 'cm:content.size:[0 TO 10]'; + expect(context.queryFragments[component.id]).toEqual(expectedQuery); + expect(context.update).toHaveBeenCalled(); + }); + +}); diff --git a/lib/content-services/search/components/search-slider/search-slider.component.ts b/lib/content-services/search/components/search-slider/search-slider.component.ts new file mode 100644 index 0000000000..bbe29b4e0e --- /dev/null +++ b/lib/content-services/search/components/search-slider/search-slider.component.ts @@ -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(); + } + } + +} diff --git a/lib/content-services/search/forms/live-error-state-matcher.ts b/lib/content-services/search/forms/live-error-state-matcher.ts new file mode 100644 index 0000000000..4257d4afa6 --- /dev/null +++ b/lib/content-services/search/forms/live-error-state-matcher.ts @@ -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)); + } + +} diff --git a/lib/content-services/search/search-configuration.interface.ts b/lib/content-services/search/search-configuration.interface.ts index ed6a406bd2..30948fcf8f 100644 --- a/lib/content-services/search/search-configuration.interface.ts +++ b/lib/content-services/search/search-configuration.interface.ts @@ -21,13 +21,11 @@ import { FacetField } from './facet-field.interface'; import { SearchCategory } from './search-category.interface'; export interface SearchConfiguration { + include?: Array; + fields?: Array; query?: { categories: Array }; - limits?: { - permissionEvaluationTime?: number; - permissionEvaluationCount?: number; - }; filterQueries?: Array; facetQueries?: Array; facetFields?: { diff --git a/lib/content-services/search/search-query-builder.service.spec.ts b/lib/content-services/search/search-query-builder.service.spec.ts index ce757edc0e..52f75332a1 100644 --- a/lib/content-services/search/search-query-builder.service.spec.ts +++ b/lib/content-services/search/search-query-builder.service.spec.ts @@ -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: [ { 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: [ { 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: [ - { 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: [ - { 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: { diff --git a/lib/content-services/search/search-query-builder.service.ts b/lib/content-services/search/search-query-builder.service.ts index dd2cb58ebf..8ad60f1c26 100644 --- a/lib/content-services/search/search-query-builder.service.ts +++ b/lib/content-services/search/search-query-builder.service.ts @@ -33,8 +33,6 @@ export class SearchQueryBuilderService { categories: Array = []; 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; diff --git a/lib/content-services/search/search.module.ts b/lib/content-services/search/search.module.ts index 8e0d3d846a..59663158fb 100644 --- a/lib/content-services/search/search.module.ts +++ b/lib/content-services/search/search.module.ts @@ -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 {} diff --git a/lib/core/app-config/schema.json b/lib/core/app-config/schema.json index 48956b2cdb..711c7650d6 100644 --- a/lib/core/app-config/schema.json +++ b/lib/core/app-config/schema.json @@ -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": {