AAE-20848 Add display external property widget (#9429)

* AAE-20848 Add display external property widget

* revert css changes

* AAE-20848 validate validatable types

* AAE-20848 implement suggestions

* fix lint
This commit is contained in:
Tomasz Gnyp
2024-03-18 10:02:49 +01:00
committed by GitHub
parent d8bd7dc424
commit b4f27d15b8
11 changed files with 411 additions and 4 deletions

View File

@@ -27,6 +27,7 @@ import { RadioButtonsCloudWidgetComponent } from './widgets/radio-buttons/radio-
import { FileViewerWidgetComponent } from './widgets/file-viewer/file-viewer.widget';
import { DisplayRichTextWidgetComponent } from './widgets/display-rich-text/display-rich-text.widget';
import { DataTableWidgetComponent } from './widgets/data-table/data-table.widget';
import { DisplayExternalPropertyWidgetComponent } from './widgets/display-external-property/display-external-property.widget';
@Injectable({
providedIn: 'root'
@@ -45,7 +46,8 @@ export class CloudFormRenderingService extends FormRenderingService {
[FormFieldTypes.RADIO_BUTTONS]: () => RadioButtonsCloudWidgetComponent,
[FormFieldTypes.ALFRESCO_FILE_VIEWER]: () => FileViewerWidgetComponent,
[FormFieldTypes.DISPLAY_RICH_TEXT]: () => DisplayRichTextWidgetComponent,
[FormFieldTypes.DATA_TABLE]: () => DataTableWidgetComponent
[FormFieldTypes.DATA_TABLE]: () => DataTableWidgetComponent,
[FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY]: () => DisplayExternalPropertyWidgetComponent
}, true);
}
}

View File

@@ -0,0 +1,30 @@
<div
class="adf-textfield adf-display-external-property-widget {{ field.className }}"
[class.adf-invalid]="!field.isValid && isTouched()"
[class.adf-readonly]="field.readOnly"
[class.adf-left-label-input-container]="field.leftLabels"
>
<div *ngIf="field.leftLabels">
<label class="adf-label adf-left-label" [attr.for]="field.id">
{{ field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span>
</label>
</div>
<div>
<mat-form-field [hideRequiredMarker]="true">
<label class="adf-label" *ngIf="!field.leftLabels" [attr.for]="field.id">
{{ field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span>
</label>
<input matInput
class="adf-input"
type="text"
data-automation-id="adf-display-external-property-widget"
[id]="field.id"
[formControl]="propertyControl"
>
</mat-form-field>
<ng-container *ngIf="!previewState">
<error-widget *ngIf="propertyLoadFailed" [required]="'FORM.FIELD.EXTERNAL_PROPERTY_LOAD_FAILED' | translate"></error-widget>
</ng-container>
</div>

View File

@@ -0,0 +1,9 @@
.adf {
&-display-external-property-widget {
width: 100%;
.adf-label {
top: 20px;
}
}
}

View File

@@ -0,0 +1,187 @@
/*!
* @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 { FormService, FormFieldModel, FormModel, FormFieldTypes, LogService } from '@alfresco/adf-core';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
import { MatInputHarness } from '@angular/material/input/testing';
import { DisplayExternalPropertyWidgetComponent } from './display-external-property.widget';
import { FormCloudService } from '../../../services/form-cloud.service';
import { TranslateModule } from '@ngx-translate/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
describe('DisplayExternalPropertyWidgetComponent', () => {
let loader: HarnessLoader;
let widget: DisplayExternalPropertyWidgetComponent;
let fixture: ComponentFixture<DisplayExternalPropertyWidgetComponent>;
let element: HTMLElement;
let logService: LogService;
let logServiceSpy: jasmine.Spy;
let formCloudService: FormCloudService;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot(),
NoopAnimationsModule,
ReactiveFormsModule,
DisplayExternalPropertyWidgetComponent
],
providers: [FormService]
}).compileComponents();
fixture = TestBed.createComponent(DisplayExternalPropertyWidgetComponent);
widget = fixture.componentInstance;
element = fixture.nativeElement;
loader = TestbedHarnessEnvironment.loader(fixture);
logService = TestBed.inject(LogService);
formCloudService = TestBed.inject(FormCloudService);
logServiceSpy = spyOn(logService, 'error');
});
it('should display initial value', async () => {
widget.field = new FormFieldModel(new FormModel({ taskId: '<id>' }), {
type: FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY,
readOnly: true,
externalProperty: 'fruitName',
value: 'banana'
});
fixture.detectChanges();
const input = await loader.getHarness(MatInputHarness);
expect(fixture.nativeElement.querySelector('.adf-invalid')).toBeFalsy();
expect(await input.getValue()).toBe('banana');
});
describe('when property load fails', () => {
beforeEach(() => {
widget.field = new FormFieldModel(new FormModel({ taskId: '<id>' }), {
type: FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY,
externalProperty: 'fruitName',
value: null
});
fixture.detectChanges();
});
it('should display the error message', () => {
const errorElement = element.querySelector('error-widget');
expect(errorElement.textContent.trim()).toContain('FORM.FIELD.EXTERNAL_PROPERTY_LOAD_FAILED');
});
it('should log the error', () => {
expect(logServiceSpy).toHaveBeenCalledWith('External property not found');
});
});
describe('when property is in preview state', () => {
beforeEach(() => {
widget.field = new FormFieldModel(new FormModel({ taskId: '<id>' }), {
type: FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY,
externalProperty: true,
value: null
});
spyOn(formCloudService, 'getPreviewState').and.returnValue(true);
fixture.detectChanges();
});
it('should NOT display the error message', () => {
const errorElement = element.querySelector('error-widget');
expect(errorElement).toBeFalsy();
});
it('should NOT log the error', () => {
expect(logServiceSpy).not.toHaveBeenCalled();
});
});
describe('when is required', () => {
beforeEach(() => {
widget.field = new FormFieldModel(new FormModel({ taskId: '<id>' }), {
type: FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY,
required: true
});
fixture.detectChanges();
});
it('should be able to display label with asterisk', () => {
const asterisk = element.querySelector('.adf-asterisk');
expect(asterisk).toBeTruthy();
expect(asterisk?.textContent).toEqual('*');
});
});
describe('when form model has left labels', () => {
it('should have left labels classes on leftLabels true', async () => {
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', leftLabels: true }), {
id: 'external-property-id',
name: 'external-property-name',
value: '',
type: FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY
});
fixture.detectChanges();
const widgetContainer = element.querySelector('.adf-left-label-input-container');
expect(widgetContainer).not.toBeNull();
const adfLeftLabel = element.querySelector('.adf-left-label');
expect(adfLeftLabel).not.toBeNull();
});
it('should not have left labels classes on leftLabels false', () => {
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id', leftLabels: false }), {
id: 'external-property-id',
name: 'external-property-name',
value: '',
type: FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY
});
fixture.detectChanges();
const widgetContainer = element.querySelector('.adf-left-label-input-container');
expect(widgetContainer).toBeNull();
const adfLeftLabel = element.querySelector('.adf-left-label');
expect(adfLeftLabel).toBeNull();
});
it('should not have left labels classes on leftLabels not present', () => {
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
id: 'external-property-id',
name: 'external-property-name',
value: '',
type: FormFieldTypes.DISPLAY_EXTERNAL_PROPERTY
});
fixture.detectChanges();
const widgetContainer = element.querySelector('.adf-left-label-input-container');
expect(widgetContainer).toBeNull();
const adfLeftLabel = element.querySelector('.adf-left-label');
expect(adfLeftLabel).toBeNull();
});
});
});

View File

@@ -0,0 +1,109 @@
/*!
* @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 { ChangeDetectionStrategy, Component, OnInit, ViewEncapsulation } from '@angular/core';
import {
WidgetComponent,
FormService,
LogService,
FormBaseModule
} from '@alfresco/adf-core';
import { CommonModule } from '@angular/common';
import { TranslateModule } from '@ngx-translate/core';
import { FormCloudService } from '../../../services/form-cloud.service';
import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
@Component({
standalone: true,
imports: [
CommonModule,
TranslateModule,
ReactiveFormsModule,
MatFormFieldModule,
MatInputModule,
FormBaseModule
],
selector: 'adf-cloud-display-external-property',
templateUrl: './display-external-property.widget.html',
styleUrls: ['./display-external-property.widget.scss'],
host: {
'(click)': 'event($event)',
'(blur)': 'event($event)',
'(change)': 'event($event)',
'(focus)': 'event($event)',
'(focusin)': 'event($event)',
'(focusout)': 'event($event)',
'(input)': 'event($event)',
'(invalid)': 'event($event)',
'(select)': 'event($event)'
},
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DisplayExternalPropertyWidgetComponent extends WidgetComponent implements OnInit {
propertyLoadFailed = false;
previewState = false;
propertyControl: FormControl;
constructor(
public readonly formService: FormService,
private readonly formCloudService: FormCloudService,
private readonly logService: LogService
) {
super(formService);
}
ngOnInit(): void {
this.initFormControl();
this.initPreviewState();
this.handleFailedPropertyLoad();
}
private initFormControl(): void {
this.propertyControl = new FormControl(
{
value: this.field?.value,
disabled: this.field?.readOnly || this.readOnly
},
this.isRequired() ? [Validators.required] : []
);
}
private isPropertyLoadFailed(): boolean {
return this.field.externalProperty && !this.field.value;
}
private handleFailedPropertyLoad(): void {
if (this.isPropertyLoadFailed()) {
this.handleError('External property not found');
}
}
private initPreviewState(): void {
this.previewState = this.formCloudService.getPreviewState();
}
private handleError(error: any): void {
if (!this.previewState) {
this.propertyLoadFailed = true;
this.logService.error(error);
}
}
}