From 20fdd9955d0f88df389b7539c98a72d37c59b062 Mon Sep 17 00:00:00 2001 From: arditdomi <32884230+arditdomi@users.noreply.github.com> Date: Wed, 4 Nov 2020 11:22:18 +0000 Subject: [PATCH] [AAE-3967] Add datetime-range search filter component (#6297) * [AAE-3967] Add datetime-range search filter component * Fix bug from max date is not resetting when clicking the clear button * Fix formatting identation on date-range component * Add unit tests * Add documentation * Fix comments --- .../components/search-date-range.component.md | 2 +- .../search-datetime-range.component.md | 85 +++++ .../images/search-datetime-range.png | Bin 0 -> 24689 bytes ...ontent-node-selector-panel.service.spec.ts | 1 + .../content-node-selector-panel.service.ts | 3 +- lib/content-services/src/lib/i18n/en.json | 4 +- .../search-date-range.component.html | 4 +- .../search-date-range.component.spec.ts | 308 ++++++++++-------- .../search-date-range.component.ts | 53 +-- .../search-datetime-range.component.html | 45 +++ .../search-datetime-range.component.scss | 5 + .../search-datetime-range.component.spec.ts | 194 +++++++++++ .../search-datetime-range.component.ts | 213 ++++++++++++ .../search-filter/search-filter.service.ts | 4 +- .../src/lib/search/search.module.ts | 3 + 15 files changed, 749 insertions(+), 175 deletions(-) create mode 100644 docs/content-services/components/search-datetime-range.component.md create mode 100644 docs/docassets/images/search-datetime-range.png create mode 100644 lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.html create mode 100644 lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.scss create mode 100644 lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.spec.ts create mode 100644 lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.ts diff --git a/docs/content-services/components/search-date-range.component.md b/docs/content-services/components/search-date-range.component.md index e85e75eb64..916f6f8b65 100644 --- a/docs/content-services/components/search-date-range.component.md +++ b/docs/content-services/components/search-date-range.component.md @@ -7,7 +7,7 @@ Last reviewed: 2018-06-11 # [Search date range component](../../../lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.ts "Defined in search-date-range.component.ts") -Implements a date range [widget](../../../lib/testing/src/lib/core/pages/form/widgets/widget.ts) for the [Search Filter component](search-filter.component.md). +Implements a [search widget](../../../lib/content-services/src/lib/search/search-widget.interface.ts) for the [Search Filter component](search-filter.component.md). ![Date Range Widget](../../docassets/images/search-date-range.png) diff --git a/docs/content-services/components/search-datetime-range.component.md b/docs/content-services/components/search-datetime-range.component.md new file mode 100644 index 0000000000..eb86ed18a8 --- /dev/null +++ b/docs/content-services/components/search-datetime-range.component.md @@ -0,0 +1,85 @@ +--- +Title: Search datetime range component +Added: v4.2.0 +Status: Active +Last reviewed: 2020-11-02 +--- + +# [Search datetime range component](../../../lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.ts "Defined in search-datetime-range.component.ts") + +Implements a [search widget](../../../lib/content-services/src/lib/search/search-widget.interface.ts) for the [Search Filter component](search-filter.component.md). + +![Date Range Widget](../../docassets/images/search-datetime-range.png) + +## Basic usage + +```json +{ + "search": { + "categories": [ + { + "id": "createdDatetimeRange", + "name": "Created Datetime (range)", + "enabled": true, + "component": { + "selector": "datetime-range", + "settings": { + "field": "cm:created" + } + } + } + ] + } +} +``` + +### Settings + +| Name | Type | Description | +| ---- | ---- | ----------- | +| field | string | Field to apply the query to. Required value | +| datetimeFormat | string | Datetime format. Datetime formats used by the datetime picker are [Moment.js](https://momentjs.com/docs/#/parsing/string-format/) instances, so you can use any datetime format supported by Moment. Default is 'DD/MM/YYYY HH:mm'. | +| maxDatetime | string | A fixed datetime that will set the maximum searchable datetime. Default is no maximum. | + +## Details + +This component lets the user select a range between two dates and times based on the particular `field`. +See the [Search filter component](search-filter.component.md) for full details of how to use widgets +in a search query. + +### Custom datetime format + +You can set the datetime range picker to work with any datetime format your app requires. You can use +any datetime format supported by [Moment.js](https://momentjs.com/docs/#/parsing/string-format/) +in the `datetimeFormat` and in the `maxDatetime` setting: + +```json +{ + "search": { + "categories": [ + { + "id": "createdDateTimeRange", + "name": "Created Datetime (range)", + "enabled": true, + "component": { + "selector": "datetime-range", + "settings": { + "field": "cm:created", + "datetimeFormat": "DD-MMM-YY HH:mm:ss", + "maxDatetime": "10-Mar-20 20:00" + } + } + } + ] + } +} +``` + +## See also + +- [Search filter component](search-filter.component.md) +- [Search check list component](search-check-list.component.md) +- [Search number range component](search-number-range.component.md) +- [Search radio component](search-radio.component.md) +- [Search slider component](search-slider.component.md) +- [Search text component](search-text.component.md) diff --git a/docs/docassets/images/search-datetime-range.png b/docs/docassets/images/search-datetime-range.png new file mode 100644 index 0000000000000000000000000000000000000000..48de6e731c6ce3de42dc3d00fde0a8fd08b8a5b1 GIT binary patch literal 24689 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 T`*5HQXM?x<5jTeG$)x`Q&xS9H literal 0 HcmV?d00001 diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.service.spec.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.service.spec.ts index eb5d1cac27..bc18274f0b 100644 --- a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.service.spec.ts +++ b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.service.spec.ts @@ -40,6 +40,7 @@ describe('ContentNodeSelectorPanelService', () => { const expectedSupportedTypesMap = new Map (); expectedSupportedTypesMap.set('d:text', 'text'); expectedSupportedTypesMap.set('d:date', 'date-range'); + expectedSupportedTypesMap.set('d:datetime', 'datetime-range'); expect(contentNodeSelectorPanelService.modelPropertyTypeToSearchFilterTypeMap).toEqual(expectedSupportedTypesMap); }); diff --git a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.service.ts b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.service.ts index 7ccca67a7b..f2b07d1b99 100644 --- a/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.service.ts +++ b/lib/content-services/src/lib/content-node-selector/content-node-selector-panel.service.ts @@ -23,13 +23,14 @@ import { SearchCategory } from '../search/search-category.interface'; }) export class ContentNodeSelectorPanelService { - propertyTypes = ['d:text', 'd:date']; + propertyTypes = ['d:text', 'd:date', 'd:datetime']; modelPropertyTypeToSearchFilterTypeMap = new Map (); customModels: any[]; constructor() { this.modelPropertyTypeToSearchFilterTypeMap.set(this.propertyTypes[0], 'text'); this.modelPropertyTypeToSearchFilterTypeMap.set(this.propertyTypes[1], 'date-range'); + this.modelPropertyTypeToSearchFilterTypeMap.set(this.propertyTypes[2], 'datetime-range'); } convertCustomModelPropertiesToSearchCategories(): SearchCategory[] { diff --git a/lib/content-services/src/lib/i18n/en.json b/lib/content-services/src/lib/i18n/en.json index c528088e28..1218374c7f 100644 --- a/lib/content-services/src/lib/i18n/en.json +++ b/lib/content-services/src/lib/i18n/en.json @@ -252,7 +252,9 @@ "NO-DAYS": "No days selected.", "INVALID-FORMAT": "Invalid Format", "INVALID-DATE": "Invalid date. The date must be in the format '{{ requiredFormat }}'", - "BEYOND-MAX-DATE": "The date is beyond the maximum date." + "BEYOND-MAX-DATE": "The date is beyond the maximum date.", + "INVALID-DATETIME": "Invalid datetime. The datetime must be in the format '{{ requiredFormat }}'", + "BEYOND-MAX-DATETIME": "The datetime is beyond the maximum datetime." } }, "ICONS": { diff --git a/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.html b/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.html index d00f35358e..27024c7516 100644 --- a/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.html +++ b/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.html @@ -12,7 +12,7 @@ - {{ getFromValidationMessage() | translate: { requiredFormat: datePickerDateFormat } }} + {{ getFromValidationMessage() | translate: { requiredFormat: datePickerFormat } }} @@ -30,7 +30,7 @@ - {{ getToValidationMessage() | translate: { requiredFormat: datePickerDateFormat } }} + {{ getToValidationMessage() | translate: { requiredFormat: datePickerFormat } }} diff --git a/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.spec.ts b/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.spec.ts index bda21cf9c5..6058ee1d68 100644 --- a/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.spec.ts +++ b/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.spec.ts @@ -25,182 +25,204 @@ import { TranslateModule } from '@ngx-translate/core'; declare let moment: any; describe('SearchDateRangeComponent', () => { - let fixture: ComponentFixture; - let component: SearchDateRangeComponent; - let adapter: MomentDateAdapter; - const fromDate = '2016-10-16'; - const toDate = '2017-10-16'; - const maxDate = '10-Mar-20'; - const dateFormatFixture = 'DD-MMM-YY'; + let fixture: ComponentFixture; + let component: SearchDateRangeComponent; + let adapter: MomentDateAdapter; + const fromDate = '2016-10-16'; + const toDate = '2017-10-16'; + const maxDate = '10-Mar-20'; + const dateFormatFixture = 'DD-MMM-YY'; - setupTestBed({ - imports: [ - TranslateModule.forRoot(), - ContentTestingModule - ] - }); + setupTestBed({ + imports: [ + TranslateModule.forRoot(), + ContentTestingModule + ] + }); - beforeEach(() => { - fixture = TestBed.createComponent(SearchDateRangeComponent); - adapter = fixture.debugElement.injector.get(DateAdapter) as MomentDateAdapter; - component = fixture.componentInstance; - }); + beforeEach(() => { + fixture = TestBed.createComponent(SearchDateRangeComponent); + adapter = fixture.debugElement.injector.get(DateAdapter) as MomentDateAdapter; + component = fixture.componentInstance; + }); - afterEach(() => fixture.destroy()); + afterEach(() => fixture.destroy()); - it('should use moment adapter', () => { - expect(adapter instanceof MomentDateAdapter).toBe(true); - expect(component.datePickerDateFormat).toBe('DD/MM/YYYY'); - }); + it('should use moment adapter', () => { + fixture.detectChanges(); - it('should setup form elements on init', () => { - fixture.detectChanges(); - expect(component.from).toBeDefined(); - expect(component.to).toBeDefined(); - expect(component.form).toBeDefined(); - }); + expect(adapter instanceof MomentDateAdapter).toBe(true); + expect(component.datePickerFormat).toBe('DD/MM/YYYY'); + }); - it('should setup the format of the date from configuration', () => { - component.settings = { field: 'cm:created', dateFormat: dateFormatFixture }; - fixture.detectChanges(); - expect(adapter.overrideDisplayFormat).toBe(dateFormatFixture); - }); + it('should setup form elements on init', () => { + fixture.detectChanges(); - it('should setup form control with formatted valid date on change', () => { - component.settings = { field: 'cm:created', dateFormat: dateFormatFixture }; - fixture.detectChanges(); + expect(component.from).toBeDefined(); + expect(component.to).toBeDefined(); + expect(component.form).toBeDefined(); + }); - const inputString = '20-feb-18'; - const momentFromInput = moment(inputString, dateFormatFixture); - expect(momentFromInput.isValid()).toBeTruthy(); + it('should setup the format of the date from configuration', () => { + component.settings = { field: 'cm:created', dateFormat: dateFormatFixture }; + fixture.detectChanges(); - component.onChangedHandler({ value: inputString }, component.from); - expect(component.from.value.toString()).toEqual(momentFromInput.toString()); - }); + expect(adapter.overrideDisplayFormat).toBe(dateFormatFixture); + }); - it('should NOT setup form control with invalid date on change', () => { - component.settings = { field: 'cm:created', dateFormat: dateFormatFixture }; - fixture.detectChanges(); + it('should setup form control with formatted valid date on change', () => { + component.settings = { field: 'cm:created', dateFormat: dateFormatFixture }; + fixture.detectChanges(); - const inputString = '20.f.18'; - const momentFromInput = moment(inputString, dateFormatFixture); - expect(momentFromInput.isValid()).toBeFalsy(); + const inputString = '20-feb-18'; + const momentFromInput = moment(inputString, dateFormatFixture); - component.onChangedHandler({ value: inputString }, component.from); - expect(component.from.value.toString()).not.toEqual(momentFromInput.toString()); - }); + expect(momentFromInput.isValid()).toBeTruthy(); - it('should reset form', () => { - fixture.detectChanges(); - component.form.setValue({ from: fromDate, to: toDate }); + component.onChangedHandler({ value: inputString }, component.from); - expect(component.from.value).toEqual(fromDate); - expect(component.to.value).toEqual(toDate); + expect(component.from.value.toString()).toEqual(momentFromInput.toString()); + }); - component.reset(); + it('should NOT setup form control with invalid date on change', () => { + component.settings = { field: 'cm:created', dateFormat: dateFormatFixture }; + fixture.detectChanges(); - expect(component.from.value).toEqual(''); - expect(component.to.value).toEqual(''); - expect(component.form.value).toEqual({ from: '', to: '' }); - }); + const inputString = '20.f.18'; + const momentFromInput = moment(inputString, dateFormatFixture); - it('should update query builder on reset', () => { - const context: any = { - queryFragments: { - createdDateRange: 'query' - }, - update() { - } - }; + expect(momentFromInput.isValid()).toBeFalsy(); - component.id = 'createdDateRange'; - component.context = context; + component.onChangedHandler({ value: inputString }, component.from); - spyOn(context, 'update').and.stub(); + expect(component.from.value.toString()).not.toEqual(momentFromInput.toString()); + }); - fixture.detectChanges(); - component.reset(); + it('should reset form', () => { + fixture.detectChanges(); + component.form.setValue({ from: fromDate, to: toDate }); - expect(context.queryFragments.createdDateRange).toEqual(''); - expect(context.update).toHaveBeenCalled(); - }); + expect(component.from.value).toEqual(fromDate); + expect(component.to.value).toEqual(toDate); - it('should update query builder on value changes', () => { - const context: any = { - queryFragments: {}, - update() { - } - }; + component.reset(); - component.id = 'createdDateRange'; - component.context = context; - component.settings = { field: 'cm:created' }; + expect(component.from.value).toEqual(''); + expect(component.to.value).toEqual(''); + expect(component.form.value).toEqual({ from: '', to: '' }); + }); - spyOn(context, 'update').and.stub(); + it('should reset fromMaxDate on reset', () => { + fixture.detectChanges(); + component.fromMaxDate = fromDate; + component.reset(); - fixture.detectChanges(); - component.apply({ - from: fromDate, - to: toDate - }, true); + expect(component.fromMaxDate).toEqual(undefined); + }); - const startDate = moment(fromDate).startOf('day').format(); - const endDate = moment(toDate).endOf('day').format(); + it('should update query builder on reset', () => { + const context: any = { + queryFragments: { + createdDateRange: 'query' + }, + update() { + } + }; - const expectedQuery = `cm:created:['${startDate}' TO '${endDate}']`; - expect(context.queryFragments[component.id]).toEqual(expectedQuery); - expect(context.update).toHaveBeenCalled(); - }); + component.id = 'createdDateRange'; + component.context = context; - it('should show date-format error when Invalid found', async(() => { - fixture.detectChanges(); - const input = fixture.debugElement.nativeElement.querySelector('[data-automation-id="date-range-from-input"]'); - input.value = '10-05-18'; - input.dispatchEvent(new Event('input')); - fixture.detectChanges(); - fixture.whenStable().then(() => { - expect(component.getFromValidationMessage()).toEqual('SEARCH.FILTER.VALIDATION.INVALID-DATE'); - }); - })); + spyOn(context, 'update').and.stub(); - it('should not show date-format error when valid found', async(() => { - fixture.detectChanges(); - const input = fixture.debugElement.nativeElement.querySelector('[data-automation-id="date-range-from-input"]'); - input.value = '10/10/2018'; - input.dispatchEvent(new Event('input')); - fixture.detectChanges(); - fixture.whenStable().then(() => { - fixture.detectChanges(); - expect(component.getFromValidationMessage()).toBeFalsy(); - }); - })); + fixture.detectChanges(); + component.reset(); - it('should have no maximum date by default', async(() => { - fixture.detectChanges(); + expect(context.queryFragments.createdDateRange).toEqual(''); + expect(context.update).toHaveBeenCalled(); + }); - expect(fixture.debugElement.nativeElement.querySelector('input[ng-reflect-max]')).toBeNull(); - })); + it('should update query builder on value changes', () => { + const context: any = { + queryFragments: {}, + update() { + } + }; - it('should be able to set a fixed maximum date', async(() => { - component.settings = { field: 'cm:created', dateFormat: dateFormatFixture, maxDate: maxDate }; - fixture.detectChanges(); + component.id = 'createdDateRange'; + component.context = context; + component.settings = { field: 'cm:created' }; - const inputs = fixture.debugElement.nativeElement.querySelectorAll('input[ng-reflect-max="Tue Mar 10 2020 23:59:59 GMT+0"]'); - expect(inputs[0]).toBeDefined(); - expect(inputs[0]).not.toBeNull(); - expect(inputs[1]).toBeDefined(); - expect(inputs[1]).not.toBeNull(); - })); + spyOn(context, 'update').and.stub(); - it('should be able to set the maximum date to today', async(() => { - component.settings = { field: 'cm:created', dateFormat: dateFormatFixture, maxDate: 'today' }; - fixture.detectChanges(); - const today = adapter.today().endOf('day').toString().slice(0, -3); + fixture.detectChanges(); + component.apply({ + from: fromDate, + to: toDate + }, true); - const inputs = fixture.debugElement.nativeElement.querySelectorAll('input[ng-reflect-max="' + today + '"]'); - expect(inputs[0]).toBeDefined(); - expect(inputs[0]).not.toBeNull(); - expect(inputs[1]).toBeDefined(); - expect(inputs[1]).not.toBeNull(); - })); + const startDate = moment(fromDate).startOf('day').format(); + const endDate = moment(toDate).endOf('day').format(); + + const expectedQuery = `cm:created:['${startDate}' TO '${endDate}']`; + + expect(context.queryFragments[component.id]).toEqual(expectedQuery); + expect(context.update).toHaveBeenCalled(); + }); + + it('should show date-format error when Invalid found', async () => { + fixture.detectChanges(); + + const input = fixture.debugElement.nativeElement.querySelector('[data-automation-id="date-range-from-input"]'); + input.value = '10-05-18'; + input.dispatchEvent(new Event('input')); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.getFromValidationMessage()).toEqual('SEARCH.FILTER.VALIDATION.INVALID-DATE'); + }); + + it('should not show date-format error when valid found', async () => { + fixture.detectChanges(); + + const input = fixture.debugElement.nativeElement.querySelector('[data-automation-id="date-range-from-input"]'); + input.value = '10/10/2018'; + input.dispatchEvent(new Event('input')); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.getFromValidationMessage()).toEqual(''); + }); + + it('should have no maximum date by default', async(() => { + fixture.detectChanges(); + + expect(fixture.debugElement.nativeElement.querySelector('input[ng-reflect-max]')).toBeNull(); + })); + + it('should be able to set a fixed maximum date', async () => { + component.settings = { field: 'cm:created', dateFormat: dateFormatFixture, maxDate: maxDate }; + fixture.detectChanges(); + + const inputs = fixture.debugElement.nativeElement.querySelectorAll('input[ng-reflect-max="Tue Mar 10 2020 23:59:59 GMT+0"]'); + + expect(inputs[0]).toBeDefined(); + expect(inputs[0]).not.toBeNull(); + expect(inputs[1]).toBeDefined(); + expect(inputs[1]).not.toBeNull(); + }); + + it('should be able to set the maximum date to today', async () => { + component.settings = { field: 'cm:created', dateFormat: dateFormatFixture, maxDate: 'today' }; + fixture.detectChanges(); + const today = adapter.today().endOf('day').toString().slice(0, -3); + + const inputs = fixture.debugElement.nativeElement.querySelectorAll('input[ng-reflect-max="' + today + '"]'); + + expect(inputs[0]).toBeDefined(); + expect(inputs[0]).not.toBeNull(); + expect(inputs[1]).toBeDefined(); + expect(inputs[1]).not.toBeNull(); + }); }); diff --git a/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.ts b/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.ts index 0e00e54acd..785455151b 100644 --- a/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.ts +++ b/lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.ts @@ -28,6 +28,11 @@ import { Moment } from 'moment'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; +export interface DateRangeValue { + from: string; + to: string; +} + declare let moment: any; const DEFAULT_FORMAT_DATE: string = 'DD/MM/YYYY'; @@ -37,8 +42,8 @@ const DEFAULT_FORMAT_DATE: string = 'DD/MM/YYYY'; templateUrl: './search-date-range.component.html', styleUrls: ['./search-date-range.component.scss'], providers: [ - {provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE]}, - {provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS} + { provide: DateAdapter, useClass: MomentDateAdapter, deps: [MAT_DATE_LOCALE] }, + { provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS } ], encapsulation: ViewEncapsulation.None, host: { class: 'adf-search-date-range' } @@ -54,7 +59,7 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit, OnDestroy id: string; settings?: SearchWidgetSettings; context?: SearchQueryBuilderService; - datePickerDateFormat = DEFAULT_FORMAT_DATE; + datePickerFormat: string; maxDate: any; fromMaxDate: any; isActive = false; @@ -82,11 +87,10 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit, OnDestroy } ngOnInit() { - if (this.settings) { - this.datePickerDateFormat = this.settings.dateFormat || DEFAULT_FORMAT_DATE; - } - const theCustomDateAdapter = this.dateAdapter; - theCustomDateAdapter.overrideDisplayFormat = this.datePickerDateFormat; + this.datePickerFormat = this.settings?.dateFormat ? this.settings.dateFormat : DEFAULT_FORMAT_DATE; + + const customDateAdapter = this.dateAdapter; + customDateAdapter.overrideDisplayFormat = this.datePickerFormat; this.userPreferencesService .select(UserPreferenceValues.Locale) @@ -107,8 +111,8 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit, OnDestroy if (this.startValue) { const splitValue = this.startValue.split('||'); - const fromValue = this.dateAdapter.parse(splitValue[0], this.datePickerDateFormat); - const toValue = this.dateAdapter.parse(splitValue[1], this.datePickerDateFormat); + const fromValue = this.dateAdapter.parse(splitValue[0], this.datePickerFormat); + const toValue = this.dateAdapter.parse(splitValue[1], this.datePickerFormat); this.from = new FormControl(fromValue, validators); this.to = new FormControl(toValue, validators); } else { @@ -145,19 +149,21 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit, OnDestroy this.apply(this.form.value, this.form.valid); } - hasValidValue() { + hasValidValue(): boolean { return this.form.valid; } - getCurrentValue() { - return { from : this.dateAdapter.format(this.form.value.from, this.datePickerDateFormat), - to: this.dateAdapter.format(this.form.value.from, this.datePickerDateFormat) }; + getCurrentValue(): DateRangeValue { + return { + from: this.dateAdapter.format(this.form.value.from, this.datePickerFormat), + to: this.dateAdapter.format(this.form.value.from, this.datePickerFormat) + }; } setValue(parsedDate: string) { const splitValue = parsedDate.split('||'); - const fromValue = this.dateAdapter.parse(splitValue[0], this.datePickerDateFormat); - const toValue = this.dateAdapter.parse(splitValue[1], this.datePickerDateFormat); + const fromValue = this.dateAdapter.parse(splitValue[0], this.datePickerFormat); + const toValue = this.dateAdapter.parse(splitValue[1], this.datePickerFormat); this.from.setValue(fromValue); this.from.markAsDirty(); this.from.markAsTouched(); @@ -177,12 +183,13 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit, OnDestroy this.context.queryFragments[this.id] = ''; this.context.update(); } + this.setFromMaxDate(); } onChangedHandler(event: any, formControl: FormControl) { const inputValue = event.value; - const formatDate = this.dateAdapter.parse(inputValue, this.datePickerDateFormat); + const formatDate = this.dateAdapter.parse(inputValue, this.datePickerFormat); if (formatDate && formatDate.isValid()) { formControl.setValue(formatDate); } else if (formatDate) { @@ -199,7 +206,7 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit, OnDestroy moment.locale(locale); } - hasParseError(formControl) { + hasParseError(formControl): boolean { return formControl.hasError('matDatepickerParse') && formControl.getError('matDatepickerParse').text; } @@ -207,13 +214,7 @@ export class SearchDateRangeComponent implements SearchWidget, OnInit, OnDestroy event.srcElement.click(); } - setFromMaxDate(): any { - let maxDate: string; - if (!this.to.value || this.maxDate && (moment(this.maxDate).isBefore(this.to.value))) { - maxDate = this.maxDate; - } else { - maxDate = moment(this.to.value); - } - this.fromMaxDate = maxDate; + setFromMaxDate() { + this.fromMaxDate = (!this.to.value || this.maxDate && (moment(this.maxDate).isBefore(this.to.value))) ? this.maxDate : moment(this.to.value); } } diff --git a/lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.html b/lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.html new file mode 100644 index 0000000000..161e992e5b --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.html @@ -0,0 +1,45 @@ +
+ + + + + + {{ getFromValidationMessage() | translate: { requiredFormat: datetimePickerFormat } }} + + + + + + + + + {{ getToValidationMessage() | translate: { requiredFormat: datetimePickerFormat } }} + + + +
+ + +
+
diff --git a/lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.scss b/lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.scss new file mode 100644 index 0000000000..aa124f9143 --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.scss @@ -0,0 +1,5 @@ +.adf-search-date-range > form { + display: inline-flex; + flex-direction: column; + width: 100%; +} diff --git a/lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.spec.ts b/lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.spec.ts new file mode 100644 index 0000000000..e01ba41e7f --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.spec.ts @@ -0,0 +1,194 @@ +/*! + * @license + * Copyright 2019 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 { SearchDatetimeRangeComponent } from './search-datetime-range.component'; +import { setupTestBed } from '@alfresco/adf-core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ContentTestingModule } from '../../../testing/content.testing.module'; +import { TranslateModule } from '@ngx-translate/core'; + +declare let moment: any; + +describe('SearchDatetimeRangeComponent', () => { + let fixture: ComponentFixture; + let component: SearchDatetimeRangeComponent; + const fromDatetime = '2016-10-16 12:30'; + const toDatetime = '2017-10-16 20:00'; + const maxDatetime = '10-Mar-20 20:00'; + const datetimeFormatFixture = 'DD-MMM-YY HH:mm'; + + setupTestBed({ + imports: [ + TranslateModule.forRoot(), + ContentTestingModule + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(SearchDatetimeRangeComponent); + component = fixture.componentInstance; + }); + + afterEach(() => fixture.destroy()); + + it('should setup form elements on init', () => { + fixture.detectChanges(); + + expect(component.from).toBeDefined(); + expect(component.to).toBeDefined(); + expect(component.form).toBeDefined(); + }); + + it('should setup form control with formatted valid datetime on change', () => { + component.settings = { field: 'cm:created', datetimeFormat: datetimeFormatFixture }; + fixture.detectChanges(); + + const inputString = '20-feb-18 20:00'; + const momentFromInput = moment(inputString, datetimeFormatFixture); + + expect(momentFromInput.isValid()).toBeTruthy(); + + component.onChangedHandler({ value: inputString }, component.from); + + expect(component.from.value.toString()).toEqual(momentFromInput.toString()); + }); + + it('should NOT setup form control with invalid datetime on change', () => { + component.settings = { field: 'cm:created', datetimeFormat: datetimeFormatFixture }; + fixture.detectChanges(); + + const inputString = '2017-10-16 20:f:00'; + const momentFromInput = moment(inputString, datetimeFormatFixture); + + expect(momentFromInput.isValid()).toBeFalsy(); + + component.onChangedHandler({ value: inputString }, component.from); + + expect(component.from.value.toString()).not.toEqual(momentFromInput.toString()); + }); + + it('should reset form', () => { + fixture.detectChanges(); + component.form.setValue({ from: fromDatetime, to: toDatetime }); + + expect(component.from.value).toEqual(fromDatetime); + expect(component.to.value).toEqual(toDatetime); + + component.reset(); + + expect(component.from.value).toEqual(''); + expect(component.to.value).toEqual(''); + expect(component.form.value).toEqual({ from: '', to: '' }); + }); + + it('should reset fromMaxDatetime on reset', () => { + fixture.detectChanges(); + component.fromMaxDatetime = fromDatetime; + component.reset(); + + expect(component.fromMaxDatetime).toEqual(undefined); + }); + + it('should update query builder on reset', () => { + const context: any = { + queryFragments: { + createdDatetimeRange: 'query' + }, + update() { + } + }; + + component.id = 'createdDatetimeRange'; + component.context = context; + + spyOn(context, 'update').and.stub(); + + fixture.detectChanges(); + component.reset(); + + expect(context.queryFragments.createdDatetimeRange).toEqual(''); + expect(context.update).toHaveBeenCalled(); + }); + + it('should update query builder on value changes', () => { + const context: any = { + queryFragments: {}, + update() { + } + }; + + component.id = 'createdDateRange'; + component.context = context; + component.settings = { field: 'cm:created' }; + + spyOn(context, 'update').and.stub(); + + fixture.detectChanges(); + component.apply({ + from: fromDatetime, + to: toDatetime + }, true); + + const startDate = moment(fromDatetime).startOf('minute').format(); + const endDate = moment(toDatetime).endOf('minute').format(); + + const expectedQuery = `cm:created:['${startDate}' TO '${endDate}']`; + + expect(context.queryFragments[component.id]).toEqual(expectedQuery); + expect(context.update).toHaveBeenCalled(); + }); + + it('should show datetime-format error when an invalid datetime is set', async () => { + fixture.detectChanges(); + component.onChangedHandler({ value: '10/14/2020 10:00:00 PM' }, component.from); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.getFromValidationMessage()).toEqual('SEARCH.FILTER.VALIDATION.INVALID-DATETIME'); + }); + + it('should not show datetime-format error when valid found', async () => { + fixture.detectChanges(); + const input = fixture.debugElement.nativeElement.querySelector('[data-automation-id="datetime-range-from-input"]'); + input.value = '10/16/2017 9:00 PM'; + input.dispatchEvent(new Event('input')); + + fixture.detectChanges(); + await fixture.whenStable(); + + expect(component.getFromValidationMessage()).toEqual(''); + }); + + it('should have no maximum datetime by default', async(() => { + fixture.detectChanges(); + + expect(fixture.debugElement.nativeElement.querySelector('input[ng-reflect-max]')).toBeNull(); + })); + + it('should be able to set a fixed maximum datetime', async () => { + component.settings = { field: 'cm:created', datetimeFormat: datetimeFormatFixture, maxDatetime: maxDatetime }; + fixture.detectChanges(); + + const inputs = fixture.debugElement.nativeElement.querySelectorAll('input[ng-reflect-max="Tue Mar 10 2020 20:00:00 GMT+0"]'); + + expect(inputs[0]).toBeDefined(); + expect(inputs[0]).not.toBeNull(); + expect(inputs[1]).toBeDefined(); + expect(inputs[1]).not.toBeNull(); + }); +}); diff --git a/lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.ts b/lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.ts new file mode 100644 index 0000000000..afa65a3993 --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.ts @@ -0,0 +1,213 @@ +/*! + * @license + * Copyright 2019 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, OnDestroy } from '@angular/core'; +import { FormControl, Validators, FormGroup } from '@angular/forms'; +import { UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core'; + +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'; +import { Moment } from 'moment'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; +import { DatetimeAdapter, MAT_DATETIME_FORMATS } from '@mat-datetimepicker/core'; +import { MAT_MOMENT_DATETIME_FORMATS } from '@mat-datetimepicker/moment'; + +export interface DatetimeRangeValue { + from: string; + to: string; +} + +declare let moment: any; + +const DEFAULT_DATETIME_FORMAT: string = 'DD/MM/YYYY HH:mm'; + +@Component({ + selector: 'adf-search-datetime-range', + templateUrl: './search-datetime-range.component.html', + styleUrls: ['./search-datetime-range.component.scss'], + providers: [ + { provide: MAT_DATETIME_FORMATS, useValue: MAT_MOMENT_DATETIME_FORMATS } + ], + encapsulation: ViewEncapsulation.None, + host: { class: 'adf-search-date-range' } +}) +export class SearchDatetimeRangeComponent implements SearchWidget, OnInit, OnDestroy { + + from: FormControl; + to: FormControl; + + form: FormGroup; + matcher = new LiveErrorStateMatcher(); + + id: string; + settings?: SearchWidgetSettings; + context?: SearchQueryBuilderService; + datetimePickerFormat: string; + maxDatetime: any; + fromMaxDatetime: any; + isActive = false; + startValue: any; + + private onDestroy$ = new Subject(); + + constructor(private dateAdapter: DatetimeAdapter, + private userPreferencesService: UserPreferencesService) { + } + + getFromValidationMessage(): string { + return this.from.hasError('invalidOnChange') || this.hasParseError(this.from) ? 'SEARCH.FILTER.VALIDATION.INVALID-DATETIME' : + this.from.hasError('matDatepickerMax') ? 'SEARCH.FILTER.VALIDATION.BEYOND-MAX-DATETIME' : + this.from.hasError('required') ? 'SEARCH.FILTER.VALIDATION.REQUIRED-VALUE' : + ''; + } + + getToValidationMessage(): string { + return this.to.hasError('invalidOnChange') || this.hasParseError(this.to) ? 'SEARCH.FILTER.VALIDATION.INVALID-DATETIME' : + this.to.hasError('matDatepickerMin') ? 'SEARCH.FILTER.VALIDATION.NO-DAYS' : + this.to.hasError('matDatepickerMax') ? 'SEARCH.FILTER.VALIDATION.BEYOND-MAX-DATETIME' : + this.to.hasError('required') ? 'SEARCH.FILTER.VALIDATION.REQUIRED-VALUE' : + ''; + } + + ngOnInit() { + this.datetimePickerFormat = this.settings?.datetimeFormat ? this.settings.datetimeFormat : DEFAULT_DATETIME_FORMAT; + + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.setLocale(locale)); + + const validators = Validators.compose([ + Validators.required + ]); + + if (this.settings && this.settings.maxDatetime) { + this.maxDatetime = moment(this.settings.maxDatetime); + } + + if (this.startValue) { + const splitValue = this.startValue.split('||'); + const fromValue = this.dateAdapter.parse(splitValue[0], this.datetimePickerFormat); + const toValue = this.dateAdapter.parse(splitValue[1], this.datetimePickerFormat); + this.from = new FormControl(fromValue, validators); + this.to = new FormControl(toValue, validators); + } else { + this.from = new FormControl('', validators); + this.to = new FormControl('', validators); + } + + this.form = new FormGroup({ + from: this.from, + to: this.to + }); + + this.setFromMaxDatetime(); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + + apply(model: { from: string, to: string }, isValid: boolean) { + if (isValid && this.id && this.context && this.settings && this.settings.field) { + this.isActive = true; + + const start = moment(model.from).startOf('minute').format(); + const end = moment(model.to).endOf('minute').format(); + + this.context.queryFragments[this.id] = `${this.settings.field}:['${start}' TO '${end}']`; + this.context.update(); + } + } + + submitValues() { + this.apply(this.form.value, this.form.valid); + } + + hasValidValue(): boolean { + return this.form.valid; + } + + getCurrentValue(): DatetimeRangeValue { + return { + from: this.dateAdapter.format(this.form.value.from, this.datetimePickerFormat), + to: this.dateAdapter.format(this.form.value.from, this.datetimePickerFormat) + }; + } + + setValue(parsedDate: string) { + const splitValue = parsedDate.split('||'); + const fromValue = this.dateAdapter.parse(splitValue[0], this.datetimePickerFormat); + const toValue = this.dateAdapter.parse(splitValue[1], this.datetimePickerFormat); + this.from.setValue(fromValue); + this.from.markAsDirty(); + this.from.markAsTouched(); + this.to.setValue(toValue); + this.to.markAsDirty(); + this.to.markAsTouched(); + this.submitValues(); + } + + reset() { + this.isActive = false; + this.form.reset({ + from: '', + to: '' + }); + if (this.id && this.context) { + this.context.queryFragments[this.id] = ''; + this.context.update(); + } + this.setFromMaxDatetime(); + } + + onChangedHandler(event: any, formControl: FormControl) { + + const inputValue = event.value; + const formatDate = this.dateAdapter.parse(inputValue, this.datetimePickerFormat); + if (formatDate && formatDate.isValid()) { + formControl.setValue(formatDate); + } else if (formatDate) { + formControl.setErrors({ + 'invalidOnChange': true + }); + } + + this.setFromMaxDatetime(); + } + + setLocale(locale) { + this.dateAdapter.setLocale(locale); + moment.locale(locale); + } + + hasParseError(formControl): boolean { + return formControl.hasError('matDatepickerParse') && formControl.getError('matDatepickerParse').text; + } + + forcePlaceholder(event: any) { + event.srcElement.click(); + } + + setFromMaxDatetime() { + this.fromMaxDatetime = (!this.to.value || this.maxDatetime && (moment(this.maxDatetime).isBefore(this.to.value))) ? this.maxDatetime : moment(this.to.value); + } +} diff --git a/lib/content-services/src/lib/search/components/search-filter/search-filter.service.ts b/lib/content-services/src/lib/search/components/search-filter/search-filter.service.ts index fc0c0caf29..743a39cbca 100644 --- a/lib/content-services/src/lib/search/components/search-filter/search-filter.service.ts +++ b/lib/content-services/src/lib/search/components/search-filter/search-filter.service.ts @@ -22,6 +22,7 @@ import { SearchSliderComponent } from '../search-slider/search-slider.component' import { SearchNumberRangeComponent } from '../search-number-range/search-number-range.component'; import { SearchCheckListComponent } from '../search-check-list/search-check-list.component'; import { SearchDateRangeComponent } from '../search-date-range/search-date-range.component'; +import { SearchDatetimeRangeComponent } from '../search-datetime-range/search-datetime-range.component'; @Injectable({ providedIn: 'root' @@ -37,7 +38,8 @@ export class SearchFilterService { 'slider': SearchSliderComponent, 'number-range': SearchNumberRangeComponent, 'check-list': SearchCheckListComponent, - 'date-range': SearchDateRangeComponent + 'date-range': SearchDateRangeComponent, + 'datetime-range': SearchDatetimeRangeComponent }; } diff --git a/lib/content-services/src/lib/search/search.module.ts b/lib/content-services/src/lib/search/search.module.ts index 6b6c41dfd7..bc509a9082 100644 --- a/lib/content-services/src/lib/search/search.module.ts +++ b/lib/content-services/src/lib/search/search.module.ts @@ -39,6 +39,7 @@ import { SearchSortingPickerComponent } from './components/search-sorting-picker import { SEARCH_QUERY_SERVICE_TOKEN } from './search-query-service.token'; import { SearchQueryBuilderService } from './search-query-builder.service'; import { SearchFilterContainerComponent } from './components/search-filter-container/search-filter-container.component'; +import { SearchDatetimeRangeComponent } from './components/search-datetime-range/search-datetime-range.component'; @NgModule({ imports: [ @@ -62,6 +63,7 @@ import { SearchFilterContainerComponent } from './components/search-filter-conta SearchPanelComponent, SearchCheckListComponent, SearchDateRangeComponent, + SearchDatetimeRangeComponent, SearchSortingPickerComponent, SearchFilterContainerComponent ], @@ -79,6 +81,7 @@ import { SearchFilterContainerComponent } from './components/search-filter-conta SearchPanelComponent, SearchCheckListComponent, SearchDateRangeComponent, + SearchDatetimeRangeComponent, SearchSortingPickerComponent, SearchFilterContainerComponent ],