[AAE-10773] Make Form core process agonostic (#8032)

* move form list in a component

* move things in the right place

* move last pice in the right place

* move things in the right place

* move people and group in the right place

* move radio and typehead
form service start remove responsibilities

* remove model service and editor service from formService

* move dropdwon in process-service
finish remove service from form service

* fix some wrong import

* move activiti

* fix double quote imports

* move dynamic table

* fix shell

* move unit test

* [ci:force] fix lint issues

* fix build and some unit test

* fix process spec type spy problems [ci:foce]

* fix

* fix broken tests

* fix lint issues

* fix cloud dropdown test

* cleanup process-service-cloud tests

* fix people process

* improve e2e test

Co-authored-by: Kasia Biernat <kasia.biernat@hyland.com>
This commit is contained in:
Eugenio Romano 2022-12-21 15:12:38 +00:00 committed by GitHub
parent eb27d38eba
commit a535af667b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
180 changed files with 1971 additions and 3260 deletions

View File

@ -15,26 +15,19 @@
* limitations under the License. * limitations under the License.
*/ */
import { Observable, of, Subject } from 'rxjs'; import { Observable, of } from 'rxjs';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { import {
AlfrescoApiService, TaskFormService
EcmModelService, } from '@alfresco/adf-process-services';
LogService, import { AlfrescoApiService, LogService } from '@alfresco/adf-core';
FormService,
FormOutcomeEvent
} from '@alfresco/adf-core';
@Injectable() @Injectable()
export class FakeFormService extends FormService { export class FakeTaskFormService extends TaskFormService {
executeOutcome = new Subject<FormOutcomeEvent>();
constructor( constructor(apiService: AlfrescoApiService, logService: LogService) {
ecmModelService: EcmModelService, super(apiService, logService);
apiService: AlfrescoApiService,
protected logService: LogService
) {
super(ecmModelService, apiService, logService);
} }
public getRestFieldValues( public getRestFieldValues(

View File

@ -17,7 +17,7 @@
import { Component, ViewChild, OnDestroy, OnInit } from '@angular/core'; import { Component, ViewChild, OnDestroy, OnInit } from '@angular/core';
import { FormModel, FormService, LogService, FormOutcomeEvent } from '@alfresco/adf-core'; import { FormModel, FormService, LogService, FormOutcomeEvent } from '@alfresco/adf-core';
import { FormComponent } from '@alfresco/adf-process-services'; import { FormComponent, EditorService } from '@alfresco/adf-process-services';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -42,7 +42,7 @@ export class FormListComponent implements OnInit, OnDestroy {
showValidationIcon = false; showValidationIcon = false;
private onDestroy$ = new Subject<boolean>(); private onDestroy$ = new Subject<boolean>();
constructor(private formService: FormService, private logService: LogService) { constructor(private formService: FormService, private editorService: EditorService, private logService: LogService) {
} }
ngOnInit() { ngOnInit() {
@ -63,7 +63,7 @@ export class FormListComponent implements OnInit, OnDestroy {
onRowDblClick(event: CustomEvent<any>) { onRowDblClick(event: CustomEvent<any>) {
const rowForm = event.detail.value.obj; const rowForm = event.detail.value.obj;
this.formService.getFormDefinitionById(rowForm.id).subscribe((formModel) => { this.editorService.getFormDefinitionById(rowForm.id).subscribe((formModel) => {
const form = this.formService.parseForm(formModel.formDefinition); const form = this.formService.parseForm(formModel.formDefinition);
this.form = form; this.form = form;
}); });

View File

@ -22,8 +22,11 @@ import {
FormOutcomeEvent, FormOutcomeEvent,
CoreAutomationService CoreAutomationService
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import {
TaskFormService
} from '@alfresco/adf-process-services';
import { InMemoryFormService } from '../../services/in-memory-form.service'; import { InMemoryFormService } from '../../services/in-memory-form.service';
import { FakeFormService } from './fake-form.service'; import { FakeTaskFormService } from './fake-tak-form.service';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -31,7 +34,7 @@ import { takeUntil } from 'rxjs/operators';
selector: 'app-form-loading', selector: 'app-form-loading',
templateUrl: './form-loading.component.html', templateUrl: './form-loading.component.html',
styleUrls: ['./form-loading.component.scss'], styleUrls: ['./form-loading.component.scss'],
providers: [{ provide: FormService, useClass: FakeFormService }] providers: [{ provide: FakeTaskFormService, useClass: TaskFormService }]
}) })
export class FormLoadingComponent implements OnInit, OnDestroy { export class FormLoadingComponent implements OnInit, OnDestroy {
form: FormModel; form: FormModel;

View File

@ -35,8 +35,7 @@ import {
ScriptFilesApi ScriptFilesApi
} from '@alfresco/js-api'; } from '@alfresco/js-api';
import { import {
FORM_FIELD_VALIDATORS, FormRenderingService, FormService, FORM_FIELD_VALIDATORS, FormRenderingService, FormService, AppConfigService, PaginationComponent, UserPreferenceValues,
DynamicTableRow, ValidateDynamicTableRowEvent, AppConfigService, PaginationComponent, UserPreferenceValues,
AlfrescoApiService, UserPreferencesService, LogService, DataCellEvent, NotificationService AlfrescoApiService, UserPreferencesService, LogService, DataCellEvent, NotificationService
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
@ -54,10 +53,12 @@ import {
TaskFiltersComponent, TaskFiltersComponent,
TaskListComponent, TaskListComponent,
ProcessFormRenderingService, ProcessFormRenderingService,
APP_LIST_LAYOUT_LIST APP_LIST_LAYOUT_LIST,
ValidateDynamicTableRowEvent,
DynamicTableRow
} from '@alfresco/adf-process-services'; } from '@alfresco/adf-process-services';
import { Subject } from 'rxjs'; import { Subject } from 'rxjs';
import { /*CustomEditorComponent*/ CustomStencil01 } from './custom-editor/custom-editor.component'; import { CustomStencil01 } from './custom-editor/custom-editor.component';
import { DemoFieldValidator } from './demo-field-validator'; import { DemoFieldValidator } from './demo-field-validator';
import { PreviewService } from '../../services/preview.service'; import { PreviewService } from '../../services/preview.service';
import { Location } from '@angular/common'; import { Location } from '@angular/common';

View File

@ -16,7 +16,7 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { AppConfigService, AlfrescoApiService, EcmModelService, LogService, import { AppConfigService, LogService,
FormFieldOption, FormService, FormValues, FormModel, FormFieldOption, FormService, FormValues, FormModel,
FormOutcomeModel, FormOutcomeEvent } from '@alfresco/adf-core'; FormOutcomeModel, FormOutcomeEvent } from '@alfresco/adf-core';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
@ -43,10 +43,8 @@ export class InMemoryFormService extends FormService {
executeOutcome = new Subject<FormOutcomeEvent>(); executeOutcome = new Subject<FormOutcomeEvent>();
constructor(appConfig: AppConfigService, constructor(appConfig: AppConfigService,
ecmModelService: EcmModelService,
apiService: AlfrescoApiService,
protected logService: LogService) { protected logService: LogService) {
super(ecmModelService, apiService, logService); super();
this.data = appConfig.get<ProcessServiceData>('activiti'); this.data = appConfig.get<ProcessServiceData>('activiti');
} }

View File

@ -45,16 +45,16 @@ Forms defined in APS have the following default mappings for the form fields:
| Number | integer | [`NumberWidgetComponent`](lib/core/src/lib/form/components/widgets/number/number.widget.ts) | | Number | integer | [`NumberWidgetComponent`](lib/core/src/lib/form/components/widgets/number/number.widget.ts) |
| Checkbox | boolean | [`CheckboxWidgetComponent`](lib/core/src/lib/form/components/widgets/checkbox/checkbox.widget.ts) | | Checkbox | boolean | [`CheckboxWidgetComponent`](lib/core/src/lib/form/components/widgets/checkbox/checkbox.widget.ts) |
| Date | date | [`DateWidgetComponent`](lib/core/src/lib/form/components/widgets/date/date.widget.ts) | | Date | date | [`DateWidgetComponent`](lib/core/src/lib/form/components/widgets/date/date.widget.ts) |
| Dropdown | dropdown | [`DropdownWidgetComponent`](lib/core/src/lib/form/components/widgets/dropdown/dropdown.widget.ts) | | Dropdown | dropdown | [`DropdownWidgetComponent`](lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.ts) |
| Typeahead | typeahead | [`TypeaheadWidgetComponent`](lib/core/src/lib/form/components/widgets/typeahead/typeahead.widget.ts) | | Typeahead | typeahead | [`TypeaheadWidgetComponent`](lib/process-services/src/lib/form/widgets/typeahead/typeahead.widget.ts) |
| Amount | amount | [`AmountWidgetComponent`](lib/core/src/lib/form/components/widgets/amount/amount.widget.ts) | | Amount | amount | [`AmountWidgetComponent`](lib/core/src/lib/form/components/widgets/amount/amount.widget.ts) |
| Radio buttons | radio-buttons | [`RadioButtonsWidgetComponent`](lib/core/src/lib/form/components/widgets/radio-buttons/radio-buttons.widget.ts) | | Radio buttons | radio-buttons | [`RadioButtonsWidgetComponent`](lib/process-services/src/lib/form/widgets/radio-buttons/radio-buttons.widget.ts) |
| People | people | [`PeopleWidgetComponent`](lib/core/src/lib/form/components/widgets/people/people.widget.ts) | | People | people | [`PeopleWidgetComponent`](lib/process-services/src/lib/form/widgets/people/people.widget.ts) |
| Group of people | functional-group | [`FunctionalGroupWidgetComponent`](lib/core/src/lib/form/components/widgets/functional-group/functional-group.widget.ts) | | Group of people | functional-group | [`FunctionalGroupWidgetComponent`](lib/process-services/src/lib/form/widgets/functional-group/functional-group.widget.ts) |
| Dynamic table | dynamic-table | [`DynamicTableWidgetComponent`](lib/core/src/lib/form/components/widgets/dynamic-table/dynamic-table.widget.ts) | | Dynamic table | dynamic-table | [`DynamicTableWidgetComponent`](lib/process-services/src/lib/form/widgets/dynamic-table/dynamic-table.widget.ts) |
| Hyperlink | hyperlink | [`HyperlinkWidgetComponent`](lib/core/src/lib/form/components/widgets/hyperlink/hyperlink.widget.ts) | | Hyperlink | hyperlink | [`HyperlinkWidgetComponent`](lib/core/src/lib/form/components/widgets/hyperlink/hyperlink.widget.ts) |
| Header | group | [`ContainerWidgetComponent`](lib/core/src/lib/form/components/widgets/container/container.widget.ts) | | Header | group | [`ContainerWidgetComponent`](lib/core/src/lib/form/components/widgets/container/container.widget.ts) |
| Attach File | upload | AttachWidgetComponent or [`UploadWidgetComponent`](lib/core/src/lib/form/components/widgets/upload/upload.widget.ts) (based on metadata) | | Attach File | upload | AttachWidgetComponent or [`UploadWidgetComponent`](lib/process-services/src/lib/form/widgets/upload/upload.widget.ts) (based on metadata) |
| Display value | readonly | [`TextWidgetComponent`](lib/core/src/lib/form/components/widgets/text/text.widget.ts) | | Display value | readonly | [`TextWidgetComponent`](lib/core/src/lib/form/components/widgets/text/text.widget.ts) |
| Display text | readonly-text | [`DisplayTextWidgetComponent`](lib/core/src/lib/form/components/widgets/display-text/display-text.widget.ts) | | Display text | readonly-text | [`DisplayTextWidgetComponent`](lib/core/src/lib/form/components/widgets/display-text/display-text.widget.ts) |
| Display Rich text | display-rich-text | [`DisplayRichTextWidgetComponent`](lib/core/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.ts) | | Display Rich text | display-rich-text | [`DisplayRichTextWidgetComponent`](lib/core/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.ts) |

View File

@ -5,7 +5,7 @@ Status: Active
Last reviewed: 2018-11-20 Last reviewed: 2018-11-20
--- ---
# [Form List Component](lib/core/src/lib/form/components/form-list.component.ts "Defined in form-list.component.ts") # [Form List Component](lib/process-services/src/lib/form/form-list/form-list.component.ts "Defined in form-list.component.ts")
Shows forms as a list. Shows forms as a list.

View File

@ -4,7 +4,7 @@ Added: v2.0.0
Status: Active Status: Active
--- ---
# [APS Alfresco Content Service](lib/core/src/lib/form/services/activiti-alfresco.service.ts "Defined in activiti-alfresco.service.ts") # [APS Alfresco Content Service](lib/process-services/src/lib/form/services/activiti-alfresco.service.ts "Defined in activiti-alfresco.service.ts")
Gets Alfresco Repository folder content based on a Repository account configured in Alfresco Process Services (APS). Gets Alfresco Repository folder content based on a Repository account configured in Alfresco Process Services (APS).

View File

@ -1,59 +0,0 @@
---
Title: Node Service
Added: v2.0.0
Status: Active
Last reviewed: 2018-11-20
---
# [Node Service](lib/core/src/lib/form/services/node.service.ts "Defined in node.service.ts") **Deprecated**
use [Nodes Api service](./nodes-api.service.md) instead of this.
Gets Alfresco Repository node metadata and creates nodes with metadata.
## Class members
### Methods
- **createNode**(name: `string`, nodeType: `string`, properties: `any`, path: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`>`<br/>
(**Deprecated:** in 3.8.0, use `createNodeInsideRoot` method from NodesApiService instead. Create a new Node from form metadata)
- _name:_ `string` - Node name
- _nodeType:_ `string` - Node type
- _properties:_ `any` - Node body properties
- _path:_ `string` - Path to the node
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`>` - The created node
- **createNodeMetadata**(nodeType: `string`, nameSpace: `any`, data: `any`, path: `string`, name?: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`>`<br/>
(**Deprecated:** in 3.8.0, use NodesApiService instead. Create a new Node from form metadata.)
- _nodeType:_ `string` - Node type
- _nameSpace:_ `any` - Namespace for properties
- _data:_ `any` - [Property](../../../lib/content-services/src/lib/content-metadata/interfaces/property.interface.ts) data to store in the node under namespace
- _path:_ `string` - Path to the node
- _name:_ `string` - (Optional) Node name
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`>` - The created node
- **getNodeMetadata**(nodeId: `string`): [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeMetadata`](../../../lib/core/models/node-metadata.model.ts)`>`<br/>
(**Deprecated:** in 3.8.0, use NodesApiService instead. Get the metadata and the nodeType for a nodeId cleaned by the prefix.)
- _nodeId:_ `string` - ID of the target node
- **Returns** [`Observable`](http://reactivex.io/documentation/observable.html)`<`[`NodeMetadata`](../../../lib/core/models/node-metadata.model.ts)`>` - Node metadata
## Details
Note that this service cannot be used to create nodes with content.
The `path` parameter to `createNode` and `createNodeMetadata` specifies an intermediate
path of folders to create between the root and the target node.
### Importing
```ts
import { NodeService } from '@alfresco/adf-core';
export class SomePageComponent implements OnInit {
constructor(private nodeService: NodeService) {
}
```
## See also
- [Nodes api service](nodes-api.service.md)
- [Deleted nodes api service](deleted-nodes-api.service.md)

View File

@ -4,7 +4,7 @@ Added: v2.0.0
Status: Active Status: Active
--- ---
# [Process Content Service](lib/core/src/lib/form/services/process-content.service.ts "Defined in process-content.service.ts") # [Process Content Service](lib/process-services/src/lib/form/services/process-content.service.ts "Defined in process-content.service.ts")
Manipulates content related to a Process Instance or Task Instance in APS. Manipulates content related to a Process Instance or Task Instance in APS.

View File

@ -4,7 +4,7 @@ Added: v2.0.0
Status: Active Status: Active
--- ---
# [APS Content Component](lib/core/src/lib/form/components/widgets/content/content.widget.ts "Defined in content.widget.ts") # [APS Content Component](lib/process-services/src/lib/form/widgets/document/content.widget.ts "Defined in content.widget.ts")
Shows the content preview. Shows the content preview.

View File

@ -759,7 +759,7 @@ Release Notes - Apps Development Framework - Version 2.0.
- \[[ADF-1879](https://issues.alfresco.com/jira/browse/ADF-1879)] - ADF Process Services is not working properly - \[[ADF-1879](https://issues.alfresco.com/jira/browse/ADF-1879)] - ADF Process Services is not working properly
- \[[ADF-1885](https://issues.alfresco.com/jira/browse/ADF-1885)] - Once accessed process services components appear throughout app - \[[ADF-1885](https://issues.alfresco.com/jira/browse/ADF-1885)] - Once accessed process services components appear throughout app
- \[[ADF-1890](https://issues.alfresco.com/jira/browse/ADF-1890)] - Viewer - Content projection for sidebar only works if sidebarPosition is left - \[[ADF-1890](https://issues.alfresco.com/jira/browse/ADF-1890)] - Viewer - Content projection for sidebar only works if sidebarPosition is left
- \[[ADF-1891](https://issues.alfresco.com/jira/browse/ADF-1891)] - [`ActivitiContentService`](lib/core/src/lib/form/services/activiti-alfresco.service.ts) is not exported - \[[ADF-1891](https://issues.alfresco.com/jira/browse/ADF-1891)] - [`ActivitiContentService`](lib/process-services/src/lib/form/services/activiti-alfresco.service.ts) is not exported
- \[[ADF-1898](https://issues.alfresco.com/jira/browse/ADF-1898)] - [`ProcessService`](../process-services/services/process.service.md).createOrUpdateProcessInstanceVariables has incorrect method signature - \[[ADF-1898](https://issues.alfresco.com/jira/browse/ADF-1898)] - [`ProcessService`](../process-services/services/process.service.md).createOrUpdateProcessInstanceVariables has incorrect method signature
- \[[ADF-1900](https://issues.alfresco.com/jira/browse/ADF-1900)] - [`ProcessService`](../process-services/services/process.service.md).getProcessInstanceVariables has incorrect method signature - \[[ADF-1900](https://issues.alfresco.com/jira/browse/ADF-1900)] - [`ProcessService`](../process-services/services/process.service.md).getProcessInstanceVariables has incorrect method signature
- \[[ADF-1901](https://issues.alfresco.com/jira/browse/ADF-1901)] - [`ProcessService`](../process-services/services/process.service.md).createDefaultFilters has incorrect method signature - \[[ADF-1901](https://issues.alfresco.com/jira/browse/ADF-1901)] - [`ProcessService`](../process-services/services/process.service.md).createDefaultFilters has incorrect method signature

View File

@ -353,7 +353,7 @@ Release Notes - Apps Development Framework - Version 2.2.
- \[[ADF-2242](https://issues.alfresco.com/jira/browse/ADF-2242)] - Search on custom sources is not working. - \[[ADF-2242](https://issues.alfresco.com/jira/browse/ADF-2242)] - Search on custom sources is not working.
- \[[ADF-2243](https://issues.alfresco.com/jira/browse/ADF-2243)] - Translation is missing for the 'Extended Search'. - \[[ADF-2243](https://issues.alfresco.com/jira/browse/ADF-2243)] - Translation is missing for the 'Extended Search'.
- \[[ADF-2246](https://issues.alfresco.com/jira/browse/ADF-2246)] - [Destination Picker] The selection from the dropdown is ignored after 'Clear' action - \[[ADF-2246](https://issues.alfresco.com/jira/browse/ADF-2246)] - [Destination Picker] The selection from the dropdown is ignored after 'Clear' action
- \[[ADF-2251](https://issues.alfresco.com/jira/browse/ADF-2251)] - [`ContentWidgetModule`](../../lib/process-services/src/lib/content-widget/content-widget.module.ts) is not exposed from ADF - \[[ADF-2251](https://issues.alfresco.com/jira/browse/ADF-2251)] - [`ContentWidgetModule`](lib/process-services/src/lib/form/widgets/content-widget/content-widget.module.ts) is not exposed from ADF
- \[[ADF-2254](https://issues.alfresco.com/jira/browse/ADF-2254)] - Viewer does not update top menu on "fileNodeId" changes - \[[ADF-2254](https://issues.alfresco.com/jira/browse/ADF-2254)] - Viewer does not update top menu on "fileNodeId" changes
- \[[ADF-2255](https://issues.alfresco.com/jira/browse/ADF-2255)] - SelectBox field does not render the data in ADF form - \[[ADF-2255](https://issues.alfresco.com/jira/browse/ADF-2255)] - SelectBox field does not render the data in ADF form
- \[[ADF-2260](https://issues.alfresco.com/jira/browse/ADF-2260)] - Action menu translation is missing when right clicking on a file/folder - \[[ADF-2260](https://issues.alfresco.com/jira/browse/ADF-2260)] - Action menu translation is missing when right clicking on a file/folder

View File

@ -432,7 +432,7 @@ These changes are noted with an arrow "->".
- `adf-grid-list` - `adf-grid-list`
- `adf-grid-list-item` - `adf-grid-list-item`
#### [../../lib/core/form/components/widgets/dynamic-table/dynamic-table.widget.scss](lib/core/src/lib/form/components/widgets/dynamic-table/dynamic-table.widget.scss) #### [../../lib/core/form/components/widgets/dynamic-table/dynamic-table.widget.scss](lib/process-services/src/lib/form/widgets/dynamic-table/dynamic-table.widget.scss)
- `adf-is-selected` - `adf-is-selected`
- `adf-no-select` - `adf-no-select`

View File

@ -83,21 +83,21 @@ formRenderingService.setComponentTypeResolver('text', customResolver, true);
| Number | integer | [`NumberWidgetComponent`](lib/core/src/lib/form/components/widgets/number/number.widget.ts) | | Number | integer | [`NumberWidgetComponent`](lib/core/src/lib/form/components/widgets/number/number.widget.ts) |
| Multi-line text | multi-line-text | [`MultilineTextWidgetComponentComponent`](lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.ts) | | Multi-line text | multi-line-text | [`MultilineTextWidgetComponentComponent`](lib/core/src/lib/form/components/widgets/multiline-text/multiline-text.widget.ts) |
| Checkbox | boolean | [`CheckboxWidgetComponent`](lib/core/src/lib/form/components/widgets/checkbox/checkbox.widget.ts) | | Checkbox | boolean | [`CheckboxWidgetComponent`](lib/core/src/lib/form/components/widgets/checkbox/checkbox.widget.ts) |
| Dropdown | dropdown | [`DropdownWidgetComponent`](lib/core/src/lib/form/components/widgets/dropdown/dropdown.widget.ts) | | Dropdown | dropdown | [`DropdownWidgetComponent`](lib/process-services/src/lib/form/widgets/dropdown/dropdown.widget.ts) |
| Date | date | [`DateWidgetComponent`](lib/core/src/lib/form/components/widgets/date/date.widget.ts) | | Date | date | [`DateWidgetComponent`](lib/core/src/lib/form/components/widgets/date/date.widget.ts) |
| Amount | amount | [`AmountWidgetComponent`](lib/core/src/lib/form/components/widgets/amount/amount.widget.ts) | | Amount | amount | [`AmountWidgetComponent`](lib/core/src/lib/form/components/widgets/amount/amount.widget.ts) |
| Radio buttons | radio-buttons | [`RadioButtonsWidgetComponent`](lib/core/src/lib/form/components/widgets/radio-buttons/radio-buttons.widget.ts) | | Radio buttons | radio-buttons | [`RadioButtonsWidgetComponent`](lib/process-services/src/lib/form/widgets/radio-buttons/radio-buttons.widget.ts) |
| Hyperlink | hyperlink | [`HyperlinkWidgetComponent`](lib/core/src/lib/form/components/widgets/hyperlink/hyperlink.widget.ts) | | Hyperlink | hyperlink | [`HyperlinkWidgetComponent`](lib/core/src/lib/form/components/widgets/hyperlink/hyperlink.widget.ts) |
| Display value | readonly | DisplayValueWidgetComponent | | Display value | readonly | DisplayValueWidgetComponent |
| Display Rich text | display-rich-text | [`DisplayRichTextWidgetComponent`](lib/core/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.ts) | | Display Rich text | display-rich-text | [`DisplayRichTextWidgetComponent`](lib/core/src/lib/form/components/widgets/display-rich-text/display-rich-text.widget.ts) |
| Display text | readonly-text | [`DisplayTextWidgetComponentComponent`](lib/core/src/lib/form/components/widgets/display-text/display-text.widget.ts) | | Display text | readonly-text | [`DisplayTextWidgetComponentComponent`](lib/core/src/lib/form/components/widgets/display-text/display-text.widget.ts) |
| Typeahead | typeahead | [`TypeaheadWidgetComponent`](lib/core/src/lib/form/components/widgets/typeahead/typeahead.widget.ts) | | Typeahead | typeahead | [`TypeaheadWidgetComponent`](lib/process-services/src/lib/form/widgets/typeahead/typeahead.widget.ts) |
| People | people | [`PeopleWidgetComponent`](lib/core/src/lib/form/components/widgets/people/people.widget.ts) | | People | people | [`PeopleWidgetComponent`](lib/process-services/src/lib/form/widgets/people/people.widget.ts) |
| Group of people | functional-group | [`FunctionalGroupWidgetComponent`](lib/core/src/lib/form/components/widgets/functional-group/functional-group.widget.ts) | | Group of people | functional-group | [`FunctionalGroupWidgetComponent`](lib/process-services/src/lib/form/widgets/functional-group/functional-group.widget.ts) |
| Dynamic table | dynamic-table | [`DynamicTableWidgetComponent`](lib/core/src/lib/form/components/widgets/dynamic-table/dynamic-table.widget.ts) | | Dynamic table | dynamic-table | [`DynamicTableWidgetComponent`](lib/process-services/src/lib/form/widgets/dynamic-table/dynamic-table.widget.ts) |
| N/A | container | [`ContainerWidgetComponent`](lib/core/src/lib/form/components/widgets/container/container.widget.ts) (layout component) | | N/A | container | [`ContainerWidgetComponent`](lib/core/src/lib/form/components/widgets/container/container.widget.ts) (layout component) |
| Header | group | [`ContainerWidgetComponent`](lib/core/src/lib/form/components/widgets/container/container.widget.ts) | | Header | group | [`ContainerWidgetComponent`](lib/core/src/lib/form/components/widgets/container/container.widget.ts) |
| Attach | upload | AttachWidgetComponent or [`UploadWidgetComponent`](lib/core/src/lib/form/components/widgets/upload/upload.widget.ts) (based on metadata) | | Attach | upload | AttachWidgetComponent or [`UploadWidgetComponent`](lib/process-services/src/lib/form/widgets/upload/upload.widget.ts) (based on metadata) |
| N/A | N/A | [`UnknownWidgetComponent`](lib/core/src/lib/form/components/widgets/unknown/unknown.widget.ts) | | N/A | N/A | [`UnknownWidgetComponent`](lib/core/src/lib/form/components/widgets/unknown/unknown.widget.ts) |
## Form Extensibility for APS/AAE ## Form Extensibility for APS/AAE

View File

@ -27,13 +27,10 @@ import { TabModel } from './tab.model';
import { fakeMetadataForm, fakeViewerForm } from '../../mock/form.mock'; import { fakeMetadataForm, fakeViewerForm } from '../../mock/form.mock';
import { Node } from '@alfresco/js-api'; import { Node } from '@alfresco/js-api';
import { UploadWidgetContentLinkModel } from './upload-widget-content-link.model'; import { UploadWidgetContentLinkModel } from './upload-widget-content-link.model';
import { AlfrescoApiService } from '../../../../services';
import { TestBed } from '@angular/core/testing';
import { CoreTestingModule, setupTestBed } from '../../../../testing'; import { CoreTestingModule, setupTestBed } from '../../../../testing';
describe('FormModel', () => { describe('FormModel', () => {
let formService: FormService; let formService: FormService;
let alfrescoApiService: AlfrescoApiService;
setupTestBed({ setupTestBed({
imports: [ imports: [
@ -42,9 +39,7 @@ describe('FormModel', () => {
}); });
beforeEach(() => { beforeEach(() => {
alfrescoApiService = TestBed.inject(AlfrescoApiService); formService = new FormService();
formService = new FormService(null, alfrescoApiService, null);
}); });
it('should store original json', () => { it('should store original json', () => {

View File

@ -21,30 +21,14 @@ import { AmountWidgetComponent } from './amount/amount.widget';
import { CheckboxWidgetComponent } from './checkbox/checkbox.widget'; import { CheckboxWidgetComponent } from './checkbox/checkbox.widget';
import { DateWidgetComponent } from './date/date.widget'; import { DateWidgetComponent } from './date/date.widget';
import { DisplayTextWidgetComponent } from './display-text/display-text.widget'; import { DisplayTextWidgetComponent } from './display-text/display-text.widget';
import { DocumentWidgetComponent } from './document/document.widget';
import { DropdownWidgetComponent } from './dropdown/dropdown.widget';
import { DynamicTableWidgetComponent } from './dynamic-table/dynamic-table.widget';
import { BooleanEditorComponent } from './dynamic-table/editors/boolean/boolean.editor';
import { DateEditorComponent } from './dynamic-table/editors/date/date.editor';
import { DateTimeEditorComponent } from './dynamic-table/editors/datetime/datetime.editor';
import { DropdownEditorComponent } from './dynamic-table/editors/dropdown/dropdown.editor';
import { RowEditorComponent } from './dynamic-table/editors/row.editor';
import { TextEditorComponent } from './dynamic-table/editors/text/text.editor';
import { AmountEditorComponent } from './dynamic-table/editors/amount/amount.editor';
import { ErrorWidgetComponent } from './error/error.component'; import { ErrorWidgetComponent } from './error/error.component';
import { FunctionalGroupWidgetComponent } from './functional-group/functional-group.widget';
import { HyperlinkWidgetComponent } from './hyperlink/hyperlink.widget'; import { HyperlinkWidgetComponent } from './hyperlink/hyperlink.widget';
import { MultilineTextWidgetComponentComponent } from './multiline-text/multiline-text.widget'; import { MultilineTextWidgetComponentComponent } from './multiline-text/multiline-text.widget';
import { NumberWidgetComponent } from './number/number.widget'; import { NumberWidgetComponent } from './number/number.widget';
import { PeopleWidgetComponent } from './people/people.widget';
import { RadioButtonsWidgetComponent } from './radio-buttons/radio-buttons.widget';
import { InputMaskDirective } from './text/text-mask.component'; import { InputMaskDirective } from './text/text-mask.component';
import { TextWidgetComponent } from './text/text.widget'; import { TextWidgetComponent } from './text/text.widget';
import { TypeaheadWidgetComponent } from './typeahead/typeahead.widget';
import { UploadWidgetComponent } from './upload/upload.widget';
import { DateTimeWidgetComponent } from './date-time/date-time.widget'; import { DateTimeWidgetComponent } from './date-time/date-time.widget';
import { JsonWidgetComponent } from './json/json.widget'; import { JsonWidgetComponent } from './json/json.widget';
import { UploadFolderWidgetComponent } from './upload-folder/upload-folder.widget';
import { FileViewerWidgetComponent } from './file-viewer/file-viewer.widget'; import { FileViewerWidgetComponent } from './file-viewer/file-viewer.widget';
import { DisplayRichTextWidgetComponent } from './display-rich-text/display-rich-text.widget'; import { DisplayRichTextWidgetComponent } from './display-rich-text/display-rich-text.widget';
@ -52,41 +36,21 @@ import { DisplayRichTextWidgetComponent } from './display-rich-text/display-rich
export * from './widget.component'; export * from './widget.component';
export * from './core'; export * from './core';
// primitives // primitives
export * from './unknown/unknown.widget'; export * from './unknown/unknown.widget';
export * from './text/text.widget'; export * from './text/text.widget';
export * from './number/number.widget'; export * from './number/number.widget';
export * from './checkbox/checkbox.widget'; export * from './checkbox/checkbox.widget';
export * from './multiline-text/multiline-text.widget'; export * from './multiline-text/multiline-text.widget';
export * from './dropdown/dropdown.widget';
export * from './hyperlink/hyperlink.widget'; export * from './hyperlink/hyperlink.widget';
export * from './radio-buttons/radio-buttons.widget';
export * from './display-text/display-text.widget'; export * from './display-text/display-text.widget';
export * from './upload/upload.widget';
export * from './typeahead/typeahead.widget';
export * from './functional-group/functional-group.widget';
export * from './people/people.widget';
export * from './date/date.widget'; export * from './date/date.widget';
export * from './amount/amount.widget'; export * from './amount/amount.widget';
export * from './dynamic-table/dynamic-table.widget';
export * from './error/error.component'; export * from './error/error.component';
export * from './document/document.widget';
export * from './date-time/date-time.widget'; export * from './date-time/date-time.widget';
export * from './json/json.widget'; export * from './json/json.widget';
export * from './upload-folder/upload-folder.widget';
export * from './file-viewer/file-viewer.widget'; export * from './file-viewer/file-viewer.widget';
export * from './display-rich-text/display-rich-text.widget'; export * from './display-rich-text/display-rich-text.widget';
// editors (dynamic table)
export * from './dynamic-table/dynamic-table.widget.model';
export * from './dynamic-table/editors/row.editor';
export * from './dynamic-table/editors/date/date.editor';
export * from './dynamic-table/editors/dropdown/dropdown.editor';
export * from './dynamic-table/editors/boolean/boolean.editor';
export * from './dynamic-table/editors/text/text.editor';
export * from './dynamic-table/editors/datetime/datetime.editor';
export * from './dynamic-table/editors/amount/amount.editor';
export * from './text/text-mask.component'; export * from './text/text-mask.component';
export const WIDGET_DIRECTIVES: any[] = [ export const WIDGET_DIRECTIVES: any[] = [
@ -95,29 +59,13 @@ export const WIDGET_DIRECTIVES: any[] = [
NumberWidgetComponent, NumberWidgetComponent,
CheckboxWidgetComponent, CheckboxWidgetComponent,
MultilineTextWidgetComponentComponent, MultilineTextWidgetComponentComponent,
DropdownWidgetComponent,
HyperlinkWidgetComponent, HyperlinkWidgetComponent,
RadioButtonsWidgetComponent,
DisplayTextWidgetComponent, DisplayTextWidgetComponent,
UploadWidgetComponent,
TypeaheadWidgetComponent,
FunctionalGroupWidgetComponent,
PeopleWidgetComponent,
DateWidgetComponent, DateWidgetComponent,
AmountWidgetComponent, AmountWidgetComponent,
DynamicTableWidgetComponent,
DateEditorComponent,
DropdownEditorComponent,
BooleanEditorComponent,
TextEditorComponent,
RowEditorComponent,
ErrorWidgetComponent, ErrorWidgetComponent,
DocumentWidgetComponent,
DateTimeWidgetComponent, DateTimeWidgetComponent,
DateTimeEditorComponent,
JsonWidgetComponent, JsonWidgetComponent,
AmountEditorComponent,
UploadFolderWidgetComponent,
FileViewerWidgetComponent, FileViewerWidgetComponent,
DisplayRichTextWidgetComponent DisplayRichTextWidgetComponent
]; ];

View File

@ -1,7 +0,0 @@
<div class="adf-upload-folder-widget {{field.className}}"
[class.adf-invalid]="!field.isValid"
[class.adf-readonly]="field.readOnly">
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk" *ngIf="isRequired()">*</span></label>
<div class="adf-upload-widget-container">
</div>
</div>

View File

@ -1,8 +0,0 @@
.adf {
&-upload-folder-widget {
width: 100%;
word-break: break-all;
padding: 0.4375em 0;
border-top: 0.8438em solid transparent;
}
}

View File

@ -1,61 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 { setupTestBed } from '../../../../testing/setup-test-bed';
import { CoreTestingModule } from '../../../../testing';
import { UploadFolderWidgetComponent } from './upload-folder.widget';
import { FormFieldModel } from '../core/form-field.model';
import { FormModel } from '../core/form.model';
import { FormFieldTypes } from '../core/form-field-types';
describe('UploadFolderWidgetComponent', () => {
let widget: UploadFolderWidgetComponent;
let fixture: ComponentFixture<UploadFolderWidgetComponent>;
let element: HTMLElement;
setupTestBed({
imports: [
CoreTestingModule
]
});
beforeEach(() => {
fixture = TestBed.createComponent(UploadFolderWidgetComponent);
widget = fixture.componentInstance;
element = fixture.nativeElement;
});
describe('when is required', () => {
it('should be able to display label with asterisk', async () => {
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
type: FormFieldTypes.UPLOAD,
required: true
});
fixture.detectChanges();
await fixture.whenStable();
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
expect(asterisk).toBeTruthy();
expect(asterisk.textContent).toEqual('*');
});
});
});

View File

@ -1,165 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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.
*/
/* eslint-disable @angular-eslint/component-selector */
import { LogService } from '../../../../services/log.service';
import { ThumbnailService } from '../../../../services/thumbnail.service';
import { Component, ElementRef, OnInit, ViewChild, ViewEncapsulation } from '@angular/core';
import { Observable, from } from 'rxjs';
import { FormService } from '../../../services/form.service';
import { ProcessContentService } from '../../../services/process-content.service';
import { ContentLinkModel } from '../core/content-link.model';
import { WidgetComponent } from '../widget.component';
import { mergeMap, map } from 'rxjs/operators';
@Component({
selector: 'upload-folder-widget',
templateUrl: './upload-folder.widget.html',
styleUrls: ['./upload-folder.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
})
export class UploadFolderWidgetComponent extends WidgetComponent implements OnInit {
hasFile: boolean;
displayText: string;
multipleOption: string = '';
mimeTypeIcon: string;
@ViewChild('uploadFiles')
fileInput: ElementRef;
constructor(public formService: FormService,
private logService: LogService,
private thumbnailService: ThumbnailService,
public processContentService: ProcessContentService) {
super(formService);
}
ngOnInit() {
if (this.field &&
this.field.value &&
this.field.value.length > 0) {
this.hasFile = true;
}
this.getMultipleFileParam();
}
removeFile(file: any) {
if (this.field) {
this.removeElementFromList(file);
}
}
onFileChanged(event: any) {
const files = event.target.files;
let filesSaved = [];
if (this.field.json.value) {
filesSaved = [...this.field.json.value];
}
if (files && files.length > 0) {
from(files)
.pipe(mergeMap((file) => this.uploadRawContent(file)))
.subscribe(
(res) => {
filesSaved.push(res);
},
() => {
this.logService.error('Error uploading file. See console output for more details.');
},
() => {
this.field.value = filesSaved;
this.field.json.value = filesSaved;
}
);
this.hasFile = true;
}
}
private uploadRawContent(file): Observable<any> {
return this.processContentService.createTemporaryRawRelatedContent(file).pipe(
map((response: any) => {
this.logService.info(response);
return response;
})
);
}
private getMultipleFileParam() {
if (this.field &&
this.field.params &&
this.field.params.multiple) {
this.multipleOption = this.field.params.multiple ? 'multiple' : '';
}
}
private removeElementFromList(file) {
const index = this.field.value.indexOf(file);
if (index !== -1) {
this.field.value.splice(index, 1);
this.field.json.value = this.field.value;
this.field.updateForm();
}
this.hasFile = this.field.value.length > 0;
this.resetFormValueWithNoFiles();
}
private resetFormValueWithNoFiles() {
if (this.field.value.length === 0) {
this.field.value = [];
this.field.json.value = [];
}
}
getIcon(mimeType) {
return this.thumbnailService.getMimeTypeIcon(mimeType);
}
fileClicked(contentLinkModel: any): void {
const file = new ContentLinkModel(contentLinkModel);
let fetch = this.processContentService.getContentPreview(file.id);
if (file.isTypeImage() || file.isTypePdf()) {
fetch = this.processContentService.getFileRawContent(file.id);
}
fetch.subscribe(
(blob: Blob) => {
file.contentBlob = blob;
this.formService.formContentClicked.next(file);
},
() => {
this.logService.error('Unable to send event for file ' + file.name);
}
);
}
}

View File

@ -20,5 +20,4 @@ export * from './form-error.event';
export * from './form-field.event'; export * from './form-field.event';
export * from './validate-form-field.event'; export * from './validate-form-field.event';
export * from './validate-form.event'; export * from './validate-form.event';
export * from './validate-dynamic-table-row.event';
export * from './form-rules.event'; export * from './form-rules.event';

View File

@ -31,8 +31,6 @@ import { MASK_DIRECTIVE, WIDGET_DIRECTIVES } from './components/widgets';
import { StartFormCustomButtonDirective } from './components/form-custom-button.directive'; import { StartFormCustomButtonDirective } from './components/form-custom-button.directive';
import { FormFieldComponent } from './components/form-field/form-field.component'; import { FormFieldComponent } from './components/form-field/form-field.component';
import { FormListComponent } from './components/form-list.component';
import { ContentWidgetComponent } from './components/widgets/content/content.widget';
import { WidgetComponent } from './components/widgets/widget.component'; import { WidgetComponent } from './components/widgets/widget.component';
import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core'; import { MatDatetimepickerModule, MatNativeDatetimeModule } from '@mat-datetimepicker/core';
import { FormRendererComponent } from './components/form-renderer.component'; import { FormRendererComponent } from './components/form-renderer.component';
@ -61,9 +59,7 @@ import { InplaceFormInputComponent } from './components/inplace-form-input/inpla
ViewerModule ViewerModule
], ],
declarations: [ declarations: [
ContentWidgetComponent,
FormFieldComponent, FormFieldComponent,
FormListComponent,
FormRendererComponent, FormRendererComponent,
StartFormCustomButtonDirective, StartFormCustomButtonDirective,
...WIDGET_DIRECTIVES, ...WIDGET_DIRECTIVES,
@ -72,9 +68,7 @@ import { InplaceFormInputComponent } from './components/inplace-form-input/inpla
InplaceFormInputComponent InplaceFormInputComponent
], ],
exports: [ exports: [
ContentWidgetComponent,
FormFieldComponent, FormFieldComponent,
FormListComponent,
FormRendererComponent, FormRendererComponent,
StartFormCustomButtonDirective, StartFormCustomButtonDirective,
...WIDGET_DIRECTIVES, ...WIDGET_DIRECTIVES,

View File

@ -17,21 +17,14 @@
export * from './components/form-field/form-field.component'; export * from './components/form-field/form-field.component';
export * from './components/form-base.component'; export * from './components/form-base.component';
export * from './components/form-list.component';
export * from './components/inplace-form-input/inplace-form-input.component'; export * from './components/inplace-form-input/inplace-form-input.component';
export * from './components/widgets/content/content.widget';
export * from './components/form-custom-button.directive'; export * from './components/form-custom-button.directive';
export * from './components/form-renderer.component'; export * from './components/form-renderer.component';
export * from './components/widgets'; export * from './components/widgets';
export * from './components/widgets/dynamic-table/dynamic-table-row.model';
export * from './services/activiti-alfresco.service';
export * from './services/ecm-model.service';
export * from './services/form-rendering.service'; export * from './services/form-rendering.service';
export * from './services/form.service'; export * from './services/form.service';
export * from './services/form-validation-service.interface'; export * from './services/form-validation-service.interface';
export * from './services/node.service';
export * from './services/process-content.service';
export * from './services/widget-visibility.service'; export * from './services/widget-visibility.service';
export * from './events'; export * from './events';
@ -39,3 +32,5 @@ export * from './events';
export * from './form-base.module'; export * from './form-base.module';
export * from './models/form-rules.model'; export * from './models/form-rules.model';
export * from './models/form-definition.model';
export * from './models/task-process-variable.model';

View File

@ -17,10 +17,8 @@
import { DynamicComponentResolver } from '../../../../index'; import { DynamicComponentResolver } from '../../../../index';
import { import {
FormFieldModel,
FormFieldTypes, FormFieldTypes,
UnknownWidgetComponent, UnknownWidgetComponent,
UploadWidgetComponent,
TextWidgetComponent, TextWidgetComponent,
JsonWidgetComponent, JsonWidgetComponent,
DisplayRichTextWidgetComponent DisplayRichTextWidgetComponent
@ -35,23 +33,6 @@ describe('FormRenderingService', () => {
service = new FormRenderingService(); service = new FormRenderingService();
}); });
it('should resolve Upload field as Upload widget', () => {
const field = new FormFieldModel(null, {
type: FormFieldTypes.UPLOAD,
params: {
link: null
}
});
const type = service.resolveComponentType(field);
expect(type).toBe(UploadWidgetComponent);
});
it('should resolve Upload widget for Upload field', () => {
const resolver = service.getComponentTypeResolver(FormFieldTypes.UPLOAD);
const type = resolver(null);
expect(type).toBe(UploadWidgetComponent);
});
it('should resolve Unknown widget for unknown field type', () => { it('should resolve Unknown widget for unknown field type', () => {
const resolver = service.getComponentTypeResolver('missing-type'); const resolver = service.getComponentTypeResolver('missing-type');
const type = resolver(null); const type = resolver(null);
@ -70,12 +51,6 @@ describe('FormRenderingService', () => {
expect(type).toBe(UnknownWidgetComponent); expect(type).toBe(UnknownWidgetComponent);
}); });
it('should fallback to custom resolver when field type missing', () => {
const resolver = service.getComponentTypeResolver(null, UploadWidgetComponent);
const type = resolver(null);
expect(type).toBe(UploadWidgetComponent);
});
it('should require field type to set resolver for type', () => { it('should require field type to set resolver for type', () => {
expect( expect(
() => service.setComponentTypeResolver( () => service.setComponentTypeResolver(
@ -120,10 +95,6 @@ describe('FormRenderingService', () => {
expect(service.resolveComponentType(null)).toBe(UnknownWidgetComponent); expect(service.resolveComponentType(null)).toBe(UnknownWidgetComponent);
}); });
it('should return custom value when resolving with no field', () => {
expect(service.resolveComponentType(null, UploadWidgetComponent)).toBe(UploadWidgetComponent);
});
it('should resolve Display Text Widget for JSON field type', () => { it('should resolve Display Text Widget for JSON field type', () => {
const resolver = service.getComponentTypeResolver('json'); const resolver = service.getComponentTypeResolver('json');
const type = resolver(null); const type = resolver(null);

View File

@ -32,20 +32,12 @@ export class FormRenderingService extends DynamicComponentMapper {
integer: DynamicComponentResolver.fromType(widgets.NumberWidgetComponent), integer: DynamicComponentResolver.fromType(widgets.NumberWidgetComponent),
'multi-line-text': DynamicComponentResolver.fromType(widgets.MultilineTextWidgetComponentComponent), 'multi-line-text': DynamicComponentResolver.fromType(widgets.MultilineTextWidgetComponentComponent),
boolean: DynamicComponentResolver.fromType(widgets.CheckboxWidgetComponent), boolean: DynamicComponentResolver.fromType(widgets.CheckboxWidgetComponent),
dropdown: DynamicComponentResolver.fromType(widgets.DropdownWidgetComponent),
date: DynamicComponentResolver.fromType(widgets.DateWidgetComponent), date: DynamicComponentResolver.fromType(widgets.DateWidgetComponent),
amount: DynamicComponentResolver.fromType(widgets.AmountWidgetComponent), amount: DynamicComponentResolver.fromType(widgets.AmountWidgetComponent),
'radio-buttons': DynamicComponentResolver.fromType(widgets.RadioButtonsWidgetComponent),
hyperlink: DynamicComponentResolver.fromType(widgets.HyperlinkWidgetComponent), hyperlink: DynamicComponentResolver.fromType(widgets.HyperlinkWidgetComponent),
'readonly-text': DynamicComponentResolver.fromType(widgets.DisplayTextWidgetComponent), 'readonly-text': DynamicComponentResolver.fromType(widgets.DisplayTextWidgetComponent),
json: DynamicComponentResolver.fromType(widgets.JsonWidgetComponent), json: DynamicComponentResolver.fromType(widgets.JsonWidgetComponent),
readonly: DynamicComponentResolver.fromType(widgets.TextWidgetComponent), readonly: DynamicComponentResolver.fromType(widgets.TextWidgetComponent),
typeahead: DynamicComponentResolver.fromType(widgets.TypeaheadWidgetComponent),
people: DynamicComponentResolver.fromType(widgets.PeopleWidgetComponent),
'functional-group': DynamicComponentResolver.fromType(widgets.FunctionalGroupWidgetComponent),
'dynamic-table': DynamicComponentResolver.fromType(widgets.DynamicTableWidgetComponent),
document: DynamicComponentResolver.fromType(widgets.DocumentWidgetComponent),
upload: DynamicComponentResolver.fromType(widgets.UploadWidgetComponent),
datetime: DynamicComponentResolver.fromType(widgets.DateTimeWidgetComponent), datetime: DynamicComponentResolver.fromType(widgets.DateTimeWidgetComponent),
'file-viewer': DynamicComponentResolver.fromType(widgets.FileViewerWidgetComponent), 'file-viewer': DynamicComponentResolver.fromType(widgets.FileViewerWidgetComponent),
'display-rich-text': DynamicComponentResolver.fromType(widgets.DisplayRichTextWidgetComponent) 'display-rich-text': DynamicComponentResolver.fromType(widgets.DisplayRichTextWidgetComponent)

View File

@ -15,40 +15,13 @@
* limitations under the License. * limitations under the License.
*/ */
import { fakeAsync, TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { formModelTabs } from '../../mock'; import { formModelTabs } from '../../mock';
import { FormService } from './form.service'; import { FormService } from './form.service';
import { setupTestBed } from '../../testing/setup-test-bed'; import { setupTestBed } from '../../testing/setup-test-bed';
import { CoreTestingModule } from '../../testing/core.testing.module'; import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
declare let jasmine: any;
const fakeGroupResponse = {
size: 2,
total: 2,
start: 0,
data: [{
id: '2004',
name: 'PEOPLE_GROUP',
externalId: null,
status: 'active',
groups: null
}, { id: 2005, name: 'PEOPLE_GROUP_2', externalId: null, status: 'active', groups: null }]
};
const fakePeopleResponse = {
size: 3,
total: 3,
start: 0,
data: [{ id: 2002, firstName: 'Peo', lastName: 'Ple', email: 'people' }, {
id: 2003,
firstName: 'Peo02',
lastName: 'Ple02',
email: 'people02'
}, { id: 2004, firstName: 'Peo03', lastName: 'Ple03', email: 'people03' }]
};
describe('Form service', () => { describe('Form service', () => {
let service: FormService; let service: FormService;
@ -62,289 +35,12 @@ describe('Form service', () => {
beforeEach(() => { beforeEach(() => {
service = TestBed.inject(FormService); service = TestBed.inject(FormService);
jasmine.Ajax.install();
}); });
afterEach(() => { afterEach(() => {
jasmine.Ajax.uninstall();
}); });
describe('Content tests', () => { describe('parseForm', () => {
const responseBody = {
data: [
{ id: '1' },
{ id: '2' }
]
};
const values = {
field1: 'one',
field2: 'two'
};
const simpleResponseBody = { id: 1, modelType: 'test' };
it('should fetch and parse process definitions', (done) => {
service.getProcessDefinitions().subscribe(() => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/process-definitions')).toBeTruthy();
expect([{ id: '1' }, { id: '2' }]).toEqual(JSON.parse(jasmine.Ajax.requests.mostRecent().response).data);
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(responseBody)
});
});
it('should fetch and parse tasks', (done) => {
service.getTasks().subscribe(() => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/tasks/query')).toBeTruthy();
expect([{ id: '1' }, { id: '2' }]).toEqual(JSON.parse(jasmine.Ajax.requests.mostRecent().response).data);
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(responseBody)
});
});
it('should fetch and parse the task by id', (done) => {
service.getTask('1').subscribe((result) => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/tasks/1')).toBeTruthy();
expect(result.id).toEqual('1');
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify({ id: '1' })
});
});
it('should save task form', (done) => {
service.saveTaskForm('1', values).subscribe(() => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1/save-form')).toBeTruthy();
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field1).toEqual(values.field1);
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field2).toEqual(values.field2);
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(responseBody)
});
});
it('should complete task form', (done) => {
service.completeTaskForm('1', values).subscribe(() => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1')).toBeTruthy();
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field1).toEqual(values.field1);
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field2).toEqual(values.field2);
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(responseBody)
});
});
it('should complete task form with a specific outcome', (done) => {
service.completeTaskForm('1', values, 'custom').subscribe(() => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1')).toBeTruthy();
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).values.field2).toEqual(values.field2);
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).outcome).toEqual('custom');
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(responseBody)
});
});
it('should get task form by id', (done) => {
service.getTaskForm('1').subscribe((result) => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/task-forms/1')).toBeTruthy();
expect(result.id).toEqual(1);
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify({ id: 1 })
});
});
it('should get form definition by id', (done) => {
service.getFormDefinitionById(1).subscribe((result) => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/form-models/1')).toBeTruthy();
expect(result.id).toEqual(1);
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify({ id: 1 })
});
});
it('should get form definition id by name', (done) => {
const formName = 'form1';
const formId = 1;
const response = {
data: [
{ id: formId }
]
};
service.getFormDefinitionByName(formName).subscribe((result) => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith(`models?filter=myReusableForms&filterText=${formName}&modelType=2`)).toBeTruthy();
expect(result).toEqual(formId);
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(response)
});
});
it('should handle error with generic message', () => {
service.handleError(null).subscribe(() => {
}, (error) => {
expect(error).toBe(FormService.UNKNOWN_ERROR_MESSAGE);
});
});
it('should handle error with error message', () => {
const message = '<error>';
service.handleError({ message }).subscribe(() => {
}, (error) => {
expect(error).toBe(message);
});
});
it('should handle error with detailed message', () => {
service.handleError({
status: '400',
statusText: 'Bad request'
}).subscribe(
() => {
},
(error) => {
expect(error).toBe('400 - Bad request');
});
});
it('should handle error with generic message', () => {
service.handleError({}).subscribe(() => {
}, (error) => {
expect(error).toBe(FormService.GENERIC_ERROR_MESSAGE);
});
});
it('should get all the forms with modelType=2', (done) => {
service.getForms().subscribe((result) => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('models?modelType=2')).toBeTruthy();
expect(result.length).toEqual(2);
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify({
data: [
{ name: 'FakeName-1', lastUpdatedByFullName: 'FakeUser-1', lastUpdated: '2017-01-02' },
{ name: 'FakeName-2', lastUpdatedByFullName: 'FakeUser-2', lastUpdated: '2017-01-03' }
]
})
});
});
it('should search for Form with modelType=2', (done) => {
const response = { data: [{ id: 1, name: 'findMe' }, { id: 2, name: 'testForm' }] };
service.searchFrom('findMe').subscribe((result) => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('models?modelType=2')).toBeTruthy();
expect(result.name).toEqual('findMe');
expect(result.id).toEqual(1);
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(response)
});
});
it('should create a Form with modelType=2', (done) => {
service.createForm('testName').subscribe(() => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('/models')).toBeTruthy();
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).modelType).toEqual(2);
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).name).toEqual('testName');
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(simpleResponseBody)
});
});
it('should return list of people', (done) => {
spyOn(service, 'getUserProfileImageApi').and.returnValue('/app/rest/users/2002/picture');
const fakeFilter: string = 'whatever';
service.getWorkflowUsers(fakeFilter).subscribe((result) => {
expect(result).toBeDefined();
expect(result.length).toBe(3);
expect(result[0].id).toBe(2002);
expect(result[0].firstName).toBe('Peo');
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(fakePeopleResponse)
});
});
it('should return list of groups', (done) => {
const fakeFilter: string = 'whatever';
service.getWorkflowGroups(fakeFilter).subscribe((result) => {
expect(result).toBeDefined();
expect(result.length).toBe(2);
expect(result[0].id).toBe('2004');
expect(result[0].name).toBe('PEOPLE_GROUP');
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(fakeGroupResponse)
});
});
it('should parse a Form Definition with tabs', () => { it('should parse a Form Definition with tabs', () => {
expect(formModelTabs.formRepresentation.formDefinition).toBeDefined(); expect(formModelTabs.formRepresentation.formDefinition).toBeDefined();
@ -352,60 +48,5 @@ describe('Form service', () => {
expect(formParsed).toBeDefined(); expect(formParsed).toBeDefined();
}); });
it('should create a Form form a Node', fakeAsync(() => {
const nameForm = 'testNode';
const formId = 100;
const stubCreateForm = () => {
jasmine.Ajax.stubRequest(
'http://localhost:9876/bpm/activiti-app/api/enterprise/models'
).andReturn({
status: 200,
statusText: 'HTTP/1.1 200 OK',
contentType: 'text/xml;charset=UTF-8',
responseText: { id: formId, name: 'test', lastUpdatedByFullName: 'uset', lastUpdated: '12-12-2016' }
});
};
const stubGetEcmModel = () => {
jasmine.Ajax.stubRequest(
'http://localhost:9876/ecm/alfresco/api/-default-/private/alfresco/versions/1/cmm/activitiFormsModel/types'
).andReturn({
status: 200,
statusText: 'HTTP/1.1 200 OK',
contentType: 'text/xml;charset=UTF-8',
responseText: {
list: {
entries: [{
entry: {
prefixedName: nameForm,
title: nameForm,
properties: [{ name: 'name' }, { name: 'email' }]
}
}, { entry: { prefixedName: 'notme', title: 'notme' } }]
}
}
});
};
const stubAddFieldsToAForm = () => {
jasmine.Ajax.stubRequest(
'http://localhost:9876/bpm/activiti-app/api/enterprise/editor/form-models/' + formId
).andReturn({
status: 200,
statusText: 'HTTP/1.1 200 OK',
contentType: 'text/xml;charset=UTF-8',
responseText: { id: formId, name: 'test', lastUpdatedByFullName: 'user', lastUpdated: '12-12-2016' }
});
};
stubCreateForm();
stubGetEcmModel();
stubAddFieldsToAForm();
service.createFormFromANode(nameForm).subscribe((result) => {
expect(result.id).toEqual(formId);
});
}));
}); });
}); });

View File

@ -15,29 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { LogService } from '../../services/log.service';
import { UserProcessModel } from '../../models';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, Subject, from, of, throwError } from 'rxjs'; import { Subject } from 'rxjs';
import { FormDefinitionModel } from '../models/form-definition.model';
import { ContentLinkModel } from '../components/widgets/core/content-link.model'; import { ContentLinkModel } from '../components/widgets/core/content-link.model';
import { GroupModel } from '../components/widgets/core/group.model';
import { EcmModelService } from './ecm-model.service';
import { map, catchError, switchMap, combineAll, defaultIfEmpty } from 'rxjs/operators';
import {
CompleteFormRepresentation,
ModelsApi,
ProcessInstanceVariablesApi,
SaveFormRepresentation,
TasksApi,
TaskFormsApi,
ProcessInstancesApi,
FormModelsApi,
ProcessDefinitionsApi,
UsersApi,
ActivitiGroupsApi
} from '@alfresco/js-api';
import { FormOutcomeEvent } from '../components/widgets/core/form-outcome-event.model'; import { FormOutcomeEvent } from '../components/widgets/core/form-outcome-event.model';
import { FormValues } from '../components/widgets/core/form-values'; import { FormValues } from '../components/widgets/core/form-values';
import { FormModel } from '../components/widgets/core/form.model'; import { FormModel } from '../components/widgets/core/form.model';
@ -47,7 +27,6 @@ import { FormFieldEvent } from '../events/form-field.event';
import { FormErrorEvent } from '../events/form-error.event'; import { FormErrorEvent } from '../events/form-error.event';
import { ValidateFormEvent } from '../events/validate-form.event'; import { ValidateFormEvent } from '../events/validate-form.event';
import { ValidateFormFieldEvent } from '../events/validate-form-field.event'; import { ValidateFormFieldEvent } from '../events/validate-form-field.event';
import { ValidateDynamicTableRowEvent } from '../events/validate-dynamic-table-row.event';
import { FormValidationService } from './form-validation-service.interface'; import { FormValidationService } from './form-validation-service.interface';
import { FormRulesEvent } from '../events/form-rules.event'; import { FormRulesEvent } from '../events/form-rules.event';
@ -56,63 +35,6 @@ import { FormRulesEvent } from '../events/form-rules.event';
}) })
export class FormService implements FormValidationService { export class FormService implements FormValidationService {
static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error';
static GENERIC_ERROR_MESSAGE: string = 'Server error';
_taskFormsApi: TaskFormsApi;
get taskFormsApi(): TaskFormsApi {
this._taskFormsApi = this._taskFormsApi ?? new TaskFormsApi(this.apiService.getInstance());
return this._taskFormsApi;
}
_taskApi: TasksApi;
get taskApi(): TasksApi {
this._taskApi = this._taskApi ?? new TasksApi(this.apiService.getInstance());
return this._taskApi;
}
_modelsApi: ModelsApi;
get modelsApi(): ModelsApi {
this._modelsApi = this._modelsApi ?? new ModelsApi(this.apiService.getInstance());
return this._modelsApi;
}
_editorApi: FormModelsApi;
get editorApi(): FormModelsApi {
this._editorApi = this._editorApi ?? new FormModelsApi(this.apiService.getInstance());
return this._editorApi;
}
_processDefinitionsApi: ProcessDefinitionsApi;
get processDefinitionsApi(): ProcessDefinitionsApi {
this._processDefinitionsApi = this._processDefinitionsApi ?? new ProcessDefinitionsApi(this.apiService.getInstance());
return this._processDefinitionsApi;
}
_processInstanceVariablesApi: ProcessInstanceVariablesApi;
get processInstanceVariablesApi(): ProcessInstanceVariablesApi {
this._processInstanceVariablesApi = this._processInstanceVariablesApi ?? new ProcessInstanceVariablesApi(this.apiService.getInstance());
return this._processInstanceVariablesApi;
}
_processInstancesApi: ProcessInstancesApi;
get processInstancesApi(): ProcessInstancesApi {
this._processInstancesApi = this._processInstancesApi ?? new ProcessInstancesApi(this.apiService.getInstance());
return this._processInstancesApi;
}
_groupsApi: ActivitiGroupsApi;
get groupsApi(): ActivitiGroupsApi {
this._groupsApi = this._groupsApi ?? new ActivitiGroupsApi(this.apiService.getInstance());
return this._groupsApi;
}
_usersApi: UsersApi;
get usersApi(): UsersApi {
this._usersApi = this._usersApi ?? new UsersApi(this.apiService.getInstance());
return this._usersApi;
}
formLoaded = new Subject<FormEvent>(); formLoaded = new Subject<FormEvent>();
formDataRefreshed = new Subject<FormEvent>(); formDataRefreshed = new Subject<FormEvent>();
formFieldValueChanged = new Subject<FormFieldEvent>(); formFieldValueChanged = new Subject<FormFieldEvent>();
@ -125,7 +47,7 @@ export class FormService implements FormValidationService {
validateForm = new Subject<ValidateFormEvent>(); validateForm = new Subject<ValidateFormEvent>();
validateFormField = new Subject<ValidateFormFieldEvent>(); validateFormField = new Subject<ValidateFormFieldEvent>();
validateDynamicTableRow = new Subject<ValidateDynamicTableRowEvent>(); validateDynamicTableRow = new Subject<FormFieldEvent>();
executeOutcome = new Subject<FormOutcomeEvent>(); executeOutcome = new Subject<FormOutcomeEvent>();
@ -133,9 +55,7 @@ export class FormService implements FormValidationService {
formRulesEvent = new Subject<FormRulesEvent>(); formRulesEvent = new Subject<FormRulesEvent>();
constructor(private ecmModelService: EcmModelService, constructor() {
private apiService: AlfrescoApiService,
protected logService: LogService) {
} }
/** /**
@ -163,449 +83,4 @@ export class FormService implements FormValidationService {
} }
return null; return null;
} }
/**
* Creates a Form with a field for each metadata property.
*
* @param formName Name of the new form
* @returns The new form
*/
createFormFromANode(formName: string): Observable<any> {
return new Observable((observer) => {
this.createForm(formName).subscribe(
(form) => {
this.ecmModelService.searchEcmType(formName, EcmModelService.MODEL_NAME).subscribe(
(customType) => {
const formDefinitionModel = new FormDefinitionModel(form.id, form.name, form.lastUpdatedByFullName, form.lastUpdated, customType.entry.properties);
from(
this.editorApi.saveForm(form.id, formDefinitionModel)
).subscribe((formData) => {
observer.next(formData);
observer.complete();
}, (err) => this.handleError(err));
},
(err) => this.handleError(err));
},
(err) => this.handleError(err));
});
}
/**
* Create a Form.
*
* @param formName Name of the new form
* @returns The new form
*/
createForm(formName: string): Observable<any> {
const dataModel = {
name: formName,
description: '',
modelType: 2,
stencilSet: 0
};
return from(
this.modelsApi.createModel(dataModel)
);
}
/**
* Saves a form.
*
* @param formId ID of the form to save
* @param formModel Model data for the form
* @returns Data for the saved form
*/
saveForm(formId: number, formModel: FormDefinitionModel): Observable<any> {
return from(
this.editorApi.saveForm(formId, formModel)
);
}
/**
* Searches for a form by name.
*
* @param name The form name to search for
* @returns Form model(s) matching the search name
*/
searchFrom(name: string): Observable<any> {
const opts = {
modelType: 2
};
return from(
this.modelsApi.getModels(opts)
)
.pipe(
map((forms: any) => forms.data.find((formData) => formData.name === name)),
catchError((err) => this.handleError(err))
);
}
/**
* Gets all the forms.
*
* @returns List of form models
*/
getForms(): Observable<any> {
const opts = {
modelType: 2
};
return from(this.modelsApi.getModels(opts))
.pipe(
map(this.toJsonArray),
catchError((err) => this.handleError(err))
);
}
/**
* Gets process definitions.
*
* @returns List of process definitions
*/
getProcessDefinitions(): Observable<any> {
return from(this.processDefinitionsApi.getProcessDefinitions({}))
.pipe(
map(this.toJsonArray),
catchError((err) => this.handleError(err))
);
}
/**
* Gets instance variables for a process.
*
* @param processInstanceId ID of the target process
* @returns List of instance variable information
*/
getProcessVariablesById(processInstanceId: string): Observable<any[]> {
return from(this.processInstanceVariablesApi.getProcessInstanceVariables(processInstanceId))
.pipe(
map(this.toJson),
catchError((err) => this.handleError(err))
);
}
/**
* Gets all the tasks.
*
* @returns List of tasks
*/
getTasks(): Observable<any> {
return from(this.taskApi.listTasks({}))
.pipe(
map(this.toJsonArray),
catchError((err) => this.handleError(err))
);
}
/**
* Gets a task.
*
* @param taskId Task Id
* @returns Task info
*/
getTask(taskId: string): Observable<any> {
return from(this.taskApi.getTask(taskId))
.pipe(
map(this.toJson),
catchError((err) => this.handleError(err))
);
}
/**
* Saves a task form.
*
* @param taskId Task Id
* @param formValues Form Values
* @returns Null response when the operation is complete
*/
saveTaskForm(taskId: string, formValues: FormValues): Observable<any> {
const saveFormRepresentation = { values: formValues } as SaveFormRepresentation;
return from(this.taskFormsApi.saveTaskForm(taskId, saveFormRepresentation))
.pipe(
catchError((err) => this.handleError(err))
);
}
/**
* Completes a Task Form.
*
* @param taskId Task Id
* @param formValues Form Values
* @param outcome Form Outcome
* @returns Null response when the operation is complete
*/
completeTaskForm(taskId: string, formValues: FormValues, outcome?: string): Observable<any> {
const completeFormRepresentation = { values: formValues } as CompleteFormRepresentation;
if (outcome) {
completeFormRepresentation.outcome = outcome;
}
return from(this.taskFormsApi.completeTaskForm(taskId, completeFormRepresentation))
.pipe(
catchError((err) => this.handleError(err))
);
}
/**
* Gets a form related to a task.
*
* @param taskId ID of the target task
* @returns Form definition
*/
getTaskForm(taskId: string): Observable<any> {
return from(this.taskFormsApi.getTaskForm(taskId))
.pipe(
map(this.toJson),
catchError((err) => this.handleError(err))
);
}
/**
* Gets a form definition.
*
* @param formId ID of the target form
* @returns Form definition
*/
getFormDefinitionById(formId: number): Observable<any> {
return from(this.editorApi.getForm(formId))
.pipe(
map(this.toJson),
catchError((err) => this.handleError(err))
);
}
/**
* Gets the form definition with a given name.
*
* @param name The form name
* @returns Form definition
*/
getFormDefinitionByName(name: string): Observable<any> {
const opts = {
filter: 'myReusableForms',
filterText: name,
modelType: 2
};
return from(this.modelsApi.getModels(opts))
.pipe(
map(this.getFormId),
catchError((err) => this.handleError(err))
);
}
/**
* Gets the start form instance for a given process.
*
* @param processId Process definition ID
* @returns Form definition
*/
getStartFormInstance(processId: string): Observable<any> {
return from(this.processInstancesApi.getProcessInstanceStartForm(processId))
.pipe(
map(this.toJson),
catchError((err) => this.handleError(err))
);
}
/**
* Gets a process instance.
*
* @param processId ID of the process to get
* @returns Process instance
*/
getProcessInstance(processId: string): Observable<any> {
return from(this.processInstancesApi.getProcessInstance(processId))
.pipe(
map(this.toJson),
catchError((err) => this.handleError(err))
);
}
/**
* Gets the start form definition for a given process.
*
* @param processId Process definition ID
* @returns Form definition
*/
getStartFormDefinition(processId: string): Observable<any> {
return from(this.processDefinitionsApi.getProcessDefinitionStartForm(processId))
.pipe(
map(this.toJson),
catchError((err) => this.handleError(err))
);
}
/**
* Gets values of fields populated by a REST backend.
*
* @param taskId Task identifier
* @param field Field identifier
* @returns Field values
*/
getRestFieldValues(taskId: string, field: string): Observable<any> {
return from(this.taskFormsApi.getRestFieldValues(taskId, field))
.pipe(
catchError((err) => this.handleError(err))
);
}
/**
* Gets values of fields populated by a REST backend using a process ID.
*
* @param processDefinitionId Process identifier
* @param field Field identifier
* @returns Field values
*/
getRestFieldValuesByProcessId(processDefinitionId: string, field: string): Observable<any> {
return from(this.processDefinitionsApi.getRestFieldValues(processDefinitionId, field))
.pipe(
catchError((err) => this.handleError(err))
);
}
/**
* Gets column values of fields populated by a REST backend using a process ID.
*
* @param processDefinitionId Process identifier
* @param field Field identifier
* @param column Column identifier
* @returns Field values
*/
getRestFieldValuesColumnByProcessId(processDefinitionId: string, field: string, column?: string): Observable<any> {
return from(this.processDefinitionsApi.getRestTableFieldValues(processDefinitionId, field, column))
.pipe(
catchError((err) => this.handleError(err))
);
}
/**
* Gets column values of fields populated by a REST backend.
*
* @param taskId Task identifier
* @param field Field identifier
* @param column Column identifier
* @returns Field values
*/
getRestFieldValuesColumn(taskId: string, field: string, column?: string): Observable<any> {
return from(this.taskFormsApi.getRestFieldColumnValues(taskId, field, column))
.pipe(
catchError((err) => this.handleError(err))
);
}
/**
* Returns a URL for the profile picture of a user.
*
* @param userId ID of the target user
* @returns URL string
*/
getUserProfileImageApi(userId: string): string {
return this.usersApi.getUserProfilePictureUrl(userId);
}
/**
* Gets a list of workflow users.
*
* @param filter Filter to select specific users
* @param groupId Group ID for the search
* @returns Array of users
*/
getWorkflowUsers(filter: string, groupId?: string): Observable<UserProcessModel[]> {
const option: any = { filter };
if (groupId) {
option.groupId = groupId;
}
return from(this.usersApi.getUsers(option))
.pipe(
switchMap(response => response.data as UserProcessModel[] || []),
map((user) => {
user.userImage = this.getUserProfileImageApi(user.id.toString());
return of(user);
}),
combineAll(),
defaultIfEmpty([]),
catchError((err) => this.handleError(err))
);
}
/**
* Gets a list of groups in a workflow.
*
* @param filter Filter to select specific groups
* @param groupId Group ID for the search
* @returns Array of groups
*/
getWorkflowGroups(filter: string, groupId?: string): Observable<GroupModel[]> {
const option: any = { filter };
if (groupId) {
option.groupId = groupId;
}
return from(this.groupsApi.getGroups(option))
.pipe(
map((response: any) => response.data || []),
catchError((err) => this.handleError(err))
);
}
/**
* Gets the ID of a form.
*
* @param form Object representing a form
* @returns ID string
*/
getFormId(form: any): string {
let result = null;
if (form && form.data && form.data.length > 0) {
result = form.data[0].id;
}
return result;
}
/**
* Creates a JSON representation of form data.
*
* @param res Object representing form data
* @returns JSON data
*/
toJson(res: any) {
if (res) {
return res || {};
}
return {};
}
/**
* Creates a JSON array representation of form data.
*
* @param res Object representing form data
* @returns JSON data
*/
toJsonArray(res: any) {
if (res) {
return res.data || [];
}
return [];
}
/**
* Reports an error message.
*
* @param error Data object with optional `message` and `status` fields for the error
* @returns Error message
*/
handleError(error: any): Observable<any> {
let errMsg = FormService.UNKNOWN_ERROR_MESSAGE;
if (error) {
errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : FormService.GENERIC_ERROR_MESSAGE;
}
this.logService.error(errMsg);
return throwError(errMsg);
}
} }

View File

@ -1,171 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 { TestBed } from '@angular/core/testing';
import { NodeMetadata } from '../../models/node-metadata.model';
import { EcmModelService } from './ecm-model.service';
import { NodeService } from './node.service';
import { setupTestBed } from '../../testing/setup-test-bed';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
declare let jasmine: any;
describe('NodeService', () => {
let service: NodeService;
setupTestBed({
imports: [
TranslateModule.forRoot(),
CoreTestingModule
]
});
beforeEach(() => {
service = TestBed.inject(NodeService);
});
beforeEach(() => {
jasmine.Ajax.install();
});
afterEach(() => {
jasmine.Ajax.uninstall();
});
it('Should fetch and node metadata', (done) => {
const responseBody = {
entry: {
id: '111-222-33-44-1123',
nodeType: 'typeTest',
properties: {
test: 'test',
testdata: 'testdata'
}
}
};
service.getNodeMetadata('-nodeid-').subscribe((result) => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('nodes/-nodeid-')).toBeTruthy();
const node = new NodeMetadata({
test: 'test',
testdata: 'testdata'
}, 'typeTest');
expect(result).toEqual(node);
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(responseBody)
});
});
it('Should clean the metadata from :', (done) => {
const responseBody = {
entry: {
id: '111-222-33-44-1123',
nodeType: 'typeTest',
properties: {
'metadata:test': 'test',
'metadata:testdata': 'testdata'
}
}
};
service.getNodeMetadata('-nodeid-').subscribe((result) => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('nodes/-nodeid-')).toBeTruthy();
const node = new NodeMetadata({
test: 'test',
testdata: 'testdata'
}, 'typeTest');
expect(result).toEqual(node);
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(responseBody)
});
});
it('Should create a node with metadata', (done) => {
const data = {
test: 'test',
testdata: 'testdata'
};
const responseBody = {
id: 'a74d91fb-ea8a-4812-ad98-ad878366b5be',
isFile: false,
isFolder: true
};
service.createNodeMetadata('typeTest', EcmModelService.MODEL_NAMESPACE, data, '/Sites/swsdp/documentLibrary', 'testNode').subscribe(() => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('-root-/children')).toBeTruthy();
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify(responseBody)
});
});
it('Should add activitiForms suffix to the metadata properties', (done) => {
const data = {
test: 'test',
testdata: 'testdata'
};
service.createNodeMetadata('typeTest', EcmModelService.MODEL_NAMESPACE, data, '/Sites/swsdp/documentLibrary').subscribe(() => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('-root-/children')).toBeTruthy();
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).properties[EcmModelService.MODEL_NAMESPACE + ':test']).toBeDefined();
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).properties[EcmModelService.MODEL_NAMESPACE + ':testdata']).toBeDefined();
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify({})
});
});
it('Should assign an UUID to the name when name not passed', (done) => {
const data = {
test: 'test',
testdata: 'testdata'
};
service.createNodeMetadata('typeTest', EcmModelService.MODEL_NAMESPACE, data, '/Sites/swsdp/documentLibrary').subscribe(() => {
expect(jasmine.Ajax.requests.mostRecent().url.endsWith('-root-/children')).toBeTruthy();
expect(JSON.parse(jasmine.Ajax.requests.mostRecent().params).name).toBeDefined();
done();
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'application/json',
responseText: JSON.stringify({})
});
});
});

View File

@ -1,70 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { NodeEntry } from '@alfresco/js-api';
import { NodeMetadata } from '../../models/node-metadata.model';
import { NodesApiService } from '../../services/nodes-api.service';
@Injectable({
providedIn: 'root'
})
/**
* @deprecated in 3.8.0, use NodesApiService instead.
*/
export class NodeService {
constructor(private nodesApiService: NodesApiService) {}
/**
* @deprecated in 3.8.0, use NodesApiService instead.
* Get the metadata and the nodeType for a nodeId cleaned by the prefix.
* @param nodeId ID of the target node
* @returns Node metadata
*/
public getNodeMetadata(nodeId: string): Observable<NodeMetadata> {
return this.nodesApiService.getNodeMetadata(nodeId);
}
/**
* @deprecated in 3.8.0, use NodesApiService instead.
* Create a new Node from form metadata.
* @param path Path to the node
* @param nodeType Node type
* @param name Node name
* @param nameSpace Namespace for properties
* @param data Property data to store in the node under namespace
* @returns The created node
*/
public createNodeMetadata(nodeType: string, nameSpace: any, data: any, path: string, name?: string): Observable<NodeEntry> {
return this.nodesApiService.createNodeMetadata(nodeType, nameSpace, data, path, name);
}
/**
* @deprecated in 3.8.0, use `createNodeInsideRoot` method from NodesApiService instead.
* Create a new Node from form metadata
* @param name Node name
* @param nodeType Node type
* @param properties Node body properties
* @param path Path to the node
* @returns The created node
*/
public createNode(name: string, nodeType: string, properties: any, path: string): Observable<NodeEntry> {
return this.nodesApiService.createNodeInsideRoot(name, nodeType, properties, path);
}
}

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { fakeAsync, TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { import {
ContainerModel, ContainerModel,
FormFieldModel, FormFieldModel,
@ -24,12 +24,11 @@ import {
TabModel, TabModel,
FormOutcomeModel FormOutcomeModel
} from '../components/widgets/core'; } from '../components/widgets/core';
import { TaskProcessVariableModel } from '../models/task-process-variable.model';
import { WidgetVisibilityModel, WidgetTypeEnum } from '../models/widget-visibility.model'; import { WidgetVisibilityModel, WidgetTypeEnum } from '../models/widget-visibility.model';
import { WidgetVisibilityService } from './widget-visibility.service'; import { WidgetVisibilityService } from './widget-visibility.service';
import { setupTestBed } from '../../testing/setup-test-bed'; import { setupTestBed } from '../../testing/setup-test-bed';
import { import {
fakeFormJson, fakeTaskProcessVariableModels, fakeFormJson,
formTest, formValues, complexVisibilityJsonVisible, formTest, formValues, complexVisibilityJsonVisible,
nextConditionForm, complexVisibilityJsonNotVisible, nextConditionForm, complexVisibilityJsonNotVisible,
headerVisibilityCond headerVisibilityCond
@ -156,160 +155,6 @@ describe('WidgetVisibilityCloudService', () => {
}); });
}); });
describe('should retrieve the process variables', () => {
const fakeFormWithField = new FormModel(fakeFormJson);
let visibilityObjTest: WidgetVisibilityModel;
const chainedVisibilityObj = new WidgetVisibilityModel({});
beforeEach(() => {
visibilityObjTest = new WidgetVisibilityModel({});
});
it('should return the process variables for task', (done) => {
service.getTaskProcessVariable('9999').subscribe(
(res) => {
expect(res).toBeDefined();
expect(res.length).toEqual(3);
expect(res[0].id).toEqual('TEST_VAR_1');
expect(res[0].type).toEqual('string');
expect(res[0].value).toEqual('test_value_1');
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: fakeTaskProcessVariableModels
});
});
it('should be able to retrieve the value of a process variable', (done) => {
service.getTaskProcessVariable('9999').subscribe(
(res: TaskProcessVariableModel[]) => {
expect(res).toBeDefined();
const varValue = service.getVariableValue(formTest, 'TEST_VAR_1', res);
expect(varValue).not.toBeUndefined();
expect(varValue).toBe('test_value_1');
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: fakeTaskProcessVariableModels
});
});
it('should return undefined if the variable does not exist', (done) => {
service.getTaskProcessVariable('9999').subscribe(
(res: TaskProcessVariableModel[]) => {
const varValue = service.getVariableValue(formTest, 'TEST_MYSTERY_VAR', res);
expect(varValue).toBeUndefined();
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: fakeTaskProcessVariableModels
});
});
it('should retrieve the value for the right field when it is a process variable', (done) => {
service.getTaskProcessVariable('9999').subscribe(
() => {
visibilityObjTest.rightValue = 'test_value_2';
spyOn(service, 'isFormFieldValid').and.returnValue(true);
const rightValue = service.getRightValue(formTest, visibilityObjTest);
expect(rightValue).not.toBeNull();
expect(rightValue).toBe('test_value_2');
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: fakeTaskProcessVariableModels
});
});
it('should retrieve the value for the left field when it is a process variable', (done) => {
service.getTaskProcessVariable('9999').subscribe(
() => {
visibilityObjTest.leftValue = 'TEST_VAR_2';
visibilityObjTest.leftType = WidgetTypeEnum.field;
const leftValue = service.getLeftValue(formTest, visibilityObjTest);
expect(leftValue).not.toBeNull();
expect(leftValue).toBe('test_value_2');
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: fakeTaskProcessVariableModels
});
});
it('should evaluate the visibility for the field between form value and process var', (done) => {
service.getTaskProcessVariable('9999').subscribe(
() => {
visibilityObjTest.leftType = 'LEFT_FORM_FIELD_ID';
visibilityObjTest.operator = '!=';
visibilityObjTest.rightValue = 'TEST_VAR_2';
const isVisible = service.isFieldVisible(fakeFormWithField, visibilityObjTest);
expect(isVisible).toBeTruthy();
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: fakeTaskProcessVariableModels
});
});
it('should evaluate visibility with multiple conditions', (done) => {
service.getTaskProcessVariable('9999').subscribe(
() => {
visibilityObjTest.leftType = 'field';
visibilityObjTest.leftValue = 'TEST_VAR_2';
visibilityObjTest.operator = '!=';
visibilityObjTest.rightValue = 'TEST_VAR_2';
visibilityObjTest.nextConditionOperator = 'and';
chainedVisibilityObj.leftType = 'field';
chainedVisibilityObj.leftValue = 'TEST_VAR_2';
chainedVisibilityObj.operator = '!empty';
visibilityObjTest.nextCondition = chainedVisibilityObj;
const isVisible = service.isFieldVisible(fakeFormWithField, visibilityObjTest);
expect(isVisible).toBeTruthy();
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: fakeTaskProcessVariableModels
});
});
it('should catch error on 403 response', fakeAsync(() => {
service.getTaskProcessVariable('9999').subscribe(() => {
}, (errorMessage) => {
expect(errorMessage).toEqual('Error while performing a call - Server error');
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 403
});
}));
});
describe('should return the value of the field', () => { describe('should return the value of the field', () => {
let visibilityObjTest: WidgetVisibilityModel; let visibilityObjTest: WidgetVisibilityModel;
let fakeFormWithField = new FormModel(fakeFormJson); let fakeFormWithField = new FormModel(fakeFormJson);
@ -644,143 +489,6 @@ describe('WidgetVisibilityCloudService', () => {
expect(fakeFormWithField.outcomes[outcomeIndex].isVisible).toBeFalsy(); expect(fakeFormWithField.outcomes[outcomeIndex].isVisible).toBeFalsy();
}); });
it('should use the form value to evaluate the visibility condition if the form value is defined', (done) => {
service.getTaskProcessVariable('9999').subscribe(
(res: TaskProcessVariableModel[]) => {
expect(res).toBeDefined();
const varValue = service.getVariableValue(formTest, 'FIELD_FORM_EMPTY', res);
expect(varValue).not.toBeUndefined();
expect(varValue).toBe('PROCESS_RIGHT_FORM_FIELD_VALUE');
visibilityObjTest.leftType = WidgetTypeEnum.field;
visibilityObjTest.leftValue = 'FIELD_FORM_EMPTY';
visibilityObjTest.operator = '==';
visibilityObjTest.rightValue = 'RIGHT_FORM_FIELD_VALUE';
const myForm = new FormModel({
id: '9999',
name: 'FORM_PROCESS_VARIABLE_VISIBILITY',
processDefinitionId: 'PROCESS_TEST:9:9999',
processDefinitionName: 'PROCESS_TEST',
processDefinitionKey: 'PROCESS_TEST',
taskId: '999',
taskName: 'TEST',
fields: [
{
fieldType: 'ContainerRepresentation',
id: '000000000000000000',
name: 'Label',
type: 'container',
value: null,
numberOfColumns: 2,
fields: {
1: [
{
fieldType: 'FormFieldRepresentation',
id: 'FIELD_FORM_EMPTY',
name: 'FIELD_FORM_EMPTY',
type: 'text',
value: 'RIGHT_FORM_FIELD_VALUE',
visibilityCondition: null,
isVisible: true
},
{
fieldType: 'FormFieldRepresentation',
id: 'FIELD_FORM_WITH_CONDITION',
name: 'FIELD_FORM_WITH_CONDITION',
type: 'text',
value: 'field_form_with_condition_value',
visibilityCondition: visibilityObjTest,
isVisible: false
}
]
}
}
]
});
service.refreshVisibility(myForm);
const fieldWithVisibilityAttached = myForm.getFieldById('FIELD_FORM_WITH_CONDITION');
expect(fieldWithVisibilityAttached.isVisible).toBeTruthy();
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: [{ id: 'FIELD_FORM_EMPTY', type: 'string', value: 'PROCESS_RIGHT_FORM_FIELD_VALUE' }]
});
});
it('should use the process value to evaluate the True visibility condition if the form value is empty', (done) => {
service.getTaskProcessVariable('9999').subscribe(
(res: TaskProcessVariableModel[]) => {
expect(res).toBeDefined();
visibilityObjTest.leftType = WidgetTypeEnum.field;
visibilityObjTest.leftValue = 'FIELD_FORM_EMPTY';
visibilityObjTest.operator = '==';
visibilityObjTest.rightType = WidgetTypeEnum.value;
visibilityObjTest.rightValue = 'PROCESS_RIGHT_FORM_FIELD_VALUE';
const myForm = new FormModel({
id: '9999',
name: 'FORM_PROCESS_VARIABLE_VISIBILITY',
processDefinitionId: 'PROCESS_TEST:9:9999',
processDefinitionName: 'PROCESS_TEST',
processDefinitionKey: 'PROCESS_TEST',
taskId: '999',
taskName: 'TEST',
fields: [
{
fieldType: 'ContainerRepresentation',
id: '000000000000000000',
name: 'Label',
type: 'container',
value: null,
numberOfColumns: 2,
fields: {
1: [
{
fieldType: 'FormFieldRepresentation',
id: 'FIELD_FORM_EMPTY',
name: 'FIELD_FORM_EMPTY',
type: 'text',
value: '',
visibilityCondition: null,
isVisible: true
},
{
fieldType: 'FormFieldRepresentation',
id: 'FIELD_FORM_WITH_CONDITION',
name: 'FIELD_FORM_WITH_CONDITION',
type: 'text',
value: 'field_form_with_condition_value',
visibilityCondition: visibilityObjTest,
isVisible: false
}
]
}
}
]
});
service.refreshVisibility(myForm);
const fieldWithVisibilityAttached = myForm.getFieldById('FIELD_FORM_WITH_CONDITION');
expect(fieldWithVisibilityAttached.isVisible).toBeTruthy();
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: [{ id: 'FIELD_FORM_EMPTY', type: 'string', value: 'PROCESS_RIGHT_FORM_FIELD_VALUE' }]
});
});
it('should use the process variables when they are passed to check the visibility', () => { it('should use the process variables when they are passed to check the visibility', () => {
visibilityObjTest.leftType = WidgetTypeEnum.field; visibilityObjTest.leftType = WidgetTypeEnum.field;
@ -837,72 +545,6 @@ describe('WidgetVisibilityCloudService', () => {
expect(fieldWithVisibilityAttached.isVisible).toBeTruthy(); expect(fieldWithVisibilityAttached.isVisible).toBeTruthy();
}); });
it('should use the process value to evaluate the False visibility condition if the form value is empty', (done) => {
service.getTaskProcessVariable('9999').subscribe(
(res: TaskProcessVariableModel[]) => {
expect(res).toBeDefined();
visibilityObjTest.leftType = 'FIELD_FORM_EMPTY';
visibilityObjTest.operator = '==';
visibilityObjTest.rightValue = 'RIGHT_FORM_FIELD_VALUE';
const myForm = new FormModel({
id: '9999',
name: 'FORM_PROCESS_VARIABLE_VISIBILITY',
processDefinitionId: 'PROCESS_TEST:9:9999',
processDefinitionName: 'PROCESS_TEST',
processDefinitionKey: 'PROCESS_TEST',
taskId: '999',
taskName: 'TEST',
fields: [
{
fieldType: 'ContainerRepresentation',
id: '000000000000000000',
name: 'Label',
type: 'container',
value: null,
numberOfColumns: 2,
fields: {
1: [
{
fieldType: 'FormFieldRepresentation',
id: 'FIELD_FORM_EMPTY',
name: 'FIELD_FORM_EMPTY',
type: 'text',
value: '',
visibilityCondition: null,
isVisible: true
},
{
fieldType: 'FormFieldRepresentation',
id: 'FIELD_FORM_WITH_CONDITION',
name: 'FIELD_FORM_WITH_CONDITION',
type: 'text',
value: 'field_form_with_condition_value',
visibilityCondition: visibilityObjTest,
isVisible: true
}
]
}
}
]
});
service.refreshVisibility(myForm);
const fieldWithVisibilityAttached = myForm.getFieldById('FIELD_FORM_WITH_CONDITION');
expect(fieldWithVisibilityAttached.isVisible).toBeFalsy();
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: [{ id: 'FIELD_FORM_EMPTY', type: 'string', value: 'PROCESS_RIGHT_FORM_FIELD_VALUE' }]
});
});
it('should refresh the visibility for single tab', () => { it('should refresh the visibility for single tab', () => {
visibilityObjTest.leftType = WidgetTypeEnum.field; visibilityObjTest.leftType = WidgetTypeEnum.field;
visibilityObjTest.leftValue = 'FIELD_TEST'; visibilityObjTest.leftValue = 'FIELD_TEST';

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { fakeAsync, TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { import {
ContainerModel, ContainerModel,
FormFieldModel, FormFieldModel,
@ -23,12 +23,10 @@ import {
FormModel, FormModel,
TabModel TabModel
} from '../components/widgets/core'; } from '../components/widgets/core';
import { TaskProcessVariableModel } from '../models/task-process-variable.model';
import { WidgetVisibilityModel } from '../models/widget-visibility.model'; import { WidgetVisibilityModel } from '../models/widget-visibility.model';
import { WidgetVisibilityService } from './widget-visibility.service'; import { WidgetVisibilityService } from './widget-visibility.service';
import { setupTestBed } from '../../testing/setup-test-bed'; import { setupTestBed } from '../../testing/setup-test-bed';
import { import {
fakeTaskProcessVariableModels,
fakeFormJson, formTest, fakeFormJson, formTest,
formValues, complexVisibilityJsonVisible, formValues, complexVisibilityJsonVisible,
complexVisibilityJsonNotVisible, tabVisibilityJsonMock, complexVisibilityJsonNotVisible, tabVisibilityJsonMock,
@ -39,8 +37,6 @@ import {
import { CoreTestingModule } from '../../testing/core.testing.module'; import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
declare let jasmine: any;
describe('WidgetVisibilityService', () => { describe('WidgetVisibilityService', () => {
let service: WidgetVisibilityService; let service: WidgetVisibilityService;
@ -56,11 +52,6 @@ describe('WidgetVisibilityService', () => {
beforeEach(() => { beforeEach(() => {
service = TestBed.inject(WidgetVisibilityService); service = TestBed.inject(WidgetVisibilityService);
jasmine.Ajax.install();
});
afterEach(() => {
jasmine.Ajax.uninstall();
}); });
describe('should be able to evaluate next condition operations', () => { describe('should be able to evaluate next condition operations', () => {
@ -161,158 +152,6 @@ describe('WidgetVisibilityService', () => {
}); });
}); });
describe('should retrieve the process variables', () => {
let fakeFormWithField: FormModel;
let visibilityObjTest: WidgetVisibilityModel;
const chainedVisibilityObj = new WidgetVisibilityModel({});
beforeEach(() => {
fakeFormWithField = new FormModel(fakeFormJson);
visibilityObjTest = new WidgetVisibilityModel({});
fakeFormWithField = new FormModel(fakeFormJson);
});
it('should return the process variables for task', (done) => {
service.getTaskProcessVariable('9999').subscribe(
(res) => {
expect(res).toBeDefined();
expect(res.length).toEqual(3);
expect(res[0].id).toEqual('TEST_VAR_1');
expect(res[0].type).toEqual('string');
expect(res[0].value).toEqual('test_value_1');
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: fakeTaskProcessVariableModels
});
});
it('should be able to retrieve the value of a process variable', (done) => {
service.getTaskProcessVariable('9999').subscribe(
(res: TaskProcessVariableModel[]) => {
expect(res).toBeDefined();
const varValue = service.getVariableValue(formTest, 'TEST_VAR_1', res);
expect(varValue).not.toBeUndefined();
expect(varValue).toBe('test_value_1');
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: fakeTaskProcessVariableModels
});
});
it('should return undefined if the variable does not exist', (done) => {
service.getTaskProcessVariable('9999').subscribe(
(res: TaskProcessVariableModel[]) => {
const varValue = service.getVariableValue(formTest, 'TEST_MYSTERY_VAR', res);
expect(varValue).toBeUndefined();
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: fakeTaskProcessVariableModels
});
});
it('should retrieve the value for the right field when it is a process variable', (done) => {
service.getTaskProcessVariable('9999').subscribe(
() => {
visibilityObjTest.rightRestResponseId = 'TEST_VAR_2';
const rightValue = service.getRightValue(formTest, visibilityObjTest);
expect(rightValue).not.toBeNull();
expect(rightValue).toBe('test_value_2');
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: fakeTaskProcessVariableModels
});
});
it('should retrieve the value for the left field when it is a process variable', (done) => {
service.getTaskProcessVariable('9999').subscribe(
() => {
visibilityObjTest.leftRestResponseId = 'TEST_VAR_2';
const leftValue = service.getLeftValue(formTest, visibilityObjTest);
expect(leftValue).not.toBeNull();
expect(leftValue).toBe('test_value_2');
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: fakeTaskProcessVariableModels
});
});
it('should evaluate the visibility for the field between form value and process var', (done) => {
service.getTaskProcessVariable('9999').subscribe(
() => {
visibilityObjTest.leftFormFieldId = 'LEFT_FORM_FIELD_ID';
visibilityObjTest.operator = '!=';
visibilityObjTest.rightRestResponseId = 'TEST_VAR_2';
const isVisible = service.isFieldVisible(fakeFormWithField, new WidgetVisibilityModel(visibilityObjTest));
expect(isVisible).toBeTruthy();
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: fakeTaskProcessVariableModels
});
});
it('should evaluate visibility with multiple conditions', (done) => {
service.getTaskProcessVariable('9999').subscribe(
() => {
visibilityObjTest.leftFormFieldId = 'LEFT_FORM_FIELD_ID';
visibilityObjTest.operator = '!=';
visibilityObjTest.rightRestResponseId = 'TEST_VAR_2';
visibilityObjTest.nextConditionOperator = 'and';
chainedVisibilityObj.leftRestResponseId = 'TEST_VAR_2';
chainedVisibilityObj.operator = '!empty';
visibilityObjTest.nextCondition = chainedVisibilityObj;
const isVisible = service.isFieldVisible(fakeFormWithField, visibilityObjTest);
expect(isVisible).toBeTruthy();
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: fakeTaskProcessVariableModels
});
});
it('should catch error on 403 response', fakeAsync(() => {
service.getTaskProcessVariable('9999').subscribe(() => {
}, (errorMessage) => {
expect(errorMessage).toEqual('Error while performing a call - Server error');
});
jasmine.Ajax.requests.mostRecent().respondWith({
status: 403
});
}));
});
describe('should return the value of the field', () => { describe('should return the value of the field', () => {
let visibilityObjTest: WidgetVisibilityModel; let visibilityObjTest: WidgetVisibilityModel;
let fakeFormWithField: FormModel; let fakeFormWithField: FormModel;
@ -650,206 +489,6 @@ describe('WidgetVisibilityService', () => {
expect(fakeFormWithField.tabs[0].isVisible).toBeFalsy(); expect(fakeFormWithField.tabs[0].isVisible).toBeFalsy();
}); });
it('should use the form value to evaluate the visibility condition if the form value is defined', (done) => {
service.getTaskProcessVariable('9999').subscribe(
(res: TaskProcessVariableModel[]) => {
expect(res).toBeDefined();
const varValue = service.getVariableValue(formTest, 'FIELD_FORM_EMPTY', res);
expect(varValue).not.toBeUndefined();
expect(varValue).toBe('PROCESS_RIGHT_FORM_FIELD_VALUE');
visibilityObjTest.leftFormFieldId = 'FIELD_FORM_EMPTY';
visibilityObjTest.operator = '==';
visibilityObjTest.rightValue = 'RIGHT_FORM_FIELD_VALUE';
const myForm = new FormModel({
id: '9999',
name: 'FORM_PROCESS_VARIABLE_VISIBILITY',
processDefinitionId: 'PROCESS_TEST:9:9999',
processDefinitionName: 'PROCESS_TEST',
processDefinitionKey: 'PROCESS_TEST',
taskId: '999',
taskName: 'TEST',
fields: [
{
fieldType: 'ContainerRepresentation',
id: '000000000000000000',
name: 'Label',
type: 'container',
value: null,
numberOfColumns: 2,
fields: {
1: [
{
fieldType: 'FormFieldRepresentation',
id: 'FIELD_FORM_EMPTY',
name: 'FIELD_FORM_EMPTY',
type: 'text',
value: 'RIGHT_FORM_FIELD_VALUE',
visibilityCondition: null,
isVisible: true
},
{
fieldType: 'FormFieldRepresentation',
id: 'FIELD_FORM_WITH_CONDITION',
name: 'FIELD_FORM_WITH_CONDITION',
type: 'text',
value: 'field_form_with_condition_value',
visibilityCondition: visibilityObjTest,
isVisible: false
}
]
}
}
]
});
service.refreshVisibility(myForm);
const fieldWithVisibilityAttached = myForm.getFieldById('FIELD_FORM_WITH_CONDITION');
expect(fieldWithVisibilityAttached.isVisible).toBeTruthy();
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: [{ id: 'FIELD_FORM_EMPTY', type: 'string', value: 'PROCESS_RIGHT_FORM_FIELD_VALUE' }]
});
});
it('should use the process value to evaluate the True visibility condition if the form value is empty', (done) => {
service.getTaskProcessVariable('9999').subscribe(
(res: TaskProcessVariableModel[]) => {
expect(res).toBeDefined();
visibilityObjTest.leftFormFieldId = 'FIELD_FORM_EMPTY';
visibilityObjTest.operator = '==';
visibilityObjTest.rightValue = 'PROCESS_RIGHT_FORM_FIELD_VALUE';
const myForm = new FormModel({
id: '9999',
name: 'FORM_PROCESS_VARIABLE_VISIBILITY',
processDefinitionId: 'PROCESS_TEST:9:9999',
processDefinitionName: 'PROCESS_TEST',
processDefinitionKey: 'PROCESS_TEST',
taskId: '999',
taskName: 'TEST',
fields: [
{
fieldType: 'ContainerRepresentation',
id: '000000000000000000',
name: 'Label',
type: 'container',
value: null,
numberOfColumns: 2,
fields: {
1: [
{
fieldType: 'FormFieldRepresentation',
id: 'FIELD_FORM_EMPTY',
name: 'FIELD_FORM_EMPTY',
type: 'text',
value: '',
visibilityCondition: null,
isVisible: true
},
{
fieldType: 'FormFieldRepresentation',
id: 'FIELD_FORM_WITH_CONDITION',
name: 'FIELD_FORM_WITH_CONDITION',
type: 'text',
value: 'field_form_with_condition_value',
visibilityCondition: visibilityObjTest,
isVisible: false
}
]
}
}
]
});
service.refreshVisibility(myForm);
const fieldWithVisibilityAttached = myForm.getFieldById('FIELD_FORM_WITH_CONDITION');
expect(fieldWithVisibilityAttached.isVisible).toBeTruthy();
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: [{ id: 'FIELD_FORM_EMPTY', type: 'string', value: 'PROCESS_RIGHT_FORM_FIELD_VALUE' }]
});
});
it('should use the process value to evaluate the False visibility condition if the form value is empty', (done) => {
service.getTaskProcessVariable('9999').subscribe(
(res: TaskProcessVariableModel[]) => {
expect(res).toBeDefined();
visibilityObjTest.leftFormFieldId = 'FIELD_FORM_EMPTY';
visibilityObjTest.operator = '==';
visibilityObjTest.rightValue = 'RIGHT_FORM_FIELD_VALUE';
const myForm = new FormModel({
id: '9999',
name: 'FORM_PROCESS_VARIABLE_VISIBILITY',
processDefinitionId: 'PROCESS_TEST:9:9999',
processDefinitionName: 'PROCESS_TEST',
processDefinitionKey: 'PROCESS_TEST',
taskId: '999',
taskName: 'TEST',
fields: [
{
fieldType: 'ContainerRepresentation',
id: '000000000000000000',
name: 'Label',
type: 'container',
value: null,
numberOfColumns: 2,
fields: {
1: [
{
fieldType: 'FormFieldRepresentation',
id: 'FIELD_FORM_EMPTY',
name: 'FIELD_FORM_EMPTY',
type: 'text',
value: '',
visibilityCondition: null,
isVisible: true
},
{
fieldType: 'FormFieldRepresentation',
id: 'FIELD_FORM_WITH_CONDITION',
name: 'FIELD_FORM_WITH_CONDITION',
type: 'text',
value: 'field_form_with_condition_value',
visibilityCondition: visibilityObjTest,
isVisible: true
}
]
}
}
]
});
service.refreshVisibility(myForm);
const fieldWithVisibilityAttached = myForm.getFieldById('FIELD_FORM_WITH_CONDITION');
expect(fieldWithVisibilityAttached.isVisible).toBeFalsy();
done();
}
);
jasmine.Ajax.requests.mostRecent().respondWith({
status: 200,
contentType: 'json',
responseText: [{ id: 'FIELD_FORM_EMPTY', type: 'string', value: 'PROCESS_RIGHT_FORM_FIELD_VALUE' }]
});
});
it('should refresh the visibility for single tab', () => { it('should refresh the visibility for single tab', () => {
visibilityObjTest.leftFormFieldId = 'FIELD_TEST'; visibilityObjTest.leftFormFieldId = 'FIELD_TEST';
visibilityObjTest.operator = '!='; visibilityObjTest.operator = '!=';

View File

@ -15,11 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { LogService } from '../../services/log.service'; import { LogService } from '../../services/log.service';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import moment from 'moment'; import moment from 'moment';
import { Observable, from, throwError } from 'rxjs';
import { import {
FormFieldModel, FormFieldModel,
FormModel, FormModel,
@ -29,25 +27,16 @@ import {
} from '../components/widgets/core'; } from '../components/widgets/core';
import { TaskProcessVariableModel } from '../models/task-process-variable.model'; import { TaskProcessVariableModel } from '../models/task-process-variable.model';
import { WidgetVisibilityModel, WidgetTypeEnum } from '../models/widget-visibility.model'; import { WidgetVisibilityModel, WidgetTypeEnum } from '../models/widget-visibility.model';
import { map, catchError } from 'rxjs/operators';
import { TaskFormsApi } from '@alfresco/js-api';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class WidgetVisibilityService { export class WidgetVisibilityService {
_taskFormsApi: TaskFormsApi;
get taskFormsApi(): TaskFormsApi {
this._taskFormsApi = this._taskFormsApi ?? new TaskFormsApi(this.apiService.getInstance());
return this._taskFormsApi;
}
private processVarList: TaskProcessVariableModel[]; private processVarList: TaskProcessVariableModel[];
private form: FormModel; private form: FormModel;
constructor(private apiService: AlfrescoApiService, constructor(private logService: LogService) {
private logService: LogService) {
} }
public refreshVisibility(form: FormModel, processVarList?: TaskProcessVariableModel[]) { public refreshVisibility(form: FormModel, processVarList?: TaskProcessVariableModel[]) {
@ -68,15 +57,15 @@ export class WidgetVisibilityService {
} }
} }
refreshEntityVisibility(element: FormFieldModel | TabModel) { public refreshEntityVisibility(element: FormFieldModel | TabModel) {
element.isVisible = this.isParentTabVisible(this.form, element) && this.evaluateVisibility(element.form, element.visibilityCondition); element.isVisible = this.isParentTabVisible(this.form, element) && this.evaluateVisibility(element.form, element.visibilityCondition);
} }
refreshOutcomeVisibility(element: FormOutcomeModel) { private refreshOutcomeVisibility(element: FormOutcomeModel) {
element.isVisible = this.evaluateVisibility(element.form, element.visibilityCondition); element.isVisible = this.evaluateVisibility(element.form, element.visibilityCondition);
} }
evaluateVisibility(form: FormModel, visibilityObj: WidgetVisibilityModel): boolean { public evaluateVisibility(form: FormModel, visibilityObj: WidgetVisibilityModel): boolean {
const isLeftFieldPresent = visibilityObj && (visibilityObj.leftType || visibilityObj.leftValue); const isLeftFieldPresent = visibilityObj && (visibilityObj.leftType || visibilityObj.leftValue);
if (!isLeftFieldPresent || isLeftFieldPresent === 'null') { if (!isLeftFieldPresent || isLeftFieldPresent === 'null') {
return true; return true;
@ -85,12 +74,12 @@ export class WidgetVisibilityService {
} }
} }
isFieldVisible(form: FormModel, visibilityObj: WidgetVisibilityModel, accumulator: any[] = [], result: boolean = false): boolean { public isFieldVisible(form: FormModel, visibilityObj: WidgetVisibilityModel, accumulator: any[] = [], result: boolean = false): boolean {
const leftValue = this.getLeftValue(form, visibilityObj); const leftValue = this.getLeftValue(form, visibilityObj);
const rightValue = this.getRightValue(form, visibilityObj); const rightValue = this.getRightValue(form, visibilityObj);
const actualResult = this.evaluateCondition(leftValue, rightValue, visibilityObj.operator); const actualResult = this.evaluateCondition(leftValue, rightValue, visibilityObj.operator);
accumulator.push({ value: actualResult, operator: visibilityObj.nextConditionOperator }); accumulator.push({value: actualResult, operator: visibilityObj.nextConditionOperator});
if (this.isValidCondition(visibilityObj.nextCondition)) { if (this.isValidCondition(visibilityObj.nextCondition)) {
result = this.isFieldVisible(form, visibilityObj.nextCondition, accumulator); result = this.isFieldVisible(form, visibilityObj.nextCondition, accumulator);
@ -124,7 +113,7 @@ export class WidgetVisibilityService {
} }
} }
getLeftValue(form: FormModel, visibilityObj: WidgetVisibilityModel): string { public getLeftValue(form: FormModel, visibilityObj: WidgetVisibilityModel): string {
let leftValue = ''; let leftValue = '';
if (visibilityObj.leftType && visibilityObj.leftType === WidgetTypeEnum.variable) { if (visibilityObj.leftType && visibilityObj.leftType === WidgetTypeEnum.variable) {
leftValue = this.getVariableValue(form, visibilityObj.leftValue, this.processVarList); leftValue = this.getVariableValue(form, visibilityObj.leftValue, this.processVarList);
@ -138,7 +127,7 @@ export class WidgetVisibilityService {
return leftValue; return leftValue;
} }
getRightValue(form: FormModel, visibilityObj: WidgetVisibilityModel): string { public getRightValue(form: FormModel, visibilityObj: WidgetVisibilityModel): string {
let valueFound = ''; let valueFound = '';
if (visibilityObj.rightType === WidgetTypeEnum.variable) { if (visibilityObj.rightType === WidgetTypeEnum.variable) {
valueFound = this.getVariableValue(form, visibilityObj.rightValue, this.processVarList); valueFound = this.getVariableValue(form, visibilityObj.rightValue, this.processVarList);
@ -154,7 +143,7 @@ export class WidgetVisibilityService {
return valueFound; return valueFound;
} }
getFormValue(form: FormModel, fieldId: string): any { public getFormValue(form: FormModel, fieldId: string): any {
const formField = this.getFormFieldById(form, fieldId); const formField = this.getFormFieldById(form, fieldId);
let value; let value;
@ -168,18 +157,18 @@ export class WidgetVisibilityService {
return value; return value;
} }
isFormFieldValid(formField: FormFieldModel): boolean { public isFormFieldValid(formField: FormFieldModel): boolean {
return formField && formField.isValid; return formField && formField.isValid;
} }
getFieldValue(valueList: any, fieldId: string): any { public getFieldValue(valueList: any, fieldId: string): any {
let labelFilterByName; let labelFilterByName;
let valueFound; let valueFound;
if (fieldId && fieldId.indexOf('_LABEL') > 0) { if (fieldId && fieldId.indexOf('_LABEL') > 0) {
labelFilterByName = fieldId.substring(0, fieldId.length - 6); labelFilterByName = fieldId.substring(0, fieldId.length - 6);
if (valueList[labelFilterByName]) { if (valueList[labelFilterByName]) {
if (Array.isArray(valueList[labelFilterByName])) { if (Array.isArray(valueList[labelFilterByName])) {
valueFound = valueList[labelFilterByName].map(({ name }) => name); valueFound = valueList[labelFilterByName].map(({name}) => name);
} else { } else {
valueFound = valueList[labelFilterByName].name; valueFound = valueList[labelFilterByName].name;
} }
@ -187,7 +176,7 @@ export class WidgetVisibilityService {
} else if (valueList[fieldId] && valueList[fieldId].id) { } else if (valueList[fieldId] && valueList[fieldId].id) {
valueFound = valueList[fieldId].id; valueFound = valueList[fieldId].id;
} else if (valueList[fieldId] && Array.isArray(valueList[fieldId])) { } else if (valueList[fieldId] && Array.isArray(valueList[fieldId])) {
valueFound = valueList[fieldId].map(({ id }) => id); valueFound = valueList[fieldId].map(({id}) => id);
} else { } else {
valueFound = valueList[fieldId]; valueFound = valueList[fieldId];
} }
@ -198,11 +187,11 @@ export class WidgetVisibilityService {
return value === undefined || value === null; return value === undefined || value === null;
} }
getFormFieldById(form: FormModel, fieldId: string): FormFieldModel { public getFormFieldById(form: FormModel, fieldId: string): FormFieldModel {
return form.getFormFields().find((formField: FormFieldModel) => this.isSearchedField(formField, fieldId)); return form.getFormFields().find((formField: FormFieldModel) => this.isSearchedField(formField, fieldId));
} }
searchValueInForm(formField: FormFieldModel, fieldId: string): string { public searchValueInForm(formField: FormFieldModel, fieldId: string): string {
let fieldValue = ''; let fieldValue = '';
if (formField) { if (formField) {
@ -219,7 +208,7 @@ export class WidgetVisibilityService {
return fieldValue; return fieldValue;
} }
isParentTabVisible(form: FormModel, currentFormField: FormFieldModel | TabModel): boolean { private isParentTabVisible(form: FormModel, currentFormField: FormFieldModel | TabModel): boolean {
const containers = this.getFormTabContainers(form); const containers = this.getFormTabContainers(form);
let isVisible: boolean = true; let isVisible: boolean = true;
containers.map((container: ContainerModel) => { containers.map((container: ContainerModel) => {
@ -281,7 +270,7 @@ export class WidgetVisibilityService {
return (field.id && fieldToFind) ? field.id.toUpperCase() === fieldToFind.toUpperCase() : false; return (field.id && fieldToFind) ? field.id.toUpperCase() === fieldToFind.toUpperCase() : false;
} }
getVariableValue(form: FormModel, name: string, processVarList: TaskProcessVariableModel[]): string { public getVariableValue(form: FormModel, name: string, processVarList: TaskProcessVariableModel[]): string {
const processVariableValue = this.getProcessVariableValue(name, processVarList); const processVariableValue = this.getProcessVariableValue(name, processVarList);
const variableDefaultValue = form.getDefaultFormVariableValue(name); const variableDefaultValue = form.getDefaultFormVariableValue(name);
@ -303,7 +292,7 @@ export class WidgetVisibilityService {
return undefined; return undefined;
} }
evaluateCondition(leftValue: any, rightValue: any, operator: string): boolean | undefined { public evaluateCondition(leftValue: any, rightValue: any, operator: string): boolean | undefined {
switch (operator) { switch (operator) {
case '==': case '==':
return leftValue + '' === rightValue + ''; return leftValue + '' === rightValue + '';
@ -339,28 +328,7 @@ export class WidgetVisibilityService {
this.processVarList = []; this.processVarList = [];
} }
getTaskProcessVariable(taskId: string): Observable<TaskProcessVariableModel[]> {
return from(this.taskFormsApi.getTaskFormVariables(taskId))
.pipe(
map((res) => {
const jsonRes = this.toJson(res);
this.processVarList = jsonRes;
return jsonRes;
}),
catchError(() => this.handleError())
);
}
toJson(res: any): any {
return res || {};
}
private isValidCondition(condition: WidgetVisibilityModel): boolean { private isValidCondition(condition: WidgetVisibilityModel): boolean {
return !!(condition && condition.operator); return !!(condition && condition.operator);
} }
private handleError() {
this.logService.error('Error while performing a call');
return throwError('Error while performing a call - Server error');
}
} }

View File

@ -16,16 +16,17 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, from, throwError } from 'rxjs'; import { Observable, from, throwError, of } from 'rxjs';
import { UserProcessModel } from '../models/user-process.model'; import { UserProcessModel } from '../models/user-process.model';
import { AlfrescoApiService } from './alfresco-api.service'; import { AlfrescoApiService } from './alfresco-api.service';
import { LogService } from './log.service'; import { LogService } from './log.service';
import { catchError, map } from 'rxjs/operators'; import { catchError, combineAll, defaultIfEmpty, map, switchMap } from 'rxjs/operators';
import { import {
TaskActionsApi, TaskActionsApi,
UsersApi, UsersApi,
ResultListDataRepresentationLightUserRepresentation ResultListDataRepresentationLightUserRepresentation, ActivitiGroupsApi
} from '@alfresco/js-api'; } from '@alfresco/js-api';
import { GroupModel } from '../form';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -44,10 +45,35 @@ export class PeopleProcessService {
return this._userApi; return this._userApi;
} }
_groupsApi: ActivitiGroupsApi;
get groupsApi(): ActivitiGroupsApi {
this._groupsApi = this._groupsApi ?? new ActivitiGroupsApi(this.apiService.getInstance());
return this._groupsApi;
}
constructor(private apiService: AlfrescoApiService, constructor(private apiService: AlfrescoApiService,
private logService: LogService) { private logService: LogService) {
} }
/**
* Gets a list of groups in a workflow.
*
* @param filter Filter to select specific groups
* @param groupId Group ID for the search
* @returns Array of groups
*/
getWorkflowGroups(filter: string, groupId?: string): Observable<GroupModel[]> {
const option: any = { filter };
if (groupId) {
option.groupId = groupId;
}
return from(this.groupsApi.getGroups(option))
.pipe(
map((response: any) => response.data || []),
catchError((err) => this.handleError(err))
);
}
/** /**
* Gets information about users across all tasks. * Gets information about users across all tasks.
* *
@ -55,15 +81,21 @@ export class PeopleProcessService {
* @param searchWord Filter text to search for * @param searchWord Filter text to search for
* @returns Array of user information objects * @returns Array of user information objects
*/ */
getWorkflowUsers(taskId?: string, searchWord?: string): Observable<UserProcessModel[]> { getWorkflowUsers(taskId?: string, searchWord?: string, groupId?: string): Observable<UserProcessModel[]> {
const option = { excludeTaskId: taskId, filter: searchWord }; const option = { excludeTaskId: taskId, filter: searchWord, groupId };
return from(this.getWorkflowUserApi(option)) return from(this.getWorkflowUserApi(option))
.pipe( .pipe(
map((response: any) => response.data || []), switchMap(response => response.data as UserProcessModel[] || []),
map((user) => {
user.userImage = this.getUserProfileImageApi(user.id.toString());
return of(user);
}),
combineAll(),
defaultIfEmpty([]),
catchError((err) => this.handleError(err)) catchError((err) => this.handleError(err))
); );
} }
/** /**
* Gets the profile picture URL for the specified user. * Gets the profile picture URL for the specified user.
* *

View File

@ -0,0 +1,110 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 {
FormModel, setupTestBed
} from '@alfresco/adf-core';
import {
Component,
DebugElement, ViewChild
} from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { TranslateModule } from '@ngx-translate/core';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { FormCloudComponent } from './form-cloud.component';
@Component({
selector: 'adf-cloud-form-with-custom-outcomes',
template: `
<adf-cloud-form #adfCloudForm>
<adf-cloud-form-custom-outcomes>
<button mat-button id="adf-custom-outcome-1" (click)="onCustomButtonOneClick()">
CUSTOM-BUTTON-1
</button>
<button mat-button id="adf-custom-outcome-2" (click)="onCustomButtonTwoClick()">
CUSTOM-BUTTON-2
</button>
</adf-cloud-form-custom-outcomes>
</adf-cloud-form>`
})
class FormCloudWithCustomOutComesComponent {
@ViewChild('adfCloudForm', { static: true })
adfCloudForm: FormCloudComponent;
onCustomButtonOneClick() {
}
onCustomButtonTwoClick() {
}
}
describe('FormCloudWithCustomOutComesComponent', () => {
let fixture: ComponentFixture<FormCloudWithCustomOutComesComponent>;
let customComponent: FormCloudWithCustomOutComesComponent;
let debugElement: DebugElement;
setupTestBed({
imports: [
TranslateModule.forRoot(),
ProcessServiceCloudTestingModule
],
declarations: [FormCloudWithCustomOutComesComponent]
});
beforeEach(() => {
fixture = TestBed.createComponent(FormCloudWithCustomOutComesComponent);
customComponent = fixture.componentInstance;
debugElement = fixture.debugElement;
const formRepresentation = {
fields: [
{ id: 'container1' }
],
outcomes: [
{ id: 'outcome-1', name: 'outcome 1' }
]
};
const form = new FormModel(formRepresentation);
customComponent.adfCloudForm.form = form;
fixture.detectChanges();
});
afterEach(() => {
fixture.destroy();
});
it('should be able to inject custom outcomes and click on custom outcomes', async () => {
fixture.detectChanges();
const onCustomButtonOneSpy = spyOn(customComponent, 'onCustomButtonOneClick').and.callThrough();
const buttonOneBtn = debugElement.query(By.css('#adf-custom-outcome-1'));
const buttonTwoBtn = debugElement.query(By.css('#adf-custom-outcome-2'));
expect(buttonOneBtn).not.toBeNull();
expect(buttonTwoBtn).not.toBeNull();
buttonOneBtn.nativeElement.click();
fixture.detectChanges();
await fixture.whenStable();
expect(onCustomButtonOneSpy).toHaveBeenCalled();
expect(buttonOneBtn.nativeElement.innerText).toBe('CUSTOM-BUTTON-1');
expect(buttonTwoBtn.nativeElement.innerText).toBe('CUSTOM-BUTTON-2');
});
});

View File

@ -17,56 +17,42 @@
/* eslint-disable @typescript-eslint/naming-convention */ /* eslint-disable @typescript-eslint/naming-convention */
import { VersionCompatibilityService } from '@alfresco/adf-content-services';
import { import {
Component, AlfrescoApiService, ContentLinkModel, CoreModule,
DebugElement,
SimpleChange,
NgModule,
Injector,
ComponentFactoryResolver,
ViewChild
} from '@angular/core';
import { By } from '@angular/platform-browser';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Observable, of, throwError } from 'rxjs';
import {
CoreModule,
FormFieldModel, FormFieldModel,
FormFieldTypes, FormFieldTypes,
FormModel, FormModel,
FormOutcomeEvent, FormOutcomeEvent,
FormOutcomeModel, FormOutcomeModel, FormRenderingService, FormService, setupTestBed,
setupTestBed, TRANSLATION_PROVIDER, UploadWidgetContentLinkModel, WidgetVisibilityService
TRANSLATION_PROVIDER,
WidgetVisibilityService,
FormService,
UploadWidgetContentLinkModel,
ContentLinkModel,
AlfrescoApiService
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { VersionCompatibilityService } from '@alfresco/adf-content-services'; import { Node } from '@alfresco/js-api';
import { ESCAPE } from '@angular/cdk/keycodes';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import {
Component, ComponentFactoryResolver, Injector, SimpleChange
} from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MatDialog } from '@angular/material/dialog';
import { MatDialogHarness } from '@angular/material/dialog/testing';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { TranslateModule, TranslateService } from '@ngx-translate/core';
import { Observable, of, throwError } from 'rxjs';
import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module'; import { ProcessServiceCloudTestingModule } from '../../testing/process-service-cloud.testing.module';
import { FormCloudService } from '../services/form-cloud.service'; import { FormCloudModule } from '../form-cloud.module';
import { FormCloudComponent } from './form-cloud.component';
import { import {
cloudFormMock, cloudFormMock,
conditionalUploadWidgetsMock, conditionalUploadWidgetsMock,
emptyFormRepresentationJSON, emptyFormRepresentationJSON,
fakeCloudForm, fakeCloudForm, fakeMetadataForm, multilingualForm
multilingualForm,
fakeMetadataForm
} from '../mocks/cloud-form.mock'; } from '../mocks/cloud-form.mock';
import { FormCloudRepresentation } from '../models/form-cloud-representation.model'; import { FormCloudRepresentation } from '../models/form-cloud-representation.model';
import { FormCloudModule } from '../form-cloud.module'; import { FormCloudService } from '../services/form-cloud.service';
import { TranslateService, TranslateModule } from '@ngx-translate/core';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { CloudFormRenderingService } from './cloud-form-rendering.service'; import { CloudFormRenderingService } from './cloud-form-rendering.service';
import { Node } from '@alfresco/js-api'; import { FormCloudComponent } from './form-cloud.component';
import { ESCAPE } from '@angular/cdk/keycodes';
import { MatDialog } from '@angular/material/dialog';
import { HarnessLoader } from '@angular/cdk/testing';
import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed';
import { MatDialogHarness } from '@angular/material/dialog/testing';
const mockOauth2Auth: any = { const mockOauth2Auth: any = {
oauth2Auth: { oauth2Auth: {
@ -83,7 +69,6 @@ describe('FormCloudComponent', () => {
let matDialog: MatDialog; let matDialog: MatDialog;
let visibilityService: WidgetVisibilityService; let visibilityService: WidgetVisibilityService;
let formRenderingService: CloudFormRenderingService; let formRenderingService: CloudFormRenderingService;
let translateService: TranslateService;
let documentRootLoader: HarnessLoader; let documentRootLoader: HarnessLoader;
@Component({ @Component({
@ -95,13 +80,6 @@ describe('FormCloudComponent', () => {
typeId = 'CustomWidget'; typeId = 'CustomWidget';
} }
@NgModule({
declarations: [CustomWidget],
exports: [CustomWidget]
})
class CustomUploadModule {
}
const buildWidget = (type: string, injector: Injector): any => { const buildWidget = (type: string, injector: Injector): any => {
const resolver = formRenderingService.getComponentTypeResolver(type); const resolver = formRenderingService.getComponentTypeResolver(type);
const widgetType = resolver(null); const widgetType = resolver(null);
@ -115,25 +93,14 @@ describe('FormCloudComponent', () => {
setupTestBed({ setupTestBed({
imports: [ imports: [
NoopAnimationsModule, ProcessServiceCloudTestingModule
TranslateModule.forRoot(),
CoreModule.forRoot(),
FormCloudModule,
CustomUploadModule
], ],
providers: [ providers: [
{
provide: TRANSLATION_PROVIDER,
multi: true,
useValue: {
name: 'app',
source: 'resources'
}
},
{ {
provide: VersionCompatibilityService, provide: VersionCompatibilityService,
useValue: {} useValue: {}
} },
{ provide: FormRenderingService, useClass: CloudFormRenderingService }
] ]
}); });
@ -144,7 +111,6 @@ describe('FormCloudComponent', () => {
formRenderingService = TestBed.inject(CloudFormRenderingService); formRenderingService = TestBed.inject(CloudFormRenderingService);
formCloudService = TestBed.inject(FormCloudService); formCloudService = TestBed.inject(FormCloudService);
translateService = TestBed.inject(TranslateService);
matDialog = TestBed.inject(MatDialog); matDialog = TestBed.inject(MatDialog);
visibilityService = TestBed.inject(WidgetVisibilityService); visibilityService = TestBed.inject(WidgetVisibilityService);
@ -1100,121 +1066,77 @@ describe('FormCloudComponent', () => {
}); });
}); });
describe('Multilingual Form', () => {
it('should translate form labels on language change', async () => {
spyOn(formCloudService, 'getForm').and.returnValue(of(multilingualForm));
const formId = '123';
const appName = 'test-app';
formComponent.formId = formId;
formComponent.appVersion = 1;
formComponent.ngOnChanges({ appName: new SimpleChange(null, appName, true) });
expect(formCloudService.getForm).toHaveBeenCalledWith(appName, formId, 1);
fixture.detectChanges();
expect(getLabelValue('textField')).toEqual('Text field');
expect(getLabelValue('fildUploadField')).toEqual('File Upload');
expect(getLabelValue('dateField')).toEqual('Date field (D-M-YYYY)');
expect(getLabelValue('amountField')).toEqual('Amount field');
fixture.ngZone.run(() => translateService.use('fr'));
await fixture.whenStable();
fixture.detectChanges();
expect(getLabelValue('textField')).toEqual('Champ de texte');
expect(getLabelValue('fildUploadField')).toEqual('Téléchargement de fichiers');
expect(getLabelValue('dateField')).toEqual('Champ de date (D-M-YYYY)');
expect(getLabelValue('amountField')).toEqual('Champ Montant');
});
const getLabelValue = (containerId: string): string => {
const label = fixture.debugElement.nativeElement.querySelector(`[id="field-${containerId}-container"] label`);
return label.innerText;
};
});
}); });
@Component({ describe('Multilingual Form', () => {
selector: 'adf-cloud-form-with-custom-outcomes', let translateService: TranslateService;
template: ` let formCloudService: FormCloudService;
<adf-cloud-form #adfCloudForm> let formComponent: FormCloudComponent;
<adf-cloud-form-custom-outcomes> let fixture: ComponentFixture<FormCloudComponent>;
<button mat-button id="adf-custom-outcome-1" (click)="onCustomButtonOneClick()">
CUSTOM-BUTTON-1
</button>
<button mat-button id="adf-custom-outcome-2" (click)="onCustomButtonTwoClick()">
CUSTOM-BUTTON-2
</button>
</adf-cloud-form-custom-outcomes>
</adf-cloud-form>`
})
class FormCloudWithCustomOutComesComponent {
@ViewChild('adfCloudForm', { static: true })
adfCloudForm: FormCloudComponent;
onCustomButtonOneClick() {
}
onCustomButtonTwoClick() {
}
}
describe('FormCloudWithCustomOutComesComponent', () => {
let fixture: ComponentFixture<FormCloudWithCustomOutComesComponent>;
let customComponent: FormCloudWithCustomOutComesComponent;
let debugElement: DebugElement;
setupTestBed({ setupTestBed({
imports: [ imports: [
NoopAnimationsModule,
TranslateModule.forRoot(), TranslateModule.forRoot(),
ProcessServiceCloudTestingModule CoreModule.forRoot()
], ],
declarations: [FormCloudWithCustomOutComesComponent] providers: [
{
provide: TRANSLATION_PROVIDER,
multi: true,
useValue: {
name: 'app',
source: 'resources'
}
}
]
}); });
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(FormCloudWithCustomOutComesComponent); translateService = TestBed.inject(TranslateService);
customComponent = fixture.componentInstance; formCloudService = TestBed.inject(FormCloudService);
debugElement = fixture.debugElement;
const formRepresentation = {
fields: [
{ id: 'container1' }
],
outcomes: [
{ id: 'outcome-1', name: 'outcome 1' }
]
};
const form = new FormModel(formRepresentation); fixture = TestBed.createComponent(FormCloudComponent);
customComponent.adfCloudForm.form = form; formComponent = fixture.componentInstance;
formComponent.form = formComponent.parseForm(fakeMetadataForm);
fixture.detectChanges(); fixture.detectChanges();
}); });
afterEach(() => { it('should translate form labels on language change', async () => {
fixture.destroy(); spyOn(formCloudService, 'getForm').and.returnValue(of(multilingualForm));
}); const formId = '123';
const appName = 'test-app';
formComponent.formId = formId;
formComponent.appVersion = 1;
it('should be able to inject custom outcomes and click on custom outcomes', async () => { formComponent.ngOnChanges({ appName: new SimpleChange(null, appName, true) });
fixture.detectChanges(); expect(formCloudService.getForm).toHaveBeenCalledWith(appName, formId, 1);
const onCustomButtonOneSpy = spyOn(customComponent, 'onCustomButtonOneClick').and.callThrough(); fixture.ngZone.run(() => translateService.use('fr'));
const buttonOneBtn = debugElement.query(By.css('#adf-custom-outcome-1'));
const buttonTwoBtn = debugElement.query(By.css('#adf-custom-outcome-2'));
expect(buttonOneBtn).not.toBeNull();
expect(buttonTwoBtn).not.toBeNull();
buttonOneBtn.nativeElement.click();
fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
fixture.detectChanges();
expect(onCustomButtonOneSpy).toHaveBeenCalled(); expect(getLabelValue('textField')).toEqual('Champ de texte');
expect(buttonOneBtn.nativeElement.innerText).toBe('CUSTOM-BUTTON-1'); expect(getLabelValue('fildUploadField')).toEqual('Téléchargement de fichiers');
expect(buttonTwoBtn.nativeElement.innerText).toBe('CUSTOM-BUTTON-2'); expect(getLabelValue('dateField')).toEqual('Champ de date (D-M-YYYY)');
expect(getLabelValue('amountField')).toEqual('Champ Montant');
fixture.ngZone.run(() => translateService.use('en'));
await fixture.whenStable();
fixture.detectChanges();
expect(getLabelValue('textField')).toEqual('Text field');
expect(getLabelValue('fildUploadField')).toEqual('File Upload');
expect(getLabelValue('dateField')).toEqual('Date field (D-M-YYYY)');
expect(getLabelValue('amountField')).toEqual('Amount field');
}); });
const getLabelValue = (containerId: string): string => {
const label = fixture.debugElement.nativeElement.querySelector(`[id="field-${containerId}-container"] label`);
return label.innerText;
};
}); });
describe('retrieve metadata on submit', () => { describe('retrieve metadata on submit', () => {

View File

@ -824,14 +824,6 @@ describe('AttachFileCloudWidgetComponent', () => {
expect(widget.field.params.fileSource.destinationFolderPath.value).toBe('mock-folder-id'); expect(widget.field.params.fileSource.destinationFolderPath.value).toBe('mock-folder-id');
}); });
it('it should get a destination folder path value from a folder variable', () => {
createUploadWidgetField(form, 'attach-file-attach', [], mockAllFileSourceWithFolderVariablePathType);
fixture.detectChanges();
expect(widget.field.params.fileSource.destinationFolderPath.type).toBe('folder');
expect(widget.field.params.fileSource.destinationFolderPath.value).toBe('mock-folder-id');
});
it('it should set destination folder path value to undefined if mapped variable deleted/renamed', () => { it('it should set destination folder path value to undefined if mapped variable deleted/renamed', () => {
createUploadWidgetField(form, 'attach-file-attach', [], mockAllFileSourceWithRenamedFolderVariablePathType); createUploadWidgetField(form, 'attach-file-attach', [], mockAllFileSourceWithRenamedFolderVariablePathType);
fixture.detectChanges(); fixture.detectChanges();

View File

@ -71,7 +71,6 @@ describe('DropdownCloudWidgetComponent', () => {
describe('Simple Dropdown', () => { describe('Simple Dropdown', () => {
beforeEach(() => { beforeEach(() => {
spyOn(formService, 'getRestFieldValues').and.callFake(() => of(fakeOptionList));
widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), { widget.field = new FormFieldModel(new FormModel({ taskId: 'fake-task-id' }), {
id: 'dropdown-id', id: 'dropdown-id',
name: 'date-name', name: 'date-name',
@ -85,13 +84,14 @@ describe('DropdownCloudWidgetComponent', () => {
}); });
it('should require field with restUrl', () => { it('should require field with restUrl', () => {
spyOn(formCloudService, 'getRestWidgetData');
widget.field = new FormFieldModel(new FormModel()); widget.field = new FormFieldModel(new FormModel());
widget.ngOnInit(); widget.ngOnInit();
expect(formService.getRestFieldValues).not.toHaveBeenCalled(); expect(formCloudService.getRestWidgetData).not.toHaveBeenCalled();
widget.field = new FormFieldModel(null, { restUrl: null }); widget.field = new FormFieldModel(null, { restUrl: null });
widget.ngOnInit(); widget.ngOnInit();
expect(formService.getRestFieldValues).not.toHaveBeenCalled(); expect(formCloudService.getRestWidgetData).not.toHaveBeenCalled();
}); });
it('should select the default value when an option is chosen as default', async () => { it('should select the default value when an option is chosen as default', async () => {
@ -339,46 +339,13 @@ describe('DropdownCloudWidgetComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
expect(element.querySelector('.adf-invalid')).toBeTruthy();
const requiredErrorElement = fixture.debugElement.query(By.css('.adf-dropdown-required-message .adf-error-text')); const requiredErrorElement = fixture.debugElement.query(By.css('.adf-dropdown-required-message .adf-error-text'));
expect(requiredErrorElement.nativeElement.innerText).toEqual('FORM.FIELD.REQUIRED'); expect(requiredErrorElement.nativeElement.innerText).toEqual('FORM.FIELD.REQUIRED');
}); });
}); });
describe('when is required', () => {
beforeEach(() => {
widget.field = new FormFieldModel( new FormModel({ taskId: '<id>' }), {
type: FormFieldTypes.DROPDOWN,
required: true
});
});
it('should be able to display label with asterisk', async () => {
fixture.detectChanges();
await fixture.whenStable();
const asterisk: HTMLElement = element.querySelector('.adf-asterisk');
expect(asterisk).toBeTruthy();
expect(asterisk.textContent).toEqual('*');
});
it('should be invalid if no default option after interaction', async () => {
fixture.detectChanges();
await fixture.whenStable();
expect(element.querySelector('.adf-invalid')).toBeFalsy();
const dropdownSelect = element.querySelector('.adf-select');
dropdownSelect.dispatchEvent(new Event('blur'));
fixture.detectChanges();
await fixture.whenStable();
expect(element.querySelector('.adf-invalid')).toBeTruthy();
});
});
describe('filter', () => { describe('filter', () => {
beforeEach(() => { beforeEach(() => {

View File

@ -135,7 +135,7 @@ describe('RadioButtonsCloudWidgetComponent', () => {
expect(widget.field.isValid).toBe(true); expect(widget.field.isValid).toBe(true);
}); });
it('should be able to set a Radio Button widget as required', () => { it('should set Radio Button as valid when required and not empty', () => {
widget.field = new FormFieldModel(new FormModel({}), { widget.field = new FormFieldModel(new FormModel({}), {
id: 'radio-id', id: 'radio-id',
name: 'radio-name-label', name: 'radio-name-label',

View File

@ -20,7 +20,6 @@ import {
AlfrescoApiService, AlfrescoApiService,
FormValues, FormValues,
AppConfigService, AppConfigService,
FormOutcomeModel,
FormModel, FormModel,
FormFieldOption FormFieldOption
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
@ -232,17 +231,7 @@ export class FormCloudService extends BaseCloudService implements FormCloudServi
formValues[variable.name] = variable.value; formValues[variable.name] = variable.value;
}); });
const form = new FormModel(flattenForm, formValues, readOnly); return new FormModel(flattenForm, formValues, readOnly);
if (!json.fields) {
form.outcomes = [
new FormOutcomeModel(form, {
id: '$save',
name: FormOutcomeModel.SAVE_ACTION,
isSystem: true
})
];
}
return form;
} }
return null; return null;
} }

View File

@ -306,7 +306,7 @@ describe('Task Cloud Service', () => {
}); });
}); });
it('should throw error if appName is not defined when querying by id', (done) => { it('should throw error if appName is not defined when querying by id with update playload', (done) => {
const appName = null; const appName = null;
const taskId = '68d54a8f'; const taskId = '68d54a8f';
const updatePayload = { description: 'New description' }; const updatePayload = { description: 'New description' };

View File

@ -338,7 +338,7 @@ describe('TaskFormCloudComponent', () => {
component.onError({}); component.onError({});
}); });
it('should emit taskCompleted when task is completed', () => { it('should reload when task is completed', () => {
spyOn(taskCloudService, 'completeTask').and.returnValue(of({})); spyOn(taskCloudService, 'completeTask').and.returnValue(of({}));
const reloadSpy = spyOn(component, 'ngOnChanges').and.callThrough(); const reloadSpy = spyOn(component, 'ngOnChanges').and.callThrough();
@ -350,7 +350,7 @@ describe('TaskFormCloudComponent', () => {
expect(reloadSpy).toHaveBeenCalled(); expect(reloadSpy).toHaveBeenCalled();
}); });
it('should emit taskClaimed when task is claimed', () => { it('should reload when task is claimed', () => {
spyOn(taskCloudService, 'claimTask').and.returnValue(of({})); spyOn(taskCloudService, 'claimTask').and.returnValue(of({}));
spyOn(component, 'hasCandidateUsers').and.returnValue(true); spyOn(component, 'hasCandidateUsers').and.returnValue(true);
const reloadSpy = spyOn(component, 'ngOnChanges').and.callThrough(); const reloadSpy = spyOn(component, 'ngOnChanges').and.callThrough();

View File

@ -168,18 +168,6 @@ describe('TaskHeaderCloudComponent', () => {
expect(valueEl.nativeElement.value).toBe('67c4z2a8f-01f3-11e9-8e36-0a58646002ad'); expect(valueEl.nativeElement.value).toBe('67c4z2a8f-01f3-11e9-8e36-0a58646002ad');
}); });
it('should display process instance id', async () => {
fixture.detectChanges();
await fixture.whenStable();
fixture.detectChanges();
const labelEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-label-processInstanceId"]'));
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="card-textitem-value-processInstanceId"]'));
expect(labelEl.nativeElement.textContent.trim()).toBe('ADF_CLOUD_TASK_HEADER.PROPERTIES.PROCESS_INSTANCE_ID');
expect(valueEl.nativeElement.value).toBe('67c4z2a8f-01f3-11e9-8e36-0a58646002ad');
});
it('should display placeholder if no due date', async () => { it('should display placeholder if no due date', async () => {
component.taskDetails.dueDate = null; component.taskDetails.dueDate = null;
component.refreshData(); component.refreshData();

View File

@ -80,4 +80,5 @@ module.exports = function (config) {
browsers: ['Chrome'], browsers: ['Chrome'],
singleRun: true singleRun: true
}); });
process.env.TZ = 'UTC';
}; };

View File

@ -16,7 +16,7 @@
*/ */
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { ProcessContentService } from '@alfresco/adf-core'; import { ProcessContentService } from '../form/services/process-content.service';
@Component({ @Component({
selector: 'adf-create-process-attachment', selector: 'adf-create-process-attachment',

View File

@ -19,10 +19,11 @@ import { SimpleChange } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { ProcessContentService, setupTestBed } from '@alfresco/adf-core'; import { setupTestBed } from '@alfresco/adf-core';
import { AttachmentComponent } from './create-task-attachment.component'; import { AttachmentComponent } from './create-task-attachment.component';
import { ProcessTestingModule } from '../testing/process.testing.module'; import { ProcessTestingModule } from '../testing/process.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { ProcessContentService } from '../form/services/process-content.service';
describe('AttachmentComponent', () => { describe('AttachmentComponent', () => {

View File

@ -16,7 +16,7 @@
*/ */
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core';
import { ProcessContentService } from '@alfresco/adf-core'; import { ProcessContentService } from '../form/services/process-content.service';
@Component({ @Component({
selector: 'adf-create-task-attachment', selector: 'adf-create-task-attachment',

View File

@ -18,12 +18,13 @@
import { SimpleChange, Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; import { SimpleChange, Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { ProcessContentService, setupTestBed } from '@alfresco/adf-core'; import { setupTestBed } from '@alfresco/adf-core';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { ProcessAttachmentListComponent } from './process-attachment-list.component'; import { ProcessAttachmentListComponent } from './process-attachment-list.component';
import { ProcessTestingModule } from '../testing/process.testing.module'; import { ProcessTestingModule } from '../testing/process.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { mockEmittedProcessAttachments, mockProcessAttachments } from '../mock/process/process-attachments.mock'; import { mockEmittedProcessAttachments, mockProcessAttachments } from '../mock/process/process-attachments.mock';
import { ProcessContentService } from '../form/services/process-content.service';
describe('ProcessAttachmentListComponent', () => { describe('ProcessAttachmentListComponent', () => {

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { ContentService, EmptyListComponent, ThumbnailService, ProcessContentService } from '@alfresco/adf-core'; import { ContentService, EmptyListComponent, ThumbnailService } from '@alfresco/adf-core';
import { import {
AfterContentInit, AfterContentInit,
ContentChild, ContentChild,
@ -28,6 +28,7 @@ import {
SimpleChanges, SimpleChanges,
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { ProcessContentService } from '../form/services/process-content.service';
@Component({ @Component({
selector: 'adf-process-attachment-list', selector: 'adf-process-attachment-list',

View File

@ -20,10 +20,11 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { of, throwError } from 'rxjs'; import { of, throwError } from 'rxjs';
import { TaskAttachmentListComponent } from './task-attachment-list.component'; import { TaskAttachmentListComponent } from './task-attachment-list.component';
import { ProcessContentService, setupTestBed } from '@alfresco/adf-core'; import { setupTestBed } from '@alfresco/adf-core';
import { ProcessTestingModule } from '../testing/process.testing.module'; import { ProcessTestingModule } from '../testing/process.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { mockEmittedTaskAttachments, mockTaskAttachments } from '../mock/task/task-attachments.mock'; import { mockEmittedTaskAttachments, mockTaskAttachments } from '../mock/task/task-attachments.mock';
import { ProcessContentService } from '../form/services/process-content.service';
describe('TaskAttachmentList', () => { describe('TaskAttachmentList', () => {

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { ContentService, ThumbnailService, EmptyListComponent, ProcessContentService } from '@alfresco/adf-core'; import { ContentService, ThumbnailService, EmptyListComponent } from '@alfresco/adf-core';
import { import {
AfterContentInit, AfterContentInit,
ContentChild, ContentChild,
@ -28,6 +28,7 @@ import {
SimpleChanges, SimpleChanges,
ViewEncapsulation ViewEncapsulation
} from '@angular/core'; } from '@angular/core';
import { ProcessContentService } from '../form/services/process-content.service';
@Component({ @Component({
selector: 'adf-task-attachment-list', selector: 'adf-task-attachment-list',

View File

@ -15,11 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { FormFieldModel } from '../components/widgets/core/form-field.model'; import { FormFieldModel, FormFieldEvent, FormModel } from '@alfresco/adf-core';
import { FormModel } from '../components/widgets/core/form.model'; import { DynamicRowValidationSummary } from '../widgets/dynamic-table/editors/models/dynamic-row-validation-summary.model';
import { DynamicRowValidationSummary } from '../components/widgets/dynamic-table/dynamic-row-validation-summary.model'; import { DynamicTableRow } from '../widgets/dynamic-table/editors/models/dynamic-table-row.model';
import { DynamicTableRow } from '../components/widgets/dynamic-table/dynamic-table-row.model';
import { FormFieldEvent } from './form-field.event';
export class ValidateDynamicTableRowEvent extends FormFieldEvent { export class ValidateDynamicTableRowEvent extends FormFieldEvent {

View File

@ -17,22 +17,19 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { FormService } from '../services/form.service'; import { setupTestBed, CoreTestingModule } from '@alfresco/adf-core';
import { FormListComponent } from './form-list.component'; import { FormListComponent } from './form-list.component';
import { setupTestBed } from '../../testing/setup-test-bed'; import { ModelService } from '../services/model.service';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
describe('TaskAttachmentList', () => { describe('TaskAttachmentList', () => {
let component: FormListComponent; let component: FormListComponent;
let fixture: ComponentFixture<FormListComponent>; let fixture: ComponentFixture<FormListComponent>;
let service: FormService; let modelService: ModelService;
let element: HTMLElement; let element: HTMLElement;
setupTestBed({ setupTestBed({
imports: [ imports: [
TranslateModule.forRoot(),
CoreTestingModule CoreTestingModule
] ]
}); });
@ -41,13 +38,13 @@ describe('TaskAttachmentList', () => {
fixture = TestBed.createComponent(FormListComponent); fixture = TestBed.createComponent(FormListComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
element = fixture.debugElement.nativeElement; element = fixture.debugElement.nativeElement;
service = TestBed.inject(FormService); modelService = TestBed.inject(ModelService);
}); });
it('should show the forms as a list', async () => { it('should show the forms as a list', async () => {
spyOn(service, 'getForms').and.returnValue(of([ spyOn(modelService, 'getForms').and.returnValue(of([
{ name: 'FakeName-1', lastUpdatedByFullName: 'FakeUser-1', lastUpdated: '2017-01-02' }, {name: 'FakeName-1', lastUpdatedByFullName: 'FakeUser-1', lastUpdated: '2017-01-02'},
{ name: 'FakeName-2', lastUpdatedByFullName: 'FakeUser-2', lastUpdated: '2017-01-03' } {name: 'FakeName-2', lastUpdatedByFullName: 'FakeUser-2', lastUpdated: '2017-01-03'}
])); ]));
component.ngOnChanges(); component.ngOnChanges();

View File

@ -16,7 +16,7 @@
*/ */
import { Component, Input, OnChanges, ViewEncapsulation } from '@angular/core'; import { Component, Input, OnChanges, ViewEncapsulation } from '@angular/core';
import { FormService } from '../services/form.service'; import { ModelService } from '../services/model.service';
@Component({ @Component({
selector: 'adf-form-list', selector: 'adf-form-list',
@ -29,7 +29,7 @@ export class FormListComponent implements OnChanges {
@Input() @Input()
forms: any [] = []; forms: any [] = [];
constructor(protected formService: FormService) { constructor(protected modelService: ModelService) {
} }
ngOnChanges() { ngOnChanges() {
@ -41,7 +41,7 @@ export class FormListComponent implements OnChanges {
} }
getForms() { getForms() {
this.formService.getForms().subscribe((forms) => { this.modelService.getForms().subscribe((forms) => {
this.forms.push(...forms); this.forms.push(...forms);
}); });
} }

View File

@ -25,18 +25,23 @@ import {
DebugElement DebugElement
} from '@angular/core'; } from '@angular/core';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { TaskRepresentation } from '@alfresco/js-api';
import { TestBed, ComponentFixture } from '@angular/core/testing'; import { TestBed, ComponentFixture } from '@angular/core/testing';
import { Observable, of, throwError } from 'rxjs'; import { Observable, of, throwError } from 'rxjs';
import { import {
FormFieldModel, FormFieldTypes, FormModel, FormOutcomeEvent, FormOutcomeModel, FormFieldModel, FormFieldTypes, FormModel, FormOutcomeEvent, FormOutcomeModel,
FormService, WidgetVisibilityService, NodeService, ContainerModel, fakeForm, FormService, WidgetVisibilityService, ContainerModel, fakeForm,
setupTestBed, setupTestBed,
NodeMetadata NodeMetadata, NodesApiService
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { FormComponent } from './form.component'; import { FormComponent } from './form.component';
import { ProcessFormRenderingService } from './process-form-rendering.service'; import { ProcessFormRenderingService } from './process-form-rendering.service';
import { ProcessTestingModule } from '../testing/process.testing.module'; import { ProcessTestingModule } from '../testing/process.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { TaskFormService } from './services/task-form.service';
import { TaskService } from './services/task.service';
import { EditorService } from './services/editor.service';
import { ModelService } from './services/model.service';
describe('FormComponent', () => { describe('FormComponent', () => {
@ -45,7 +50,11 @@ describe('FormComponent', () => {
let formService: FormService; let formService: FormService;
let visibilityService: WidgetVisibilityService; let visibilityService: WidgetVisibilityService;
let nodeService: NodeService; let taskFormService: TaskFormService;
let taskService: TaskService;
let editorService: EditorService;
let modelService: ModelService;
let nodeService: NodesApiService;
let formRenderingService: ProcessFormRenderingService; let formRenderingService: ProcessFormRenderingService;
@Component({ @Component({
@ -87,8 +96,12 @@ describe('FormComponent', () => {
visibilityService = TestBed.inject(WidgetVisibilityService); visibilityService = TestBed.inject(WidgetVisibilityService);
spyOn(visibilityService, 'refreshVisibility').and.stub(); spyOn(visibilityService, 'refreshVisibility').and.stub();
nodeService = TestBed.inject(NodesApiService);
formService = TestBed.inject(FormService); formService = TestBed.inject(FormService);
nodeService = TestBed.inject(NodeService); taskService = TestBed.inject(TaskService);
taskFormService = TestBed.inject(TaskFormService);
editorService = TestBed.inject(EditorService);
modelService = TestBed.inject(ModelService);
formRenderingService = TestBed.inject(ProcessFormRenderingService); formRenderingService = TestBed.inject(ProcessFormRenderingService);
fixture = TestBed.createComponent(FormComponent); fixture = TestBed.createComponent(FormComponent);
@ -159,14 +172,14 @@ describe('FormComponent', () => {
it('should enable custom outcome buttons', () => { it('should enable custom outcome buttons', () => {
const formModel = new FormModel(); const formModel = new FormModel();
formComponent.form = formModel; formComponent.form = formModel;
const outcome = new FormOutcomeModel(formModel, { id: 'action1', name: 'Action 1' }); const outcome = new FormOutcomeModel(formModel, {id: 'action1', name: 'Action 1'});
expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy(); expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
}); });
it('should allow controlling [complete] button visibility', () => { it('should allow controlling [complete] button visibility', () => {
const formModel = new FormModel(); const formModel = new FormModel();
formComponent.form = formModel; formComponent.form = formModel;
const outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.SAVE_ACTION }); const outcome = new FormOutcomeModel(formModel, {id: '$save', name: FormOutcomeModel.SAVE_ACTION});
formComponent.showSaveButton = true; formComponent.showSaveButton = true;
expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy(); expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
@ -179,7 +192,7 @@ describe('FormComponent', () => {
const formModel = new FormModel(); const formModel = new FormModel();
formModel.readOnly = true; formModel.readOnly = true;
formComponent.form = formModel; formComponent.form = formModel;
const outcome = new FormOutcomeModel(formModel, { id: '$complete', name: FormOutcomeModel.COMPLETE_ACTION }); const outcome = new FormOutcomeModel(formModel, {id: '$complete', name: FormOutcomeModel.COMPLETE_ACTION});
formComponent.showCompleteButton = true; formComponent.showCompleteButton = true;
expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy(); expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
@ -189,23 +202,23 @@ describe('FormComponent', () => {
const formModel = new FormModel(); const formModel = new FormModel();
formModel.readOnly = true; formModel.readOnly = true;
formComponent.form = formModel; formComponent.form = formModel;
const outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.SAVE_ACTION }); const outcome = new FormOutcomeModel(formModel, {id: '$save', name: FormOutcomeModel.SAVE_ACTION});
formComponent.showSaveButton = true; formComponent.showSaveButton = true;
expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeFalsy(); expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeFalsy();
}); });
it('should show [custom-outcome] button with readOnly form and selected custom-outcome', () => { it('should show [custom-outcome] button with readOnly form and selected custom-outcome', () => {
const formModel = new FormModel({ selectedOutcome: 'custom-outcome' }); const formModel = new FormModel({selectedOutcome: 'custom-outcome'});
formModel.readOnly = true; formModel.readOnly = true;
formComponent.form = formModel; formComponent.form = formModel;
let outcome = new FormOutcomeModel(formModel, { id: '$customoutome', name: 'custom-outcome' }); let outcome = new FormOutcomeModel(formModel, {id: '$customoutome', name: 'custom-outcome'});
formComponent.showCompleteButton = true; formComponent.showCompleteButton = true;
formComponent.showSaveButton = true; formComponent.showSaveButton = true;
expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy(); expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
outcome = new FormOutcomeModel(formModel, { id: '$customoutome2', name: 'custom-outcome2' }); outcome = new FormOutcomeModel(formModel, {id: '$customoutome2', name: 'custom-outcome2'});
expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeFalsy(); expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeFalsy();
}); });
@ -213,7 +226,7 @@ describe('FormComponent', () => {
const formModel = new FormModel(); const formModel = new FormModel();
formModel.readOnly = false; formModel.readOnly = false;
formComponent.form = formModel; formComponent.form = formModel;
const outcome = new FormOutcomeModel(formModel, { id: '$save', name: FormOutcomeModel.COMPLETE_ACTION }); const outcome = new FormOutcomeModel(formModel, {id: '$save', name: FormOutcomeModel.COMPLETE_ACTION});
formComponent.showCompleteButton = true; formComponent.showCompleteButton = true;
expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy(); expect(formComponent.isOutcomeButtonVisible(outcome, formComponent.form.readOnly)).toBeTruthy();
@ -240,44 +253,6 @@ describe('FormComponent', () => {
expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId); expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId);
}); });
it('should get process variable if is a process task', () => {
spyOn(formService, 'getTaskForm').and.callFake((currentTaskId) => new Observable((observer) => {
observer.next({ taskId: currentTaskId });
observer.complete();
}));
spyOn(visibilityService, 'getTaskProcessVariable').and.returnValue(of(null));
spyOn(formService, 'getTask').and.callFake((currentTaskId) => new Observable((observer) => {
observer.next({ taskId: currentTaskId, processDefinitionId: '10201' });
observer.complete();
}));
const taskId = '123';
formComponent.taskId = taskId;
formComponent.loadForm();
expect(visibilityService.getTaskProcessVariable).toHaveBeenCalledWith(taskId);
});
it('should not get process variable if is not a process task', () => {
spyOn(formService, 'getTaskForm').and.callFake((currentTaskId) => new Observable((observer) => {
observer.next({ taskId: currentTaskId });
observer.complete();
}));
spyOn(visibilityService, 'getTaskProcessVariable').and.returnValue(of(null));
spyOn(formService, 'getTask').and.callFake((currentTaskId) => new Observable((observer) => {
observer.next({ taskId: currentTaskId, processDefinitionId: 'null' });
observer.complete();
}));
const taskId = '123';
formComponent.taskId = taskId;
formComponent.loadForm();
expect(visibilityService.getTaskProcessVariable).toHaveBeenCalledWith(taskId);
});
it('should get form definition by form id on load', () => { it('should get form definition by form id on load', () => {
spyOn(formComponent, 'getFormDefinitionByFormId').and.stub(); spyOn(formComponent, 'getFormDefinitionByFormId').and.stub();
const formId = 123; const formId = 123;
@ -289,13 +264,13 @@ describe('FormComponent', () => {
}); });
it('should refresh visibility when the form is loaded', () => { it('should refresh visibility when the form is loaded', () => {
spyOn(formService, 'getFormDefinitionById').and.returnValue(of(JSON.parse(JSON.stringify(fakeForm)))); spyOn(editorService, 'getFormDefinitionById').and.returnValue(of(JSON.parse(JSON.stringify(fakeForm))));
const formId = 123; const formId = 123;
formComponent.formId = formId; formComponent.formId = formId;
formComponent.loadForm(); formComponent.loadForm();
expect(formService.getFormDefinitionById).toHaveBeenCalledWith(formId); expect(editorService.getFormDefinitionById).toHaveBeenCalledWith(formId);
expect(visibilityService.refreshVisibility).toHaveBeenCalled(); expect(visibilityService.refreshVisibility).toHaveBeenCalled();
}); });
@ -314,7 +289,7 @@ describe('FormComponent', () => {
const taskId = '<task id>'; const taskId = '<task id>';
const change = new SimpleChange(null, taskId, true); const change = new SimpleChange(null, taskId, true);
formComponent.ngOnChanges({ taskId: change }); formComponent.ngOnChanges({taskId: change});
expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId); expect(formComponent.getFormByTaskId).toHaveBeenCalledWith(taskId);
}); });
@ -324,7 +299,7 @@ describe('FormComponent', () => {
const formId = '123'; const formId = '123';
const change = new SimpleChange(null, formId, true); const change = new SimpleChange(null, formId, true);
formComponent.ngOnChanges({ formId: change }); formComponent.ngOnChanges({formId: change});
expect(formComponent.getFormDefinitionByFormId).toHaveBeenCalledWith(formId); expect(formComponent.getFormDefinitionByFormId).toHaveBeenCalledWith(formId);
}); });
@ -334,7 +309,7 @@ describe('FormComponent', () => {
const formName = '<form>'; const formName = '<form>';
const change = new SimpleChange(null, formName, true); const change = new SimpleChange(null, formName, true);
formComponent.ngOnChanges({ formName: change }); formComponent.ngOnChanges({formName: change});
expect(formComponent.getFormDefinitionByFormName).toHaveBeenCalledWith(formName); expect(formComponent.getFormDefinitionByFormName).toHaveBeenCalledWith(formName);
}); });
@ -359,7 +334,7 @@ describe('FormComponent', () => {
spyOn(formComponent, 'getFormDefinitionByFormId').and.stub(); spyOn(formComponent, 'getFormDefinitionByFormId').and.stub();
spyOn(formComponent, 'getFormDefinitionByFormName').and.stub(); spyOn(formComponent, 'getFormDefinitionByFormName').and.stub();
formComponent.ngOnChanges({ tag: new SimpleChange(null, 'hello world', true) }); formComponent.ngOnChanges({tag: new SimpleChange(null, 'hello world', true)});
expect(formComponent.getFormByTaskId).not.toHaveBeenCalled(); expect(formComponent.getFormByTaskId).not.toHaveBeenCalled();
expect(formComponent.getFormDefinitionByFormId).not.toHaveBeenCalled(); expect(formComponent.getFormDefinitionByFormId).not.toHaveBeenCalled();
@ -369,7 +344,7 @@ describe('FormComponent', () => {
it('should complete form on custom outcome click', () => { it('should complete form on custom outcome click', () => {
const formModel = new FormModel(); const formModel = new FormModel();
const outcomeName = 'Custom Action'; const outcomeName = 'Custom Action';
const outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName }); const outcome = new FormOutcomeModel(formModel, {id: 'custom1', name: outcomeName});
let saved = false; let saved = false;
formComponent.form = formModel; formComponent.form = formModel;
@ -434,7 +409,7 @@ describe('FormComponent', () => {
it('should do nothing when clicking outcome for readonly form', () => { it('should do nothing when clicking outcome for readonly form', () => {
const formModel = new FormModel(); const formModel = new FormModel();
const outcomeName = 'Custom Action'; const outcomeName = 'Custom Action';
const outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName }); const outcome = new FormOutcomeModel(formModel, {id: 'custom1', name: outcomeName});
formComponent.form = formModel; formComponent.form = formModel;
spyOn(formComponent, 'completeTaskForm').and.stub(); spyOn(formComponent, 'completeTaskForm').and.stub();
@ -453,7 +428,7 @@ describe('FormComponent', () => {
it('should require loaded form when clicking outcome', () => { it('should require loaded form when clicking outcome', () => {
const formModel = new FormModel(); const formModel = new FormModel();
const outcomeName = 'Custom Action'; const outcomeName = 'Custom Action';
const outcome = new FormOutcomeModel(formModel, { id: 'custom1', name: outcomeName }); const outcome = new FormOutcomeModel(formModel, {id: 'custom1', name: outcomeName});
formComponent.readOnly = false; formComponent.readOnly = false;
formComponent.form = null; formComponent.form = null;
@ -462,7 +437,7 @@ describe('FormComponent', () => {
it('should not execute unknown system outcome', () => { it('should not execute unknown system outcome', () => {
const formModel = new FormModel(); const formModel = new FormModel();
const outcome = new FormOutcomeModel(formModel, { id: 'unknown', name: 'Unknown', isSystem: true }); const outcome = new FormOutcomeModel(formModel, {id: 'unknown', name: 'Unknown', isSystem: true});
formComponent.form = formModel; formComponent.form = formModel;
expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy(); expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy();
@ -470,26 +445,26 @@ describe('FormComponent', () => {
it('should require custom action name to complete form', () => { it('should require custom action name to complete form', () => {
const formModel = new FormModel(); const formModel = new FormModel();
let outcome = new FormOutcomeModel(formModel, { id: 'custom' }); let outcome = new FormOutcomeModel(formModel, {id: 'custom'});
formComponent.form = formModel; formComponent.form = formModel;
expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy(); expect(formComponent.onOutcomeClicked(outcome)).toBeFalsy();
outcome = new FormOutcomeModel(formModel, { id: 'custom', name: 'Custom' }); outcome = new FormOutcomeModel(formModel, {id: 'custom', name: 'Custom'});
spyOn(formComponent, 'completeTaskForm').and.stub(); spyOn(formComponent, 'completeTaskForm').and.stub();
expect(formComponent.onOutcomeClicked(outcome)).toBeTruthy(); expect(formComponent.onOutcomeClicked(outcome)).toBeTruthy();
}); });
it('should fetch and parse form by task id', (done) => { it('should fetch and parse form by task id', (done) => {
spyOn(formService, 'getTask').and.returnValue(of({})); spyOn(taskService, 'getTask').and.returnValue(of(<TaskRepresentation>{}));
spyOn(formService, 'getTaskForm').and.callFake((currentTaskId) => new Observable((observer) => { spyOn(taskFormService, 'getTaskForm').and.callFake((currentTaskId) => new Observable((observer) => {
observer.next({ taskId: currentTaskId }); observer.next({taskId: currentTaskId});
observer.complete(); observer.complete();
})); }));
const taskId = '456'; const taskId = '456';
formComponent.formLoaded.subscribe(() => { formComponent.formLoaded.subscribe(() => {
expect(formService.getTaskForm).toHaveBeenCalledWith(taskId); expect(taskFormService.getTaskForm).toHaveBeenCalledWith(taskId);
expect(formComponent.form).toBeDefined(); expect(formComponent.form).toBeDefined();
expect(formComponent.form.taskId).toBe(taskId); expect(formComponent.form.taskId).toBe(taskId);
done(); done();
@ -502,9 +477,9 @@ describe('FormComponent', () => {
it('should handle error when getting form by task id', (done) => { it('should handle error when getting form by task id', (done) => {
const error = 'Some error'; const error = 'Some error';
spyOn(formService, 'getTask').and.returnValue(of({})); spyOn(taskService, 'getTask').and.returnValue(of(<TaskRepresentation>{}));
spyOn(formComponent, 'handleError').and.stub(); spyOn(formComponent, 'handleError').and.stub();
spyOn(formService, 'getTaskForm').and.callFake(() => throwError(error)); spyOn(taskFormService, 'getTaskForm').and.callFake(() => throwError(error));
formComponent.getFormByTaskId('123').then((_) => { formComponent.getFormByTaskId('123').then((_) => {
expect(formComponent.handleError).toHaveBeenCalledWith(error); expect(formComponent.handleError).toHaveBeenCalledWith(error);
@ -513,9 +488,9 @@ describe('FormComponent', () => {
}); });
it('should apply readonly state when getting form by task id', (done) => { it('should apply readonly state when getting form by task id', (done) => {
spyOn(formService, 'getTask').and.returnValue(of({})); spyOn(taskService, 'getTask').and.returnValue(of(<TaskRepresentation>{}));
spyOn(formService, 'getTaskForm').and.callFake((taskId) => new Observable((observer) => { spyOn(taskFormService, 'getTaskForm').and.callFake((taskId) => new Observable((observer) => {
observer.next({ taskId }); observer.next({taskId});
observer.complete(); observer.complete();
})); }));
@ -528,8 +503,8 @@ describe('FormComponent', () => {
}); });
it('should fetch and parse form definition by id', () => { it('should fetch and parse form definition by id', () => {
spyOn(formService, 'getFormDefinitionById').and.callFake((currentFormId) => new Observable((observer) => { spyOn(editorService, 'getFormDefinitionById').and.callFake((currentFormId) => new Observable((observer) => {
observer.next({ id: currentFormId }); observer.next({id: currentFormId});
observer.complete(); observer.complete();
})); }));
@ -549,20 +524,20 @@ describe('FormComponent', () => {
const error = 'Some error'; const error = 'Some error';
spyOn(formComponent, 'handleError').and.stub(); spyOn(formComponent, 'handleError').and.stub();
spyOn(formService, 'getFormDefinitionById').and.callFake(() => throwError(error)); spyOn(editorService, 'getFormDefinitionById').and.callFake(() => throwError(error));
formComponent.getFormDefinitionByFormId(123); formComponent.getFormDefinitionByFormId(123);
expect(formComponent.handleError).toHaveBeenCalledWith(error); expect(formComponent.handleError).toHaveBeenCalledWith(error);
}); });
it('should fetch and parse form definition by form name', () => { it('should fetch and parse form definition by form name', () => {
spyOn(formService, 'getFormDefinitionByName').and.callFake((currentFormName) => new Observable((observer) => { spyOn(modelService, 'getFormDefinitionByName').and.callFake((currentFormName) => new Observable((observer) => {
observer.next(currentFormName); observer.next(currentFormName);
observer.complete(); observer.complete();
})); }));
spyOn(formService, 'getFormDefinitionById').and.callFake((currentFormName) => new Observable((observer) => { spyOn(editorService, 'getFormDefinitionById').and.callFake((currentFormName) => new Observable((observer) => {
observer.next({ name: currentFormName }); observer.next({name: currentFormName});
observer.complete(); observer.complete();
})); }));
@ -574,13 +549,13 @@ describe('FormComponent', () => {
formComponent.getFormDefinitionByFormName(formName); formComponent.getFormDefinitionByFormName(formName);
expect(loaded).toBeTruthy(); expect(loaded).toBeTruthy();
expect(formService.getFormDefinitionByName).toHaveBeenCalledWith(formName); expect(modelService.getFormDefinitionByName).toHaveBeenCalledWith(formName);
expect(formComponent.form).toBeDefined(); expect(formComponent.form).toBeDefined();
expect(formComponent.form.name).toBe(formName); expect(formComponent.form.name).toBe(formName);
}); });
it('should save task form and raise corresponding event', () => { it('should save task form and raise corresponding event', () => {
spyOn(formService, 'saveTaskForm').and.callFake(() => new Observable((observer) => { spyOn(taskFormService, 'saveTaskForm').and.callFake(() => new Observable((observer) => {
observer.next(); observer.next();
observer.complete(); observer.complete();
})); }));
@ -595,31 +570,31 @@ describe('FormComponent', () => {
const formModel = new FormModel({ const formModel = new FormModel({
taskId: '123', taskId: '123',
fields: [ fields: [
{ id: 'field1' }, {id: 'field1'},
{ id: 'field2' } {id: 'field2'}
] ]
}); });
formComponent.form = formModel; formComponent.form = formModel;
formComponent.saveTaskForm(); formComponent.saveTaskForm();
expect(formService.saveTaskForm).toHaveBeenCalledWith(formModel.taskId, formModel.values); expect(taskFormService.saveTaskForm).toHaveBeenCalledWith(formModel.taskId, formModel.values);
expect(saved).toBeTruthy(); expect(saved).toBeTruthy();
expect(savedForm).toEqual(formModel); expect(savedForm).toEqual(formModel);
}); });
it('should handle error during form save', () => { it('should handle error during form save', () => {
const error = 'Error'; const error = 'Error';
spyOn(formService, 'saveTaskForm').and.callFake(() => throwError(error)); spyOn(taskFormService, 'saveTaskForm').and.callFake(() => throwError(error));
spyOn(formComponent, 'handleError').and.stub(); spyOn(formComponent, 'handleError').and.stub();
formComponent.form = new FormModel({ taskId: '123' }); formComponent.form = new FormModel({taskId: '123'});
formComponent.saveTaskForm(); formComponent.saveTaskForm();
expect(formComponent.handleError).toHaveBeenCalledWith(error); expect(formComponent.handleError).toHaveBeenCalledWith(error);
}); });
it('should require form with task id to save', () => { it('should require form with task id to save', () => {
spyOn(formService, 'saveTaskForm').and.stub(); spyOn(taskFormService, 'saveTaskForm').and.stub();
formComponent.form = null; formComponent.form = null;
formComponent.saveTaskForm(); formComponent.saveTaskForm();
@ -627,11 +602,11 @@ describe('FormComponent', () => {
formComponent.form = new FormModel(); formComponent.form = new FormModel();
formComponent.saveTaskForm(); formComponent.saveTaskForm();
expect(formService.saveTaskForm).not.toHaveBeenCalled(); expect(taskFormService.saveTaskForm).not.toHaveBeenCalled();
}); });
it('should require form with task id to complete', () => { it('should require form with task id to complete', () => {
spyOn(formService, 'completeTaskForm').and.stub(); spyOn(taskFormService, 'completeTaskForm').and.stub();
formComponent.form = null; formComponent.form = null;
formComponent.completeTaskForm('save'); formComponent.completeTaskForm('save');
@ -639,11 +614,11 @@ describe('FormComponent', () => {
formComponent.form = new FormModel(); formComponent.form = new FormModel();
formComponent.completeTaskForm('complete'); formComponent.completeTaskForm('complete');
expect(formService.completeTaskForm).not.toHaveBeenCalled(); expect(taskFormService.completeTaskForm).not.toHaveBeenCalled();
}); });
it('should complete form and raise corresponding event', () => { it('should complete form and raise corresponding event', () => {
spyOn(formService, 'completeTaskForm').and.callFake(() => new Observable((observer) => { spyOn(taskFormService, 'completeTaskForm').and.callFake(() => new Observable((observer) => {
observer.next(); observer.next();
observer.complete(); observer.complete();
})); }));
@ -655,15 +630,15 @@ describe('FormComponent', () => {
const formModel = new FormModel({ const formModel = new FormModel({
taskId: '123', taskId: '123',
fields: [ fields: [
{ id: 'field1' }, {id: 'field1'},
{ id: 'field2' } {id: 'field2'}
] ]
}); });
formComponent.form = formModel; formComponent.form = formModel;
formComponent.completeTaskForm(outcome); formComponent.completeTaskForm(outcome);
expect(formService.completeTaskForm).toHaveBeenCalledWith(formModel.taskId, formModel.values, outcome); expect(taskFormService.completeTaskForm).toHaveBeenCalledWith(formModel.taskId, formModel.values, outcome);
expect(completed).toBeTruthy(); expect(completed).toBeTruthy();
}); });
@ -675,7 +650,7 @@ describe('FormComponent', () => {
const form = formComponent.parseForm({ const form = formComponent.parseForm({
id: 1, id: 1,
fields: [ fields: [
{ id: 'field1', type: FormFieldTypes.CONTAINER } {id: 'field1', type: FormFieldTypes.CONTAINER}
] ]
}); });
@ -688,7 +663,7 @@ describe('FormComponent', () => {
it('should provide outcomes for form definition', () => { it('should provide outcomes for form definition', () => {
spyOn(formComponent, 'getFormDefinitionOutcomes').and.callThrough(); spyOn(formComponent, 'getFormDefinitionOutcomes').and.callThrough();
const form = formComponent.parseForm({ id: 1 }); const form = formComponent.parseForm({id: 1});
expect(formComponent.getFormDefinitionOutcomes).toHaveBeenCalledWith(form); expect(formComponent.getFormDefinitionOutcomes).toHaveBeenCalledWith(form);
}); });
@ -753,7 +728,7 @@ describe('FormComponent', () => {
const nodeId = '<id>'; const nodeId = '<id>';
const change = new SimpleChange(null, nodeId, false); const change = new SimpleChange(null, nodeId, false);
formComponent.ngOnChanges({ nodeId: change }); formComponent.ngOnChanges({nodeId: change});
expect(nodeService.getNodeMetadata).toHaveBeenCalledWith(nodeId); expect(nodeService.getNodeMetadata).toHaveBeenCalledWith(nodeId);
expect(formComponent.loadFormFromActiviti).toHaveBeenCalled(); expect(formComponent.loadFormFromActiviti).toHaveBeenCalled();
@ -973,10 +948,10 @@ describe('FormComponent', () => {
id: 'option_2', id: 'option_2',
name: 'test2' name: 'test2'
}; };
formValues.radio = { id: 'option_2', name: 'Option 2' }; formValues.radio = {id: 'option_2', name: 'Option 2'};
const change = new SimpleChange(null, formValues, false); const change = new SimpleChange(null, formValues, false);
formComponent.data = formValues; formComponent.data = formValues;
formComponent.ngOnChanges({ data: change }); formComponent.ngOnChanges({data: change});
formFields = formComponent.form.getFormFields(); formFields = formComponent.form.getFormFields();
dropdownField = formFields.find((field) => field.id === 'dropdownId'); dropdownField = formFields.find((field) => field.id === 'dropdownId');
@ -996,7 +971,7 @@ describe('FormComponent', () => {
formValues.radio = 'option_3'; formValues.radio = 'option_3';
const change = new SimpleChange(null, formValues, false); const change = new SimpleChange(null, formValues, false);
formComponent.data = formValues; formComponent.data = formValues;
formComponent.ngOnChanges({ data: change }); formComponent.ngOnChanges({data: change});
formFields = formComponent.form.getFormFields(); formFields = formComponent.form.getFormFields();
radioFieldById = formFields.find((field) => field.id === 'radio'); radioFieldById = formFields.find((field) => field.id === 'radio');
@ -1021,7 +996,7 @@ describe('FormComponent', () => {
class FormWithCustomOutComesComponent { class FormWithCustomOutComesComponent {
@ViewChild('adfForm', { static: true }) @ViewChild('adfForm', {static: true})
adfForm: FormComponent; adfForm: FormComponent;
onCustomButtonOneClick() { onCustomButtonOneClick() {
@ -1051,10 +1026,10 @@ describe('FormWithCustomOutComesComponent', () => {
debugElement = fixture.debugElement; debugElement = fixture.debugElement;
const formRepresentation = { const formRepresentation = {
fields: [ fields: [
{ id: 'container1' } {id: 'container1'}
], ],
outcomes: [ outcomes: [
{ id: 'outcome-1', name: 'outcome 1' } {id: 'outcome-1', name: 'outcome 1'}
] ]
}; };

View File

@ -15,13 +15,41 @@
* limitations under the License. * limitations under the License.
*/ */
import { Component, EventEmitter, Input, Output, ViewEncapsulation, SimpleChanges, OnInit, OnDestroy, OnChanges } from '@angular/core'; import {
import { EcmModelService, NodeService, WidgetVisibilityService, Component,
FormService, FormBaseComponent, FormOutcomeModel, EventEmitter,
FormEvent, FormErrorEvent, FormFieldModel, Input,
FormModel, FormOutcomeEvent, FormValues, ContentLinkModel } from '@alfresco/adf-core'; Output,
import { Observable, of, Subject } from 'rxjs'; ViewEncapsulation,
SimpleChanges,
OnInit,
OnDestroy,
OnChanges
} from '@angular/core';
import {
WidgetVisibilityService,
FormService,
FormBaseComponent,
FormOutcomeModel,
FormEvent,
FormErrorEvent,
FormFieldModel,
FormModel,
FormOutcomeEvent,
FormValues,
ContentLinkModel,
NodesApiService,
FormDefinitionModel,
TaskProcessVariableModel
} from '@alfresco/adf-core';
import { from, Observable, of, Subject } from 'rxjs';
import { switchMap, takeUntil } from 'rxjs/operators'; import { switchMap, takeUntil } from 'rxjs/operators';
import { EcmModelService } from './services/ecm-model.service';
import { ModelService } from './services/model.service';
import { EditorService } from './services/editor.service';
import { TaskService } from './services/task.service';
import { TaskFormService } from './services/task-form.service';
import { TaskRepresentation } from '@alfresco/js-api';
@Component({ @Component({
selector: 'adf-form', selector: 'adf-form',
@ -59,7 +87,7 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro
data: FormValues; data: FormValues;
/** The form will set a prefixed space for invisible fields. */ /** The form will set a prefixed space for invisible fields. */
@Input() @Input()
enableFixedSpacedForm: boolean = true; enableFixedSpacedForm: boolean = true;
/** Emitted when the form is submitted with the `Save` or custom outcomes. */ /** Emitted when the form is submitted with the `Save` or custom outcomes. */
@ -87,9 +115,13 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro
protected onDestroy$ = new Subject<boolean>(); protected onDestroy$ = new Subject<boolean>();
constructor(protected formService: FormService, constructor(protected formService: FormService,
protected taskFormService: TaskFormService,
protected taskService: TaskService,
protected editorService: EditorService,
protected modelService: ModelService,
protected visibilityService: WidgetVisibilityService, protected visibilityService: WidgetVisibilityService,
protected ecmModelService: EcmModelService, protected ecmModelService: EcmModelService,
protected nodeService: NodeService) { protected nodeService: NodesApiService) {
super(); super();
} }
@ -168,13 +200,13 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro
} }
} }
findProcessVariablesByTaskId(taskId: string): Observable<any> { findProcessVariablesByTaskId(taskId: string): Observable<TaskProcessVariableModel[]> {
return this.formService.getTask(taskId).pipe( return this.taskService.getTask(taskId).pipe(
switchMap((task: any) => { switchMap((task: TaskRepresentation) => {
if (this.isAProcessTask(task)) { if (this.isAProcessTask(task)) {
return this.visibilityService.getTaskProcessVariable(taskId); return this.taskFormService.getTaskProcessVariable(taskId);
} else { } else {
return of({}); return of([]);
} }
}) })
); );
@ -186,13 +218,13 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro
getFormByTaskId(taskId: string): Promise<FormModel> { getFormByTaskId(taskId: string): Promise<FormModel> {
return new Promise<FormModel>(resolve => { return new Promise<FormModel>(resolve => {
this.findProcessVariablesByTaskId(taskId).subscribe(() => { this.findProcessVariablesByTaskId(taskId).subscribe((taskProcessVariables) => {
this.formService this.taskFormService
.getTaskForm(taskId) .getTaskForm(taskId)
.subscribe( .subscribe(
(form) => { (form) => {
const parsedForm = this.parseForm(form); const parsedForm = this.parseForm(form);
this.visibilityService.refreshVisibility(parsedForm); this.visibilityService.refreshVisibility(parsedForm, taskProcessVariables);
parsedForm.validateForm(); parsedForm.validateForm();
this.form = parsedForm; this.form = parsedForm;
this.onFormLoaded(this.form); this.onFormLoaded(this.form);
@ -208,7 +240,7 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro
} }
getFormDefinitionByFormId(formId: number) { getFormDefinitionByFormId(formId: number) {
this.formService this.editorService
.getFormDefinitionById(formId) .getFormDefinitionById(formId)
.subscribe( .subscribe(
(form) => { (form) => {
@ -225,11 +257,11 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro
} }
getFormDefinitionByFormName(formName: string) { getFormDefinitionByFormName(formName: string) {
this.formService this.modelService
.getFormDefinitionByName(formName) .getFormDefinitionByName(formName)
.subscribe( .subscribe(
(id) => { (id) => {
this.formService.getFormDefinitionById(id).subscribe( this.editorService.getFormDefinitionById(id).subscribe(
(form) => { (form) => {
this.form = this.parseForm(form); this.form = this.parseForm(form);
this.visibilityService.refreshVisibility(this.form); this.visibilityService.refreshVisibility(this.form);
@ -249,7 +281,7 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro
saveTaskForm() { saveTaskForm() {
if (this.form && this.form.taskId) { if (this.form && this.form.taskId) {
this.formService this.taskFormService
.saveTaskForm(this.form.taskId, this.form.values) .saveTaskForm(this.form.taskId, this.form.values)
.subscribe( .subscribe(
() => { () => {
@ -263,7 +295,7 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro
completeTaskForm(outcome?: string) { completeTaskForm(outcome?: string) {
if (this.form && this.form.taskId) { if (this.form && this.form.taskId) {
this.formService this.taskFormService
.completeTaskForm(this.form.taskId, this.form.values, outcome) .completeTaskForm(this.form.taskId, this.form.values, outcome)
.subscribe( .subscribe(
() => { () => {
@ -300,7 +332,7 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro
*/ */
getFormDefinitionOutcomes(form: FormModel): FormOutcomeModel[] { getFormDefinitionOutcomes(form: FormModel): FormOutcomeModel[] {
return [ return [
new FormOutcomeModel(form, { id: '$save', name: FormOutcomeModel.SAVE_ACTION, isSystem: true }) new FormOutcomeModel(form, {id: '$save', name: FormOutcomeModel.SAVE_ACTION, isSystem: true})
]; ];
} }
@ -311,10 +343,10 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro
} }
loadFormFromActiviti(nodeType: string): any { loadFormFromActiviti(nodeType: string): any {
this.formService.searchFrom(nodeType).subscribe( this.modelService.searchFrom(nodeType).subscribe(
(form) => { (form) => {
if (!form) { if (!form) {
this.formService.createFormFromANode(nodeType).subscribe((formMetadata) => { this.createFormFromANode(nodeType).subscribe((formMetadata) => {
this.loadFormFromFormId(formMetadata.id); this.loadFormFromFormId(formMetadata.id);
}); });
} else { } else {
@ -327,6 +359,33 @@ export class FormComponent extends FormBaseComponent implements OnInit, OnDestro
); );
} }
/**
* Creates a Form with a field for each metadata property.
*
* @param formName Name of the new form
* @returns The new form
*/
createFormFromANode(formName: string): Observable<any> {
return new Observable((observer) => {
this.modelService.createForm(formName).subscribe(
(form) => {
this.ecmModelService.searchEcmType(formName, EcmModelService.MODEL_NAME).subscribe(
(customType) => {
const formDefinitionModel = new FormDefinitionModel(form.id, form.name, form.lastUpdatedByFullName, form.lastUpdated, customType.entry.properties);
from(
this.editorService.saveForm(form.id, formDefinitionModel)
).subscribe((formData) => {
observer.next(formData);
observer.complete();
}, (err) => this.handleError(err));
},
(err) => this.handleError(err));
},
(err) => this.handleError(err));
});
}
protected storeFormAsMetadata() { protected storeFormAsMetadata() {
if (this.saveMetadata) { if (this.saveMetadata) {
this.ecmModelService.createEcmTypeForActivitiForm(this.formName, this.form).subscribe((type) => { this.ecmModelService.createEcmTypeForActivitiForm(this.formName, this.form).subscribe((type) => {

View File

@ -21,17 +21,23 @@ import { of } from 'rxjs';
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { formDefinitionDropdownField, formDefinitionTwoTextFields, import {
formDefinitionRequiredField, FormService, setupTestBed, formDefinitionDropdownField, formDefinitionTwoTextFields,
formDefinitionRequiredField, setupTestBed,
formDefVisibilitiFieldDependsOnNextOne, formDefVisibilitiFieldDependsOnPreviousOne, formDefVisibilitiFieldDependsOnNextOne, formDefVisibilitiFieldDependsOnPreviousOne,
formReadonlyTwoTextFields } from '@alfresco/adf-core'; formReadonlyTwoTextFields
} from '@alfresco/adf-core';
import { FormComponent } from './form.component'; import { FormComponent } from './form.component';
import { ProcessTestingModule } from '../testing/process.testing.module'; import { ProcessTestingModule } from '../testing/process.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { TaskService } from './services/task.service';
import { TaskFormService } from './services/task-form.service';
import { TaskRepresentation } from '@alfresco/js-api';
describe('FormComponent UI and visibility', () => { describe('FormComponent UI and visibility', () => {
let component: FormComponent; let component: FormComponent;
let service: FormService; let taskService: TaskService;
let taskFormService: TaskFormService;
let fixture: ComponentFixture<FormComponent>; let fixture: ComponentFixture<FormComponent>;
const openSelect = () => { const openSelect = () => {
@ -50,7 +56,8 @@ describe('FormComponent UI and visibility', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(FormComponent); fixture = TestBed.createComponent(FormComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
service = TestBed.inject(FormService); taskService = TestBed.inject(TaskService);
taskFormService = TestBed.inject(TaskFormService);
}); });
afterEach(() => { afterEach(() => {
@ -60,11 +67,11 @@ describe('FormComponent UI and visibility', () => {
describe('Validation icon', () => { describe('Validation icon', () => {
it('should display valid icon for valid form', () => { it('should display valid icon for valid form', () => {
spyOn(service, 'getTask').and.returnValue(of({})); spyOn(taskService, 'getTask').and.returnValue(of(<TaskRepresentation>{}));
spyOn(service, 'getTaskForm').and.returnValue(of(formDefinitionTwoTextFields)); spyOn(taskFormService, 'getTaskForm').and.returnValue(of(formDefinitionTwoTextFields));
const change = new SimpleChange(null, 1, true); const change = new SimpleChange(null, 1, true);
component.ngOnChanges({ taskId: change }); component.ngOnChanges({taskId: change});
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).toBeDefined(); expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).toBeDefined();
expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).not.toBeNull(); expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).not.toBeNull();
@ -72,11 +79,11 @@ describe('FormComponent UI and visibility', () => {
}); });
it('should display invalid icon for valid form', () => { it('should display invalid icon for valid form', () => {
spyOn(service, 'getTask').and.returnValue(of({})); spyOn(taskService, 'getTask').and.returnValue(of(<TaskRepresentation>{}));
spyOn(service, 'getTaskForm').and.returnValue(of(formDefinitionRequiredField)); spyOn(taskFormService, 'getTaskForm').and.returnValue(of(formDefinitionRequiredField));
const change = new SimpleChange(null, 1, true); const change = new SimpleChange(null, 1, true);
component.ngOnChanges({ taskId: change }); component.ngOnChanges({taskId: change});
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).toBeNull(); expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).toBeNull();
expect(fixture.debugElement.query(By.css('#adf-invalid-form-icon'))).toBeDefined(); expect(fixture.debugElement.query(By.css('#adf-invalid-form-icon'))).toBeDefined();
@ -84,11 +91,11 @@ describe('FormComponent UI and visibility', () => {
}); });
it('should NOT display validation icon when [showValidationIcon] is false', () => { it('should NOT display validation icon when [showValidationIcon] is false', () => {
spyOn(service, 'getTask').and.returnValue(of({})); spyOn(taskService, 'getTask').and.returnValue(of(<TaskRepresentation>{}));
spyOn(service, 'getTaskForm').and.returnValue(of(formDefinitionTwoTextFields)); spyOn(taskFormService, 'getTaskForm').and.returnValue(of(formDefinitionTwoTextFields));
const change = new SimpleChange(null, 1, true); const change = new SimpleChange(null, 1, true);
component.ngOnChanges({ taskId: change }); component.ngOnChanges({taskId: change});
component.showValidationIcon = false; component.showValidationIcon = false;
fixture.detectChanges(); fixture.detectChanges();
expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).toBeNull(); expect(fixture.debugElement.query(By.css('#adf-valid-form-icon'))).toBeNull();
@ -99,11 +106,11 @@ describe('FormComponent UI and visibility', () => {
describe('form definition', () => { describe('form definition', () => {
it('should display two text fields form definition', () => { it('should display two text fields form definition', () => {
spyOn(service, 'getTask').and.returnValue(of({})); spyOn(taskService, 'getTask').and.returnValue(of(<TaskRepresentation>{}));
spyOn(service, 'getTaskForm').and.returnValue(of(formDefinitionTwoTextFields)); spyOn(taskFormService, 'getTaskForm').and.returnValue(of(formDefinitionTwoTextFields));
const change = new SimpleChange(null, 1, true); const change = new SimpleChange(null, 1, true);
component.ngOnChanges({ taskId: change }); component.ngOnChanges({taskId: change});
fixture.detectChanges(); fixture.detectChanges();
const firstNameEl = fixture.debugElement.query(By.css('#firstname')); const firstNameEl = fixture.debugElement.query(By.css('#firstname'));
@ -116,11 +123,11 @@ describe('FormComponent UI and visibility', () => {
}); });
it('should display dropdown field', async () => { it('should display dropdown field', async () => {
spyOn(service, 'getTask').and.returnValue(of({})); spyOn(taskService, 'getTask').and.returnValue(of(<TaskRepresentation>{}));
spyOn(service, 'getTaskForm').and.returnValue(of(formDefinitionDropdownField)); spyOn(taskFormService, 'getTaskForm').and.returnValue(of(formDefinitionDropdownField));
const change = new SimpleChange(null, 1, true); const change = new SimpleChange(null, 1, true);
component.ngOnChanges({ taskId: change }); component.ngOnChanges({taskId: change});
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();
@ -150,11 +157,11 @@ describe('FormComponent UI and visibility', () => {
describe('Visibility conditions', () => { describe('Visibility conditions', () => {
it('should hide the field based on the next one', () => { it('should hide the field based on the next one', () => {
spyOn(service, 'getTask').and.returnValue(of({})); spyOn(taskService, 'getTask').and.returnValue(of(<TaskRepresentation>{}));
spyOn(service, 'getTaskForm').and.returnValue(of(formDefVisibilitiFieldDependsOnNextOne)); spyOn(taskFormService, 'getTaskForm').and.returnValue(of(formDefVisibilitiFieldDependsOnNextOne));
const change = new SimpleChange(null, 1, true); const change = new SimpleChange(null, 1, true);
component.ngOnChanges({ taskId: change }); component.ngOnChanges({taskId: change});
fixture.detectChanges(); fixture.detectChanges();
const firstEl = fixture.debugElement.query(By.css('#field-country-container')); const firstEl = fixture.debugElement.query(By.css('#field-country-container'));
@ -167,11 +174,11 @@ describe('FormComponent UI and visibility', () => {
}); });
it('should hide the field based on the previous one', () => { it('should hide the field based on the previous one', () => {
spyOn(service, 'getTask').and.returnValue(of({})); spyOn(taskService, 'getTask').and.returnValue(of(<TaskRepresentation>{}));
spyOn(service, 'getTaskForm').and.returnValue(of(formDefVisibilitiFieldDependsOnPreviousOne)); spyOn(taskFormService, 'getTaskForm').and.returnValue(of(formDefVisibilitiFieldDependsOnPreviousOne));
const change = new SimpleChange(null, 1, true); const change = new SimpleChange(null, 1, true);
component.ngOnChanges({ taskId: change }); component.ngOnChanges({taskId: change});
fixture.detectChanges(); fixture.detectChanges();
const firstEl = fixture.debugElement.query(By.css('#name')); const firstEl = fixture.debugElement.query(By.css('#name'));
@ -184,11 +191,11 @@ describe('FormComponent UI and visibility', () => {
}); });
it('should show the hidden field when the visibility condition change to true', () => { it('should show the hidden field when the visibility condition change to true', () => {
spyOn(service, 'getTask').and.returnValue(of({})); spyOn(taskService, 'getTask').and.returnValue(of(<TaskRepresentation>{}));
spyOn(service, 'getTaskForm').and.returnValue(of(formDefVisibilitiFieldDependsOnNextOne)); spyOn(taskFormService, 'getTaskForm').and.returnValue(of(formDefVisibilitiFieldDependsOnNextOne));
const change = new SimpleChange(null, 1, true); const change = new SimpleChange(null, 1, true);
component.ngOnChanges({ taskId: change }); component.ngOnChanges({taskId: change});
fixture.detectChanges(); fixture.detectChanges();
let firstEl = fixture.debugElement.query(By.css('#field-country-container')); let firstEl = fixture.debugElement.query(By.css('#field-country-container'));
@ -209,11 +216,11 @@ describe('FormComponent UI and visibility', () => {
describe('Readonly Form', () => { describe('Readonly Form', () => {
it('should display two text fields readonly', async () => { it('should display two text fields readonly', async () => {
spyOn(service, 'getTask').and.returnValue(of({})); spyOn(taskService, 'getTask').and.returnValue(of(<TaskRepresentation>{}));
spyOn(service, 'getTaskForm').and.returnValue(of(formReadonlyTwoTextFields)); spyOn(taskFormService, 'getTaskForm').and.returnValue(of(formReadonlyTwoTextFields));
const change = new SimpleChange(null, 1, true); const change = new SimpleChange(null, 1, true);
component.ngOnChanges({ taskId: change }); component.ngOnChanges({taskId: change});
fixture.detectChanges(); fixture.detectChanges();
await fixture.whenStable(); await fixture.whenStable();

View File

@ -21,21 +21,48 @@ import { CoreModule } from '@alfresco/adf-core';
import { FormComponent } from './form.component'; import { FormComponent } from './form.component';
import { StartFormComponent } from './start-form.component'; import { StartFormComponent } from './start-form.component';
import { FormCustomOutcomesComponent } from './form-custom-outcomes.component'; import { FormCustomOutcomesComponent } from './form-custom-outcomes.component';
import { DocumentWidgetComponent } from './widgets/document/document.widget';
import { ContentWidgetComponent } from './widgets/document/content.widget';
import { UploadWidgetComponent } from './widgets/upload/upload.widget';
import { FormListComponent } from './form-list/form-list.component';
import { FunctionalGroupWidgetComponent } from './widgets/functional-group/functional-group.widget';
import { PeopleWidgetComponent } from './widgets/people/people.widget';
import { RadioButtonsWidgetComponent } from './widgets/radio-buttons/radio-buttons.widget';
import { TypeaheadWidgetComponent } from './widgets/typeahead/typeahead.widget';
import { DropdownWidgetComponent } from './widgets/dropdown/dropdown.widget';
import { DynamicTableModule } from './widgets/dynamic-table/dynamic-table.module';
@NgModule({ @NgModule({
imports: [ imports: [
DynamicTableModule,
CoreModule, CoreModule,
MaterialModule MaterialModule
], ],
declarations: [ declarations: [
UploadWidgetComponent,
FormComponent, FormComponent,
StartFormComponent, StartFormComponent,
FormCustomOutcomesComponent FormCustomOutcomesComponent,
DocumentWidgetComponent,
ContentWidgetComponent,
PeopleWidgetComponent,
FunctionalGroupWidgetComponent,
FormListComponent,
RadioButtonsWidgetComponent,
DropdownWidgetComponent,
TypeaheadWidgetComponent
], ],
exports: [ exports: [
FormComponent, FormComponent,
StartFormComponent, StartFormComponent,
FormCustomOutcomesComponent FormCustomOutcomesComponent,
PeopleWidgetComponent,
FunctionalGroupWidgetComponent,
RadioButtonsWidgetComponent,
TypeaheadWidgetComponent,
DropdownWidgetComponent,
FormListComponent
] ]
}) })
export class FormModule {} export class FormModule {
}

View File

@ -0,0 +1,103 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 { ProcessFormRenderingService } from './process-form-rendering.service';
import { FormFieldModel, FormFieldTypes } from '@alfresco/adf-core';
import { AttachFolderWidgetComponent } from './widgets/content-widget/attach-folder-widget.component';
import { DropdownWidgetComponent } from './widgets/dropdown/dropdown.widget';
import { DynamicTableWidgetComponent } from './widgets/dynamic-table/dynamic-table.widget';
import { FunctionalGroupWidgetComponent } from './widgets/functional-group/functional-group.widget';
import { PeopleWidgetComponent } from './widgets/people/people.widget';
import { RadioButtonsWidgetComponent } from './widgets/radio-buttons/radio-buttons.widget';
import { TypeaheadWidgetComponent } from './widgets/typeahead/typeahead.widget';
import { DocumentWidgetComponent } from './widgets/document/document.widget';
import { AttachFileWidgetComponent } from './widgets/content-widget/attach-file-widget.component';
describe('ProcessFormRenderingService', () => {
let service: ProcessFormRenderingService;
beforeEach(() => {
service = new ProcessFormRenderingService();
});
it('should resolve Upload field as Upload widget', () => {
const field = new FormFieldModel(null, {
type: FormFieldTypes.UPLOAD,
params: {
link: null
}
});
const type = service.resolveComponentType(field);
expect(type).toBe(AttachFileWidgetComponent);
});
it('should resolve Upload widget for Upload', () => {
const resolver = service.getComponentTypeResolver(FormFieldTypes.UPLOAD);
const type = resolver(null);
expect(type).toBe(AttachFileWidgetComponent);
});
it('should resolve Upload widget for dropdown', () => {
const resolver = service.getComponentTypeResolver(FormFieldTypes.DROPDOWN);
const type = resolver(null);
expect(type).toBe(DropdownWidgetComponent);
});
it('should resolve Upload widget for typeahead', () => {
const resolver = service.getComponentTypeResolver(FormFieldTypes.TYPEAHEAD);
const type = resolver(null);
expect(type).toBe(TypeaheadWidgetComponent);
});
it('should resolve Upload widget for radio button', () => {
const resolver = service.getComponentTypeResolver(FormFieldTypes.RADIO_BUTTONS);
const type = resolver(null);
expect(type).toBe(RadioButtonsWidgetComponent);
});
it('should resolve Upload widget for select folder', () => {
const resolver = service.getComponentTypeResolver(FormFieldTypes.ATTACH_FOLDER);
const type = resolver(null);
expect(type).toBe(AttachFolderWidgetComponent);
});
it('should resolve Upload widget for document', () => {
const resolver = service.getComponentTypeResolver(FormFieldTypes.DOCUMENT);
const type = resolver(null);
expect(type).toBe(DocumentWidgetComponent);
});
it('should resolve Upload widget for people', () => {
const resolver = service.getComponentTypeResolver(FormFieldTypes.PEOPLE);
const type = resolver(null);
expect(type).toBe(PeopleWidgetComponent);
});
it('should resolve Upload widget for group', () => {
const resolver = service.getComponentTypeResolver(FormFieldTypes.FUNCTIONAL_GROUP);
const type = resolver(null);
expect(type).toBe(FunctionalGroupWidgetComponent);
});
it('should resolve Upload widget for dynamic table', () => {
const resolver = service.getComponentTypeResolver(FormFieldTypes.DYNAMIC_TABLE);
const type = resolver(null);
expect(type).toBe(DynamicTableWidgetComponent);
});
});

View File

@ -16,9 +16,19 @@
*/ */
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { FormRenderingService } from '@alfresco/adf-core'; import {
import { AttachFileWidgetComponent } from '../content-widget/attach-file-widget.component'; FormFieldTypes,
import { AttachFolderWidgetComponent } from '../content-widget/attach-folder-widget.component'; FormRenderingService
} from '@alfresco/adf-core';
import { AttachFileWidgetComponent } from './widgets/content-widget/attach-file-widget.component';
import { AttachFolderWidgetComponent } from './widgets/content-widget/attach-folder-widget.component';
import { DocumentWidgetComponent } from './widgets/document/document.widget';
import { PeopleWidgetComponent } from './widgets/people/people.widget';
import { FunctionalGroupWidgetComponent } from './widgets/functional-group/functional-group.widget';
import { RadioButtonsWidgetComponent } from './widgets/radio-buttons/radio-buttons.widget';
import { TypeaheadWidgetComponent } from './widgets/typeahead/typeahead.widget';
import { DynamicTableWidgetComponent } from './widgets/dynamic-table/dynamic-table.widget';
import { DropdownWidgetComponent } from './widgets/dropdown/dropdown.widget';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -28,8 +38,15 @@ export class ProcessFormRenderingService extends FormRenderingService {
super(); super();
this.register({ this.register({
upload: () => AttachFileWidgetComponent, [FormFieldTypes.DROPDOWN]: () => DropdownWidgetComponent,
'select-folder': () => AttachFolderWidgetComponent [FormFieldTypes.TYPEAHEAD]: () => TypeaheadWidgetComponent,
[FormFieldTypes.RADIO_BUTTONS]: () => RadioButtonsWidgetComponent,
[FormFieldTypes.UPLOAD]: () => AttachFileWidgetComponent,
[FormFieldTypes.ATTACH_FOLDER]: () => AttachFolderWidgetComponent,
[FormFieldTypes.DOCUMENT]: () => DocumentWidgetComponent,
[FormFieldTypes.PEOPLE]: () => PeopleWidgetComponent,
[FormFieldTypes.FUNCTIONAL_GROUP]: () => FunctionalGroupWidgetComponent,
[FormFieldTypes.DYNAMIC_TABLE]: () => DynamicTableWidgetComponent
}, true); }, true);
} }
} }

View File

@ -15,8 +15,23 @@
* limitations under the License. * limitations under the License.
*/ */
export * from './widgets/index';
export * from './services/ecm-model.service';
export * from './services/editor.service';
export * from './services/process-content.service';
export * from './services/task.service';
export * from './services/task-form.service';
export * from './services/process-definition.service';
export * from './services/activiti-alfresco.service';
export * from './process-form-rendering.service';
export * from './events/validate-dynamic-table-row.event';
export * from './form-list/form-list.component';
export * from './form.component'; export * from './form.component';
export * from './start-form.component'; export * from './start-form.component';
export * from './process-form-rendering.service';
export * from './form-custom-outcomes.component'; export * from './form-custom-outcomes.component';
export * from './form.module'; export * from './form.module';

View File

@ -15,9 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { AlfrescoApiService } from '../../services/alfresco-api.service'; import { AlfrescoApiService, LogService, SitesService, ExternalContent, ExternalContentLink } from '@alfresco/adf-core';
import { LogService } from '../../services/log.service';
import { SitesService } from '../../services/sites.service';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { import {
IntegrationAlfrescoOnPremiseApi, IntegrationAlfrescoOnPremiseApi,
@ -26,8 +24,6 @@ import {
ActivitiContentApi ActivitiContentApi
} from '@alfresco/js-api'; } from '@alfresco/js-api';
import { Observable, from, throwError } from 'rxjs'; import { Observable, from, throwError } from 'rxjs';
import { ExternalContent } from '../components/widgets/core/external-content';
import { ExternalContentLink } from '../components/widgets/core/external-content-link';
import { map, catchError } from 'rxjs/operators'; import { map, catchError } from 'rxjs/operators';
@Injectable({ @Injectable({

View File

@ -16,11 +16,9 @@
*/ */
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { FormModel } from '../components/widgets/core/form.model'; import { FormModel, setupTestBed, CoreTestingModule } from '@alfresco/adf-core';
import { EcmModelService } from './ecm-model.service'; import { EcmModelService } from './ecm-model.service';
import { setupTestBed } from '../../testing/setup-test-bed';
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
declare let jasmine: any; declare let jasmine: any;

View File

@ -15,11 +15,9 @@
* limitations under the License. * limitations under the License.
*/ */
import { LogService } from '../../services/log.service'; import { AlfrescoApiService, LogService, FormModel } from '@alfresco/adf-core';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, from } from 'rxjs'; import { Observable, from } from 'rxjs';
import { FormModel } from '../components/widgets/core/form.model';
import { map, catchError } from 'rxjs/operators'; import { map, catchError } from 'rxjs/operators';
import { CustomModelApi } from '@alfresco/js-api'; import { CustomModelApi } from '@alfresco/js-api';
@ -216,7 +214,7 @@ export class EcmModelService {
return {}; return {};
} }
handleError(err: any): any { private handleError(err: any): any {
this.logService.error(err); this.logService.error(err);
} }
} }

View File

@ -0,0 +1,97 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 { AlfrescoApiService, FormDefinitionModel, LogService } from '@alfresco/adf-core';
import { Injectable } from '@angular/core';
import { Observable, from, throwError } from 'rxjs';
import { FormModelsApi } from '@alfresco/js-api';
import { catchError, map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class EditorService {
static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error';
static GENERIC_ERROR_MESSAGE: string = 'Server error';
_editorApi: FormModelsApi;
get editorApi(): FormModelsApi {
this._editorApi = this._editorApi ?? new FormModelsApi(this.apiService.getInstance());
return this._editorApi;
}
constructor(private apiService: AlfrescoApiService, private logService: LogService) {
}
/**
* Saves a form.
*
* @param formId ID of the form to save
* @param formModel Model data for the form
* @returns Data for the saved form
*/
saveForm(formId: number, formModel: FormDefinitionModel): Observable<any> {
return from(
this.editorApi.saveForm(formId, formModel)
);
}
/**
* Gets a form definition.
*
* @param formId ID of the target form
* @returns Form definition
*/
getFormDefinitionById(formId: number): Observable<any> {
return from(this.editorApi.getForm(formId))
.pipe(
map(this.toJson),
catchError((err) => this.handleError(err))
);
}
/**
* Creates a JSON representation of form data.
*
* @param res Object representing form data
* @returns JSON data
*/
toJson(res: any) {
if (res) {
return res || {};
}
return {};
}
/**
* Reports an error message.
*
* @param error Data object with optional `message` and `status` fields for the error
* @returns Error message
*/
private handleError(error: any): Observable<any> {
let errMsg = EditorService.UNKNOWN_ERROR_MESSAGE;
if (error) {
errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : EditorService.GENERIC_ERROR_MESSAGE;
}
this.logService.error(errMsg);
return throwError(errMsg);
}
}

View File

@ -0,0 +1,162 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 { AlfrescoApiService, LogService } from '@alfresco/adf-core';
import { Injectable } from '@angular/core';
import { Observable, from, throwError } from 'rxjs';
import { ModelsApi } from '@alfresco/js-api';
import { catchError, map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class ModelService {
static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error';
static GENERIC_ERROR_MESSAGE: string = 'Server error';
_modelsApi: ModelsApi;
get modelsApi(): ModelsApi {
this._modelsApi = this._modelsApi ?? new ModelsApi(this.apiService.getInstance());
return this._modelsApi;
}
constructor(private apiService: AlfrescoApiService, private logService: LogService) {
}
/**
* Create a Form.
*
* @param formName Name of the new form
* @returns The new form
*/
createForm(formName: string): Observable<any> {
const dataModel = {
name: formName,
description: '',
modelType: 2,
stencilSet: 0
};
return from(
this.modelsApi.createModel(dataModel)
);
}
/**
* Gets all the forms.
*
* @returns List of form models
*/
getForms(): Observable<any> {
const opts = {
modelType: 2
};
return from(this.modelsApi.getModels(opts))
.pipe(
map(this.toJsonArray),
catchError((err) => this.handleError(err))
);
}
/**
* Creates a JSON array representation of form data.
*
* @param res Object representing form data
* @returns JSON data
*/
toJsonArray(res: any) {
if (res) {
return res.data || [];
}
return [];
}
/**
* Searches for a form by name.
*
* @param name The form name to search for
* @returns Form model(s) matching the search name
*/
searchFrom(name: string): Observable<any> {
const opts = {
modelType: 2
};
return from(
this.modelsApi.getModels(opts)
)
.pipe(
map((forms: any) => forms.data.find((formData) => formData.name === name)),
catchError((err) => this.handleError(err))
);
}
/**
* Gets the form definition with a given name.
*
* @param name The form name
* @returns Form definition
*/
getFormDefinitionByName(name: string): Observable<any> {
const opts = {
filter: 'myReusableForms',
filterText: name,
modelType: 2
};
return from(this.modelsApi.getModels(opts))
.pipe(
map(this.getFormId),
catchError((err) => this.handleError(err))
);
}
/**
* Gets the ID of a form.
*
* @param form Object representing a form
* @returns ID string
*/
getFormId(form: any): string {
let result = null;
if (form && form.data && form.data.length > 0) {
result = form.data[0].id;
}
return result;
}
/**
* Reports an error message.
*
* @param error Data object with optional `message` and `status` fields for the error
* @returns Error message
*/
private handleError(error: any): Observable<any> {
let errMsg = ModelService.UNKNOWN_ERROR_MESSAGE;
if (error) {
errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : ModelService.GENERIC_ERROR_MESSAGE;
}
this.logService.error(errMsg);
return throwError(errMsg);
}
}

View File

@ -18,9 +18,8 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { ProcessContentService } from './process-content.service'; import { ProcessContentService } from './process-content.service';
import { setupTestBed } from '../../testing/setup-test-bed';
import { CoreTestingModule } from '../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { setupTestBed, CoreTestingModule } from '@alfresco/adf-core';
declare let jasmine: any; declare let jasmine: any;

View File

@ -15,8 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { AlfrescoApiService } from '../../services/alfresco-api.service'; import { AlfrescoApiService, LogService } from '@alfresco/adf-core';
import { LogService } from '../../services/log.service';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { ActivitiContentApi, RelatedContentRepresentation } from '@alfresco/js-api'; import { ActivitiContentApi, RelatedContentRepresentation } from '@alfresco/js-api';
import { Observable, from, throwError } from 'rxjs'; import { Observable, from, throwError } from 'rxjs';

View File

@ -0,0 +1,100 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 { AlfrescoApiService, LogService } from '@alfresco/adf-core';
import { Injectable } from '@angular/core';
import { Observable, from, throwError } from 'rxjs';
import { ProcessDefinitionsApi } from '@alfresco/js-api';
import { catchError } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class ProcessDefinitionService {
static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error';
static GENERIC_ERROR_MESSAGE: string = 'Server error';
_processDefinitionsApi: ProcessDefinitionsApi;
get processDefinitionsApi(): ProcessDefinitionsApi {
this._processDefinitionsApi = this._processDefinitionsApi ?? new ProcessDefinitionsApi(this.apiService.getInstance());
return this._processDefinitionsApi;
}
constructor(private apiService: AlfrescoApiService, private logService: LogService) {
}
/**
* Gets values of fields populated by a REST backend using a process ID.
*
* @param processDefinitionId Process identifier
* @param field Field identifier
* @returns Field values
*/
getRestFieldValuesByProcessId(processDefinitionId: string, field: string): Observable<any> {
return from(this.processDefinitionsApi.getRestFieldValues(processDefinitionId, field))
.pipe(
catchError((err) => this.handleError(err))
);
}
/**
* Gets column values of fields populated by a REST backend using a process ID.
*
* @param processDefinitionId Process identifier
* @param field Field identifier
* @param column Column identifier
* @returns Field values
*/
getRestFieldValuesColumnByProcessId(processDefinitionId: string, field: string, column?: string): Observable<any> {
return from(this.processDefinitionsApi.getRestTableFieldValues(processDefinitionId, field, column))
.pipe(
catchError((err) => this.handleError(err))
);
}
/**
* Creates a JSON representation of form data.
*
* @param res Object representing form data
* @returns JSON data
*/
toJson(res: any) {
if (res) {
return res || {};
}
return {};
}
/**
* Reports an error message.
*
* @param error Data object with optional `message` and `status` fields for the error
* @returns Error message
*/
private handleError(error: any): Observable<any> {
let errMsg = ProcessDefinitionService.UNKNOWN_ERROR_MESSAGE;
if (error) {
errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : ProcessDefinitionService.GENERIC_ERROR_MESSAGE;
}
this.logService.error(errMsg);
return throwError(errMsg);
}
}

View File

@ -0,0 +1,157 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 { AlfrescoApiService, FormValues, LogService, TaskProcessVariableModel } from '@alfresco/adf-core';
import { Injectable } from '@angular/core';
import { from, Observable, throwError } from 'rxjs';
import { CompleteFormRepresentation, SaveFormRepresentation, TaskFormsApi } from '@alfresco/js-api';
import { catchError, map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class TaskFormService {
static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error';
static GENERIC_ERROR_MESSAGE: string = 'Server error';
_taskFormsApi: TaskFormsApi;
get taskFormsApi(): TaskFormsApi {
this._taskFormsApi = this._taskFormsApi ?? new TaskFormsApi(this.apiService.getInstance());
return this._taskFormsApi;
}
constructor(private apiService: AlfrescoApiService, private logService: LogService) {
}
/**
* Saves a task form.
*
* @param taskId Task Id
* @param formValues Form Values
* @returns Null response when the operation is complete
*/
saveTaskForm(taskId: string, formValues: FormValues): Observable<any> {
const saveFormRepresentation = { values: formValues } as SaveFormRepresentation;
return from(this.taskFormsApi.saveTaskForm(taskId, saveFormRepresentation))
.pipe(
catchError((err) => this.handleError(err))
);
}
/**
* Completes a Task Form.
*
* @param taskId Task Id
* @param formValues Form Values
* @param outcome Form Outcome
* @returns Null response when the operation is complete
*/
completeTaskForm(taskId: string, formValues: FormValues, outcome?: string): Observable<any> {
const completeFormRepresentation = { values: formValues } as CompleteFormRepresentation;
if (outcome) {
completeFormRepresentation.outcome = outcome;
}
return from(this.taskFormsApi.completeTaskForm(taskId, completeFormRepresentation))
.pipe(
catchError((err) => this.handleError(err))
);
}
/**
* Gets a form related to a task.
*
* @param taskId ID of the target task
* @returns Form definition
*/
getTaskForm(taskId: string): Observable<any> {
return from(this.taskFormsApi.getTaskForm(taskId))
.pipe(
map(this.toJson),
catchError((err) => this.handleError(err))
);
}
/**
* Gets values of fields populated by a REST backend.
*
* @param taskId Task identifier
* @param field Field identifier
* @returns Field values
*/
getRestFieldValues(taskId: string, field: string): Observable<any> {
return from(this.taskFormsApi.getRestFieldValues(taskId, field))
.pipe(
catchError((err) => this.handleError(err))
);
}
/**
* Gets column values of fields populated by a REST backend.
*
* @param taskId Task identifier
* @param field Field identifier
* @param column Column identifier
* @returns Field values
*/
getRestFieldValuesColumn(taskId: string, field: string, column?: string): Observable<any> {
return from(this.taskFormsApi.getRestFieldColumnValues(taskId, field, column))
.pipe(
catchError((err) => this.handleError(err))
);
}
getTaskProcessVariable(taskId: string): Observable<TaskProcessVariableModel[]> {
return from(this.taskFormsApi.getTaskFormVariables(taskId))
.pipe(
map((res) => this.toJson(res)),
catchError((err) => this.handleError(err))
);
}
/**
* Creates a JSON representation of form data.
*
* @param res Object representing form data
* @returns JSON data
*/
toJson(res: any) {
if (res) {
return res || {};
}
return {};
}
/**
* Reports an error message.
*
* @param error Data object with optional `message` and `status` fields for the error
* @returns Error message
*/
private handleError(error: any): Observable<any> {
let errMsg = TaskFormService.UNKNOWN_ERROR_MESSAGE;
if (error) {
errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : TaskFormService.GENERIC_ERROR_MESSAGE;
}
this.logService.error(errMsg);
return throwError(errMsg);
}
}

View File

@ -0,0 +1,85 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* 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 { AlfrescoApiService, LogService } from '@alfresco/adf-core';
import { Injectable } from '@angular/core';
import { Observable, from, throwError } from 'rxjs';
import { TaskRepresentation, TasksApi } from '@alfresco/js-api';
import { catchError, map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class TaskService {
static UNKNOWN_ERROR_MESSAGE: string = 'Unknown error';
static GENERIC_ERROR_MESSAGE: string = 'Server error';
_taskApi: TasksApi;
get taskApi(): TasksApi {
this._taskApi = this._taskApi ?? new TasksApi(this.apiService.getInstance());
return this._taskApi;
}
constructor(private apiService: AlfrescoApiService, private logService: LogService) {
}
/**
* Gets a task.
*
* @param taskId Task Id
* @returns Task info
*/
getTask(taskId: string): Observable<TaskRepresentation> {
return from(this.taskApi.getTask(taskId))
.pipe(
map(this.toJson),
catchError((err) => this.handleError(err))
);
}
/**
* Creates a JSON representation of form data.
*
* @param res Object representing form data
* @returns JSON data
*/
toJson(res: any) {
if (res) {
return res || {};
}
return {};
}
/**
* Reports an error message.
*
* @param error Data object with optional `message` and `status` fields for the error
* @returns Error message
*/
private handleError(error: any): Observable<any> {
let errMsg = TaskService.UNKNOWN_ERROR_MESSAGE;
if (error) {
errMsg = (error.message) ? error.message :
error.status ? `${error.status} - ${error.statusText}` : TaskService.GENERIC_ERROR_MESSAGE;
}
this.logService.error(errMsg);
return throwError(errMsg);
}
}

View File

@ -25,18 +25,19 @@ import {
taskFormSingleUploadMock, taskFormMultipleUploadMock, preselectedSingleNode, preselectedMultipleeNode taskFormSingleUploadMock, taskFormMultipleUploadMock, preselectedSingleNode, preselectedMultipleeNode
} from './start-form.component.mock'; } from './start-form.component.mock';
import { StartFormComponent } from './start-form.component'; import { StartFormComponent } from './start-form.component';
import { FormService, WidgetVisibilityService, setupTestBed, FormModel, FormOutcomeModel } from '@alfresco/adf-core'; import { WidgetVisibilityService, setupTestBed, FormModel, FormOutcomeModel } from '@alfresco/adf-core';
import { TranslateService, TranslateModule } from '@ngx-translate/core'; import { TranslateService, TranslateModule } from '@ngx-translate/core';
import { ProcessTestingModule } from '../testing/process.testing.module'; import { ProcessTestingModule } from '../testing/process.testing.module';
import { ProcessService } from '../process-list/services/process.service';
describe('StartFormComponent', () => { describe('StartFormComponent', () => {
let formService: FormService;
let component: StartFormComponent; let component: StartFormComponent;
let fixture: ComponentFixture<StartFormComponent>; let fixture: ComponentFixture<StartFormComponent>;
let getStartFormSpy: jasmine.Spy; let getStartFormSpy: jasmine.Spy;
let visibilityService: WidgetVisibilityService; let visibilityService: WidgetVisibilityService;
let translate: TranslateService; let translate: TranslateService;
let processService: ProcessService;
const exampleId1 = 'my:process1'; const exampleId1 = 'my:process1';
const exampleId2 = 'my:process2'; const exampleId2 = 'my:process2';
@ -52,11 +53,11 @@ describe('StartFormComponent', () => {
beforeEach(() => { beforeEach(() => {
fixture = TestBed.createComponent(StartFormComponent); fixture = TestBed.createComponent(StartFormComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
formService = TestBed.inject(FormService); processService = TestBed.inject(ProcessService);
visibilityService = TestBed.inject(WidgetVisibilityService); visibilityService = TestBed.inject(WidgetVisibilityService);
translate = TestBed.inject(TranslateService); translate = TestBed.inject(TranslateService);
getStartFormSpy = spyOn(formService, 'getStartFormDefinition').and.returnValue(of({ getStartFormSpy = spyOn(processService, 'getStartFormDefinition').and.returnValue(of({
processDefinitionName: 'my:process' processDefinitionName: 'my:process'
})); }));
@ -71,27 +72,27 @@ describe('StartFormComponent', () => {
it('should load start form on change if processDefinitionId defined', () => { it('should load start form on change if processDefinitionId defined', () => {
component.processDefinitionId = exampleId1; component.processDefinitionId = exampleId1;
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) }); component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
expect(formService.getStartFormDefinition).toHaveBeenCalled(); expect(processService.getStartFormDefinition).toHaveBeenCalled();
}); });
it('should load start form when processDefinitionId changed', () => { it('should load start form when processDefinitionId changed', () => {
component.processDefinitionId = exampleId1; component.processDefinitionId = exampleId1;
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) }); component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
expect(formService.getStartFormDefinition).toHaveBeenCalled(); expect(processService.getStartFormDefinition).toHaveBeenCalled();
}); });
it('should check visibility when the start form is loaded', () => { it('should check visibility when the start form is loaded', () => {
spyOn(visibilityService, 'refreshVisibility'); spyOn(visibilityService, 'refreshVisibility');
component.processDefinitionId = exampleId1; component.processDefinitionId = exampleId1;
component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) }); component.ngOnChanges({ processDefinitionId: new SimpleChange(exampleId1, exampleId2, true) });
expect(formService.getStartFormDefinition).toHaveBeenCalled(); expect(processService.getStartFormDefinition).toHaveBeenCalled();
expect(visibilityService.refreshVisibility).toHaveBeenCalled(); expect(visibilityService.refreshVisibility).toHaveBeenCalled();
}); });
it('should not load start form when changes notified but no change to processDefinitionId', () => { it('should not load start form when changes notified but no change to processDefinitionId', () => {
component.processDefinitionId = undefined; component.processDefinitionId = undefined;
component.ngOnChanges({ otherProp: new SimpleChange(exampleId1, exampleId2, true) }); component.ngOnChanges({ otherProp: new SimpleChange(exampleId1, exampleId2, true) });
expect(formService.getStartFormDefinition).not.toHaveBeenCalled(); expect(processService.getStartFormDefinition).not.toHaveBeenCalled();
}); });
it('should be able to inject sigle file as value into the form with an upload single widget', () => { it('should be able to inject sigle file as value into the form with an upload single widget', () => {

View File

@ -30,6 +30,11 @@ import {
} from '@angular/core'; } from '@angular/core';
import { FormComponent } from './form.component'; import { FormComponent } from './form.component';
import { ContentLinkModel, FormService, WidgetVisibilityService, FormOutcomeModel } from '@alfresco/adf-core'; import { ContentLinkModel, FormService, WidgetVisibilityService, FormOutcomeModel } from '@alfresco/adf-core';
import { ProcessService } from '../process-list/services/process.service';
import { EditorService } from './services/editor.service';
import { ModelService } from './services/model.service';
import { TaskFormService } from './services/task-form.service';
import { TaskService } from './services/task.service';
@Component({ @Component({
selector: 'adf-start-form', selector: 'adf-start-form',
@ -70,8 +75,13 @@ export class StartFormComponent extends FormComponent implements OnChanges, OnIn
@ViewChild('outcomesContainer') @ViewChild('outcomesContainer')
outcomesContainer: ElementRef = null; outcomesContainer: ElementRef = null;
constructor(formService: FormService, visibilityService: WidgetVisibilityService) { constructor(public processService: ProcessService,
super(formService, visibilityService, null, null); taskFormService: TaskFormService,
taskService: TaskService,
editorService: EditorService,
modelService: ModelService,
formService: FormService, visibilityService: WidgetVisibilityService) {
super(formService, taskFormService, taskService, editorService, modelService, visibilityService, null, null);
this.showTitle = false; this.showTitle = false;
} }
@ -99,9 +109,9 @@ export class StartFormComponent extends FormComponent implements OnChanges, OnIn
} }
loadStartForm(processId: string) { loadStartForm(processId: string) {
this.formService.getProcessInstance(processId) this.processService.getProcess(processId)
.subscribe((instance: any) => { .subscribe((instance: any) => {
this.formService this.processService
.getStartFormInstance(processId) .getStartFormInstance(processId)
.subscribe( .subscribe(
(form) => { (form) => {
@ -117,7 +127,7 @@ export class StartFormComponent extends FormComponent implements OnChanges, OnIn
} }
getStartFormDefinition(processId: string) { getStartFormDefinition(processId: string) {
this.formService this.processService
.getStartFormDefinition(processId) .getStartFormDefinition(processId)
.subscribe( .subscribe(
(form) => { (form) => {

View File

@ -19,7 +19,7 @@ import { ComponentFixture, TestBed } from '@angular/core/testing';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { ContentModule, ContentNodeSelectorPanelComponent, DocumentListService } from '@alfresco/adf-content-services'; import { ContentModule, ContentNodeSelectorPanelComponent, DocumentListService } from '@alfresco/adf-content-services';
import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core'; import { EventEmitter, NO_ERRORS_SCHEMA } from '@angular/core';
import { ProcessTestingModule } from '../testing/process.testing.module'; import { ProcessTestingModule } from '../../../testing/process.testing.module';
import { AttachFileWidgetDialogComponent } from './attach-file-widget-dialog.component'; import { AttachFileWidgetDialogComponent } from './attach-file-widget-dialog.component';
import { setupTestBed, AuthenticationService, SitesService, AlfrescoApiService, NodesApiService } from '@alfresco/adf-core'; import { setupTestBed, AuthenticationService, SitesService, AlfrescoApiService, NodesApiService } from '@alfresco/adf-core';
import { AttachFileWidgetDialogComponentData } from './attach-file-widget-dialog-component.interface'; import { AttachFileWidgetDialogComponentData } from './attach-file-widget-dialog-component.interface';

View File

@ -20,7 +20,7 @@ import { MatDialog } from '@angular/material/dialog';
import { AttachFileWidgetDialogService } from './attach-file-widget-dialog.service'; import { AttachFileWidgetDialogService } from './attach-file-widget-dialog.service';
import { Subject, of } from 'rxjs'; import { Subject, of } from 'rxjs';
import { setupTestBed } from '@alfresco/adf-core'; import { setupTestBed } from '@alfresco/adf-core';
import { ProcessTestingModule } from '../testing/process.testing.module'; import { ProcessTestingModule } from '../../../testing/process.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
describe('AttachFileWidgetDialogService', () => { describe('AttachFileWidgetDialogService', () => {

View File

@ -40,7 +40,7 @@
(click)="openSelectDialogFromFileSource()"> (click)="openSelectDialogFromFileSource()">
{{field.params?.fileSource?.name}} {{field.params?.fileSource?.name}}
<mat-icon> <mat-icon>
<img alt="alfresco" class="adf-attach-widget__image-logo" src="../assets/images/alfresco-flower.svg"> <img alt="alfresco" class="adf-attach-widget__image-logo" src="../../../assets/images/alfresco-flower.svg">
</mat-icon> </mat-icon>
</button> </button>
<div *ngIf="!isDefinedSourceFolder()"> <div *ngIf="!isDefinedSourceFolder()">
@ -49,7 +49,7 @@
(click)="openSelectDialog(repo)"> (click)="openSelectDialog(repo)">
{{repo.name}} {{repo.name}}
<mat-icon> <mat-icon>
<img alt="alfresco" class="adf-attach-widget__image-logo" src="../assets/images/alfresco-flower.svg"> <img alt="alfresco" class="adf-attach-widget__image-logo" src="../../../assets/images/alfresco-flower.svg">
</mat-icon> </mat-icon>
</button> </button>
</div> </div>

View File

@ -23,8 +23,6 @@ import {
FormModel, FormModel,
FormFieldTypes, FormFieldTypes,
FormService, FormService,
ProcessContentService,
ActivitiContentService,
FormFieldMetadata, FormFieldMetadata,
setupTestBed, setupTestBed,
DownloadService DownloadService
@ -32,9 +30,11 @@ import {
import { ContentNodeDialogService, ContentModule } from '@alfresco/adf-content-services'; import { ContentNodeDialogService, ContentModule } from '@alfresco/adf-content-services';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { Node } from '@alfresco/js-api'; import { Node } from '@alfresco/js-api';
import { ProcessTestingModule } from '../testing/process.testing.module'; import { ProcessTestingModule } from '../../../testing/process.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { AttachFileWidgetDialogService } from './attach-file-widget-dialog.service'; import { AttachFileWidgetDialogService } from './attach-file-widget-dialog.service';
import { ActivitiContentService } from '../../services/activiti-alfresco.service';
import { ProcessContentService } from '../../services/process-content.service';
const fakeRepositoryListAnswer = [ const fakeRepositoryListAnswer = [
{ {

View File

@ -19,16 +19,13 @@
import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core'; import { Component, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
import { import {
ActivitiContentService,
AppConfigService, AppConfigService,
AppConfigValues, AppConfigValues,
ContentService, ContentService,
DownloadService, DownloadService,
FormService, FormService,
LogService, LogService,
ProcessContentService, ThumbnailService
ThumbnailService,
UploadWidgetComponent
} from '@alfresco/adf-core'; } from '@alfresco/adf-core';
import { ContentNodeDialogService } from '@alfresco/adf-content-services'; import { ContentNodeDialogService } from '@alfresco/adf-content-services';
import { import {
@ -40,6 +37,9 @@ import {
import { from, of, Subject, zip } from 'rxjs'; import { from, of, Subject, zip } from 'rxjs';
import { mergeMap, takeUntil } from 'rxjs/operators'; import { mergeMap, takeUntil } from 'rxjs/operators';
import { AttachFileWidgetDialogService } from './attach-file-widget-dialog.service'; import { AttachFileWidgetDialogService } from './attach-file-widget-dialog.service';
import { UploadWidgetComponent } from '../upload/upload.widget';
import { ProcessContentService } from '../../services/process-content.service';
import { ActivitiContentService } from '../../services/activiti-alfresco.service';
@Component({ @Component({
selector: 'attach-widget', selector: 'attach-widget',

View File

@ -27,7 +27,7 @@ import {
import { ContentNodeDialogService } from '@alfresco/adf-content-services'; import { ContentNodeDialogService } from '@alfresco/adf-content-services';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { Node } from '@alfresco/js-api'; import { Node } from '@alfresco/js-api';
import { ProcessTestingModule } from '../testing/process.testing.module'; import { ProcessTestingModule } from '../../../testing/process.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
const fakeMinimalNode: Node = { const fakeMinimalNode: Node = {

View File

@ -16,7 +16,7 @@
*/ */
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { MaterialModule } from '../material.module'; import { MaterialModule } from '../../../material.module';
import { CoreModule } from '@alfresco/adf-core'; import { CoreModule } from '@alfresco/adf-core';
import { ContentNodeSelectorModule } from '@alfresco/adf-content-services'; import { ContentNodeSelectorModule } from '@alfresco/adf-content-services';

View File

@ -18,15 +18,16 @@
import { SimpleChange } from '@angular/core'; import { SimpleChange } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing'; import { ComponentFixture, fakeAsync, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { ContentService } from '../../../../services'; import {
ContentService,
ContentLinkModel,
CoreTestingModule,
setupTestBed
} from '@alfresco/adf-core';
import { of } from 'rxjs'; import { of } from 'rxjs';
import { ProcessContentService } from '../../../services/process-content.service';
import { ContentLinkModel } from '../index';
import { ContentWidgetComponent } from './content.widget'; import { ContentWidgetComponent } from './content.widget';
import { setupTestBed } from '../../../../testing/setup-test-bed';
import { CoreTestingModule } from '../../../../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
import { ProcessContentService } from '../../services/process-content.service';
declare let jasmine: any; declare let jasmine: any;
@ -41,7 +42,7 @@ describe('ContentWidgetComponent', () => {
const createFakeImageBlob = () => { const createFakeImageBlob = () => {
const data = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg=='); const data = atob('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==');
return new Blob([data], { type: 'image/png' }); return new Blob([data], {type: 'image/png'});
}; };
const createFakePdfBlob = (): Blob => { const createFakePdfBlob = (): Blob => {
@ -59,7 +60,7 @@ describe('ContentWidgetComponent', () => {
'CjAwMDAwMDAwNzkgMDAwMDAgbiAKMDAwMDAwMDE3MyAwMDAwMCBuIAowMDAwMDAwMzAxIDAw' + 'CjAwMDAwMDAwNzkgMDAwMDAgbiAKMDAwMDAwMDE3MyAwMDAwMCBuIAowMDAwMDAwMzAxIDAw' +
'MDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9v' + 'MDAwIG4gCjAwMDAwMDAzODAgMDAwMDAgbiAKdHJhaWxlcgo8PAogIC9TaXplIDYKICAvUm9v' +
'dCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G'); 'dCAxIDAgUgo+PgpzdGFydHhyZWYKNDkyCiUlRU9G');
return new Blob([pdfData], { type: 'application/pdf' }); return new Blob([pdfData], {type: 'application/pdf'});
}; };
setupTestBed({ setupTestBed({
@ -115,7 +116,7 @@ describe('ContentWidgetComponent', () => {
const contentId = 1; const contentId = 1;
const change = new SimpleChange(null, contentId, true); const change = new SimpleChange(null, contentId, true);
component.ngOnChanges({ id: change }); component.ngOnChanges({id: change});
jasmine.Ajax.requests.mostRecent().respondWith({ jasmine.Ajax.requests.mostRecent().respondWith({
status: 200, status: 200,
@ -155,7 +156,7 @@ describe('ContentWidgetComponent', () => {
const contentId = 1; const contentId = 1;
const change = new SimpleChange(null, contentId, true); const change = new SimpleChange(null, contentId, true);
component.ngOnChanges({ id: change }); component.ngOnChanges({id: change});
jasmine.Ajax.requests.mostRecent().respondWith({ jasmine.Ajax.requests.mostRecent().respondWith({
status: 200, status: 200,
@ -183,7 +184,7 @@ describe('ContentWidgetComponent', () => {
const contentId = 1; const contentId = 1;
const change = new SimpleChange(null, contentId, true); const change = new SimpleChange(null, contentId, true);
component.ngOnChanges({ id: change }); component.ngOnChanges({id: change});
component.contentLoaded.subscribe(() => { component.contentLoaded.subscribe(() => {
fixture.detectChanges(); fixture.detectChanges();

View File

@ -15,13 +15,10 @@
* limitations under the License. * limitations under the License.
*/ */
import { ContentService } from '../../../../services/content.service'; import { ContentService, LogService, ContentLinkModel, FormService } from '@alfresco/adf-core';
import { LogService } from '../../../../services/log.service';
import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewEncapsulation } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, ViewEncapsulation } from '@angular/core';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ProcessContentService } from '../../../services/process-content.service'; import { ProcessContentService } from '../../services/process-content.service';
import { ContentLinkModel } from '../core/content-link.model';
import { FormService } from '../../../services/form.service';
@Component({ @Component({
selector: 'adf-content', selector: 'adf-content',

View File

@ -16,8 +16,7 @@
*/ */
import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { Component, OnInit, ViewEncapsulation } from '@angular/core';
import { FormService } from '../../../services/form.service'; import { FormService, WidgetComponent } from '@alfresco/adf-core';
import { WidgetComponent } from '../widget.component';
@Component({ @Component({
selector: 'adf-form-document-widget', selector: 'adf-form-document-widget',
@ -41,7 +40,7 @@ export class DocumentWidgetComponent extends WidgetComponent implements OnInit {
hasFile: boolean = false; hasFile: boolean = false;
constructor(public formService: FormService) { constructor(public formService: FormService) {
super(formService); super(formService);
} }
ngOnInit() { ngOnInit() {

Some files were not shown because too many files have changed in this diff Show More