diff --git a/demo-shell/src/app/components/process-service/process-service.component.html b/demo-shell/src/app/components/process-service/process-service.component.html index 4b6263aa97..4b9c970ddd 100644 --- a/demo-shell/src/app/components/process-service/process-service.component.html +++ b/demo-shell/src/app/components/process-service/process-service.component.html @@ -76,6 +76,7 @@ + +``` + +## Class members + +### Properties + +| Name | Type | Default value | Description | +| ---- | ---- | ------------- | ----------- | +| fieldValidators | [`FormFieldValidator`](../../../lib/core/form/components/widgets/core/form-field-validator.ts)`[]` | \[] | Field validators for use with the form. | +| readOnlyForm | `boolean` | false | Toggles read-only state of the form. All form widgets render as read-only if enabled. | +| showFormCompleteButton | `boolean` | true | Toggles rendering of the `Complete` outcome button. | +| showFormRefreshButton | `boolean` | true | Toggles rendering of the `Refresh` button. | +| showFormSaveButton | `boolean` | true | Toggles rendering of the `Save` outcome button. | +| showFormTitle | `boolean` | false | Toggles rendering of the form title. | +| showCancelButton | `boolean` | true | Toggles rendering of the `Cancel` empty form button. | +| showFormValidationIcon | `boolean` | true | Toggle rendering of the validation icon +| taskId | `string` | | (**required**) The id of the task whose details we are asking for. | + +### Events + +| Name | Type | Description | +| ---- | ---- | ----------- | +| cancel | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when the "Cancel" button is clicked. | +| completed | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when the form associated with the task is completed. | +| error | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`` | Emitted when an error occurs. | +| executeOutcome | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormOutcomeEvent`](../../../lib/core/form/components/widgets/core/form-outcome-event.model.ts)`>` | Emitted when any outcome is executed. Default behaviour can be prevented via `event.preventDefault()`. | +| formCompleted | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormModel`](../../../lib/core/form/components/widgets/core/form.model.ts)`>` | Emitted when the form is submitted with the `Complete` outcome. | +| formContentClicked | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`ContentLinkModel`](../../../lib/core/form/components/widgets/core/content-link.model.ts)`>` | Emitted when the form field content is clicked. | +| formLoaded | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormModel`](../../../lib/core/form/components/widgets/core/form.model.ts)`>` | Emitted when the form is loaded or reloaded. | +| formError | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormFieldModel`](../../core/models/form-field.model.md)`[]>` | Emitted when the supplied form values have a validation error. | +| formSaved | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`FormModel`](../../../lib/core/form/components/widgets/core/form.model.ts)`>` | Emitted when the form is submitted with the `Save` or custom outcomes. | + +## See also + +- [Form component](./form.component.md) +- [Form field model](../../core/models/form-field.model.md) +- [Form service](../../core/services/form.service.md) diff --git a/e2e/pages/adf/process-services/attach-form.page.ts b/e2e/pages/adf/process-services/attach-form.page.ts index 03caff5a82..291888772a 100644 --- a/e2e/pages/adf/process-services/attach-form.page.ts +++ b/e2e/pages/adf/process-services/attach-form.page.ts @@ -20,11 +20,11 @@ import { BrowserVisibility, BrowserActions, DropdownPage } from '@alfresco/adf-t export class AttachFormPage { - noFormMessage: ElementFinder = element(by.id('adf-no-form-message')); - attachFormButton: ElementFinder = element(by.id('adf-no-form-attach-form-button')); - completeButton: ElementFinder = element(by.id('adf-no-form-complete-button')); + noFormMessage: ElementFinder = element(by.css('.adf-empty-content__title')); + attachFormButton: ElementFinder = element(by.id('adf-attach-form-attach-button')); + completeButton: ElementFinder = element(by.id('adf-attach-form-complete-button')); formDropdown: ElementFinder = element(by.id('form_id')); - cancelButton: ElementFinder = element(by.id('adf-no-form-cancel-button')); + cancelButton: ElementFinder = element(by.id('adf-attach-form-cancel-button')); defaultTitle: ElementFinder = element(by.css('mat-card-title[class="mat-card-title mat-card-title"]')); attachFormDropdown = new DropdownPage(element(by.css("div[class='adf-attach-form-row']"))); @@ -66,6 +66,6 @@ export class AttachFormPage { } async checkAttachFormButtonIsDisabled(): Promise { - await BrowserVisibility.waitUntilElementIsVisible(element(by.css('button[id="adf-no-form-attach-form-button"][disabled]'))); + await BrowserVisibility.waitUntilElementIsVisible(element(by.css('button[id="adf-attach-form-attach-button"][disabled]'))); } } diff --git a/e2e/pages/adf/process-services/task-details.page.ts b/e2e/pages/adf/process-services/task-details.page.ts index be363835a7..4f2947a8ad 100644 --- a/e2e/pages/adf/process-services/task-details.page.ts +++ b/e2e/pages/adf/process-services/task-details.page.ts @@ -55,10 +55,11 @@ export class TaskDetailsPage { involvePeopleHeader: ElementFinder = element(by.css('div[class="adf-search-text-header"]')); removeInvolvedPeople: ElementFinder = element(by.css('button[data-automation-id="Remove"]')); peopleTitle: ElementFinder = element(by.id('people-title')); + noFormMessage: ElementFinder = element(by.css('span[id*="no-form-message"]')); cancelAttachForm: ElementFinder = element(by.id('adf-no-form-cancel-button')); attachFormButton: ElementFinder = element(by.id('adf-no-form-attach-form-button')); disabledAttachFormButton: ElementFinder = element(by.css('button[id="adf-no-form-attach-form-button"][disabled]')); - removeAttachForm: ElementFinder = element(by.id('adf-no-form-remove-button')); + removeAttachForm: ElementFinder = element(by.id('adf-attach-form-remove-button')); attachFormName: ElementFinder = element(by.css('span[class="adf-form-title ng-star-inserted"]')); emptyTaskDetails: ElementFinder = element(by.css('adf-task-details > div > div')); priority: ElementFinder = element(by.css('span[data-automation-id*="priority"] span')); @@ -163,6 +164,14 @@ export class TaskDetailsPage { await BrowserActions.click(this.formNameField); } + async checkStandaloneNoFormMessageIsDisplayed(): Promise { + await BrowserVisibility.waitUntilElementIsVisible(this.noFormMessage); + } + + async getNoFormMessage(): Promise { + return BrowserActions.getText(this.noFormMessage); + } + getAssignee(): Promise { return BrowserActions.getText(this.assigneeField); } diff --git a/e2e/process-services/attach-folder.e2e.ts b/e2e/process-services/attach-folder.e2e.ts index 9a7b949c62..4086bad93a 100644 --- a/e2e/process-services/attach-folder.e2e.ts +++ b/e2e/process-services/attach-folder.e2e.ts @@ -99,6 +99,7 @@ describe('Attach Folder', () => { await contentNodeSelector.clickMoveCopyButton(); await widget.attachFolderWidget().checkFolderIsAttached(app.UPLOAD_FOLDER_FORM_CS.FIELD.widget_id, user.email); await widget.attachFolderWidget().removeFolder(app.UPLOAD_FOLDER_FORM_CS.FIELD.widget_id, user.email); + await taskPage.formFields().checkWidgetIsVisible(app.UPLOAD_FOLDER_FORM_CS.FIELD.widget_id); await widget.attachFolderWidget().checkFolderIsNotAttached(app.UPLOAD_FOLDER_FORM_CS.FIELD.widget_id, user.email); }); }); diff --git a/e2e/process-services/attach-form-component.e2e.ts b/e2e/process-services/attach-form-component.e2e.ts index e8839b10d4..debb477374 100644 --- a/e2e/process-services/attach-form-component.e2e.ts +++ b/e2e/process-services/attach-form-component.e2e.ts @@ -22,12 +22,14 @@ import { UsersActions } from '../actions/users.actions'; import { NavigationBarPage } from '../pages/adf/navigation-bar.page'; import { AttachFormPage } from '../pages/adf/process-services/attach-form.page'; import { TasksPage } from '../pages/adf/process-services/tasks.page'; +import { TaskDetailsPage } from '../pages/adf/process-services/task-details.page'; import CONSTANTS = require('../util/constants'); describe('Attach Form Component', () => { const loginPage = new LoginPage(); const taskPage = new TasksPage(); + const taskDetailsPage = new TaskDetailsPage(); const attachFormPage = new AttachFormPage(); const formFields = new FormFields(); const navigationBarPage = new NavigationBarPage(); @@ -83,9 +85,9 @@ describe('Attach Form Component', () => { await taskPage.filtersPage().goToFilter(CONSTANTS.TASK_FILTERS.MY_TASKS); await taskPage.tasksListPage().selectRow(testNames.taskName); - await attachFormPage.checkNoFormMessageIsDisplayed(); - await attachFormPage.checkAttachFormButtonIsDisplayed(); - await attachFormPage.checkCompleteButtonIsDisplayed(); + await taskPage.taskDetails().checkStandaloneNoFormMessageIsDisplayed(); + await taskPage.taskDetails().checkAttachFormButtonIsDisplayed(); + await taskPage.taskDetails().checkCompleteTaskButtonIsDisplayed(); }); it('[C280048] Should be able to view the attach-form component after clicking cancel button', async () => { @@ -94,7 +96,7 @@ describe('Attach Form Component', () => { await taskPage.filtersPage().goToFilter(CONSTANTS.TASK_FILTERS.MY_TASKS); await taskPage.tasksListPage().selectRow(testNames.taskName); - await attachFormPage.clickAttachFormButton(); + await taskPage.taskDetails().clickAttachFormButton(); await attachFormPage.checkDefaultFormTitleIsDisplayed(testNames.formTitle); await attachFormPage.checkFormDropdownIsDisplayed(); await attachFormPage.checkCancelButtonIsDisplayed(); @@ -104,7 +106,7 @@ describe('Attach Form Component', () => { await formFields.checkWidgetIsReadOnlyMode(testNames.widgetTitle); await attachFormPage.clickCancelButton(); - await attachFormPage.checkAttachFormButtonIsDisplayed(); + await taskPage.taskDetails().checkAttachFormButtonIsDisplayed(); }); it('[C280017] Should be able to attach a form on a standalone task and complete', async () => { @@ -113,7 +115,7 @@ describe('Attach Form Component', () => { await taskPage.filtersPage().goToFilter(CONSTANTS.TASK_FILTERS.MY_TASKS); await taskPage.tasksListPage().selectRow(testNames.taskName); - await attachFormPage.clickAttachFormButton(); + await taskDetailsPage.clickAttachFormButton(); await attachFormPage.selectAttachFormOption(testNames.formName); await attachFormPage.clickAttachFormButton(); diff --git a/e2e/process-services/standalone-task.e2e.ts b/e2e/process-services/standalone-task.e2e.ts index 50874f5220..b14518470e 100644 --- a/e2e/process-services/standalone-task.e2e.ts +++ b/e2e/process-services/standalone-task.e2e.ts @@ -81,7 +81,7 @@ describe('Start Task - Task App', () => { await taskPage.taskDetails().checkAttachFormButtonIsDisplayed(); await taskPage.taskDetails().checkAttachFormButtonIsEnabled(); await expect(await taskPage.taskDetails().getFormName()).toEqual(CONSTANTS.TASK_DETAILS.NO_FORM); - await expect(await taskPage.formFields().getNoFormMessage()).toEqual(noFormMessage); + await expect(await taskDetails.getNoFormMessage()).toEqual(noFormMessage); }); it('[C268910] Should a standalone task be displayed in completed tasks when completing it', async () => { @@ -107,17 +107,20 @@ describe('Start Task - Task App', () => { await taskPage.tasksListPage().checkContentIsDisplayed(tasks[2]); await taskPage.formFields().noFormIsDisplayed(); + await taskPage.taskDetails().clickAttachFormButton(); const formFields = await taskPage.formFields(); - await formFields.clickOnAttachFormButton(); await formFields.selectForm(app.formName); await formFields.clickOnAttachFormButton(); + await taskPage.formFields().checkFormIsDisplayed(); + await taskPage.taskDetails().checkCompleteFormButtonIsDisplayed(); await expect(await taskPage.taskDetails().getFormName()).toEqual(app.formName); }); it('[C268912] Should a standalone task be displayed when removing the form from APS', async () => { const task = await taskPage.createNewTask(); + const taskDetails = await taskPage.taskDetails(); await task.addName(tasks[3]); await task.selectForm(app.formName); await task.clickStartButton(); @@ -134,6 +137,6 @@ describe('Start Task - Task App', () => { await taskPage.formFields().noFormIsDisplayed(); await expect(await taskPage.taskDetails().getFormName()).toEqual(CONSTANTS.TASK_DETAILS.NO_FORM); - await expect(await taskPage.formFields().getNoFormMessage()).toEqual(noFormMessage); + await expect(await taskDetails.getNoFormMessage()).toEqual(noFormMessage); }); }); diff --git a/e2e/process-services/task-details-form.e2e.ts b/e2e/process-services/task-details-form.e2e.ts index 4b08f38586..9760ffe451 100644 --- a/e2e/process-services/task-details-form.e2e.ts +++ b/e2e/process-services/task-details-form.e2e.ts @@ -27,11 +27,13 @@ import { TaskDetailsPage } from '../pages/adf/process-services/task-details.page import { TasksListPage } from '../pages/adf/process-services/tasks-list.page'; import CONSTANTS = require('../util/constants'); import { TasksPage } from '../pages/adf/process-services/tasks.page'; +import { AttachFormPage } from '../pages/adf/process-services/attach-form.page'; describe('Task Details - Form', () => { const loginPage = new LoginPage(); const tasksListPage = new TasksListPage(); const taskDetailsPage = new TaskDetailsPage(); + const attachFormPage = new AttachFormPage(); const taskPage = new TasksPage(); const filtersPage = new FiltersPage(); const widget = new Widget(); @@ -101,25 +103,27 @@ describe('Task Details - Form', () => { await tasksListPage.selectRow(task.name); await taskDetailsPage.clickForm(); - await taskDetailsPage.checkAttachFormDropdownIsDisplayed(); - await taskDetailsPage.checkAttachFormButtonIsDisabled(); + await attachFormPage.checkFormDropdownIsDisplayed(); + await attachFormPage.checkAttachFormButtonIsDisabled(); - await taskDetailsPage.selectAttachFormOption(newForm.name); + await attachFormPage.selectAttachFormOption(newForm.name); await taskDetailsPage.checkSelectedForm(newForm.name); - await taskDetailsPage.checkAttachFormButtonIsEnabled(); + await attachFormPage.checkAttachFormButtonIsDisplayed(); - await taskDetailsPage.checkCancelAttachFormIsDisplayed(); - await taskDetailsPage.clickCancelAttachForm(); + await attachFormPage.checkCancelButtonIsDisplayed(); + await attachFormPage.clickCancelButton(); await taskDetailsPage.checkFormIsAttached(attachedForm.name); await taskDetailsPage.clickForm(); - await taskDetailsPage.checkAttachFormDropdownIsDisplayed(); - await taskDetailsPage.selectAttachFormOption(newForm.name); + await attachFormPage.checkFormDropdownIsDisplayed(); + await attachFormPage.checkAttachFormButtonIsDisabled(); + await attachFormPage.selectAttachFormOption(newForm.name); + await taskDetailsPage.checkSelectedForm(newForm.name); - await taskDetailsPage.checkAttachFormButtonIsDisplayed(); - await taskDetailsPage.clickAttachFormButton(); + await attachFormPage.checkAttachFormButtonIsDisplayed(); + await attachFormPage.clickAttachFormButton(); await taskDetailsPage.checkFormIsAttached(newForm.name); }); diff --git a/lib/process-services/src/lib/i18n/en.json b/lib/process-services/src/lib/i18n/en.json index e0fd931a35..ec187067bd 100644 --- a/lib/process-services/src/lib/i18n/en.json +++ b/lib/process-services/src/lib/i18n/en.json @@ -336,5 +336,20 @@ "CHOOSE_ITEM": "Choose {{ name }} to...", "CHOOSE_IN": "Choose file in {{ name }}..." } + }, + "ADF_TASK_FORM": { + "EMPTY_FORM": { + "SUBTITLE": "Attach a form that can be viewed later", + "COMPLETE-TASK-MESSAGE": "Task {{taskName}} completed", + "COMPLETE-TASK-SUB-MESSAGE": "No forms to be added", + "BUTTONS": { + "COMPLETE": "COMPLETE", + "CANCEL": "CANCEL" + } + }, + "COMPLETED_TASK": { + "TITLE": "Task {{taskName}} completed", + "SUBTITLE": "No forms to be added" + } } } diff --git a/lib/process-services/src/lib/mock/task/task-details.mock.ts b/lib/process-services/src/lib/mock/task/task-details.mock.ts index 86dbf9805b..bf04da9c1f 100644 --- a/lib/process-services/src/lib/mock/task/task-details.mock.ts +++ b/lib/process-services/src/lib/mock/task/task-details.mock.ts @@ -97,6 +97,46 @@ export let standaloneTaskWithoutForm = new TaskDetailsModel({ memberOfCandidateGroup: false }); +export let completedStandaloneTaskWithoutForm = new TaskDetailsModel({ + id: '200', + name: 'Standalone Task Without Form', + description: null, + category: null, + assignee: { + id: 1001, + firstName: 'Wilbur', + lastName: 'Adams', + email: 'wilbur@app.activiti.com' + }, + created: '2016-11-03T15:25:42.749+0000', + dueDate: null, + endDate: new Date(), + duration: null, + priority: 50, + parentTaskId: null, + parentTaskName: null, + processInstanceId: null, + processInstanceName: null, + processDefinitionId: null, + processDefinitionName: null, + processDefinitionDescription: null, + processDefinitionKey: null, + processDefinitionCategory: null, + processDefinitionVersion: null, + processDefinitionDeploymentId: null, + formKey: null, + processInstanceStartUserId: null, + initiatorCanCompleteTask: false, + adhocTaskCanBeReassigned: false, + taskDefinitionKey: 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE', + executionId: '86', + involvedGroups: [], + involvedPeople: [], + memberOfCandidateUsers: false, + managerOfCandidateGroup: false, + memberOfCandidateGroup: false +}); + export let taskDetailsMock = new TaskDetailsModel({ id: '91', name: 'Request translation', @@ -137,6 +177,54 @@ export let taskDetailsMock = new TaskDetailsModel({ memberOfCandidateGroup: false }); +export let initiatorCanCompleteTaskDetailsMock = new TaskDetailsModel({ + id: '91', + name: 'Request translation', + description: null, + category: null, + assignee: { email: 'mock-user-email' }, + created: '2016-11-03T15:25:42.749+0000', + dueDate: null, + endDate: null, + duration: null, + priority: 50, + parentTaskId: null, + parentTaskName: null, + processInstanceId: '86', + processInstanceName: null, + processDefinitionId: 'TranslationProcess:2:8', + processDefinitionName: 'Translation Process', + processDefinitionDescription: null, + processDefinitionKey: 'TranslationProcess', + processDefinitionCategory: 'http://www.activiti.org/processdef', + processDefinitionVersion: 2, + processDefinitionDeploymentId: '5', + formKey: '4', + processInstanceStartUserId: '1001', + initiatorCanCompleteTask: true, + adhocTaskCanBeReassigned: false, + taskDefinitionKey: 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE', + executionId: '86', + involvedGroups: [], + involvedPeople: [ + { + id: 1001, + firstName: 'Wilbur', + lastName: 'Adams', + email: 'wilbur@app.activiti.com' + }, + { + id: 111, + firstName: 'fake-first-name', + lastName: 'fake-last-name', + email: 'fake@app.activiti.com' + } + ], + memberOfCandidateUsers: false, + managerOfCandidateGroup: false, + memberOfCandidateGroup: false +}); + export let taskDetailsWithOutAssigneeMock = new TaskDetailsModel({ id: '91', name: 'Request translation', @@ -183,6 +271,7 @@ export let claimableTaskDetailsMock = new TaskDetailsModel({ endDate: null, duration: null, priority: 50, + formKey: '4', parentTaskId: null, parentTaskName: null, processInstanceId: '86', @@ -353,6 +442,36 @@ export let taskDetailsWithOutCandidateGroup = new TaskDetailsModel({ ] }); +export let completedTaskWithFormMock = new TaskDetailsModel({ + id: '91', + name: 'Request translation', + description: null, + category: null, + assignee: { + id: 1001, + firstName: 'Wilbur', + lastName: 'Adams', + email: 'wilbur@app.activiti.com' + }, + created: '2016-11-03T15:25:42.749+0000', + dueDate: null, + endDate: new Date(), + duration: null, + priority: 50, + formKey: '91', + parentTaskId: null, + parentTaskName: null, + processInstanceId: '86', + processInstanceName: null, + processDefinitionId: 'TranslationProcess:2:8', + processDefinitionName: 'Translation Process', + involvedGroups: [], + involvedPeople: [], + managerOfCandidateGroup: true, + memberOfCandidateGroup: true, + memberOfCandidateUsers: false +}); + export let completedTaskDetailsMock = new TaskDetailsModel({ id: '91', name: 'Request translation', @@ -366,9 +485,10 @@ export let completedTaskDetailsMock = new TaskDetailsModel({ }, created: '2016-11-03T15:25:42.749+0000', dueDate: null, - endDate: '2016-11-03T15:25:42.749+0000', + endDate: new Date(), duration: null, priority: 50, + formKey: null, parentTaskId: null, parentTaskName: null, processInstanceId: '86', @@ -382,6 +502,40 @@ export let completedTaskDetailsMock = new TaskDetailsModel({ memberOfCandidateUsers: false }); +export let taskDetailsWithOutFormMock = new TaskDetailsModel({ + 'id': '91', + 'name': 'Request translation', + 'description': 'fake description', + 'category': null, + 'assignee': {'id': 1001, 'firstName': 'Admin', 'lastName': 'Paul', 'email': 'my@mymail.com' }, + 'created': '2016-11-03T15:25:42.749+0000', + 'dueDate': '2016-11-03T15:25:42.749+0000', + 'endDate': null, + 'duration': null, + 'priority': 50, + 'parentTaskId': null, + 'parentTaskName': null, + 'processInstanceId': '86', + 'processInstanceName': null, + 'processDefinitionId': 'TranslationProcess:2:8', + 'processDefinitionName': 'Translation Process', + 'processDefinitionDescription': null, + 'processDefinitionKey': 'TranslationProcess', + 'processDefinitionCategory': 'http://www.activiti.org/processdef', + 'processDefinitionVersion': 2, + 'processDefinitionDeploymentId': '5', + 'formKey': null, + 'processInstanceStartUserId': '1001', + 'initiatorCanCompleteTask': false, + 'adhocTaskCanBeReassigned': false, + 'taskDefinitionKey': 'sid-DDECD9E4-0299-433F-9193-C3D905C3EEBE', + 'executionId': '86', + 'involvedPeople': [], + 'memberOfCandidateUsers': false, + 'managerOfCandidateGroup': false, + 'memberOfCandidateGroup': false +}); + export const taskFormMock = { id: 4, name: 'Translation request', diff --git a/lib/process-services/src/lib/styles/_index.scss b/lib/process-services/src/lib/styles/_index.scss index c1ab2ec474..41893e597e 100644 --- a/lib/process-services/src/lib/styles/_index.scss +++ b/lib/process-services/src/lib/styles/_index.scss @@ -11,6 +11,7 @@ @import '../content-widget/attach-file-widget-dialog.component'; @import '../form/start-form.component'; @import '../process-list/components/start-process.component'; +@import '../task-list/components/task-form/task-form.component'; @mixin adf-process-services-theme($theme) { @include adf-process-filters-theme($theme); @@ -26,4 +27,5 @@ @include adf-attach-file-widget-dialog-component-theme($theme); @include adf-start-form-component-theme($theme); @include adf-process-services-create-theme($theme); + @include adf-task-form-theme($theme); } diff --git a/lib/process-services/src/lib/task-list/components/attach-form.component.html b/lib/process-services/src/lib/task-list/components/attach-form.component.html index f1864b2127..d74dd3f9af 100644 --- a/lib/process-services/src/lib/task-list/components/attach-form.component.html +++ b/lib/process-services/src/lib/task-list/components/attach-form.component.html @@ -1,7 +1,7 @@
-
+

{{ 'ADF_TASK_LIST.ATTACH_FORM.SELECT_FORM' | translate }}

@@ -23,13 +23,13 @@
- +
- +
- - + +
diff --git a/lib/process-services/src/lib/task-list/components/attach-form.component.scss b/lib/process-services/src/lib/task-list/components/attach-form.component.scss index 00cc380d2c..83df7b3753 100644 --- a/lib/process-services/src/lib/task-list/components/attach-form.component.scss +++ b/lib/process-services/src/lib/task-list/components/attach-form.component.scss @@ -9,7 +9,7 @@ margin: 20px 0; } - .adf-no-form-mat-card-actions { + .adf-attach-form-mat-card-actions { justify-content: space-between; margin-top: 30px; text-align: right; diff --git a/lib/process-services/src/lib/task-list/components/attach-form.component.spec.ts b/lib/process-services/src/lib/task-list/components/attach-form.component.spec.ts index 4d29e83222..51eb327cbf 100644 --- a/lib/process-services/src/lib/task-list/components/attach-form.component.spec.ts +++ b/lib/process-services/src/lib/task-list/components/attach-form.component.spec.ts @@ -51,7 +51,7 @@ describe('AttachFormComponent', () => { it('should show the attach button disabled', async(() => { fixture.detectChanges(); fixture.whenStable().then(() => { - const attachButton = fixture.debugElement.query(By.css('#adf-no-form-attach-form-button')); + const attachButton = fixture.debugElement.query(By.css('#adf-attach-form-attach-button')); expect(attachButton.nativeElement.disabled).toBeTruthy(); }); })); @@ -60,7 +60,7 @@ describe('AttachFormComponent', () => { fixture.detectChanges(); fixture.whenStable().then(() => { const emitSpy = spyOn(component.cancelAttachForm, 'emit'); - const el = fixture.nativeElement.querySelector('#adf-no-form-cancel-button'); + const el = fixture.nativeElement.querySelector('#adf-attach-form-cancel-button'); el.click(); expect(emitSpy).toHaveBeenCalled(); }); @@ -72,8 +72,8 @@ describe('AttachFormComponent', () => { spyOn(taskService, 'attachFormToATask').and.returnValue(of(true)); fixture.detectChanges(); fixture.whenStable().then(() => { - expect(element.querySelector('#adf-no-form-attach-form-button')).toBeDefined(); - const el = fixture.nativeElement.querySelector('#adf-no-form-attach-form-button'); + expect(element.querySelector('#adf-attach-form-attach-button')).toBeDefined(); + const el = fixture.nativeElement.querySelector('#adf-attach-form-attach-button'); el.click(); expect(taskService.attachFormToATask).toHaveBeenCalledWith(1, 2); }); @@ -87,7 +87,7 @@ describe('AttachFormComponent', () => { spyOn(taskService, 'attachFormToATask').and.returnValue(of(true)); fixture.detectChanges(); fixture.whenStable().then(() => { - const attachButton = fixture.debugElement.query(By.css('#adf-no-form-attach-form-button')); + const attachButton = fixture.debugElement.query(By.css('#adf-attach-form-attach-button')); expect(attachButton.nativeElement.disabled).toBeFalsy(); }); })); @@ -103,7 +103,7 @@ describe('AttachFormComponent', () => { fixture.detectChanges(); component.attachFormControl.setValue(2); fixture.detectChanges(); - const attachButton = fixture.debugElement.query(By.css('#adf-no-form-attach-form-button')); + const attachButton = fixture.debugElement.query(By.css('#adf-attach-form-attach-button')); expect(attachButton.nativeElement.disabled).toBeTruthy(); }); })); @@ -140,8 +140,8 @@ describe('AttachFormComponent', () => { fixture.detectChanges(); fixture.whenStable().then(() => { fixture.detectChanges(); - expect(element.querySelector('#adf-no-form-remove-button')).toBeDefined(); - const el = fixture.nativeElement.querySelector('#adf-no-form-remove-button'); + expect(element.querySelector('#adf-attach-form-remove-button')).toBeDefined(); + const el = fixture.nativeElement.querySelector('#adf-attach-form-remove-button'); el.click(); expect(component.formId).toBeNull(); }); @@ -163,7 +163,7 @@ describe('AttachFormComponent', () => { fixture.detectChanges(); fixture.whenStable().then(() => { const emitSpy = spyOn(component.success, 'emit'); - const el = fixture.nativeElement.querySelector('#adf-no-form-attach-form-button'); + const el = fixture.nativeElement.querySelector('#adf-attach-form-attach-button'); el.click(); expect(emitSpy).toHaveBeenCalled(); }); diff --git a/lib/process-services/src/lib/task-list/components/no-task-detail-template.directive.spec.ts b/lib/process-services/src/lib/task-list/components/no-task-detail-template.directive.spec.ts index d34e9fbac4..bf18647d4c 100644 --- a/lib/process-services/src/lib/task-list/components/no-task-detail-template.directive.spec.ts +++ b/lib/process-services/src/lib/task-list/components/no-task-detail-template.directive.spec.ts @@ -17,19 +17,14 @@ import { NoTaskDetailsTemplateDirective } from './no-task-detail-template.directive'; import { TaskDetailsComponent } from './task-details.component'; -import { AuthenticationService } from '@alfresco/adf-core'; -import { of } from 'rxjs'; describe('NoTaskDetailsTemplateDirective', () => { let component: NoTaskDetailsTemplateDirective; let detailsComponent: TaskDetailsComponent; - let authService: AuthenticationService; beforeEach(() => { - authService = new AuthenticationService(null, null, null, null, null); - spyOn(authService, 'getBpmLoggedUser').and.returnValue(of({ email: 'fake-email' })); - detailsComponent = new TaskDetailsComponent(null, authService, null, null, null, null); + detailsComponent = new TaskDetailsComponent(null, null, null, null, null); component = new NoTaskDetailsTemplateDirective(detailsComponent); }); diff --git a/lib/process-services/src/lib/task-list/components/task-details.component.html b/lib/process-services/src/lib/task-list/components/task-details.component.html index f1ba932ec0..a8c352a037 100644 --- a/lib/process-services/src/lib/task-list/components/task-details.component.html +++ b/lib/process-services/src/lib/task-list/components/task-details.component.html @@ -22,51 +22,21 @@
- - - - - - - -
-
-
- {{'ADF_TASK_LIST.STANDALONE_TASK.NO_FORM_MESSAGE' | translate}} -
-
-
-
- - -
- -
-
- -
- + (completed)="onComplete()" + (showAttachForm)="onShowAttachForm()" + (executeOutcome)='onFormExecuteOutcome($event)' + (error)="onFormError($event)" #activitiTaskForm> + { let getTaskDetailsSpy: jasmine.Spy; let getTasksSpy: jasmine.Spy; let assignTaskSpy: jasmine.Spy; - let completeTaskSpy: jasmine.Spy; let logService: LogService; let commentProcessService: CommentProcessService; let peopleProcessService: PeopleProcessService; @@ -99,7 +96,6 @@ describe('TaskDetailsComponent', () => { getTasksSpy = spyOn(service, 'getTasks').and.returnValue(of(tasksMock)); assignTaskSpy = spyOn(service, 'assignTask').and.returnValue(of(fakeUser)); - completeTaskSpy = spyOn(service, 'completeTask').and.returnValue(of({})); commentProcessService = TestBed.get(CommentProcessService); authService = TestBed.get(AuthenticationService); @@ -175,42 +171,6 @@ describe('TaskDetailsComponent', () => { }); })); - it('should display task standalone component when the task does not have an associated form', async(() => { - component.taskId = '123'; - getTaskDetailsSpy.and.returnValue(of(standaloneTaskWithoutForm)); - - fixture.detectChanges(); - fixture.whenStable().then(() => { - fixture.detectChanges(); - expect(component.isStandaloneTaskWithoutForm()).toBeTruthy(); - expect(fixture.debugElement.query(By.css('adf-task-standalone'))).not.toBeNull(); - }); - })); - - it('should not display task standalone component when the task has a form', async(() => { - component.taskId = '123'; - getTaskDetailsSpy.and.returnValue(of(standaloneTaskWithForm)); - fixture.detectChanges(); - fixture.whenStable().then(() => { - fixture.detectChanges(); - expect(component.isStandaloneTaskWithForm()).toBeTruthy(); - expect(fixture.debugElement.query(By.css('adf-task-standalone'))).toBeDefined(); - expect(fixture.debugElement.query(By.css('adf-task-standalone'))).not.toBeNull(); - }); - })); - - it('should display the AttachFormComponent when standaloneTaskWithForm and click on attach button', async(() => { - component.taskId = '123'; - getTaskDetailsSpy.and.returnValue(of(standaloneTaskWithForm)); - fixture.detectChanges(); - component.onShowAttachForm(); - fixture.whenStable().then(() => { - fixture.detectChanges(); - expect(component.isStandaloneTaskWithForm()).toBeTruthy(); - expect(fixture.debugElement.query(By.css('adf-attach-form'))).toBeDefined(); - }); - })); - it('should display the claim message when the task is not assigned', async(() => { component.taskDetails = taskDetailsWithOutAssigneeMock; fixture.detectChanges(); @@ -229,86 +189,6 @@ describe('TaskDetailsComponent', () => { }); })); - describe('and form with visiblity', () => { - - beforeEach(async () => { - component.taskId = '123'; - spyOn(formService, 'completeTaskForm').and.returnValue(of({})); - taskDetailsMock.formKey = '4'; - getTaskDetailsSpy.and.returnValue(of(taskDetailsMock)); - fixture.detectChanges(); - await fixture.whenStable(); - }); - - it('[C312410] - Should be possible to complete a task that has an invisible field on a form with a value', async (done) => { - component.formCompleted.subscribe((form: FormModel) => { - expect(form.id).toBe(taskFormMock.id); - done(); - }); - component.taskDetails.initiatorCanCompleteTask = true; - component.showNextTask = false; - - fixture.detectChanges(); - await fixture.whenStable(); - const inputTextOne: HTMLInputElement = fixture.nativeElement.querySelector('#text1'); - expect(inputTextOne).toBeDefined(); - expect(inputTextOne).not.toBeNull(); - const inputTextTwo: HTMLInputElement = fixture.nativeElement.querySelector('#text2'); - expect(inputTextTwo).toBeDefined(); - expect(inputTextTwo).not.toBeNull(); - let inputTextThree: HTMLInputElement = fixture.nativeElement.querySelector('#text3'); - expect(inputTextThree).toBeDefined(); - expect(inputTextThree).not.toBeNull(); - - inputTextOne.value = 'a'; - inputTextOne.dispatchEvent(new Event('input')); - inputTextTwo.value = 'a'; - inputTextTwo.dispatchEvent(new Event('input')); - inputTextThree.value = 'a'; - inputTextThree.dispatchEvent(new Event('input')); - fixture.detectChanges(); - await fixture.whenStable(); - inputTextThree = fixture.nativeElement.querySelector('#text3'); - expect(inputTextThree).toBeDefined(); - expect(inputTextThree).not.toBeNull(); - - inputTextOne.value = 'b'; - inputTextOne.dispatchEvent(new Event('input')); - fixture.detectChanges(); - await fixture.whenStable(); - const inputThreeContainer = fixture.nativeElement.querySelector('#field-text3-container'); - expect(inputThreeContainer.hidden).toBe(true); - const completeOutcomeButton: HTMLButtonElement = fixture.nativeElement.querySelector('#adf-form-complete'); - expect(completeOutcomeButton.hidden).toBe(false); - completeOutcomeButton.click(); - fixture.detectChanges(); - }); - - it('[C277278] - Should show if the form is valid via the validation icon', async () => { - const numberInput: HTMLInputElement = fixture.nativeElement.querySelector('#numberField'); - let validationForm = fixture.nativeElement.querySelector('#adf-valid-form-icon'); - - expect(numberInput).toBeDefined(); - expect(numberInput).not.toBeNull(); - expect(validationForm.textContent).toBe('check_circle'); - - numberInput.value = 'a'; - numberInput.dispatchEvent(new Event('input')); - fixture.detectChanges(); - await fixture.whenStable(); - const invalidForm = fixture.nativeElement.querySelector('#adf-invalid-form-icon'); - expect(invalidForm).not.toBeNull(); - expect(invalidForm.textContent).toBe('error'); - - numberInput.value = '4'; - numberInput.dispatchEvent(new Event('input')); - fixture.detectChanges(); - await fixture.whenStable(); - validationForm = fixture.nativeElement.querySelector('#adf-valid-form-icon'); - expect(validationForm.textContent).toBe('check_circle'); - }); - }); - describe('change detection', () => { let change; @@ -414,11 +294,6 @@ describe('TaskDetailsComponent', () => { expect(getTasksSpy).not.toHaveBeenCalled(); }); - it('should call service to complete task when complete button clicked', () => { - component.onComplete(); - expect(completeTaskSpy).toHaveBeenCalled(); - }); - it('should emit a complete event when complete button clicked and task completed', () => { const emitSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit'); component.onComplete(); diff --git a/lib/process-services/src/lib/task-list/components/task-details.component.ts b/lib/process-services/src/lib/task-list/components/task-details.component.ts index e2a9bbba76..99dc1ded74 100644 --- a/lib/process-services/src/lib/task-list/components/task-details.component.ts +++ b/lib/process-services/src/lib/task-list/components/task-details.component.ts @@ -17,7 +17,6 @@ import { PeopleProcessService, UserProcessModel, - AuthenticationService, CardViewUpdateService, ClickNotification, LogService, @@ -42,8 +41,8 @@ import { Observable, Observer, of, Subject } from 'rxjs'; import { TaskQueryRequestRepresentationModel } from '../models/filter.model'; import { TaskDetailsModel } from '../models/task-details.model'; import { TaskListService } from './../services/tasklist.service'; -import { UserRepresentation } from '@alfresco/js-api'; import { catchError, share, takeUntil } from 'rxjs/operators'; +import { TaskFormComponent } from './task-form/task-form.component'; @Component({ selector: 'adf-task-details', @@ -61,6 +60,9 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy { @ViewChild('errorDialog') errorDialog: TemplateRef; + @ViewChild('activitiTaskForm') + taskFormComponent: TaskFormComponent; + /** Toggles debug mode. */ @Input() debugMode: boolean = false; @@ -179,11 +181,9 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy { peopleSearch: Observable; - currentLoggedUser: UserRepresentation; data: any; constructor(private taskListService: TaskListService, - private authService: AuthenticationService, private peopleProcessService: PeopleProcessService, private logService: LogService, private cardViewUpdateService: CardViewUpdateService, @@ -192,9 +192,6 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy { ngOnInit() { this.peopleSearch = new Observable((observer) => this.peopleSearchObserver = observer).pipe(share()); - this.authService.getBpmLoggedUser().subscribe(user => { - this.currentLoggedUser = user; - }); if (this.taskId) { this.loadDetails(this.taskId); @@ -225,26 +222,6 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy { } } - isStandaloneTask(): boolean { - return !(this.taskDetails && (!!this.taskDetails.processDefinitionId)); - } - - isStandaloneTaskWithForm(): boolean { - return this.isStandaloneTask() && this.hasFormKey(); - } - - isStandaloneTaskWithoutForm(): boolean { - return this.isStandaloneTask() && !this.hasFormKey(); - } - - isFormComponentVisible(): boolean { - return this.hasFormKey() && !this.isShowAttachForm(); - } - - isTaskStandaloneComponentVisible(): boolean { - return this.isStandaloneTaskWithoutForm() && !this.isShowAttachForm(); - } - isShowAttachForm(): boolean { return this.showAttachForm; } @@ -256,13 +233,6 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy { this.taskDetails = null; } - /** - * Check if the task has a form - */ - hasFormKey(): boolean { - return (this.taskDetails && (!!this.taskDetails.formKey)); - } - isTaskActive() { return this.taskDetails && this.taskDetails.duration === null; } @@ -329,44 +299,6 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy { return !!this.taskDetails.assignee; } - private hasEmailAddress(): boolean { - return this.taskDetails.assignee.email ? true : false; - } - - isAssignedToMe(): boolean { - return this.isAssigned() && this.hasEmailAddress() ? - this.isEmailEqual(this.taskDetails.assignee.email, this.currentLoggedUser.email) : - this.isExternalIdEqual(this.taskDetails.assignee.externalId, this.currentLoggedUser.externalId); - } - - private isEmailEqual(assigneeMail: string, currentLoggedEmail: string): boolean { - return assigneeMail.toLocaleLowerCase() === currentLoggedEmail.toLocaleLowerCase(); - } - - private isExternalIdEqual(assigneeExternalId: string, currentUserExternalId: string): boolean { - return assigneeExternalId.toLocaleLowerCase() === currentUserExternalId.toLocaleLowerCase(); - } - - isCompleteButtonEnabled(): boolean { - return this.isAssignedToMe() || this.canInitiatorComplete(); - } - - isCompleteButtonVisible(): boolean { - return !this.hasFormKey() && this.isTaskActive() && this.isCompleteButtonEnabled(); - } - - canInitiatorComplete(): boolean { - return this.taskDetails.initiatorCanCompleteTask; - } - - isSaveButtonVisible(): boolean { - return this.hasSaveButton() && (!this.canInitiatorComplete() || this.isAssignedToMe()); - } - - hasSaveButton(): boolean { - return this.showFormSaveButton; - } - /** * Retrieve the next open task * @param processInstanceId @@ -395,9 +327,7 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy { * Complete button clicked */ onComplete(): void { - this.taskListService - .completeTask(this.taskId) - .subscribe(() => this.onFormCompleted(null)); + this.onFormCompleted(null); } onShowAttachForm() { @@ -410,6 +340,7 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy { onCompleteAttachForm() { this.showAttachForm = false; + this.taskFormComponent.loadTask(this.taskId); this.loadDetails(this.taskId); } @@ -464,10 +395,6 @@ export class TaskDetailsComponent implements OnInit, OnChanges, OnDestroy { this.loadDetails(taskId); } - isCompletedTask(): boolean { - return this.taskDetails && this.taskDetails.endDate ? true : undefined; - } - searchUser(searchedWord: string) { this.peopleProcessService.getWorkflowUsers(null, searchedWord) .subscribe( diff --git a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.html b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.html new file mode 100644 index 0000000000..b51dec7261 --- /dev/null +++ b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.html @@ -0,0 +1,74 @@ + + + + + + + + + + +

+ + {{taskDetails.name}} + + {{'FORM.FORM_RENDERER.NAMELESS_TASK' | translate}} + + +

+
+
+ + + + + + + + + + + + +
+
+
+
+ +
+ +
+
\ No newline at end of file diff --git a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.scss b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.scss new file mode 100644 index 0000000000..1d2cf3f358 --- /dev/null +++ b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.scss @@ -0,0 +1,32 @@ +@mixin adf-task-form-theme($theme) { + + $config: mat-typography-config(); + + .adf-task-form { + &-container { + overflow: hidden; + } + + &-actions { + float: right; + padding-bottom: 25px !important; + padding-right: 25px !important; + + & .mat-button { + height: 36px; + border-radius: 5px; + + } + + & .mat-button-wrapper { + width: 58px; + height: 20px; + opacity: 0.54; + font-size: mat-font-size($config, body-2); + font-weight: bold; + } + } + } + + +} diff --git a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts new file mode 100644 index 0000000000..edcb18d408 --- /dev/null +++ b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.spec.ts @@ -0,0 +1,504 @@ +/*! + * @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 { TaskFormComponent } from './task-form.component'; +import { + setupTestBed, + TranslationService, + TranslationMock, + FormService, + AuthenticationService, + FormModel, + FormOutcomeEvent, + FormOutcomeModel +} from '@alfresco/adf-core'; +import { NoopAnimationsModule } from '@angular/platform-browser/animations'; +import { TaskListService } from '../../services/tasklist.service'; +import { TranslateStore } from '@ngx-translate/core'; +import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core'; +import { of, throwError } from 'rxjs'; +import { + taskFormMock, + taskDetailsMock, + completedTaskDetailsMock, + taskDetailsWithOutFormMock, + standaloneTaskWithoutForm, + completedStandaloneTaskWithoutForm, + claimableTaskDetailsMock, + initiatorCanCompleteTaskDetailsMock +} from '../../../mock/task/task-details.mock'; +import { TaskDetailsModel } from '../../models/task-details.model'; +import { TaskListModule } from '../../task-list.module'; + +describe('TaskFormComponent', () => { + let component: TaskFormComponent; + let fixture: ComponentFixture; + let formService: FormService; + let taskListService: TaskListService; + let getTaskDetailsSpy: jasmine.Spy; + let completeTaskSpy: jasmine.Spy; + let element: HTMLElement; + let authService: AuthenticationService; + let getBpmLoggedUserSpy: jasmine.Spy; + + setupTestBed({ + imports: [ + NoopAnimationsModule, + TaskListModule + ], + providers: [ + { provide: TranslationService, useClass: TranslationMock }, + TranslateStore], + schemas: [NO_ERRORS_SCHEMA] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(TaskFormComponent); + component = fixture.componentInstance; + element = fixture.nativeElement; + + taskListService = TestBed.get(TaskListService); + formService = TestBed.get(FormService); + + getTaskDetailsSpy = spyOn(taskListService, 'getTaskDetails').and.returnValue(of(taskDetailsMock)); + completeTaskSpy = spyOn(taskListService, 'completeTask').and.returnValue(of({})); + spyOn(formService, 'getTaskForm').and.returnValue(of(taskFormMock)); + taskDetailsMock.processDefinitionId = null; + spyOn(formService, 'getTask').and.returnValue(of(taskDetailsMock)); + authService = TestBed.get(AuthenticationService); + getBpmLoggedUserSpy = spyOn(authService, 'getBpmLoggedUser').and.returnValue(of({ email: 'fake-email' })); + }); + + afterEach(async() => { + await fixture.whenStable(); + getTaskDetailsSpy.calls.reset(); + fixture.destroy(); + }); + + describe('Task with form', () => { + + beforeEach(async() => { + await fixture.whenStable(); + getTaskDetailsSpy.calls.reset(); + }); + + it('Should be able to display task form', async () => { + component.taskId = '123'; + taskDetailsMock.formKey = '4'; + component.currentLoggedUser = taskDetailsMock.assignee; + getTaskDetailsSpy.and.returnValue(of(taskDetailsMock)); + fixture.detectChanges(); + await fixture.whenStable(); + const activitFormSelector = element.querySelector('adf-form'); + const inputFieldOne = fixture.debugElement.nativeElement.querySelector('#text1'); + const inputFieldTwo = fixture.debugElement.nativeElement.querySelector('#text2'); + const inputFieldThree = fixture.debugElement.nativeElement.querySelector('#text3'); + expect(activitFormSelector).toBeDefined(); + expect(inputFieldOne['disabled']).toEqual(false); + expect(inputFieldTwo['disabled']).toEqual(false); + expect(inputFieldThree['disabled']).toEqual(false); + }); + + it('Should be able to complete assigned task', async () => { + getBpmLoggedUserSpy.and.returnValue(of({ id: 1001, firstName: 'Wilbur', lastName: 'Adams', email: 'wilbur@app.activiti.com' })); + getTaskDetailsSpy.and.returnValue(of(taskDetailsMock)); + const formCompletedSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit'); + const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(of({})); + component.taskId = '123'; + component.ngOnInit(); + fixture.detectChanges(); + await fixture.whenStable(); + const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete'); + expect(completeButton['disabled']).toEqual(false); + completeButton.click(); + expect(completeTaskFormSpy).toHaveBeenCalled(); + expect(formCompletedSpy).toHaveBeenCalled(); + }); + + it('Should be able to complete the task as a process initiator', async () => { + const formCompletedSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit'); + const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(of({})); + getTaskDetailsSpy.and.returnValue(of(initiatorCanCompleteTaskDetailsMock)); + component.taskId = '123'; + fixture.detectChanges(); + await fixture.whenStable(); + const activitFormSelector = element.querySelector('adf-form'); + const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete'); + expect(activitFormSelector).toBeDefined(); + expect(completeButton['disabled']).toEqual(false); + completeButton.click(); + expect(completeTaskFormSpy).toHaveBeenCalled(); + expect(formCompletedSpy).toHaveBeenCalled(); + }); + + it('Should emit error event in case form complete service fails', async () => { + const errorSpy: jasmine.Spy = spyOn(component.error, 'emit'); + const completeTaskFormSpy = spyOn(formService, 'completeTaskForm').and.returnValue(throwError({message: 'servce failed'})); + getTaskDetailsSpy.and.returnValue(of(initiatorCanCompleteTaskDetailsMock)); + component.taskId = '123'; + fixture.detectChanges(); + await fixture.whenStable(); + const activitFormSelector = element.querySelector('adf-form'); + const completeButton = fixture.debugElement.nativeElement.querySelector('#adf-form-complete'); + expect(activitFormSelector).toBeDefined(); + expect(completeButton['disabled']).toEqual(false); + completeButton.click(); + expect(completeTaskFormSpy).toHaveBeenCalled(); + expect(errorSpy).toHaveBeenCalled(); + }); + + }); + + describe('change detection', () => { + + beforeEach(async() => { + component.taskId = '123'; + fixture.detectChanges(); + await fixture.whenStable(); + getTaskDetailsSpy.calls.reset(); + }); + + it('should fetch new task details when taskId changed', () => { + const change = new SimpleChange('123', '456', true); + component.ngOnChanges({ 'taskId': change }); + fixture.detectChanges(); + expect(getTaskDetailsSpy).toHaveBeenCalledWith('123'); + }); + + it('should NOT fetch new task details when taskId changed to null', async () => { + const nullChange = new SimpleChange('123', null, true); + component.ngOnChanges({ 'taskId': nullChange }); + fixture.detectChanges(); + await fixture.whenStable(); + expect(getTaskDetailsSpy).not.toHaveBeenCalled(); + }); + }); + + describe('Task assigned to candidates', () => { + + beforeEach(async() => { + component.taskId = '123'; + fixture.detectChanges(); + await fixture.whenStable(); + getTaskDetailsSpy.calls.reset(); + }); + + it('Should be able to display form in readonly mode if the task assigned to candidates', async() => { + getTaskDetailsSpy.and.returnValue(of(claimableTaskDetailsMock)); + fixture.detectChanges(); + await fixture.whenStable(); + const activitFormSelector = element.querySelector('adf-form'); + const inputFieldOne = fixture.debugElement.nativeElement.querySelector('#text1'); + const inputFieldTwo = fixture.debugElement.nativeElement.querySelector('#text2'); + const inputFieldThree = fixture.debugElement.nativeElement.querySelector('#text3'); + expect(activitFormSelector).toBeDefined(); + expect(inputFieldOne['disabled']).toEqual(true); + expect(inputFieldTwo['disabled']).toEqual(true); + expect(inputFieldThree['disabled']).toEqual(true); + }); + }); + + describe('Form events', () => { + + beforeEach(() => { + component.taskId = '123'; + fixture.detectChanges(); + }); + + it('Should emit a save event when form saved', () => { + const formSavedSpy: jasmine.Spy = spyOn(component.formSaved, 'emit'); + component.onFormSaved(new FormModel()); + expect(formSavedSpy).toHaveBeenCalled(); + }); + + it('Should emit a outcome execution event when form outcome executed', () => { + const executeOutcomeSpy: jasmine.Spy = spyOn(component.executeOutcome, 'emit'); + component.onFormExecuteOutcome(new FormOutcomeEvent(new FormOutcomeModel(new FormModel()))); + expect(executeOutcomeSpy).toHaveBeenCalled(); + }); + + it('Should emit a complete event when form completed', () => { + const formCompletedSpy: jasmine.Spy = spyOn(component.formCompleted, 'emit'); + component.onFormCompleted(new FormModel()); + expect(formCompletedSpy).toHaveBeenCalled(); + }); + + it('Should call service to complete task when complete button clicked', () => { + component.onCompleteTask(); + expect(completeTaskSpy).toHaveBeenCalled(); + }); + + it('Should emit a complete event when complete button clicked and task completed', () => { + const completeSpy: jasmine.Spy = spyOn(component.completed, 'emit'); + component.onCompleteTask(); + expect(completeSpy).toHaveBeenCalled(); + }); + + it('Should emit a load event when form loaded', () => { + const formLoadedSpy: jasmine.Spy = spyOn(component.formLoaded, 'emit'); + component.onFormLoaded(new FormModel()); + expect(formLoadedSpy).toHaveBeenCalled(); + }); + + it('Should emit an error event when form error occurs', () => { + const formErrorSpy: jasmine.Spy = spyOn(component.formError, 'emit'); + component.onFormError({}); + expect(formErrorSpy).toHaveBeenCalled(); + }); + + it('Should emit an error event when form services fails', () => { + const errorSpy: jasmine.Spy = spyOn(component.error, 'emit'); + component.onError({}); + expect(errorSpy).toHaveBeenCalled(); + }); + }); + + describe('Completed Process Task', () => { + + beforeEach(async() => { + component.taskId = '123'; + fixture.detectChanges(); + await fixture.whenStable(); + getTaskDetailsSpy.calls.reset(); + }); + + it('Should be able to display form in readonly mode if the task completed', async() => { + getTaskDetailsSpy.and.returnValue(of(completedTaskDetailsMock)); + fixture.detectChanges(); + await fixture.whenStable(); + const activitFormSelector = element.querySelector('adf-form'); + const inputFieldOne = fixture.debugElement.nativeElement.querySelector('#text1'); + const inputFieldTwo = fixture.debugElement.nativeElement.querySelector('#text2'); + const inputFieldThree = fixture.debugElement.nativeElement.querySelector('#text3'); + expect(activitFormSelector).toBeDefined(); + expect(inputFieldOne['disabled']).toEqual(true); + expect(inputFieldTwo['disabled']).toEqual(true); + expect(inputFieldThree['disabled']).toEqual(true); + }); + + it('Should be able to show completed message and cancel button for the completed task without form', async () => { + completedTaskDetailsMock.formKey = null; + component.taskDetails = new TaskDetailsModel(completedTaskDetailsMock); + fixture.detectChanges(); + await fixture.whenStable(); + const completeButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-complete-button'); + const cancelButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-cancel-button'); + const completedFormMessage = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__title'); + const subMessage = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__subtitle'); + expect(completeButtonElement).toBeNull(); + expect(cancelButtonElement).not.toBeNull(); + expect(completedFormMessage.innerText).toContain('ADF_TASK_FORM.COMPLETED_TASK.TITLE'); + expect(subMessage.innerText).toContain('ADF_TASK_FORM.COMPLETED_TASK.SUBTITLE'); + }); + + it('Should not display complete button to the completed task without form', async () => { + completedTaskDetailsMock.formKey = null; + component.taskDetails = new TaskDetailsModel(completedTaskDetailsMock); + fixture.detectChanges(); + await fixture.whenStable(); + const completeButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-complete-button'); + expect(completeButtonElement).toBeNull(); + }); + }); + + describe('Process Task with no form', () => { + + beforeEach(() => { + component.taskId = '123'; + fixture.detectChanges(); + }); + + it('Should be able to show no form message if the task does not attached a form', async () => { + component.taskDetails = new TaskDetailsModel(taskDetailsWithOutFormMock); + fixture.detectChanges(); + await fixture.whenStable(); + const completeButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-complete-button'); + const cancelButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-cancel-button'); + const completedFormMessage = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__title'); + const subMessage = fixture.debugElement.nativeElement.querySelector('.adf-empty-content__subtitle'); + expect(completeButtonElement).not.toBeNull(); + expect(cancelButtonElement).not.toBeNull(); + expect(completedFormMessage.innerText).toContain('ADF_TASK_LIST.STANDALONE_TASK.NO_FORM_MESSAGE'); + expect(subMessage.innerText).toContain('ADF_TASK_FORM.EMPTY_FORM.SUBTITLE'); + }); + + it('Should be able display complete button to a task without form', async () => { + component.taskDetails = new TaskDetailsModel(taskDetailsWithOutFormMock); + fixture.detectChanges(); + await fixture.whenStable(); + const completeButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-complete-button'); + expect(completeButtonElement).not.toBeNull(); + expect(completeButtonElement['disabled']).toEqual(false); + }); + + it('Should be able to complete a task with no form when complete button is clicked', async () => { + fixture.detectChanges(); + component.taskDetails = new TaskDetailsModel(taskDetailsWithOutFormMock); + fixture.detectChanges(); + await fixture.whenStable(); + const completeButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-complete-button'); + completeButtonElement.click(); + expect(completeTaskSpy).toHaveBeenCalledWith('91'); + }); + + it('Should emit error event in case complete task service fails', async () => { + const errorSpy: jasmine.Spy = spyOn(component.error, 'emit'); + completeTaskSpy.and.returnValue(throwError({message: 'servce failed'})); + component.taskDetails = new TaskDetailsModel(taskDetailsWithOutFormMock); + fixture.detectChanges(); + await fixture.whenStable(); + const activitFormSelector = element.querySelector('adf-form'); + const completeButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-complete-button'); + expect(activitFormSelector).toBeDefined(); + expect(completeButtonElement['disabled']).toEqual(false); + completeButtonElement.click(); + expect(errorSpy).toHaveBeenCalled(); + }); + + it('Should be able to emit cancel event on task with no-form when cancel button is clicked', async () => { + const cancelSpy = spyOn(component.cancel, 'emit'); + getTaskDetailsSpy.and.returnValue(of(taskDetailsWithOutFormMock)); + component.taskDetails = new TaskDetailsModel(taskDetailsWithOutFormMock); + fixture.detectChanges(); + await fixture.whenStable(); + const cancelButtonElement = fixture.debugElement.nativeElement.querySelector('#adf-no-form-cancel-button'); + cancelButtonElement.click(); + expect(cancelSpy).toHaveBeenCalled(); + }); + }); + + describe('Standalone Task with no form', () => { + + beforeEach(() => { + component.taskId = '123'; + fixture.detectChanges(); + }); + + it('Should be able to display empty template in case standalone task does not attached a form', async () => { + component.taskDetails = new TaskDetailsModel(standaloneTaskWithoutForm); + fixture.detectChanges(); + await fixture.whenStable(); + const taskStandAlone = element.querySelector('adf-task-standalone'); + const noFormMessage = fixture.debugElement.nativeElement.querySelector('#adf-no-form-message'); + expect(taskStandAlone).not.toBeNull(); + expect(noFormMessage.innerText).toContain('ADF_TASK_LIST.STANDALONE_TASK.NO_FORM_MESSAGE'); + }); + + it('Should be able display attach form button for a standalone task without form', async() => { + const showAttachFormSpy = spyOn(component.showAttachForm, 'emit'); + component.taskDetails = new TaskDetailsModel(standaloneTaskWithoutForm); + fixture.detectChanges(); + await fixture.whenStable(); + const attacheFormButton = fixture.debugElement.nativeElement.querySelector('#adf-no-form-attach-form-button'); + expect(attacheFormButton).not.toBeNull(); + attacheFormButton.click(); + expect(showAttachFormSpy).toHaveBeenCalled(); + }); + + it('Should be able to display completed template if standalone task completed', async() => { + component.taskDetails = completedStandaloneTaskWithoutForm; + fixture.detectChanges(); + await fixture.whenStable(); + const taskStandAlone = element.querySelector('adf-task-standalone'); + const completedFormMessage = fixture.debugElement.nativeElement.querySelector('#adf-completed-form-message'); + const subMessage = fixture.debugElement.nativeElement.querySelector('.adf-no-form-submessage'); + expect(taskStandAlone).not.toBeNull(); + expect(completedFormMessage.innerText).toContain('ADF_TASK_LIST.STANDALONE_TASK.COMPLETE_TASK_MESSAGE'); + expect(subMessage.innerText).toContain('ADF_TASK_LIST.STANDALONE_TASK.COMPLETE_TASK_SUB_MESSAGE'); + }); + }); + + describe('Form with visiblity', () => { + + beforeEach(async () => { + component.taskId = '123'; + spyOn(formService, 'completeTaskForm').and.returnValue(of({})); + taskDetailsMock.formKey = '4'; + getTaskDetailsSpy.and.returnValue(of(taskDetailsMock)); + fixture.detectChanges(); + await fixture.whenStable(); + }); + + it('[C312410] - Should be possible to complete a task that has an invisible field on a form with a value', async () => { + component.formCompleted.subscribe((form: FormModel) => { + expect(form.id).toBe(taskFormMock.id); + }); + component.taskDetails.initiatorCanCompleteTask = true; + + fixture.detectChanges(); + await fixture.whenStable(); + const inputTextOne: HTMLInputElement = fixture.nativeElement.querySelector('#text1'); + expect(inputTextOne).toBeDefined(); + expect(inputTextOne).not.toBeNull(); + const inputTextTwo: HTMLInputElement = fixture.nativeElement.querySelector('#text2'); + expect(inputTextTwo).toBeDefined(); + expect(inputTextTwo).not.toBeNull(); + let inputTextThree: HTMLInputElement = fixture.nativeElement.querySelector('#text3'); + expect(inputTextThree).toBeDefined(); + expect(inputTextThree).not.toBeNull(); + + inputTextOne.value = 'a'; + inputTextOne.dispatchEvent(new Event('input')); + inputTextTwo.value = 'a'; + inputTextTwo.dispatchEvent(new Event('input')); + inputTextThree.value = 'a'; + inputTextThree.dispatchEvent(new Event('input')); + fixture.detectChanges(); + await fixture.whenStable(); + inputTextThree = fixture.nativeElement.querySelector('#text3'); + expect(inputTextThree).toBeDefined(); + expect(inputTextThree).not.toBeNull(); + + inputTextOne.value = 'b'; + inputTextOne.dispatchEvent(new Event('input')); + fixture.detectChanges(); + await fixture.whenStable(); + const inputThreeContainer = fixture.nativeElement.querySelector('#field-text3-container'); + expect(inputThreeContainer.hidden).toBe(true); + const completeOutcomeButton: HTMLButtonElement = fixture.nativeElement.querySelector('#adf-form-complete'); + expect(completeOutcomeButton.hidden).toBe(false); + completeOutcomeButton.click(); + fixture.detectChanges(); + }); + + it('[C277278] - Should show if the form is valid via the validation icon', async () => { + const numberInput: HTMLInputElement = fixture.nativeElement.querySelector('#numberField'); + let validationForm = fixture.nativeElement.querySelector('#adf-valid-form-icon'); + + expect(numberInput).toBeDefined(); + expect(numberInput).not.toBeNull(); + expect(validationForm.textContent).toBe('check_circle'); + + numberInput.value = 'a'; + numberInput.dispatchEvent(new Event('input')); + fixture.detectChanges(); + await fixture.whenStable(); + const invalidForm = fixture.nativeElement.querySelector('#adf-invalid-form-icon'); + expect(invalidForm).not.toBeNull(); + expect(invalidForm.textContent).toBe('error'); + + numberInput.value = '4'; + numberInput.dispatchEvent(new Event('input')); + fixture.detectChanges(); + await fixture.whenStable(); + validationForm = fixture.nativeElement.querySelector('#adf-valid-form-icon'); + expect(validationForm.textContent).toBe('check_circle'); + }); + }); +}); diff --git a/lib/process-services/src/lib/task-list/components/task-form/task-form.component.ts b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.ts new file mode 100644 index 0000000000..0cd3b8188a --- /dev/null +++ b/lib/process-services/src/lib/task-list/components/task-form/task-form.component.ts @@ -0,0 +1,287 @@ +/*! + * @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 { Component, OnInit, Input, Output, EventEmitter, SimpleChanges } from '@angular/core'; +import { + FormModel, + ContentLinkModel, + FormRenderingService, + FormFieldValidator, + FormOutcomeEvent, + AuthenticationService, + TranslationService, + FormFieldModel +} from '@alfresco/adf-core'; +import { TaskDetailsModel } from '../../models/task-details.model'; +import { TaskListService } from '../../services/tasklist.service'; +import { UserRepresentation } from '@alfresco/js-api'; +import { AttachFileWidgetComponent } from '../../../content-widget/attach-file-widget.component'; +import { AttachFolderWidgetComponent } from '../../../content-widget/attach-folder-widget.component'; +import { Observable } from 'rxjs'; + +@Component({ + selector: 'adf-task-form', + templateUrl: './task-form.component.html', + styleUrls: ['./task-form.component.scss'] +}) +export class TaskFormComponent implements OnInit { + + /** (**required**) The id of the task whose details we are asking for. */ + @Input() + taskId: string; + + /** Toggles rendering of the form title. */ + @Input() + showFormTitle: boolean = false; + + /** Toggles rendering of the `Complete` outcome button. */ + @Input() + showFormCompleteButton: boolean = true; + + /** Toggles rendering of the `Save` outcome button. */ + @Input() + showFormSaveButton: boolean = true; + + /** Toggle rendering of the `Cancel` button. */ + @Input() + showCancelButton: boolean = true; + + /** Toggles read-only state of the form. All form widgets render as read-only + * if enabled. + */ + @Input() + readOnlyForm: boolean = false; + + /** Toggles rendering of the `Refresh` button. */ + @Input() + showFormRefreshButton: boolean = true; + + /** Toggle rendering of the validation icon next to the form title. */ + @Input() + showFormValidationIcon: boolean = true; + + /** Field validators for use with the form. */ + @Input() + fieldValidators: FormFieldValidator[] = []; + + /** Emitted when the form is submitted with the `Save` or custom outcomes. */ + @Output() + formSaved = new EventEmitter(); + + /** Emitted when the form is submitted with the `Complete` outcome. */ + @Output() + formCompleted = new EventEmitter(); + + /** Emitted when the form field content is clicked. */ + @Output() + formContentClicked = new EventEmitter(); + + /** Emitted when the form is loaded or reloaded. */ + @Output() + formLoaded = new EventEmitter(); + + /** Emitted when the form associated with the form task is attached. */ + @Output() + showAttachForm: EventEmitter = new EventEmitter(); + + /** Emitted when any outcome is executed. Default behaviour can be prevented + * via `event.preventDefault()`. + */ + @Output() + executeOutcome = new EventEmitter(); + + /** Emitted when the form associated with the task is completed. */ + @Output() + completed = new EventEmitter(); + + /** Emitted when the supplied form values have a validation error. */ + @Output() + formError: EventEmitter = new EventEmitter(); + + /** Emitted when an error occurs. */ + @Output() + error = new EventEmitter(); + + /** Emitted when the "Cancel" button is clicked. */ + @Output() + cancel = new EventEmitter(); + + taskDetails: TaskDetailsModel; + currentLoggedUser: UserRepresentation; + loading: boolean = false; + completedTaskMessage: string; + internalReadOnlyForm: boolean = false; + + constructor( + private taskListService: TaskListService, + private authService: AuthenticationService, + private formRenderingService: FormRenderingService, + private translationService: TranslationService + ) { + this.formRenderingService.setComponentTypeResolver('upload', () => AttachFileWidgetComponent, true); + this.formRenderingService.setComponentTypeResolver('select-folder', () => AttachFolderWidgetComponent, true); + } + + ngOnInit() { + this.authService.getBpmLoggedUser().subscribe(user => { + this.currentLoggedUser = user; + }); + this.loadTask(this.taskId); + } + + ngOnChanges(changes: SimpleChanges) { + const taskId = changes['taskId']; + if (taskId && taskId.currentValue) { + this.loadTask(this.taskId); + return; + } + } + + loadTask(taskId: string) { + this.loading = true; + if (taskId) { + this.taskListService.getTaskDetails(taskId).subscribe( + (res: TaskDetailsModel) => { + this.taskDetails = res; + + if (!this.taskDetails.name) { + this.taskDetails.name = 'No name'; + } + + const endDate: any = res.endDate; + if (endDate && !isNaN(endDate.getTime())) { + this.internalReadOnlyForm = true; + } else { + this.internalReadOnlyForm = this.readOnlyForm; + } + this.loading = false; + }); + } + } + + onFormSaved(savedForm: FormModel) { + this.formSaved.emit(savedForm); + } + + onFormCompleted(form: FormModel) { + this.formCompleted.emit(form); + } + + onFormLoaded(form: FormModel): void { + this.formLoaded.emit(form); + } + + onFormContentClick(content: ContentLinkModel): void { + this.formContentClicked.emit(content); + } + + onFormExecuteOutcome(outcome: FormOutcomeEvent) { + this.executeOutcome.emit(outcome); + } + + onFormError(error: any) { + this.formError.emit(error); + } + + onError(error: any) { + this.error.emit(error); + } + + onCompleteTask() { + this.taskListService.completeTask(this.taskDetails.id).subscribe( + () => this.completed.emit(), + (error) => this.error.emit(error)); + } + + onCancel() { + this.cancel.emit(); + } + + onShowAttachForm() { + this.showAttachForm.emit(); + } + + hasFormKey(): boolean { + return (this.taskDetails && (!!this.taskDetails.formKey)); + } + + isStandaloneTask(): boolean { + return !(this.taskDetails && (!!this.taskDetails.processDefinitionId)); + } + + isTaskLoaded(): boolean { + return !!this.taskDetails; + } + + isCompletedTask(): boolean { + return this.taskDetails && this.taskDetails.endDate !== undefined && this.taskDetails.endDate !== null; + } + + isCompleteButtonVisible(): boolean { + return !this.hasFormKey() && this.isTaskActive() && this.isCompleteButtonEnabled(); + } + + isTaskActive() { + return this.taskDetails && this.taskDetails.duration === null; + } + + isAssigned(): boolean { + return !!this.taskDetails.assignee; + } + + private hasEmailAddress(): boolean { + return this.taskDetails.assignee.email ? true : false; + } + + isAssignedToMe(): boolean { + return this.isAssigned() && this.hasEmailAddress() ? + this.isEmailEqual() : + this.isExternalIdEqual(); + } + + private isEmailEqual(): boolean { + return (this.taskDetails.assignee && this.currentLoggedUser) && ( this.taskDetails.assignee.email.toLocaleLowerCase() === this.currentLoggedUser.email.toLocaleLowerCase()); + } + + private isExternalIdEqual(): boolean { + return (this.taskDetails.assignee && this.currentLoggedUser) && (this.taskDetails.assignee.externalId === this.currentLoggedUser.externalId); + } + + isCompleteButtonEnabled(): boolean { + return this.isAssignedToMe() || this.canInitiatorComplete(); + } + + canInitiatorComplete(): boolean { + return this.taskDetails.initiatorCanCompleteTask; + } + + isReadOnlyForm(): boolean { + return this.internalReadOnlyForm || !(this.isAssignedToMe() || this.canInitiatorComplete()); + } + + isSaveButtonVisible(): boolean { + return this.showFormSaveButton && (!this.canInitiatorComplete() || this.isAssignedToMe()); + } + + canCompleteTask(): boolean { + return !this.isCompletedTask() && this.isAssignedToMe(); + } + + getCompletedTaskTranslatedMessage(): Observable { + return this.translationService.get('ADF_TASK_FORM.COMPLETED_TASK.TITLE', { taskName: this.taskDetails.name }); + } +} diff --git a/lib/process-services/src/lib/task-list/public-api.ts b/lib/process-services/src/lib/task-list/public-api.ts index c431ce01f5..a921856032 100644 --- a/lib/process-services/src/lib/task-list/public-api.ts +++ b/lib/process-services/src/lib/task-list/public-api.ts @@ -20,6 +20,7 @@ export * from './components/checklist.component'; export * from './components/task-header.component'; export * from './components/no-task-detail-template.directive'; export * from './components/task-filters.component'; +export * from './components/task-form/task-form.component'; export * from './components/task-details.component'; export * from './components/task-audit.directive'; export * from './components/start-task.component'; diff --git a/lib/process-services/src/lib/task-list/task-list.module.ts b/lib/process-services/src/lib/task-list/task-list.module.ts index 9d66ca47a4..76eaae9156 100644 --- a/lib/process-services/src/lib/task-list/task-list.module.ts +++ b/lib/process-services/src/lib/task-list/task-list.module.ts @@ -31,6 +31,7 @@ import { NoTaskDetailsTemplateDirective } from './components/no-task-detail-temp import { StartTaskComponent } from './components/start-task.component'; import { TaskAuditDirective } from './components/task-audit.directive'; import { TaskDetailsComponent } from './components/task-details.component'; +import { TaskFormComponent } from './components/task-form/task-form.component'; import { TaskFiltersComponent } from './components/task-filters.component'; import { TaskHeaderComponent } from './components/task-header.component'; import { TaskListComponent } from './components/task-list.component'; @@ -56,6 +57,7 @@ import { FormModule } from '../form/form.module'; TaskFiltersComponent, TaskListComponent, TaskDetailsComponent, + TaskFormComponent, TaskAuditDirective, ChecklistComponent, TaskHeaderComponent, @@ -68,6 +70,7 @@ import { FormModule } from '../form/form.module'; TaskFiltersComponent, TaskListComponent, TaskDetailsComponent, + TaskFormComponent, TaskAuditDirective, ChecklistComponent, TaskHeaderComponent, diff --git a/lib/testing/src/lib/core/pages/form/form-fields.ts b/lib/testing/src/lib/core/pages/form/form-fields.ts index 3b808d67c7..7189cb2d86 100644 --- a/lib/testing/src/lib/core/pages/form/form-fields.ts +++ b/lib/testing/src/lib/core/pages/form/form-fields.ts @@ -26,9 +26,9 @@ export class FormFields { saveButton: ElementFinder = element(by.cssContainingText('mat-card-actions[class*="adf-for"] span', 'SAVE')); valueLocator: Locator = by.css('input'); labelLocator: Locator = by.css('label'); - noFormMessage: ElementFinder = element(by.css('span[id*="no-form-message"]')); + noFormMessage: ElementFinder = element(by.css('.adf-empty-content__title')); completedTaskNoFormMessage: ElementFinder = element(by.css('div[id*="completed-form-message"] p')); - attachFormButton: ElementFinder = element(by.id('adf-no-form-attach-form-button')); + attachFormButton: ElementFinder = element(by.id('adf-attach-form-attach-button')); completeButton: ElementFinder = element(by.id('adf-form-complete')); errorMessage: Locator = by.css('.adf-error-text-container .adf-error-text'); diff --git a/lib/testing/src/lib/process-services/pages/form-fields.page.ts b/lib/testing/src/lib/process-services/pages/form-fields.page.ts index d366fbe984..47ccf20545 100644 --- a/lib/testing/src/lib/process-services/pages/form-fields.page.ts +++ b/lib/testing/src/lib/process-services/pages/form-fields.page.ts @@ -28,9 +28,9 @@ export class FormFieldsPage { saveButton: ElementFinder = element(by.cssContainingText('mat-card-actions[class*="adf-for"] span', 'SAVE')); valueLocator: Locator = by.css('input'); labelLocator: Locator = by.css('label'); - noFormMessage: ElementFinder = element(by.css('span[id*="no-form-message"]')); + noFormMessage: ElementFinder = element(by.css('.adf-empty-content__title')); completedTaskNoFormMessage: ElementFinder = element(by.css('div[id*="completed-form-message"] p')); - attachFormButton: ElementFinder = element(by.id('adf-no-form-attach-form-button')); + attachFormButton: ElementFinder = element(by.id('adf-attach-form-attach-button')); completeButton: ElementFinder = element(by.id('adf-form-complete')); errorMessage: Locator = by.css('.adf-error-text-container .adf-error-text');