diff --git a/demo-shell/resources/i18n/en.json b/demo-shell/resources/i18n/en.json index ae460cccb4..647904c1b0 100644 --- a/demo-shell/resources/i18n/en.json +++ b/demo-shell/resources/i18n/en.json @@ -105,7 +105,8 @@ "PEOPLE_CLOUD": "People Cloud Component", "GROUPS_CLOUD": "Groups Cloud Component", "CONFIRM-DIALOG": "Confirmation Dialog", - "COMMUNITY": "Community" + "COMMUNITY": "Community", + "SERVICE_TASK_LIST": "Service Task List" }, "TRASHCAN": { "ACTIONS": { diff --git a/demo-shell/src/app.config.json b/demo-shell/src/app.config.json index 0f3ecbdc8f..cf55983c35 100644 --- a/demo-shell/src/app.config.json +++ b/demo-shell/src/app.config.json @@ -969,6 +969,35 @@ "delete" ] }, + "adf-edit-service-task-filter": { + "filterProperties": [ + "appName", + "serviceTaskId", + "activityName", + "activityType", + "completedDate", + "startedDate", + "sort", + "order", + "status", + "elementId", + "serviceName", + "processInstanceId", + "processDefinitionId", + "serviceName" + ], + "sortProperties": [ + "id", + "activityName", + "completedDate", + "startedDate" + ], + "actions": [ + "save", + "saveAs", + "delete" + ] + }, "adf-edit-process-filter": { "filterProperties": [ "status", @@ -1066,6 +1095,44 @@ ] } }, + "adf-cloud-service-task-list": { + "presets": { + "default": [ + { + "key": "entry.id", + "type": "text", + "title": "ADF_CLOUD_SERVICE_TASK_LIST.PROPERTIES.ID", + "sortable": true + }, + { + "key": "entry.activityName", + "type": "text", + "title": "ADF_CLOUD_SERVICE_TASK_LIST.PROPERTIES.ACTIVITY_NAME", + "sortable": true + }, + { + "key": "entry.status", + "type": "text", + "title": "ADF_CLOUD_SERVICE_TASK_LIST.PROPERTIES.STATUS", + "sortable": true + }, + { + "key": "entry.startedDate", + "type": "date", + "title": "ADF_CLOUD_SERVICE_TASK_LIST.PROPERTIES.STARTED_DATE", + "sortable": true, + "format": "timeAgo" + }, + { + "key": "entry.completedDate", + "type": "date", + "title": "ADF_CLOUD_SERVICE_TASK_LIST.PROPERTIES.COMPLETED_DATE", + "sortable": true, + "format": "timeAgo" + } + ] + } + }, "adf-cloud-process-list": { "presets": { "default": [ diff --git a/demo-shell/src/app/app.module.ts b/demo-shell/src/app/app.module.ts index 3e4434206f..38eed6b3ed 100644 --- a/demo-shell/src/app/app.module.ts +++ b/demo-shell/src/app/app.module.ts @@ -65,6 +65,7 @@ import { TasksCloudDemoComponent } from './components/cloud/tasks-cloud-demo.com import { ProcessesCloudDemoComponent } from './components/cloud/processes-cloud-demo.component'; import { TaskDetailsCloudDemoComponent } from './components/cloud/task-details-cloud-demo.component'; import { TaskHeaderCloudDemoComponent } from './components/cloud/task-header-cloud-demo.component'; +import { ServiceTaskListCloudDemoComponent } from './components/cloud/service-task-list-cloud-demo.component'; import { CloudViewerComponent } from './components/cloud/cloud-viewer.component'; import { ProcessDetailsCloudDemoComponent } from './components/cloud/process-details-cloud-demo.component'; import { StartTaskCloudDemoComponent } from './components/cloud/start-task-cloud-demo.component'; @@ -193,7 +194,8 @@ registerLocaleData(localeSv); ConfirmDialogExampleComponent, CustomEditorComponent, CustomWidgetComponent, - ProcessCloudLayoutComponent + ProcessCloudLayoutComponent, + ServiceTaskListCloudDemoComponent ], providers: [ { diff --git a/demo-shell/src/app/app.routes.ts b/demo-shell/src/app/app.routes.ts index c98c513e92..6bd5e74a23 100644 --- a/demo-shell/src/app/app.routes.ts +++ b/demo-shell/src/app/app.routes.ts @@ -54,6 +54,7 @@ import { DemoErrorComponent } from './components/error/demo-error.component'; import { TaskHeaderCloudDemoComponent } from './components/cloud/task-header-cloud-demo.component'; import { FilteredSearchComponent } from './components/files/filtered-search.component'; import { ProcessCloudLayoutComponent } from './components/cloud/process-cloud-layout.component'; +import { ServiceTaskListCloudDemoComponent } from './components/cloud/service-task-list-cloud-demo.component'; export const appRoutes: Routes = [ { path: 'login', loadChildren: () => import('./components/login/login.module').then(m => m.AppLoginModule) }, @@ -176,22 +177,31 @@ export const appRoutes: Routes = [ { path: 'cloud', canActivate: [AuthGuardSsoRoleService], - data: { roles: ['ACTIVITI_USER'], redirectUrl: '/error/403' }, + data: { roles: ['ACTIVITI_ADMIN', 'ACTIVITI_USER'], redirectUrl: '/error/403' }, children: [ { path: '', + data: { roles: ['ACTIVITI_USER'], redirectUrl: '/error/403' }, component: AppsCloudDemoComponent }, { path: 'people-group-cloud', + data: { roles: ['ACTIVITI_USER'], redirectUrl: '/error/403' }, component: PeopleGroupCloudDemoComponent }, { path: 'task-header-cloud', + data: { roles: ['ACTIVITI_USER'], redirectUrl: '/error/403' }, component: TaskHeaderCloudDemoComponent }, + { + path: 'service-task-list', + data: { roles: ['ACTIVITI_ADMIN'], redirectUrl: '/error/403' }, + component: ServiceTaskListCloudDemoComponent + }, { path: 'community', + data: { roles: ['ACTIVITI_USER'], redirectUrl: '/error/403' }, loadChildren: () => import('./components/cloud/community/community.module').then(m => m.AppCommunityModule) }, { diff --git a/demo-shell/src/app/components/app-layout/app-layout.component.ts b/demo-shell/src/app/components/app-layout/app-layout.component.ts index 334c159f47..76faedde67 100644 --- a/demo-shell/src/app/components/app-layout/app-layout.component.ts +++ b/demo-shell/src/app/components/app-layout/app-layout.component.ts @@ -55,7 +55,8 @@ export class AppLayoutComponent implements OnInit, OnDestroy { { href: '/cloud/community', icon: 'cloud', title: 'APP_LAYOUT.COMMUNITY' }, { href: '/form-cloud', icon: 'poll', title: 'APP_LAYOUT.FORM' }, { href: '/cloud/people-group-cloud', icon: 'group', title: 'APP_LAYOUT.PEOPLE_GROUPS_CLOUD' }, - { href: '/cloud/task-header-cloud', icon: 'cloud', title: 'APP_LAYOUT.TASK_HEADER_CLOUD.COMPONENT_NAME' } + { href: '/cloud/task-header-cloud', icon: 'cloud', title: 'APP_LAYOUT.TASK_HEADER_CLOUD.COMPONENT_NAME' }, + { href: '/cloud/service-task-list', icon: 'cloud', title: 'APP_LAYOUT.SERVICE_TASK_LIST' } ] }, { href: '/activiti', icon: 'device_hub', title: 'APP_LAYOUT.PROCESS_SERVICES', children: [ diff --git a/demo-shell/src/app/components/cloud/service-task-list-cloud-demo.component.html b/demo-shell/src/app/components/cloud/service-task-list-cloud-demo.component.html new file mode 100644 index 0000000000..e95cb3aecb --- /dev/null +++ b/demo-shell/src/app/components/cloud/service-task-list-cloud-demo.component.html @@ -0,0 +1,29 @@ +
+ + +
+ + + + +
+
diff --git a/demo-shell/src/app/components/cloud/service-task-list-cloud-demo.component.ts b/demo-shell/src/app/components/cloud/service-task-list-cloud-demo.component.ts new file mode 100644 index 0000000000..747ec48113 --- /dev/null +++ b/demo-shell/src/app/components/cloud/service-task-list-cloud-demo.component.ts @@ -0,0 +1,101 @@ +/*! + * @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, OnDestroy, ViewChild } from '@angular/core'; +import { TaskListCloudSortingModel, ServiceTaskListCloudComponent, ServiceTaskFilterCloudModel } from '@alfresco/adf-process-services-cloud'; +import { UserPreferencesService, AppConfigService, PaginationModel } from '@alfresco/adf-core'; +import { CloudLayoutService, CloudServiceSettings } from './services/cloud-layout.service'; +import { Subject } from 'rxjs'; +import { takeUntil } from 'rxjs/operators'; + +@Component({ + templateUrl: 'service-task-list-cloud-demo.component.html' +}) +export class ServiceTaskListCloudDemoComponent implements OnInit, OnDestroy { + + public static ACTION_SAVE_AS = 'saveAs'; + public static ACTION_DELETE = 'delete'; + static TASK_FILTER_PROPERTY_KEYS = 'adf-edit-service-task-filter'; + + @ViewChild('taskCloud') + taskCloud: ServiceTaskListCloudComponent; + + isFilterLoaded = false; + + selectedRow: any; + + sortArray: TaskListCloudSortingModel[]; + editedFilter: ServiceTaskFilterCloudModel; + taskFilterProperties: any = { filterProperties: [], sortProperties: [], actions: [] }; + + multiselect: boolean; + selectedRows: string[] = []; + actionMenu: boolean; + contextMenu: boolean; + actions: any[] = []; + selectedAction: { id: number, name: string, actionType: string}; + selectedContextAction: { id: number, name: string, actionType: string}; + selectionMode: string; + + private onDestroy$ = new Subject(); + + constructor( + private cloudLayoutService: CloudLayoutService, + private userPreference: UserPreferencesService, + private appConfig: AppConfigService) { + + const properties = this.appConfig.get>(ServiceTaskListCloudDemoComponent.TASK_FILTER_PROPERTY_KEYS); + if (properties === this.taskFilterProperties) { + this.taskFilterProperties = properties; + } + } + + ngOnInit() { + this.isFilterLoaded = false; + this.cloudLayoutService.settings$ + .pipe(takeUntil(this.onDestroy$)) + .subscribe(settings => this.setCurrentSettings(settings)); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + + setCurrentSettings(settings: CloudServiceSettings) { + if (settings) { + this.multiselect = settings.multiselect; + this.selectionMode = settings.selectionMode; + this.actionMenu = settings.actionMenu; + this.contextMenu = settings.contextMenu; + this.actions = settings.actions; + } + } + + onChangePageSize(event: PaginationModel) { + this.userPreference.paginationSize = event.maxItems; + } + + resetSelectedRows() { + this.selectedRows = []; + } + + onFilterChange(filter: ServiceTaskFilterCloudModel) { + this.editedFilter = Object.assign({}, filter); + this.sortArray = [new TaskListCloudSortingModel({ orderBy: this.editedFilter.sort, direction: this.editedFilter.order })]; + } +} diff --git a/lib/core/datatable/data/data-table.schema.ts b/lib/core/datatable/data/data-table.schema.ts index ad0198286d..219bb2bc4e 100644 --- a/lib/core/datatable/data/data-table.schema.ts +++ b/lib/core/datatable/data/data-table.schema.ts @@ -79,4 +79,12 @@ export abstract class DataTableSchema { private getDefaultLayoutPreset(): DataColumn[] { return (this.layoutPresets['default']).map((col) => new ObjectDataColumn(col)); } + + public setPresetKey(presetKey: string) { + this.presetKey = presetKey; + } + + public setPresetsModel(presetsModel: any) { + this.presetsModel = presetsModel; + } } diff --git a/lib/process-services-cloud/src/lib/i18n/en.json b/lib/process-services-cloud/src/lib/i18n/en.json index 3038e0f7a2..4a127240b9 100644 --- a/lib/process-services-cloud/src/lib/i18n/en.json +++ b/lib/process-services-cloud/src/lib/i18n/en.json @@ -101,11 +101,26 @@ } } }, + "ADF_CLOUD_SERVICE_TASK_LIST": { + "PROPERTIES": { + "ACTIVITY_NAME": "Activity name", + "ACTIVITY_TYPE": "Status", + "ID": "Id", + "COMPLETED_DATE": "Completed date", + "STARTED_DATE": "Started date", + "STATUS": "Status" + } + }, "ADF_CLOUD_TASK_FILTERS": { "MY_TASKS": "My Tasks", "QUEUED_TASKS": "Queued Tasks", "COMPLETED_TASKS": "Completed Tasks" }, + "ADF_CLOUD_SERVICE_TASK_FILTERS": { + "ALL_SERVICE_TASKS": "All Service Tasks", + "ERRORED_TASKS": "Errored Tasks", + "COMPLETED_TASKS": "Completed Tasks" + }, "ADF_CLOUD_PROCESS_FILTERS": { "ALL_PROCESSES": "All", "RUNNING_PROCESSES": "Running", @@ -153,6 +168,24 @@ "CANCEL": "CANCEL" } }, + "ADF_CLOUD_EDIT_SERVICE_TASK_FILTER": { + "LABEL": { + "APP_NAME": "ApplicationName", + "SERVICE_TASK_ID": "ServiceTaskId", + "SERVICE_NAME": "ServiceName", + "ELEMENT_ID": "ElementId", + "PROCESS_DEF_NAME": "ProcessDefinitionName", + "PROCESS_DEF_ID": "ProcessDefinitionId", + "PROCESS_INSTANCE_ID": "ProcessInstanceId", + "STATUS": "Status", + "DIRECTION": "Direction", + "ACTIVITY_NAME": "ActivityName", + "ACTIVITY_TYPE": "ActivityType", + "SORT": "Sort", + "STARTED_DATE": "StartedDate", + "COMPLETED_DATE": "CompletedDate" + } + }, "ADF_CLOUD_EDIT_PROCESS_FILTER": { "TITLE": "Customize your filter", "LABEL": { diff --git a/lib/process-services-cloud/src/lib/styles/_index.scss b/lib/process-services-cloud/src/lib/styles/_index.scss index 6f59178c9d..9acaec92d5 100644 --- a/lib/process-services-cloud/src/lib/styles/_index.scss +++ b/lib/process-services-cloud/src/lib/styles/_index.scss @@ -6,8 +6,8 @@ @import './../process/process-filters/components/edit-process-filter-cloud.component.scss'; @import './../task/task-form/components/task-form-cloud.component'; @import './../task/start-task/components/start-task-cloud.component.scss'; -@import './../task/task-filters/components/edit-task-filter-cloud.component.scss'; -@import './../task/task-filters/components/task-filters-cloud.component.scss'; +@import './../task/task-filters/components/edit-task-filters/base-edit-task-filter-cloud.component.scss'; +@import './../task/task-filters/components/base-task-filters-cloud.component.scss'; @import './../process/start-process/components/start-process-cloud.component'; @import './../form/components/widgets/attach-file/attach-file-cloud-widget.component.scss'; diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-filters/components/base-task-filters-cloud.component.html similarity index 100% rename from lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.html rename to lib/process-services-cloud/src/lib/task/task-filters/components/base-task-filters-cloud.component.html diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.scss b/lib/process-services-cloud/src/lib/task/task-filters/components/base-task-filters-cloud.component.scss similarity index 100% rename from lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.scss rename to lib/process-services-cloud/src/lib/task/task-filters/components/base-task-filters-cloud.component.scss diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/base-task-filters-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/base-task-filters-cloud.component.ts new file mode 100644 index 0000000000..7b4fd646a1 --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/base-task-filters-cloud.component.ts @@ -0,0 +1,54 @@ +/*! + * @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 { EventEmitter, Input, Output, OnDestroy, Directive } from '@angular/core'; +import { Subject } from 'rxjs'; +import { FilterParamsModel } from '../models/filter-cloud.model'; + +@Directive() +// tslint:disable-next-line: directive-class-suffix +export abstract class BaseTaskFiltersCloudComponent implements OnDestroy { + /** Display filters available to the current user for the application with the specified name. */ + @Input() + appName: string = ''; + + /** + * Parameters to use for the task filter cloud. If there is no match then the default filter + * (the first one in the list) is selected. + */ + @Input() + filterParam: FilterParamsModel; + + /** Toggles display of the filter's icons. */ + @Input() + showIcons: boolean = false; + + /** Emitted when the list is loaded. */ + @Output() + success: EventEmitter = new EventEmitter(); + + /** Emitted when an error occurs during loading. */ + @Output() + error: EventEmitter = new EventEmitter(); + + protected onDestroy$ = new Subject(); + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } +} diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.html deleted file mode 100644 index e55abfdb1a..0000000000 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.html +++ /dev/null @@ -1,91 +0,0 @@ - - - - - {{taskFilter.name | translate}} - - {{ 'ADF_CLOUD_EDIT_TASK_FILTER.TITLE' | translate}} -
- - - -
-
-
- -
- -
-
-
- -
-
- - - - - {{ propertyOption.label }} - - - - - - - - {{taskFilterProperty.label | translate}} - - - -
-
-
{{'ADF_TASK_LIST.START_TASK.FORM.ERROR.DATE'|translate}}
- warning -
-
-
-
- - {{taskFilterProperty.label | translate}} - -
- - - -
- -
-
-
-
-
-
-
diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts deleted file mode 100644 index 30ba4310e4..0000000000 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.ts +++ /dev/null @@ -1,719 +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 { Component, OnChanges, Input, Output, EventEmitter, SimpleChanges, OnInit, OnDestroy } from '@angular/core'; -import { AbstractControl, FormGroup, FormBuilder } from '@angular/forms'; -import { DateAdapter } from '@angular/material/core'; -import { MatDialog } from '@angular/material/dialog'; -import { debounceTime, filter, takeUntil, finalize, switchMap } from 'rxjs/operators'; -import { Subject, Observable, of } from 'rxjs'; -import moment from 'moment-es6'; -import { Moment } from 'moment'; - -import { TaskFilterCloudModel, TaskFilterProperties, FilterOptions, TaskFilterAction } from './../models/filter-cloud.model'; -import { TaskFilterCloudService } from '../services/task-filter-cloud.service'; -import { TaskFilterDialogCloudComponent } from './task-filter-dialog-cloud.component'; -import { TranslationService, UserPreferencesService, UserPreferenceValues, IdentityUserModel, IdentityUserService } from '@alfresco/adf-core'; -import { AppsProcessCloudService } from '../../../app/services/apps-process-cloud.service'; -import { ApplicationInstanceModel } from '../../../app/models/application-instance.model'; -import { DateCloudFilterType, DateRangeFilter } from '../../../models/date-cloud-filter.model'; -import { ProcessDefinitionCloud } from '../../../models/process-definition-cloud.model'; -import { TaskCloudService } from '../../services/task-cloud.service'; - -@Component({ - selector: 'adf-cloud-edit-task-filter', - templateUrl: './edit-task-filter-cloud.component.html', - styleUrls: ['./edit-task-filter-cloud.component.scss'] -}) -export class EditTaskFilterCloudComponent implements OnInit, OnChanges, OnDestroy { - - public static ACTION_SAVE = 'save'; - public static ACTION_SAVE_AS = 'saveAs'; - public static ACTION_DELETE = 'delete'; - public static APP_RUNNING_STATUS: string = 'RUNNING'; - public static APPLICATION_NAME: string = 'appName'; - public static PROCESS_DEFINITION_NAME: string = 'processDefinitionName'; - public static LAST_MODIFIED: string = 'lastModified'; - public static SORT: string = 'sort'; - public static ORDER: string = 'order'; - public static DEFAULT_TASK_FILTER_PROPERTIES = ['status', 'assignee', 'sort', 'order']; - public static DEFAULT_SORT_PROPERTIES = ['id', 'name', 'createdDate', 'priority']; - public static DEFAULT_ACTIONS = ['save', 'saveAs', 'delete']; - public FORMAT_DATE: string = 'DD/MM/YYYY'; - - /** (required) Name of the app. */ - @Input() - appName: string = ''; - - /** user role. */ - @Input() - role: string = ''; - - /** (required) ID of the task filter. */ - @Input() - id: string; - - /** List of task filter properties to display. */ - @Input() - filterProperties: string[] = EditTaskFilterCloudComponent.DEFAULT_TASK_FILTER_PROPERTIES; - - /** List of sort properties to display. */ - @Input() - sortProperties: string[] = EditTaskFilterCloudComponent.DEFAULT_SORT_PROPERTIES; - - /** List of task filter actions. */ - @Input() - actions: string[] = EditTaskFilterCloudComponent.DEFAULT_ACTIONS; - - /** Toggles the filter actions. */ - @Input() - showFilterActions = true; - - /** Toggles the title. */ - @Input() - showTitle = true; - - /** Toggles display of task filter name */ - @Input() - showTaskFilterName = true; - - /** Emitted when a task filter property changes. */ - @Output() - filterChange: EventEmitter = new EventEmitter(); - - /** Emitted when a filter action occurs (i.e Save, Save As, Delete). */ - @Output() - action: EventEmitter = new EventEmitter(); - - taskFilter: TaskFilterCloudModel; - changedTaskFilter: TaskFilterCloudModel; - - status = [ - { label: 'ALL', value: '' }, - { label: 'CREATED', value: 'CREATED' }, - { label: 'ASSIGNED', value: 'ASSIGNED' }, - { label: 'SUSPENDED', value: 'SUSPENDED' }, - { label: 'CANCELLED', value: 'CANCELLED' }, - { label: 'COMPLETED', value: 'COMPLETED' } - ]; - - directions = [ - { label: 'ASC', value: 'ASC' }, - { label: 'DESC', value: 'DESC' } - ]; - actionDisabledForDefault = [ - EditTaskFilterCloudComponent.ACTION_SAVE, - EditTaskFilterCloudComponent.ACTION_DELETE - ]; - allProcessDefinitionNamesOption = { label: 'All', value: '' }; - - private applicationNames: any[] = []; - private processDefinitionNames: any[] = []; - private formHasBeenChanged = false; - editTaskFilterForm: FormGroup; - taskFilterProperties: TaskFilterProperties[] = []; - taskFilterActions: TaskFilterAction[] = []; - toggleFilterActions: boolean = false; - - private onDestroy$ = new Subject(); - isLoading: boolean = false; - - constructor( - private formBuilder: FormBuilder, - public dialog: MatDialog, - private translateService: TranslationService, - private taskFilterCloudService: TaskFilterCloudService, - private dateAdapter: DateAdapter, - private userPreferencesService: UserPreferencesService, - private appsProcessCloudService: AppsProcessCloudService, - private identityUserService: IdentityUserService, - private taskCloudService: TaskCloudService) { - } - - ngOnInit() { - this.userPreferencesService - .select(UserPreferenceValues.Locale) - .pipe(takeUntil(this.onDestroy$)) - .subscribe(locale => this.dateAdapter.setLocale(locale)); - } - - ngOnChanges(changes: SimpleChanges) { - const id = changes['id']; - if (id && id.currentValue !== id.previousValue) { - this.retrieveTaskFilterAndBuildForm(); - } - } - - ngOnDestroy() { - this.onDestroy$.next(true); - this.onDestroy$.complete(); - } - - buildForm(taskFilterProperties: TaskFilterProperties[]) { - this.formHasBeenChanged = false; - this.editTaskFilterForm = this.formBuilder.group(this.getFormControlsConfig(taskFilterProperties)); - this.onFilterChange(); - } - - getFormControlsConfig(taskFilterProperties: TaskFilterProperties[]): any { - const properties = taskFilterProperties.map((property: TaskFilterProperties) => { - if (!!property.attributes) { - return this.getAttributesControlConfig(property); - } else { - return { [property.key]: property.value }; - } - }); - return properties.reduce(((result, current) => Object.assign(result, current)), {}); - } - - private getAttributesControlConfig(property: TaskFilterProperties) { - return Object.values(property.attributes).reduce((result, key) => { - result[key] = property.value[key]; - return result; - }, {}); - } - - /** - * Check for edit task filter form changes - */ - onFilterChange() { - this.editTaskFilterForm.valueChanges - .pipe( - debounceTime(200), - filter(() => this.isFormValid()), - takeUntil(this.onDestroy$) - ) - .subscribe((formValues: TaskFilterCloudModel) => { - this.setLastModifiedToFilter(formValues); - this.changedTaskFilter = new TaskFilterCloudModel(Object.assign({}, this.taskFilter, formValues)); - this.formHasBeenChanged = !this.compareFilters(this.changedTaskFilter, this.taskFilter); - this.filterChange.emit(this.changedTaskFilter); - }); - } - - private setLastModifiedToFilter(formValues: TaskFilterCloudModel) { - if (formValues.lastModifiedTo && Date.parse(formValues.lastModifiedTo.toString())) { - const lastModifiedToFilterValue = moment(formValues.lastModifiedTo); - lastModifiedToFilterValue.set({ - hour: 23, - minute: 59, - second: 59 - }); - formValues.lastModifiedTo = lastModifiedToFilterValue.toDate(); - } - } - - /** - * Fetches task filter by application name and filter id and creates filter properties, build form - */ - retrieveTaskFilterAndBuildForm() { - this.isLoading = true; - this.taskFilterCloudService.getTaskFilterById(this.appName, this.id) - .pipe( - finalize(() => this.isLoading = false), - takeUntil(this.onDestroy$) - ) - .subscribe(response => { - this.taskFilter = new TaskFilterCloudModel(response); - this.taskFilterProperties = this.createAndFilterProperties(); - this.taskFilterActions = this.createAndFilterActions(); - this.buildForm(this.taskFilterProperties); - }); - } - - createAndFilterProperties() { - this.checkMandatoryFilterProperties(); - - if (this.checkForProperty(EditTaskFilterCloudComponent.APPLICATION_NAME)) { - this.applicationNames = []; - this.getRunningApplications(); - } - if (this.checkForProperty(EditTaskFilterCloudComponent.PROCESS_DEFINITION_NAME)) { - this.processDefinitionNames = []; - this.getProcessDefinitions(); - } - - const defaultProperties = this.createTaskFilterProperties(this.taskFilter); - let filteredProperties = defaultProperties.filter((filterProperty: TaskFilterProperties) => this.isValidProperty(this.filterProperties, filterProperty)); - - if (!this.hasSortProperty()) { - filteredProperties = this.removeOrderProperty(filteredProperties); - } - - if (this.hasLastModifiedProperty()) { - filteredProperties = [...filteredProperties, ...this.createLastModifiedProperty()]; - } - return filteredProperties; - } - - checkMandatoryFilterProperties() { - if (this.filterProperties === undefined || this.filterProperties.length === 0) { - this.filterProperties = EditTaskFilterCloudComponent.DEFAULT_TASK_FILTER_PROPERTIES; - } - } - - private isValidProperty(filterProperties: string[], filterProperty: any): boolean { - return filterProperties ? filterProperties.indexOf(filterProperty.key) >= 0 : true; - } - - checkForProperty(property: string): boolean { - return this.filterProperties ? this.filterProperties.indexOf(property) >= 0 : false; - } - - hasSortProperty(): boolean { - return this.filterProperties.indexOf(EditTaskFilterCloudComponent.SORT) >= 0; - } - - removeOrderProperty(filteredProperties: TaskFilterProperties[]): TaskFilterProperties[] { - if (filteredProperties && filteredProperties.length > 0) { - return filteredProperties.filter(property => property.key !== EditTaskFilterCloudComponent.ORDER); - } - return []; - } - - hasLastModifiedProperty(): boolean { - return this.filterProperties.indexOf(EditTaskFilterCloudComponent.LAST_MODIFIED) >= 0; - } - - get createSortProperties(): any { - this.checkMandatorySortProperties(); - const sortProperties = this.sortProperties.map((property: string) => { - return { label: property.charAt(0).toUpperCase() + property.slice(1), value: property }; - }); - return sortProperties; - } - - checkMandatorySortProperties(): void { - if (this.sortProperties === undefined || this.sortProperties.length === 0) { - this.sortProperties = EditTaskFilterCloudComponent.DEFAULT_SORT_PROPERTIES; - } - } - - createAndFilterActions(): TaskFilterAction[] { - this.checkMandatoryActions(); - return this.createFilterActions() - .filter(action => this.isValidAction(this.actions, action)); - } - - checkMandatoryActions(): void { - if (this.actions === undefined || this.actions.length === 0) { - this.actions = EditTaskFilterCloudComponent.DEFAULT_ACTIONS; - } - } - - private isValidAction(actions: string[], action: any): boolean { - return actions ? actions.indexOf(action.actionType) >= 0 : true; - } - - isFormValid(): boolean { - return this.editTaskFilterForm.valid; - } - - getPropertyController(property: TaskFilterProperties): AbstractControl { - return this.editTaskFilterForm.get(property.key); - } - - onDateChanged(newDateValue: any, dateProperty: TaskFilterProperties) { - if (newDateValue) { - const momentDate = moment(newDateValue, this.FORMAT_DATE, true); - - if (momentDate.isValid()) { - this.getPropertyController(dateProperty).setValue(momentDate.toDate()); - this.getPropertyController(dateProperty).setErrors(null); - } else { - this.getPropertyController(dateProperty).setErrors({invalid: true}); - } - } - } - - onDateTypeChange(dateType: DateCloudFilterType, property: TaskFilterProperties) { - this.editTaskFilterForm.get(property.attributes.dateType).setValue(dateType); - } - - onDateRangeFilterChanged(dateRange: DateRangeFilter, property: TaskFilterProperties) { - this.editTaskFilterForm.get(property.attributes?.from).setValue( - dateRange.startDate ? dateRange.startDate.toISOString() : null - ); - this.editTaskFilterForm.get(property.attributes?.to).setValue( - dateRange.endDate ? dateRange.endDate.toISOString() : null - ); - } - - onChangedUser(users: IdentityUserModel[], userProperty: TaskFilterProperties) { - this.getPropertyController(userProperty).setValue(users[0]?.username); - } - - hasError(property: TaskFilterProperties): boolean { - return this.getPropertyController(property).errors && this.getPropertyController(property).errors.invalid; - } - - /** - * Return true if both filters are same - * @param editedQuery, @param currentQuery - */ - compareFilters(editedQuery: TaskFilterCloudModel, currentQuery: TaskFilterCloudModel): boolean { - return JSON.stringify(editedQuery).toLowerCase() === JSON.stringify(currentQuery).toLowerCase(); - } - - getRunningApplications() { - this.appsProcessCloudService - .getDeployedApplicationsByStatus(EditTaskFilterCloudComponent.APP_RUNNING_STATUS, this.role) - .pipe(takeUntil(this.onDestroy$)) - .subscribe((applications: ApplicationInstanceModel[]) => { - if (applications && applications.length > 0) { - applications.map((application) => { - this.applicationNames.push({ label: application.name, value: application.name }); - }); - } - }); - } - - getProcessDefinitions() { - this.taskCloudService.getProcessDefinitions(this.appName) - .pipe(takeUntil(this.onDestroy$)) - .subscribe((processDefinitions: ProcessDefinitionCloud[]) => { - if (processDefinitions && processDefinitions.length > 0) { - this.processDefinitionNames.push(this.allProcessDefinitionNamesOption); - processDefinitions.map((processDefinition) => { - this.processDefinitionNames.push({ label: processDefinition.name, value: processDefinition.name }); - }); - } - }); - } - - executeFilterActions(action: TaskFilterAction): void { - if (action.actionType === EditTaskFilterCloudComponent.ACTION_SAVE) { - this.save(action); - } else if (action.actionType === EditTaskFilterCloudComponent.ACTION_SAVE_AS) { - this.saveAs(action); - } else if (action.actionType === EditTaskFilterCloudComponent.ACTION_DELETE) { - this.delete(action); - } - } - - save(saveAction: TaskFilterAction): void { - this.taskFilterCloudService - .updateFilter(this.changedTaskFilter) - .pipe(takeUntil(this.onDestroy$)) - .subscribe(() => { - saveAction.filter = this.changedTaskFilter; - this.action.emit(saveAction); - this.formHasBeenChanged = this.compareFilters(this.changedTaskFilter, this.taskFilter); - }); - } - - delete(deleteAction: TaskFilterAction): void { - this.taskFilterCloudService - .deleteFilter(this.taskFilter) - .pipe( - filter((filters) => { - deleteAction.filter = this.taskFilter; - this.action.emit(deleteAction); - return filters.length === 0; - }), - switchMap(() => this.restoreDefaultTaskFilters()), - takeUntil(this.onDestroy$)) - .subscribe(() => {}); - } - - saveAs(saveAsAction: TaskFilterAction): void { - const dialogRef = this.dialog.open(TaskFilterDialogCloudComponent, { - data: { - name: this.translateService.instant(this.taskFilter.name) - }, - height: 'auto', - minWidth: '30%' - }); - dialogRef.afterClosed().subscribe((result) => { - if (result && result.action === TaskFilterDialogCloudComponent.ACTION_SAVE) { - const filterId = Math.random().toString(36).substr(2, 9); - const filterKey = this.getSanitizeFilterName(result.name); - const newFilter = { - name: result.name, - icon: result.icon, - id: filterId, - key: 'custom-' + filterKey - }; - const resultFilter: TaskFilterCloudModel = Object.assign({}, this.changedTaskFilter, newFilter); - this.taskFilterCloudService.addFilter(resultFilter) - .pipe(takeUntil(this.onDestroy$)).subscribe(() => { - saveAsAction.filter = resultFilter; - this.action.emit(saveAsAction); - }); - } - }); - } - - /** - * Return filter name - * @param filterName - */ - getSanitizeFilterName(filterName: string): string { - const nameWithHyphen = this.replaceSpaceWithHyphen(filterName.trim()); - return nameWithHyphen.toLowerCase(); - } - - /** - * Return name with hyphen - * @param name - */ - replaceSpaceWithHyphen(name: string): string { - const regExt = new RegExp(' ', 'g'); - return name.replace(regExt, '-'); - } - - restoreDefaultTaskFilters(): Observable { - return this.taskFilterCloudService.getTaskListFilters(this.appName); - } - - showActions(): boolean { - return this.showFilterActions; - } - - onExpand(): void { - this.toggleFilterActions = true; - } - - onClose(): void { - this.toggleFilterActions = false; - } - - isDateType(property: TaskFilterProperties): boolean { - return property.type === 'date'; - } - - isDateRangeType(property: TaskFilterProperties): boolean { - return property.type === 'date-range'; - } - - isSelectType(property: TaskFilterProperties): boolean { - return property.type === 'select'; - } - - isTextType(property: TaskFilterProperties): boolean { - return property.type === 'text'; - } - - isCheckBoxType(property: TaskFilterProperties): boolean { - return property.type === 'checkbox'; - } - - isUserSelectType(property: TaskFilterProperties): boolean { - return property.type === 'people'; - } - - isDisabledAction(action: TaskFilterAction): boolean { - return this.isDisabledForDefaultFilters(action) ? true : this.hasFormChanged(action); - } - - isDisabledForDefaultFilters(action: TaskFilterAction): boolean { - return ( - this.taskFilterCloudService.isDefaultFilter(this.taskFilter.name) && - this.actionDisabledForDefault.includes(action.actionType) - ); - } - - hasFormChanged(action: TaskFilterAction): boolean { - if (action.actionType === EditTaskFilterCloudComponent.ACTION_SAVE) { - return !this.formHasBeenChanged; - } - if (action.actionType === EditTaskFilterCloudComponent.ACTION_SAVE_AS) { - return !this.formHasBeenChanged; - } - if (action.actionType === EditTaskFilterCloudComponent.ACTION_DELETE) { - return false; - } - - return false; - } - - getUserByUsername(username: string): Observable { - if (username) { - return this.identityUserService.findUserByUsername(username); - } - return of([]); - } - - createFilterActions(): TaskFilterAction[] { - return [ - new TaskFilterAction({ - actionType: EditTaskFilterCloudComponent.ACTION_SAVE, - icon: 'save', - tooltip: 'ADF_CLOUD_EDIT_TASK_FILTER.TOOL_TIP.SAVE' - }), - new TaskFilterAction({ - actionType: EditTaskFilterCloudComponent.ACTION_SAVE_AS, - icon: 'unarchive', - tooltip: 'ADF_CLOUD_EDIT_TASK_FILTER.TOOL_TIP.SAVE_AS' - }), - new TaskFilterAction({ - actionType: EditTaskFilterCloudComponent.ACTION_DELETE, - icon: 'delete', - tooltip: 'ADF_CLOUD_EDIT_TASK_FILTER.TOOL_TIP.DELETE' - }) - ]; - } - - createLastModifiedProperty(): TaskFilterProperties[] { - return [ - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.LAST_MODIFIED_FROM', - type: 'date', - key: 'lastModifiedFrom', - value: '' - }), - - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.LAST_MODIFIED_TO', - type: 'date', - key: 'lastModifiedTo', - value: '' - }) - ]; - } - - createTaskFilterProperties(currentTaskFilter: TaskFilterCloudModel): TaskFilterProperties[] { - return [ - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.APP_NAME', - type: 'select', - key: 'appName', - value: currentTaskFilter.appName || '', - options: this.applicationNames - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.TASK_ID', - type: 'text', - key: 'taskId', - value: '' - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.STATUS', - type: 'select', - key: 'status', - value: currentTaskFilter.status || this.status[0].value, - options: this.status - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.ASSIGNMENT', - type: 'text', - key: 'assignee', - value: currentTaskFilter.assignee || '' - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PROCESS_DEF_NAME', - type: 'select', - key: 'processDefinitionName', - value: currentTaskFilter.processDefinitionName || '', - options: this.processDefinitionNames - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PROCESS_INSTANCE_ID', - type: 'text', - key: 'processInstanceId', - value: currentTaskFilter.processInstanceId || '' - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PROCESS_DEF_ID', - type: 'text', - key: 'processDefinitionId', - value: currentTaskFilter.processDefinitionId || '' - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.TASK_NAME', - type: 'text', - key: 'taskName', - value: currentTaskFilter.taskName || '' - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PARENT_TASK_ID', - type: 'text', - key: 'parentTaskId', - value: currentTaskFilter.parentTaskId || '' - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PRIORITY', - type: 'text', - key: 'priority', - value: currentTaskFilter.priority || '' - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.OWNER', - type: 'text', - key: 'owner', - value: currentTaskFilter.owner || '' - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.CREATED_DATE', - type: 'date', - key: 'createdDate', - value: '' - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.DUE_DATE', - type: 'date', - key: 'dueDate', - value: '' - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.SORT', - type: 'select', - key: 'sort', - value: currentTaskFilter.sort || this.createSortProperties[0].value, - options: this.createSortProperties - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.DIRECTION', - type: 'select', - key: 'order', - value: currentTaskFilter.order || this.directions[0].value, - options: this.directions - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.STAND_ALONE', - type: 'checkbox', - key: 'standalone', - value: currentTaskFilter.standalone || false - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.DUE_DATE', - type: 'date-range', - key: 'dueDateRange', - attributes: { dateType: 'dueDateType', from: '_dueDateFrom', to: '_dueDateTo'}, - value: { - dueDateType: currentTaskFilter.dueDateType || null, - _dueDateFrom: currentTaskFilter.dueDateFrom || null, - _dueDateTo: currentTaskFilter.dueDateTo || null - }, - dateFilterOptions: [ - DateCloudFilterType.NO_DATE, - DateCloudFilterType.TODAY, - DateCloudFilterType.TOMORROW, - DateCloudFilterType.NEXT_7_DAYS, - DateCloudFilterType.RANGE - ] - }), - new TaskFilterProperties({ - label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.COMPLETED_BY', - type: 'people', - key: 'completedBy', - value: this.getUserByUsername(currentTaskFilter.completedBy), - selectionMode: 'single' - }) - ]; - } -} diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/base-edit-task-filter-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/base-edit-task-filter-cloud.component.html new file mode 100644 index 0000000000..48c6bff6e0 --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/base-edit-task-filter-cloud.component.html @@ -0,0 +1,109 @@ + + + + + {{taskFilter.name | translate}} + + {{ 'ADF_CLOUD_EDIT_TASK_FILTER.TITLE' | translate}} +
+ + + +
+
+
+ +
+ +
+
+
+ +
+
+ + + + + {{ propertyOption.label }} + + + + + + + + {{taskFilterProperty.label | translate}} + + + + + +
+
+
{{'ADF_TASK_LIST.START_TASK.FORM.ERROR.DATE'|translate}} +
+ warning +
+
+
+
+ + {{taskFilterProperty.label | translate}} + +
+ + + +
+
+
+
+
+
diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.scss b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/base-edit-task-filter-cloud.component.scss similarity index 100% rename from lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.scss rename to lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/base-edit-task-filter-cloud.component.scss diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/base-edit-task-filter-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/base-edit-task-filter-cloud.component.ts new file mode 100644 index 0000000000..9ce699e491 --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/base-edit-task-filter-cloud.component.ts @@ -0,0 +1,410 @@ +/*! + * @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 { OnChanges, SimpleChanges, OnInit, OnDestroy, Directive, Input, Output, EventEmitter } from '@angular/core'; +import { FilterOptions, TaskFilterAction, TaskFilterProperties } from '../../models/filter-cloud.model'; +import { TaskCloudService } from './../../../services/task-cloud.service'; +import { AppsProcessCloudService } from './../../../../app/services/apps-process-cloud.service'; +import { ApplicationInstanceModel } from './../../../../app/models/application-instance.model'; +import { ProcessDefinitionCloud } from './../../../../models/process-definition-cloud.model'; +import { DateCloudFilterType, DateRangeFilter } from '../../../../models/date-cloud-filter.model'; +import moment, { Moment } from 'moment'; +import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms'; +import { debounceTime, filter, takeUntil } from 'rxjs/operators'; +import { Subject } from 'rxjs'; +import { DateAdapter } from '@angular/material/core'; +import { UserPreferencesService, UserPreferenceValues } from '@alfresco/adf-core'; + +@Directive() +// tslint:disable-next-line: directive-class-suffix +export abstract class BaseEditTaskFilterCloudComponent implements OnInit, OnChanges, OnDestroy { + + public static ACTION_SAVE = 'save'; + public static ACTION_SAVE_AS = 'saveAs'; + public static ACTION_DELETE = 'delete'; + public static APP_RUNNING_STATUS: string = 'RUNNING'; + public static APPLICATION_NAME: string = 'appName'; + public static PROCESS_DEFINITION_NAME: string = 'processDefinitionName'; + public static LAST_MODIFIED: string = 'lastModified'; + public static SORT: string = 'sort'; + public static ORDER: string = 'order'; + public static DEFAULT_ACTIONS = ['save', 'saveAs', 'delete']; + public static FORMAT_DATE: string = 'DD/MM/YYYY'; + public static DIRECTIONS = [ + { label: 'ASC', value: 'ASC' }, + { label: 'DESC', value: 'DESC' } + ]; + public static ACTIONS_DISABLED_BY_DEFAULT = [ + BaseEditTaskFilterCloudComponent.ACTION_SAVE, + BaseEditTaskFilterCloudComponent.ACTION_DELETE + ]; + + /** (required) Name of the app. */ + @Input() + appName: string = ''; + + /** (required) ID of the task filter. */ + @Input() + id: string; + + /** Toggles the title. */ + @Input() + showTitle = true; + + /** Toggles display of task filter name */ + @Input() + showTaskFilterName = true; + + /** List of task filter properties to display. */ + @Input() + filterProperties: string[] = []; + + /** user role. */ + @Input() + role: string = ''; + + /** Toggles the filter actions. */ + @Input() + showFilterActions = true; + + /** List of task filter actions. */ + @Input() + actions: string[] = BaseEditTaskFilterCloudComponent.DEFAULT_ACTIONS; + + /** List of sort properties to display. */ + @Input() + sortProperties: string[] = []; + + /** Emitted when a filter action occurs (i.e Save, Save As, Delete). */ + @Output() + action: EventEmitter = new EventEmitter(); + + protected applicationNames: any[] = []; + protected processDefinitionNames: any[] = []; + protected formHasBeenChanged = false; + editTaskFilterForm: FormGroup; + taskFilterProperties: TaskFilterProperties[] = []; + taskFilterActions: TaskFilterAction[] = []; + toggleFilterActions: boolean = false; + allProcessDefinitionNamesOption = { label: 'All', value: '' }; + + protected onDestroy$ = new Subject(); + isLoading: boolean = false; + + constructor( + protected formBuilder: FormBuilder, + protected dateAdapter: DateAdapter, + protected userPreferencesService: UserPreferencesService, + protected appsProcessCloudService: AppsProcessCloudService, + protected taskCloudService: TaskCloudService) { + } + + ngOnInit() { + this.userPreferencesService + .select(UserPreferenceValues.Locale) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(locale => this.dateAdapter.setLocale(locale)); + } + + ngOnChanges(changes: SimpleChanges) { + const id = changes['id']; + if (id && id.currentValue !== id.previousValue) { + this.retrieveTaskFilterAndBuildForm(); + } + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + + createFilterActions(): TaskFilterAction[] { + return [ + new TaskFilterAction({ + actionType: BaseEditTaskFilterCloudComponent.ACTION_SAVE, + icon: 'save', + tooltip: 'ADF_CLOUD_EDIT_TASK_FILTER.TOOL_TIP.SAVE' + }), + new TaskFilterAction({ + actionType: BaseEditTaskFilterCloudComponent.ACTION_SAVE_AS, + icon: 'unarchive', + tooltip: 'ADF_CLOUD_EDIT_TASK_FILTER.TOOL_TIP.SAVE_AS' + }), + new TaskFilterAction({ + actionType: BaseEditTaskFilterCloudComponent.ACTION_DELETE, + icon: 'delete', + tooltip: 'ADF_CLOUD_EDIT_TASK_FILTER.TOOL_TIP.DELETE' + }) + ]; + } + + hasFormChanged(action: TaskFilterAction): boolean { + if (action.actionType === BaseEditTaskFilterCloudComponent.ACTION_SAVE) { + return !this.formHasBeenChanged; + } + if (action.actionType === BaseEditTaskFilterCloudComponent.ACTION_SAVE_AS) { + return !this.formHasBeenChanged; + } + if (action.actionType === BaseEditTaskFilterCloudComponent.ACTION_DELETE) { + return false; + } + + return false; + } + + onExpand(): void { + this.toggleFilterActions = true; + } + + onClose(): void { + this.toggleFilterActions = false; + } + + isDateType(property: TaskFilterProperties): boolean { + return property.type === 'date'; + } + + isDateRangeType(property: TaskFilterProperties): boolean { + return property.type === 'date-range'; + } + + isSelectType(property: TaskFilterProperties): boolean { + return property.type === 'select'; + } + + isTextType(property: TaskFilterProperties): boolean { + return property.type === 'text'; + } + + isCheckBoxType(property: TaskFilterProperties): boolean { + return property.type === 'checkbox'; + } + + isDisabledAction(action: TaskFilterAction): boolean { + return this.isDisabledForDefaultFilters(action) ? true : this.hasFormChanged(action); + } + + /** + * Return filter name + * @param filterName + */ + getSanitizeFilterName(filterName: string): string { + const nameWithHyphen = this.replaceSpaceWithHyphen(filterName.trim()); + return nameWithHyphen.toLowerCase(); + } + + /** + * Return name with hyphen + * @param name + */ + replaceSpaceWithHyphen(name: string): string { + const regExt = new RegExp(' ', 'g'); + return name.replace(regExt, '-'); + } + + executeFilterActions(action: TaskFilterAction): void { + if (action.actionType === BaseEditTaskFilterCloudComponent.ACTION_SAVE) { + this.save(action); + } else if (action.actionType === BaseEditTaskFilterCloudComponent.ACTION_SAVE_AS) { + this.saveAs(action); + } else if (action.actionType === BaseEditTaskFilterCloudComponent.ACTION_DELETE) { + this.delete(action); + } + } + + getRunningApplications() { + this.appsProcessCloudService + .getDeployedApplicationsByStatus(BaseEditTaskFilterCloudComponent.APP_RUNNING_STATUS, this.role) + .pipe(takeUntil(this.onDestroy$)) + .subscribe((applications: ApplicationInstanceModel[]) => { + if (applications && applications.length > 0) { + applications.map((application) => { + this.applicationNames.push({ label: application.name, value: application.name }); + }); + } + }); + } + + getProcessDefinitions() { + this.taskCloudService.getProcessDefinitions(this.appName) + .pipe(takeUntil(this.onDestroy$)) + .subscribe((processDefinitions: ProcessDefinitionCloud[]) => { + if (processDefinitions && processDefinitions.length > 0) { + this.processDefinitionNames.push(this.allProcessDefinitionNamesOption); + processDefinitions.map((processDefinition) => { + this.processDefinitionNames.push({ label: processDefinition.name, value: processDefinition.name }); + }); + } + }); + } + + checkMandatoryActions(): void { + if (this.actions === undefined || this.actions.length === 0) { + this.actions = BaseEditTaskFilterCloudComponent.DEFAULT_ACTIONS; + } + } + + private isValidAction(actions: string[], action: any): boolean { + return actions ? actions.indexOf(action.actionType) >= 0 : true; + } + + isFormValid(): boolean { + return this.editTaskFilterForm.valid; + } + + getPropertyController(property: TaskFilterProperties): AbstractControl { + return this.editTaskFilterForm.get(property.key); + } + + onDateChanged(newDateValue: any, dateProperty: TaskFilterProperties) { + if (newDateValue) { + const momentDate = moment(newDateValue, BaseEditTaskFilterCloudComponent.FORMAT_DATE, true); + + if (momentDate.isValid()) { + this.getPropertyController(dateProperty).setValue(momentDate.toDate()); + this.getPropertyController(dateProperty).setErrors(null); + } else { + this.getPropertyController(dateProperty).setErrors({ invalid: true }); + } + } + } + + onDateRangeFilterChanged(dateRange: DateRangeFilter, property: TaskFilterProperties) { + this.editTaskFilterForm.get(property.attributes?.from).setValue( + dateRange.startDate ? dateRange.startDate.toISOString() : null + ); + this.editTaskFilterForm.get(property.attributes?.to).setValue( + dateRange.endDate ? dateRange.endDate.toISOString() : null + ); + } + + hasError(property: TaskFilterProperties): boolean { + return this.getPropertyController(property).errors && this.getPropertyController(property).errors.invalid; + } + + hasLastModifiedProperty(): boolean { + return this.filterProperties.indexOf(BaseEditTaskFilterCloudComponent.LAST_MODIFIED) >= 0; + } + + get createSortProperties(): FilterOptions[] { + this.checkMandatorySortProperties(); + const sortProperties = this.sortProperties.map((property: string) => { + return { label: property.charAt(0).toUpperCase() + property.slice(1), value: property }; + }); + return sortProperties; + } + + createAndFilterActions(): TaskFilterAction[] { + this.checkMandatoryActions(); + return this.createFilterActions().filter(action => this.isValidAction(this.actions, action)); + } + + isValidProperty(filterProperties: string[], filterProperty: any): boolean { + return filterProperties ? filterProperties.indexOf(filterProperty.key) >= 0 : true; + } + + checkForProperty(property: string): boolean { + return this.filterProperties ? this.filterProperties.indexOf(property) >= 0 : false; + } + + hasSortProperty(): boolean { + return this.filterProperties.indexOf(BaseEditTaskFilterCloudComponent.SORT) >= 0; + } + + removeOrderProperty(filteredProperties: TaskFilterProperties[]): TaskFilterProperties[] { + if (filteredProperties && filteredProperties.length > 0) { + return filteredProperties.filter(property => property.key !== BaseEditTaskFilterCloudComponent.ORDER); + } + return []; + } + + createAndFilterProperties() { + this.checkMandatoryFilterProperties(); + + if (this.checkForProperty(BaseEditTaskFilterCloudComponent.APPLICATION_NAME)) { + this.applicationNames = []; + this.getRunningApplications(); + } + if (this.checkForProperty(BaseEditTaskFilterCloudComponent.PROCESS_DEFINITION_NAME)) { + this.processDefinitionNames = []; + this.getProcessDefinitions(); + } + + const defaultProperties = this.createTaskFilterProperties(); + let filteredProperties = defaultProperties.filter((filterProperty: TaskFilterProperties) => this.isValidProperty(this.filterProperties, filterProperty)); + + if (!this.hasSortProperty()) { + filteredProperties = this.removeOrderProperty(filteredProperties); + } + + return filteredProperties; + } + + /** + * Check for edit task filter form changes + */ + onFilterChange() { + this.editTaskFilterForm.valueChanges + .pipe( + debounceTime(200), + filter(() => this.isFormValid()), + takeUntil(this.onDestroy$) + ) + .subscribe((formValues) => { + this.assignNewFilter(formValues); + }); + } + + getFormControlsConfig(taskFilterProperties: TaskFilterProperties[]): any { + const properties = taskFilterProperties.map((property: TaskFilterProperties) => { + if (!!property.attributes) { + return this.getAttributesControlConfig(property); + } else { + return { [property.key]: property.value }; + } + }); + return properties.reduce(((result, current) => Object.assign(result, current)), {}); + } + + private getAttributesControlConfig(property: TaskFilterProperties) { + return Object.values(property.attributes).reduce((result, key) => { + result[key] = property.value[key]; + return result; + }, {}); + } + + buildForm(taskFilterProperties: TaskFilterProperties[]) { + this.formHasBeenChanged = false; + this.editTaskFilterForm = this.formBuilder.group(this.getFormControlsConfig(taskFilterProperties)); + this.onFilterChange(); + } + + onDateTypeChange(dateType: DateCloudFilterType, property: TaskFilterProperties) { + this.editTaskFilterForm.get(property.attributes.dateType).setValue(dateType); + } + + abstract save(action: TaskFilterAction): void; + abstract saveAs(action: TaskFilterAction): void; + abstract delete(action: TaskFilterAction): void; + abstract checkMandatorySortProperties(): void; + abstract checkMandatoryFilterProperties(): void; + abstract isDisabledForDefaultFilters(action: TaskFilterAction): boolean; + abstract createTaskFilterProperties(): TaskFilterProperties[]; + abstract retrieveTaskFilterAndBuildForm(): void; + abstract assignNewFilter(formValues): void; + +} diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/edit-service-task-filter-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/edit-service-task-filter-cloud.component.spec.ts new file mode 100644 index 0000000000..1e4d79ffa8 --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/edit-service-task-filter-cloud.component.spec.ts @@ -0,0 +1,706 @@ +/*! + * @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 { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { SimpleChange } from '@angular/core'; +import { By } from '@angular/platform-browser'; +import { setupTestBed } from '@alfresco/adf-core'; +import { MatDialog } from '@angular/material/dialog'; +import { of } from 'rxjs'; +import { debounceTime } from 'rxjs/operators'; +import { TASK_FILTERS_SERVICE_TOKEN } from '../../../../services/cloud-token.service'; +import { LocalPreferenceCloudService } from '../../../../services/local-preference-cloud.service'; +import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module'; +import { AppsProcessCloudService } from '../../../../app/services/apps-process-cloud.service'; +import { fakeApplicationInstance } from '../../../../app/mock/app-model.mock'; +import { TaskFiltersCloudModule } from '../../task-filters-cloud.module'; +import { ServiceTaskFilterCloudService } from '../../services/service-task-filter-cloud.service'; +import { TaskCloudService } from '../../../services/task-cloud.service'; +import { fakeServiceFilter } from '../../mock/task-filters-cloud.mock'; +import { TranslateModule } from '@ngx-translate/core'; +import { EditServiceTaskFilterCloudComponent } from './edit-service-task-filter-cloud.component'; + +describe('EditServiceTaskFilterCloudComponent', () => { + let component: EditServiceTaskFilterCloudComponent; + let service: ServiceTaskFilterCloudService; + let appsService: AppsProcessCloudService; + let fixture: ComponentFixture; + let dialog: MatDialog; + let getTaskFilterSpy: jasmine.Spy; + let getRunningApplicationsSpy: jasmine.Spy; + let taskService: TaskCloudService; + + setupTestBed({ + imports: [ + TranslateModule.forRoot(), + ProcessServiceCloudTestingModule, + TaskFiltersCloudModule + ], + providers: [ + MatDialog, + { provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService } + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(EditServiceTaskFilterCloudComponent); + component = fixture.componentInstance; + service = TestBed.inject(ServiceTaskFilterCloudService); + appsService = TestBed.inject(AppsProcessCloudService); + taskService = TestBed.inject(TaskCloudService); + dialog = TestBed.inject(MatDialog); + spyOn(dialog, 'open').and.returnValue({ + afterClosed: of({ + action: EditServiceTaskFilterCloudComponent.ACTION_SAVE, + icon: 'icon', + name: 'fake-name' + }) + }); + getTaskFilterSpy = spyOn(service, 'getTaskFilterById').and.returnValue(of(fakeServiceFilter)); + getRunningApplicationsSpy = spyOn(appsService, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance)); + fixture.detectChanges(); + }); + + afterEach(() => fixture.destroy()); + + it('should fetch task filter by taskId', () => { + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(getTaskFilterSpy).toHaveBeenCalled(); + expect(component.taskFilter.name).toEqual('FakeInvolvedTasks'); + expect(component.taskFilter.icon).toEqual('adjust'); + expect(component.taskFilter.status).toEqual('CREATED'); + expect(component.taskFilter.order).toEqual('ASC'); + expect(component.taskFilter.sort).toEqual('id'); + }); + }); + + it('should fetch process definitions when processDefinitionName filter property is set', async(() => { + const processSpy = spyOn(taskService, 'getProcessDefinitions').and.returnValue(of([{ id: 'fake-id', name: 'fake-name' }])); + fixture.detectChanges(); + component.filterProperties = ['processDefinitionName']; + fixture.detectChanges(); + const taskFilterIdChange = new SimpleChange(null, 'mock-process-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + const controller = component.editTaskFilterForm.get('processDefinitionName'); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(processSpy).toHaveBeenCalled(); + expect(controller).toBeDefined(); + }); + })); + + it('should display filter name as title', async(() => { + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + const title = fixture.debugElement.nativeElement.querySelector('#adf-edit-task-filter-title-id'); + const subTitle = fixture.debugElement.nativeElement.querySelector('#adf-edit-task-filter-sub-title-id'); + expect(title.innerText).toEqual('FakeInvolvedTasks'); + expect(subTitle.innerText.trim()).toEqual('ADF_CLOUD_EDIT_TASK_FILTER.TITLE'); + })); + + it('should not display filter name if showFilterName is false', async(() => { + const taskFilterIdChange = new SimpleChange(null, 'mock-task-filter-id', true); + component.showTaskFilterName = false; + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + const title = fixture.debugElement.nativeElement.querySelector('#adf-edit-task-filter-title-id'); + + fixture.whenStable().then(() => { + expect(title).toBeNull(); + }); + })); + + it('should not display mat-spinner if isloading set to false', async(() => { + const taskFilterIdChange = new SimpleChange(null, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + const title = fixture.debugElement.nativeElement.querySelector('#adf-edit-task-filter-title-id'); + const subTitle = fixture.debugElement.nativeElement.querySelector('#adf-edit-task-filter-sub-title-id'); + const matSpinnerElement = fixture.debugElement.nativeElement.querySelector('.adf-cloud-edit-task-filter-loading-margin'); + + fixture.whenStable().then(() => { + expect(matSpinnerElement).toBeNull(); + expect(title.innerText).toEqual('FakeInvolvedTasks'); + expect(subTitle.innerText.trim()).toEqual('ADF_CLOUD_EDIT_TASK_FILTER.TITLE'); + }); + })); + + it('should display mat-spinner if isloading set to true', async(() => { + component.isLoading = true; + const taskFilterIdChange = new SimpleChange(null, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + + const matSpinnerElement = fixture.debugElement.nativeElement.querySelector('.adf-cloud-edit-task-filter-loading-margin'); + + fixture.whenStable().then(() => { + expect(matSpinnerElement).toBeDefined(); + }); + })); + + describe('EditServiceTaskFilter form', () => { + + beforeEach(() => { + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + }); + + it('should defined editTaskFilter form ', () => { + expect(component.editTaskFilterForm).toBeDefined(); + }); + + it('should create editTaskFilter form with default user task properties', async(() => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + const appNameController = component.editTaskFilterForm.get('appName'); + const statusController = component.editTaskFilterForm.get('status'); + const sortController = component.editTaskFilterForm.get('sort'); + const orderController = component.editTaskFilterForm.get('order'); + const activityNameController = component.editTaskFilterForm.get('activityName'); + expect(component.editTaskFilterForm).toBeDefined(); + expect(appNameController.value).toBe('mock-app-name'); + expect(statusController.value).toBe('COMPLETED'); + expect(sortController.value).toBe('id'); + expect(orderController.value).toBe('ASC'); + expect(activityNameController.value).toBe('fake-activity'); + }); + })); + + describe('Save & Delete buttons', () => { + it('should disable save and delete button for default task filters', async(() => { + getTaskFilterSpy.and.returnValue(of({ + name: 'ADF_CLOUD_SERVICE_TASK_FILTERS.ALL_SERVICE_TASKS', + id: 'filter-id', + key: 'all-fake-task', + icon: 'adjust', + sort: 'startDate', + status: 'ALL', + order: 'DESC' + })); + + const taskFilterIdChange = new SimpleChange(null, 'filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + + component.toggleFilterActions = true; + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-save"]'); + expect(saveButton.disabled).toBe(true); + const deleteButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-delete"]'); + expect(deleteButton.disabled).toBe(true); + }); + })); + + it('should enable delete button for custom task filters', async(() => { + const taskFilterIdChange = new SimpleChange(null, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + + component.toggleFilterActions = true; + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-save"]'); + expect(saveButton.disabled).toBe(true); + const deleteButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-delete"]'); + expect(deleteButton.disabled).toBe(false); + }); + })); + + it('should enable save button if the filter is changed for custom task filters', (done) => { + const taskFilterIdChange = new SimpleChange(null, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + + component.toggleFilterActions = true; + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + + component.editTaskFilterForm.valueChanges + .pipe(debounceTime(300)) + .subscribe(() => { + const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-save"]'); + fixture.detectChanges(); + expect(saveButton.disabled).toBe(false); + done(); + }); + + const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger'); + stateElement.click(); + fixture.detectChanges(); + const sortOptions = fixture.debugElement.queryAll(By.css('.mat-option-text')); + sortOptions[3].nativeElement.click(); + fixture.detectChanges(); + }); + + it('should disable save button if the filter is not changed for custom filter', async(() => { + component.toggleFilterActions = true; + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-save"]'); + expect(saveButton.disabled).toBe(true); + }); + })); + }); + + describe('SaveAs button', () => { + it('should disable saveAs button if the process filter is not changed for default filter', async(() => { + getTaskFilterSpy.and.returnValue(of({ + name: 'ADF_CLOUD_TASK_FILTERS.MY_TASKS', + id: 'filter-id', + key: 'all-fake-task', + icon: 'adjust', + sort: 'startDate', + status: 'ALL', + order: 'DESC' + })); + + const taskFilterIdChange = new SimpleChange(null, 'filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + + component.toggleFilterActions = true; + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]'); + expect(saveButton.disabled).toEqual(true); + }); + })); + + it('should disable saveAs button if the process filter is not changed for custom filter', async(() => { + component.toggleFilterActions = true; + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]'); + expect(saveButton.disabled).toEqual(true); + }); + })); + + it('should enable saveAs button if the filter values are changed for default filter', (done) => { + getTaskFilterSpy.and.returnValue(of({ + name: 'ADF_CLOUD_TASK_FILTERS.MY_TASKS', + id: 'filter-id', + key: 'all-fake-task', + icon: 'adjust', + sort: 'startDate', + status: 'ALL', + order: 'DESC' + })); + + const taskFilterIdChange = new SimpleChange(null, 'filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + + component.toggleFilterActions = true; + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + + component.editTaskFilterForm.valueChanges + .pipe(debounceTime(300)) + .subscribe(() => { + const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]'); + fixture.detectChanges(); + expect(saveButton.disabled).toEqual(false); + done(); + }); + + const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger'); + stateElement.click(); + fixture.detectChanges(); + + const sortOptions = fixture.debugElement.queryAll(By.css('.mat-option-text')); + sortOptions[3].nativeElement.click(); + fixture.detectChanges(); + }); + + it('should enable saveAs button if the filter values are changed for custom filter', (done) => { + component.toggleFilterActions = true; + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + + component.editTaskFilterForm.valueChanges + .pipe(debounceTime(300)) + .subscribe(() => { + const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]'); + fixture.detectChanges(); + expect(saveButton.disabled).toEqual(false); + done(); + }); + + const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger'); + stateElement.click(); + fixture.detectChanges(); + + const sortOptions = fixture.debugElement.queryAll(By.css('.mat-option-text')); + sortOptions[3].nativeElement.click(); + fixture.detectChanges(); + }); + }); + + it('should display current task filter details', async(() => { + fixture.detectChanges(); + fixture.whenStable().then(() => { + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-status"]'); + const assigneeElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-assignee"]'); + const sortElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"]'); + const orderElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-order"]'); + expect(assigneeElement).toBeDefined(); + expect(stateElement.textContent.trim()).toBe('COMPLETED'); + expect(sortElement.textContent.trim()).toBe('Id'); + expect(orderElement.textContent.trim()).toBe('ASC'); + }); + })); + + it('should display all the statuses that are defined in the task filter', async(() => { + + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + + const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-status"]'); + stateElement.click(); + fixture.detectChanges(); + + const statusOptions = fixture.debugElement.queryAll(By.css('[data-automation-id="adf-cloud-edit-task-property-options-status"]')); + + expect(statusOptions[0].nativeElement.textContent.trim()).toBe('ALL'); + expect(statusOptions[1].nativeElement.textContent.trim()).toBe('STARTED'); + expect(statusOptions[2].nativeElement.textContent.trim()).toBe('COMPLETED'); + expect(statusOptions[3].nativeElement.textContent.trim()).toBe('CANCELLED'); + expect(statusOptions[4].nativeElement.textContent.trim()).toBe('ERROR'); + })); + + it('should display sort drop down', async(() => { + fixture.detectChanges(); + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + const sortElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"]'); + sortElement.click(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + const sortOptions = fixture.debugElement.queryAll(By.css('.mat-option-text')); + expect(sortOptions.length).toEqual(4); + }); + })); + + it('should display order drop down', async(() => { + fixture.detectChanges(); + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + const orderElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-order"]'); + orderElement.click(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + const orderOptions = fixture.debugElement.queryAll(By.css('.mat-option-text')); + expect(orderOptions.length).toEqual(2); + }); + })); + + it('should able to build a editTaskFilter form with default properties if input is empty', async(() => { + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + component.filterProperties = []; + fixture.detectChanges(); + const stateController = component.editTaskFilterForm.get('status'); + const sortController = component.editTaskFilterForm.get('sort'); + const orderController = component.editTaskFilterForm.get('order'); + const activityNameController = component.editTaskFilterForm.get('activityName'); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(component.taskFilterProperties.length).toBe(5); + expect(component.editTaskFilterForm).toBeDefined(); + expect(stateController.value).toBe('COMPLETED'); + expect(sortController.value).toBe('id'); + expect(orderController.value).toBe('ASC'); + expect(activityNameController.value).toBe('fake-activity'); + }); + })); + + it('should able to fetch running applications when appName property defined in the input', async(() => { + component.filterProperties = ['appName', 'processInstanceId', 'priority']; + fixture.detectChanges(); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + const appController = component.editTaskFilterForm.get('appName'); + fixture.detectChanges(); + fixture.whenStable().then(() => { + expect(getRunningApplicationsSpy).toHaveBeenCalled(); + expect(appController).toBeDefined(); + expect(appController.value).toBe('mock-app-name'); + }); + })); + }); + + describe('sort properties', () => { + + it('should display default sort properties', async(() => { + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + const sortElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"]'); + sortElement.click(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + const sortController = component.editTaskFilterForm.get('sort'); + const sortOptions = fixture.debugElement.queryAll(By.css('.mat-option-text')); + expect(sortController.value).toBe('id'); + expect(sortOptions.length).toEqual(4); + }); + })); + + it('should display sort properties when sort properties are specified', async(() => { + component.sortProperties = ['id', 'name', 'processInstanceId']; + getTaskFilterSpy.and.returnValue(of({ + sort: 'my-custom-sort', + processInstanceId: 'process-instance-id', + priority: '12' + })); + fixture.detectChanges(); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + const sortElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"]'); + sortElement.click(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + const sortController = component.editTaskFilterForm.get('sort'); + const sortOptions = fixture.debugElement.queryAll(By.css('.mat-option-text')); + expect(component.sortProperties.length).toBe(3); + expect(sortController.value).toBe('my-custom-sort'); + expect(sortOptions.length).toEqual(3); + }); + })); + + it('should display default sort properties if input is empty', async(() => { + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + component.sortProperties = []; + fixture.detectChanges(); + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + const sortElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"]'); + sortElement.click(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + const sortController = component.editTaskFilterForm.get('sort'); + const sortOptions = fixture.debugElement.queryAll(By.css('.mat-option-text')); + expect(sortController.value).toBe('id'); + expect(sortOptions.length).toEqual(4); + }); + })); + }); + + describe('filter actions', () => { + + it('should display default filter actions', async(() => { + component.toggleFilterActions = true; + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + const saveAsButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]'); + const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-save"]'); + const deleteButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-delete"]'); + expect(component.taskFilterActions.map(action => action.actionType)).toEqual(['save', 'saveAs', 'delete']); + expect(component.taskFilterActions.length).toBe(3); + expect(saveButton.disabled).toBe(true); + expect(saveAsButton.disabled).toBe(true); + expect(deleteButton.disabled).toBe(false); + }); + })); + + it('should display filter actions when input actions are specified', async(() => { + component.actions = ['save']; + fixture.detectChanges(); + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + component.toggleFilterActions = true; + fixture.detectChanges(); + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + fixture.whenStable().then(() => { + const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-save"]'); + expect(component.taskFilterActions.map(action => action.actionType)).toEqual(['save']); + expect(component.taskFilterActions.length).toBe(1); + expect(saveButton.disabled).toBeTruthy(); + const saveAsButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]'); + const deleteButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-delete"]'); + expect(saveAsButton).toBeFalsy(); + expect(deleteButton).toBeFalsy(); + }); + })); + + }); + + describe('edit filter actions', () => { + + beforeEach(() => { + const taskFilterIdChange = new SimpleChange(undefined, 'mock-task-filter-id', true); + component.ngOnChanges({ 'id': taskFilterIdChange }); + fixture.detectChanges(); + spyOn(component.action, 'emit').and.callThrough(); + }); + + it('should emit save event and save the filter on click save button', async(() => { + component.toggleFilterActions = true; + spyOn(service, 'updateFilter').and.returnValue(of({})); + fixture.detectChanges(); + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger'); + stateElement.click(); + fixture.detectChanges(); + const sortOptions = fixture.debugElement.queryAll(By.css('.mat-option-text')); + sortOptions[3].nativeElement.click(); + fixture.detectChanges(); + const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-save"]'); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(saveButton.disabled).toBe(false); + saveButton.click(); + expect(service.updateFilter).toHaveBeenCalled(); + expect(component.action.emit).toHaveBeenCalled(); + }); + })); + + it('should emit delete event and delete the filter on click of delete button', async(() => { + component.toggleFilterActions = true; + spyOn(service, 'deleteFilter').and.returnValue(of({})); + fixture.detectChanges(); + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger'); + stateElement.click(); + fixture.detectChanges(); + const deleteButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-delete"]'); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(deleteButton.disabled).toBe(false); + deleteButton.click(); + expect(service.deleteFilter).toHaveBeenCalled(); + expect(component.action.emit).toHaveBeenCalled(); + }); + })); + + it('should emit saveAs event and add filter on click saveAs button', async(() => { + component.toggleFilterActions = true; + spyOn(service, 'addFilter').and.returnValue(of({})); + fixture.detectChanges(); + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + const sortElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger'); + sortElement.click(); + fixture.detectChanges(); + const sortOptions = fixture.debugElement.queryAll(By.css('.mat-option-text')); + sortOptions[2].nativeElement.click(); + fixture.detectChanges(); + const saveAsButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]'); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(saveAsButton.disabled).toBe(false); + saveAsButton.click(); + expect(service.addFilter).toHaveBeenCalled(); + expect(component.action.emit).toHaveBeenCalled(); + expect(dialog.open).toHaveBeenCalled(); + }); + })); + + it('should call restore default filters service on deletion of last filter', async(() => { + component.toggleFilterActions = true; + spyOn(service, 'deleteFilter').and.returnValue(of([])); + const restoreDefaultFiltersSpy = spyOn(component, 'restoreDefaultTaskFilters').and.returnValue(of([])); + fixture.detectChanges(); + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger'); + stateElement.click(); + fixture.detectChanges(); + const deleteButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-delete"]'); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(deleteButton.disabled).toBe(false); + deleteButton.click(); + expect(service.deleteFilter).toHaveBeenCalled(); + expect(component.action.emit).toHaveBeenCalled(); + expect(restoreDefaultFiltersSpy).toHaveBeenCalled(); + }); + })); + + it('should not call restore default filters service on deletion of first filter', async(() => { + component.toggleFilterActions = true; + spyOn(service, 'deleteFilter').and.returnValue(of([{ name: 'mock-filter-name' }])); + const restoreDefaultFiltersSpy = spyOn(component, 'restoreDefaultTaskFilters').and.returnValue(of([])); + fixture.detectChanges(); + const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); + expansionPanel.click(); + fixture.detectChanges(); + const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger'); + stateElement.click(); + fixture.detectChanges(); + const deleteButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-delete"]'); + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(deleteButton.disabled).toBe(false); + deleteButton.click(); + expect(service.deleteFilter).toHaveBeenCalled(); + expect(component.action.emit).toHaveBeenCalled(); + expect(restoreDefaultFiltersSpy).not.toHaveBeenCalled(); + }); + })); + }); +}); diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/edit-service-task-filter-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/edit-service-task-filter-cloud.component.ts new file mode 100644 index 0000000000..6875626a9c --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/edit-service-task-filter-cloud.component.ts @@ -0,0 +1,267 @@ +/*! + * @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, Output, EventEmitter } from '@angular/core'; +import { FormBuilder } from '@angular/forms'; +import { DateAdapter } from '@angular/material/core'; +import { MatDialog } from '@angular/material/dialog'; +import { filter, takeUntil, finalize, switchMap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { Moment } from 'moment'; + +import { TaskFilterProperties, TaskFilterAction, ServiceTaskFilterCloudModel } from '../../models/filter-cloud.model'; +import { TaskFilterDialogCloudComponent } from '../task-filter-dialog/task-filter-dialog-cloud.component'; +import { TranslationService, UserPreferencesService } from '@alfresco/adf-core'; +import { AppsProcessCloudService } from '../../../../app/services/apps-process-cloud.service'; +import { TaskCloudService } from '../../../services/task-cloud.service'; +import { ServiceTaskFilterCloudService } from '../../services/service-task-filter-cloud.service'; +import { BaseEditTaskFilterCloudComponent } from './base-edit-task-filter-cloud.component'; + +@Component({ + selector: 'adf-cloud-edit-service-task-filter', + templateUrl: './base-edit-task-filter-cloud.component.html', + styleUrls: ['./base-edit-task-filter-cloud.component.scss'] +}) +export class EditServiceTaskFilterCloudComponent extends BaseEditTaskFilterCloudComponent { + + public static DEFAULT_TASK_FILTER_PROPERTIES = ['appName', 'activityName', 'status', 'sort', 'order']; + public static DEFAULT_TASK_SORT_PROPERTIES = ['id', 'name', 'startedDate', 'completedDate']; + public static DEFAULT_TASK_STATUS_PROPERTIES = [ + { label: 'ALL', value: '' }, + { label: 'STARTED', value: 'STARTED' }, + { label: 'COMPLETED', value: 'COMPLETED' }, + { label: 'CANCELLED', value: 'CANCELLED' }, + { label: 'ERROR', value: 'ERROR' } + ]; + + /** Emitted when a task filter property changes. */ + @Output() + filterChange: EventEmitter = new EventEmitter(); + + taskFilter: ServiceTaskFilterCloudModel; + changedTaskFilter: ServiceTaskFilterCloudModel; + + constructor( + protected formBuilder: FormBuilder, + public dialog: MatDialog, + private translateService: TranslationService, + private serviceTaskFilterCloudService: ServiceTaskFilterCloudService, + protected dateAdapter: DateAdapter, + protected userPreferencesService: UserPreferencesService, + protected appsProcessCloudService: AppsProcessCloudService, + protected taskCloudService: TaskCloudService) { + super(formBuilder, dateAdapter, userPreferencesService, appsProcessCloudService, taskCloudService); + } + + assignNewFilter(formValues: ServiceTaskFilterCloudModel) { + this.changedTaskFilter = Object.assign({}, this.taskFilter, formValues) as ServiceTaskFilterCloudModel; + this.formHasBeenChanged = !this.compareFilters(this.changedTaskFilter, this.taskFilter); + this.filterChange.emit(this.changedTaskFilter); + } + + /** + * Fetches task filter by application name and filter id and creates filter properties, build form + */ + retrieveTaskFilterAndBuildForm() { + this.isLoading = true; + this.serviceTaskFilterCloudService.getTaskFilterById(this.appName, this.id) + .pipe( + finalize(() => this.isLoading = false), + takeUntil(this.onDestroy$) + ) + .subscribe(response => { + this.taskFilter = response as ServiceTaskFilterCloudModel; + this.taskFilterProperties = this.createAndFilterProperties(); + this.taskFilterActions = this.createAndFilterActions(); + this.buildForm(this.taskFilterProperties); + }); + } + + checkMandatoryFilterProperties() { + if (this.filterProperties === undefined || this.filterProperties.length === 0) { + this.filterProperties = EditServiceTaskFilterCloudComponent.DEFAULT_TASK_FILTER_PROPERTIES; + } + } + + checkMandatorySortProperties(): void { + if (this.sortProperties === undefined || this.sortProperties.length === 0) { + this.sortProperties = EditServiceTaskFilterCloudComponent.DEFAULT_TASK_SORT_PROPERTIES; + } + } + + /** + * Return true if both filters are same + * @param editedQuery, @param currentQuery + */ + compareFilters( + editedQuery: ServiceTaskFilterCloudModel, + currentQuery: ServiceTaskFilterCloudModel + ): boolean { + return JSON.stringify(editedQuery).toLowerCase() === JSON.stringify(currentQuery).toLowerCase(); + } + + save(saveAction: TaskFilterAction): void { + this.serviceTaskFilterCloudService + .updateFilter(this.changedTaskFilter) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(() => { + saveAction.filter = this.changedTaskFilter; + this.action.emit(saveAction); + this.formHasBeenChanged = this.compareFilters(this.changedTaskFilter, this.taskFilter); + }); + } + + delete(deleteAction: TaskFilterAction): void { + this.serviceTaskFilterCloudService + .deleteFilter(this.taskFilter) + .pipe( + filter((filters) => { + deleteAction.filter = this.taskFilter; + this.action.emit(deleteAction); + return filters.length === 0; + }), + switchMap(() => this.restoreDefaultTaskFilters()), + takeUntil(this.onDestroy$)) + .subscribe(() => { }); + } + + saveAs(saveAsAction: TaskFilterAction): void { + const dialogRef = this.dialog.open(TaskFilterDialogCloudComponent, { + data: { + name: this.translateService.instant(this.taskFilter.name) + }, + height: 'auto', + minWidth: '30%' + }); + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action === TaskFilterDialogCloudComponent.ACTION_SAVE) { + const filterId = Math.random().toString(36).substr(2, 9); + const filterKey = this.getSanitizeFilterName(result.name); + const newFilter = { + name: result.name, + icon: result.icon, + id: filterId, + key: 'custom-' + filterKey + }; + const resultFilter: ServiceTaskFilterCloudModel = Object.assign({}, this.changedTaskFilter, newFilter); + this.serviceTaskFilterCloudService.addFilter(resultFilter) + .pipe(takeUntil(this.onDestroy$)).subscribe(() => { + saveAsAction.filter = resultFilter; + this.action.emit(saveAsAction); + }); + } + }); + } + + isDisabledForDefaultFilters(action: TaskFilterAction): boolean { + return ( + this.serviceTaskFilterCloudService.isDefaultFilter(this.taskFilter.name) && + EditServiceTaskFilterCloudComponent.ACTIONS_DISABLED_BY_DEFAULT.includes(action.actionType) + ); + } + + restoreDefaultTaskFilters(): Observable { + return this.serviceTaskFilterCloudService.getTaskListFilters(this.appName); + } + + createTaskFilterProperties(): TaskFilterProperties[] { + return [ + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_SERVICE_TASK_FILTER.LABEL.APP_NAME', + type: 'select', + key: 'appName', + value: this.taskFilter.appName || '', + options: this.applicationNames + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_SERVICE_TASK_FILTER.LABEL.SERVICE_TASK_ID', + type: 'text', + key: 'serviceTaskId', + value: this.taskFilter.serviceTaskId || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_SERVICE_TASK_FILTER.LABEL.ELEMENT_ID', + type: 'text', + key: 'elementId', + value: this.taskFilter.elementId || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_SERVICE_TASK_FILTER.LABEL.ACTIVITY_NAME', + type: 'text', + key: 'activityName', + value: this.taskFilter.activityName || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_SERVICE_TASK_FILTER.LABEL.ACTIVITY_TYPE', + type: 'text', + key: 'activityType', + value: this.taskFilter.activityType || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_SERVICE_TASK_FILTER.LABEL.SORT', + type: 'select', + key: 'sort', + value: this.taskFilter.sort || this.createSortProperties[0].value, + options: this.createSortProperties + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_SERVICE_TASK_FILTER.LABEL.DIRECTION', + type: 'select', + key: 'order', + value: this.taskFilter.order || EditServiceTaskFilterCloudComponent.DIRECTIONS[0].value, + options: EditServiceTaskFilterCloudComponent.DIRECTIONS + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_SERVICE_TASK_FILTER.LABEL.STATUS', + type: 'select', + key: 'status', + value: this.taskFilter.status || EditServiceTaskFilterCloudComponent.DEFAULT_TASK_STATUS_PROPERTIES[0].value, + options: EditServiceTaskFilterCloudComponent.DEFAULT_TASK_STATUS_PROPERTIES + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_SERVICE_TASK_FILTER.LABEL.STARTED_DATE', + type: 'date', + key: 'startedDate', + value: this.taskFilter.completedDate || false + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_SERVICE_TASK_FILTER.LABEL.COMPLETED_DATE', + type: 'date', + key: 'completedDate', + value: this.taskFilter.completedDate || false + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_SERVICE_TASK_FILTER.LABEL.PROCESS_INSTANCE_ID', + type: 'text', + key: 'processInstanceId', + value: this.taskFilter.processInstanceId || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_SERVICE_TASK_FILTER.LABEL.PROCESS_DEF_ID', + type: 'text', + key: 'processDefinitionId', + value: this.taskFilter.processDefinitionId || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_SERVICE_TASK_FILTER.LABEL.SERVICE_NAME', + type: 'text', + key: 'serviceName', + value: this.taskFilter.serviceName || '' + }) + ]; + } +} diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/edit-task-filter-cloud.component.spec.ts similarity index 93% rename from lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.spec.ts rename to lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/edit-task-filter-cloud.component.spec.ts index 6a7db9bc3a..b2a842e1e5 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filter-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/edit-task-filter-cloud.component.spec.ts @@ -23,21 +23,21 @@ import { setupTestBed } from '@alfresco/adf-core'; import { MatDialog } from '@angular/material/dialog'; import { of } from 'rxjs'; import { debounceTime } from 'rxjs/operators'; -import { TASK_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service'; -import { LocalPreferenceCloudService } from '../../../services/local-preference-cloud.service'; -import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module'; -import { AppsProcessCloudService } from '../../../app/services/apps-process-cloud.service'; -import { fakeApplicationInstance } from '../../../app/mock/app-model.mock'; -import { TaskFiltersCloudModule } from '../task-filters-cloud.module'; +import { TASK_FILTERS_SERVICE_TOKEN } from '../../../../services/cloud-token.service'; +import { LocalPreferenceCloudService } from '../../../../services/local-preference-cloud.service'; +import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module'; +import { AppsProcessCloudService } from '../../../../app/services/apps-process-cloud.service'; +import { fakeApplicationInstance } from '../../../../app/mock/app-model.mock'; +import { TaskFiltersCloudModule } from '../../task-filters-cloud.module'; import { EditTaskFilterCloudComponent } from './edit-task-filter-cloud.component'; -import { TaskFilterCloudService } from '../services/task-filter-cloud.service'; -import { TaskCloudService } from '../../services/task-cloud.service'; -import { TaskFilterDialogCloudComponent } from './task-filter-dialog-cloud.component'; -import { fakeFilter } from '../mock/task-filters-cloud.mock'; +import { TaskFilterCloudService } from '../../services/task-filter-cloud.service'; +import { TaskCloudService } from '../../../services/task-cloud.service'; +import { fakeFilter } from '../../mock/task-filters-cloud.mock'; import { AbstractControl } from '@angular/forms'; import moment from 'moment-es6'; import { TranslateModule } from '@ngx-translate/core'; -import { DateCloudFilterType } from '../../../models/date-cloud-filter.model'; +import { DateCloudFilterType } from '../../../../models/date-cloud-filter.model'; +import { TaskFilterCloudModel } from '../../models/filter-cloud.model'; describe('EditTaskFilterCloudComponent', () => { let component: EditTaskFilterCloudComponent; @@ -68,11 +68,13 @@ describe('EditTaskFilterCloudComponent', () => { appsService = TestBed.inject(AppsProcessCloudService); taskService = TestBed.inject(TaskCloudService); dialog = TestBed.inject(MatDialog); - spyOn(dialog, 'open').and.returnValue({ afterClosed: of({ - action: TaskFilterDialogCloudComponent.ACTION_SAVE, - icon: 'icon', - name: 'fake-name' - }) }); + spyOn(dialog, 'open').and.returnValue({ + afterClosed: of({ + action: EditTaskFilterCloudComponent.ACTION_SAVE, + icon: 'icon', + name: 'fake-name' + }) + }); getTaskFilterSpy = spyOn(service, 'getTaskFilterById').and.returnValue(of(fakeFilter)); getRunningApplicationsSpy = spyOn(appsService, 'getDeployedApplicationsByStatus').and.returnValue(of(fakeApplicationInstance)); fixture.detectChanges(); @@ -172,7 +174,7 @@ describe('EditTaskFilterCloudComponent', () => { expect(component.editTaskFilterForm).toBeDefined(); }); - it('should create editTaskFilter form with default properties', async(() => { + it('should create editTaskFilter form with default user task properties', async(() => { fixture.detectChanges(); fixture.whenStable().then(() => { const stateController = component.editTaskFilterForm.get('status'); @@ -244,13 +246,13 @@ describe('EditTaskFilterCloudComponent', () => { fixture.detectChanges(); component.editTaskFilterForm.valueChanges - .pipe(debounceTime(300)) - .subscribe(() => { - const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-save"]'); - fixture.detectChanges(); - expect(saveButton.disabled).toBe(false); - done(); - }); + .pipe(debounceTime(300)) + .subscribe(() => { + const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-save"]'); + fixture.detectChanges(); + expect(saveButton.disabled).toBe(false); + done(); + }); const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger'); stateElement.click(); @@ -330,13 +332,13 @@ describe('EditTaskFilterCloudComponent', () => { fixture.detectChanges(); component.editTaskFilterForm.valueChanges - .pipe(debounceTime(300)) - .subscribe(() => { - const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]'); - fixture.detectChanges(); - expect(saveButton.disabled).toEqual(false); - done(); - }); + .pipe(debounceTime(300)) + .subscribe(() => { + const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]'); + fixture.detectChanges(); + expect(saveButton.disabled).toEqual(false); + done(); + }); const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger'); stateElement.click(); @@ -354,13 +356,13 @@ describe('EditTaskFilterCloudComponent', () => { fixture.detectChanges(); component.editTaskFilterForm.valueChanges - .pipe(debounceTime(300)) - .subscribe(() => { - const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]'); - fixture.detectChanges(); - expect(saveButton.disabled).toEqual(false); - done(); - }); + .pipe(debounceTime(300)) + .subscribe(() => { + const saveButton = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-filter-action-saveAs"]'); + fixture.detectChanges(); + expect(saveButton.disabled).toEqual(false); + done(); + }); const stateElement = fixture.debugElement.nativeElement.querySelector('[data-automation-id="adf-cloud-edit-task-property-sort"] .mat-select-trigger'); stateElement.click(); @@ -657,7 +659,9 @@ describe('EditTaskFilterCloudComponent', () => { }); component.filterChange.subscribe(() => { - expect(component.changedTaskFilter.lastModifiedTo.toISOString()).toEqual(lastModifiedToFilter.toISOString()); + if (component.changedTaskFilter instanceof TaskFilterCloudModel) { + expect(component.changedTaskFilter.lastModifiedTo.toISOString()).toEqual(lastModifiedToFilter.toISOString()); + } done(); }); component.onFilterChange(); @@ -764,7 +768,7 @@ describe('EditTaskFilterCloudComponent', () => { it('should not call restore default filters service on deletion of first filter', async(() => { component.toggleFilterActions = true; - spyOn(service, 'deleteFilter').and.returnValue(of([{ name: 'mock-filter-name'}])); + spyOn(service, 'deleteFilter').and.returnValue(of([{ name: 'mock-filter-name' }])); const restoreDefaultFiltersSpy = spyOn(component, 'restoreDefaultTaskFilters').and.returnValue(of([])); fixture.detectChanges(); const expansionPanel = fixture.debugElement.nativeElement.querySelector('mat-expansion-panel-header'); diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/edit-task-filter-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/edit-task-filter-cloud.component.ts new file mode 100644 index 0000000000..d3fcdec248 --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/edit-task-filters/edit-task-filter-cloud.component.ts @@ -0,0 +1,334 @@ +/*! + * @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, Output, EventEmitter } from '@angular/core'; +import { FormBuilder } from '@angular/forms'; +import { DateAdapter } from '@angular/material/core'; +import { MatDialog } from '@angular/material/dialog'; +import { filter, takeUntil, finalize, switchMap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import moment from 'moment-es6'; +import { Moment } from 'moment'; + +import { TaskFilterCloudModel, TaskFilterProperties, TaskFilterAction } from '../../models/filter-cloud.model'; +import { TaskFilterCloudService } from '../../services/task-filter-cloud.service'; +import { TaskFilterDialogCloudComponent } from '../task-filter-dialog/task-filter-dialog-cloud.component'; +import { TranslationService, UserPreferencesService } from '@alfresco/adf-core'; +import { AppsProcessCloudService } from '../../../../app/services/apps-process-cloud.service'; +import { DateCloudFilterType } from '../../../../models/date-cloud-filter.model'; +import { TaskCloudService } from '../../../services/task-cloud.service'; +import { BaseEditTaskFilterCloudComponent } from './base-edit-task-filter-cloud.component'; + +@Component({ + selector: 'adf-cloud-edit-task-filter', + templateUrl: './base-edit-task-filter-cloud.component.html', + styleUrls: ['./base-edit-task-filter-cloud.component.scss'] +}) +export class EditTaskFilterCloudComponent extends BaseEditTaskFilterCloudComponent { + + public static DEFAULT_TASK_FILTER_PROPERTIES = ['status', 'assignee', 'sort', 'order']; + public static DEFAULT_TASK_SORT_PROPERTIES = ['id', 'name', 'createdDate', 'priority']; + public static DEFAULT_TASK_STATUS_PROPERTIES = [ + { label: 'ALL', value: '' }, + { label: 'CREATED', value: 'CREATED' }, + { label: 'ASSIGNED', value: 'ASSIGNED' }, + { label: 'SUSPENDED', value: 'SUSPENDED' }, + { label: 'CANCELLED', value: 'CANCELLED' }, + { label: 'COMPLETED', value: 'COMPLETED' } + ]; + + /** Emitted when a task filter property changes. */ + @Output() + filterChange: EventEmitter = new EventEmitter(); + + taskFilter: TaskFilterCloudModel; + changedTaskFilter: TaskFilterCloudModel; + + constructor( + protected formBuilder: FormBuilder, + public dialog: MatDialog, + private translateService: TranslationService, + private taskFilterCloudService: TaskFilterCloudService, + protected dateAdapter: DateAdapter, + protected userPreferencesService: UserPreferencesService, + protected appsProcessCloudService: AppsProcessCloudService, + protected taskCloudService: TaskCloudService) { + super(formBuilder, dateAdapter, userPreferencesService, appsProcessCloudService, taskCloudService); + } + + assignNewFilter(formValues: TaskFilterCloudModel) { + this.setLastModifiedToFilter( formValues); + this.changedTaskFilter = new TaskFilterCloudModel(Object.assign({}, this.taskFilter, formValues)); + this.formHasBeenChanged = !this.compareFilters(this.changedTaskFilter, this.taskFilter); + this.filterChange.emit(this.changedTaskFilter); + } + + /** + * Fetches task filter by application name and filter id and creates filter properties, build form + */ + retrieveTaskFilterAndBuildForm() { + this.isLoading = true; + this.taskFilterCloudService.getTaskFilterById(this.appName, this.id) + .pipe( + finalize(() => this.isLoading = false), + takeUntil(this.onDestroy$) + ) + .subscribe(response => { + this.taskFilter = new TaskFilterCloudModel(response); + this.taskFilterProperties = this.createAndFilterProperties(); + if (this.hasLastModifiedProperty()) { + this.taskFilterProperties = [...this.taskFilterProperties, ...this.createLastModifiedProperty()]; + } + this.taskFilterActions = this.createAndFilterActions(); + this.buildForm(this.taskFilterProperties); + }); + } + + checkMandatoryFilterProperties() { + if (this.filterProperties === undefined || this.filterProperties.length === 0) { + this.filterProperties = EditTaskFilterCloudComponent.DEFAULT_TASK_FILTER_PROPERTIES; + } + } + + checkMandatorySortProperties(): void { + if (this.sortProperties === undefined || this.sortProperties.length === 0) { + this.sortProperties = EditTaskFilterCloudComponent.DEFAULT_TASK_SORT_PROPERTIES; + } + } + + private setLastModifiedToFilter(formValues: TaskFilterCloudModel) { + if (formValues.lastModifiedTo && Date.parse(formValues.lastModifiedTo.toString())) { + const lastModifiedToFilterValue = moment(formValues.lastModifiedTo); + lastModifiedToFilterValue.set({ + hour: 23, + minute: 59, + second: 59 + }); + formValues.lastModifiedTo = lastModifiedToFilterValue.toDate(); + } + } + + /** + * Return true if both filters are same + * @param editedQuery, @param currentQuery + */ + compareFilters( + editedQuery: TaskFilterCloudModel, + currentQuery: TaskFilterCloudModel + ): boolean { + return JSON.stringify(editedQuery).toLowerCase() === JSON.stringify(currentQuery).toLowerCase(); + } + + save(saveAction: TaskFilterAction): void { + this.taskFilterCloudService + .updateFilter(this.changedTaskFilter) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(() => { + saveAction.filter = this.changedTaskFilter; + this.action.emit(saveAction); + this.formHasBeenChanged = this.compareFilters(this.changedTaskFilter, this.taskFilter); + }); + } + + delete(deleteAction: TaskFilterAction): void { + this.taskFilterCloudService + .deleteFilter(this.taskFilter) + .pipe( + filter((filters) => { + deleteAction.filter = this.taskFilter; + this.action.emit(deleteAction); + return filters.length === 0; + }), + switchMap(() => this.restoreDefaultTaskFilters()), + takeUntil(this.onDestroy$)) + .subscribe(() => { }); + } + + saveAs(saveAsAction: TaskFilterAction): void { + const dialogRef = this.dialog.open(TaskFilterDialogCloudComponent, { + data: { + name: this.translateService.instant(this.taskFilter.name) + }, + height: 'auto', + minWidth: '30%' + }); + dialogRef.afterClosed().subscribe((result) => { + if (result && result.action === TaskFilterDialogCloudComponent.ACTION_SAVE) { + const filterId = Math.random().toString(36).substr(2, 9); + const filterKey = this.getSanitizeFilterName(result.name); + const newFilter = { + name: result.name, + icon: result.icon, + id: filterId, + key: 'custom-' + filterKey + }; + const resultFilter: TaskFilterCloudModel = Object.assign({}, this.changedTaskFilter, newFilter); + this.taskFilterCloudService.addFilter(resultFilter) + .pipe(takeUntil(this.onDestroy$)).subscribe(() => { + saveAsAction.filter = resultFilter; + this.action.emit(saveAsAction); + }); + } + }); + } + + isDisabledForDefaultFilters(action: TaskFilterAction): boolean { + return ( + this.taskFilterCloudService.isDefaultFilter(this.taskFilter.name) && + EditTaskFilterCloudComponent.ACTIONS_DISABLED_BY_DEFAULT.includes(action.actionType) + ); + } + + restoreDefaultTaskFilters(): Observable { + return this.taskFilterCloudService.getTaskListFilters(this.appName); + } + + createLastModifiedProperty(): TaskFilterProperties[] { + return [ + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.LAST_MODIFIED_FROM', + type: 'date', + key: 'lastModifiedFrom', + value: '' + }), + + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.LAST_MODIFIED_TO', + type: 'date', + key: 'lastModifiedTo', + value: '' + }) + ]; + } + + createTaskFilterProperties(): TaskFilterProperties[] { + return [ + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.APP_NAME', + type: 'select', + key: 'appName', + value: this.taskFilter.appName || '', + options: this.applicationNames + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.TASK_ID', + type: 'text', + key: 'taskId', + value: '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.STATUS', + type: 'select', + key: 'status', + value: this.taskFilter.status || EditTaskFilterCloudComponent.DEFAULT_TASK_STATUS_PROPERTIES[0].value, + options: EditTaskFilterCloudComponent.DEFAULT_TASK_STATUS_PROPERTIES + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.ASSIGNMENT', + type: 'text', + key: 'assignee', + value: this.taskFilter.assignee || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PROCESS_DEF_NAME', + type: 'select', + key: 'processDefinitionName', + value: this.taskFilter.processDefinitionName || '', + options: this.processDefinitionNames + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PROCESS_INSTANCE_ID', + type: 'text', + key: 'processInstanceId', + value: this.taskFilter.processInstanceId || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PROCESS_DEF_ID', + type: 'text', + key: 'processDefinitionId', + value: this.taskFilter.processDefinitionId || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.TASK_NAME', + type: 'text', + key: 'taskName', + value: this.taskFilter.taskName || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PARENT_TASK_ID', + type: 'text', + key: 'parentTaskId', + value: this.taskFilter.parentTaskId || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.PRIORITY', + type: 'text', + key: 'priority', + value: this.taskFilter.priority || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.OWNER', + type: 'text', + key: 'owner', + value: this.taskFilter.owner || '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.CREATED_DATE', + type: 'date', + key: 'createdDate', + value: '' + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.SORT', + type: 'select', + key: 'sort', + value: this.taskFilter.sort || this.createSortProperties[0].value, + options: this.createSortProperties + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.DIRECTION', + type: 'select', + key: 'order', + value: this.taskFilter.order || EditTaskFilterCloudComponent.DIRECTIONS[0].value, + options: EditTaskFilterCloudComponent.DIRECTIONS + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.STAND_ALONE', + type: 'checkbox', + key: 'standalone', + value: this.taskFilter.standalone || false + }), + new TaskFilterProperties({ + label: 'ADF_CLOUD_EDIT_TASK_FILTER.LABEL.DUE_DATE', + type: 'date-range', + key: 'dueDateRange', + attributes: { dateType: 'dueDateType', from: '_dueDateFrom', to: '_dueDateTo'}, + value: { + dueDateType: this.taskFilter.dueDateType || null, + _dueDateFrom: this.taskFilter.dueDateFrom || null, + _dueDateTo: this.taskFilter.dueDateTo || null + }, + dateFilterOptions: [ + DateCloudFilterType.NO_DATE, + DateCloudFilterType.TOMORROW, + DateCloudFilterType.NEXT_7_DAYS, + DateCloudFilterType.RANGE + ] + }) + ]; + } +} diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/service-task-filters-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/service-task-filters-cloud.component.spec.ts new file mode 100644 index 0000000000..c55dbdeaf0 --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/service-task-filters-cloud.component.spec.ts @@ -0,0 +1,354 @@ +/*! + * @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 { SimpleChange } from '@angular/core'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { setupTestBed } from '@alfresco/adf-core'; +import { from, Observable } from 'rxjs'; +import { TASK_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service'; +import { LocalPreferenceCloudService } from '../../../services/local-preference-cloud.service'; +import { FilterParamsModel } from '../models/filter-cloud.model'; +import { By } from '@angular/platform-browser'; +import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module'; +import { TaskFiltersCloudModule } from '../task-filters-cloud.module'; +import { fakeGlobalServiceFilters } from '../mock/task-filters-cloud.mock'; +import { TranslateModule } from '@ngx-translate/core'; +import { ServiceTaskFilterCloudService } from '../services/service-task-filter-cloud.service'; +import { ServiceTaskFiltersCloudComponent } from './service-task-filters-cloud.component'; + +describe('ServiceTaskFiltersCloudComponent', () => { + + let serviceTaskFilterCloudService: ServiceTaskFilterCloudService; + + const fakeGlobalFilterObservable = + new Observable(function(observer) { + observer.next(fakeGlobalServiceFilters); + observer.complete(); + }); + + const fakeGlobalFilterPromise = new Promise(function (resolve) { + resolve(fakeGlobalServiceFilters); + }); + + const mockErrorFilterList = { + error: 'wrong request' + }; + + const mockErrorFilterPromise = Promise.reject(mockErrorFilterList); + + let component: ServiceTaskFiltersCloudComponent; + let fixture: ComponentFixture; + + setupTestBed({ + imports: [ + TranslateModule.forRoot(), + ProcessServiceCloudTestingModule, + TaskFiltersCloudModule + ], + providers: [ + { provide: TASK_FILTERS_SERVICE_TOKEN, useClass: LocalPreferenceCloudService } + ] + }); + + beforeEach(() => { + fixture = TestBed.createComponent(ServiceTaskFiltersCloudComponent); + component = fixture.componentInstance; + + serviceTaskFilterCloudService = TestBed.inject(ServiceTaskFilterCloudService); + }); + + it('should attach specific icon for each filter if hasIcon is true', async(() => { + spyOn(serviceTaskFilterCloudService, 'getTaskListFilters').and.returnValue(fakeGlobalFilterObservable); + const change = new SimpleChange(undefined, 'my-app-1', true); + component.ngOnChanges({'appName': change}); + fixture.detectChanges(); + component.showIcons = true; + fixture.whenStable().then(() => { + fixture.detectChanges(); + expect(component.filters.length).toBe(3); + const filters = fixture.nativeElement.querySelectorAll('.adf-icon'); + expect(filters.length).toBe(3); + expect(filters[0].innerText).toContain('adjust'); + expect(filters[1].innerText).toContain('done'); + expect(filters[2].innerText).toContain('inbox'); + }); + })); + + it('should not attach icons for each filter if hasIcon is false', (done) => { + spyOn(serviceTaskFilterCloudService, 'getTaskListFilters').and.returnValue(from(fakeGlobalFilterPromise)); + + component.showIcons = false; + const change = new SimpleChange(undefined, 'my-app-1', true); + component.ngOnChanges({'appName': change}); + fixture.detectChanges(); + + fixture.whenStable().then(() => { + fixture.detectChanges(); + const filters: any = fixture.debugElement.queryAll(By.css('.adf-icon')); + expect(filters.length).toBe(0); + done(); + }); + }); + + it('should display the filters', async(() => { + spyOn(serviceTaskFilterCloudService, 'getTaskListFilters').and.returnValue(fakeGlobalFilterObservable); + const change = new SimpleChange(undefined, 'my-app-1', true); + component.ngOnChanges({'appName': change}); + fixture.detectChanges(); + component.showIcons = true; + fixture.whenStable().then(() => { + fixture.detectChanges(); + const filters = fixture.debugElement.queryAll(By.css('.adf-filters__entry')); + expect(component.filters.length).toBe(3); + expect(filters.length).toBe(3); + expect(filters[0].nativeElement.innerText).toContain('FakeServiceTasks'); + expect(filters[1].nativeElement.innerText).toContain('FakeMyServiceTasks1'); + expect(filters[2].nativeElement.innerText).toContain('FakeMyServiceTasks2'); + }); + })); + + it('should emit an error with a bad response', (done) => { + spyOn(serviceTaskFilterCloudService, 'getTaskListFilters').and.returnValue(from(mockErrorFilterPromise)); + + const appName = 'my-app-1'; + const change = new SimpleChange(null, appName, true); + component.ngOnChanges({'appName': change}); + + component.error.subscribe((err) => { + expect(err).toBeDefined(); + done(); + }); + }); + + it('should return the filter task list', (done) => { + spyOn(serviceTaskFilterCloudService, 'getTaskListFilters').and.returnValue(from(fakeGlobalFilterPromise)); + const appName = 'my-app-1'; + const change = new SimpleChange(null, appName, true); + component.ngOnChanges({ 'appName': change }); + + component.success.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.filters).toBeDefined(); + expect(component.filters.length).toEqual(3); + done(); + }); + }); + + it('should return the filter task list, filtered By Name', (done) => { + spyOn(serviceTaskFilterCloudService, 'getTaskListFilters').and.returnValue(from(fakeGlobalFilterPromise)); + const appName = 'my-app-1'; + const change = new SimpleChange(null, appName, true); + component.ngOnChanges({ 'appName': change }); + + component.success.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.filters).toBeDefined(); + expect(component.filters[0].name).toEqual('FakeServiceTasks'); + expect(component.filters[1].name).toEqual('FakeMyServiceTasks1'); + expect(component.filters[2].name).toEqual('FakeMyServiceTasks2'); + done(); + }); + }); + + it('should select the first filter as default', async(() => { + spyOn(serviceTaskFilterCloudService, 'getTaskListFilters').and.returnValue(fakeGlobalFilterObservable); + + const appName = 'my-app-1'; + const change = new SimpleChange(null, appName, true); + + fixture.detectChanges(); + component.ngOnChanges({ 'appName': change }); + + component.success.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.currentFilter).toBeDefined(); + expect(component.currentFilter.name).toEqual('FakeServiceTasks'); + }); + + })); + + it('should select the task filter based on the input by name param', async(() => { + spyOn(serviceTaskFilterCloudService, 'getTaskListFilters').and.returnValue(fakeGlobalFilterObservable); + + component.filterParam = new FilterParamsModel({ name: 'FakeMyServiceTasks1' }); + const appName = 'my-app-1'; + const change = new SimpleChange(null, appName, true); + + fixture.detectChanges(); + component.ngOnChanges({ 'appName': change }); + + component.success.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.currentFilter).toBeDefined(); + expect(component.currentFilter.name).toEqual('FakeMyServiceTasks1'); + }); + + })); + + it('should select the default task filter if filter input does not exist', async(() => { + spyOn(serviceTaskFilterCloudService, 'getTaskListFilters').and.returnValue(fakeGlobalFilterObservable); + + component.filterParam = new FilterParamsModel({ name: 'UnexistableFilter' }); + + const appName = 'my-app-1'; + const change = new SimpleChange(null, appName, true); + + fixture.detectChanges(); + component.ngOnChanges({ 'appName': change }); + + component.success.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.currentFilter).toBeDefined(); + expect(component.currentFilter.name).toEqual('FakeServiceTasks'); + }); + + })); + + it('should select the task filter based on the input by index param', async(() => { + spyOn(serviceTaskFilterCloudService, 'getTaskListFilters').and.returnValue(fakeGlobalFilterObservable); + + component.filterParam = new FilterParamsModel({ index: 2 }); + + const appName = 'my-app-1'; + const change = new SimpleChange(null, appName, true); + + fixture.detectChanges(); + component.ngOnChanges({ 'appName': change }); + + component.success.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.currentFilter).toBeDefined(); + expect(component.currentFilter.name).toEqual('FakeMyServiceTasks2'); + }); + + })); + + it('should select the task filter based on the input by id param', async(() => { + spyOn(serviceTaskFilterCloudService, 'getTaskListFilters').and.returnValue(fakeGlobalFilterObservable); + + component.filterParam = new FilterParamsModel({ id: 12 }); + const appName = 'my-app-1'; + const change = new SimpleChange(null, appName, true); + + fixture.detectChanges(); + component.ngOnChanges({ 'appName': change }); + + component.success.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.currentFilter).toBeDefined(); + expect(component.currentFilter.name).toEqual('FakeMyServiceTasks2'); + }); + + })); + + it('should emit an event when a filter is selected', async(() => { + spyOn(serviceTaskFilterCloudService, 'getTaskListFilters').and.returnValue(fakeGlobalFilterObservable); + + component.filterParam = new FilterParamsModel({ id: 12 }); + + const appName = 'my-app-1'; + const change = new SimpleChange(null, appName, true); + component.ngOnChanges({ 'appName': change }); + + fixture.detectChanges(); + spyOn(component, 'selectFilterAndEmit').and.stub(); + const filterButton = fixture.debugElement.nativeElement.querySelector(`[data-automation-id="${fakeGlobalServiceFilters[1].key}_filter"]`); + + filterButton.click(); + expect(component.selectFilterAndEmit).toHaveBeenCalledWith(fakeGlobalServiceFilters[1]); + })); + + it('should reset the filter when the param is undefined', async(() => { + spyOn(serviceTaskFilterCloudService, 'getTaskListFilters').and.returnValue(fakeGlobalFilterObservable); + spyOn(component, 'selectFilterAndEmit'); + component.currentFilter = null; + + const filterName = undefined; + const change = new SimpleChange(null, filterName, false); + component.ngOnChanges({ 'filterParam': change }); + + fixture.detectChanges(); + expect(component.selectFilterAndEmit).toHaveBeenCalledWith(undefined); + expect(component.currentFilter).toEqual(undefined); + })); + + it('should reload filters by appName on binding changes', () => { + spyOn(component, 'getFilters').and.stub(); + const appName = 'my-app-1'; + + const change = new SimpleChange(null, appName, true); + component.ngOnChanges({ 'appName': change }); + + expect(component.getFilters).toHaveBeenCalledWith(appName); + }); + + it('should change current filter when filterParam (name) changes', () => { + component.filters = fakeGlobalServiceFilters; + component.currentFilter = null; + + const change = new SimpleChange(null, { name: fakeGlobalServiceFilters[1].name }, true); + component.ngOnChanges({ 'filterParam': change }); + + fixture.whenStable().then(() => { + expect(component.currentFilter.name).toEqual(fakeGlobalServiceFilters[1].name); + }); + }); + + it('should change current filter when filterParam (key) changes', () => { + component.filters = fakeGlobalServiceFilters; + component.currentFilter = null; + + const change = new SimpleChange(null, { key: fakeGlobalServiceFilters[2].key }, true); + component.ngOnChanges({ 'filterParam': change }); + + fixture.whenStable().then(() => { + expect(component.currentFilter.key).toEqual(fakeGlobalServiceFilters[2].key); + }); + }); + + it('should change current filter when filterParam (index) changes', () => { + component.filters = fakeGlobalServiceFilters; + component.currentFilter = null; + const position = 1; + + const change = new SimpleChange(null, { index: position }, true); + component.ngOnChanges({ 'filterParam': change }); + + fixture.whenStable().then(() => { + expect(component.currentFilter.name).toEqual(fakeGlobalServiceFilters[position].name); + }); + }); + + it('should reload filters by app name on binding changes', () => { + spyOn(component, 'getFilters').and.stub(); + const appName = 'fake-app-name'; + + const change = new SimpleChange(null, appName, true); + component.ngOnChanges({ 'appName': change }); + + expect(component.getFilters).toHaveBeenCalledWith(appName); + }); + + it('should return the current filter after one is selected', () => { + const filter = new FilterParamsModel({ name: 'FakeMyServiceTasks2' }); + component.filters = fakeGlobalServiceFilters; + + expect(component.currentFilter).toBeUndefined(); + component.selectFilter(filter); + expect(component.currentFilter).toBe(fakeGlobalServiceFilters[2]); + }); +}); diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/service-task-filters-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/service-task-filters-cloud.component.ts new file mode 100644 index 0000000000..04a9a50b08 --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/service-task-filters-cloud.component.ts @@ -0,0 +1,123 @@ +/*! + * @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, EventEmitter, OnChanges, Output, SimpleChanges, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; +import { FilterParamsModel, ServiceTaskFilterCloudModel } from '../models/filter-cloud.model'; +import { TranslationService } from '@alfresco/adf-core'; +import { takeUntil } from 'rxjs/operators'; +import { BaseTaskFiltersCloudComponent } from './base-task-filters-cloud.component'; +import { ServiceTaskFilterCloudService } from '../services/service-task-filter-cloud.service'; + +@Component({ + selector: 'adf-cloud-service-task-filters', + templateUrl: './base-task-filters-cloud.component.html', + styleUrls: ['base-task-filters-cloud.component.scss'] +}) +export class ServiceTaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent implements OnInit, OnChanges { + + /** Emitted when a filter in the list is clicked. */ + @Output() + filterClick: EventEmitter = new EventEmitter(); + + filters$: Observable; + filters: ServiceTaskFilterCloudModel[] = []; + currentFilter: ServiceTaskFilterCloudModel; + + constructor(private serviceTaskFilterCloudService: ServiceTaskFilterCloudService, + private translationService: TranslationService) { + super(); + } + + ngOnInit() { + this.getFilters(this.appName); + } + + ngOnChanges(changes: SimpleChanges) { + const appName = changes['appName']; + const filter = changes['filterParam']; + if (appName && appName.currentValue !== appName.previousValue) { + this.getFilters(appName.currentValue); + } else if (filter && filter.currentValue !== filter.previousValue) { + this.selectFilterAndEmit(filter.currentValue); + } + } + + /** + * Return the filter list filtered by appName + */ + getFilters(appName: string) { + this.filters$ = this.serviceTaskFilterCloudService.getTaskListFilters(appName); + + this.filters$.pipe(takeUntil(this.onDestroy$)).subscribe( + (res: ServiceTaskFilterCloudModel[]) => { + this.resetFilter(); + this.filters = Object.assign([], res); + this.selectFilterAndEmit(this.filterParam); + this.success.emit(res); + }, + (err: any) => { + this.error.emit(err); + } + ); + } + + public selectFilter(paramFilter: FilterParamsModel) { + if (paramFilter) { + this.currentFilter = (this.filters as Array).find((filter: any, index) => + paramFilter.index === index || + paramFilter.key === filter.key || + paramFilter.id === filter.id || + (paramFilter.name && + (paramFilter.name.toLocaleLowerCase() === this.translationService.instant(filter.name).toLocaleLowerCase()) + )); + } + } + + public selectFilterAndEmit(newParamFilter: FilterParamsModel) { + if (newParamFilter) { + this.selectFilter(newParamFilter); + this.filterClick.emit(this.currentFilter); + } else { + this.currentFilter = undefined; + } + } + + /** + * Select as default task filter the first in the list + */ + public selectDefaultTaskFilter() { + if (!this.isFilterListEmpty()) { + this.currentFilter = this.filters[0]; + } + } + + /** + * Check if the filter list is empty + */ + isFilterListEmpty(): boolean { + return this.filters === undefined || (this.filters && this.filters.length === 0); + } + + /** + * Reset the filters properties + */ + private resetFilter() { + this.filters = []; + this.currentFilter = undefined; + } +} diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog/task-filter-dialog-cloud.component.html similarity index 100% rename from lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog-cloud.component.html rename to lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog/task-filter-dialog-cloud.component.html diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog-cloud.component.scss b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog/task-filter-dialog-cloud.component.scss similarity index 100% rename from lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog-cloud.component.scss rename to lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog/task-filter-dialog-cloud.component.scss diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog/task-filter-dialog-cloud.component.spec.ts similarity index 96% rename from lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog-cloud.component.spec.ts rename to lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog/task-filter-dialog-cloud.component.spec.ts index 0677bdafee..1e414b5a7e 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog/task-filter-dialog-cloud.component.spec.ts @@ -18,9 +18,9 @@ import { ComponentFixture, TestBed, async } from '@angular/core/testing'; import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; import { TaskFilterDialogCloudComponent } from './task-filter-dialog-cloud.component'; -import { TaskFiltersCloudModule } from '../task-filters-cloud.module'; +import { TaskFiltersCloudModule } from '../../task-filters-cloud.module'; import { setupTestBed } from '@alfresco/adf-core'; -import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module'; +import { ProcessServiceCloudTestingModule } from '../../../../testing/process-service-cloud.testing.module'; import { TranslateModule } from '@ngx-translate/core'; describe('TaskFilterDialogCloudComponent', () => { diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog/task-filter-dialog-cloud.component.ts similarity index 100% rename from lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog-cloud.component.ts rename to lib/process-services-cloud/src/lib/task/task-filters/components/task-filter-dialog/task-filter-dialog-cloud.component.ts diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.spec.ts index 5c31cf83ba..0362491151 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.spec.ts @@ -349,6 +349,6 @@ describe('TaskFiltersCloudComponent', () => { expect(component.currentFilter).toBeUndefined(); component.selectFilter(filter); - expect(component.getCurrentFilter()).toBe(fakeGlobalFilter[0]); + expect(component.currentFilter).toBe(fakeGlobalFilter[0]); }); }); diff --git a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.ts index 97970c0a6c..d63d2a7819 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/components/task-filters-cloud.component.ts @@ -15,55 +15,31 @@ * limitations under the License. */ -import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges, OnDestroy, OnInit } from '@angular/core'; -import { Observable, Subject } from 'rxjs'; +import { Component, EventEmitter, OnChanges, Output, SimpleChanges, OnInit } from '@angular/core'; +import { Observable } from 'rxjs'; import { TaskFilterCloudService } from '../services/task-filter-cloud.service'; import { TaskFilterCloudModel, FilterParamsModel } from '../models/filter-cloud.model'; import { TranslationService } from '@alfresco/adf-core'; import { takeUntil } from 'rxjs/operators'; +import { BaseTaskFiltersCloudComponent } from './base-task-filters-cloud.component'; @Component({ selector: 'adf-cloud-task-filters', - templateUrl: './task-filters-cloud.component.html', - styleUrls: ['task-filters-cloud.component.scss'] + templateUrl: './base-task-filters-cloud.component.html', + styleUrls: ['base-task-filters-cloud.component.scss'] }) -export class TaskFiltersCloudComponent implements OnInit, OnChanges, OnDestroy { - /** Display filters available to the current user for the application with the specified name. */ - @Input() - appName: string = ''; - - /** - * Parameters to use for the task filter cloud. If there is no match then the default filter - * (the first one in the list) is selected. - */ - @Input() - filterParam: FilterParamsModel; - - /** Toggles display of the filter's icons. */ - @Input() - showIcons: boolean = false; - +export class TaskFiltersCloudComponent extends BaseTaskFiltersCloudComponent implements OnInit, OnChanges { /** Emitted when a filter in the list is clicked. */ @Output() filterClick: EventEmitter = new EventEmitter(); - /** Emitted when the list is loaded. */ - @Output() - success: EventEmitter = new EventEmitter(); - - /** Emitted when an error occurs during loading. */ - @Output() - error: EventEmitter = new EventEmitter(); - filters$: Observable; - + filters: TaskFilterCloudModel[] = []; currentFilter: TaskFilterCloudModel; - filters: TaskFilterCloudModel [] = []; - - private onDestroy$ = new Subject(); - - constructor(private taskFilterCloudService: TaskFilterCloudService, private translationService: TranslationService) { + constructor(private taskFilterCloudService: TaskFilterCloudService, + private translationService: TranslationService) { + super(); } ngOnInit() { @@ -80,11 +56,6 @@ export class TaskFiltersCloudComponent implements OnInit, OnChanges, OnDestroy { } } - ngOnDestroy() { - this.onDestroy$.next(true); - this.onDestroy$.complete(); - } - /** * Return the filter list filtered by appName */ @@ -106,7 +77,7 @@ export class TaskFiltersCloudComponent implements OnInit, OnChanges, OnDestroy { public selectFilter(paramFilter: FilterParamsModel) { if (paramFilter) { - this.currentFilter = this.filters.find( (filter: TaskFilterCloudModel, index) => + this.currentFilter = this.filters.find((filter: any, index) => paramFilter.index === index || paramFilter.key === filter.key || paramFilter.id === filter.id || @@ -134,13 +105,6 @@ export class TaskFiltersCloudComponent implements OnInit, OnChanges, OnDestroy { } } - /** - * Return the current task - */ - getCurrentFilter(): TaskFilterCloudModel { - return this.currentFilter; - } - /** * Check if the filter list is empty */ diff --git a/lib/process-services-cloud/src/lib/task/task-filters/mock/task-filters-cloud.mock.ts b/lib/process-services-cloud/src/lib/task/task-filters/mock/task-filters-cloud.mock.ts index b8a70bd993..84b074d78d 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/mock/task-filters-cloud.mock.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/mock/task-filters-cloud.mock.ts @@ -15,9 +15,9 @@ * limitations under the License. */ -import { TaskFilterCloudModel } from '../models/filter-cloud.model'; +import { TaskFilterCloudModel, ServiceTaskFilterCloudModel } from '../models/filter-cloud.model'; -export let fakeGlobalFilter = [ +export const fakeGlobalFilter = [ new TaskFilterCloudModel({ name: 'FakeInvolvedTasks', key: 'fake-involved-tasks', @@ -44,7 +44,31 @@ export let fakeGlobalFilter = [ }) ]; -export let fakeFilter = new TaskFilterCloudModel({ +export const fakeGlobalServiceFilters = [ + { + name: 'FakeServiceTasks', + key: 'fake-involved-tasks', + icon: 'adjust', + id: '10', + status: 'open' + } as ServiceTaskFilterCloudModel, + { + name: 'FakeMyServiceTasks1', + key: 'fake-my-tast1', + icon: 'done', + id: '11', + status: 'open' + } as ServiceTaskFilterCloudModel, + { + name: 'FakeMyServiceTasks2', + key: 'fake-my-tast2', + icon: 'inbox', + id: '12', + status: 'open' + } as ServiceTaskFilterCloudModel +]; + +export const fakeFilter = new TaskFilterCloudModel({ name: 'FakeInvolvedTasks', icon: 'adjust', id: 'mock-task-filter-id', @@ -56,7 +80,19 @@ export let fakeFilter = new TaskFilterCloudModel({ sort: 'id' }); -export let fakeAllTaskFilter = new TaskFilterCloudModel({ +export const fakeServiceFilter = { + name: 'FakeInvolvedTasks', + icon: 'adjust', + id: 'mock-task-filter-id', + status: 'COMPLETED', + appName: 'mock-app-name', + processDefinitionId: 'process-def-id', + activityName: 'fake-activity', + order: 'ASC', + sort: 'id' +} as ServiceTaskFilterCloudModel; + +export const fakeAllTaskFilter = new TaskFilterCloudModel({ name: 'AllTasks', icon: 'adjust', id: 'mock-task-filter-id', diff --git a/lib/process-services-cloud/src/lib/task/task-filters/models/filter-cloud.model.ts b/lib/process-services-cloud/src/lib/task/task-filters/models/filter-cloud.model.ts index 6420149d0c..534da7a3c8 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/models/filter-cloud.model.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/models/filter-cloud.model.ts @@ -19,7 +19,7 @@ import { DateCloudFilterType } from '../../../models/date-cloud-filter.model'; import { DateRangeFilterService } from '../../../common/date-range-filter/date-range-filter.service'; import { ComponentSelectionMode } from '../../../types'; -export class TaskFilterCloudModel { +export class TaskFilterCloudModel { id: string; name: string; key: string; @@ -116,6 +116,38 @@ export class TaskFilterCloudModel { return !!this.dateRangeFilterService.isDateRangeType(type); } } + +export interface ServiceTaskFilterCloudModel { + id?: string; + name?: string; + key?: string; + icon?: string; + index?: number; + appName?: string; + status?: string; + sort?: string; + order?: string; + activityName?: string; + activityType?: string; + completedDate?: Date; + elementId?: string; + executionId?: string; + processDefinitionId?: string; + processDefinitionKey?: string; + processDefinitionVersion?: number; + processInstanceId?: string; + serviceTaskId?: string; + serviceFullName?: string; + serviceName?: string; + serviceVersion?: string; + startedDate?: Date; +} + +export enum TaskType { + UserTask = 'userTask', + ServiceTask = 'serviceTask' +} + export class FilterParamsModel { id?: string; @@ -137,7 +169,7 @@ export class TaskFilterAction { actionType: string; icon: string; tooltip: string; - filter: TaskFilterCloudModel; + filter: TaskFilterCloudModel | ServiceTaskFilterCloudModel; constructor(obj?: any) { if (obj) { diff --git a/lib/process-services-cloud/src/lib/task/task-filters/public-api.ts b/lib/process-services-cloud/src/lib/task/task-filters/public-api.ts index c19bbea7f8..0712740a68 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/public-api.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/public-api.ts @@ -16,7 +16,8 @@ */ export * from './components/task-filters-cloud.component'; -export * from './components/edit-task-filter-cloud.component'; +export * from './components/edit-task-filters/edit-task-filter-cloud.component'; +export * from './components/edit-task-filters/edit-service-task-filter-cloud.component'; export * from './models/filter-cloud.model'; diff --git a/lib/process-services-cloud/src/lib/task/task-filters/services/service-task-filter-cloud.service.ts b/lib/process-services-cloud/src/lib/task/task-filters/services/service-task-filter-cloud.service.ts new file mode 100644 index 0000000000..71433cb029 --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-filters/services/service-task-filter-cloud.service.ts @@ -0,0 +1,295 @@ +/*! + * @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 { IdentityUserService } from '@alfresco/adf-core'; +import { Injectable, Inject } from '@angular/core'; +import { Observable, of, BehaviorSubject } from 'rxjs'; +import { ServiceTaskFilterCloudModel } from '../models/filter-cloud.model'; +import { switchMap, map } from 'rxjs/operators'; +import { PreferenceCloudServiceInterface } from '../../../services/preference-cloud.interface'; +import { TASK_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service'; + +@Injectable({ + providedIn: 'root' +}) +export class ServiceTaskFilterCloudService { + private filtersSubject: BehaviorSubject; + filters$: Observable; + + constructor( + private identityUserService: IdentityUserService, + @Inject(TASK_FILTERS_SERVICE_TOKEN) + public preferenceService: PreferenceCloudServiceInterface + ) { + this.filtersSubject = new BehaviorSubject([]); + this.filters$ = this.filtersSubject.asObservable(); + } + + /** + * Creates and returns the default task filters for an app. + * @param appName Name of the target app + * @returns Observable of default filters task filters just created or created filters + */ + private createDefaultFilters(appName: string) { + const key: string = this.prepareKey(appName); + this.preferenceService.getPreferences(appName, key).pipe( + switchMap((response: any) => { + const preferences = (response && response.list && response.list.entries) ? response.list.entries : []; + if (!this.hasPreferences(preferences) || !this.hasTaskFilters(preferences, key)) { + return this.createTaskFilters(appName, key, this.defaultServiceTaskFilters(appName)); + } else { + return of(this.findFiltersByKeyInPreferences(preferences, key)); + } + }) + ).subscribe((filters) => { + this.addFiltersToStream(filters); + }); + } + + /** + * Checks user preference are empty or not + * @param preferences User preferences of the target app + * @returns Boolean value if the preferences are not empty + */ + private hasPreferences(preferences: any): boolean { + return preferences && preferences.length > 0; + } + + /** + * Checks for task filters in given user preferences + * @param preferences User preferences of the target app + * @param key Key of the task filters + * @param filters Details of create filter + * @returns Boolean value if the preference has task filters + */ + private hasTaskFilters(preferences: any, key: string): boolean { + const filters = preferences.find((filter: any) => { return filter.entry.key === key; }); + return (filters && filters.entry) ? JSON.parse(filters.entry.value).length > 0 : false; + } + + /** + * Calls create preference api to create task filters + * @param appName Name of the target app + * @param key Key of the task instance filters + * @param filters Details of new task filter + * @returns Observable of created task filters + */ + private createTaskFilters(appName: string, key: string, filters: ServiceTaskFilterCloudModel[]): Observable { + return this.preferenceService.createPreference(appName, key, filters); + } + + /** + * Calls get preference api to get task filter by preference key + * @param appName Name of the target app + * @param key Key of the task filters + * @returns Observable of task filters + */ + private getTaskFiltersByKey(appName: string, key: string): Observable { + return this.preferenceService.getPreferenceByKey(appName, key); + } + + /** + * Gets all task filters for a task app. + * @param appName Name of the target app + * @returns Observable of task filter details + */ + getTaskListFilters(appName?: string): Observable { + this.createDefaultFilters(appName); + return this.filters$; + } + + /** + * Gets a task filter. + * @param appName Name of the target app + * @param id ID of the task + * @returns Details of the task filter + */ + getTaskFilterById(appName: string, id: string): Observable { + const key: string = this.prepareKey(appName); + return this.getTaskFiltersByKey(appName, key).pipe( + switchMap((filters: ServiceTaskFilterCloudModel[]) => { + if (filters && filters.length === 0) { + return this.createTaskFilters(appName, key, this.defaultServiceTaskFilters(appName)); + } else { + return of(filters); + } + }), + map((filters: any) => { + return filters.filter((filter: ServiceTaskFilterCloudModel) => { + return filter.id === id; + })[0]; + }) + ); + } + + /** + * Adds a new task filter. + * @param filter The new filter to add + * @returns Observable of task instance filters with newly added filter + */ + addFilter(newFilter: ServiceTaskFilterCloudModel): Observable { + const key: string = this.prepareKey(newFilter.appName); + return this.getTaskFiltersByKey(newFilter.appName, key).pipe( + switchMap((filters: ServiceTaskFilterCloudModel[]) => { + if (filters && filters.length === 0) { + return this.createTaskFilters(newFilter.appName, key, [newFilter]); + } else { + filters.push(newFilter); + return this.preferenceService.updatePreference(newFilter.appName, key, filters); + } + }), + map((filters: ServiceTaskFilterCloudModel[]) => { + this.addFiltersToStream(filters); + return filters; + }) + ); + } + + private addFiltersToStream(filters: ServiceTaskFilterCloudModel[]) { + this.filtersSubject.next(filters); + } + + /** + * Updates a task filter. + * @param filter The filter to update + * @returns Observable of task instance filters with updated filter + */ + updateFilter(updatedFilter: ServiceTaskFilterCloudModel): Observable { + const key: string = this.prepareKey(updatedFilter.appName); + return this.getTaskFiltersByKey(updatedFilter.appName, key).pipe( + switchMap((filters: ServiceTaskFilterCloudModel[]) => { + if (filters && filters.length === 0) { + return this.createTaskFilters(updatedFilter.appName, key, [updatedFilter]); + } else { + const itemIndex = filters.findIndex((filter: ServiceTaskFilterCloudModel) => filter.id === updatedFilter.id); + filters[itemIndex] = updatedFilter; + return this.updateTaskFilters(updatedFilter.appName, key, filters); + } + }), + map((updatedFilters: ServiceTaskFilterCloudModel[]) => { + this.addFiltersToStream(updatedFilters); + return updatedFilters; + }) + ); + } + + /** + * Deletes a task filter + * @param filter The filter to delete + * @returns Observable of task instance filters without deleted filter + */ + deleteFilter(deletedFilter: ServiceTaskFilterCloudModel): Observable { + const key = this.prepareKey(deletedFilter.appName); + return this.getTaskFiltersByKey(deletedFilter.appName, key).pipe( + switchMap((filters: ServiceTaskFilterCloudModel[]) => { + if (filters && filters.length > 0) { + filters = filters.filter(filter => filter.id !== deletedFilter.id); + return this.updateTaskFilters(deletedFilter.appName, key, filters); + } + return of([]); + }), + map(filters => { + this.addFiltersToStream(filters); + return filters; + }) + ); + } + + /** + * Checks if given filter is a default filter + * @param filterName Name of the target task filter + * @returns Boolean value for whether the filter is a default filter + */ + isDefaultFilter(filterName: string): boolean { + const defaultFilters = this.defaultServiceTaskFilters(); + return defaultFilters.findIndex((filter) => filterName === filter.name) !== -1; + } + + /** + * Calls update preference api to update task filter + * @param appName Name of the target app + * @param key Key of the task filters + * @param filters Details of update filter + * @returns Observable of updated task filters + */ + private updateTaskFilters(appName: string, key: string, filters: ServiceTaskFilterCloudModel[]): Observable { + return this.preferenceService.updatePreference(appName, key, filters); + } + + /** + * Creates a uniq key with appName and username + * @param appName Name of the target app + * @returns String of task filters preference key + */ + private prepareKey(appName: string): string { + return `task-filters-${appName}-${this.identityUserService.getCurrentUserInfo().username}`; + } + + /** + * Finds and returns the task filters from preferences + * @param appName Name of the target app + * @returns Array of TaskFilterCloudModel + */ + private findFiltersByKeyInPreferences(preferences: any, key: string): ServiceTaskFilterCloudModel[] { + const result = preferences.find((filter: any) => { return filter.entry.key === key; }); + return result && result.entry ? JSON.parse(result.entry.value) : []; + } + + /** + * Creates and returns the default filters for a task app. + * @param appName Name of the target app + * @returns Array of TaskFilterCloudModel + */ + private defaultServiceTaskFilters(appName?: string): ServiceTaskFilterCloudModel[] { + return [ + { + id: this.generateRandomId(), + name: 'ADF_CLOUD_SERVICE_TASK_FILTERS.ALL_SERVICE_TASKS', + key: 'my-service-tasks', + icon: 'inbox', + appName, + status: 'ALL', + sort: 'startedDate', + order: 'DESC' + } as ServiceTaskFilterCloudModel, + { + id: this.generateRandomId(), + name: 'ADF_CLOUD_SERVICE_TASK_FILTERS.COMPLETED_TASKS', + key: 'completed-tasks', + icon: 'done', + appName, + status: 'COMPLETED', + sort: 'completedDate', + order: 'DESC' + } as ServiceTaskFilterCloudModel, + { + id: this.generateRandomId(), + name: 'ADF_CLOUD_SERVICE_TASK_FILTERS.ERRORED_TASKS', + key: 'errored-service-tasks', + icon: 'error', + appName, + status: 'ERROR', + sort: 'startedDate', + order: 'DESC' + } as ServiceTaskFilterCloudModel + ]; + } + + generateRandomId(): string { + return Math.random().toString(36).substr(2, 9); + } +} diff --git a/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.spec.ts b/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.spec.ts index c4bf692ab0..72d5a9fcd2 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.spec.ts @@ -38,6 +38,7 @@ import { import { UserPreferenceCloudService } from '../../../services/user-preference-cloud.service'; import { PreferenceCloudServiceInterface } from '../../../services/preference-cloud.interface'; import { HttpClientTestingModule } from '@angular/common/http/testing'; +import { TaskFilterCloudModel } from '../models/filter-cloud.model'; describe('TaskFilterCloudService', () => { let service: TaskFilterCloudService; @@ -257,7 +258,7 @@ describe('Inject [LocalPreferenceCloudService] into the TaskFilterCloudService', it('should create default task filters if there are no task filter preferences', (done) => { const appName = 'fakeAppName'; - service.getTaskListFilters(appName).subscribe((res) => { + service.getTaskListFilters(appName).subscribe((res: TaskFilterCloudModel[]) => { expect(res.length).toEqual(3); expect(res[0].name).toEqual('ADF_CLOUD_TASK_FILTERS.MY_TASKS'); diff --git a/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts b/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts index e680365539..da2ed40748 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/services/task-filter-cloud.service.ts @@ -17,9 +17,9 @@ import { IdentityUserService } from '@alfresco/adf-core'; import { Injectable, Inject } from '@angular/core'; -import { Observable, of, BehaviorSubject, throwError } from 'rxjs'; +import { Observable, of, BehaviorSubject } from 'rxjs'; import { TaskFilterCloudModel } from '../models/filter-cloud.model'; -import { switchMap, map, catchError } from 'rxjs/operators'; +import { switchMap, map } from 'rxjs/operators'; import { PreferenceCloudServiceInterface } from '../../../services/preference-cloud.interface'; import { TASK_FILTERS_SERVICE_TOKEN } from '../../../services/cloud-token.service'; @@ -49,15 +49,12 @@ export class TaskFilterCloudService { this.preferenceService.getPreferences(appName, key).pipe( switchMap((response: any) => { const preferences = (response && response.list && response.list.entries) ? response.list.entries : []; - if (!this.hasPreferences(preferences)) { - return this.createTaskFilters(appName, key, this.defaultTaskFilters(appName)); - } else if (!this.hasTaskFilters(preferences, key)) { + if (!this.hasPreferences(preferences) || !this.hasTaskFilters(preferences, key)) { return this.createTaskFilters(appName, key, this.defaultTaskFilters(appName)); } else { return of(this.findFiltersByKeyInPreferences(preferences, key)); } - }), - catchError((err) => this.handleTaskError(err)) + }) ).subscribe((filters) => { this.addFiltersToStream(filters); }); @@ -131,13 +128,12 @@ export class TaskFilterCloudService { return of(filters); } }), - map((filters: TaskFilterCloudModel[]) => { + map((filters: any) => { return filters.filter((filter: TaskFilterCloudModel) => { return filter.id === id; })[0]; - }), - catchError((err) => this.handleTaskError(err)) - ); + }) + ); } /** @@ -148,9 +144,9 @@ export class TaskFilterCloudService { addFilter(newFilter: TaskFilterCloudModel): Observable { const key: string = this.prepareKey(newFilter.appName); return this.getTaskFiltersByKey(newFilter.appName, key).pipe( - switchMap((filters: TaskFilterCloudModel[]) => { + switchMap((filters: any) => { if (filters && filters.length === 0) { - return this.createTaskFilters(newFilter.appName, key, [newFilter]); + return this.createTaskFilters(newFilter.appName, key, [newFilter]); } else { filters.push(newFilter); return this.preferenceService.updatePreference(newFilter.appName, key, filters); @@ -159,8 +155,7 @@ export class TaskFilterCloudService { map((filters: TaskFilterCloudModel[]) => { this.addFiltersToStream(filters); return filters; - }), - catchError((err) => this.handleTaskError(err)) + }) ); } @@ -176,9 +171,9 @@ export class TaskFilterCloudService { updateFilter(updatedFilter: TaskFilterCloudModel): Observable { const key: string = this.prepareKey(updatedFilter.appName); return this.getTaskFiltersByKey(updatedFilter.appName, key).pipe( - switchMap((filters: any) => { + switchMap((filters: TaskFilterCloudModel[]) => { if (filters && filters.length === 0) { - return this.createTaskFilters(updatedFilter.appName, key, [updatedFilter]); + return this.createTaskFilters(updatedFilter.appName, key, [updatedFilter]); } else { const itemIndex = filters.findIndex((filter: TaskFilterCloudModel) => filter.id === updatedFilter.id); filters[itemIndex] = updatedFilter; @@ -188,8 +183,7 @@ export class TaskFilterCloudService { map((updatedFilters: TaskFilterCloudModel[]) => { this.addFiltersToStream(updatedFilters); return updatedFilters; - }), - catchError((err) => this.handleTaskError(err)) + }) ); } @@ -201,7 +195,7 @@ export class TaskFilterCloudService { deleteFilter(deletedFilter: TaskFilterCloudModel): Observable { const key = this.prepareKey(deletedFilter.appName); return this.getTaskFiltersByKey(deletedFilter.appName, key).pipe( - switchMap(filters => { + switchMap((filters: TaskFilterCloudModel[]) => { if (filters && filters.length > 0) { filters = filters.filter(filter => filter.id !== deletedFilter.id); return this.updateTaskFilters(deletedFilter.appName, key, filters); @@ -211,8 +205,7 @@ export class TaskFilterCloudService { map(filters => { this.addFiltersToStream(filters); return filters; - }), - catchError((err) => this.handleTaskError(err)) + }) ); } @@ -237,22 +230,13 @@ export class TaskFilterCloudService { return this.preferenceService.updatePreference(appName, key, filters); } - /** - * Gets the username field from the access token. - * @returns Username string - */ - getUsername(): string { - const user = this.identityUserService.getCurrentUserInfo(); - return user.username; - } - /** * Creates a uniq key with appName and username * @param appName Name of the target app * @returns String of task filters preference key */ private prepareKey(appName: string): string { - return `task-filters-${appName}-${this.getUsername()}`; + return `task-filters-${appName}-${this.identityUserService.getCurrentUserInfo().username}`; } /** @@ -265,10 +249,6 @@ export class TaskFilterCloudService { return result && result.entry ? JSON.parse(result.entry.value) : []; } - private handleTaskError(error: any) { - return throwError(error || 'Server error'); - } - /** * Creates and returns the default filters for a task app. * @param appName Name of the target app @@ -282,7 +262,7 @@ export class TaskFilterCloudService { icon: 'inbox', appName, status: 'ASSIGNED', - assignee: this.getUsername(), + assignee: this.identityUserService.getCurrentUserInfo().username, sort: 'createdDate', order: 'DESC' }), diff --git a/lib/process-services-cloud/src/lib/task/task-filters/task-filters-cloud.module.ts b/lib/process-services-cloud/src/lib/task/task-filters/task-filters-cloud.module.ts index 8016a51076..9d189b2d8a 100644 --- a/lib/process-services-cloud/src/lib/task/task-filters/task-filters-cloud.module.ts +++ b/lib/process-services-cloud/src/lib/task/task-filters/task-filters-cloud.module.ts @@ -23,12 +23,14 @@ import { TaskFiltersCloudComponent } from './components/task-filters-cloud.compo import { MaterialModule } from '../../material.module'; import { CoreModule, MomentDateAdapter, MOMENT_DATE_FORMATS } from '@alfresco/adf-core'; import { HttpClientModule } from '@angular/common/http'; -import { EditTaskFilterCloudComponent } from './components/edit-task-filter-cloud.component'; -import { TaskFilterDialogCloudComponent } from './components/task-filter-dialog-cloud.component'; import { AppListCloudModule } from './../../app/app-list-cloud.module'; import { DateAdapter, MAT_DATE_FORMATS } from '@angular/material/core'; import { ProcessCommonModule } from '../../common/process-common.module'; import { PeopleCloudModule } from '../../people/people-cloud.module'; +import { EditServiceTaskFilterCloudComponent } from './components/edit-task-filters/edit-service-task-filter-cloud.component'; +import { EditTaskFilterCloudComponent } from './components/edit-task-filters/edit-task-filter-cloud.component'; +import { TaskFilterDialogCloudComponent } from './components/task-filter-dialog/task-filter-dialog-cloud.component'; +import { ServiceTaskFiltersCloudComponent } from './components/service-task-filters-cloud.component'; @NgModule({ imports: [ @@ -43,8 +45,19 @@ import { PeopleCloudModule } from '../../people/people-cloud.module'; ProcessCommonModule, PeopleCloudModule ], - declarations: [TaskFiltersCloudComponent, EditTaskFilterCloudComponent, TaskFilterDialogCloudComponent], - exports: [TaskFiltersCloudComponent, EditTaskFilterCloudComponent], + declarations: [ + TaskFiltersCloudComponent, + ServiceTaskFiltersCloudComponent, + EditTaskFilterCloudComponent, + EditServiceTaskFilterCloudComponent, + TaskFilterDialogCloudComponent + ], + exports: [ + TaskFiltersCloudComponent, + ServiceTaskFiltersCloudComponent, + EditTaskFilterCloudComponent, + EditServiceTaskFilterCloudComponent + ], providers: [ { provide: DateAdapter, useClass: MomentDateAdapter }, { provide: MAT_DATE_FORMATS, useValue: MOMENT_DATE_FORMATS } diff --git a/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.html b/lib/process-services-cloud/src/lib/task/task-list/components/base-task-list-cloud.component.html similarity index 100% rename from lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.html rename to lib/process-services-cloud/src/lib/task/task-list/components/base-task-list-cloud.component.html diff --git a/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.scss b/lib/process-services-cloud/src/lib/task/task-list/components/base-task-list-cloud.component.scss similarity index 100% rename from lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.scss rename to lib/process-services-cloud/src/lib/task/task-list/components/base-task-list-cloud.component.scss diff --git a/lib/process-services-cloud/src/lib/task/task-list/components/base-task-list-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-list/components/base-task-list-cloud.component.ts new file mode 100644 index 0000000000..9bd9680a3f --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-list/components/base-task-list-cloud.component.ts @@ -0,0 +1,256 @@ +/*! + * @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 { OnChanges, Input, SimpleChanges, Output, EventEmitter, ContentChild, AfterContentInit, OnDestroy, OnInit, Directive } from '@angular/core'; +import { + AppConfigService, UserPreferencesService, + DataTableSchema, UserPreferenceValues, + PaginatedComponent, PaginationModel, + DataRowEvent, CustomEmptyContentTemplateDirective, DataCellEvent, DataRowActionEvent +} from '@alfresco/adf-core'; +import { taskPresetsCloudDefaultModel } from '../models/task-preset-cloud.model'; +import { TaskQueryCloudRequestModel } from '../models/filter-cloud-model'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { TaskListCloudSortingModel } from '../models/task-list-sorting.model'; +import { takeUntil } from 'rxjs/operators'; + +@Directive() +// tslint:disable-next-line: directive-class-suffix +export abstract class BaseTaskListCloudComponent extends DataTableSchema implements OnChanges, AfterContentInit, PaginatedComponent, OnDestroy, OnInit { + + static ENTRY_PREFIX = 'entry.'; + + @ContentChild(CustomEmptyContentTemplateDirective) + emptyCustomContent: CustomEmptyContentTemplateDirective; + + /** The name of the application. */ + @Input() + appName: string = ''; + + /** + * Row selection mode. Can be none, `single` or `multiple`. For `multiple` mode, + * you can use the Cmd (macOS) or Ctrl (Win) modifier key to toggle selection for + * multiple rows. + */ + @Input() + selectionMode: string = 'single'; // none|single|multiple + + /** Toggles multiple row selection, rendering a checkbox at the beginning of each row. */ + @Input() + multiselect: boolean = false; + + /** Toggles the sticky header mode. */ + @Input() + stickyHeader: boolean = false; + + /** + * Specifies how the table should be sorted. The parameters are for BE sorting. + */ + @Input() + sorting: TaskListCloudSortingModel[]; + + /** Toggles the data actions column. */ + @Input() + showActions: boolean = false; + + /** Position of the actions dropdown menu. Can be "left" or "right". */ + @Input() + actionsPosition: string = 'right'; // left|right + + /** Toggles custom context menu for the component. */ + @Input() + showContextMenu: boolean = false; + + /** Emitted before the context menu is displayed for a row. */ + @Output() + showRowContextMenu = new EventEmitter(); + + /** Emitted before the actions menu is displayed for a row. */ + @Output() + showRowActionsMenu = new EventEmitter(); + + /** Emitted when the user executes a row action. */ + @Output() + executeRowAction = new EventEmitter(); + + /** Emitted when a task in the list is clicked */ + @Output() + rowClick: EventEmitter = new EventEmitter(); + + /** Emitted when rows are selected/unselected */ + @Output() + rowsSelected: EventEmitter = new EventEmitter(); + + /** Emitted when the task list is loaded */ + @Output() + success: EventEmitter = new EventEmitter(); + + /** Emitted when an error occurs. */ + @Output() + error: EventEmitter = new EventEmitter(); + + pagination: BehaviorSubject; + + requestNode: TaskQueryCloudRequestModel; + rows: any[] = []; + size: number; + skipCount: number = 0; + currentInstanceId: any; + isLoading = true; + selectedInstances: any[]; + formattedSorting: any[]; + private defaultSorting = { key: 'startDate', direction: 'desc' }; + + private onDestroy$ = new Subject(); + + constructor(appConfigService: AppConfigService, + private userPreferences: UserPreferencesService, + presetKey: string) { + super(appConfigService, presetKey, taskPresetsCloudDefaultModel); + this.size = userPreferences.paginationSize; + + this.pagination = new BehaviorSubject( { + maxItems: this.size, + skipCount: 0, + totalItems: 0 + }); + + } + + ngOnInit() { + this.userPreferences + .select(UserPreferenceValues.PaginationSize) + .pipe(takeUntil(this.onDestroy$)) + .subscribe(pageSize => this.size = pageSize); + } + + ngOnChanges(changes: SimpleChanges) { + if (changes['sorting']) { + this.formatSorting(changes['sorting'].currentValue); + } + this.reload(); + } + + ngOnDestroy() { + this.onDestroy$.next(true); + this.onDestroy$.complete(); + } + + ngAfterContentInit() { + this.createDatatableSchema(); + } + + reload() { + this.requestNode = this.createRequestNode(); + if (this.requestNode.appName || this.requestNode.appName === '') { + this.load(this.requestNode); + } else { + this.rows = []; + } + } + + isListEmpty(): boolean { + return !this.rows || this.rows.length === 0; + } + + /** + * Resets the pagination values + */ + resetPagination() { + this.skipCount = 0; + this.size = this.userPreferences.paginationSize; + this.pagination.next({ + skipCount: 0, + maxItems: this.size + }); + } + + /** + * Resets the pagination values and + * Reloads the task list + * @param pagination Pagination values to be set + */ + updatePagination(pagination: PaginationModel) { + this.size = pagination.maxItems; + this.skipCount = pagination.skipCount; + this.pagination.next(pagination); + this.reload(); + } + + onSortingChanged(event: CustomEvent) { + this.setSorting(event.detail); + this.formatSorting(this.sorting); + this.reload(); + } + + onRowClick(item: DataRowEvent) { + this.currentInstanceId = item.value.getValue('entry.id'); + this.rowClick.emit(this.currentInstanceId); + } + + onRowSelect(event: CustomEvent) { + this.selectedInstances = [...event.detail.selection]; + this.rowsSelected.emit(this.selectedInstances); + } + + onRowUnselect(event: CustomEvent) { + this.selectedInstances = [...event.detail.selection]; + this.rowsSelected.emit(this.selectedInstances); + } + + onRowKeyUp(event: CustomEvent) { + if (event.detail.keyboardEvent.key === 'Enter') { + event.preventDefault(); + this.currentInstanceId = event.detail.row.getValue('entry.id'); + this.rowClick.emit(this.currentInstanceId); + } + } + + onShowRowActionsMenu(event: DataCellEvent) { + this.showRowActionsMenu.emit(event); + } + + onShowRowContextMenu(event: DataCellEvent) { + this.showRowContextMenu.emit(event); + } + + onExecuteRowAction(row: DataRowActionEvent) { + this.executeRowAction.emit(row); + } + + setSorting(sortDetail) { + const sorting = sortDetail ? { + orderBy: sortDetail.key.replace(BaseTaskListCloudComponent.ENTRY_PREFIX, ''), + direction: sortDetail.direction.toUpperCase() + } : { ... this.defaultSorting }; + this.sorting = [new TaskListCloudSortingModel(sorting)]; + } + + formatSorting(sorting: TaskListCloudSortingModel[]) { + this.formattedSorting = this.isValidSorting(sorting) ? [ + BaseTaskListCloudComponent.ENTRY_PREFIX + sorting[0].orderBy, + sorting[0].direction.toLocaleLowerCase() + ] : null; + } + + isValidSorting(sorting: TaskListCloudSortingModel[]) { + return sorting.length && sorting[0].orderBy && sorting[0].direction; + } + + abstract load(requestNode); + abstract createRequestNode(); +} diff --git a/lib/process-services-cloud/src/lib/task/task-list/components/service-task-list-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-list/components/service-task-list-cloud.component.spec.ts new file mode 100644 index 0000000000..6398fbca62 --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-list/components/service-task-list-cloud.component.spec.ts @@ -0,0 +1,524 @@ +/*! + * @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, SimpleChange, ViewChild } from '@angular/core'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { AppConfigService, setupTestBed, DataRowEvent, ObjectDataRow } from '@alfresco/adf-core'; +import { TaskListCloudService } from '../services/task-list-cloud.service'; +import { ServiceTaskListCloudComponent } from './service-task-list-cloud.component'; +import { fakeServiceTask, fakeCustomSchema } from '../mock/fake-task-response.mock'; +import { of } from 'rxjs'; +import { ProcessServiceCloudTestingModule } from '../../../testing/process-service-cloud.testing.module'; +import { Person } from '@alfresco/js-api'; +import { TranslateModule } from '@ngx-translate/core'; +import { TaskListCloudSortingModel } from '../models/task-list-sorting.model'; +import { skip } from 'rxjs/operators'; + +@Component({ + template: ` + + + + + + ` +}) +class CustomTaskListComponent { + @ViewChild(ServiceTaskListCloudComponent) + taskList: ServiceTaskListCloudComponent; + + getFullName(person: Person): string { + return `${person.firstName} ${person.lastName}`; + } +} +@Component({ + template: ` + + +

+
+
+ ` +}) +class EmptyTemplateComponent { +} +@Component({ + template: ` + + + + + + ` +}) +class CustomCopyContentTaskListComponent { + @ViewChild(ServiceTaskListCloudComponent, { static: true }) + taskList: ServiceTaskListCloudComponent; +} + +describe('ServiceTaskListCloudComponent', () => { + let component: ServiceTaskListCloudComponent; + let fixture: ComponentFixture; + let appConfig: AppConfigService; + let taskListCloudService: TaskListCloudService; + + setupTestBed({ + imports: [ + TranslateModule.forRoot(), + ProcessServiceCloudTestingModule + ], + declarations: [ + EmptyTemplateComponent + ] + }); + + beforeEach(() => { + appConfig = TestBed.inject(AppConfigService); + taskListCloudService = TestBed.inject(TaskListCloudService); + fixture = TestBed.createComponent(ServiceTaskListCloudComponent); + component = fixture.componentInstance; + appConfig.config = Object.assign(appConfig.config, { + 'adf-cloud-service-task-list': { + 'presets': { + 'fakeCustomSchema': [ + { + 'key': 'fakeName', + 'type': 'text', + 'title': 'ADF_CLOUD_TASK_LIST.PROPERTIES.FAKE', + 'sortable': true + }, + { + 'key': 'fakeTaskName', + 'type': 'text', + 'title': 'ADF_CLOUD_TASK_LIST.PROPERTIES.TASK_FAKE', + 'sortable': true + } + ] + } + } + }); + }); + + afterEach(() => { + fixture.destroy(); + }); + + it('should use the default schemaColumn as default', () => { + component.ngAfterContentInit(); + expect(component.columns).toBeDefined(); + expect(component.columns.length).toEqual(3); + }); + + it('should display empty content when process list is empty', () => { + const emptyList = { list: { entries: [] } }; + spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(emptyList)); + + fixture.detectChanges(); + expect(component.isLoading).toBe(true); + let loadingContent = fixture.debugElement.query(By.css('mat-progress-spinner')); + expect(loadingContent.nativeElement).toBeDefined(); + + const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); + component.ngOnChanges({ appName }); + fixture.detectChanges(); + + loadingContent = fixture.debugElement.query(By.css('mat-progress-spinner')); + expect(loadingContent).toBeFalsy(); + + const emptyContent = fixture.debugElement.query(By.css('.adf-empty-content')); + expect(emptyContent.nativeElement).toBeDefined(); + }); + + it('should load spinner and show the content', () => { + spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); + + fixture.detectChanges(); + expect(component.isLoading).toBe(true); + let loadingContent = fixture.debugElement.query(By.css('mat-progress-spinner')); + expect(loadingContent.nativeElement).toBeDefined(); + + component.ngOnChanges({ appName }); + fixture.detectChanges(); + + expect(component.isLoading).toBe(false); + loadingContent = fixture.debugElement.query(By.css('mat-progress-spinner')); + expect(loadingContent).toBeFalsy(); + + const emptyContent = fixture.debugElement.query(By.css('.adf-empty-content')); + expect(emptyContent).toBeFalsy(); + + expect(component.rows.length).toEqual(1); + }); + + it('should use the custom schemaColumn from app.config.json', () => { + component.presetColumn = 'fakeCustomSchema'; + component.ngAfterContentInit(); + fixture.detectChanges(); + expect(component.columns).toEqual(fakeCustomSchema); + }); + + it('should fetch custom schemaColumn when the input presetColumn is defined', () => { + component.presetColumn = 'fakeCustomSchema'; + fixture.detectChanges(); + expect(component.columns).toBeDefined(); + expect(component.columns.length).toEqual(2); + }); + + it('should return an empty task list when no input parameters are passed', () => { + component.ngAfterContentInit(); + expect(component.rows).toBeDefined(); + expect(component.isListEmpty()).toBeTruthy(); + }); + + it('should return the results if an application name is given', (done) => { + spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); + component.success.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.rows).toBeDefined(); + expect(component.isListEmpty()).not.toBeTruthy(); + expect(component.rows.length).toEqual(1); + expect(component.rows[0].entry['appName']).toBe('simpleapp'); + expect(component.rows[0].entry['activityType']).toBe('serviceTask'); + expect(component.rows[0].entry['id']).toBe('04fdf69f-4ddd-48ab-9563-da776c9b163c'); + expect(component.rows[0].entry['elementId']).toBe('ServiceTask_0lszm0x'); + expect(component.rows[0].entry['executionId']).toBe('2023b099-fced-11ea-b116-62141048995a'); + expect(component.rows[0].entry['startedDate']).toBe('2020-09-22T16:03:37.444+0000'); + expect(component.rows[0].entry['completedDate']).toBe('2020-09-22T16:03:37.482+0000'); + expect(component.rows[0].entry['processDefinitionVersion']).toBe(1); + expect(component.rows[0].entry['processDefinitionId']).toBe('Process_24rkVVSR:1:0db78dcd-fc14-11ea-bce0-62141048995a'); + expect(component.rows[0].entry['processInstanceId']).toBe('2023b097-fced-11ea-b116-62141048995a'); + expect(component.rows[0].entry['status']).toBe('COMPLETED'); + expect(component.rows[0].entry['serviceFullName']).toBe('simpleapp-rb'); + expect(component.rows[0].entry['serviceName']).toBe('simpleapp-rb'); + done(); + }); + component.appName = appName.currentValue; + component.ngOnChanges({ 'appName': appName }); + fixture.detectChanges(); + }); + + it('should reload tasks when reload() is called', (done) => { + component.appName = 'fake'; + spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + component.success.subscribe((res) => { + expect(res).toBeDefined(); + expect(component.rows).toBeDefined(); + expect(component.isListEmpty()).not.toBeTruthy(); + done(); + }); + fixture.detectChanges(); + component.reload(); + }); + + it('should emit row click event', (done) => { + const row = new ObjectDataRow({ + entry: { + id: '999' + } + }); + const rowEvent = new DataRowEvent(row, null); + component.rowClick.subscribe((taskId) => { + expect(taskId).toEqual('999'); + expect(component.currentInstanceId).toEqual('999'); + done(); + }); + component.onRowClick(rowEvent); + }); + + describe('component changes', () => { + + beforeEach(() => { + component.rows = fakeServiceTask.list.entries; + fixture.detectChanges(); + }); + + it('should NOT reload the task list when no parameters changed', () => { + spyOn(taskListCloudService, 'getTaskByRequest'); + component.rows = null; + fixture.detectChanges(); + expect(component.isListEmpty()).toBeTruthy(); + }); + + it('should reload the task list when input parameters changed', () => { + const getServiceTaskByRequestSpy = spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + component.appName = 'mock-app-name'; + component.queryParams.status = 'mock-status'; + const queryParams = new SimpleChange(undefined, { status: 'mock-status' }, true); + component.ngOnChanges({ + 'queryParams': queryParams + }); + fixture.detectChanges(); + expect(component.isListEmpty()).toBeFalsy(); + expect(getServiceTaskByRequestSpy).toHaveBeenCalled(); + }); + + it('should set formattedSorting if sorting input changes', () => { + spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + spyOn(component, 'formatSorting').and.callThrough(); + + component.appName = 'mock-app-name'; + const mockSort = [ + new TaskListCloudSortingModel({ + orderBy: 'startDate', + direction: 'DESC' + }) + ]; + const sortChange = new SimpleChange(undefined, mockSort, true); + component.ngOnChanges({ + 'sorting': sortChange + }); + fixture.detectChanges(); + expect(component.formatSorting).toHaveBeenCalledWith(mockSort); + expect(component.formattedSorting).toEqual([ServiceTaskListCloudComponent.ENTRY_PREFIX + 'startDate', 'desc']); + }); + + it('should reload task list when sorting on a column changes', () => { + const getServiceTaskByRequestSpy = spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + component.onSortingChanged(new CustomEvent('sorting-changed', { + detail: { + key: 'fakeName', + direction: 'asc' + }, + bubbles: true + })); + fixture.detectChanges(); + expect(component.sorting).toEqual([ + new TaskListCloudSortingModel({ + orderBy: 'fakeName', + direction: 'ASC' + }) + ]); + expect(component.formattedSorting).toEqual(['entry.fakeName', 'asc']); + expect(component.isListEmpty()).toBeFalsy(); + expect(getServiceTaskByRequestSpy).toHaveBeenCalled(); + }); + + it('should reset pagination when resetPaginationValues is called', async (done) => { + spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + + const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); + component.ngOnChanges({ appName }); + fixture.detectChanges(); + + const size = component.size; + const skipCount = component.skipCount; + component.pagination.pipe(skip(3)) + .subscribe((updatedPagination) => { + fixture.detectChanges(); + expect(component.size).toBe(size); + expect(component.skipCount).toBe(skipCount); + expect(updatedPagination.maxItems).toEqual(size); + expect(updatedPagination.skipCount).toEqual(skipCount); + done(); + }); + + const pagination = { + maxItems: 250, + skipCount: 200 + }; + component.updatePagination(pagination); + await fixture.whenStable(); + component.resetPagination(); + }); + + it('should set pagination and reload when updatePagination is called', (done) => { + spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + spyOn(component, 'reload').and.stub(); + const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); + component.ngOnChanges({ appName }); + fixture.detectChanges(); + + const pagination = { + maxItems: 250, + skipCount: 200 + }; + component.pagination.pipe(skip(1)) + .subscribe((updatedPagination) => { + fixture.detectChanges(); + expect(component.size).toBe(pagination.maxItems); + expect(component.skipCount).toBe(pagination.skipCount); + expect(updatedPagination.maxItems).toEqual(pagination.maxItems); + expect(updatedPagination.skipCount).toEqual(pagination.skipCount); + done(); + }); + + component.updatePagination(pagination); + }); + }); + + describe('Injecting custom colums for tasklist - CustomTaskListComponent', () => { + let fixtureCustom: ComponentFixture; + let componentCustom: CustomTaskListComponent; + let customCopyComponent: CustomCopyContentTaskListComponent; + let element: any; + let copyFixture: ComponentFixture; + + setupTestBed({ + imports: [ + TranslateModule.forRoot(), + ProcessServiceCloudTestingModule + ], + declarations: [ + CustomTaskListComponent, + CustomCopyContentTaskListComponent + ] + }); + + beforeEach(() => { + spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + fixtureCustom = TestBed.createComponent(CustomTaskListComponent); + copyFixture = TestBed.createComponent(CustomCopyContentTaskListComponent); + fixtureCustom.detectChanges(); + componentCustom = fixtureCustom.componentInstance; + customCopyComponent = copyFixture.componentInstance; + element = copyFixture.debugElement.nativeElement; + }); + + afterEach(() => { + fixtureCustom.destroy(); + copyFixture.destroy(); + }); + + it('should fetch custom schemaColumn from html', () => { + fixture.detectChanges(); + expect(componentCustom.taskList.columnList).toBeDefined(); + expect(componentCustom.taskList.columns[0]['title']).toEqual('ADF_CLOUD_TASK_LIST.PROPERTIES.NAME'); + expect(componentCustom.taskList.columns[1]['title']).toEqual('ADF_CLOUD_TASK_LIST.PROPERTIES.CREATED'); + expect(componentCustom.taskList.columns.length).toEqual(2); + }); + + it('it should show copy tooltip when key is present in data-colunn', async(() => { + copyFixture.detectChanges(); + const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); + copyFixture.whenStable().then(() => { + copyFixture.detectChanges(); + const spanHTMLElement: HTMLInputElement = element.querySelector('span[title="04fdf69f-4ddd-48ab-9563-da776c9b163c"]'); + spanHTMLElement.dispatchEvent(new Event('mouseenter')); + copyFixture.detectChanges(); + expect(copyFixture.debugElement.nativeElement.querySelector('.adf-copy-tooltip')).not.toBeNull(); + }); + customCopyComponent.taskList.appName = appName.currentValue; + customCopyComponent.taskList.ngOnChanges({ 'appName': appName }); + copyFixture.detectChanges(); + })); + + it('it should not show copy tooltip when key is not present in data-column', (done) => { + const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); + customCopyComponent.taskList.success.subscribe(() => { + copyFixture.whenStable().then(() => { + copyFixture.detectChanges(); + const spanHTMLElement: HTMLInputElement = element.querySelector('span[title="serviceTaskName"]'); + spanHTMLElement.dispatchEvent(new Event('mouseenter')); + copyFixture.detectChanges(); + expect(copyFixture.debugElement.nativeElement.querySelector('.adf-copy-tooltip')).toBeNull(); + done(); + }); + }); + customCopyComponent.taskList.appName = appName.currentValue; + customCopyComponent.taskList.ngOnChanges({ 'appName': appName }); + copyFixture.detectChanges(); + }); + }); + + describe('Copy cell content directive from app.config specifications', () => { + + let element: any; + let taskSpy: jasmine.Spy; + + setupTestBed({ + imports: [ + TranslateModule.forRoot(), + ProcessServiceCloudTestingModule + ] + }); + + beforeEach(() => { + appConfig = TestBed.inject(AppConfigService); + taskListCloudService = TestBed.inject(TaskListCloudService); + appConfig.config = Object.assign(appConfig.config, { + 'adf-cloud-service-task-list': { + 'presets': { + 'fakeCustomSchema': [ + { + 'key': 'entry.id', + 'type': 'text', + 'title': 'ADF_CLOUD_TASK_LIST.PROPERTIES.FAKE', + 'sortable': true, + 'copyContent': true + }, + { + 'key': 'entry.activityName', + 'type': 'text', + 'title': 'ADF_CLOUD_TASK_LIST.PROPERTIES.TASK_FAKE', + 'sortable': true + } + ] + } + } + }); + fixture = TestBed.createComponent(ServiceTaskListCloudComponent); + component = fixture.componentInstance; + element = fixture.debugElement.nativeElement; + taskSpy = spyOn(taskListCloudService, 'getServiceTaskByRequest').and.returnValue(of(fakeServiceTask)); + + }); + afterEach(() => { + fixture.destroy(); + }); + + it('shoud show tooltip if config copyContent flag is true', async(() => { + taskSpy.and.returnValue(of(fakeServiceTask)); + const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); + + component.success.subscribe(() => { + fixture.whenStable().then(() => { + fixture.detectChanges(); + const spanHTMLElement: HTMLInputElement = element.querySelector('span[title="04fdf69f-4ddd-48ab-9563-da776c9b163c"]'); + spanHTMLElement.dispatchEvent(new Event('mouseenter')); + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement.querySelector('.adf-copy-tooltip')).not.toBeNull(); + }); + }); + + component.presetColumn = 'fakeCustomSchema'; + component.appName = appName.currentValue; + component.ngOnChanges({ 'appName': appName }); + component.ngAfterContentInit(); + })); + + it('shoud not show tooltip if config copyContent flag is true', async(() => { + taskSpy.and.returnValue(of(fakeServiceTask)); + const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); + component.success.subscribe(() => { + fixture.whenStable().then(() => { + fixture.detectChanges(); + const spanHTMLElement: HTMLInputElement = element.querySelector('span[title="serviceTaskName"]'); + spanHTMLElement.dispatchEvent(new Event('mouseenter')); + fixture.detectChanges(); + expect(fixture.debugElement.nativeElement.querySelector('.adf-copy-tooltip')).toBeNull(); + }); + }); + component.presetColumn = 'fakeCustomSchema'; + component.appName = appName.currentValue; + component.ngOnChanges({ 'appName': appName }); + component.ngAfterContentInit(); + })); + }); +}); diff --git a/lib/process-services-cloud/src/lib/task/task-list/components/service-task-list-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-list/components/service-task-list-cloud.component.ts new file mode 100644 index 0000000000..d944162088 --- /dev/null +++ b/lib/process-services-cloud/src/lib/task/task-list/components/service-task-list-cloud.component.ts @@ -0,0 +1,69 @@ +/*! + * @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, ViewEncapsulation, Input } from '@angular/core'; +import { + AppConfigService, UserPreferencesService +} from '@alfresco/adf-core'; +import { ServiceTaskQueryCloudRequestModel } from '../models/filter-cloud-model'; +import { TaskListCloudService } from '../services/task-list-cloud.service'; +import { BaseTaskListCloudComponent } from './base-task-list-cloud.component'; + +@Component({ + selector: 'adf-cloud-service-task-list', + templateUrl: './base-task-list-cloud.component.html', + styleUrls: ['./base-task-list-cloud.component.scss'], + encapsulation: ViewEncapsulation.None +}) +export class ServiceTaskListCloudComponent extends BaseTaskListCloudComponent { + + static PRESET_KEY = 'adf-cloud-service-task-list.presets'; + + @Input() + queryParams: { [key: string]: string } = {}; + + constructor(private taskListCloudService: TaskListCloudService, + appConfigService: AppConfigService, + userPreferences: UserPreferencesService) { + super(appConfigService, userPreferences, ServiceTaskListCloudComponent.PRESET_KEY); + } + + load(requestNode: ServiceTaskQueryCloudRequestModel) { + this.isLoading = true; + this.taskListCloudService.getServiceTaskByRequest(requestNode).subscribe( + (tasks) => { + this.rows = tasks.list.entries; + this.success.emit(tasks); + this.isLoading = false; + this.pagination.next(tasks.list.pagination); + }, (error) => { + this.error.emit(error); + this.isLoading = false; + }); + } + + createRequestNode(): ServiceTaskQueryCloudRequestModel { + const requestNode = { + ...this.queryParams, + appName: this.appName, + maxItems: this.size, + skipCount: this.skipCount, + sorting: this.sorting + }; + return requestNode; + } +} diff --git a/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.spec.ts b/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.spec.ts index 655f0a261f..5cd91b4a9f 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.spec.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.spec.ts @@ -50,7 +50,7 @@ class CustomTaskListComponent { getFullName(person: Person): string { return `${person.firstName} ${person.lastName}`; } - } +} @Component({ template: ` @@ -130,7 +130,7 @@ describe('TaskListCloudComponent', () => { }); it('should display empty content when process list is empty', () => { - const emptyList = {list: {entries: []}}; + const emptyList = { list: { entries: [] } }; spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(emptyList)); fixture.detectChanges(); @@ -248,7 +248,7 @@ describe('TaskListCloudComponent', () => { const rowEvent = new DataRowEvent(row, null); component.rowClick.subscribe((taskId) => { expect(taskId).toEqual('999'); - expect(component.getCurrentId()).toEqual('999'); + expect(component.currentInstanceId).toEqual('999'); done(); }); component.onRowClick(rowEvent); @@ -262,8 +262,8 @@ describe('TaskListCloudComponent', () => { }); it('should NOT reload the task list when no parameters changed', () => { + spyOn(taskListCloudService, 'getTaskByRequest'); component.rows = null; - component.ngOnChanges({}); fixture.detectChanges(); expect(component.isListEmpty()).toBeTruthy(); }); @@ -341,14 +341,14 @@ describe('TaskListCloudComponent', () => { const size = component.size; const skipCount = component.skipCount; component.pagination.pipe(skip(3)) - .subscribe((updatedPagination) => { + .subscribe((updatedPagination) => { fixture.detectChanges(); expect(component.size).toBe(size); expect(component.skipCount).toBe(skipCount); expect(updatedPagination.maxItems).toEqual(size); expect(updatedPagination.skipCount).toEqual(skipCount); done(); - }); + }); const pagination = { maxItems: 250, @@ -371,14 +371,14 @@ describe('TaskListCloudComponent', () => { skipCount: 200 }; component.pagination.pipe(skip(1)) - .subscribe((updatedPagination) => { + .subscribe((updatedPagination) => { fixture.detectChanges(); expect(component.size).toBe(pagination.maxItems); expect(component.skipCount).toBe(pagination.skipCount); expect(updatedPagination.maxItems).toEqual(pagination.maxItems); expect(updatedPagination.skipCount).toEqual(pagination.skipCount); done(); - }); + }); component.updatePagination(pagination); }); @@ -442,7 +442,7 @@ describe('TaskListCloudComponent', () => { it('it should not show copy tooltip when key is not present in data-column', (done) => { const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); - customCopyComponent.taskList.success.subscribe( () => { + customCopyComponent.taskList.success.subscribe(() => { copyFixture.whenStable().then(() => { copyFixture.detectChanges(); const spanHTMLElement: HTMLInputElement = element.querySelector('span[title="standalone-subtask"]'); @@ -456,13 +456,13 @@ describe('TaskListCloudComponent', () => { customCopyComponent.taskList.ngOnChanges({ 'appName': appName }); copyFixture.detectChanges(); }); - }); + }); describe('Creating an empty custom template - EmptyTemplateComponent', () => { let fixtureEmpty: ComponentFixture; beforeEach(() => { - const emptyList = {list: {entries: []}}; + const emptyList = { list: { entries: [] } }; spyOn(taskListCloudService, 'getTaskByRequest').and.returnValue(of(emptyList)); fixtureEmpty = TestBed.createComponent(EmptyTemplateComponent); @@ -498,7 +498,7 @@ describe('TaskListCloudComponent', () => { ] }); - beforeEach( () => { + beforeEach(() => { appConfig = TestBed.inject(AppConfigService); taskListCloudService = TestBed.inject(TaskListCloudService); appConfig.config = Object.assign(appConfig.config, { @@ -536,7 +536,7 @@ describe('TaskListCloudComponent', () => { taskSpy.and.returnValue(of(fakeGlobalTask)); const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); - component.success.subscribe( () => { + component.success.subscribe(() => { fixture.whenStable().then(() => { fixture.detectChanges(); const spanHTMLElement: HTMLInputElement = element.querySelector('span[title="11fe013d-c263-11e8-b75b-0a5864600540"]'); @@ -555,7 +555,7 @@ describe('TaskListCloudComponent', () => { it('shoud not show tooltip if config copyContent flag is true', async(() => { taskSpy.and.returnValue(of(fakeGlobalTask)); const appName = new SimpleChange(null, 'FAKE-APP-NAME', true); - component.success.subscribe( () => { + component.success.subscribe(() => { fixture.whenStable().then(() => { fixture.detectChanges(); const spanHTMLElement: HTMLInputElement = element.querySelector('span[title="standalone-subtask"]'); diff --git a/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.ts b/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.ts index 215c890acb..d064ca3abe 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/components/task-list-cloud.component.ts @@ -15,36 +15,21 @@ * limitations under the License. */ -import { Component, ViewEncapsulation, OnChanges, Input, SimpleChanges, Output, EventEmitter, ContentChild, AfterContentInit, OnDestroy, OnInit } from '@angular/core'; -import { AppConfigService, UserPreferencesService, - DataTableSchema, UserPreferenceValues, - PaginatedComponent, PaginationModel, - DataRowEvent, CustomEmptyContentTemplateDirective, DataCellEvent, DataRowActionEvent } from '@alfresco/adf-core'; -import { taskPresetsCloudDefaultModel } from '../models/task-preset-cloud.model'; +import { Component, ViewEncapsulation, Input } from '@angular/core'; +import { AppConfigService, UserPreferencesService } from '@alfresco/adf-core'; import { TaskQueryCloudRequestModel } from '../models/filter-cloud-model'; -import { BehaviorSubject, Subject } from 'rxjs'; import { TaskListCloudService } from '../services/task-list-cloud.service'; -import { TaskListCloudSortingModel } from '../models/task-list-sorting.model'; -import { takeUntil } from 'rxjs/operators'; +import { BaseTaskListCloudComponent } from './base-task-list-cloud.component'; @Component({ - selector: 'adf-cloud-task-list', - templateUrl: './task-list-cloud.component.html', - styleUrls: ['./task-list-cloud.component.scss'], - encapsulation: ViewEncapsulation.None + selector: 'adf-cloud-task-list', + templateUrl: './base-task-list-cloud.component.html', + styleUrls: ['./base-task-list-cloud.component.scss'], + encapsulation: ViewEncapsulation.None }) - -export class TaskListCloudComponent extends DataTableSchema implements OnChanges, AfterContentInit, PaginatedComponent, OnDestroy, OnInit { +export class TaskListCloudComponent extends BaseTaskListCloudComponent { static PRESET_KEY = 'adf-cloud-task-list.presets'; - static ENTRY_PREFIX = 'entry.'; - - @ContentChild(CustomEmptyContentTemplateDirective) - emptyCustomContent: CustomEmptyContentTemplateDirective; - - /** The name of the application. */ - @Input() - appName: string = ''; /** * The assignee of the process. Possible values are: "assignee" (the current user is the assignee), @@ -70,7 +55,7 @@ export class TaskListCloudComponent extends DataTableSchema implements OnChanges @Input() lastModifiedTo: string = ''; - /** Filter the tasks. Display only tasks with dueDate greater or equal than the supplied date. */ + /** Filter the tasks. Display only tasks with dueDate greater or equal than the supplied date. */ @Input() dueDateFrom: string = ''; @@ -122,150 +107,15 @@ export class TaskListCloudComponent extends DataTableSchema implements OnChanges @Input() standalone: boolean = false; - /** - * Row selection mode. Can be none, `single` or `multiple`. For `multiple` mode, - * you can use the Cmd (macOS) or Ctrl (Win) modifier key to toggle selection for - * multiple rows. - */ - @Input() - selectionMode: string = 'single'; // none|single|multiple - - /** Toggles multiple row selection, rendering a checkbox at the beginning of each row. */ - @Input() - multiselect: boolean = false; - - /** Toggles the sticky header mode. */ - @Input() - stickyHeader: boolean = false; - - /** - * Specifies how the table should be sorted. The parameters are for BE sorting. - */ - @Input() - sorting: TaskListCloudSortingModel[]; - - /** Toggles the data actions column. */ - @Input() - showActions: boolean = false; - - /** Position of the actions dropdown menu. Can be "left" or "right". */ - @Input() - actionsPosition: string = 'right'; // left|right - - /** Toggles custom context menu for the component. */ - @Input() - showContextMenu: boolean = false; - - /** Emitted before the context menu is displayed for a row. */ - @Output() - showRowContextMenu = new EventEmitter(); - - /** Emitted before the actions menu is displayed for a row. */ - @Output() - showRowActionsMenu = new EventEmitter(); - - /** Emitted when the user executes a row action. */ - @Output() - executeRowAction = new EventEmitter(); - - /** Emitted when a task in the list is clicked */ - @Output() - rowClick: EventEmitter = new EventEmitter(); - - /** Emitted when rows are selected/unselected */ - @Output() - rowsSelected: EventEmitter = new EventEmitter(); - - /** Emitted when the task list is loaded */ - @Output() - success: EventEmitter = new EventEmitter(); - - /** Emitted when an error occurs. */ - @Output() - error: EventEmitter = new EventEmitter(); - - pagination: BehaviorSubject; - - requestNode: TaskQueryCloudRequestModel; - rows: any[] = []; - size: number; - skipCount: number = 0; - currentInstanceId: any; - isLoading = true; - selectedInstances: any[]; - formattedSorting: any[]; - private defaultSorting = { key: 'startDate', direction: 'desc' }; - - private onDestroy$ = new Subject(); - constructor(private taskListCloudService: TaskListCloudService, appConfigService: AppConfigService, - private userPreferences: UserPreferencesService) { - super(appConfigService, TaskListCloudComponent.PRESET_KEY, taskPresetsCloudDefaultModel); - this.size = userPreferences.paginationSize; - - this.pagination = new BehaviorSubject( { - maxItems: this.size, - skipCount: 0, - totalItems: 0 - }); - + userPreferences: UserPreferencesService) { + super(appConfigService, userPreferences, TaskListCloudComponent.PRESET_KEY); } - ngOnInit() { - this.userPreferences - .select(UserPreferenceValues.PaginationSize) - .pipe(takeUntil(this.onDestroy$)) - .subscribe(pageSize => this.size = pageSize); - } - - ngOnChanges(changes: SimpleChanges) { - if (this.isPropertyChanged(changes, 'sorting')) { - this.formatSorting(changes['sorting'].currentValue); - } - if (this.isAnyPropertyChanged(changes)) { - this.reload(); - } - } - - ngOnDestroy() { - this.onDestroy$.next(true); - this.onDestroy$.complete(); - } - - ngAfterContentInit() { - this.createDatatableSchema(); - } - - getCurrentId(): string { - return this.currentInstanceId; - } - - private isAnyPropertyChanged(changes: SimpleChanges): boolean { - for (const property in changes) { - if (this.isPropertyChanged(changes, property)) { - return true; - } - } - return false; - } - - private isPropertyChanged(changes: SimpleChanges, property: string): boolean { - return changes.hasOwnProperty(property); - } - - reload() { - this.requestNode = this.createRequestNode(); - if (this.requestNode.appName || this.requestNode.appName === '') { - this.load(this.requestNode); - } else { - this.rows = []; - } - } - - private load(requestNode: TaskQueryCloudRequestModel) { + load(requestNode: TaskQueryCloudRequestModel) { this.isLoading = true; - this.taskListCloudService.getTaskByRequest(requestNode).subscribe( + this.taskListCloudService.getTaskByRequest( requestNode).subscribe( (tasks) => { this.rows = tasks.list.entries; this.success.emit(tasks); @@ -277,77 +127,7 @@ export class TaskListCloudComponent extends DataTableSchema implements OnChanges }); } - isListEmpty(): boolean { - return !this.rows || this.rows.length === 0; - } - - /** - * Resets the pagination values - */ - resetPagination() { - this.skipCount = 0; - this.size = this.userPreferences.paginationSize; - this.pagination.next({ - skipCount: 0, - maxItems: this.size - }); - } - - /** - * Resets the pagination values and - * Reloads the task list - * @param pagination Pagination values to be set - */ - updatePagination(pagination: PaginationModel) { - this.size = pagination.maxItems; - this.skipCount = pagination.skipCount; - this.pagination.next(pagination); - this.reload(); - } - - onSortingChanged(event: CustomEvent) { - this.setSorting(event.detail); - this.formatSorting(this.sorting); - this.reload(); - } - - onRowClick(item: DataRowEvent) { - this.currentInstanceId = item.value.getValue('entry.id'); - this.rowClick.emit(this.currentInstanceId); - } - - onRowSelect(event: CustomEvent) { - this.selectedInstances = [...event.detail.selection]; - this.rowsSelected.emit(this.selectedInstances); - } - - onRowUnselect(event: CustomEvent) { - this.selectedInstances = [...event.detail.selection]; - this.rowsSelected.emit(this.selectedInstances); - } - - onRowKeyUp(event: CustomEvent) { - if (event.detail.keyboardEvent.key === 'Enter') { - event.preventDefault(); - this.currentInstanceId = event.detail.row.getValue('entry.id'); - this.rowClick.emit(this.currentInstanceId); - } - } - - onShowRowActionsMenu(event: DataCellEvent) { - this.showRowActionsMenu.emit(event); - } - - onShowRowContextMenu(event: DataCellEvent) { - this.showRowContextMenu.emit(event); - } - - onExecuteRowAction(row: DataRowActionEvent) { - this.executeRowAction.emit(row); - } - - private createRequestNode() { - + createRequestNode(): TaskQueryCloudRequestModel { const requestNode = { appName: this.appName, assignee: this.assignee, @@ -374,23 +154,4 @@ export class TaskListCloudComponent extends DataTableSchema implements OnChanges }; return new TaskQueryCloudRequestModel(requestNode); } - - setSorting(sortDetail) { - const sorting = sortDetail ? { - orderBy: sortDetail.key.replace(TaskListCloudComponent.ENTRY_PREFIX, ''), - direction: sortDetail.direction.toUpperCase() - } : { ... this.defaultSorting }; - this.sorting = [new TaskListCloudSortingModel(sorting)]; - } - - formatSorting(sorting: TaskListCloudSortingModel[]) { - this.formattedSorting = this.isValidSorting(sorting) ? [ - TaskListCloudComponent.ENTRY_PREFIX + sorting[0].orderBy, - sorting[0].direction.toLocaleLowerCase() - ] : null; - } - - isValidSorting(sorting: TaskListCloudSortingModel[]) { - return sorting.length && sorting[0].orderBy && sorting[0].direction; - } } diff --git a/lib/process-services-cloud/src/lib/task/task-list/mock/fake-task-response.mock.ts b/lib/process-services-cloud/src/lib/task/task-list/mock/fake-task-response.mock.ts index 217591cecb..50e93a6b35 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/mock/fake-task-response.mock.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/mock/fake-task-response.mock.ts @@ -79,7 +79,7 @@ export const fakeTaskCloudList = { } }; -export let fakeGlobalTask = { +export const fakeGlobalTask = { list: { entries: [ { @@ -117,7 +117,41 @@ export let fakeGlobalTask = { } }; -export let fakeCustomSchema = +export const fakeServiceTask = { + list: { + entries: [ + { + entry: { + activityType: 'serviceTask', + activityName: 'serviceTaskName', + appName: 'simpleapp', + completedDate: '2020-09-22T16:03:37.482+0000', + elementId: 'ServiceTask_0lszm0x', + executionId: '2023b099-fced-11ea-b116-62141048995a', + id: '04fdf69f-4ddd-48ab-9563-da776c9b163c', + processDefinitionId: 'Process_24rkVVSR:1:0db78dcd-fc14-11ea-bce0-62141048995a', + processDefinitionKey: 'Process_24rkVVSR', + processDefinitionVersion: 1, + processInstanceId: '2023b097-fced-11ea-b116-62141048995a', + serviceFullName: 'simpleapp-rb', + serviceName: 'simpleapp-rb', + serviceVersion: '', + startedDate: '2020-09-22T16:03:37.444+0000', + status: 'COMPLETED' + } + } + ], + pagination: { + skipCount: 0, + maxItems: 100, + count: 1, + hasMoreItems: false, + totalItems: 1 + } + } +}; + +export const fakeCustomSchema = [ new ObjectDataColumn({ 'key': 'fakeName', diff --git a/lib/process-services-cloud/src/lib/task/task-list/models/filter-cloud-model.ts b/lib/process-services-cloud/src/lib/task/task-list/models/filter-cloud-model.ts index 876d589bf8..f099a0ff77 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/models/filter-cloud-model.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/models/filter-cloud-model.ts @@ -74,3 +74,26 @@ export class TaskQueryCloudRequestModel { } } } + +export interface ServiceTaskQueryCloudRequestModel { + appName: string; + appVersion?: string; + id?: string; + status?: string; + maxItems: number; + skipCount: number; + sorting?: TaskListCloudSortingModel[]; + activityName?: string; + activityType?: string; + completedDate?: Date; + elementId?: string; + executionId?: string; + processDefinitionId?: string; + processDefinitionKey?: string; + processDefinitionVersion?: number; + processInstanceId?: string; + serviceFullName?: string; + serviceName?: string; + serviceVersion?: string; + startedDate?: Date; +} diff --git a/lib/process-services-cloud/src/lib/task/task-list/models/task-preset-cloud.model.ts b/lib/process-services-cloud/src/lib/task/task-list/models/task-preset-cloud.model.ts index 48f0f7babd..d67964e93b 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/models/task-preset-cloud.model.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/models/task-preset-cloud.model.ts @@ -15,7 +15,7 @@ * limitations under the License. */ -export let taskPresetsCloudDefaultModel = { +export const taskPresetsCloudDefaultModel = { 'default': [ { 'key': 'entry.name', @@ -39,3 +39,34 @@ export let taskPresetsCloudDefaultModel = { } ] }; + +export const serviceTaskPresetsCloudDefaultModel = { + 'default': [ + { + 'key': 'entry.activityName', + 'type': 'text', + 'title': 'ADF_CLOUD_SERVICE_TASK_LIST.PROPERTIES.ACTIVITY_NAME', + 'sortable': true + }, + { + 'key': 'entry.status', + 'type': 'text', + 'title': 'ADF_CLOUD_SERVICE_TASK_LIST.PROPERTIES.STATUS', + 'sortable': true + }, + { + 'key': 'entry.startedDate', + 'type': 'text', + 'title': 'ADF_CLOUD_SERVICE_TASK_LIST.PROPERTIES.STARTED_DATE', + 'cssClass': 'hidden', + 'sortable': true + }, + { + 'key': 'entry.completedDate', + 'type': 'text', + 'title': 'ADF_CLOUD_SERVICE_TASK_LIST.PROPERTIES.COMPLETED_DATE', + 'cssClass': 'hidden', + 'sortable': true + } + ] +}; diff --git a/lib/process-services-cloud/src/lib/task/task-list/public-api.ts b/lib/process-services-cloud/src/lib/task/task-list/public-api.ts index cda7578d63..7f17dc60c8 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/public-api.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/public-api.ts @@ -16,6 +16,7 @@ */ export * from './components/task-list-cloud.component'; +export * from './components/service-task-list-cloud.component'; export * from './models/filter-cloud-model'; export * from './models/task-list-sorting.model'; diff --git a/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts b/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts index 809431fc56..a64351ab0b 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/services/task-list-cloud.service.ts @@ -17,7 +17,7 @@ import { Injectable } from '@angular/core'; import { AlfrescoApiService, AppConfigService, LogService } from '@alfresco/adf-core'; -import { TaskQueryCloudRequestModel } from '../models/filter-cloud-model'; +import { TaskQueryCloudRequestModel, ServiceTaskQueryCloudRequestModel } from '../models/filter-cloud-model'; import { Observable, throwError } from 'rxjs'; import { TaskListCloudSortingModel } from '../models/task-list-sorting.model'; import { BaseCloudService } from '../../../services/base-cloud.service'; @@ -51,6 +51,26 @@ export class TaskListCloudService extends BaseCloudService { } } + /** + * Finds a task using an object with optional query properties. + * @param requestNode Query object + * @returns Task information + */ + getServiceTaskByRequest(requestNode: ServiceTaskQueryCloudRequestModel): Observable { + if (requestNode.appName || requestNode.appName === '') { + const queryUrl = `${this.getBasePath(requestNode.appName)}/query/admin/v1/service-tasks`; + const queryParams = this.buildQueryParams(requestNode); + const sortingParams = this.buildSortingParam(requestNode.sorting); + if (sortingParams) { + queryParams['sort'] = sortingParams; + } + return this.get(queryUrl, queryParams); + } else { + this.logService.error('Appname is mandatory for querying task'); + return throwError('Appname not configured'); + } + } + private buildQueryParams(requestNode: TaskQueryCloudRequestModel): Object { const queryParam: Object = {}; for (const property in requestNode) { diff --git a/lib/process-services-cloud/src/lib/task/task-list/task-list-cloud.module.ts b/lib/process-services-cloud/src/lib/task/task-list/task-list-cloud.module.ts index 439921c4a6..376207690e 100644 --- a/lib/process-services-cloud/src/lib/task/task-list/task-list-cloud.module.ts +++ b/lib/process-services-cloud/src/lib/task/task-list/task-list-cloud.module.ts @@ -19,6 +19,7 @@ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MaterialModule } from '../../material.module'; import { TaskListCloudComponent } from './components/task-list-cloud.component'; +import { ServiceTaskListCloudComponent } from './components/service-task-list-cloud.component'; import { CoreModule } from '@alfresco/adf-core'; @NgModule({ @@ -27,7 +28,13 @@ import { CoreModule } from '@alfresco/adf-core'; MaterialModule, CoreModule ], - declarations: [TaskListCloudComponent], - exports: [TaskListCloudComponent] + declarations: [ + TaskListCloudComponent, + ServiceTaskListCloudComponent + ], + exports: [ + TaskListCloudComponent, + ServiceTaskListCloudComponent + ] }) export class TaskListCloudModule { }