mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-05-12 17:04:46 +00:00
[ACS-4130] Added autocomplete to folder rules 'Has Category' condition (#3464)
* [ACS-4130] Added autocomplete for 'Has Category' option in manage rules * [ACS-4130] Added loading spinner and 'No options found' template for Has Category rule condition. Options are now fetched as soon as user selected 'Has Category' option * [ACS-4130] Added code to fetch category name when viewing/editing existing rule with has category option selected * [ACS-4130] Resolved issues related to editing existing rules with 'Has Category' condition * [ACS-4130] Added safety checks and minor code refactoring * [ACS-4130] Added unit tests for new autocomplete functionality * [ACS-4130] Added feature to auto select first option from autocomplete dropdown when user focuses out of autocomplete input field * [ACS-4130] Minor code refactoring. Moved constants from global scope to local scope * [ACS-4130] Moved mock data to conditions.mock.ts. Removed redundant return type * [ACS-4130] Resolved PR review comments - AutoCompleteOption is now an interface. Changed occurences of autocomplete with auto-complete. Removed/Added types * [ACS-4130] Resolved PR review comments - AutoCompleteOption is now built using a single common helper method * [ACS-4130] Added missed types
This commit is contained in:
parent
c7e2912759
commit
ec18f6b9cb
@ -139,6 +139,9 @@
|
|||||||
},
|
},
|
||||||
"ERRORS": {
|
"ERRORS": {
|
||||||
"DELETE_RULE_SET_LINK_FAILED": "Error while trying to delete a link from a rule set"
|
"DELETE_RULE_SET_LINK_FAILED": "Error while trying to delete a link from a rule set"
|
||||||
|
},
|
||||||
|
"AUTOCOMPLETE": {
|
||||||
|
"NO_OPTIONS_FOUND": "No options found"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,12 +37,65 @@ export const mimeTypeMock: RuleSimpleCondition = {
|
|||||||
parameter: ''
|
parameter: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
export const categoryMock: RuleSimpleCondition = {
|
export const tagMock: RuleSimpleCondition = {
|
||||||
field: 'category',
|
field: 'tag',
|
||||||
comparator: 'equals',
|
comparator: 'equals',
|
||||||
parameter: ''
|
parameter: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const categoriesListMock = {
|
||||||
|
list: {
|
||||||
|
pagination: {
|
||||||
|
count: 3,
|
||||||
|
hasMoreItems: false,
|
||||||
|
totalItems: 0,
|
||||||
|
skipCount: 0,
|
||||||
|
maxItems: 25
|
||||||
|
},
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
entry: {
|
||||||
|
path: {
|
||||||
|
name: '/a/fake/category/path/1'
|
||||||
|
},
|
||||||
|
hasChildren: false,
|
||||||
|
name: 'FakeCategory1',
|
||||||
|
id: 'fake-category-id-1',
|
||||||
|
nodeType: 'cm:category',
|
||||||
|
isFile: false,
|
||||||
|
isFolder: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entry: {
|
||||||
|
path: {
|
||||||
|
name: '/a/fake/category/path/2'
|
||||||
|
},
|
||||||
|
hasChildren: false,
|
||||||
|
name: 'FakeCategory2',
|
||||||
|
id: 'fake-category-id-2',
|
||||||
|
nodeType: 'cm:category',
|
||||||
|
isFile: false,
|
||||||
|
isFolder: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
entry: {
|
||||||
|
path: {
|
||||||
|
name: '/a/fake/category/path/3'
|
||||||
|
},
|
||||||
|
hasChildren: false,
|
||||||
|
name: 'FakeCategory3',
|
||||||
|
id: 'fake-category-id-3',
|
||||||
|
nodeType: 'cm:category',
|
||||||
|
isFile: false,
|
||||||
|
isFolder: false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const simpleConditionUnknownFieldMock: RuleSimpleCondition = {
|
export const simpleConditionUnknownFieldMock: RuleSimpleCondition = {
|
||||||
field: 'unknown-field',
|
field: 'unknown-field',
|
||||||
comparator: 'equals',
|
comparator: 'equals',
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export type RuleConditionFieldType = 'string' | 'number' | 'date' | 'type' | 'special' | 'mimeType';
|
export type RuleConditionFieldType = 'string' | 'number' | 'date' | 'type' | 'special' | 'mimeType' | 'auto-complete';
|
||||||
|
|
||||||
export interface RuleConditionField {
|
export interface RuleConditionField {
|
||||||
name: string;
|
name: string;
|
||||||
@ -30,7 +30,7 @@ export interface RuleConditionField {
|
|||||||
type: RuleConditionFieldType;
|
type: RuleConditionFieldType;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const comparatorHiddenForConditionFieldType: string[] = ['special', 'mimeType'];
|
export const comparatorHiddenForConditionFieldType: string[] = ['special', 'mimeType', 'auto-complete'];
|
||||||
|
|
||||||
export const ruleConditionFields: RuleConditionField[] = [
|
export const ruleConditionFields: RuleConditionField[] = [
|
||||||
{
|
{
|
||||||
@ -56,7 +56,7 @@ export const ruleConditionFields: RuleConditionField[] = [
|
|||||||
{
|
{
|
||||||
name: 'category',
|
name: 'category',
|
||||||
label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.HAS_CATEGORY',
|
label: 'ACA_FOLDER_RULES.RULE_DETAILS.FIELDS.HAS_CATEGORY',
|
||||||
type: 'special'
|
type: 'auto-complete'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'tag',
|
name: 'tag',
|
||||||
|
@ -21,14 +21,54 @@
|
|||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field class="aca-rule-simple-condition__form__parameter-input">
|
<mat-form-field class="aca-rule-simple-condition__form__parameter-input" [ngSwitch]="selectedField.type">
|
||||||
<mat-select formControlName="parameter" data-automation-id="simple-condition-value-select" *ngIf="selectedField?.type === 'mimeType'; else valueInput">
|
<mat-select formControlName="parameter" data-automation-id="simple-condition-value-select" *ngSwitchCase="'mimeType'">
|
||||||
<mat-option *ngFor="let mimeType of mimeTypes"
|
<mat-option *ngFor="let mimeType of mimeTypes"
|
||||||
[value]="mimeType.value">
|
[value]="mimeType.value">
|
||||||
{{ mimeType.label }}
|
{{ mimeType.label }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
<ng-template #valueInput>
|
<ng-template [ngSwitchCase]="'auto-complete'">
|
||||||
|
<input
|
||||||
|
matInput
|
||||||
|
[matAutocomplete]="auto"
|
||||||
|
formControlName="parameter"
|
||||||
|
(focusout)="autoSelectValidOption()"
|
||||||
|
data-automation-id="auto-complete-input-field"
|
||||||
|
/>
|
||||||
|
<mat-autocomplete
|
||||||
|
#auto="matAutocomplete"
|
||||||
|
data-automation-id="folder-rule-auto-complete"
|
||||||
|
[autoActiveFirstOption]="true"
|
||||||
|
[autoSelectActiveOption]="true"
|
||||||
|
[displayWith]="autoCompleteDisplayFunction">
|
||||||
|
<mat-option disabled *ngIf="showLoadingSpinner; else optionList">
|
||||||
|
<span class="aca-rule-simple-condition__auto-complete-loading-spinner">
|
||||||
|
<mat-progress-spinner
|
||||||
|
color="primary"
|
||||||
|
mode="indeterminate"
|
||||||
|
data-automation-id="auto-complete-loading-spinner"
|
||||||
|
[diameter]="25"
|
||||||
|
></mat-progress-spinner>
|
||||||
|
</span>
|
||||||
|
</mat-option>
|
||||||
|
<ng-template #optionList>
|
||||||
|
<ng-container *ngIf="autoCompleteOptions?.length > 0; else noOptionsTemplate">
|
||||||
|
<mat-option
|
||||||
|
*ngFor="let option of autoCompleteOptions"
|
||||||
|
[value]="option.value">
|
||||||
|
{{ option.displayLabel }}
|
||||||
|
</mat-option>
|
||||||
|
</ng-container>
|
||||||
|
<ng-template #noOptionsTemplate>
|
||||||
|
<mat-option disabled>
|
||||||
|
{{ 'ACA_FOLDER_RULES.AUTOCOMPLETE.NO_OPTIONS_FOUND' | translate }}
|
||||||
|
</mat-option>
|
||||||
|
</ng-template>
|
||||||
|
</ng-template>
|
||||||
|
</mat-autocomplete>
|
||||||
|
</ng-template>
|
||||||
|
<ng-template ngSwitchDefault>
|
||||||
<input matInput placeholder="{{ 'ACA_FOLDER_RULES.RULE_DETAILS.PLACEHOLDER.VALUE' | translate }}" type="text" formControlName="parameter" data-automation-id="value-input">
|
<input matInput placeholder="{{ 'ACA_FOLDER_RULES.RULE_DETAILS.PLACEHOLDER.VALUE' | translate }}" type="text" formControlName="parameter" data-automation-id="value-input">
|
||||||
</ng-template>
|
</ng-template>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
@ -15,4 +15,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&__auto-complete-loading-spinner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,16 +22,21 @@
|
|||||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
import { ComponentFixture, discardPeriodicTasks, fakeAsync, TestBed, tick } from '@angular/core/testing';
|
||||||
import { RuleSimpleConditionUiComponent } from './rule-simple-condition.ui-component';
|
import { RuleSimpleConditionUiComponent } from './rule-simple-condition.ui-component';
|
||||||
import { CoreTestingModule } from '@alfresco/adf-core';
|
import { CoreTestingModule } from '@alfresco/adf-core';
|
||||||
import { By } from '@angular/platform-browser';
|
import { By } from '@angular/platform-browser';
|
||||||
import { DebugElement } from '@angular/core';
|
import { DebugElement } from '@angular/core';
|
||||||
import { categoryMock, mimeTypeMock, simpleConditionUnknownFieldMock } from '../../mock/conditions.mock';
|
import { tagMock, mimeTypeMock, simpleConditionUnknownFieldMock, categoriesListMock } from '../../mock/conditions.mock';
|
||||||
import { MimeType } from './rule-mime-types';
|
import { MimeType } from './rule-mime-types';
|
||||||
|
import { CategoryService } from '@alfresco/adf-content-services';
|
||||||
|
import { of } from 'rxjs';
|
||||||
|
import { RuleSimpleCondition } from '../../model/rule-simple-condition.model';
|
||||||
|
import { delay } from 'rxjs/operators';
|
||||||
|
|
||||||
describe('RuleSimpleConditionUiComponent', () => {
|
describe('RuleSimpleConditionUiComponent', () => {
|
||||||
let fixture: ComponentFixture<RuleSimpleConditionUiComponent>;
|
let fixture: ComponentFixture<RuleSimpleConditionUiComponent>;
|
||||||
|
let categoryService: CategoryService;
|
||||||
|
|
||||||
const getByDataAutomationId = (dataAutomationId: string): DebugElement =>
|
const getByDataAutomationId = (dataAutomationId: string): DebugElement =>
|
||||||
fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`));
|
fixture.debugElement.query(By.css(`[data-automation-id="${dataAutomationId}"]`));
|
||||||
@ -45,12 +50,20 @@ describe('RuleSimpleConditionUiComponent', () => {
|
|||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const setValueInInputField = (inputFieldDataAutomationId: string, value: string) => {
|
||||||
|
const inputField = fixture.debugElement.query(By.css(`[data-automation-id="${inputFieldDataAutomationId}"]`)).nativeElement;
|
||||||
|
inputField.value = value;
|
||||||
|
inputField.dispatchEvent(new Event('input'));
|
||||||
|
fixture.detectChanges();
|
||||||
|
};
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [CoreTestingModule, RuleSimpleConditionUiComponent]
|
imports: [CoreTestingModule, RuleSimpleConditionUiComponent]
|
||||||
});
|
});
|
||||||
|
|
||||||
fixture = TestBed.createComponent(RuleSimpleConditionUiComponent);
|
fixture = TestBed.createComponent(RuleSimpleConditionUiComponent);
|
||||||
|
categoryService = TestBed.inject(CategoryService);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should default the field to the name, the comparator to equals and the value empty', () => {
|
it('should default the field to the name, the comparator to equals and the value empty', () => {
|
||||||
@ -87,6 +100,20 @@ describe('RuleSimpleConditionUiComponent', () => {
|
|||||||
expect(getComputedStyle(comparatorFormField).display).toBe('none');
|
expect(getComputedStyle(comparatorFormField).display).toBe('none');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should hide the comparator select box if the type of the field is autoComplete', () => {
|
||||||
|
const autoCompleteField = 'category';
|
||||||
|
fixture.detectChanges();
|
||||||
|
const comparatorFormField = getByDataAutomationId('comparator-form-field').nativeElement;
|
||||||
|
|
||||||
|
expect(fixture.componentInstance.isComparatorHidden).toBeFalsy();
|
||||||
|
expect(getComputedStyle(comparatorFormField).display).not.toBe('none');
|
||||||
|
|
||||||
|
changeMatSelectValue('field-select', autoCompleteField);
|
||||||
|
|
||||||
|
expect(fixture.componentInstance.isComparatorHidden).toBeTruthy();
|
||||||
|
expect(getComputedStyle(comparatorFormField).display).toBe('none');
|
||||||
|
});
|
||||||
|
|
||||||
it('should set the comparator to equals if the field is set to a type with different comparators', () => {
|
it('should set the comparator to equals if the field is set to a type with different comparators', () => {
|
||||||
const onChangeFieldSpy = spyOn(fixture.componentInstance, 'onChangeField').and.callThrough();
|
const onChangeFieldSpy = spyOn(fixture.componentInstance, 'onChangeField').and.callThrough();
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
@ -165,9 +192,104 @@ describe('RuleSimpleConditionUiComponent', () => {
|
|||||||
|
|
||||||
expect(getByDataAutomationId('simple-condition-value-select')).toBeTruthy();
|
expect(getByDataAutomationId('simple-condition-value-select')).toBeTruthy();
|
||||||
|
|
||||||
fixture.componentInstance.writeValue(categoryMock);
|
fixture.componentInstance.writeValue(tagMock);
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
|
|
||||||
expect(getByDataAutomationId('value-input').nativeElement.value).toBe('');
|
expect(getByDataAutomationId('value-input').nativeElement.value).toBe('');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should provide auto-complete option when category is selected', () => {
|
||||||
|
fixture.detectChanges();
|
||||||
|
changeMatSelectValue('field-select', 'category');
|
||||||
|
|
||||||
|
expect(getByDataAutomationId('auto-complete-input-field')).toBeTruthy();
|
||||||
|
expect(fixture.componentInstance.form.get('parameter').value).toEqual('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should fetch category list when category option is selected', fakeAsync(() => {
|
||||||
|
spyOn(categoryService, 'searchCategories').and.returnValue(of(categoriesListMock));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
changeMatSelectValue('field-select', 'category');
|
||||||
|
tick(500);
|
||||||
|
|
||||||
|
expect(categoryService.searchCategories).toHaveBeenCalledWith('');
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should fetch new category list with user input when user types into parameter field after category option is select', fakeAsync(() => {
|
||||||
|
const categoryValue = 'a new category';
|
||||||
|
spyOn(categoryService, 'searchCategories').and.returnValue(of(categoriesListMock));
|
||||||
|
|
||||||
|
fixture.detectChanges();
|
||||||
|
changeMatSelectValue('field-select', 'category');
|
||||||
|
tick(500);
|
||||||
|
expect(categoryService.searchCategories).toHaveBeenCalledWith('');
|
||||||
|
|
||||||
|
setValueInInputField('auto-complete-input-field', categoryValue);
|
||||||
|
tick(500);
|
||||||
|
expect(categoryService.searchCategories).toHaveBeenCalledWith(categoryValue);
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should fetch category details when a saved rule with category condition is edited', () => {
|
||||||
|
const savedCategoryMock: RuleSimpleCondition = {
|
||||||
|
field: 'category',
|
||||||
|
comparator: 'equals',
|
||||||
|
parameter: 'a-fake-category-id'
|
||||||
|
};
|
||||||
|
|
||||||
|
const fakeCategory = {
|
||||||
|
entry: {
|
||||||
|
path: '/a/fake/category/path',
|
||||||
|
hasChildren: false,
|
||||||
|
name: 'FakeCategory',
|
||||||
|
id: 'fake-category-id-1'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
spyOn(categoryService, 'getCategory').and.returnValue(of(fakeCategory));
|
||||||
|
|
||||||
|
fixture.componentInstance.writeValue(savedCategoryMock);
|
||||||
|
fixture.detectChanges();
|
||||||
|
|
||||||
|
expect(categoryService.getCategory).toHaveBeenCalledWith(savedCategoryMock.parameter, { include: ['path'] });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should show loading spinner while auto-complete options are fetched, and then remove it once it is received', fakeAsync(() => {
|
||||||
|
spyOn(categoryService, 'searchCategories').and.returnValue(of(categoriesListMock).pipe(delay(1000)));
|
||||||
|
fixture.detectChanges();
|
||||||
|
changeMatSelectValue('field-select', 'category');
|
||||||
|
tick(500);
|
||||||
|
getByDataAutomationId('auto-complete-input-field')?.nativeElement?.click();
|
||||||
|
let loadingSpinner = getByDataAutomationId('auto-complete-loading-spinner');
|
||||||
|
expect(loadingSpinner).not.toBeNull();
|
||||||
|
tick(1000);
|
||||||
|
fixture.detectChanges();
|
||||||
|
loadingSpinner = getByDataAutomationId('auto-complete-loading-spinner');
|
||||||
|
expect(loadingSpinner).toBeNull();
|
||||||
|
discardPeriodicTasks();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should display correct label for category when user selects a category from auto-complete dropdown', fakeAsync(() => {
|
||||||
|
spyOn(categoryService, 'searchCategories').and.returnValue(of(categoriesListMock));
|
||||||
|
fixture.detectChanges();
|
||||||
|
changeMatSelectValue('field-select', 'category');
|
||||||
|
tick(500);
|
||||||
|
getByDataAutomationId('auto-complete-input-field')?.nativeElement?.click();
|
||||||
|
changeMatSelectValue('folder-rule-auto-complete', categoriesListMock.list.entries[0].entry.id);
|
||||||
|
const displayValue = getByDataAutomationId('auto-complete-input-field')?.nativeElement?.value;
|
||||||
|
expect(displayValue).toBe('category/path/1/FakeCategory1');
|
||||||
|
discardPeriodicTasks();
|
||||||
|
}));
|
||||||
|
|
||||||
|
it('should automatically select first category when user focuses out of parameter form field with category option selected', fakeAsync(() => {
|
||||||
|
spyOn(categoryService, 'searchCategories').and.returnValue(of(categoriesListMock));
|
||||||
|
fixture.detectChanges();
|
||||||
|
changeMatSelectValue('field-select', 'category');
|
||||||
|
tick(500);
|
||||||
|
const autoCompleteInputField = getByDataAutomationId('auto-complete-input-field')?.nativeElement;
|
||||||
|
autoCompleteInputField.value = 'FakeCat';
|
||||||
|
autoCompleteInputField.dispatchEvent(new Event('focusout'));
|
||||||
|
const parameterValue = fixture.componentInstance.form.get('parameter').value;
|
||||||
|
expect(parameterValue).toEqual(categoriesListMock.list.entries[0].entry.id);
|
||||||
|
discardPeriodicTasks();
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
@ -22,22 +22,47 @@
|
|||||||
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
* from Hyland Software. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Component, forwardRef, Input, OnDestroy, ViewEncapsulation } from '@angular/core';
|
import { Component, forwardRef, Input, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { AbstractControl, ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
|
import { AbstractControl, ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR, ReactiveFormsModule } from '@angular/forms';
|
||||||
import { RuleSimpleCondition } from '../../model/rule-simple-condition.model';
|
import { RuleSimpleCondition } from '../../model/rule-simple-condition.model';
|
||||||
import { comparatorHiddenForConditionFieldType, RuleConditionField, ruleConditionFields } from './rule-condition-fields';
|
import { comparatorHiddenForConditionFieldType, RuleConditionField, ruleConditionFields } from './rule-condition-fields';
|
||||||
import { RuleConditionComparator, ruleConditionComparators } from './rule-condition-comparators';
|
import { RuleConditionComparator, ruleConditionComparators } from './rule-condition-comparators';
|
||||||
import { AppConfigService } from '@alfresco/adf-core';
|
import { AppConfigService } from '@alfresco/adf-core';
|
||||||
import { MimeType } from './rule-mime-types';
|
import { MimeType } from './rule-mime-types';
|
||||||
import { CommonModule } from '@angular/common';
|
import { AsyncPipe, CommonModule } from '@angular/common';
|
||||||
import { TranslateModule } from '@ngx-translate/core';
|
import { TranslateModule } from '@ngx-translate/core';
|
||||||
import { MatFormFieldModule } from '@angular/material/form-field';
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
import { MatSelectModule } from '@angular/material/select';
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
import { MatInputModule } from '@angular/material/input';
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { CategoryService } from '@alfresco/adf-content-services';
|
||||||
|
import { MatAutocompleteModule } from '@angular/material/autocomplete';
|
||||||
|
import { debounceTime, distinctUntilChanged, first, takeUntil } from 'rxjs/operators';
|
||||||
|
import { Subject, Subscription } from 'rxjs';
|
||||||
|
import { MatOptionModule } from '@angular/material/core';
|
||||||
|
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
|
||||||
|
import { CategoryEntry } from '@alfresco/js-api';
|
||||||
|
|
||||||
|
interface AutoCompleteOption {
|
||||||
|
displayLabel: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AUTOCOMPLETE_OPTIONS_DEBOUNCE_TIME = 500;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [CommonModule, TranslateModule, ReactiveFormsModule, MatFormFieldModule, MatSelectModule, MatInputModule],
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
TranslateModule,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatSelectModule,
|
||||||
|
MatInputModule,
|
||||||
|
MatAutocompleteModule,
|
||||||
|
AsyncPipe,
|
||||||
|
MatOptionModule,
|
||||||
|
MatProgressSpinnerModule
|
||||||
|
],
|
||||||
selector: 'aca-rule-simple-condition',
|
selector: 'aca-rule-simple-condition',
|
||||||
templateUrl: './rule-simple-condition.ui-component.html',
|
templateUrl: './rule-simple-condition.ui-component.html',
|
||||||
styleUrls: ['./rule-simple-condition.ui-component.scss'],
|
styleUrls: ['./rule-simple-condition.ui-component.scss'],
|
||||||
@ -51,7 +76,7 @@ import { MatInputModule } from '@angular/material/input';
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
export class RuleSimpleConditionUiComponent implements ControlValueAccessor, OnDestroy {
|
export class RuleSimpleConditionUiComponent implements OnInit, ControlValueAccessor, OnDestroy {
|
||||||
readonly fields = ruleConditionFields;
|
readonly fields = ruleConditionFields;
|
||||||
|
|
||||||
form = new FormGroup({
|
form = new FormGroup({
|
||||||
@ -62,6 +87,12 @@ export class RuleSimpleConditionUiComponent implements ControlValueAccessor, OnD
|
|||||||
|
|
||||||
mimeTypes: MimeType[] = [];
|
mimeTypes: MimeType[] = [];
|
||||||
|
|
||||||
|
autoCompleteOptions: AutoCompleteOption[] = [];
|
||||||
|
|
||||||
|
showLoadingSpinner: boolean;
|
||||||
|
|
||||||
|
private onDestroy$ = new Subject<void>();
|
||||||
|
private autoCompleteOptionsSubscription: Subscription;
|
||||||
private _readOnly = false;
|
private _readOnly = false;
|
||||||
@Input()
|
@Input()
|
||||||
get readOnly(): boolean {
|
get readOnly(): boolean {
|
||||||
@ -71,15 +102,9 @@ export class RuleSimpleConditionUiComponent implements ControlValueAccessor, OnD
|
|||||||
this.setDisabledState(isReadOnly);
|
this.setDisabledState(isReadOnly);
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(private config: AppConfigService) {
|
constructor(private config: AppConfigService, private categoryService: CategoryService) {
|
||||||
this.mimeTypes = this.config.get<Array<MimeType>>('mimeTypes');
|
this.mimeTypes = this.config.get<Array<MimeType>>('mimeTypes');
|
||||||
}
|
}
|
||||||
|
|
||||||
private formSubscription = this.form.valueChanges.subscribe((value: any) => {
|
|
||||||
this.onChange(value);
|
|
||||||
this.onTouch();
|
|
||||||
});
|
|
||||||
|
|
||||||
get isSelectedFieldKnown(): boolean {
|
get isSelectedFieldKnown(): boolean {
|
||||||
const selectedFieldName = this.form.get('field').value;
|
const selectedFieldName = this.form.get('field').value;
|
||||||
return this.fields.findIndex((field: RuleConditionField) => selectedFieldName === field.name) > -1;
|
return this.fields.findIndex((field: RuleConditionField) => selectedFieldName === field.name) > -1;
|
||||||
@ -102,6 +127,7 @@ export class RuleSimpleConditionUiComponent implements ControlValueAccessor, OnD
|
|||||||
get isComparatorHidden(): boolean {
|
get isComparatorHidden(): boolean {
|
||||||
return comparatorHiddenForConditionFieldType.includes(this.selectedField?.type);
|
return comparatorHiddenForConditionFieldType.includes(this.selectedField?.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
get comparatorControl(): AbstractControl {
|
get comparatorControl(): AbstractControl {
|
||||||
return this.form.get('comparator');
|
return this.form.get('comparator');
|
||||||
}
|
}
|
||||||
@ -115,6 +141,18 @@ export class RuleSimpleConditionUiComponent implements ControlValueAccessor, OnD
|
|||||||
|
|
||||||
writeValue(value: RuleSimpleCondition) {
|
writeValue(value: RuleSimpleCondition) {
|
||||||
this.form.setValue(value);
|
this.form.setValue(value);
|
||||||
|
if (value?.field === 'category') {
|
||||||
|
this.showLoadingSpinner = true;
|
||||||
|
this.categoryService
|
||||||
|
.getCategory(value.parameter, { include: ['path'] })
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((category: CategoryEntry) => {
|
||||||
|
this.showLoadingSpinner = false;
|
||||||
|
const option = this.buildAutocompleteOptionFromCategory(category.entry.id, category.entry.path, category.entry.name);
|
||||||
|
this.autoCompleteOptions.push(option);
|
||||||
|
this.parameterControl.setValue(option.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
registerOnChange(fn: () => void) {
|
registerOnChange(fn: () => void) {
|
||||||
@ -147,6 +185,70 @@ export class RuleSimpleConditionUiComponent implements ControlValueAccessor, OnD
|
|||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
ngOnDestroy() {
|
||||||
this.formSubscription.unsubscribe();
|
this.onDestroy$.next();
|
||||||
|
this.onDestroy$.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.form.valueChanges.pipe(takeUntil(this.onDestroy$)).subscribe((value: RuleSimpleCondition) => {
|
||||||
|
this.onChange(value);
|
||||||
|
this.onTouch();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.form
|
||||||
|
.get('field')
|
||||||
|
.valueChanges.pipe(distinctUntilChanged(), takeUntil(this.onDestroy$))
|
||||||
|
.subscribe((field: string) => {
|
||||||
|
if (field === 'category') {
|
||||||
|
this.autoCompleteOptionsSubscription = this.form
|
||||||
|
.get('parameter')
|
||||||
|
.valueChanges.pipe(distinctUntilChanged(), debounceTime(AUTOCOMPLETE_OPTIONS_DEBOUNCE_TIME), takeUntil(this.onDestroy$))
|
||||||
|
.subscribe((categoryName) => {
|
||||||
|
this.getCategories(categoryName);
|
||||||
|
});
|
||||||
|
this.parameterControl.setValue('');
|
||||||
|
} else {
|
||||||
|
this.autoCompleteOptionsSubscription?.unsubscribe();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCategories(categoryName: string) {
|
||||||
|
this.showLoadingSpinner = true;
|
||||||
|
this.categoryService
|
||||||
|
.searchCategories(categoryName)
|
||||||
|
.pipe(first())
|
||||||
|
.subscribe((existingCategoriesResult) => {
|
||||||
|
this.showLoadingSpinner = false;
|
||||||
|
const options: AutoCompleteOption[] = existingCategoriesResult?.list?.entries?.map((rowEntry) =>
|
||||||
|
this.buildAutocompleteOptionFromCategory(rowEntry.entry.id, rowEntry.entry.path.name, rowEntry.entry.name)
|
||||||
|
);
|
||||||
|
if (options.length > 0) {
|
||||||
|
this.autoCompleteOptions = this.sortAutoCompleteOptions(options);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private sortAutoCompleteOptions(autoCompleteOptions: AutoCompleteOption[]): AutoCompleteOption[] {
|
||||||
|
return autoCompleteOptions.sort((option1, option2) => option1.displayLabel.localeCompare(option2.displayLabel));
|
||||||
|
}
|
||||||
|
|
||||||
|
autoCompleteDisplayFunction = (optionValue: string): string =>
|
||||||
|
optionValue && this.autoCompleteOptions ? this.autoCompleteOptions.find((option) => option.value === optionValue)?.displayLabel : optionValue;
|
||||||
|
|
||||||
|
autoSelectValidOption() {
|
||||||
|
const currentValue = this.parameterControl.value;
|
||||||
|
const isValidValueSelected = !!this.autoCompleteOptions?.find((option) => option.value === currentValue);
|
||||||
|
if (!isValidValueSelected) {
|
||||||
|
this.parameterControl.setValue(this.autoCompleteOptions?.[0].value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildAutocompleteOptionFromCategory(categoryId: string, categoryPath: string, categoryName: string): AutoCompleteOption {
|
||||||
|
const path = categoryPath.split('/').splice(3).join('/');
|
||||||
|
return {
|
||||||
|
value: categoryId,
|
||||||
|
displayLabel: path ? `${path}/${categoryName}` : categoryName
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user