From 640a73653058a142eb8a6804961da78afab9d7bf Mon Sep 17 00:00:00 2001 From: "swapnil.verma" Date: Fri, 21 Jul 2023 11:13:30 +0530 Subject: [PATCH] [ACS-4985] Resolved e2e test cases [ACS-4985] Revert test fix for e2e execution. Updated search.config.ts for e2e test cases Testing global install of webdriver-manager for e2e webdriver fix [ACS-4985] Resolved e2e test failures [ACS-4985] Increased value for drag emulation to account for increased width of the side filters panel in demo-shell search results window. [ACS-4985] Resolved unit test failures [ACS-4985] Resolved lint issues [ACS-4985] Moved new components to v6.2.0 in versionIndex.md [ACS-4985] Added documentation to versionIndex.md and README.md [ACS-4985] Moved inLast input field back to input type number. [ACS-4985] Resolved issue where typing a special character after adding some numbers in the 'In the last' input field would clear out the field [ACS-4985] Updated test cases after query generation changes [ACS-4985] Added missing null check when generating query [ACS-4985] Added *ngFor back to the search-date-range-advanced-tabbed.component.html [ACS-4985] Updated query generation logic. Now both 'In the last' and 'Between' options use the start date to end date query format [ACS-4985] Removed ANY case from switch (will be handled by default case) [ACS-4985] Split declarations into multiple lines [ACS-4985] Fixed code smell in regex [ACS-4985] Updated dates in documentation [ACS-4985] Updated documentation [ACS-4985] Added link for AngularMaterial bug for CSS workaround [ACS-4985] Added test cases for end date validation. Fixed minor issue when setting invalid date error on between date form fields [ACS-4985] Added validation when user manually enters the start and end dates [ACS-4985] Added borders to inputs [ACS-4985] Updated test cases for SearchDateRangeAdvancedTabbedComponent [ACS-4985] Transferred business logic from SearchDateRangeAdvancedComponent and SearchFilterTabbedComponent to SearchDateRangeAdvancedTabbedComponent. Updated test cases accordingly [ACS-4985] Resolved PR review comments [ACS-4985] Removed unused code from base-query-builder.service.ts [ACS-4985] Resolved linting and unit test issues [ACS-4985] Resolved minor issues where switching between tabs could sometime cause the tab content to not show up [ACS-4985] Resolved minor issues with display label creation [ACS-4985] Updated component to use MatDateFnsAdapter. BetweenStartDate and BetweenEndDate are now formatted when selected from the calender [ACS-4985] Resolved issue where clear button would not clear the values properly [ACS-4985] Added @angular/material-date-fns-adapter package [ACS-4985] Added image for updated documentation [ACS-4985] Added validation to SearchDateRangeAdvancedComponent [ACS-4985] Updated documentation for components [ACS-4985] Removed disableUpdateOnSubmit flag from search widgets [ACS-4985] Updated the documentation for the components [ACS-4985] Added test cases for SearchDateRangeAdvancedTabbedComponent. Moved pending logic from template to typescript [ACS-4985] Added clear and apply button to SearchDateRangeAdvancedTabbedComponent. Moved logic from template to typescript file [ACS-4985] Updated test cases for SearchFilterTabbedComponent. Added safety checks to component [ACS-4985] Added field validation to test case [ACS-4985] Updated SearchDateRangeAdvancedTabbed component to no longer use getters and setters [ACS-4985] Updated test cases for SearchDateRangeAdvancedComponent. Minor updates to the component template and logic. Component no longer uses getters and setters in template [ACS-4985] Updated SearchDateRangeAdvancedTabbed component to use variables instead of getters and setters [ACS-4985] Updated app.cconfig for demo-shell to use new date-range-advanced configuration ACS-4985 Fixed issue with nx build, some clean ups, using changes in configuration [ACS-4985] Updated test cases for changed date format [ACS-4985] Updated date formats for SearchDateRangeAdvancedComponent [ACS-4985] Removed fdescribe test cases for SearchDateRangeAdvancedComponent. [ACS-4985] Fixed test cases for SearchDateRangeAdvancedComponent. [ACS-4985] Fixed erroneous imports [ACS-4985] Added license headers and re-ordered imports [ACS-4985] Updated test cases for SearchDateRangeAdvancedComponent from moment.js to date-fns [ACS-4985] Migrated SearchDateRangeAdvancedComponent from moment.js to date-fns Added import for BaseQueryBuilderService in public-api.ts. Fixes #8647 [ACS-4985] Updated imports in test cases [ACS-4985] Added exports for SearchDateRangeAdvanced and SearchFilterTabbed components to public-api.ts. Updated imports in both components [ACS-4985] Resolved minor issue where the reset method would still trigger multiple api calls when used with the TabbedComponent [ACS-4985] Added test cases for SearchDateRangeAdvancedComponent. Minor update to test cases for SearchFilterTabbedComponent [ACS-4985] Updated Labels for 'In last' date range option [ACS-4985] Updated SearchModule declarations. Fixed minor typo in SearchFilterTabbedComponent [ACS-4985] Added test cases for SearchFilterTabbedComponent. Added test case placeholders for SearchDateRangeAdvancedComponent [ACS-4985] Added data-automation-id to search-date-range-advanced.component.html [ACS-4985] Added test cases for SearchFilterTabbedComponent [ACS-4985] Removed vertical mode from SearchFilterTabbedComponent [ACS-4985] Updated UI for search filters. Minor fixes [ACS-4985] Added documentation for SearchFilterTabbedComponent and SearchDateRangeAdvancedComponent [ACS-4985] Added compatibility of all search filters/facets with SearchFilterTabbedComponent [ACS-4985] Using widget-composite component now correctly updates the search query on submit. Added optional property to disable update on submit button click for widget-composite. [ACS-4985] Added SearchFilterTabbedComponent and SearchDateRangeAdvancedComponent. Added config for using the new components --- demo-shell/src/app.config.json | 17 ++ .../search/search-result.component.scss | 1 - docs/README.md | 3 + ...ch-date-range-advanced-tabbed.component.md | 113 +++++++ .../search-date-range-advanced.component.md | 47 +++ .../search-filter-tabbed.component.md | 41 +++ .../components/search-filter.component.md | 2 + .../search-date-range-advanced-tabbed.png | Bin 0 -> 16327 bytes .../images/search-date-range-advanced.png | Bin 0 -> 8199 bytes .../docassets/images/search-filter-tabbed.png | Bin 0 -> 16534 bytes docs/versionIndex.md | 3 + e2e/search/components/search-radio.e2e.ts | 18 +- e2e/search/search.config.ts | 17 ++ lib/content-services/src/lib/i18n/en.json | 32 ++ ...-date-range-advanced-tabbed.component.html | 13 + ...te-range-advanced-tabbed.component.spec.ts | 236 +++++++++++++++ ...ch-date-range-advanced-tabbed.component.ts | 171 +++++++++++ .../date-range-type.ts | 22 ++ .../in-last-date-type.ts | 22 ++ .../search-date-range-advanced.component.html | 45 +++ .../search-date-range-advanced.component.scss | 45 +++ ...arch-date-range-advanced.component.spec.ts | 281 ++++++++++++++++++ .../search-date-range-advanced.component.ts | 180 +++++++++++ .../search-date-range-advanced.ts | 27 ++ .../search-widget-chip.component.html | 2 +- .../search-widget-chip.component.ts | 7 + .../search-filter-tab.directive.ts | 28 ++ .../search-filter-tabbed.component.html | 5 + .../search-filter-tabbed.component.scss | 20 ++ .../search-filter-tabbed.component.ts | 30 ++ .../src/lib/search/public-api.ts | 3 + .../src/lib/search/search.module.ts | 12 +- .../search/services/search-filter.service.ts | 6 +- .../src/lib/common/utils/date-fns-utils.ts | 81 +++++ lib/core/src/lib/common/utils/public-api.ts | 1 + .../pages/search/search-slider.page.ts | 2 +- package-lock.json | 14 + package.json | 1 + 38 files changed, 1534 insertions(+), 14 deletions(-) create mode 100644 docs/content-services/components/search-date-range-advanced-tabbed.component.md create mode 100644 docs/content-services/components/search-date-range-advanced.component.md create mode 100644 docs/content-services/components/search-filter-tabbed.component.md create mode 100644 docs/docassets/images/search-date-range-advanced-tabbed.png create mode 100644 docs/docassets/images/search-date-range-advanced.png create mode 100644 docs/docassets/images/search-filter-tabbed.png create mode 100644 lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.html create mode 100644 lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.spec.ts create mode 100644 lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.ts create mode 100644 lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/date-range-type.ts create mode 100644 lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/in-last-date-type.ts create mode 100644 lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.html create mode 100644 lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.scss create mode 100644 lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.spec.ts create mode 100644 lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.ts create mode 100644 lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.ts create mode 100644 lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tab.directive.ts create mode 100644 lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.html create mode 100644 lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.scss create mode 100644 lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.ts create mode 100644 lib/core/src/lib/common/utils/date-fns-utils.ts diff --git a/demo-shell/src/app.config.json b/demo-shell/src/app.config.json index 886300e64db..d54ced5a3ef 100644 --- a/demo-shell/src/app.config.json +++ b/demo-shell/src/app.config.json @@ -322,6 +322,23 @@ } } }, + { + "id": "createdModifiedDateRange", + "name": "Date", + "enabled": true, + "component": { + "selector": "date-range-advanced", + "settings": { + "dateFormat": "dd-MMM-yy", + "maxDate": "today", + "field": "cm:created, cm:modified", + "displayedLabelsByField": { + "cm:created": "Created Date", + "cm:modified": "Modified Date" + } + } + } + }, { "id": "queryType", "name": "Type", diff --git a/demo-shell/src/app/components/search/search-result.component.scss b/demo-shell/src/app/components/search/search-result.component.scss index e50c780e8e0..a0334839f8e 100644 --- a/demo-shell/src/app/components/search/search-result.component.scss +++ b/demo-shell/src/app/components/search/search-result.component.scss @@ -3,7 +3,6 @@ margin-left: 5px; .app-search-settings { - width: 260px; border: 1px solid #eee; } diff --git a/docs/README.md b/docs/README.md index 3e3cdb73200..7572ca13b1a 100644 --- a/docs/README.md +++ b/docs/README.md @@ -289,12 +289,15 @@ for more information about installing and using the source code. | [Search Chip Input Component](content-services/components/search-chip-input.component.md) | Displays input for providing phrases display as "chips". | [Source](../lib/content-services/src/lib/search/components/search-chip-input/search-chip-input.component.ts) | | [Search Chip Autocomplete Input component](content-services/components/search-chip-autocomplete-input.component.md) | Displays an input with autocomplete options. | [Source](../lib/content-services/src/lib/search/components/search-chip-autocomplete-input/search-chip-autocomplete-input.component.ts) | | [Search Chip List Component](content-services/components/search-chip-list.component.md) | Displays search criteria as a set of "chips". | [Source](../lib/content-services/src/lib/search/components/search-chip-list/search-chip-list.component.ts) | +| [Search Date Range Advanced Component](content-services/components/search-date-range-advanced.component.md) | Displays a UI to configure different kinds of search criteria around date. Options are 'Anyytime', 'In the last' and 'Between' | [Source](../lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.ts) | | [Search control component](content-services/components/search-control.component.md) | Displays a input text that shows find-as-you-type suggestions. | [Source](../lib/content-services/src/lib/search/components/search-control.component.ts) | | [Search date range component](content-services/components/search-date-range.component.md) | Implements a search widget for the Search Filter component. | [Source](../lib/content-services/src/lib/search/components/search-date-range/search-date-range.component.ts) | +| [Search date range advanced tabbed component](content-services/components/search-date-range-advanced-tabbed.component.md) | Implements a tabbed advanced search widget for the Search Filter component. | [Source](../lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.ts) | | [Search datetime range component](content-services/components/search-datetime-range.component.md) | Implements a search widget for the Search Filter component. | [Source](../lib/content-services/src/lib/search/components/search-datetime-range/search-datetime-range.component.ts) | | [Search Filter Autocomplete Chips component](content-services/components/search-filter-autocomplete-chips.component.md) | Implements a search widget for the Search Filter component. | [Source](../lib/content-services/src/lib/search/components/search-filter-autocomplete-chips/search-filter-autocomplete-chips.component.ts) | | [Search Filter Chips component](content-services/components/search-filter-chips.component.md) | Represents a chip based container component for custom search and faceted search settings. | [Source](../lib/content-services/src/lib/search/components/search-filter-chips/search-filter-chips.component.ts) | | [Search Filter component](content-services/components/search-filter.component.md) | Represents a main container component for custom search and faceted search settings. | [Source](../lib/content-services/src/lib/search/components/search-filter/search-filter.component.ts) | +| [Search Filter Tabbed component](content-services/components/search-filter-tabbed.component.md) | Represents a container component for creating tabbed layout. | [Source](../lib/content-services/src/lib/search/components/search-filter/search-filter.component.ts) | | [Search Form component](content-services/components/search-form.component.md) | Search Form screenshot | [Source](../lib/content-services/src/lib/search/components/search-form/search-form.component.ts) | | [Search Logical Filter component](content-services/components/search-logical-filter.component.md) | Displays 3 chip inputs each representing different logical condition for search query. | [Source](../lib/content-services/src/lib/search/components/search-logical-filter/search-logical-filter.component.ts) | | [Search Properties component](content-services/components/search-properties.component.md) | Allows to search by file size and type.| [Source](../lib/content-services/src/lib/search/components/search-properties/search-properties.component.ts) | diff --git a/docs/content-services/components/search-date-range-advanced-tabbed.component.md b/docs/content-services/components/search-date-range-advanced-tabbed.component.md new file mode 100644 index 00000000000..de189e4d5d4 --- /dev/null +++ b/docs/content-services/components/search-date-range-advanced-tabbed.component.md @@ -0,0 +1,113 @@ +--- +Title: Search date range advanced tabbed component +Added: v6.2.0 +Status: Active +Last reviewed: 2023-07-10 +--- + +# [Search date range advanced tabbed component](../../../lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.ts "Defined in search-date-range-advanced-tabbed.component.ts") + +Represents a tabbed advanced date range [search widget](../../../lib/content-services/src/lib/search/models/search-widget.interface.ts) for +the [Search Filter component](search-filter.component.md). + +![Date Range Advanced Widget](../../docassets/images/search-date-range-advanced-tabbed.png) + +## Basic usage + +```json +{ + "search": { + "categories": [ + { + "id": "createdModifiedDateRange", + "name": "Date", + "enabled": true, + "component": { + "selector": "date-range-advanced", + "settings": { + "dateFormat": "dd-MMM-yy", + "maxDate": "today", + "field": "cm:created, cm:modified", + "displayedLabelsByField": { + "cm:created": "Created Date", + "cm:modified": "Modified Date" + } + } + } + } + ] + } +} +``` + +### Settings + +| Name | Type | Description | +|------------------------|---------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| field | string | Fields to apply the query to. Multiple, comma separated fields can be passed, to create multiple tabs per field. Required value | +| dateFormat | string | Date format. Dates used by the datepicker are Javascript Date objects, using [date-fns](https://date-fns.org/v2.30.0/docs/format) for formatting, so you can use any date format supported by the library. Default is 'dd-MMM-yy (sample date - 07-Jun-23) | +| maxDate | string | A fixed date (in format mentioned above, default format: dd-MMM-yy) or the string `"today"` that will set the maximum searchable date. Default is today. | +| displayedLabelsByField | { [key: string]: string } | A javascript object containing the different display labels to be used for each tab name, identified by the field for a particular tab. | + +## Details + +This component creates a tabbed layout where each tab consists of the [SearchDateRangeAdvanced](./search-date-range-advanced-tabbed.component.md) component, which allows user to create a query containing multiple date related queries in one go. + +See the [Search filter component](search-filter.component.md) for full details of how to use widgets in a search query. + +### Custom date format + +You can set the date range picker to work with any date format your app requires. You can use +any date format supported by the [date-fns](https://date-fns.org/v2.30.0/docs/format) library +in the `dateFormat` and in the `maxDate` setting: + +```json +{ + "search": { + "categories": [ + { + "id": "createdModifiedDateRange", + "name": "Date", + "enabled": true, + "component": { + "selector": "date-range-advanced", + "settings": { + "dateFormat": "dd-MMM-yy", + "maxDate": "02-May-23", + "field": "cm:created, cm:modified", + "displayedLabelsByField": { + "cm:created": "Created Date", + "cm:modified": "Modified Date" + } + } + } + } + ] + } +} +``` + +The [SearchDateRangeAdvanced](./search-date-range-advanced-tabbed.component.md) component allows 3 different kinds of date related operations to be performed. +Based on what information is provided to that component, this component will create different kinds of queries - + +- Anytime - No date filters are applied on the `field`. This option is selected by default +- In the last - Allows to user to apply a filter to only show results from the last 'n' unit of time. +- Between - Allows the user to select a range of dates to filter the search results. + +The queries generated by this filter when using the 'In the last' or 'Between' options is of the form - + +`:[ TO ]` + + +## See also + +- [Search Configuration Guide](../../user-guide/search-configuration-guide.md) +- [Search Query Builder service](../services/search-query-builder.service.md) +- [Search Widget Interface](../interfaces/search-widget.interface.md) +- [Search check list component](search-check-list.component.md) +- [Search date range component](search-date-range.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) +- [Search filter tabbed component](search-filter-tabbed.component.md) diff --git a/docs/content-services/components/search-date-range-advanced.component.md b/docs/content-services/components/search-date-range-advanced.component.md new file mode 100644 index 00000000000..231365e3f21 --- /dev/null +++ b/docs/content-services/components/search-date-range-advanced.component.md @@ -0,0 +1,47 @@ +--- +Title: Search date range advanced component +Added: v6.2.0 +Status: Active +Last reviewed: 2023-07-10 +--- + +# [Search date range advanced component](../../../lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.ts "Defined in search-date-range-advanced.component.ts") + +Represents an advanced date range component for +the [SearchAdvancedDateRangeTabbedComponent](search-date-range-advanced-tabbed.component.md). + +![Date Range Advanced Widget](../../docassets/images/search-date-range-advanced.png) + +## Basic usage + +```html + + +``` + +## Class Members + +### Properties + +| Name | Type | Description | +|--------------|-------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| field | string | Field to apply the query to. Required value | +| maxDate | string | A fixed date (default format: dd-MMM-yy) or the string `"today"` that will set the maximum searchable date. Default is today. | +| dateFormat | string | Date format. Dates used by the datepicker are Javascript Date objects, using [date-fns](https://date-fns.org/v2.30.0/docs/format) for formatting, so you can use any date format supported by the library. Default is 'dd-MMM-yy (sample date - 07-Jun-23) | +| initialValue | SearchDateRangeAdvanced | Initial value for the component | + +### Events + +| Name | Type | Description | +|---------------------|------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------| +| changed | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`>` | Emitted whenever a change is made in the component values. Emits the changes being made in the component. | +| valid | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted whenever a change is made in the component values. Emits a flag indicating whether the current state of the component is valid or not. | + +## Details + +This component lets the user choose a variety of options to perform date related operations. + +- Anytime - No date related data will be returned. This option is selected by default +- In the last - Allows user to perform date related operations over a period of time. The user can select the length of the period from current time, +as well as its unit. Currently, 3 units are supported - Days, Weeks, and Months. +- Between - Allows the user to select a range of dates to perform operations on. diff --git a/docs/content-services/components/search-filter-tabbed.component.md b/docs/content-services/components/search-filter-tabbed.component.md new file mode 100644 index 00000000000..404ade13980 --- /dev/null +++ b/docs/content-services/components/search-filter-tabbed.component.md @@ -0,0 +1,41 @@ +--- +Title: Search filter tabbed component +Added: v6.2.0 +Status: Active +Last reviewed: 2023-07-10 +--- + +# [Search filter tabbed component](../../../lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.ts "Defined in search-filter-tabbed.component.ts") + +Represents a container for [Search Filters](search-filter.component.md) to provide a tabbed user interface for the filters. + +![Search Filter Tabbed Widget](../../docassets/images/search-filter-tabbed.png) + +## Basic Usage + +```html + + + + + + +``` + +In order to generate a tabbed widget for multiple search filters, you can pass in the search filter widget component as a content child of the adf-search-filter-tabbed component as shown above. + +Additionally, you also have to make sure that the search filter being passed as a content child of the adf-search-filter-tabbed component, also has the adf-search-filter-tabbed directive applied on it, +with the name input property being assigned the value of whatever name should be displayed for that particular tab + +## See also + +- [Search Configuration Guide](../../user-guide/search-configuration-guide.md) +- [Search Query Builder service](../services/search-query-builder.service.md) +- [Search Widget Interface](../interfaces/search-widget.interface.md) +- [Search check list component](search-check-list.component.md) +- [Search date range component](search-date-range.component.md) +- [Search date range advanced component](search-date-range-advanced.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/content-services/components/search-filter.component.md b/docs/content-services/components/search-filter.component.md index af6acfd7649..86d457d3100 100644 --- a/docs/content-services/components/search-filter.component.md +++ b/docs/content-services/components/search-filter.component.md @@ -37,7 +37,9 @@ to build and execute the query. - [Search Widget Interface](../interfaces/search-widget.interface.md) - [Search check list component](search-check-list.component.md) - [Search date range component](search-date-range.component.md) +- [Search date range advanced component](search-date-range-advanced.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) +- [Search filter tabbed component](search-filter-tabbed.component.md) diff --git a/docs/docassets/images/search-date-range-advanced-tabbed.png b/docs/docassets/images/search-date-range-advanced-tabbed.png new file mode 100644 index 0000000000000000000000000000000000000000..b636120d42638a1137f6653be9ce5467bbc03577 GIT binary patch literal 16327 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 literal 0 HcmV?d00001 diff --git a/docs/docassets/images/search-date-range-advanced.png b/docs/docassets/images/search-date-range-advanced.png new file mode 100644 index 0000000000000000000000000000000000000000..90d17bca04b2b77bead93e319c5e11a46214de69 GIT binary patch literal 8199 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 literal 0 HcmV?d00001 diff --git a/docs/docassets/images/search-filter-tabbed.png b/docs/docassets/images/search-filter-tabbed.png new file mode 100644 index 0000000000000000000000000000000000000000..85e8a8d2a01c9f01d9f31da48584a8685ca8a138 GIT binary patch literal 16534 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 O;^`APRF3$|H~$4Bo3q{k literal 0 HcmV?d00001 diff --git a/docs/versionIndex.md b/docs/versionIndex.md index 6b7614fc30e..b7940419cdf 100644 --- a/docs/versionIndex.md +++ b/docs/versionIndex.md @@ -48,6 +48,9 @@ backend services have been tested with each released version of ADF. - [Search Properties component](content-services/components/search-properties.component.md) +- [Search Date Range Advanced Component](content-services/components/search-date-range-advanced.component.md) +- [Search Date Range Advanced Tabbed Component](content-services/components/search-date-range-advanced-tabbed.component.md) +- [Search Filter Tabbed Component](content-services/components/search-filter-tabbed.component.md) diff --git a/e2e/search/components/search-radio.e2e.ts b/e2e/search/components/search-radio.e2e.ts index 771b7ec09d2..42a3e616d90 100644 --- a/e2e/search/components/search-radio.e2e.ts +++ b/e2e/search/components/search-radio.e2e.ts @@ -155,7 +155,7 @@ describe('Search Radio Component', () => { await searchFiltersPage.clickTypeFilterHeader(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(10); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(13); await navigationBarPage.navigateToContentServices(); @@ -170,7 +170,7 @@ describe('Search Radio Component', () => { await searchFiltersPage.clickTypeFilterHeader(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(10); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(13); await navigationBarPage.navigateToContentServices(); jsonFile.categories[5].component.settings.pageSize = 9; @@ -184,7 +184,7 @@ describe('Search Radio Component', () => { await searchFiltersPage.clickTypeFilterHeader(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(9); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(12); await searchFiltersPage.typeFiltersPage().checkShowMoreButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); @@ -213,21 +213,21 @@ describe('Search Radio Component', () => { await searchFiltersPage.clickTypeFilterHeader(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(5); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(8); await searchFiltersPage.typeFiltersPage().checkShowMoreButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); await searchFiltersPage.typeFiltersPage().clickShowMoreButton(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(10); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(13); await searchFiltersPage.typeFiltersPage().checkShowMoreButtonIsNotDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().clickShowLessButton(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(5); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(8); await searchFiltersPage.typeFiltersPage().checkShowMoreButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); @@ -244,21 +244,21 @@ describe('Search Radio Component', () => { await searchFiltersPage.clickTypeFilterHeader(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(5); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(8); await searchFiltersPage.typeFiltersPage().checkShowMoreButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); await searchFiltersPage.typeFiltersPage().clickShowMoreButton(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(10); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(13); await searchFiltersPage.typeFiltersPage().checkShowMoreButtonIsNotDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().clickShowLessButton(); - await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(5); + await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(8); await searchFiltersPage.typeFiltersPage().checkShowMoreButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); diff --git a/e2e/search/search.config.ts b/e2e/search/search.config.ts index 45ab494d425..89c50d1247b 100644 --- a/e2e/search/search.config.ts +++ b/e2e/search/search.config.ts @@ -176,6 +176,23 @@ export class SearchConfiguration { ] } } + }, + { + id: 'createdModifiedDateRange', + name: 'Date', + enabled: true, + component: { + selector: 'date-range-advanced', + settings: { + dateFormat: 'dd-MMM-yy', + maxDate: 'today', + field: 'cm:created, cm:modified', + displayedLabelsByField: { + "cm:created": 'Created Date', + "cm:modified": 'Modified Date' + } + } + } } ], highlight: { diff --git a/lib/content-services/src/lib/i18n/en.json b/lib/content-services/src/lib/i18n/en.json index c6534fb37c1..8162a3d4f11 100644 --- a/lib/content-services/src/lib/i18n/en.json +++ b/lib/content-services/src/lib/i18n/en.json @@ -415,6 +415,38 @@ "EXCLUDE_LABEL": "EXCLUDE these words", "EXCLUDE_HINT": "Results will exclude matches with these words" }, + "DATE_RANGE_ADVANCED": { + "OPTIONS": { + "ANYTIME": "Anytime", + "IN_LAST": "In the last", + "BETWEEN": "Between" + }, + "IN_LAST_LABELS": { + "DAYS": "Days", + "WEEKS": "Weeks", + "MONTHS": "Months" + }, + "IN_LAST_DISPLAY_LABELS": { + "DAYS": "In the last {{value}} days", + "WEEKS": "In the last {{value}} weeks", + "MONTHS": "In the last {{value}} months" + }, + "BETWEEN_PLACEHOLDERS": { + "START_DATE": "Start Date", + "END_DATE": "End Date" + }, + "ERROR": { + "IN_LAST": "Value required", + "START_DATE": { + "REQUIRED": "Start Date required", + "INVALID_FORMAT": "Start Date invalid" + }, + "END_DATE": { + "REQUIRED": "End Date required", + "INVALID_FORMAT": "End Date invalid" + } + } + }, "SEARCH_PROPERTIES": { "FILE_SIZE": "File Size", "FILE_SIZE_PLACEHOLDER": "Enter file size", diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.html b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.html new file mode 100644 index 00000000000..45047255aa2 --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.html @@ -0,0 +1,13 @@ + + + + + + diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.spec.ts b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.spec.ts new file mode 100644 index 00000000000..c57141917e8 --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.spec.ts @@ -0,0 +1,236 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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 { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TranslateModule } from '@ngx-translate/core'; +import { ContentTestingModule } from '../../../testing/content.testing.module'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { SearchDateRangeAdvanced } from './search-date-range-advanced/search-date-range-advanced'; +import { SearchFilterTabbedComponent } from '../search-filter-tabbed/search-filter-tabbed.component'; +import { SearchDateRangeAdvancedComponent } from './search-date-range-advanced/search-date-range-advanced.component'; +import { SearchDateRangeAdvancedTabbedComponent } from './search-date-range-advanced-tabbed.component'; +import { DateRangeType } from './search-date-range-advanced/date-range-type'; +import { InLastDateType } from './search-date-range-advanced/in-last-date-type'; +import { + endOfDay, + endOfToday, + formatISO, + parse, + startOfDay, startOfMonth, + startOfWeek, + subDays, + subMonths, + subWeeks +} from 'date-fns'; + +@Component({ + selector: 'adf-search-filter-tabbed', + template: `` +}) +export class MockSearchFilterTabbedComponent {} + +@Component({ + selector: 'adf-search-date-range-advanced', + template: `` +}) +export class MockSearchDateRangeAdvancedComponent { + @Input() + dateFormat: string; + @Input() + maxDate: string; + @Input() + field: string; + @Input() + initialValue: SearchDateRangeAdvanced; + + @Output() + changed = new EventEmitter>(); + @Output() + valid = new EventEmitter(); +} +describe('SearchDateRangeAdvancedTabbedComponent', () => { + let component: SearchDateRangeAdvancedTabbedComponent; + let fixture: ComponentFixture; + let betweenMockData: SearchDateRangeAdvanced; + let inLastMockData: SearchDateRangeAdvanced; + let anyMockDate: SearchDateRangeAdvanced; + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [SearchDateRangeAdvancedTabbedComponent, SearchFilterTabbedComponent, SearchDateRangeAdvancedComponent], + imports: [ + TranslateModule.forRoot(), + ContentTestingModule + ], + providers: [ + { provide: SearchFilterTabbedComponent, useClass: MockSearchFilterTabbedComponent }, + { provide: SearchDateRangeAdvancedComponent, useClass: MockSearchDateRangeAdvancedComponent } + ] + }); + fixture = TestBed.createComponent(SearchDateRangeAdvancedTabbedComponent); + + component = fixture.componentInstance; + component.id = 'dateRangeAdvanced'; + component.context = { + queryFragments: { + dateRangeAdvanced: '' + }, + update: jasmine.createSpy('update') + } as any; + component.settings = { + hideDefaultAction: false, + dateFormat: 'dd-MMM-yy', + maxDate: 'today', + field: 'createdDate, modifiedDate', + displayedLabelsByField: { + createdDate: 'Created Date', + modifiedDate: 'Modified Date' + } + }; + component.tabsValidity = { + createdDate: true, + modifiedDate: true + }; + + betweenMockData = { + dateRangeType: DateRangeType.BETWEEN, + inLastValueType: InLastDateType.DAYS, + inLastValue: undefined, + betweenStartDate: parse('05-Jun-23', 'dd-MMM-yy', new Date()), + betweenEndDate: parse('07-Jun-23', 'dd-MMM-yy', new Date()) + }; + inLastMockData = { + dateRangeType: DateRangeType.IN_LAST, + inLastValueType: InLastDateType.WEEKS, + inLastValue: '5', + betweenStartDate: undefined, + betweenEndDate: undefined + }; + anyMockDate = { + dateRangeType: DateRangeType.ANY, + inLastValueType: InLastDateType.DAYS, + inLastValue: null, + betweenStartDate: null, + betweenEndDate: null + }; + + fixture.detectChanges(); + }); + + it('should be able to generate separate fields on init', () => { + fixture.detectChanges(); + expect(component.fields.length).toBe(2); + expect(component.fields).toEqual(['createdDate', 'modifiedDate']); + }); + + it('should return hasValidValue as false if any of the fields has an invalid value', () => { + component.tabsValidity['createdDate'] = false; + fixture.detectChanges(); + expect(component.hasValidValue()).toBeFalse(); + fixture.detectChanges(); + component.tabsValidity['modifiedDate'] = false; + fixture.detectChanges(); + expect(component.hasValidValue()).toBeFalse(); + component.tabsValidity['createdDate'] = true; + component.tabsValidity['modifiedDate'] = true; + fixture.detectChanges(); + expect(component.hasValidValue()).toBeTrue(); + }); + + it('should update displayValue when values are submitted', () => { + spyOn(component.displayValue$, 'next'); + component.onDateRangedValueChanged(betweenMockData, 'createdDate'); + component.onDateRangedValueChanged(inLastMockData, 'modifiedDate'); + fixture.detectChanges(); + component.submitValues(); + expect(component.displayValue$.next).toHaveBeenCalledWith('CREATED DATE: 05-Jun-23 - 07-Jun-23 MODIFIED DATE: SEARCH.DATE_RANGE_ADVANCED.IN_LAST_DISPLAY_LABELS.WEEKS'); + + component.onDateRangedValueChanged(anyMockDate, 'createdDate'); + component.onDateRangedValueChanged(anyMockDate, 'modifiedDate'); + fixture.detectChanges(); + component.submitValues(); + expect(component.displayValue$.next).toHaveBeenCalledWith(''); + }); + + it('should update query when values are changed', () => { + component.onDateRangedValueChanged(betweenMockData, 'createdDate'); + component.onDateRangedValueChanged(inLastMockData, 'modifiedDate'); + fixture.detectChanges(); + let inLastStartDate = startOfWeek(subWeeks(new Date(), 5)); + let query = `createdDate:['${formatISO(startOfDay(betweenMockData.betweenStartDate))}' TO '${formatISO(endOfDay(betweenMockData.betweenEndDate))}']` + + ` AND modifiedDate:['${formatISO(startOfDay(inLastStartDate))}' TO '${formatISO(endOfToday())}']`; + expect(component.combinedQuery).toEqual(query); + + inLastMockData = { + dateRangeType: DateRangeType.IN_LAST, + inLastValueType: InLastDateType.DAYS, + inLastValue: '9', + betweenStartDate: null, + betweenEndDate: null + }; + component.onDateRangedValueChanged(inLastMockData, 'modifiedDate'); + fixture.detectChanges(); + inLastStartDate = startOfDay(subDays(new Date(), 9)); + query = `createdDate:['${formatISO(startOfDay(betweenMockData.betweenStartDate))}' TO '${formatISO(endOfDay(betweenMockData.betweenEndDate))}']` + + ` AND modifiedDate:['${formatISO(startOfDay(inLastStartDate))}' TO '${formatISO(endOfToday())}']`; + expect(component.combinedQuery).toEqual(query); + + inLastMockData = { + dateRangeType: DateRangeType.IN_LAST, + inLastValueType: InLastDateType.MONTHS, + inLastValue: '7', + betweenStartDate: null, + betweenEndDate: null + }; + component.onDateRangedValueChanged(inLastMockData, 'modifiedDate'); + fixture.detectChanges(); + inLastStartDate = startOfMonth(subMonths(new Date(), 7)); + query = `createdDate:['${formatISO(startOfDay(betweenMockData.betweenStartDate))}' TO '${formatISO(endOfDay(betweenMockData.betweenEndDate))}']` + + ` AND modifiedDate:['${formatISO(startOfDay(inLastStartDate))}' TO '${formatISO(endOfToday())}']`; + expect(component.combinedQuery).toEqual(query); + expect(component.combinedQuery).toEqual(query); + + component.onDateRangedValueChanged(anyMockDate, 'createdDate'); + component.onDateRangedValueChanged(anyMockDate, 'modifiedDate'); + fixture.detectChanges(); + expect(component.combinedQuery).toEqual(''); + }); + + it('should trigger context.update() when values are submitted', () => { + component.onDateRangedValueChanged(betweenMockData, 'createdDate'); + component.onDateRangedValueChanged(inLastMockData, 'modifiedDate'); + fixture.detectChanges(); + component.submitValues(); + fixture.detectChanges(); + const inLastStartDate = startOfWeek(subWeeks(new Date(), 5)); + const query = `createdDate:['${formatISO(startOfDay(betweenMockData.betweenStartDate))}' TO '${formatISO(endOfDay(betweenMockData.betweenEndDate))}']` + + ` AND modifiedDate:['${formatISO(startOfDay(inLastStartDate))}' TO '${formatISO(endOfToday())}']`; + expect(component.context.queryFragments['dateRangeAdvanced']).toEqual(query); + expect(component.context.update).toHaveBeenCalled(); + }); + + it('should clear values and search filter when widget is reset', () => { + spyOn(component.displayValue$, 'next'); + component.reset(); + fixture.detectChanges(); + expect(component.combinedQuery).toBe(''); + expect(component.combinedDisplayValue).toBe(''); + expect(component.displayValue$.next).toHaveBeenCalledWith(''); + expect(component.context.queryFragments['dateRangeAdvanced']).toEqual(''); + expect(component.context.update).toHaveBeenCalled(); + }); +}); diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.ts b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.ts new file mode 100644 index 00000000000..2e20e2b6105 --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component.ts @@ -0,0 +1,171 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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, OnInit, ViewEncapsulation } from '@angular/core'; +import { Subject } from 'rxjs'; +import { SearchDateRangeAdvanced } from './search-date-range-advanced/search-date-range-advanced'; +import { DateRangeType } from './search-date-range-advanced/date-range-type'; +import { SearchWidget } from '../../models/search-widget.interface'; +import { SearchWidgetSettings } from '../../models/search-widget-settings.interface'; +import { SearchQueryBuilderService } from '../../services/search-query-builder.service'; +import { InLastDateType } from './search-date-range-advanced/in-last-date-type'; +import { TranslationService } from '@alfresco/adf-core'; +import { + endOfDay, + endOfToday, + format, + formatISO, + startOfDay, + startOfMonth, + startOfWeek, + subDays, + subMonths, + subWeeks +} from 'date-fns'; + +@Component({ + selector: 'adf-search-date-range-advanced-tabbed', + templateUrl: './search-date-range-advanced-tabbed.component.html', + encapsulation: ViewEncapsulation.None +}) +export class SearchDateRangeAdvancedTabbedComponent implements SearchWidget, OnInit { + displayValue$ = new Subject(); + id: string; + startValue: SearchDateRangeAdvanced = { + dateRangeType: DateRangeType.ANY, + inLastValueType: InLastDateType.DAYS, + inLastValue: undefined, + betweenStartDate: undefined, + betweenEndDate: undefined + }; + settings?: SearchWidgetSettings; + context?: SearchQueryBuilderService; + fields: string[]; + tabsValidity: { [key: string]: boolean } = {}; + combinedQuery: string; + combinedDisplayValue: string; + + private value: { [key: string]: Partial } = {}; + private queryMapByField: Map = new Map(); + private displayValueMapByField: Map = new Map(); + + constructor(private translateService: TranslationService) {} + + ngOnInit(): void { + this.fields = this.settings?.field.split(',').map(field => field.trim()); + } + + getCurrentValue(): { [key: string]: Partial } { + return this.value; + } + + hasValidValue(): boolean { + return Object.values(this.tabsValidity).every((valid) => valid); + } + + reset() { + this.combinedQuery = ''; + this.combinedDisplayValue = ''; + this.startValue = { + ...this.startValue + }; + this.submitValues(); + } + + setValue(value: { [key: string]: SearchDateRangeAdvanced }) { + this.value = value; + } + + submitValues() { + this.context.queryFragments[this.id] = this.combinedQuery; + this.displayValue$.next(this.combinedDisplayValue); + if (this.id && this.context) { + this.context.update(); + } + } + onDateRangedValueChanged(value: Partial, field: string) { + this.value[field] = value; + this.updateQuery(value, field); + this.updateDisplayValue(value, field); + } + + private generateQuery(value: Partial, field: string): string { + let query = ''; + let startDate: Date; + let endDate: Date; + if (value.dateRangeType === DateRangeType.IN_LAST) { + if (value.inLastValue) { + switch(value.inLastValueType) { + case InLastDateType.DAYS: + startDate = startOfDay(subDays(new Date(), parseInt(value.inLastValue, 10))); + break; + case InLastDateType.WEEKS: + startDate = startOfWeek(subWeeks(new Date(), parseInt(value.inLastValue, 10))); + break; + case InLastDateType.MONTHS: + startDate = startOfMonth(subMonths(new Date(), parseInt(value.inLastValue, 10))); + break; + default: + break; + } + } + endDate = endOfToday(); + } else if (value.dateRangeType === DateRangeType.BETWEEN) { + if (value.betweenStartDate && value.betweenEndDate) { + startDate = startOfDay(value.betweenStartDate); + endDate = endOfDay(value.betweenEndDate); + } + } + if (startDate && endDate) { + query = `${field}:['${formatISO(startDate)}' TO '${formatISO(endDate)}']`; + } + return query; + } + + private generateDisplayValue(value: Partial): string { + let displayValue = ''; + if (value.dateRangeType === DateRangeType.IN_LAST && value.inLastValue) { + displayValue = this.translateService.instant(`SEARCH.DATE_RANGE_ADVANCED.IN_LAST_DISPLAY_LABELS.${value.inLastValueType}`, { + value: value.inLastValue + }); + } else if (value.dateRangeType === DateRangeType.BETWEEN && value.betweenStartDate && value.betweenEndDate) { + displayValue = `${format(startOfDay(value.betweenStartDate), this.settings.dateFormat)} - ${format(endOfDay(value.betweenEndDate), this.settings.dateFormat)}`; + } + return displayValue; + } + + private updateQuery(value: Partial, field: string) { + this.combinedQuery = ''; + this.queryMapByField.set(field, this.generateQuery(value, field)); + this.queryMapByField.forEach((query: string) => { + if (query) { + this.combinedQuery = this.combinedQuery ? `${this.combinedQuery} AND ${query}` : `${query}`; + } + }); + } + + private updateDisplayValue(value: Partial, field: string) { + this.combinedDisplayValue = ''; + this.displayValueMapByField.set(field, this.generateDisplayValue(value)); + this.displayValueMapByField.forEach((displayValue: string, fieldForDisplayLabel: string) => { + if (displayValue) { + const displayLabelForField = `${this.translateService.instant(this.settings.displayedLabelsByField[fieldForDisplayLabel]).toUpperCase()}: ${displayValue}`; + this.combinedDisplayValue = this.combinedDisplayValue ? `${this.combinedDisplayValue} ${displayLabelForField}` : `${displayLabelForField}`; + } + }); + } +} diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/date-range-type.ts b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/date-range-type.ts new file mode 100644 index 00000000000..4f01c957e4d --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/date-range-type.ts @@ -0,0 +1,22 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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. + */ + +export enum DateRangeType { + ANY = 'ANY', + IN_LAST = 'IN_LAST', + BETWEEN = 'BETWEEN', +} diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/in-last-date-type.ts b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/in-last-date-type.ts new file mode 100644 index 00000000000..ab947414e7e --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/in-last-date-type.ts @@ -0,0 +1,22 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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. + */ + +export enum InLastDateType { + DAYS = 'DAYS', + WEEKS = 'WEEKS', + MONTHS = 'MONTHS' +} diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.html b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.html new file mode 100644 index 00000000000..d30351fbfef --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.html @@ -0,0 +1,45 @@ + + + + + {{ 'SEARCH.DATE_RANGE_ADVANCED.OPTIONS.ANYTIME' | translate }} + + + + + {{ 'SEARCH.DATE_RANGE_ADVANCED.OPTIONS.IN_LAST' | translate }} + + + + {{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.IN_LAST' | translate }} + + + + {{ 'SEARCH.DATE_RANGE_ADVANCED.IN_LAST_LABELS.DAYS' | translate }} + {{ 'SEARCH.DATE_RANGE_ADVANCED.IN_LAST_LABELS.WEEKS' | translate }} + {{ 'SEARCH.DATE_RANGE_ADVANCED.IN_LAST_LABELS.MONTHS' | translate }} + + + + + + {{ 'SEARCH.DATE_RANGE_ADVANCED.OPTIONS.BETWEEN' | translate }} + + + + + + + + + + {{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.START_DATE.INVALID_FORMAT' | translate }} + {{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.START_DATE.REQUIRED' | translate }} + {{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.END_DATE.INVALID_FORMAT' | translate }} + {{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.END_DATE.REQUIRED' | translate }} + + + + diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.scss b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.scss new file mode 100644 index 00000000000..a24ad08732e --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.scss @@ -0,0 +1,45 @@ +.adf-search-date-range-advanced { + .mat-radio-group { + display: flex; + flex-direction: column; + padding: 5px; + } + + .mat-radio-button { + margin: 5px; + } + + .adf-search-date-range-horizontal-container { + display: flex; + flex-direction: row; + padding-bottom: 15px; + + .adf-search-date-range-input-field { + width: 75px; + } + + .adf-search-date-range-form-field { + padding-left: 10px; + flex: 1; + + .mat-form-field-wrapper { + padding-bottom: 0; + margin-bottom: 1.25em; + border: 1px solid var(--adf-theme-mat-grey-color-a400); + border-radius: 5px; + + .mat-form-field-infix { + border: 0; + } + + .mat-form-field-underline { + display: none; + } + + .mat-form-field-subscript-wrapper { + margin-top: 2em; + } + } + } + } +} diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.spec.ts b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.spec.ts new file mode 100644 index 00000000000..fa570f6c6be --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.spec.ts @@ -0,0 +1,281 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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 { ComponentFixture, TestBed } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { TranslateModule } from '@ngx-translate/core'; +import { ContentTestingModule } from '../../../../testing/content.testing.module'; +import { SearchDateRangeAdvancedComponent } from './search-date-range-advanced.component'; +import { addDays, endOfToday, format, parse, startOfYesterday, subDays } from 'date-fns'; +import { Validators } from '@angular/forms'; + +describe('SearchDateRangeAdvancedComponent', () => { + let component: SearchDateRangeAdvancedComponent; + let fixture: ComponentFixture; + + const startDateSampleValue = parse('05-Jun-23', 'dd-MMM-yy', new Date()); + const endDateSampleValue = parse('07-Jun-23', 'dd-MMM-yy', new Date()); + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [SearchDateRangeAdvancedComponent], + imports: [ + TranslateModule.forRoot(), + ContentTestingModule + ] + }); + + fixture = TestBed.createComponent(SearchDateRangeAdvancedComponent); + component = fixture.componentInstance; + component.field = 'test-field'; + component.dateFormat = 'dd-MMM-yy'; + component.maxDate = 'today'; + component.form.setValue({ + dateRangeType: component.DateRangeType.ANY, + inLastValueType: component.InLastDateType.DAYS, + inLastValue: null, + betweenStartDate: null, + betweenEndDate: null + }); + fixture.detectChanges(); + }); + + const getElementBySelector = (selector: string) => fixture.debugElement.query(By.css(selector)).nativeElement; + + const enterValueInInputFieldAndTriggerEvent = (inputElementId: string, value: string, event = 'input') => { + const inputField = getElementBySelector(`[data-automation-id="${inputElementId}"]`); + inputField.value = value; + inputField.dispatchEvent(new Event(event)); + fixture.detectChanges(); + }; + + const selectDropdownOption = (itemId: string) => { + const matSelect = fixture.debugElement.query(By.css('[data-automation-id="date-range-advanced-in-last-dropdown"]')).nativeElement; + matSelect.click(); + fixture.detectChanges(); + const matOption = fixture.debugElement.query(By.css(`[data-automation-id="${itemId}"]`)).nativeElement; + matOption.click(); + fixture.detectChanges(); + }; + + it('should set values if initial value is provided', () => { + let value: any = { + dateRangeType: component.DateRangeType.ANY + }; + component.initialValue = value; + component.ngOnInit(); + expect(component.form.controls.dateRangeType.value).toEqual(component.DateRangeType.ANY); + + value = { + dateRangeType: component.DateRangeType.IN_LAST, + inLastValueType: component.InLastDateType.WEEKS, + inLastValue: '5' + }; + component.initialValue = value; + component.ngOnInit(); + expect(component.form.controls.dateRangeType.value).toEqual(component.DateRangeType.IN_LAST); + expect(component.form.controls.inLastValueType.value).toEqual(component.InLastDateType.WEEKS); + expect(component.form.controls.inLastValue.value).toEqual('5'); + + value = { + dateRangeType: component.DateRangeType.BETWEEN, + betweenStartDate: startOfYesterday(), + betweenEndDate: endOfToday() + }; + component.initialValue = value; + component.ngOnInit(); + expect(component.form.controls.dateRangeType.value).toEqual(component.DateRangeType.BETWEEN); + expect(component.form.controls.betweenStartDate.value).toEqual(startOfYesterday()); + expect(component.form.controls.betweenEndDate.value).toEqual(endOfToday()); + }); + + it('should not have any validators on any input fields when anytime option is selected', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.ANY); + fixture.detectChanges(); + expect(component.form.controls.inLastValue.validator).toBeNull(); + expect(component.form.controls.betweenStartDate.validator).toBeNull(); + expect(component.form.controls.betweenEndDate.validator).toBeNull(); + }); + + it('should set the required validator on in last input field and remove validators from between input fields when In the last option is selected', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.IN_LAST); + fixture.detectChanges(); + expect(component.form.controls.inLastValue.hasValidator(Validators.required)).toBeTrue(); + expect(component.form.controls.betweenStartDate.validator).toBeNull(); + expect(component.form.controls.betweenEndDate.validator).toBeNull(); + }); + + it('should set the validators on in between input fields and remove validator from in last input fields when Between option is selected', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.BETWEEN); + fixture.detectChanges(); + expect(component.form.controls.betweenStartDate.hasValidator(Validators.required)).toBeTrue(); + expect(component.form.controls.betweenEndDate.hasValidator(Validators.required)).toBeTrue(); + expect(component.form.controls.betweenEndDate.hasValidator(component.endDateValidator)).toBeTrue(); + expect(component.form.controls.inLastValue.validator).toBeNull(); + }); + + it('should not be able to set zero or negative values in In the last input field', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.IN_LAST); + fixture.detectChanges(); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-in-last-input', '-5'); + let inLastInputFieldValue = getElementBySelector('[data-automation-id="date-range-advanced-in-last-input"]').value; + expect(inLastInputFieldValue).toBe('5'); + + enterValueInInputFieldAndTriggerEvent('date-range-advanced-in-last-input', '0'); + inLastInputFieldValue = getElementBySelector('[data-automation-id="date-range-advanced-in-last-input"]').value; + expect(inLastInputFieldValue).toBe(''); + }); + + it('should give an invalid date error when manually setting a start date and an end date that are not in the correct format', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.BETWEEN); + fixture.detectChanges(); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-between-start-input', 'invalid-date-input', 'change'); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-between-end-input', 'invalid-date-input', 'change'); + expect(component.form.controls.betweenStartDate.errors.invalidDate).toBeTrue(); + expect(component.form.controls.betweenEndDate.errors.invalidDate).toBeTrue(); + }); + + it('should give an invalid date error when manually setting a start Date that is after the end date', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.BETWEEN); + fixture.detectChanges(); + component.form.controls.betweenEndDate.setValue(new Date()); + const startDate = format(addDays(component.form.controls.betweenEndDate.value, 3), component.dateFormat); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-between-start-input', startDate, 'change'); + expect(component.form.controls.betweenEndDate.errors.invalidDate).toBeTrue(); + }); + + it('should give an invalid date error when manually setting an end Date that is before the start date', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.BETWEEN); + fixture.detectChanges(); + component.form.controls.betweenStartDate.setValue(new Date()); + const endDate = format(subDays(component.form.controls.betweenStartDate.value, 3), component.dateFormat); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-between-end-input', endDate, 'change'); + expect(component.form.controls.betweenEndDate.errors.invalidDate).toBeTrue(); + }); + + it('should give an invalid date error when setting an endDate that is after the max date', () => { + component.form.controls.dateRangeType.setValue(component.DateRangeType.BETWEEN); + fixture.detectChanges(); + const endDate = format(addDays(component.convertedMaxDate, 3), component.dateFormat); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-between-end-input', endDate, 'change'); + expect(component.form.controls.betweenEndDate.errors.invalidDate).toBeTrue(); + }); + + it('should emit valid as false when form is invalid', () => { + spyOn(component.valid, 'emit'); + component.form.controls.dateRangeType.setValue(component.DateRangeType.IN_LAST); + fixture.detectChanges(); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-in-last-input', ''); + selectDropdownOption('date-range-advanced-in-last-option-weeks'); + expect(component.valid.emit).toHaveBeenCalledWith(false); + + component.form.controls.dateRangeType.setValue(component.DateRangeType.BETWEEN); + fixture.detectChanges(); + expect(component.valid.emit).toHaveBeenCalledWith(false); + }); + + it('should emit valid as true when form is valid', () => { + spyOn(component.valid, 'emit'); + component.form.controls.dateRangeType.setValue(component.DateRangeType.IN_LAST); + fixture.detectChanges(); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-in-last-input', '5'); + selectDropdownOption('date-range-advanced-in-last-option-weeks'); + expect(component.valid.emit).toHaveBeenCalledWith(true); + + component.form.controls.dateRangeType.setValue(component.DateRangeType.BETWEEN); + fixture.detectChanges(); + component.betweenStartDateFormControl.setValue(startDateSampleValue); + component.betweenEndDateFormControl.setValue(endDateSampleValue); + fixture.detectChanges(); + expect(component.valid.emit).toHaveBeenCalledWith(true); + }); + + it('should not emit values when form is invalid', () => { + spyOn(component.changed, 'emit'); + let value = { + dateRangeType: component.DateRangeType.IN_LAST, + inLastValueType: component.InLastDateType.WEEKS, + inLastValue: '', + betweenStartDate: undefined, + betweenEndDate: undefined + }; + let dateRangeTypeRadioButton = getElementBySelector('[data-automation-id="date-range-advanced-in-last"] .mat-radio-input'); + dateRangeTypeRadioButton.click(); + selectDropdownOption('date-range-advanced-in-last-option-weeks'); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-in-last-input', ''); + expect(component.changed.emit).not.toHaveBeenCalledWith(value); + + component.form.patchValue({ + dateRangeType: component.DateRangeType.ANY, + inLastValueType: component.InLastDateType.DAYS, + inLastValue: undefined, + betweenStartDate: undefined, + betweenEndDate: undefined + }); + + value = { + dateRangeType: component.DateRangeType.BETWEEN, + inLastValueType: component.InLastDateType.DAYS, + inLastValue: undefined, + betweenStartDate: '', + betweenEndDate: '' + }; + dateRangeTypeRadioButton = getElementBySelector('[data-automation-id="date-range-advanced-between"] .mat-radio-input'); + dateRangeTypeRadioButton.click(); + fixture.detectChanges(); + expect(component.changed.emit).not.toHaveBeenCalledWith(value); + }); + + it('should emit values when form is valid', () => { + spyOn(component.changed, 'emit'); + let value = { + dateRangeType: component.DateRangeType.IN_LAST, + inLastValueType: component.InLastDateType.WEEKS, + inLastValue: 5, + betweenStartDate: null, + betweenEndDate: null + }; + let dateRangeTypeRadioButton = getElementBySelector('[data-automation-id="date-range-advanced-in-last"] .mat-radio-input'); + dateRangeTypeRadioButton.click(); + selectDropdownOption('date-range-advanced-in-last-option-weeks'); + enterValueInInputFieldAndTriggerEvent('date-range-advanced-in-last-input', '5'); + fixture.detectChanges(); + expect(component.changed.emit).toHaveBeenCalledWith(value); + + component.form.patchValue({ + dateRangeType: component.DateRangeType.ANY, + inLastValueType: component.InLastDateType.DAYS, + inLastValue: undefined, + betweenStartDate: undefined, + betweenEndDate: undefined + }); + + value = { + dateRangeType: component.DateRangeType.BETWEEN, + inLastValueType: component.InLastDateType.DAYS, + inLastValue: undefined, + betweenStartDate: startDateSampleValue, + betweenEndDate: endDateSampleValue + }; + dateRangeTypeRadioButton = getElementBySelector('[data-automation-id="date-range-advanced-between"] .mat-radio-input'); + dateRangeTypeRadioButton.click(); + component.betweenStartDateFormControl.setValue(startDateSampleValue); + component.betweenEndDateFormControl.setValue(endDateSampleValue); + fixture.detectChanges(); + expect(component.changed.emit).toHaveBeenCalledWith(value); + }); +}); diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.ts b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.ts new file mode 100644 index 00000000000..d0792a9c196 --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component.ts @@ -0,0 +1,180 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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, EventEmitter, Inject, Input, OnDestroy, OnInit, Output, ViewEncapsulation } from '@angular/core'; +import { Subject } from 'rxjs'; +import { endOfDay, parse, isValid, isBefore, isAfter } from 'date-fns'; +import { DateAdapter, MAT_DATE_FORMATS, MAT_DATE_LOCALE, MatDateFormats } from '@angular/material/core'; +import { DateFnsAdapter, MAT_DATE_FNS_FORMATS } from '@angular/material-date-fns-adapter'; +import { InLastDateType } from './in-last-date-type'; +import { DateRangeType } from './date-range-type'; +import { SearchDateRangeAdvanced } from './search-date-range-advanced'; +import { FormBuilder, UntypedFormControl, Validators } from '@angular/forms'; +import { takeUntil } from 'rxjs/operators'; +import { UserPreferencesService, UserPreferenceValues, DateFnsUtils } from '@alfresco/adf-core'; + +const DEFAULT_DATE_DISPLAY_FORMAT = 'dd-MMM-yy'; + +@Component({ + selector: 'adf-search-date-range-advanced', + templateUrl: './search-date-range-advanced.component.html', + styleUrls: ['./search-date-range-advanced.component.scss'], + providers: [ + { provide: DateAdapter, useClass: DateFnsAdapter, deps: [ MAT_DATE_LOCALE ] }, + { provide: MAT_DATE_FORMATS, useValue: MAT_DATE_FNS_FORMATS } + ], + encapsulation: ViewEncapsulation.None, + host: {class: 'adf-search-date-range-advanced'} +}) +export class SearchDateRangeAdvancedComponent implements OnInit, OnDestroy { + @Input() + dateFormat = DEFAULT_DATE_DISPLAY_FORMAT; + @Input() + maxDate: string; + @Input() + field: string; + @Input() + set initialValue(value: SearchDateRangeAdvanced) { + if (value) { + this.form.patchValue(value); + } + } + + @Output() + changed = new EventEmitter>(); + @Output() + valid = new EventEmitter(); + + form = this.formBuilder.group({ + dateRangeType: DateRangeType.ANY, + inLastValueType: InLastDateType.DAYS, + inLastValue: undefined, + betweenStartDate: undefined, + betweenEndDate: undefined + }); + betweenStartDateFormControl = this.form.controls.betweenStartDate; + betweenEndDateFormControl = this.form.controls.betweenEndDate; + convertedMaxDate: Date; + private destroy$ = new Subject(); + + readonly DateRangeType = DateRangeType; + readonly InLastDateType = InLastDateType; + + constructor(private formBuilder: FormBuilder, + private userPreferencesService: UserPreferencesService, + private dateAdapter: DateAdapter, + @Inject(MAT_DATE_FORMATS) private dateFormatConfig: MatDateFormats) {} + + readonly endDateValidator = (formControl: UntypedFormControl): ({ [key: string]: boolean } | null) => { + if (isBefore(formControl.value, this.betweenStartDateFormControl.value) || isAfter(formControl.value, this.convertedMaxDate)) { + return { + invalidDate: true + }; + } + return {}; + }; + + ngOnInit(): void { + this.dateFormatConfig.display.dateInput = this.dateFormat; + this.convertedMaxDate = endOfDay(this.maxDate && this.maxDate !== 'today' ? + parse(this.maxDate, this.dateFormat, new Date()) : new Date()); + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.destroy$)) + .subscribe(locale => this.dateAdapter.setLocale(DateFnsUtils.getLocaleFromString(locale))); + this.form.controls.dateRangeType.valueChanges.pipe(takeUntil(this.destroy$)) + .subscribe((dateRangeType) => this.updateValidators(dateRangeType)); + this.form.valueChanges.pipe(takeUntil(this.destroy$)) + .subscribe(() => this.onChange()); + } + + ngOnDestroy() { + this.destroy$.next(); + this.destroy$.complete(); + } + + private updateValidators(dateRangeType: DateRangeType) { + switch(dateRangeType) { + case DateRangeType.BETWEEN: + this.betweenStartDateFormControl.setValidators(Validators.required); + this.betweenEndDateFormControl.setValidators([Validators.required, this.endDateValidator]); + this.form.controls.inLastValue.clearValidators(); + break; + case DateRangeType.IN_LAST: + this.form.controls.inLastValue.setValidators(Validators.required); + this.betweenStartDateFormControl.clearValidators(); + this.betweenEndDateFormControl.clearValidators(); + break; + default: + this.form.controls.inLastValue.clearValidators(); + this.betweenStartDateFormControl.clearValidators(); + this.betweenEndDateFormControl.clearValidators(); + break; + } + this.betweenStartDateFormControl.updateValueAndValidity(); + this.betweenEndDateFormControl.updateValueAndValidity(); + this.form.controls.inLastValue.updateValueAndValidity(); + } + + private onChange(): void { + if (this.form.valid) { + this.changed.emit(this.form.value); + } + this.valid.emit(this.form.valid); + } + + dateChanged(event: Event, formControl: UntypedFormControl) { + if (event?.target['value']?.trim()) { + const date = parse(event.target['value'], this.dateFormat, new Date()); + if(!isValid(date)) { + formControl.setErrors({ + ...formControl.errors, + required: false, + invalidDate: true + }); + } else { + formControl.setErrors({ + ...formControl.errors, + invalidDate: false + }); + formControl.setValue(date); + } + } + } + + narrowDownAllowedCharacters(event: Event) { + if (parseInt((event.target as HTMLInputElement).value, 10) === 0) { + (event.target as HTMLInputElement).value = ''; + } else { + (event.target as HTMLInputElement).value = (event.target as HTMLInputElement).value.replace(/\D/g, ''); + } + } + + preventIncorrectNumberCharacters(event: KeyboardEvent): boolean { + switch(event.key) { + case '.': + case '-': + case 'e': + case '+': + return false; + case '0': + return !!(event.target as HTMLInputElement).value; + default: + return true; + } + } +} diff --git a/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.ts b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.ts new file mode 100644 index 00000000000..a51e0d96b2a --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.ts @@ -0,0 +1,27 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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 { DateRangeType } from './date-range-type'; +import { InLastDateType } from './in-last-date-type'; + +export interface SearchDateRangeAdvanced { + dateRangeType: DateRangeType; + inLastValueType?: InLastDateType; + inLastValue?: string; + betweenStartDate?: Date; + betweenEndDate?: Date; +} diff --git a/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.html b/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.html index 779e40803a2..f645c1dad4d 100644 --- a/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.html +++ b/lib/content-services/src/lib/search/components/search-filter-chips/search-widget-chip/search-widget-chip.component.html @@ -19,7 +19,7 @@ {{ chipIcon }} - +
) { } +} diff --git a/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.html b/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.html new file mode 100644 index 00000000000..558c13b1eeb --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.html @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.scss b/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.scss new file mode 100644 index 00000000000..fa74c8d502d --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.scss @@ -0,0 +1,20 @@ +adf-search-filter-tabbed { + .mat-tab-label { + flex: 1; + + &.mat-tab-label-active { + border-bottom: 2px solid var(--theme-primary-color); + } + } + + // The important tag is used here as a workaround for a bug in angular material, when MatTabs are used in conjunction with MatMenu + // https://github.com/angular/components/issues/27426 + .mat-tab-body.mat-tab-body-active .mat-tab-body-content { + display: block; + visibility: visible !important; + } + + .mat-ink-bar { + display: none; + } +} diff --git a/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.ts b/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.ts new file mode 100644 index 00000000000..50a03adff7f --- /dev/null +++ b/lib/content-services/src/lib/search/components/search-filter-tabbed/search-filter-tabbed.component.ts @@ -0,0 +1,30 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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, ContentChildren, QueryList, ViewEncapsulation } from '@angular/core'; +import { SearchFilterTabDirective } from './search-filter-tab.directive'; + +@Component({ + selector: 'adf-search-filter-tabbed', + templateUrl: './search-filter-tabbed.component.html', + styleUrls: ['./search-filter-tabbed.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class SearchFilterTabbedComponent { + @ContentChildren(SearchFilterTabDirective) + tabsContents: QueryList; +} diff --git a/lib/content-services/src/lib/search/public-api.ts b/lib/content-services/src/lib/search/public-api.ts index d47030feac4..52176d8ff17 100644 --- a/lib/content-services/src/lib/search/public-api.ts +++ b/lib/content-services/src/lib/search/public-api.ts @@ -34,6 +34,7 @@ export * from './services/search-facet-filters.service'; export * from './services/search-filter.service'; export * from './services/search.service'; export * from './services/search-configuration.service'; +export * from './services/base-query-builder.service'; export * from './mocks/search.service.mock'; @@ -61,6 +62,8 @@ export * from './components/search-filter-chips/search-filter-chips.component'; export * from './components/search-filter-chips/search-filter-menu-card/search-filter-menu-card.component'; export * from './components/search-facet-field/search-facet-field.component'; export * from './components/search-logical-filter/search-logical-filter.component'; +export * from './components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component'; +export * from './components/search-filter-tabbed/search-filter-tabbed.component'; export * from './components/reset-search.directive'; export * from './components/search-chip-autocomplete-input/search-chip-autocomplete-input.component'; export * from './components/search-filter-autocomplete-chips/search-filter-autocomplete-chips.component'; diff --git a/lib/content-services/src/lib/search/search.module.ts b/lib/content-services/src/lib/search/search.module.ts index 7c74d625bd5..4a90815dd72 100644 --- a/lib/content-services/src/lib/search/search.module.ts +++ b/lib/content-services/src/lib/search/search.module.ts @@ -52,6 +52,10 @@ import { SearchFacetChipComponent } from './components/search-filter-chips/searc import { SearchLogicalFilterComponent } from './components/search-logical-filter/search-logical-filter.component'; import { ResetSearchDirective } from './components/reset-search.directive'; import { SearchPropertiesComponent } from './components/search-properties/search-properties.component'; +import { SearchFilterTabbedComponent } from './components/search-filter-tabbed/search-filter-tabbed.component'; +import { SearchDateRangeAdvancedComponent } from './components/search-date-range-advanced-tabbed/search-date-range-advanced/search-date-range-advanced.component'; +import { SearchDateRangeAdvancedTabbedComponent } from './components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component'; +import { SearchFilterTabDirective } from './components/search-filter-tabbed/search-filter-tab.directive'; @NgModule({ imports: [ @@ -90,7 +94,11 @@ import { SearchPropertiesComponent } from './components/search-properties/search SearchFacetChipComponent, SearchLogicalFilterComponent, ResetSearchDirective, - SearchPropertiesComponent + SearchPropertiesComponent, + SearchFilterTabbedComponent, + SearchDateRangeAdvancedComponent, + SearchDateRangeAdvancedTabbedComponent, + SearchFilterTabDirective ], exports: [ SearchComponent, @@ -116,6 +124,8 @@ import { SearchPropertiesComponent } from './components/search-properties/search SearchFilterMenuCardComponent, SearchFacetFieldComponent, SearchLogicalFilterComponent, + SearchFilterTabbedComponent, + SearchDateRangeAdvancedComponent, ResetSearchDirective ], providers: [ diff --git a/lib/content-services/src/lib/search/services/search-filter.service.ts b/lib/content-services/src/lib/search/services/search-filter.service.ts index a1cc1e9053d..491bf471bfb 100644 --- a/lib/content-services/src/lib/search/services/search-filter.service.ts +++ b/lib/content-services/src/lib/search/services/search-filter.service.ts @@ -26,6 +26,9 @@ import { SearchDatetimeRangeComponent } from '../components/search-datetime-rang import { SearchLogicalFilterComponent } from '../components/search-logical-filter/search-logical-filter.component'; import { SearchFilterAutocompleteChipsComponent } from '../components/search-filter-autocomplete-chips/search-filter-autocomplete-chips.component'; import { SearchPropertiesComponent } from '../components/search-properties/search-properties.component'; +import { + SearchDateRangeAdvancedTabbedComponent +} from '../components/search-date-range-advanced-tabbed/search-date-range-advanced-tabbed.component'; @Injectable({ providedIn: 'root' @@ -45,7 +48,8 @@ export class SearchFilterService { 'date-range': SearchDateRangeComponent, 'datetime-range': SearchDatetimeRangeComponent, 'logical-filter': SearchLogicalFilterComponent, - 'autocomplete-chips': SearchFilterAutocompleteChipsComponent + 'autocomplete-chips': SearchFilterAutocompleteChipsComponent, + 'date-range-advanced': SearchDateRangeAdvancedTabbedComponent }; } diff --git a/lib/core/src/lib/common/utils/date-fns-utils.ts b/lib/core/src/lib/common/utils/date-fns-utils.ts new file mode 100644 index 00000000000..56cf705043b --- /dev/null +++ b/lib/core/src/lib/common/utils/date-fns-utils.ts @@ -0,0 +1,81 @@ +/*! + * @license + * Copyright © 2005-2023 Hyland Software, Inc. and its affiliates. All rights reserved. + * + * 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 {ar, cs, da, de, enUS, es, fi, fr, it, ja, nb, nl, pl, ptBR, ru, sv, zhCN} from 'date-fns/locale'; + +export class DateFnsUtils { + static getLocaleFromString(locale: string): Locale { + let dateFnsLocale: Locale; + switch(locale) { + case 'ar': + dateFnsLocale = ar; + break; + case 'cs': + dateFnsLocale = cs; + break; + case 'da': + dateFnsLocale = da; + break; + case 'de': + dateFnsLocale = de; + break; + case 'en': + dateFnsLocale = enUS; + break; + case 'es': + dateFnsLocale = es; + break; + case 'fi': + dateFnsLocale = fi; + break; + case 'fr': + dateFnsLocale = fr; + break; + case 'it': + dateFnsLocale = it; + break; + case 'ja': + dateFnsLocale = ja; + break; + case 'nb': + dateFnsLocale = nb; + break; + case 'nl': + dateFnsLocale = nl; + break; + case 'pl': + dateFnsLocale = pl; + break; + case 'pt-BR': + dateFnsLocale = ptBR; + break; + case 'ru': + dateFnsLocale = ru; + break; + case 'sv': + dateFnsLocale = sv; + break; + case 'zh-CN': + dateFnsLocale = zhCN; + break; + default: + dateFnsLocale = enUS; + break; + } + return dateFnsLocale; + } +} diff --git a/lib/core/src/lib/common/utils/public-api.ts b/lib/core/src/lib/common/utils/public-api.ts index e2a3d3f80c8..8b3fbaa6164 100644 --- a/lib/core/src/lib/common/utils/public-api.ts +++ b/lib/core/src/lib/common/utils/public-api.ts @@ -20,3 +20,4 @@ export * from './file-utils'; export * from './moment-date-formats.model'; export * from './moment-date-adapter'; export * from './string-utils'; +export * from './date-fns-utils'; diff --git a/lib/testing/src/lib/protractor/content-services/pages/search/search-slider.page.ts b/lib/testing/src/lib/protractor/content-services/pages/search/search-slider.page.ts index 579a4a9b02d..8c7c0d5fbf8 100644 --- a/lib/testing/src/lib/protractor/content-services/pages/search/search-slider.page.ts +++ b/lib/testing/src/lib/protractor/content-services/pages/search/search-slider.page.ts @@ -45,7 +45,7 @@ export class SearchSliderPage { async setValue(value: number): Promise { const elem = this.filter.$(this.slider).$('.mat-slider-wrapper'); await browser.actions().mouseMove(elem, { x: 0, y: 0 }).perform(); - await browser.actions().mouseDown().mouseMove({x: value * 10, y: 0}).mouseUp().perform(); + await browser.actions().mouseDown().mouseMove({x: value * 20, y: 0}).mouseUp().perform(); } async checkSliderIsDisplayed(): Promise { diff --git a/package-lock.json b/package-lock.json index 35a83bdc892..cb53b50f6bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@angular/core": "14.1.3", "@angular/forms": "14.1.3", "@angular/material": "14.1.2", + "@angular/material-date-fns-adapter": "^14.1.2", "@angular/material-moment-adapter": "14.1.2", "@angular/platform-browser": "14.1.3", "@angular/platform-browser-dynamic": "14.1.3", @@ -1216,6 +1217,19 @@ "rxjs": "^6.5.3 || ^7.4.0" } }, + "node_modules/@angular/material-date-fns-adapter": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@angular/material-date-fns-adapter/-/material-date-fns-adapter-14.1.2.tgz", + "integrity": "sha512-98XaKVCybB/6hveqBiVQ88XWklxW27U917Uwq6sIgf6k62ZvrhPMOyz/arTCuR/OYRgJyWV2ykUxdqvGzr28+Q==", + "dependencies": { + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/core": "^14.0.0 || ^15.0.0", + "@angular/material": "14.1.2", + "date-fns": "^2.23.0" + } + }, "node_modules/@angular/material-moment-adapter": { "version": "14.1.2", "license": "MIT", diff --git a/package.json b/package.json index 0d9e31c08b9..197d491332b 100644 --- a/package.json +++ b/package.json @@ -62,6 +62,7 @@ "@angular/core": "14.1.3", "@angular/forms": "14.1.3", "@angular/material": "14.1.2", + "@angular/material-date-fns-adapter": "^14.1.2", "@angular/material-moment-adapter": "14.1.2", "@angular/platform-browser": "14.1.3", "@angular/platform-browser-dynamic": "14.1.3",