[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
This commit is contained in:
swapnil.verma 2023-07-21 11:13:30 +05:30 committed by eromano
parent c8b4083f32
commit 640a736530
38 changed files with 1534 additions and 14 deletions

View File

@ -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", "id": "queryType",
"name": "Type", "name": "Type",

View File

@ -3,7 +3,6 @@
margin-left: 5px; margin-left: 5px;
.app-search-settings { .app-search-settings {
width: 260px;
border: 1px solid #eee; border: 1px solid #eee;
} }

View File

@ -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 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 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 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 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 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 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 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 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 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 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 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) | | [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) |

View File

@ -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 -
`<field>:[<from_date> TO <to_date>]`
## 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)

View File

@ -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
<adf-search-date-range-advanced></adf-search-date-range-advanced>
```
## 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)`<Partial<SearchDateRangeAdvanced>>` | 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)`<boolean>` | 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.

View File

@ -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
<adf-search-filter-tabbed>
<ng-container *ngFor="let field of fields">
<my-search-filter *adf-search-filter-tab="MyTabLabel"></my-search-filter>
</ng-container>
</adf-search-filter-tabbed>
```
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)

View File

@ -37,7 +37,9 @@ to build and execute the query.
- [Search Widget Interface](../interfaces/search-widget.interface.md) - [Search Widget Interface](../interfaces/search-widget.interface.md)
- [Search check list component](search-check-list.component.md) - [Search check list component](search-check-list.component.md)
- [Search date range component](search-date-range.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 number range component](search-number-range.component.md)
- [Search radio component](search-radio.component.md) - [Search radio component](search-radio.component.md)
- [Search slider component](search-slider.component.md) - [Search slider component](search-slider.component.md)
- [Search text component](search-text.component.md) - [Search text component](search-text.component.md)
- [Search filter tabbed component](search-filter-tabbed.component.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -48,6 +48,9 @@ backend services have been tested with each released version of ADF.
<!--v620 start--> <!--v620 start-->
- [Search Properties component](content-services/components/search-properties.component.md) - [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)
<!--v620 end--> <!--v620 end-->

View File

@ -155,7 +155,7 @@ describe('Search Radio Component', () => {
await searchFiltersPage.clickTypeFilterHeader(); await searchFiltersPage.clickTypeFilterHeader();
await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(10); await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(13);
await navigationBarPage.navigateToContentServices(); await navigationBarPage.navigateToContentServices();
@ -170,7 +170,7 @@ describe('Search Radio Component', () => {
await searchFiltersPage.clickTypeFilterHeader(); await searchFiltersPage.clickTypeFilterHeader();
await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(10); await expect(await searchFiltersPage.typeFiltersPage().getRadioButtonsNumberOnPage()).toBe(13);
await navigationBarPage.navigateToContentServices(); await navigationBarPage.navigateToContentServices();
jsonFile.categories[5].component.settings.pageSize = 9; jsonFile.categories[5].component.settings.pageSize = 9;
@ -184,7 +184,7 @@ describe('Search Radio Component', () => {
await searchFiltersPage.clickTypeFilterHeader(); 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().checkShowMoreButtonIsDisplayed();
await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed();
@ -213,21 +213,21 @@ describe('Search Radio Component', () => {
await searchFiltersPage.clickTypeFilterHeader(); 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().checkShowMoreButtonIsDisplayed();
await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed();
await searchFiltersPage.typeFiltersPage().clickShowMoreButton(); 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().checkShowMoreButtonIsNotDisplayed();
await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsDisplayed();
await searchFiltersPage.typeFiltersPage().clickShowLessButton(); 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().checkShowMoreButtonIsDisplayed();
await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed();
@ -244,21 +244,21 @@ describe('Search Radio Component', () => {
await searchFiltersPage.clickTypeFilterHeader(); 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().checkShowMoreButtonIsDisplayed();
await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed();
await searchFiltersPage.typeFiltersPage().clickShowMoreButton(); 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().checkShowMoreButtonIsNotDisplayed();
await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsDisplayed();
await searchFiltersPage.typeFiltersPage().clickShowLessButton(); 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().checkShowMoreButtonIsDisplayed();
await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed(); await searchFiltersPage.typeFiltersPage().checkShowLessButtonIsNotDisplayed();

View File

@ -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: { highlight: {

View File

@ -415,6 +415,38 @@
"EXCLUDE_LABEL": "EXCLUDE these words", "EXCLUDE_LABEL": "EXCLUDE these words",
"EXCLUDE_HINT": "Results will exclude matches with 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": { "SEARCH_PROPERTIES": {
"FILE_SIZE": "File Size", "FILE_SIZE": "File Size",
"FILE_SIZE_PLACEHOLDER": "Enter file size", "FILE_SIZE_PLACEHOLDER": "Enter file size",

View File

@ -0,0 +1,13 @@
<adf-search-filter-tabbed>
<ng-container *ngFor="let field of fields">
<adf-search-date-range-advanced
*adf-search-filter-tab="settings.displayedLabelsByField[field]"
[dateFormat]="settings.dateFormat"
[maxDate]="settings.maxDate"
[field]="field"
[initialValue]="startValue"
(changed)="onDateRangedValueChanged($event, field)"
(valid)="tabsValidity[field]=$event">
</adf-search-date-range-advanced>
</ng-container>
</adf-search-filter-tabbed>

View File

@ -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<Partial<SearchDateRangeAdvanced>>();
@Output()
valid = new EventEmitter<boolean>();
}
describe('SearchDateRangeAdvancedTabbedComponent', () => {
let component: SearchDateRangeAdvancedTabbedComponent;
let fixture: ComponentFixture<SearchDateRangeAdvancedTabbedComponent>;
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();
});
});

View File

@ -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<string>();
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<SearchDateRangeAdvanced> } = {};
private queryMapByField: Map<string, string> = new Map<string, string>();
private displayValueMapByField: Map<string, string> = new Map<string, string>();
constructor(private translateService: TranslationService) {}
ngOnInit(): void {
this.fields = this.settings?.field.split(',').map(field => field.trim());
}
getCurrentValue(): { [key: string]: Partial<SearchDateRangeAdvanced> } {
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<SearchDateRangeAdvanced>, field: string) {
this.value[field] = value;
this.updateQuery(value, field);
this.updateDisplayValue(value, field);
}
private generateQuery(value: Partial<SearchDateRangeAdvanced>, 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<SearchDateRangeAdvanced>): 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<SearchDateRangeAdvanced>, 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<SearchDateRangeAdvanced>, 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}`;
}
});
}
}

View File

@ -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',
}

View File

@ -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'
}

View File

@ -0,0 +1,45 @@
<ng-container [formGroup]="form">
<mat-radio-group formControlName="dateRangeType">
<span class="adf-search-date-range-horizontal-container">
<mat-radio-button [value]="DateRangeType.ANY" data-automation-id="date-range-advanced-anytime">
{{ 'SEARCH.DATE_RANGE_ADVANCED.OPTIONS.ANYTIME' | translate }}
</mat-radio-button>
</span>
<span class="adf-search-date-range-horizontal-container">
<mat-radio-button [value]="DateRangeType.IN_LAST" data-automation-id="date-range-advanced-in-last">
{{ 'SEARCH.DATE_RANGE_ADVANCED.OPTIONS.IN_LAST' | translate }}
</mat-radio-button>
<mat-form-field class="adf-search-date-range-form-field adf-search-date-range-input-field">
<input matInput type="number" min="1" formControlName="inLastValue" data-automation-id="date-range-advanced-in-last-input" (input)="narrowDownAllowedCharacters($event)" (keydown)="preventIncorrectNumberCharacters($event)">
<mat-error *ngIf="form.controls.inLastValue.errors?.required">{{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.IN_LAST' | translate }}</mat-error>
</mat-form-field>
<mat-form-field class="adf-search-date-range-form-field">
<mat-select formControlName="inLastValueType" data-automation-id="date-range-advanced-in-last-dropdown">
<mat-option data-automation-id="date-range-advanced-in-last-option-days" [value]="InLastDateType.DAYS">{{ 'SEARCH.DATE_RANGE_ADVANCED.IN_LAST_LABELS.DAYS' | translate }}</mat-option>
<mat-option data-automation-id="date-range-advanced-in-last-option-weeks" [value]="InLastDateType.WEEKS">{{ 'SEARCH.DATE_RANGE_ADVANCED.IN_LAST_LABELS.WEEKS' | translate }}</mat-option>
<mat-option data-automation-id="date-range-advanced-in-last-option-months" [value]="InLastDateType.MONTHS">{{ 'SEARCH.DATE_RANGE_ADVANCED.IN_LAST_LABELS.MONTHS' | translate }}</mat-option>
</mat-select>
</mat-form-field>
</span>
<span class="adf-search-date-range-horizontal-container">
<mat-radio-button [value]="DateRangeType.BETWEEN" data-automation-id="date-range-advanced-between">
{{ 'SEARCH.DATE_RANGE_ADVANCED.OPTIONS.BETWEEN' | translate }}
</mat-radio-button>
<mat-form-field class="adf-search-date-range-form-field">
<mat-date-range-input [rangePicker]="$any(picker)" [max]="convertedMaxDate">
<input matStartDate placeholder="{{ 'SEARCH.DATE_RANGE_ADVANCED.BETWEEN_PLACEHOLDERS.START_DATE' | translate }}"
data-automation-id="date-range-advanced-between-start-input" [formControl]="betweenStartDateFormControl" (change)="dateChanged($event, betweenStartDateFormControl)">
<input matEndDate placeholder="{{ 'SEARCH.DATE_RANGE_ADVANCED.BETWEEN_PLACEHOLDERS.END_DATE' | translate }}"
data-automation-id="date-range-advanced-between-end-input" [formControl]="betweenEndDateFormControl" (change)="dateChanged($event, betweenEndDateFormControl)">
</mat-date-range-input>
<mat-datepicker-toggle matSuffix [for]="picker" data-automation-id="date-range-advanced-between-datepicker-toggle"></mat-datepicker-toggle>
<mat-date-range-picker #picker></mat-date-range-picker>
<mat-error *ngIf="betweenStartDateFormControl.errors?.invalidDate">{{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.START_DATE.INVALID_FORMAT' | translate }}</mat-error>
<mat-error *ngIf="betweenStartDateFormControl.errors?.required">{{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.START_DATE.REQUIRED' | translate }}</mat-error>
<mat-error *ngIf="betweenEndDateFormControl.errors?.invalidDate">{{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.END_DATE.INVALID_FORMAT' | translate }}</mat-error>
<mat-error *ngIf="betweenEndDateFormControl.errors?.required">{{ 'SEARCH.DATE_RANGE_ADVANCED.ERROR.END_DATE.REQUIRED' | translate }}</mat-error>
</mat-form-field>
</span>
</mat-radio-group>
</ng-container>

View File

@ -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;
}
}
}
}
}

View File

@ -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<SearchDateRangeAdvancedComponent>;
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);
});
});

View File

@ -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<Partial<SearchDateRangeAdvanced>>();
@Output()
valid = new EventEmitter<boolean>();
form = this.formBuilder.group<SearchDateRangeAdvanced>({
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<void>();
readonly DateRangeType = DateRangeType;
readonly InLastDateType = InLastDateType;
constructor(private formBuilder: FormBuilder,
private userPreferencesService: UserPreferencesService,
private dateAdapter: DateAdapter<DateFnsAdapter>,
@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;
}
}
}

View File

@ -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;
}

View File

@ -19,7 +19,7 @@
<mat-icon>{{ chipIcon }}</mat-icon> <mat-icon>{{ chipIcon }}</mat-icon>
</mat-chip> </mat-chip>
<mat-menu #menu="matMenu" backdropClass="adf-search-filter-chip-menu" [class]="'adf-search-filter-chip-menu-panel-' + category.id" (closed)="onClosed()"> <mat-menu #menu="matMenu" class="adf-search-widget-extra-width" backdropClass="adf-search-filter-chip-menu" [class]="'adf-search-filter-chip-menu-panel-' + category.id" (closed)="onClosed()">
<div #menuContainer [attr.data-automation-id]="'search-field-' + category.name"> <div #menuContainer [attr.data-automation-id]="'search-field-' + category.name">
<adf-search-filter-menu-card (click)="$event.stopPropagation()" <adf-search-filter-menu-card (click)="$event.stopPropagation()"
(keydown.tab)="$event.stopPropagation();" (keydown.tab)="$event.stopPropagation();"

View File

@ -24,6 +24,13 @@ import { SearchWidgetContainerComponent } from '../../search-widget-container/se
@Component({ @Component({
selector: 'adf-search-widget-chip', selector: 'adf-search-widget-chip',
templateUrl: './search-widget-chip.component.html', templateUrl: './search-widget-chip.component.html',
styles: [
`
.adf-search-widget-extra-width {
max-width: 500px;
}
`
],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None
}) })
export class SearchWidgetChipComponent { export class SearchWidgetChipComponent {

View File

@ -0,0 +1,28 @@
/*!
* @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 { Directive, Input, TemplateRef } from '@angular/core';
@Directive({
selector: '[adf-search-filter-tab]'
})
export class SearchFilterTabDirective {
@Input('adf-search-filter-tab')
name: string;
constructor(public readonly templateRef: TemplateRef<any>) { }
}

View File

@ -0,0 +1,5 @@
<mat-tab-group>
<mat-tab *ngFor="let tabContent of tabsContents" label="{{tabContent.name | translate}}">
<ng-container *ngTemplateOutlet="tabContent.templateRef"></ng-container>
</mat-tab>
</mat-tab-group>

View File

@ -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;
}
}

View File

@ -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<SearchFilterTabDirective>;
}

View File

@ -34,6 +34,7 @@ export * from './services/search-facet-filters.service';
export * from './services/search-filter.service'; export * from './services/search-filter.service';
export * from './services/search.service'; export * from './services/search.service';
export * from './services/search-configuration.service'; export * from './services/search-configuration.service';
export * from './services/base-query-builder.service';
export * from './mocks/search.service.mock'; 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-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-facet-field/search-facet-field.component';
export * from './components/search-logical-filter/search-logical-filter.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/reset-search.directive';
export * from './components/search-chip-autocomplete-input/search-chip-autocomplete-input.component'; export * from './components/search-chip-autocomplete-input/search-chip-autocomplete-input.component';
export * from './components/search-filter-autocomplete-chips/search-filter-autocomplete-chips.component'; export * from './components/search-filter-autocomplete-chips/search-filter-autocomplete-chips.component';

View File

@ -52,6 +52,10 @@ import { SearchFacetChipComponent } from './components/search-filter-chips/searc
import { SearchLogicalFilterComponent } from './components/search-logical-filter/search-logical-filter.component'; import { SearchLogicalFilterComponent } from './components/search-logical-filter/search-logical-filter.component';
import { ResetSearchDirective } from './components/reset-search.directive'; import { ResetSearchDirective } from './components/reset-search.directive';
import { SearchPropertiesComponent } from './components/search-properties/search-properties.component'; 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({ @NgModule({
imports: [ imports: [
@ -90,7 +94,11 @@ import { SearchPropertiesComponent } from './components/search-properties/search
SearchFacetChipComponent, SearchFacetChipComponent,
SearchLogicalFilterComponent, SearchLogicalFilterComponent,
ResetSearchDirective, ResetSearchDirective,
SearchPropertiesComponent SearchPropertiesComponent,
SearchFilterTabbedComponent,
SearchDateRangeAdvancedComponent,
SearchDateRangeAdvancedTabbedComponent,
SearchFilterTabDirective
], ],
exports: [ exports: [
SearchComponent, SearchComponent,
@ -116,6 +124,8 @@ import { SearchPropertiesComponent } from './components/search-properties/search
SearchFilterMenuCardComponent, SearchFilterMenuCardComponent,
SearchFacetFieldComponent, SearchFacetFieldComponent,
SearchLogicalFilterComponent, SearchLogicalFilterComponent,
SearchFilterTabbedComponent,
SearchDateRangeAdvancedComponent,
ResetSearchDirective ResetSearchDirective
], ],
providers: [ providers: [

View File

@ -26,6 +26,9 @@ import { SearchDatetimeRangeComponent } from '../components/search-datetime-rang
import { SearchLogicalFilterComponent } from '../components/search-logical-filter/search-logical-filter.component'; 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 { SearchFilterAutocompleteChipsComponent } from '../components/search-filter-autocomplete-chips/search-filter-autocomplete-chips.component';
import { SearchPropertiesComponent } from '../components/search-properties/search-properties.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({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -45,7 +48,8 @@ export class SearchFilterService {
'date-range': SearchDateRangeComponent, 'date-range': SearchDateRangeComponent,
'datetime-range': SearchDatetimeRangeComponent, 'datetime-range': SearchDatetimeRangeComponent,
'logical-filter': SearchLogicalFilterComponent, 'logical-filter': SearchLogicalFilterComponent,
'autocomplete-chips': SearchFilterAutocompleteChipsComponent 'autocomplete-chips': SearchFilterAutocompleteChipsComponent,
'date-range-advanced': SearchDateRangeAdvancedTabbedComponent
}; };
} }

View File

@ -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;
}
}

View File

@ -20,3 +20,4 @@ export * from './file-utils';
export * from './moment-date-formats.model'; export * from './moment-date-formats.model';
export * from './moment-date-adapter'; export * from './moment-date-adapter';
export * from './string-utils'; export * from './string-utils';
export * from './date-fns-utils';

View File

@ -45,7 +45,7 @@ export class SearchSliderPage {
async setValue(value: number): Promise<void> { async setValue(value: number): Promise<void> {
const elem = this.filter.$(this.slider).$('.mat-slider-wrapper'); const elem = this.filter.$(this.slider).$('.mat-slider-wrapper');
await browser.actions().mouseMove(elem, { x: 0, y: 0 }).perform(); 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<void> { async checkSliderIsDisplayed(): Promise<void> {

14
package-lock.json generated
View File

@ -18,6 +18,7 @@
"@angular/core": "14.1.3", "@angular/core": "14.1.3",
"@angular/forms": "14.1.3", "@angular/forms": "14.1.3",
"@angular/material": "14.1.2", "@angular/material": "14.1.2",
"@angular/material-date-fns-adapter": "^14.1.2",
"@angular/material-moment-adapter": "14.1.2", "@angular/material-moment-adapter": "14.1.2",
"@angular/platform-browser": "14.1.3", "@angular/platform-browser": "14.1.3",
"@angular/platform-browser-dynamic": "14.1.3", "@angular/platform-browser-dynamic": "14.1.3",
@ -1216,6 +1217,19 @@
"rxjs": "^6.5.3 || ^7.4.0" "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": { "node_modules/@angular/material-moment-adapter": {
"version": "14.1.2", "version": "14.1.2",
"license": "MIT", "license": "MIT",

View File

@ -62,6 +62,7 @@
"@angular/core": "14.1.3", "@angular/core": "14.1.3",
"@angular/forms": "14.1.3", "@angular/forms": "14.1.3",
"@angular/material": "14.1.2", "@angular/material": "14.1.2",
"@angular/material-date-fns-adapter": "^14.1.2",
"@angular/material-moment-adapter": "14.1.2", "@angular/material-moment-adapter": "14.1.2",
"@angular/platform-browser": "14.1.3", "@angular/platform-browser": "14.1.3",
"@angular/platform-browser-dynamic": "14.1.3", "@angular/platform-browser-dynamic": "14.1.3",