ACS-8238: Remove IsIncludedPipe and TabLabelsPipe (#9899)

This commit is contained in:
Denys Vuika 2024-07-09 12:24:32 -04:00 committed by GitHub
parent 709b291863
commit c6b121a698
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 144 additions and 254 deletions

View File

@ -1,28 +0,0 @@
---
Title: Is Included pipe
Added: v6.1.0
Status: Active
Last reviewed: 2023-06-13
---
# [Is Included pipe](../../../lib/content-services/src/lib/pipes/is-included.pipe.ts "Defined in is-included.pipe.ts")
Checks if the provided value is contained in the provided array.
## Basic Usage
<!-- {% raw %} -->
```HTML
<mat-option [disabled]="value | adfIsIncluded: arrayOfValues : comparator"></mat-option>
```
<!-- {% endraw %} -->
## Details
The pipe takes the provided value and checks if that value is included in the provided array and returns the appropriate boolean value.
## See also
- [File upload error pipe](./file-upload-error.pipe.md)

View File

@ -102,7 +102,6 @@ backend services have been tested with each released version of ADF.
- [Search Logical Filter component](content-services/components/search-logical-filter.component.md)
- [Search Chip Autocomplete Input component](content-services/components/search-chip-autocomplete-input.component.md)
- [Search Filter Autocomplete Chips component](content-services/components/search-filter-autocomplete-chips.component.md)
- [Is Included pipe](content-services/pipes/is-included.pipe.md)
<!--v610 end-->

View File

@ -23,11 +23,6 @@ import { Pagination, PeopleApi, PersonBodyCreate, PersonBodyUpdate } from '@alfr
import { EcmUserModel } from '../models/ecm-user.model';
import { ContentService } from './content.service';
// eslint-disable-next-line no-shadow
export enum ContentGroups {
ALFRESCO_ADMINISTRATORS = 'ALFRESCO_ADMINISTRATORS'
}
export interface PeopleContentQueryResponse {
pagination: Pagination;
entries: EcmUserModel[];
@ -56,11 +51,7 @@ export class PeopleContentService {
return this._peopleApi;
}
constructor(
private apiService: AlfrescoApiService,
authenticationService: AuthenticationService,
private contentService: ContentService
) {
constructor(private apiService: AlfrescoApiService, authenticationService: AuthenticationService, private contentService: ContentService) {
authenticationService.onLogout.subscribe(() => {
this.resetLocalCurrentUser();
});
@ -73,10 +64,7 @@ export class PeopleContentService {
* @returns User information
*/
getPerson(personId: string): Observable<EcmUserModel> {
return from(this.peopleApi.getPerson(personId))
.pipe(
map((personEntry) => new EcmUserModel(personEntry.entry))
);
return from(this.peopleApi.getPerson(personId)).pipe(map((personEntry) => new EcmUserModel(personEntry.entry)));
}
getCurrentPerson(): Observable<EcmUserModel> {
@ -92,7 +80,7 @@ export class PeopleContentService {
if (this.currentUser) {
return of(this.currentUser);
}
return this.getPerson('-me-').pipe(tap(user => (this.currentUser = user)));
return this.getPerson('-me-').pipe(tap((user) => (this.currentUser = user)));
}
/**
@ -118,7 +106,7 @@ export class PeopleContentService {
* @returns Response containing pagination and list of entries
*/
listPeople(requestQuery?: PeopleContentQueryRequestModel): Observable<PeopleContentQueryResponse> {
const requestQueryParams = {skipCount: requestQuery?.skipCount, maxItems: requestQuery?.maxItems};
const requestQueryParams = { skipCount: requestQuery?.skipCount, maxItems: requestQuery?.maxItems };
const orderBy = this.buildOrderArray(requestQuery?.sorting);
if (orderBy.length) {
requestQueryParams['orderBy'] = orderBy;
@ -126,7 +114,7 @@ export class PeopleContentService {
const promise = this.peopleApi.listPeople(requestQueryParams);
return from(promise).pipe(
map(response => ({
map((response) => ({
pagination: response.list.pagination,
entries: response.list.entries.map((person) => person.entry as EcmUserModel)
}))
@ -141,9 +129,7 @@ export class PeopleContentService {
* @returns Created new person
*/
createPerson(newPerson: PersonBodyCreate, opts?: any): Observable<EcmUserModel> {
return from(this.peopleApi.createPerson(newPerson, opts)).pipe(
map((res) => res?.entry as EcmUserModel)
);
return from(this.peopleApi.createPerson(newPerson, opts)).pipe(map((res) => res?.entry as EcmUserModel));
}
/**
@ -155,9 +141,7 @@ export class PeopleContentService {
* @returns Updated person model
*/
updatePerson(personId: string, details: PersonBodyUpdate, opts?: any): Observable<EcmUserModel> {
return from(this.peopleApi.updatePerson(personId, details, opts)).pipe(
map((res) => res?.entry as EcmUserModel)
);
return from(this.peopleApi.updatePerson(personId, details, opts)).pipe(map((res) => res?.entry as EcmUserModel));
}
/**

View File

@ -17,10 +17,8 @@
import { NgModule } from '@angular/core';
import { NodeNameTooltipPipe } from './node-name-tooltip.pipe';
import { IsIncludedPipe } from './is-included.pipe';
import { TabLabelsPipe } from './tab-labels.pipe';
export const CONTENT_PIPES = [NodeNameTooltipPipe, IsIncludedPipe, TabLabelsPipe] as const;
export const CONTENT_PIPES = [NodeNameTooltipPipe] as const;
/**
* @deprecated Use the individual pipe modules instead.

View File

@ -1,51 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 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 { IsIncludedPipe } from './is-included.pipe';
describe('IsIncludedPipe', () => {
let pipe: IsIncludedPipe<any>;
const array = [1, 2, 'test', [null], {}];
beforeEach(() => {
pipe = new IsIncludedPipe();
});
it('should return true if the string is contained in an array', () => {
expect(pipe.transform('test', array)).toBeTruthy();
});
it('should return false if the string is not contained in an array', () => {
expect(pipe.transform('test 1', array)).toBeFalsy();
});
it('should return true if the number is in the array', () => {
expect(pipe.transform(2, array)).toBeTruthy();
});
it('should return false if the number is not contained in an array', () => {
expect(pipe.transform(50, array)).toBeFalsy();
});
it('should use provided comparator to check if value contains in the provided array', () => {
const arrayOfObjects = [{id: 'id-1', value: 'value-1'}, {id: 'id-2', value: 'value-2'}];
const filterFunction = (extension1, extension2) => extension1.value === extension2.value;
expect(pipe.transform({id: 'id-1', value: 'value-1'}, arrayOfObjects, filterFunction)).toBeTruthy();
expect(pipe.transform({id: 'id-1', value: 'value-3'}, arrayOfObjects, filterFunction)).toBeFalsy();
});
});

View File

@ -1,28 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 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 { Pipe, PipeTransform } from '@angular/core';
@Pipe({
name: 'adfIsIncluded',
standalone: true
})
export class IsIncludedPipe<T> implements PipeTransform {
transform(value: T, array: T[], compare?: (value1: T, value2: T) => boolean): boolean {
return compare ? array.some((arrayValue) => compare(value, arrayValue)) : array.includes(value);
}
}

View File

@ -16,6 +16,4 @@
*/
export * from './node-name-tooltip.pipe';
export * from './is-included.pipe';
export * from './tab-labels.pipe';
export * from './content-pipe.module';

View File

@ -1,43 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 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 { TabLabelsPipe } from './tab-labels.pipe';
import {
mockSearchFilterWithoutDisplayedLabelsByField,
mockSearchFilterWithWrongDisplayedLabelsByField,
mockSearchFilterWithDisplayedLabelsByField
} from '../mock/date-range-search-filter.mock';
describe('TabLabelsPipe', () => {
const pipe = new TabLabelsPipe();
it('should default to field name when there are no settings available', () => {
expect(pipe.transform('test', null)).toBe('test');
});
it('should default to field name when settings do not contain "displayedLabelsByField" property', () => {
expect(pipe.transform('test', mockSearchFilterWithoutDisplayedLabelsByField.component.settings)).toBe('test');
});
it('should default to field name when settings with "displayedLabelsByField" property are available but do not contain field', () => {
expect(pipe.transform('test', mockSearchFilterWithWrongDisplayedLabelsByField.component.settings)).toBe('test');
});
it('should display correct label when settings with "displayedLabelsByField" property are available and do contain field', () => {
expect(pipe.transform('test', mockSearchFilterWithDisplayedLabelsByField.component.settings)).toBe('test-tab-label');
});
});

View File

@ -1,29 +0,0 @@
/*!
* @license
* Copyright © 2005-2024 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 { Pipe, PipeTransform } from '@angular/core';
import { SearchWidgetSettings } from '../search/models/search-widget-settings.interface';
@Pipe({
name: 'tabLabels',
standalone: true
})
export class TabLabelsPipe implements PipeTransform {
transform(field: string, settings?: SearchWidgetSettings): string {
return settings?.displayedLabelsByField?.[field] ? settings.displayedLabelsByField[field] : field;
}
}

View File

@ -1,14 +1,14 @@
<mat-form-field class="adf-chip-list" appearance="outline">
<mat-chip-list #chipList [attr.aria-label]="'SEARCH.FILTER.ARIA-LABEL.OPTIONS-SELECTION' | translate">
<mat-chip
class="adf-option-chips adf-autocomplete-added-option-chips"
*ngFor="let option of selectedOptions"
(removed)="remove(option)">
<span [title]="option.fullPath ? ('SEARCH.RESULTS.WILL_CONTAIN' | translate:{searchTerm: option.fullPath}) : undefined">
<mat-chip class="adf-option-chips adf-autocomplete-added-option-chips" *ngFor="let option of selectedOptions" (removed)="remove(option)">
<span [title]="option.fullPath ? ('SEARCH.RESULTS.WILL_CONTAIN' | translate : { searchTerm: option.fullPath }) : undefined">
{{ option.value }}
</span>
<button matChipRemove class="adf-option-chips-delete-button adf-autocomplete-added-option-chips-delete-button"
[title]="'SEARCH.FILTER.BUTTONS.REMOVE' | translate">
<button
matChipRemove
class="adf-option-chips-delete-button adf-autocomplete-added-option-chips-delete-button"
[title]="'SEARCH.FILTER.BUTTONS.REMOVE' | translate"
>
<mat-icon class="adf-option-chips-delete-icon adf-autocomplete-added-option-chips-delete-icon">close</mat-icon>
</button>
</mat-chip>
@ -29,12 +29,14 @@
(optionActivated)="activeAnyOption = true" (closed)="activeAnyOption = false">
<ng-container *ngIf="optionInput.value.length > 0">
<mat-option
[disabled]="option | adfIsIncluded: selectedOptions : compareOption"
*ngFor="let option of filteredOptions" [value]="option"
[attr.data-automation-id]="'option-' + (option.value)"
[title]="option.fullPath ? ('SEARCH.RESULTS.WILL_CONTAIN' | translate:{searchTerm: option.fullPath || option.value}): undefined"
*ngFor="let option of filteredOptions"
[value]="option"
[disabled]="isOptionSelected(option)"
[attr.data-automation-id]="'option-' + option.value"
[title]="option.fullPath ? ('SEARCH.RESULTS.WILL_CONTAIN' | translate : { searchTerm: option.fullPath || option.value }) : undefined"
class="adf-search-chip-autocomplete-added-option"
[ngClass]="(option | adfIsIncluded: selectedOptions : compareOption) && 'adf-autocomplete-added-option'">
[ngClass]="isOptionSelected(option) && 'adf-autocomplete-added-option'"
>
{{ option.fullPath || option.value }}
</mat-option>
</ng-container>

View File

@ -294,4 +294,46 @@ describe('SearchChipAutocompleteInputComponent', () => {
await fixture.whenStable();
expect(inputChangedSpy).toHaveBeenCalledOnceWith('test-value');
});
describe('isOptionSelected', () => {
beforeEach(() => {
component.autocompleteOptions = [{ value: 'option1' }, { value: 'option2' }];
fixture.detectChanges();
});
it('should return true if option is already selected', () => {
const option = { value: 'option1' };
component.selectedOptions = [option];
expect(component.isOptionSelected(option)).toBeTrue();
});
it('should return false if option is not selected', () => {
component.selectedOptions = [{ value: 'option1' }];
expect(component.isOptionSelected({ value: 'option2' })).toBeFalse();
});
it('should return true if custom compare function finds a match', () => {
component.compareOption = (option1, option2) => option1.value.charAt(0) === option2.value.charAt(0);
component.selectedOptions = [{ value: 'apple' }];
expect(component.isOptionSelected({ value: 'apricot' })).toBeTrue();
});
it('should return false if custom compare function does not find a match', () => {
component.compareOption = (option1, option2) => option1.value.charAt(0) === option2.value.charAt(0);
component.selectedOptions = [{ value: 'banana' }];
expect(component.isOptionSelected({ value: 'cherry' })).toBeFalse();
});
it('should return false if there are no selected options', () => {
component.selectedOptions = [];
expect(component.isOptionSelected({ value: 'option1' })).toBeFalse();
});
it('should handle undefined compareOption gracefully', () => {
component.compareOption = undefined;
const option = { value: 'option1' };
component.selectedOptions = [option];
expect(component.isOptionSelected(option)).toBeTrue();
});
});
});

View File

@ -153,6 +153,23 @@ export class SearchChipAutocompleteInputComponent implements OnInit, OnDestroy,
this.optionsChanged.emit(this.selectedOptions);
}
/**
* Determines if a given option is already selected.
*
* This method checks if the provided `option` is present in the `selectedOptions` array.
* If a custom comparison function (`compareOption`) is provided, it uses this function to determine equality.
* Otherwise, it falls back to using strict equality comparison.
*
* @param option - The option to check for selection.
* @returns `true` if the option is selected, `false` otherwise.
*/
isOptionSelected(option: AutocompleteOption): boolean {
const compare = this.compareOption;
const array = this.selectedOptions;
return compare ? array.some((arrayValue) => compare(option, arrayValue)) : array.includes(option);
}
private isAdded(value: string): boolean {
const valueLowerCase = value.toLowerCase();
return this.selectedOptions.some((option) => option.value.toLowerCase() === valueLowerCase);

View File

@ -1,7 +1,7 @@
<adf-search-filter-tabbed>
<ng-container *ngFor="let field of fields">
<adf-search-date-range
*adf-search-filter-tab="field | tabLabels: settings"
*adf-search-filter-tab="getTabLabel(field)"
[dateFormat]="settings.dateFormat"
[maxDate]="settings.maxDate"
[field]="field"

View File

@ -51,6 +51,7 @@ export class MockSearchDateRangeComponent {
@Output()
valid = new EventEmitter<boolean>();
}
describe('SearchDateRangeTabbedComponent', () => {
let component: SearchDateRangeTabbedComponent;
let fixture: ComponentFixture<SearchDateRangeTabbedComponent>;
@ -225,4 +226,41 @@ describe('SearchDateRangeTabbedComponent', () => {
expect(component.context.queryFragments['dateRange']).toEqual('');
expect(component.context.update).toHaveBeenCalled();
});
describe('SearchDateRangeTabbedComponent getTabLabel', () => {
beforeEach(() => {
component.settings = {
displayedLabelsByField: {
createdDate: 'Created Date',
modifiedDate: 'Modified Date'
}
} as any;
});
it('should return the custom label if it is defined in settings', () => {
const label = component.getTabLabel('createdDate');
expect(label).toEqual('Created Date');
});
it('should return the field name if no custom label is defined in settings', () => {
const label = component.getTabLabel('someOtherField');
expect(label).toEqual('someOtherField');
});
it('should handle undefined settings gracefully', () => {
component.settings = undefined;
const label = component.getTabLabel('createdDate');
expect(label).toEqual('createdDate');
});
it('should handle null field input gracefully', () => {
const label = component.getTabLabel(null);
expect(label).toBeNull();
});
it('should handle undefined field input gracefully', () => {
const label = component.getTabLabel(undefined);
expect(label).toBeUndefined();
});
});
});

View File

@ -24,18 +24,7 @@ import { SearchWidgetSettings } from '../../models/search-widget-settings.interf
import { SearchQueryBuilderService } from '../../services/search-query-builder.service';
import { InLastDateType } from './search-date-range/in-last-date-type';
import { TranslationService } from '@alfresco/adf-core';
import {
endOfDay,
endOfToday,
format,
formatISO,
startOfDay,
startOfMonth,
startOfWeek,
subDays,
subMonths,
subWeeks
} from 'date-fns';
import { endOfDay, endOfToday, format, formatISO, startOfDay, startOfMonth, startOfWeek, subDays, subMonths, subWeeks } from 'date-fns';
const DEFAULT_DATE_DISPLAY_FORMAT = 'dd-MMM-yy';
@ -68,7 +57,7 @@ export class SearchDateRangeTabbedComponent implements SearchWidget, OnInit {
constructor(private translateService: TranslationService) {}
ngOnInit(): void {
this.fields = this.settings?.field.split(',').map(field => field.trim());
this.fields = this.settings?.field.split(',').map((field) => field.trim());
this.setDefaultDateFormatSettings();
}
@ -99,6 +88,10 @@ export class SearchDateRangeTabbedComponent implements SearchWidget, OnInit {
this.value = value;
}
getTabLabel(field: string): string {
return this.settings?.displayedLabelsByField?.[field] ? this.settings.displayedLabelsByField[field] : field;
}
submitValues() {
this.context.queryFragments[this.id] = this.combinedQuery;
this.displayValue$.next(this.combinedDisplayValue);
@ -118,7 +111,7 @@ export class SearchDateRangeTabbedComponent implements SearchWidget, OnInit {
let endDate: Date;
if (value.dateRangeType === DateRangeType.IN_LAST) {
if (value.inLastValue) {
switch(value.inLastValueType) {
switch (value.inLastValueType) {
case InLastDateType.DAYS:
startDate = startOfDay(subDays(new Date(), parseInt(value.inLastValue, 10)));
break;
@ -152,7 +145,10 @@ export class SearchDateRangeTabbedComponent implements SearchWidget, OnInit {
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)}`;
displayValue = `${format(startOfDay(value.betweenStartDate), this.settings.dateFormat)} - ${format(
endOfDay(value.betweenEndDate),
this.settings.dateFormat
)}`;
}
return displayValue;
}
@ -172,13 +168,19 @@ export class SearchDateRangeTabbedComponent implements SearchWidget, OnInit {
this.displayValueMapByField.set(field, this.generateDisplayValue(value));
this.displayValueMapByField.forEach((displayValue: string, fieldForDisplayLabel: string) => {
if (displayValue) {
const displayLabelForField = `${this.translateService.instant(this.getDisplayLabelForField(fieldForDisplayLabel)).toUpperCase()}: ${displayValue}`;
this.combinedDisplayValue = this.combinedDisplayValue ? `${this.combinedDisplayValue} ${displayLabelForField}` : `${displayLabelForField}`;
const displayLabelForField = `${this.translateService
.instant(this.getDisplayLabelForField(fieldForDisplayLabel))
.toUpperCase()}: ${displayValue}`;
this.combinedDisplayValue = this.combinedDisplayValue
? `${this.combinedDisplayValue} ${displayLabelForField}`
: `${displayLabelForField}`;
}
});
}
private getDisplayLabelForField(fieldForDisplayLabel: string): string {
return this.settings?.displayedLabelsByField?.[fieldForDisplayLabel] ? this.settings.displayedLabelsByField[fieldForDisplayLabel] : fieldForDisplayLabel;
return this.settings?.displayedLabelsByField?.[fieldForDisplayLabel]
? this.settings.displayedLabelsByField[fieldForDisplayLabel]
: fieldForDisplayLabel;
}
}

View File

@ -56,20 +56,9 @@ import { SearchFilterTabDirective } from './components/search-filter-tabbed/sear
import { SearchFacetChipTabbedComponent } from './components/search-filter-chips/search-facet-chip-tabbed/search-facet-chip-tabbed.component';
import { SearchFacetTabbedContentComponent } from './components/search-filter-chips/search-facet-chip-tabbed/search-facet-tabbed-content.component';
import { SearchInputComponent } from './components/search-input';
import { IsIncludedPipe, TabLabelsPipe } from '../pipes';
@NgModule({
imports: [
CommonModule,
FormsModule,
ReactiveFormsModule,
MaterialModule,
CoreModule,
SearchTextModule,
SearchInputComponent,
IsIncludedPipe,
TabLabelsPipe
],
imports: [CommonModule, FormsModule, ReactiveFormsModule, MaterialModule, CoreModule, SearchTextModule, SearchInputComponent],
declarations: [
SearchComponent,
SearchControlComponent,