diff --git a/rest/taskana-rest-spring-example-common/src/main/java/pro/taskana/rest/ExampleWebSecurityConfig.java b/rest/taskana-rest-spring-example-common/src/main/java/pro/taskana/rest/ExampleWebSecurityConfig.java index 19ebcdbb4..db0606373 100644 --- a/rest/taskana-rest-spring-example-common/src/main/java/pro/taskana/rest/ExampleWebSecurityConfig.java +++ b/rest/taskana-rest-spring-example-common/src/main/java/pro/taskana/rest/ExampleWebSecurityConfig.java @@ -1,8 +1,5 @@ package pro.taskana.rest; -import java.util.List; -import java.util.Map; -import java.util.function.Function; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.web.servlet.FilterRegistrationBean; @@ -21,6 +18,10 @@ import org.springframework.web.filter.CorsFilter; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + @Configuration public class ExampleWebSecurityConfig { @@ -51,7 +52,7 @@ public class ExampleWebSecurityConfig { final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); - config.addAllowedOrigin("*"); + config.addAllowedOriginPattern("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); diff --git a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/assembler/CollectionRepresentationModelAssembler.java b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/assembler/CollectionRepresentationModelAssembler.java index eb5db02f4..d699cf5e8 100644 --- a/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/assembler/CollectionRepresentationModelAssembler.java +++ b/rest/taskana-rest-spring/src/main/java/pro/taskana/common/rest/assembler/CollectionRepresentationModelAssembler.java @@ -3,8 +3,11 @@ package pro.taskana.common.rest.assembler; import java.util.List; import java.util.stream.Collectors; import java.util.stream.StreamSupport; +import org.springframework.hateoas.Link; import org.springframework.hateoas.RepresentationModel; import org.springframework.hateoas.server.RepresentationModelAssembler; +import org.springframework.web.servlet.support.ServletUriComponentsBuilder; +import org.springframework.web.util.UriComponentsBuilder; import pro.taskana.common.rest.models.CollectionRepresentationModel; @@ -17,6 +20,16 @@ public interface CollectionRepresentationModelAssembler< default C toTaskanaCollectionModel(Iterable entities) { return StreamSupport.stream(entities.spliterator(), false) .map(this::toModel) - .collect(Collectors.collectingAndThen(Collectors.toList(), this::buildCollectionEntity)); + .collect( + Collectors.collectingAndThen( + Collectors.toList(), + content -> addLinksToCollectionModel(buildCollectionEntity(content)))); + } + + default C addLinksToCollectionModel(C model) { + final UriComponentsBuilder original = ServletUriComponentsBuilder.fromCurrentRequest(); + + model.add(Link.of(original.toUriString()).withSelfRel()); + return model; } } diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/classification/rest/ClassificationDefinitionControllerIntTest.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/classification/rest/ClassificationDefinitionControllerIntTest.java index 694ca975d..8b3c6dce9 100644 --- a/rest/taskana-rest-spring/src/test/java/pro/taskana/classification/rest/ClassificationDefinitionControllerIntTest.java +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/classification/rest/ClassificationDefinitionControllerIntTest.java @@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.io.FileSystemResource; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Links; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -102,7 +103,7 @@ class ClassificationDefinitionControllerIntTest { ClassificationDefinitionCollectionRepresentationModel.class)); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).isNotNull(); - assertThat(response.getBody().getLinks()).isEmpty(); + assertThat(response.getBody().getLink(IanaLinkRelations.SELF)).isPresent(); assertThat(response.getBody().getContent()) .extracting(ClassificationDefinitionRepresentationModel::getClassification) .extracting(ClassificationRepresentationModel::getLinks) diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/classification/rest/ClassificationDefinitionControllerRestDocTest.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/classification/rest/ClassificationDefinitionControllerRestDocTest.java index b699e7fe5..47a8f68cd 100644 --- a/rest/taskana-rest-spring/src/test/java/pro/taskana/classification/rest/ClassificationDefinitionControllerRestDocTest.java +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/classification/rest/ClassificationDefinitionControllerRestDocTest.java @@ -34,7 +34,7 @@ class ClassificationDefinitionControllerRestDocTest extends BaseRestDocTest { classification.setServiceLevel("P1D"); ClassificationCollectionRepresentationModel importCollection = - assembler.toTaskanaCollectionModel(List.of(classification)); + new ClassificationCollectionRepresentationModel(List.of(assembler.toModel(classification))); this.mockMvc .perform( diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/workbasket/rest/WorkbasketControllerRestDocTest.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/workbasket/rest/WorkbasketControllerRestDocTest.java index 5c98417aa..3e8b97475 100644 --- a/rest/taskana-rest-spring/src/test/java/pro/taskana/workbasket/rest/WorkbasketControllerRestDocTest.java +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/workbasket/rest/WorkbasketControllerRestDocTest.java @@ -103,7 +103,8 @@ class WorkbasketControllerRestDocTest extends BaseRestDocTest { accessItem.setPermission(WorkbasketPermission.OPEN, true); WorkbasketAccessItemCollectionRepresentationModel repModel = - accessItemAssembler.toTaskanaCollectionModel(List.of(accessItem)); + new WorkbasketAccessItemCollectionRepresentationModel( + List.of(accessItemAssembler.toModel(accessItem))); mockMvc .perform( diff --git a/rest/taskana-rest-spring/src/test/java/pro/taskana/workbasket/rest/WorkbasketDefinitionControllerIntTest.java b/rest/taskana-rest-spring/src/test/java/pro/taskana/workbasket/rest/WorkbasketDefinitionControllerIntTest.java index cea2e94dc..92d4c5b4e 100644 --- a/rest/taskana-rest-spring/src/test/java/pro/taskana/workbasket/rest/WorkbasketDefinitionControllerIntTest.java +++ b/rest/taskana-rest-spring/src/test/java/pro/taskana/workbasket/rest/WorkbasketDefinitionControllerIntTest.java @@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.io.FileSystemResource; +import org.springframework.hateoas.IanaLinkRelations; import org.springframework.hateoas.Links; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; @@ -102,7 +103,7 @@ class WorkbasketDefinitionControllerIntTest { executeExportRequestForDomain("DOMAIN_A"); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getBody()).isNotNull(); - assertThat(response.getBody().getLinks()).isEmpty(); + assertThat(response.getBody().getLink(IanaLinkRelations.SELF)).isPresent(); assertThat(response.getBody().getContent()) .extracting(WorkbasketDefinitionRepresentationModel::getWorkbasket) .extracting(WorkbasketRepresentationModel::getLinks) diff --git a/web/angular.json b/web/angular.json index 93eb971fc..13d4d4d8a 100644 --- a/web/angular.json +++ b/web/angular.json @@ -20,7 +20,10 @@ "main": "src/main.ts", "tsConfig": "src/tsconfig.app.json", "polyfills": "src/polyfills.ts", - "assets": ["src/assets", "src/environments/data-sources"], + "assets": [ + "src/assets", + "src/environments/data-sources" + ], "styles": [ "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "./node_modules/bootstrap/dist/css/bootstrap.min.css", @@ -80,7 +83,10 @@ "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { - "tsConfig": ["src/tsconfig.app.json", "src/tsconfig.spec.json"], + "tsConfig": [ + "src/tsconfig.app.json", + "src/tsconfig.spec.json" + ], "exclude": [] } } @@ -95,11 +101,14 @@ "defaultProject": "taskana-web", "schematics": { "@schematics/angular:component": { - "prefix": "app", + "prefix": "taskana", "style": "scss" }, "@schematics/angular:directive": { - "prefix": "app" + "prefix": "taskana" } + }, + "cli": { + "analytics": false } } diff --git a/web/src/app/administration/components/access-items-management/access-items-management.component.html b/web/src/app/administration/components/access-items-management/access-items-management.component.html index 82eb1b5c2..d9351f087 100644 --- a/web/src/app/administration/components/access-items-management/access-items-management.component.html +++ b/web/src/app/administration/components/access-items-management/access-items-management.component.html @@ -51,7 +51,7 @@ + menuPosition="left" [defaultSortBy]="defaultSortBy"> @@ -124,4 +124,4 @@ - \ No newline at end of file + diff --git a/web/src/app/administration/components/access-items-management/access-items-management.component.spec.ts b/web/src/app/administration/components/access-items-management/access-items-management.component.spec.ts index a173e1393..45fec96f7 100644 --- a/web/src/app/administration/components/access-items-management/access-items-management.component.spec.ts +++ b/web/src/app/administration/components/access-items-management/access-items-management.component.spec.ts @@ -1,4 +1,4 @@ -import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { AccessItemsManagementComponent } from './access-items-management.component'; import { FormsValidatorService } from '../../../shared/services/forms-validator/forms-validator.service'; import { Actions, NgxsModule, ofActionDispatched, Store } from '@ngxs/store'; @@ -14,11 +14,11 @@ import { AccessItemsManagementState } from '../../../shared/store/access-items-m import { Observable } from 'rxjs'; import { GetAccessItems } from '../../../shared/store/access-items-management-store/access-items-management.actions'; import { MatSnackBarModule } from '@angular/material/snack-bar'; -import { MatDialogModule, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog'; +import { MatDialogModule, MatDialogRef } from '@angular/material/dialog'; import { TypeAheadComponent } from '../../../shared/components/type-ahead/type-ahead.component'; import { TypeaheadModule } from 'ngx-bootstrap'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { Direction, Sorting } from '../../../shared/models/sorting'; +import { Direction, Sorting, WorkbasketAccessItemQuerySortParameter } from '../../../shared/models/sorting'; import { StartupService } from '../../../shared/services/startup/startup.service'; import { TaskanaEngineService } from '../../../shared/services/taskana-engine/taskana-engine.service'; import { WindowRefService } from '../../../shared/services/window/window.service'; @@ -62,8 +62,9 @@ describe('AccessItemsManagementComponent', () => { @Component({ selector: 'taskana-shared-sort', template: '' }) class TaskanaSharedSortStub { - @Input() sortingFields: Map; - @Output() performSorting = new EventEmitter(); + @Input() sortingFields: Map; + @Input() defaultSortBy: WorkbasketAccessItemQuerySortParameter; + @Output() performSorting = new EventEmitter>(); } beforeEach(async(() => { @@ -159,7 +160,7 @@ describe('AccessItemsManagementComponent', () => { { accessId: '1', name: 'users' }, { accessId: '2', name: 'users' } ]; - app.sortModel = { sortBy: 'access-id', sortDirection: 'desc' }; + app.sortModel = { 'sort-by': WorkbasketAccessItemQuerySortParameter.ACCESS_ID, order: Direction.DESC }; app.searchForAccessItemsWorkbaskets(); fixture.detectChanges(); let actionDispatched = false; @@ -188,7 +189,10 @@ describe('AccessItemsManagementComponent', () => { }); it('should invoke sorting function correctly', () => { - const newSort = new Sorting('access-id', Direction.DESC); + const newSort: Sorting = { + 'sort-by': WorkbasketAccessItemQuerySortParameter.ACCESS_ID, + order: Direction.DESC + }; app.accessId = { accessId: '1', name: 'max' }; app.groups = [{ accessId: '1', name: 'users' }]; app.sorting(newSort); diff --git a/web/src/app/administration/components/access-items-management/access-items-management.component.ts b/web/src/app/administration/components/access-items-management/access-items-management.component.ts index d317ee3a8..fffdcd057 100644 --- a/web/src/app/administration/components/access-items-management/access-items-management.component.ts +++ b/web/src/app/administration/components/access-items-management/access-items-management.component.ts @@ -4,7 +4,12 @@ import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@ang import { Observable, Subject } from 'rxjs'; import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service'; import { WorkbasketAccessItems } from 'app/shared/models/workbasket-access-items'; -import { Direction, Sorting } from 'app/shared/models/sorting'; +import { + Direction, + Sorting, + WORKBASKET_ACCESS_ITEM_SORT_PARAMETER_NAMING, + WorkbasketAccessItemQuerySortParameter +} from 'app/shared/models/sorting'; import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors'; import { takeUntil } from 'rxjs/operators'; import { AccessIdDefinition } from '../../../shared/models/access-id'; @@ -18,6 +23,7 @@ import { } from '../../../shared/store/access-items-management-store/access-items-management.actions'; import { AccessItemsManagementSelector } from '../../../shared/store/access-items-management-store/access-items-management.selector'; import { MatDialog } from '@angular/material/dialog'; +import { WorkbasketAccessItemQueryFilterParameter } from '../../../shared/models/workbasket-access-item-query-filter-parameter'; @Component({ selector: 'taskana-administration-access-items-management', @@ -31,20 +37,19 @@ export class AccessItemsManagementComponent implements OnInit { accessIdName: string; panelState: boolean = false; accessItemsForm: FormGroup; - toggleValidationAccessIdMap = new Map(); accessId: AccessIdDefinition; groups: AccessIdDefinition[]; - sortingFields = new Map([ - ['access-id', 'Access id'], - ['workbasket-key', 'Workbasket Key'] - ]); - sortModel: Sorting = new Sorting('access-id', Direction.DESC); + defaultSortBy: WorkbasketAccessItemQuerySortParameter = WorkbasketAccessItemQuerySortParameter.ACCESS_ID; + sortingFields: Map = WORKBASKET_ACCESS_ITEM_SORT_PARAMETER_NAMING; + sortModel: Sorting = { + 'sort-by': this.defaultSortBy, + order: Direction.DESC + }; accessItems: WorkbasketAccessItems[]; isGroup: boolean = false; - @Select(EngineConfigurationSelectors.accessItemsCustomisation) accessItemsCustomization$: Observable< - AccessItemsCustomisation - >; + @Select(EngineConfigurationSelectors.accessItemsCustomisation) + accessItemsCustomization$: Observable; @Select(AccessItemsManagementSelector.groups) groups$: Observable; customFields$: Observable; destroy$ = new Subject(); @@ -81,15 +86,16 @@ export class AccessItemsManagementComponent implements OnInit { searchForAccessItemsWorkbaskets() { this.removeFocus(); - this.store - .dispatch(new GetAccessItems([this.accessId, ...this.groups], '', '', this.sortModel)) - .subscribe((state) => { - this.setAccessItemsGroups( - state['accessItemsManagement'].accessItemsResource - ? state['accessItemsManagement'].accessItemsResource.accessItems - : [] - ); - }); + const filterParameter: WorkbasketAccessItemQueryFilterParameter = { + 'access-id': [this.accessId, ...this.groups].map((a) => a.accessId) + }; + this.store.dispatch(new GetAccessItems(filterParameter, this.sortModel)).subscribe((state) => { + this.setAccessItemsGroups( + state['accessItemsManagement'].accessItemsResource + ? state['accessItemsManagement'].accessItemsResource.accessItems + : [] + ); + }); } setAccessItemsGroups(accessItems: Array) { @@ -150,7 +156,7 @@ export class AccessItemsManagementComponent implements OnInit { return this.formsValidatorService.isFieldValid(this.accessItemsGroups[index], field); } - sorting(sort: Sorting) { + sorting(sort: Sorting) { this.sortModel = sort; this.searchForAccessItemsWorkbaskets(); } diff --git a/web/src/app/administration/components/classification-details/classification-details.component.html b/web/src/app/administration/components/classification-details/classification-details.component.html index 45d34b528..a482ce89a 100644 --- a/web/src/app/administration/components/classification-details/classification-details.component.html +++ b/web/src/app/administration/components/classification-details/classification-details.component.html @@ -177,12 +177,12 @@ + class="detailed-fields__category-icon" [src]="(getCategoryIcon(this.classification.category) | async)?.left"> {{this.classification.category}} - + {{category}} diff --git a/web/src/app/administration/components/classification-details/classification-details.component.spec.ts b/web/src/app/administration/components/classification-details/classification-details.component.spec.ts index 38a05df20..9e9277d42 100644 --- a/web/src/app/administration/components/classification-details/classification-details.component.spec.ts +++ b/web/src/app/administration/components/classification-details/classification-details.component.spec.ts @@ -185,8 +185,8 @@ describe('ClassificationDetailsComponent', () => { it('should return icon for category when getCategoryIcon() is called and category exists', (done) => { const categoryIcon = component.getCategoryIcon('AUTOMATIC'); categoryIcon.subscribe((iconPair) => { - expect(iconPair.name).toBe('assets/icons/categories/automatic.svg'); - expect(iconPair.text).toBe('AUTOMATIC'); + expect(iconPair.left).toBe('assets/icons/categories/automatic.svg'); + expect(iconPair.right).toBe('AUTOMATIC'); done(); }); }); @@ -194,7 +194,7 @@ describe('ClassificationDetailsComponent', () => { it('should return icon when getCategoryIcon() is called and category does not exist', (done) => { const categoryIcon = component.getCategoryIcon('WATER'); categoryIcon.subscribe((iconPair) => { - expect(iconPair.name).toBe('assets/icons/categories/missing-icon.svg'); + expect(iconPair.left).toBe('assets/icons/categories/missing-icon.svg'); done(); }); }); diff --git a/web/src/app/administration/components/classification-details/classification-details.component.ts b/web/src/app/administration/components/classification-details/classification-details.component.ts index c586b0604..00fd45af6 100644 --- a/web/src/app/administration/components/classification-details/classification-details.component.ts +++ b/web/src/app/administration/components/classification-details/classification-details.component.ts @@ -7,7 +7,6 @@ import { highlight } from 'app/shared/animations/validation.animation'; import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service'; import { DomainService } from 'app/shared/services/domain/domain.service'; -import { Pair } from 'app/shared/models/pair'; import { NgForm } from '@angular/forms'; import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service'; import { ImportExportService } from 'app/administration/services/import-export.service'; @@ -30,6 +29,7 @@ import { CopyClassification, DeselectClassification } from '../../../shared/store/classification-store/classification.actions'; +import { Pair } from '../../../shared/models/pair'; @Component({ selector: 'taskana-administration-classification-details', @@ -137,12 +137,12 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy { this.store.dispatch(new DeselectClassification()); } - getCategoryIcon(category: string): Observable { + getCategoryIcon(category: string): Observable> { return this.categoryIcons$.pipe( map((iconMap) => iconMap[category] - ? new Pair(iconMap[category], category) - : new Pair(iconMap.missing, 'Category does not match with the configuration') + ? { left: iconMap[category], right: category } + : { left: iconMap.missing, right: 'Category does not match with the configuration' } ) ); } diff --git a/web/src/app/administration/components/classification-list/classification-list.component.html b/web/src/app/administration/components/classification-list/classification-list.component.html index b9b5e2467..1f60afa43 100644 --- a/web/src/app/administration/components/classification-list/classification-list.component.html +++ b/web/src/app/administration/components/classification-list/classification-list.component.html @@ -26,21 +26,21 @@ matTooltip="Filter Category"> filter_list - diff --git a/web/src/app/administration/components/classification-list/classification-list.component.spec.ts b/web/src/app/administration/components/classification-list/classification-list.component.spec.ts index 1500ddfc9..c0e8d6508 100644 --- a/web/src/app/administration/components/classification-list/classification-list.component.spec.ts +++ b/web/src/app/administration/components/classification-list/classification-list.component.spec.ts @@ -200,8 +200,8 @@ describe('ClassificationListComponent', () => { it('should return icon for category when getCategoryIcon is called and category exists', (done) => { const categoryIcon = component.getCategoryIcon('MANUAL'); categoryIcon.subscribe((iconPair) => { - expect(iconPair.name).toBe('assets/icons/categories/manual.svg'); - expect(iconPair.text).toBe('MANUAL'); + expect(iconPair.left).toBe('assets/icons/categories/manual.svg'); + expect(iconPair.right).toBe('MANUAL'); done(); }); }); @@ -209,7 +209,7 @@ describe('ClassificationListComponent', () => { it('should return a special icon when getCategoryIcon is called and category does not exist', (done) => { const categoryIcon = component.getCategoryIcon('CLOUD'); categoryIcon.subscribe((iconPair) => { - expect(iconPair.name).toBe('assets/icons/categories/missing-icon.svg'); + expect(iconPair.left).toBe('assets/icons/categories/missing-icon.svg'); done(); }); }); diff --git a/web/src/app/administration/components/classification-list/classification-list.component.ts b/web/src/app/administration/components/classification-list/classification-list.component.ts index dbdc2f482..0c2857aa4 100644 --- a/web/src/app/administration/components/classification-list/classification-list.component.ts +++ b/web/src/app/administration/components/classification-list/classification-list.component.ts @@ -6,7 +6,6 @@ import { Actions, ofActionCompleted, ofActionDispatched, Select, Store } from '@ import { ImportExportService } from 'app/administration/services/import-export.service'; import { TaskanaType } from 'app/shared/models/taskana-type'; -import { Pair } from 'app/shared/models/pair'; import { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors'; import { ClassificationSelectors } from 'app/shared/store/classification-store/classification.selectors'; import { Location } from '@angular/common'; @@ -19,6 +18,7 @@ import { import { DomainService } from '../../../shared/services/domain/domain.service'; import { ClassificationSummary } from '../../../shared/models/classification-summary'; import { RequestInProgressService } from '../../../shared/services/request-in-progress/request-in-progress.service'; +import { Pair } from '../../../shared/models/pair'; @Component({ selector: 'taskana-administration-classification-list', @@ -94,15 +94,15 @@ export class ClassificationListComponent implements OnInit, OnDestroy { this.location.go(this.location.path().replace(/(classifications).*/g, 'classifications/new-classification')); } - getCategoryIcon(category: string): Observable { + getCategoryIcon(category: string): Observable> { return this.categoryIcons$.pipe( map((iconMap) => { if (category === '') { - return new Pair(iconMap['all'], 'All'); + return { left: iconMap['all'], right: 'All' }; } return iconMap[category] - ? new Pair(iconMap[category], category) - : new Pair(iconMap.missing, 'Category does not match with the configuration'); + ? { left: iconMap[category], right: category } + : { left: iconMap.missing, right: 'Category does not match with the configuration' }; }) ); } diff --git a/web/src/app/administration/components/tree/tree.component.html b/web/src/app/administration/components/tree/tree.component.html index 93e6144f9..128ca88a3 100644 --- a/web/src/app/administration/components/tree/tree.component.html +++ b/web/src/app/administration/components/tree/tree.component.html @@ -2,8 +2,8 @@ (moveNode)="onMoveNode($event)" (treeDrop)="onDrop($event)"> - + {{ node.data.key }} diff --git a/web/src/app/administration/components/tree/tree.component.ts b/web/src/app/administration/components/tree/tree.component.ts index a19fdd367..f3c0dc7ee 100644 --- a/web/src/app/administration/components/tree/tree.component.ts +++ b/web/src/app/administration/components/tree/tree.component.ts @@ -13,7 +13,6 @@ import { import { TreeNodeModel } from 'app/administration/models/tree-node'; import { ITreeOptions, KEYS, TREE_ACTIONS, TreeComponent } from 'angular-tree-component'; -import { Pair } from 'app/shared/models/pair'; import { combineLatest, Observable, Subject } from 'rxjs'; import { filter, map, takeUntil } from 'rxjs/operators'; import { Select, Store } from '@ngxs/store'; @@ -32,6 +31,7 @@ import { UpdateClassification } from '../../../shared/store/classification-store/classification.actions'; import { ClassificationTreeService } from '../../services/classification-tree.service'; +import { Pair } from '../../../shared/models/pair'; @Component({ selector: 'taskana-administration-tree', @@ -168,12 +168,12 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy } } - getCategoryIcon(category: string): Observable { + getCategoryIcon(category: string): Observable> { return this.categoryIcons$.pipe( map((iconMap) => iconMap[category] - ? new Pair(iconMap[category], category) - : new Pair(iconMap.missing, 'Category does not match with the configuration') + ? { left: iconMap[category], right: category } + : { left: iconMap.missing, right: 'Category does not match with the configuration' } ) ); } diff --git a/web/src/app/administration/components/type-icon/icon-type.component.spec.ts b/web/src/app/administration/components/type-icon/icon-type.component.spec.ts index f4ebbb467..6ca3ee7b6 100644 --- a/web/src/app/administration/components/type-icon/icon-type.component.spec.ts +++ b/web/src/app/administration/components/type-icon/icon-type.component.spec.ts @@ -2,6 +2,7 @@ import { Component, DebugElement, Input } from '@angular/core'; import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { IconTypeComponent } from './icon-type.component'; import { SvgIconComponent, SvgIconRegistryService } from 'angular-svg-icon'; +import { WorkbasketType } from '../../../shared/models/workbasket-type'; @Component({ selector: 'svg-icon', template: '' }) class SvgIconStub { @@ -31,11 +32,11 @@ describe('IconTypeComponent', () => { }); it('should return icon path dependent on the type when calling getIconPath', () => { - expect(component.getIconPath('PERSONAL')).toBe('user.svg'); - expect(component.getIconPath('GROUP')).toBe('users.svg'); - expect(component.getIconPath('TOPIC')).toBe('topic.svg'); - expect(component.getIconPath('CLEARANCE')).toBe('clearance.svg'); - expect(component.getIconPath('CLOUD')).toBe('asterisk.svg'); + expect(component.getIconPath(WorkbasketType.PERSONAL)).toBe('user.svg'); + expect(component.getIconPath(WorkbasketType.GROUP)).toBe('users.svg'); + expect(component.getIconPath(WorkbasketType.TOPIC)).toBe('topic.svg'); + expect(component.getIconPath(WorkbasketType.CLEARANCE)).toBe('clearance.svg'); + expect(component.getIconPath(undefined)).toBe('asterisk.svg'); }); it('should display svg-icon', () => { diff --git a/web/src/app/administration/components/type-icon/icon-type.component.ts b/web/src/app/administration/components/type-icon/icon-type.component.ts index 78e9dfeca..e2543fa15 100644 --- a/web/src/app/administration/components/type-icon/icon-type.component.ts +++ b/web/src/app/administration/components/type-icon/icon-type.component.ts @@ -1,5 +1,5 @@ import { Component, Input } from '@angular/core'; -import { ICONTYPES } from 'app/shared/models/icon-types'; +import { WorkbasketType } from 'app/shared/models/workbasket-type'; @Component({ selector: 'taskana-administration-icon-type', @@ -8,7 +8,7 @@ import { ICONTYPES } from 'app/shared/models/icon-types'; }) export class IconTypeComponent { @Input() - type: ICONTYPES = ICONTYPES.ALL; + type: WorkbasketType; @Input() selected = false; @@ -22,24 +22,15 @@ export class IconTypeComponent { @Input() size = 'small'; - public static get allTypes(): Map { - return new Map([ - ['PERSONAL', 'Personal'], - ['GROUP', 'Group'], - ['CLEARANCE', 'Clearance'], - ['TOPIC', 'Topic'] - ]); - } - - getIconPath(type: string) { + getIconPath(type: WorkbasketType) { switch (type) { - case 'PERSONAL': + case WorkbasketType.PERSONAL: return 'user.svg'; - case 'GROUP': + case WorkbasketType.GROUP: return 'users.svg'; - case 'TOPIC': + case WorkbasketType.TOPIC: return 'topic.svg'; - case 'CLEARANCE': + case WorkbasketType.CLEARANCE: return 'clearance.svg'; default: return 'asterisk.svg'; diff --git a/web/src/app/administration/components/workbasket-details/workbasket-details.component.ts b/web/src/app/administration/components/workbasket-details/workbasket-details.component.ts index 5015ac257..01e78b655 100644 --- a/web/src/app/administration/components/workbasket-details/workbasket-details.component.ts +++ b/web/src/app/administration/components/workbasket-details/workbasket-details.component.ts @@ -10,7 +10,7 @@ import { takeUntil } from 'rxjs/operators'; import { WorkbasketAndAction, WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors'; import { TaskanaDate } from '../../../shared/util/taskana.date'; import { Location } from '@angular/common'; -import { ICONTYPES } from '../../../shared/models/icon-types'; +import { WorkbasketType } from '../../../shared/models/workbasket-type'; import { DeselectWorkbasket, OnButtonPressed, @@ -94,7 +94,7 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy, OnChanges initWorkbasket() { const emptyWorkbasket: Workbasket = {}; emptyWorkbasket.domain = this.domainService.getSelectedDomainValue(); - emptyWorkbasket.type = ICONTYPES.PERSONAL; + emptyWorkbasket.type = WorkbasketType.PERSONAL; this.addDateToWorkbasket(emptyWorkbasket); this.workbasket = emptyWorkbasket; } diff --git a/web/src/app/administration/components/workbasket-distribution-targets-list/workbasket-distribution-targets-list.component.html b/web/src/app/administration/components/workbasket-distribution-targets-list/workbasket-distribution-targets-list.component.html index 7bc95b6b7..a0a13987b 100644 --- a/web/src/app/administration/components/workbasket-distribution-targets-list/workbasket-distribution-targets-list.component.html +++ b/web/src/app/administration/components/workbasket-distribution-targets-list/workbasket-distribution-targets-list.component.html @@ -21,8 +21,8 @@ check_box_outline_blank - +
diff --git a/web/src/app/administration/components/workbasket-distribution-targets-list/workbasket-distribution-targets-list.component.spec.ts b/web/src/app/administration/components/workbasket-distribution-targets-list/workbasket-distribution-targets-list.component.spec.ts index 62979f4f4..ed7bc688e 100644 --- a/web/src/app/administration/components/workbasket-distribution-targets-list/workbasket-distribution-targets-list.component.spec.ts +++ b/web/src/app/administration/components/workbasket-distribution-targets-list/workbasket-distribution-targets-list.component.spec.ts @@ -1,9 +1,8 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { Component, DebugElement, EventEmitter, Input, Output } from '@angular/core'; import { WorkbasketDistributionTargetsListComponent } from './workbasket-distribution-targets-list.component'; -import { Filter } from '../../../shared/models/filter'; import { InfiniteScrollModule } from 'ngx-infinite-scroll'; -import { ICONTYPES } from '../../../shared/models/icon-types'; +import { WorkbasketType } from '../../../shared/models/workbasket-type'; import { SelectWorkBasketPipe } from '../../../shared/pipes/select-workbaskets.pipe'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { workbasketReadStateMock } from '../../../shared/store/mock-data/mock-store'; @@ -11,10 +10,11 @@ import { Side } from '../workbasket-distribution-targets/workbasket-distribution import { MatIconModule } from '@angular/material/icon'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatListModule } from '@angular/material/list'; +import { WorkbasketQueryFilterParameter } from '../../../shared/models/workbasket-query-parameters'; -@Component({ selector: 'taskana-shared-filter', template: '' }) +@Component({ selector: 'taskana-shared-workbasket-filter', template: '' }) class FilterStub { - @Output() performFilter = new EventEmitter(); + @Output() performFilter = new EventEmitter(); } @Component({ selector: 'taskana-shared-spinner', template: '' }) @@ -24,7 +24,7 @@ class SpinnerStub { @Component({ selector: 'taskana-administration-icon-type', template: '' }) class IconTypeStub { - @Input() type: ICONTYPES = ICONTYPES.ALL; + @Input() type: WorkbasketType; @Input() text: string; } diff --git a/web/src/app/administration/components/workbasket-distribution-targets-list/workbasket-distribution-targets-list.component.ts b/web/src/app/administration/components/workbasket-distribution-targets-list/workbasket-distribution-targets-list.component.ts index 06a8d3b7b..7a9580306 100644 --- a/web/src/app/administration/components/workbasket-distribution-targets-list/workbasket-distribution-targets-list.component.ts +++ b/web/src/app/administration/components/workbasket-distribution-targets-list/workbasket-distribution-targets-list.component.ts @@ -9,10 +9,11 @@ import { ViewChild } from '@angular/core'; import { WorkbasketSummary } from 'app/shared/models/workbasket-summary'; -import { Filter } from 'app/shared/models/filter'; import { expandDown } from 'app/shared/animations/expand.animation'; import { Side } from '../workbasket-distribution-targets/workbasket-distribution-targets.component'; import { MatSelectionList } from '@angular/material/list'; +import { Pair } from '../../../shared/models/pair'; +import { WorkbasketQueryFilterParameter } from '../../../shared/models/workbasket-query-parameters'; @Component({ selector: 'taskana-administration-workbasket-distribution-targets-list', @@ -23,7 +24,7 @@ import { MatSelectionList } from '@angular/material/list'; export class WorkbasketDistributionTargetsListComponent implements OnInit, AfterContentChecked { @Input() distributionTargets: WorkbasketSummary[]; @Input() distributionTargetsSelected: WorkbasketSummary[]; - @Output() performDualListFilter = new EventEmitter<{ filterBy: Filter; side: Side }>(); + @Output() performDualListFilter = new EventEmitter>(); @Input() requestInProgress = false; @Input() loadingItems? = false; @Input() side: Side; @@ -33,7 +34,6 @@ export class WorkbasketDistributionTargetsListComponent implements OnInit, After @Output() allSelectedChange = new EventEmitter(); toolbarState = false; - component = ''; @ViewChild('workbasket') distributionTargetsList: MatSelectionList; constructor(private changeDetector: ChangeDetectorRef) {} @@ -55,17 +55,13 @@ export class WorkbasketDistributionTargetsListComponent implements OnInit, After this.allSelectedChange.emit(this.allSelected); } - setComponent(component: string) { - this.component = component; - } - onScroll() { this.scrolling.emit(this.side); } - performAvailableFilter(filterModel: Filter) { - if (this.component === 'distribution-target') { - this.performDualListFilter.emit({ filterBy: filterModel, side: this.side }); + performAvailableFilter(pair: Pair) { + if (pair.left === 'distribution-target') { + this.performDualListFilter.emit({ left: this.side, right: pair.right }); } } diff --git a/web/src/app/administration/components/workbasket-distribution-targets/workbasket-distribution-targets.component.spec.ts b/web/src/app/administration/components/workbasket-distribution-targets/workbasket-distribution-targets.component.spec.ts index ec5850027..de4eed7c7 100644 --- a/web/src/app/administration/components/workbasket-distribution-targets/workbasket-distribution-targets.component.spec.ts +++ b/web/src/app/administration/components/workbasket-distribution-targets/workbasket-distribution-targets.component.spec.ts @@ -2,7 +2,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { Component, DebugElement, EventEmitter, Input, Output } from '@angular/core'; import { Side, WorkbasketDistributionTargetsComponent } from './workbasket-distribution-targets.component'; import { WorkbasketSummary } from '../../../shared/models/workbasket-summary'; -import { Filter } from '../../../shared/models/filter'; import { MatIconModule } from '@angular/material/icon'; import { MatToolbarModule } from '@angular/material/toolbar'; import { MatButtonModule } from '@angular/material/button'; @@ -20,6 +19,8 @@ import { selectedWorkbasketMock, workbasketReadStateMock } from '../../../shared/store/mock-data/mock-store'; +import { WorkbasketQueryFilterParameter } from '../../../shared/models/workbasket-query-parameters'; +import { Pair } from '../../../shared/models/pair'; const routeParamsMock = { id: 'workbasket' }; const activatedRouteMock = { @@ -31,7 +32,7 @@ const activatedRouteMock = { class WorkbasketDistributionTargetsListStub { @Input() distributionTargets: WorkbasketSummary[]; @Input() distributionTargetsSelected: WorkbasketSummary[]; - @Output() performDualListFilter = new EventEmitter<{ filterBy: Filter; side: Side }>(); + @Output() performDualListFilter = new EventEmitter>(); @Input() requestInProgress = false; @Input() loadingItems? = false; @Input() side: Side; @@ -128,11 +129,14 @@ describe('WorkbasketDistributionTargetsComponent', () => { expect(component.distributionTargetsSelected).toHaveLength(3); //mock-data has 3 entries }); + // TODO: was ist das für ein test? it('should emit filter model and side when performing filter', () => { const performDualListFilterSpy = jest.spyOn(component, 'performFilter'); - const filterModelMock: Filter = { filterParams: { name: '', description: '', owner: '', type: '', key: '' } }; - component.performFilter({ filterBy: filterModelMock, side: component.side }); - expect(performDualListFilterSpy).toHaveBeenCalledWith({ filterBy: filterModelMock, side: component.side }); + const filterModelMock: WorkbasketQueryFilterParameter = { domain: ['DOMAIN_A'] }; + + component.performFilter({ left: Side.AVAILABLE, right: filterModelMock }); + + expect(performDualListFilterSpy).toHaveBeenCalledWith({ right: filterModelMock, left: Side.AVAILABLE }); }); it('should move distribution targets to selected list', () => { diff --git a/web/src/app/administration/components/workbasket-distribution-targets/workbasket-distribution-targets.component.ts b/web/src/app/administration/components/workbasket-distribution-targets/workbasket-distribution-targets.component.ts index 0ddf73217..1769d2e56 100644 --- a/web/src/app/administration/components/workbasket-distribution-targets/workbasket-distribution-targets.component.ts +++ b/web/src/app/administration/components/workbasket-distribution-targets/workbasket-distribution-targets.component.ts @@ -8,7 +8,6 @@ import { WorkbasketDistributionTargets } from 'app/shared/models/workbasket-dist import { ACTION } from 'app/shared/models/action'; import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service'; import { SavingWorkbasketService, SavingInformation } from 'app/administration/services/saving-workbaskets.service'; -import { TaskanaQueryParameters } from 'app/shared/util/query-parameters'; import { Page } from 'app/shared/models/page'; import { Select, Store } from '@ngxs/store'; import { filter, takeUntil } from 'rxjs/operators'; @@ -20,8 +19,10 @@ import { UpdateWorkbasketDistributionTargets } from '../../../shared/store/workbasket-store/workbasket.actions'; import { WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors'; -import { MatDialog } from '@angular/material/dialog'; import { ButtonAction } from '../../models/button-action'; +import { Pair } from '../../../shared/models/pair'; +import { WorkbasketQueryFilterParameter } from '../../../shared/models/workbasket-query-parameters'; +import { QueryPagingParameter } from '../../../shared/models/query-paging-parameter'; export enum Side { AVAILABLE, @@ -54,7 +55,11 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy loadingItems = false; side = Side; private initialized = false; - page: Page; + currentPage: Page; + pageParameter: QueryPagingParameter = { + page: 1, + 'page-size': 9 + }; cards: number; selectAllLeft = false; selectAllRight = false; @@ -77,8 +82,7 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy private workbasketService: WorkbasketService, private savingWorkbaskets: SavingWorkbasketService, private notificationsService: NotificationService, - private store: Store, - public matDialog: MatDialog + private store: Store ) {} /** @@ -100,7 +104,7 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy .pipe(takeUntil(this.destroy$)) .pipe(filter((availableDistributionTargets) => typeof availableDistributionTargets !== 'undefined')) .subscribe((availableDistributionTargets) => { - this.availableDistributionTargets = [...availableDistributionTargets]; + this.availableDistributionTargets = availableDistributionTargets.map((wb) => ({ ...wb })); }); this.savingWorkbaskets @@ -118,7 +122,7 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy this.distributionTargetsSelectedResource = { ...workbasketDistributionTargets }; this.distributionTargetsSelected = this.distributionTargetsSelectedResource.distributionTargets; this.distributionTargetsSelectedClone = { ...this.distributionTargetsSelected }; - TaskanaQueryParameters.page = 1; + this.pageParameter.page = 1; this.getWorkbaskets(); } }); @@ -140,7 +144,7 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy } onScroll() { - if (this.page.totalPages > TaskanaQueryParameters.page) { + if (this.currentPage && this.currentPage.totalPages > this.pageParameter.page) { this.loadingItems = true; this.getNextPage(); } @@ -157,20 +161,20 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy getWorkbaskets(side?: Side) { if (this.distributionTargetsSelected && !this.initialized) { this.initialized = true; - TaskanaQueryParameters.pageSize = this.cards + this.distributionTargetsSelected.length; + this.pageParameter['page-size'] = this.cards + this.distributionTargetsSelected.length; } this.workbasketService - .getWorkBasketsSummary(true) + .getWorkBasketsSummary(true, undefined, undefined, this.pageParameter) .pipe(takeUntil(this.destroy$)) .subscribe((distributionTargetsAvailable: WorkbasketSummaryRepresentation) => { - if (TaskanaQueryParameters.page === 1) { + if (this.pageParameter === 1) { this.availableDistributionTargets = []; - this.page = distributionTargetsAvailable.page; + this.currentPage = distributionTargetsAvailable.page; } - if (side === this.side.AVAILABLE) { + if (side === Side.AVAILABLE) { this.availableDistributionTargets.push(...distributionTargetsAvailable.workbaskets); - } else if (side === this.side.SELECTED) { + } else if (side === Side.SELECTED) { this.distributionTargetsLeft = Object.assign([], distributionTargetsAvailable.workbaskets); } else { this.availableDistributionTargets.push(...distributionTargetsAvailable.workbaskets); @@ -181,38 +185,24 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy } getNextPage(side?: Side) { - TaskanaQueryParameters.page += 1; + this.pageParameter.page += 1; this.getWorkbaskets(side); } - performFilter(dualListFilter: any) { + performFilter({ left: side, right: filter }: Pair) { this.workbasketService - .getWorkBasketsSummary( - true, - '', - '', - '', - dualListFilter.filterBy.filterParams.name, - dualListFilter.filterBy.filterParams.description, - '', - dualListFilter.filterBy.filterParams.owner, - dualListFilter.filterBy.filterParams.type, - '', - dualListFilter.filterBy.filterParams.key, - '', - true - ) + .getWorkBasketsSummary(true, filter) .pipe(takeUntil(this.destroy$)) .subscribe((distributionTargetsAvailable: WorkbasketSummaryRepresentation) => { - this.fillDistributionTargets(dualListFilter.side, []); + this.fillDistributionTargets(side, []); - if (TaskanaQueryParameters.page === 1) { + if (this.pageParameter === 1) { this.availableDistributionTargets = []; - this.page = distributionTargetsAvailable.page; + this.currentPage = distributionTargetsAvailable.page; } - if (dualListFilter.side === this.side.AVAILABLE) { + if (side === Side.AVAILABLE) { this.availableDistributionTargets.push(...distributionTargetsAvailable.workbaskets); - } else if (dualListFilter.side === this.side.SELECTED) { + } else if (side === Side.SELECTED) { this.distributionTargetsLeft = Object.assign([], distributionTargetsAvailable.workbaskets); } else { this.availableDistributionTargets.push(...distributionTargetsAvailable.workbaskets); @@ -240,8 +230,8 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy this.distributionTargetsSelected = [...this.distributionTargetsSelected, ...itemsSelected]; this.distributionTargetsLeft = this.distributionTargetsLeft.concat(itemsSelected); if ( - itemsLeft - itemsSelected.length <= TaskanaQueryParameters.pageSize && - itemsLeft + itemsRight < this.page.totalElements + itemsLeft - itemsSelected.length <= this.pageParameter['page-size'] && + itemsLeft + itemsRight < this.currentPage.totalElements ) { this.getNextPage(side); } diff --git a/web/src/app/administration/components/workbasket-information/workbasket-information.component.spec.ts b/web/src/app/administration/components/workbasket-information/workbasket-information.component.spec.ts index 9c542af2c..9d295c278 100644 --- a/web/src/app/administration/components/workbasket-information/workbasket-information.component.spec.ts +++ b/web/src/app/administration/components/workbasket-information/workbasket-information.component.spec.ts @@ -4,7 +4,7 @@ import { Component, DebugElement, Input } from '@angular/core'; import { Actions, NgxsModule, ofActionDispatched, Store } from '@ngxs/store'; import { Observable, of } from 'rxjs'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; -import { ICONTYPES } from '../../../shared/models/icon-types'; +import { WorkbasketType } from '../../../shared/models/workbasket-type'; import { MapValuesPipe } from '../../../shared/pipes/map-values.pipe'; import { RemoveNoneTypePipe } from '../../../shared/pipes/remove-empty-type.pipe'; import { WorkbasketService } from '../../../shared/services/workbasket/workbasket.service'; @@ -50,7 +50,7 @@ class FieldErrorDisplayStub { @Component({ selector: 'taskana-administration-icon-type', template: '' }) class IconTypeStub { - @Input() type: ICONTYPES = ICONTYPES.ALL; + @Input() type: WorkbasketType; @Input() text: string; } diff --git a/web/src/app/administration/components/workbasket-list-toolbar/workbasket-list-toolbar.component.html b/web/src/app/administration/components/workbasket-list-toolbar/workbasket-list-toolbar.component.html index d12105aac..887d205e8 100644 --- a/web/src/app/administration/components/workbasket-list-toolbar/workbasket-list-toolbar.component.html +++ b/web/src/app/administration/components/workbasket-list-toolbar/workbasket-list-toolbar.component.html @@ -29,10 +29,9 @@
- +
diff --git a/web/src/app/administration/components/workbasket-list-toolbar/workbasket-list-toolbar.component.spec.ts b/web/src/app/administration/components/workbasket-list-toolbar/workbasket-list-toolbar.component.spec.ts index df873a124..9dec6b55a 100644 --- a/web/src/app/administration/components/workbasket-list-toolbar/workbasket-list-toolbar.component.spec.ts +++ b/web/src/app/administration/components/workbasket-list-toolbar/workbasket-list-toolbar.component.spec.ts @@ -9,8 +9,7 @@ import { WorkbasketService } from '../../../shared/services/workbasket/workbaske import { DomainService } from '../../../shared/services/domain/domain.service'; import { CreateWorkbasket } from '../../../shared/store/workbasket-store/workbasket.actions'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; -import { Filter } from '../../../shared/models/filter'; -import { Sorting } from '../../../shared/models/sorting'; +import { Direction, Sorting, WorkbasketQuerySortParameter } from '../../../shared/models/sorting'; import { ACTION } from '../../../shared/models/action'; import { TaskanaType } from '../../../shared/models/taskana-type'; import { MatIconModule } from '@angular/material/icon'; @@ -18,6 +17,8 @@ import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatDialogModule } from '@angular/material/dialog'; import { RouterTestingModule } from '@angular/router/testing'; import { RequestInProgressService } from '../../../shared/services/request-in-progress/request-in-progress.service'; +import { Pair } from '../../../shared/models/pair'; +import { WorkbasketQueryFilterParameter } from '../../../shared/models/workbasket-query-parameters'; const getDomainFn = jest.fn().mockReturnValue(true); const domainServiceMock = jest.fn().mockImplementation( @@ -34,15 +35,15 @@ class ImportExportStub { @Component({ selector: 'taskana-shared-sort', template: '' }) class SortStub { - @Input() sortingFields: Map; - @Input() defaultSortBy = 'key'; - @Output() performSorting = new EventEmitter(); + @Input() sortingFields: Map; + @Input() defaultSortBy: WorkbasketQuerySortParameter; + @Output() performSorting = new EventEmitter>(); } -@Component({ selector: 'taskana-shared-filter', template: '' }) +@Component({ selector: 'taskana-shared-workbasket-filter', template: '' }) class FilterStub { @Input() isExpanded = false; - @Output() performFilter = new EventEmitter(); + @Output() performFilter = new EventEmitter(); } const requestInProgressServiceSpy = jest.fn().mockImplementation( @@ -109,25 +110,35 @@ describe('WorkbasketListToolbarComponent', () => { })); it('should emit value when sorting is called', (done) => { - const mockSort: Sorting = { sortBy: '123', sortDirection: 'asc' }; - let sort: Sorting = { sortBy: '123', sortDirection: 'asc' }; - component.performSorting.subscribe((sortBy: Sorting) => { + const mockSort: Sorting = { + 'sort-by': WorkbasketQuerySortParameter.KEY, + order: Direction.ASC + }; + let sort: Sorting = undefined; + component.performSorting.subscribe((sortBy: Sorting) => { sort = sortBy; done(); }); - component.sorting(sort); + component.sorting(mockSort); expect(sort).toMatchObject(mockSort); }); - it('should emit value when filtering is called', async((done) => { - const mockFilter: Filter = { filterParams: 'abc' }; - let filterBy: Filter = { filterParams: 'abc' }; - component.performFilter.subscribe((filter: Filter) => { - filterBy = filter; - done(); - }); - component.filtering(filterBy); - expect(filterBy).toMatchObject(mockFilter); + it('should NOT emit value when filtering is called with wrong component', async((done) => { + const mockFilter: Pair = { left: 'foo', right: { domain: ['DOMAIN_A'] } }; + const performFilterSpy = jest.spyOn(component.performFilter, 'emit'); + component.filtering(mockFilter); + expect(performFilterSpy).toBeCalledTimes(0); + })); + + it('should emit value when filtering is called with correct component', async((done) => { + const mockFilter: Pair = { + left: 'workbasket-list', + right: { domain: ['DOMAIN_A'] } + }; + const performFilterSpy = jest.spyOn(component.performFilter, 'emit'); + component.filtering(mockFilter); + expect(performFilterSpy).toBeCalledTimes(1); + expect(performFilterSpy).toBeCalledWith(mockFilter.right); })); /* HTML */ @@ -151,7 +162,7 @@ describe('WorkbasketListToolbarComponent', () => { }); it('should display filter component', () => { - expect(debugElement.nativeElement.querySelector('taskana-shared-filter')).toBeTruthy(); + expect(debugElement.nativeElement.querySelector('taskana-shared-workbasket-filter')).toBeTruthy(); }); it('should show expanded filter component only when filter button is clicked', () => { diff --git a/web/src/app/administration/components/workbasket-list-toolbar/workbasket-list-toolbar.component.ts b/web/src/app/administration/components/workbasket-list-toolbar/workbasket-list-toolbar.component.ts index 606ca96e4..b69521eaf 100644 --- a/web/src/app/administration/components/workbasket-list-toolbar/workbasket-list-toolbar.component.ts +++ b/web/src/app/administration/components/workbasket-list-toolbar/workbasket-list-toolbar.component.ts @@ -1,6 +1,5 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { Sorting } from 'app/shared/models/sorting'; -import { Filter } from 'app/shared/models/filter'; +import { Sorting, WORKBASKET_SORT_PARAMETER_NAMING, WorkbasketQuerySortParameter } from 'app/shared/models/sorting'; import { WorkbasketSummary } from 'app/shared/models/workbasket-summary'; import { TaskanaType } from 'app/shared/models/taskana-type'; import { expandDown } from 'app/shared/animations/expand.animation'; @@ -11,6 +10,8 @@ import { ACTION } from '../../../shared/models/action'; import { CreateWorkbasket } from '../../../shared/store/workbasket-store/workbasket.actions'; import { WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors'; import { WorkbasketService } from '../../../shared/services/workbasket/workbasket.service'; +import { WorkbasketQueryFilterParameter } from '../../../shared/models/workbasket-query-parameters'; +import { Pair } from '../../../shared/models/pair'; @Component({ selector: 'taskana-administration-workbasket-list-toolbar', @@ -21,31 +22,15 @@ import { WorkbasketService } from '../../../shared/services/workbasket/workbaske export class WorkbasketListToolbarComponent implements OnInit { @Input() workbasketListExpanded: boolean = true; @Input() workbaskets: Array; - @Input() workbasketDefaultSortBy: string; - @Output() performSorting = new EventEmitter(); - @Output() performFilter = new EventEmitter(); + @Input() workbasketDefaultSortBy: WorkbasketQuerySortParameter; + @Output() performSorting = new EventEmitter>(); + @Output() performFilter = new EventEmitter(); selectionToImport = TaskanaType.WORKBASKETS; - sortingFields = new Map([ - ['name', 'Name'], - ['key', 'Key'], - ['description', 'Description'], - ['owner', 'Owner'], - ['type', 'Type'] - ]); - filteringTypes = new Map([ - ['ALL', 'All'], - ['PERSONAL', 'Personal'], - ['GROUP', 'Group'], - ['CLEARANCE', 'Clearance'], - ['TOPIC', 'Topic'] - ]); + sortingFields: Map = WORKBASKET_SORT_PARAMETER_NAMING; - filterParams = { name: '', key: '', type: '', description: '', owner: '' }; - filterType = TaskanaType.WORKBASKETS; isExpanded = false; showFilter = false; - component = ''; @Select(WorkbasketSelectors.workbasketActiveAction) workbasketActiveAction$: Observable; @@ -61,20 +46,16 @@ export class WorkbasketListToolbarComponent implements OnInit { }); } - sorting(sort: Sorting) { + sorting(sort: Sorting) { this.performSorting.emit(sort); } - filtering(filterBy: Filter) { - if (this.component === 'workbasket-list') { - this.performFilter.emit(filterBy); + filtering({ left: component, right: filter }: Pair) { + if (component === 'workbasket-list') { + this.performFilter.emit(filter); } } - setComponent(component: string) { - this.component = component; - } - addWorkbasket() { if (this.action !== ACTION.CREATE) { this.store.dispatch(new CreateWorkbasket()); diff --git a/web/src/app/administration/components/workbasket-list/workbasket-list.component.spec.ts b/web/src/app/administration/components/workbasket-list/workbasket-list.component.spec.ts index ecff654aa..0b41bf431 100644 --- a/web/src/app/administration/components/workbasket-list/workbasket-list.component.spec.ts +++ b/web/src/app/administration/components/workbasket-list/workbasket-list.component.spec.ts @@ -10,11 +10,9 @@ import { MatDialogModule } from '@angular/material/dialog'; import { OrientationService } from '../../../shared/services/orientation/orientation.service'; import { ImportExportService } from '../../services/import-export.service'; import { DeselectWorkbasket, SelectWorkbasket } from '../../../shared/store/workbasket-store/workbasket.actions'; -import { TaskanaQueryParameters } from '../../../shared/util/query-parameters'; import { WorkbasketSummary } from '../../../shared/models/workbasket-summary'; -import { Sorting } from '../../../shared/models/sorting'; -import { Filter } from '../../../shared/models/filter'; -import { ICONTYPES } from '../../../shared/models/icon-types'; +import { Direction, Sorting, WorkbasketQuerySortParameter } from '../../../shared/models/sorting'; +import { WorkbasketType } from '../../../shared/models/workbasket-type'; import { Page } from '../../../shared/models/page'; import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatSelectModule } from '@angular/material/select'; @@ -24,6 +22,7 @@ import { DomainService } from '../../../shared/services/domain/domain.service'; import { RouterTestingModule } from '@angular/router/testing'; import { RequestInProgressService } from '../../../shared/services/request-in-progress/request-in-progress.service'; import { selectedWorkbasketMock } from '../../../shared/store/mock-data/mock-store'; +import { WorkbasketQueryFilterParameter } from '../../../shared/models/workbasket-query-parameters'; const workbasketSavedTriggeredFn = jest.fn().mockReturnValue(of(1)); const workbasketSummaryFn = jest.fn().mockReturnValue(of({})); @@ -74,13 +73,13 @@ class WorkbasketListToolbarStub { @Input() workbaskets: Array; @Input() workbasketDefaultSortBy: string; @Input() workbasketListExpanded: boolean; - @Output() performSorting = new EventEmitter(); - @Output() performFilter = new EventEmitter(); + @Output() performSorting = new EventEmitter>(); + @Output() performFilter = new EventEmitter(); } @Component({ selector: 'taskana-administration-icon-type', template: '' }) class IconTypeStub { - @Input() type: ICONTYPES = ICONTYPES.ALL; + @Input() type: WorkbasketType; @Input() selected = false; } @@ -158,13 +157,16 @@ describe('WorkbasketListComponent', () => { })); it('should set sort value when performSorting is called', () => { - const sort = { sortBy: '1', sortDirection: 'asc' }; + const sort: Sorting = { + 'sort-by': WorkbasketQuerySortParameter.TYPE, + order: Direction.ASC + }; component.performSorting(sort); expect(component.sort).toMatchObject(sort); }); it('should set filter value when performFilter is called', () => { - const filter = { filterParams: '123' }; + const filter: WorkbasketQueryFilterParameter = { domain: ['123'] }; component.performFilter(filter); expect(component.filterBy).toMatchObject(filter); }); @@ -172,6 +174,6 @@ describe('WorkbasketListComponent', () => { it('should change page value when change page function is called ', () => { const page = 2; component.changePage(page); - expect(TaskanaQueryParameters.page).toBe(page); + expect(component.pageParameter.page).toBe(page); }); }); diff --git a/web/src/app/administration/components/workbasket-list/workbasket-list.component.ts b/web/src/app/administration/components/workbasket-list/workbasket-list.component.ts index 613b6ad9d..e764460d1 100644 --- a/web/src/app/administration/components/workbasket-list/workbasket-list.component.ts +++ b/web/src/app/administration/components/workbasket-list/workbasket-list.component.ts @@ -3,13 +3,11 @@ import { Observable, Subject } from 'rxjs'; import { WorkbasketSummaryRepresentation } from 'app/shared/models/workbasket-summary-representation'; import { WorkbasketSummary } from 'app/shared/models/workbasket-summary'; -import { Filter } from 'app/shared/models/filter'; -import { Sorting } from 'app/shared/models/sorting'; +import { Direction, Sorting, WorkbasketQuerySortParameter } from 'app/shared/models/sorting'; import { Orientation } from 'app/shared/models/orientation'; import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service'; import { OrientationService } from 'app/shared/services/orientation/orientation.service'; -import { TaskanaQueryParameters } from 'app/shared/util/query-parameters'; import { ImportExportService } from 'app/administration/services/import-export.service'; import { Actions, ofActionCompleted, ofActionDispatched, Select, Store } from '@ngxs/store'; import { takeUntil } from 'rxjs/operators'; @@ -23,6 +21,8 @@ import { Workbasket } from '../../../shared/models/workbasket'; import { MatSelectionList } from '@angular/material/list'; import { DomainService } from '../../../shared/services/domain/domain.service'; import { RequestInProgressService } from '../../../shared/services/request-in-progress/request-in-progress.service'; +import { WorkbasketQueryFilterParameter } from '../../../shared/models/workbasket-query-parameters'; +import { QueryPagingParameter } from '../../../shared/models/query-paging-parameter'; @Component({ selector: 'taskana-administration-workbasket-list', @@ -31,13 +31,17 @@ import { RequestInProgressService } from '../../../shared/services/request-in-pr }) export class WorkbasketListComponent implements OnInit, OnDestroy { selectedId = ''; - pageSelected = 1; - pageSize = 9; type = 'workbaskets'; - cards: number = this.pageSize; - workbasketDefaultSortBy: string = 'name'; - sort: Sorting = new Sorting(this.workbasketDefaultSortBy); - filterBy: Filter = new Filter({ name: '', owner: '', type: '', description: '', key: '' }); + workbasketDefaultSortBy: WorkbasketQuerySortParameter = WorkbasketQuerySortParameter.NAME; + sort: Sorting = { + 'sort-by': this.workbasketDefaultSortBy, + order: Direction.ASC + }; + filterBy: WorkbasketQueryFilterParameter = {}; + pageParameter: QueryPagingParameter = { + page: 1, + 'page-size': 9 + }; requestInProgress: boolean; requestInProgressLocal = false; @Input() expanded: boolean; @@ -88,9 +92,6 @@ export class WorkbasketListComponent implements OnInit, OnDestroy { } }); - TaskanaQueryParameters.page = this.pageSelected; - TaskanaQueryParameters.pageSize = this.pageSize; - this.workbasketService .workbasketSavedTriggered() .pipe(takeUntil(this.destroy$)) @@ -145,23 +146,23 @@ export class WorkbasketListComponent implements OnInit, OnDestroy { } } - performSorting(sort: Sorting) { + performSorting(sort: Sorting) { this.sort = sort; this.performRequest(); } - performFilter(filterBy: Filter) { + performFilter(filterBy: WorkbasketQueryFilterParameter) { this.filterBy = filterBy; this.performRequest(); } changePage(page) { - TaskanaQueryParameters.page = page; + this.pageParameter.page = page; this.performRequest(); } refreshWorkbasketList() { - this.cards = this.orientationService.calculateNumberItemsList( + this.pageParameter['page-size'] = this.orientationService.calculateNumberItemsList( window.innerHeight, 92, 200 + this.toolbarElement.nativeElement.offsetHeight, @@ -171,27 +172,9 @@ export class WorkbasketListComponent implements OnInit, OnDestroy { } performRequest() { - TaskanaQueryParameters.pageSize = this.cards; - this.store - .dispatch( - new GetWorkbasketsSummary( - true, - this.sort.sortBy, - this.sort.sortDirection, - '', - this.filterBy.filterParams.name, - this.filterBy.filterParams.description, - '', - this.filterBy.filterParams.owner, - this.filterBy.filterParams.type, - '', - this.filterBy.filterParams.key, - '' - ) - ) - .subscribe(() => { - this.requestInProgressService.setRequestInProgress(false); - }); + this.store.dispatch(new GetWorkbasketsSummary(true, this.filterBy, this.sort, this.pageParameter)).subscribe(() => { + this.requestInProgressService.setRequestInProgress(false); + }); } ngOnDestroy() { diff --git a/web/src/app/history/history-routing.module.ts b/web/src/app/history/history-routing.module.ts index 8a8b03129..5512d74bc 100644 --- a/web/src/app/history/history-routing.module.ts +++ b/web/src/app/history/history-routing.module.ts @@ -1,11 +1,11 @@ import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; -import { TaskQueryComponent } from './task-query/task-query.component'; +import { TaskHistoryQueryComponent } from './task-history-query/task-history-query.component'; const routes: Routes = [ { path: '', - component: TaskQueryComponent + component: TaskHistoryQueryComponent }, { path: '**', diff --git a/web/src/app/history/history.module.ts b/web/src/app/history/history.module.ts index 4ce608837..7a174ce4e 100644 --- a/web/src/app/history/history.module.ts +++ b/web/src/app/history/history.module.ts @@ -4,10 +4,10 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { SharedModule } from 'app/shared/shared.module'; import { HistoryRoutingModule } from './history-routing.module'; -import { TaskQueryComponent } from './task-query/task-query.component'; +import { TaskHistoryQueryComponent } from './task-history-query/task-history-query.component'; @NgModule({ imports: [CommonModule, HistoryRoutingModule, SharedModule, FormsModule, ReactiveFormsModule], - declarations: [TaskQueryComponent] + declarations: [TaskHistoryQueryComponent] }) export class HistoryModule {} diff --git a/web/src/app/history/services/task-query/task-query.service.ts b/web/src/app/history/services/task-history-query/task-history-query.service.ts similarity index 65% rename from web/src/app/history/services/task-query/task-query.service.ts rename to web/src/app/history/services/task-history-query/task-history-query.service.ts index 2af32cb27..d4cef9de2 100644 --- a/web/src/app/history/services/task-query/task-query.service.ts +++ b/web/src/app/history/services/task-history-query/task-history-query.service.ts @@ -1,56 +1,36 @@ import { Injectable } from '@angular/core'; -import { TaskHistoryEventData } from 'app/shared/models/task-history-event'; import { TaskHistoryEventResourceData } from 'app/shared/models/task-history-event-resource'; import { QueryParameters } from 'app/shared/models/query-parameters'; import { TaskanaQueryParameters } from 'app/shared/util/query-parameters'; -import { Direction } from 'app/shared/models/sorting'; +import { Sorting, TaskHistoryQuerySortParameter } from 'app/shared/models/sorting'; import { Observable, of } from 'rxjs'; import { HttpClient } from '@angular/common/http'; -import { environment } from 'environments/environment'; import { StartupService } from '../../../shared/services/startup/startup.service'; +import { TaskHistoryQueryFilterParameter } from '../../../shared/models/task-history-query-filter-parameter'; +import { QueryPagingParameter } from '../../../shared/models/query-paging-parameter'; +import { asUrlQueryString } from '../../../shared/util/query-parameters-v2'; @Injectable({ providedIn: 'root' }) -export class TaskQueryService { +export class TaskHistoryQueryService { constructor(private httpClient: HttpClient, private startupService: StartupService) {} get url(): string { return this.startupService.getTaskanaRestUrl(); } - queryTask( - orderBy: string = 'created', - sortDirection: string = Direction.ASC, - searchForValues: TaskHistoryEventData, - allPages: boolean = false + getTaskHistoryEvents( + filterParameter?: TaskHistoryQueryFilterParameter, + sortParameter?: Sorting, + pagingParameter?: QueryPagingParameter ): Observable { return this.httpClient.get( - `${this.url}/v1/task-history-event${this.getQueryParameters( - orderBy, - sortDirection, - searchForValues.taskId, - searchForValues.parentBusinessProcessId, - searchForValues.businessProcessId, - searchForValues.eventType, - searchForValues.userId, - searchForValues.domain, - searchForValues.workbasketKey, - searchForValues.porCompany, - searchForValues.porSystem, - searchForValues.porInstance, - searchForValues.porType, - searchForValues.porValue, - searchForValues.taskClassificationKey, - searchForValues.taskClassificationCategory, - searchForValues.attachmentClassificationKey, - searchForValues.custom1, - searchForValues.custom2, - searchForValues.custom3, - searchForValues.custom4, - searchForValues.created, - allPages - )}` + `${this.url}/v1/task-history-event${asUrlQueryString({ + ...filterParameter, + ...sortParameter, + ...pagingParameter + })}` ); } @@ -78,7 +58,7 @@ export class TaskQueryService { custom4: string, created: string, allPages: boolean = false - ): string { + ): void { const parameters = new QueryParameters(); parameters.SORTBY = orderBy; parameters.SORTDIRECTION = sortDirection; @@ -107,7 +87,5 @@ export class TaskQueryService { delete TaskanaQueryParameters.page; delete TaskanaQueryParameters.pageSize; } - - return TaskanaQueryParameters.getQueryParameters(parameters); } } diff --git a/web/src/app/history/task-query/task-query.component.html b/web/src/app/history/task-history-query/task-history-query.component.html similarity index 98% rename from web/src/app/history/task-query/task-query.component.html rename to web/src/app/history/task-history-query/task-history-query.component.html index d11bb6783..0a79b2156 100644 --- a/web/src/app/history/task-query/task-query.component.html +++ b/web/src/app/history/task-history-query/task-history-query.component.html @@ -22,7 +22,7 @@ {{getHeaderFieldDescription(taskHeader.key)}} - sort diff --git a/web/src/app/history/task-query/task-query.component.scss b/web/src/app/history/task-history-query/task-history-query.component.scss similarity index 100% rename from web/src/app/history/task-query/task-query.component.scss rename to web/src/app/history/task-history-query/task-history-query.component.scss diff --git a/web/src/app/history/task-query/task-query.component.ts b/web/src/app/history/task-history-query/task-history-query.component.ts similarity index 84% rename from web/src/app/history/task-query/task-query.component.ts rename to web/src/app/history/task-history-query/task-history-query.component.ts index 63a282186..31d6f5079 100644 --- a/web/src/app/history/task-query/task-query.component.ts +++ b/web/src/app/history/task-history-query/task-history-query.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { Direction, Sorting } from 'app/shared/models/sorting'; +import { Direction, Sorting, TaskHistoryQuerySortParameter } from 'app/shared/models/sorting'; import { OrientationService } from 'app/shared/services/orientation/orientation.service'; import { Subscription } from 'rxjs'; import { Orientation } from 'app/shared/models/orientation'; @@ -8,29 +8,30 @@ import { FormControl, FormGroup } from '@angular/forms'; import { TaskHistoryEventResourceData } from 'app/shared/models/task-history-event-resource'; import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service'; import { TaskHistoryEventData } from '../../shared/models/task-history-event'; -import { TaskQueryService } from '../services/task-query/task-query.service'; -import { NotificationService } from '../../shared/services/notifications/notification.service'; +import { TaskHistoryQueryService } from '../services/task-history-query/task-history-query.service'; @Component({ selector: 'taskana-task-query', - templateUrl: './task-query.component.html', - styleUrls: ['./task-query.component.scss'] + templateUrl: './task-history-query.component.html', + styleUrls: ['./task-history-query.component.scss'] }) -export class TaskQueryComponent implements OnInit { +export class TaskHistoryQueryComponent implements OnInit { taskQueryResource: TaskHistoryEventResourceData; taskQuery: Array; taskQueryHeader = new TaskHistoryEventData(); - orderBy = new Sorting(TaskanaQueryParameters.parameters.CREATED); + orderBy: Sorting = { + 'sort-by': TaskHistoryQuerySortParameter.CREATED, + order: Direction.ASC + }; orientationSubscription: Subscription; taskQuerySubscription: Subscription; taskQueryForm = new FormGroup({}); constructor( - private taskQueryService: TaskQueryService, + private taskQueryService: TaskHistoryQueryService, private orientationService: OrientationService, - private requestInProgressService: RequestInProgressService, - private errorsService: NotificationService + private requestInProgressService: RequestInProgressService ) {} ngOnInit() { @@ -146,11 +147,12 @@ export class TaskQueryComponent implements OnInit { } changeOrderBy(key: string) { + console.log(key); if (this.filterFieldsToAllowQuerying(key)) { - if (this.orderBy.sortBy === key) { - this.orderBy.sortDirection = this.toggleSortDirection(this.orderBy.sortDirection); - } - this.orderBy.sortBy = key; + // if (this.orderBy.sortBy === key) { + // this.orderBy.sortDirection = this.toggleSortDirection(this.orderBy.sortDirection); + // } + // this.orderBy.sortBy = key; } } @@ -193,12 +195,12 @@ export class TaskQueryComponent implements OnInit { this.requestInProgressService.setRequestInProgress(true); this.calculateQueryPages(); this.taskQuerySubscription = this.taskQueryService - .queryTask( - this.orderBy.sortBy.replace(/([A-Z])|([0-9])/g, (g) => `-${g[0].toLowerCase()}`), - this.orderBy.sortDirection, - new TaskHistoryEventData(this.taskQueryForm.value), - false - ) + .getTaskHistoryEvents + // this.orderBy.sortBy.replace(/([A-Z])|([0-9])/g, (g) => `-${g[0].toLowerCase()}`), + // this.orderBy.sortDirection, + // new TaskHistoryEventData(this.taskQueryForm.value), + // false + () .subscribe((taskQueryResource) => { this.requestInProgressService.setRequestInProgress(false); this.taskQueryResource = taskQueryResource.taskHistoryEvents ? taskQueryResource : null; diff --git a/web/src/app/monitor/components/classification-report/classification-report.component.ts b/web/src/app/monitor/components/classification-report/classification-report.component.ts index ce7a679b0..1c960e1da 100644 --- a/web/src/app/monitor/components/classification-report/classification-report.component.ts +++ b/web/src/app/monitor/components/classification-report/classification-report.component.ts @@ -1,5 +1,5 @@ import { Component, OnInit } from '@angular/core'; -import { RestConnectorService } from 'app/monitor/services/rest-connector.service'; +import { MonitorService } from 'app/monitor/services/monitor.service'; import { ChartData } from 'app/monitor/models/chart-data'; import { ReportData } from '../../models/report-data'; import { ChartColorsDefinition } from '../../models/chart-colors'; @@ -22,7 +22,7 @@ export class ClassificationReportComponent implements OnInit { lineChartColors = ChartColorsDefinition.getColors(); - constructor(private restConnectorService: RestConnectorService) {} + constructor(private restConnectorService: MonitorService) {} async ngOnInit() { this.reportData = await this.restConnectorService.getClassificationTasksReport().toPromise(); diff --git a/web/src/app/monitor/components/report-table/report-table.component.html b/web/src/app/monitor/components/report-table/report-table.component.html index cd4b16444..e393e6c96 100644 --- a/web/src/app/monitor/components/report-table/report-table.component.html +++ b/web/src/app/monitor/components/report-table/report-table.component.html @@ -8,7 +8,7 @@
{{header}}
-
{{reportData.meta.totalDesc}}
+
{{reportData.meta.sumRowDesc}}
diff --git a/web/src/app/monitor/components/task-report/task-report.component.ts b/web/src/app/monitor/components/task-report/task-report.component.ts index 1a5ba3f72..8e6cb837c 100644 --- a/web/src/app/monitor/components/task-report/task-report.component.ts +++ b/web/src/app/monitor/components/task-report/task-report.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { ReportData } from 'app/monitor/models/report-data'; -import { RestConnectorService } from '../../services/rest-connector.service'; +import { MonitorService } from '../../services/monitor.service'; @Component({ selector: 'taskana-monitor-task-report', @@ -13,7 +13,7 @@ export class TaskReportComponent implements OnInit { pieChartType = 'pie'; reportData: ReportData; - constructor(private restConnectorService: RestConnectorService) {} + constructor(private restConnectorService: MonitorService) {} async ngOnInit() { this.reportData = await this.restConnectorService.getTaskStatusReport().toPromise(); diff --git a/web/src/app/monitor/components/timestamp-report/timestamp-report.component.ts b/web/src/app/monitor/components/timestamp-report/timestamp-report.component.ts index 08f4f357d..36de12a94 100644 --- a/web/src/app/monitor/components/timestamp-report/timestamp-report.component.ts +++ b/web/src/app/monitor/components/timestamp-report/timestamp-report.component.ts @@ -1,6 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { ReportData } from '../../models/report-data'; -import { RestConnectorService } from '../../services/rest-connector.service'; +import { MonitorService } from '../../services/monitor.service'; @Component({ selector: 'taskana-monitor-timestamp-report', @@ -10,7 +10,7 @@ import { RestConnectorService } from '../../services/rest-connector.service'; export class TimestampReportComponent implements OnInit { reportData: ReportData; - constructor(private restConnectorService: RestConnectorService) {} + constructor(private restConnectorService: MonitorService) {} ngOnInit() { this.restConnectorService.getDailyEntryExitReport().subscribe((data: ReportData) => { diff --git a/web/src/app/monitor/components/workbasket-report-due-date/workbasket-report-due-date.component.ts b/web/src/app/monitor/components/workbasket-report-due-date/workbasket-report-due-date.component.ts index d2cdde20d..3265f782a 100644 --- a/web/src/app/monitor/components/workbasket-report-due-date/workbasket-report-due-date.component.ts +++ b/web/src/app/monitor/components/workbasket-report-due-date/workbasket-report-due-date.component.ts @@ -2,7 +2,7 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core'; import { ReportData } from '../../models/report-data'; import { ChartData } from '../../models/chart-data'; import { ChartColorsDefinition } from '../../models/chart-colors'; -import { RestConnectorService } from '../../services/rest-connector.service'; +import { MonitorService } from '../../services/monitor.service'; import { MetaInfoData } from '../../models/meta-info-data'; @Component({ @@ -26,7 +26,7 @@ export class WorkbasketReportDueDateComponent implements OnInit { lineChartColors = ChartColorsDefinition.getColors(); - constructor(private restConnectorService: RestConnectorService) {} + constructor(private restConnectorService: MonitorService) {} async ngOnInit() { this.reportData = await this.restConnectorService.getWorkbasketStatisticsQueryingByDueDate().toPromise(); diff --git a/web/src/app/monitor/components/workbasket-report-planned-date/workbasket-report-planned-date.component.ts b/web/src/app/monitor/components/workbasket-report-planned-date/workbasket-report-planned-date.component.ts index b89e81c2a..4423650ff 100644 --- a/web/src/app/monitor/components/workbasket-report-planned-date/workbasket-report-planned-date.component.ts +++ b/web/src/app/monitor/components/workbasket-report-planned-date/workbasket-report-planned-date.component.ts @@ -2,7 +2,7 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core'; import { ReportData } from '../../models/report-data'; import { ChartData } from '../../models/chart-data'; import { ChartColorsDefinition } from '../../models/chart-colors'; -import { RestConnectorService } from '../../services/rest-connector.service'; +import { MonitorService } from '../../services/monitor.service'; import { MetaInfoData } from '../../models/meta-info-data'; @Component({ @@ -27,7 +27,7 @@ export class WorkbasketReportPlannedDateComponent implements OnInit { lineChartColors = ChartColorsDefinition.getColors(); - constructor(private restConnectorService: RestConnectorService) {} + constructor(private restConnectorService: MonitorService) {} async ngOnInit() { this.reportData = await this.restConnectorService.getWorkbasketStatisticsQueryingByPlannedDate().toPromise(); diff --git a/web/src/app/monitor/models/meta-info-data.ts b/web/src/app/monitor/models/meta-info-data.ts index 897f61480..671c418bb 100644 --- a/web/src/app/monitor/models/meta-info-data.ts +++ b/web/src/app/monitor/models/meta-info-data.ts @@ -3,5 +3,5 @@ export class MetaInfoData { date: string; header: Array; rowDesc: Array; - totalDesc: string; + sumRowDesc: string; } diff --git a/web/src/app/monitor/monitor.module.ts b/web/src/app/monitor/monitor.module.ts index a54dc5248..8562f700e 100644 --- a/web/src/app/monitor/monitor.module.ts +++ b/web/src/app/monitor/monitor.module.ts @@ -17,7 +17,7 @@ import { TaskReportComponent } from './components/task-report/task-report.compon import { ClassificationReportComponent } from './components/classification-report/classification-report.component'; import { TimestampReportComponent } from './components/timestamp-report/timestamp-report.component'; -import { RestConnectorService } from './services/rest-connector.service'; +import { MonitorService } from './services/monitor.service'; import { WorkbasketReportComponent } from './components/workbasket-report/workbasket-report.component'; import { WorkbasketReportPlannedDateComponent } from './components/workbasket-report-planned-date/workbasket-report-planned-date.component'; @@ -51,6 +51,6 @@ const DECLARATIONS = [ @NgModule({ declarations: DECLARATIONS, imports: MODULES, - providers: [RestConnectorService, MapToIterable] + providers: [MonitorService, MapToIterable] }) export class MonitorModule {} diff --git a/web/src/app/monitor/services/rest-connector.service.ts b/web/src/app/monitor/services/monitor.service.ts similarity index 70% rename from web/src/app/monitor/services/rest-connector.service.ts rename to web/src/app/monitor/services/monitor.service.ts index e1169e162..9b6fac3b7 100644 --- a/web/src/app/monitor/services/rest-connector.service.ts +++ b/web/src/app/monitor/services/monitor.service.ts @@ -4,28 +4,40 @@ import { environment } from 'environments/environment'; import { Observable } from 'rxjs'; import { ChartData } from 'app/monitor/models/chart-data'; import { ReportData } from '../models/report-data'; +import { asUrlQueryString } from '../../shared/util/query-parameters-v2'; +import { TaskState } from '../../shared/models/task-state'; const monitorUrl = '/v1/monitor/'; @Injectable() -export class RestConnectorService { +export class MonitorService { constructor(private httpClient: HttpClient) {} getTaskStatusReport(): Observable { + const queryParams = { + states: [TaskState.READY, TaskState.CLAIMED, TaskState.COMPLETED] + }; return this.httpClient.get( - `${environment.taskanaRestUrl + monitorUrl}tasks-status-report?states=READY,CLAIMED,COMPLETED` + `${environment.taskanaRestUrl + monitorUrl}tasks-status-report${asUrlQueryString(queryParams)}` ); } getWorkbasketStatisticsQueryingByDueDate(): Observable { + const queryParams = { + states: [TaskState.READY, TaskState.CLAIMED, TaskState.COMPLETED] + }; return this.httpClient.get( - `${environment.taskanaRestUrl + monitorUrl}tasks-workbasket-report?states=READY,CLAIMED,COMPLETED` + `${environment.taskanaRestUrl + monitorUrl}tasks-workbasket-report${asUrlQueryString(queryParams)}` ); } getWorkbasketStatisticsQueryingByPlannedDate(): Observable { + const queryParams = { + daysInPast: 7, + states: [TaskState.READY, TaskState.CLAIMED, TaskState.COMPLETED] + }; return this.httpClient.get( - `${environment.taskanaRestUrl}/v1/monitor/tasks-workbasket-planned-date-report?daysInPast=7&states=READY,CLAIMED,COMPLETED` + `${environment.taskanaRestUrl}/v1/monitor/tasks-workbasket-planned-date-report${asUrlQueryString(queryParams)}` ); } diff --git a/web/src/app/shared/components/filter/filter.component.html b/web/src/app/shared/components/filter/filter.component.html deleted file mode 100644 index 4d0f8ca10..000000000 --- a/web/src/app/shared/components/filter/filter.component.html +++ /dev/null @@ -1,127 +0,0 @@ - -
- - -
- - - Filter by name - - - - - - - - -
- - - -
- - -
-
- - Filter by name - - - - - Filter by key - - -
- -
- - Filter by description - - - - - Filter by owner - - -
-
- - - -
- - - - - - - - - - - - - -
-
-
- - - - -
-
- -
-
- -
-
- -
- -
-
- - -
-
- diff --git a/web/src/app/shared/components/filter/filter.component.ts b/web/src/app/shared/components/filter/filter.component.ts deleted file mode 100644 index 546015861..000000000 --- a/web/src/app/shared/components/filter/filter.component.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; -import { ICONTYPES } from 'app/shared/models/icon-types'; -import { Filter } from 'app/shared/models/filter'; -import { TaskanaType } from 'app/shared/models/taskana-type'; - -@Component({ - selector: 'taskana-shared-filter', - templateUrl: './filter.component.html', - styleUrls: ['./filter.component.scss'] -}) -export class FilterComponent implements OnInit { - @Input() component: string; - @Input() allTypes: Map = new Map([ - [ICONTYPES.ALL, 'All'], - [ICONTYPES.PERSONAL, 'Personal'], - [ICONTYPES.GROUP, 'Group'], - [ICONTYPES.CLEARANCE, 'Clearance'], - [ICONTYPES.TOPIC, 'Topic'] - ]); - - @Input() allStates: Map = new Map([ - ['ALL', 'All'], - ['READY', 'Ready'], - ['CLAIMED', 'Claimed'], - ['COMPLETED', 'Completed'] - ]); - - @Input() filterParams = { name: '', key: '', type: '', description: '', owner: '' }; - @Input() filterType = TaskanaType.WORKBASKETS; - @Input() isExpanded = true; - @Output() performFilter = new EventEmitter(); - @Output() inputComponent = new EventEmitter(); - - filter: Filter; - filterParamKeys = []; - lastFilterKey: string; - toggleDropDown = false; - - ngOnInit(): void { - this.initializeFilterModel(); - if (this.filterParams) { - this.filterParamKeys = Object.keys(this.filterParams); - this.lastFilterKey = this.filterParamKeys[this.filterParamKeys.length - 1]; - } - } - - selectType(type: ICONTYPES) { - this.filter.filterParams.type = type === ICONTYPES.ALL ? '' : type; - } - - selectState(state: ICONTYPES) { - this.filter.filterParams.state = state === 'ALL' ? '' : state; - } - - clear() { - Object.keys(this.filterParams).forEach((key) => { - this.filterParams[key] = ''; - }); - this.initializeFilterModel(); - } - - search() { - this.inputComponent.emit(this.component); - this.performFilter.emit(this.filter); - } - - initializeFilterModel(): void { - this.filter = new Filter(this.filterParams); - } - - checkUppercaseFilterType(filterType: string) { - return filterType === 'type' || filterType === 'state'; - } - - filterTypeIsWorkbasket(): boolean { - return this.filterType === TaskanaType.WORKBASKETS; - } - - /** - * keys that are hardcoded in the HTML need to be specified here - * @returns {string[]} - */ - getUnusedKeys(): string[] { - return Object.keys(this.filterParamKeys).filter((key) => ['name', 'key', 'type'].indexOf(key) < 0); - } -} diff --git a/web/src/app/shared/components/popup/dialog-pop-up.component.ts b/web/src/app/shared/components/popup/dialog-pop-up.component.ts index 6a5682018..dc349f2f1 100644 --- a/web/src/app/shared/components/popup/dialog-pop-up.component.ts +++ b/web/src/app/shared/components/popup/dialog-pop-up.component.ts @@ -29,9 +29,9 @@ export class DialogPopUpComponent implements OnInit { } initError() { - this.title = notifications.get(this.data.key).name || ''; + this.title = notifications.get(this.data.key).left || ''; this.message = - notifications.get(this.data.key).text || (this.data && this.data.passedError && this.data.passedError.error) + notifications.get(this.data.key).right || (this.data && this.data.passedError && this.data.passedError.error) ? this.data.passedError.error.message : ''; if (this.data.additions) { diff --git a/web/src/app/shared/components/sort/sort.component.html b/web/src/app/shared/components/sort/sort.component.html index 98cc05b28..28a5ad953 100644 --- a/web/src/app/shared/components/sort/sort.component.html +++ b/web/src/app/shared/components/sort/sort.component.html @@ -10,8 +10,8 @@ - - +
+
+ + +
diff --git a/web/src/app/shared/components/task-filter/task-filter.component.scss b/web/src/app/shared/components/task-filter/task-filter.component.scss new file mode 100644 index 000000000..e69de29bb diff --git a/web/src/app/shared/components/task-filter/task-filter.component.ts b/web/src/app/shared/components/task-filter/task-filter.component.ts new file mode 100644 index 000000000..dda3ec010 --- /dev/null +++ b/web/src/app/shared/components/task-filter/task-filter.component.ts @@ -0,0 +1,36 @@ +import { Component, EventEmitter, OnInit, Output } from '@angular/core'; +import { ALL_STATES, TaskState } from '../../models/task-state'; +import { TaskQueryFilterParameter } from '../../models/task-query-filter-parameter'; + +@Component({ + selector: 'taskana-shared-task-filter', + templateUrl: './task-filter.component.html', + styleUrls: ['./task-filter.component.scss'] +}) +export class TaskFilterComponent implements OnInit { + filter: TaskQueryFilterParameter; + + @Output() performFilter = new EventEmitter(); + + allStates: Map = ALL_STATES; + + ngOnInit(): void { + this.clear(); + } + + selectState(state: TaskState) { + this.filter.state = state ? [state] : []; + } + + search() { + this.performFilter.emit(this.filter); + } + + clear() { + this.filter = { + priority: [], + 'name-like': [], + 'owner-like': [] + }; + } +} diff --git a/web/src/app/shared/components/toast/toast.component.ts b/web/src/app/shared/components/toast/toast.component.ts index 9b32e910e..b9d28d2df 100644 --- a/web/src/app/shared/components/toast/toast.component.ts +++ b/web/src/app/shared/components/toast/toast.component.ts @@ -15,7 +15,7 @@ export class ToastComponent implements OnInit { ngOnInit(): void { if (this.data) { - this.message = notifications.get(this.data.key).text; + this.message = notifications.get(this.data.key).right; if (this.data.additions) { this.data.additions.forEach((value: string, replacementKey: string) => { this.message = this.message.replace(`{${replacementKey}}`, value); diff --git a/web/src/app/shared/components/workbasket-filter/workbasket-filter.component.html b/web/src/app/shared/components/workbasket-filter/workbasket-filter.component.html new file mode 100644 index 000000000..2e6b1cd5e --- /dev/null +++ b/web/src/app/shared/components/workbasket-filter/workbasket-filter.component.html @@ -0,0 +1,89 @@ +
+ +
+ + + Filter by name + + + + + + + + +
+ + + +
+ + +
+
+ + Filter by name + + + + + Filter by key + + +
+ +
+ + Filter by description + + + + + Filter by owner + + +
+
+ + + +
+ + + + + + + + + + + + + +
+
+
diff --git a/web/src/app/shared/components/filter/filter.component.scss b/web/src/app/shared/components/workbasket-filter/workbasket-filter.component.scss similarity index 60% rename from web/src/app/shared/components/filter/filter.component.scss rename to web/src/app/shared/components/workbasket-filter/workbasket-filter.component.scss index bfdce6066..d10d58407 100644 --- a/web/src/app/shared/components/filter/filter.component.scss +++ b/web/src/app/shared/components/workbasket-filter/workbasket-filter.component.scss @@ -18,15 +18,10 @@ margin: 0 8px 0 8px; } -.filter__action-buttons { - width: 100%; - padding: 10px 0; - display: flex; - justify-content: space-between; -} .filter__undo-buttons { margin-left: 4px; } + .filter__search-button { background: $aquamarine; color: white; @@ -38,37 +33,15 @@ justify-content: space-between; } -.dropdown-menu-users { - & > li { - margin-bottom: 5px; - } - margin-left: 15px; +.filter__action-buttons { + width: 100%; + padding: 10px 0; + display: flex; + justify-content: space-between; } -.list-group-search { - padding: 0 15px; - border-top: 1px solid #ddd; -} -.list-group-search { - border-top: none; -} - -row.padding { - padding: 1px 0; -} - -.filter-list { - list-style-type: none; -} - -.filter-list > li { - padding-top: 5px; -} - -.blue { - color: #2e9eca; -} - -button.btn.btn-default.pull-right.margin-right { - margin-top: 1px; +.filter__search-button { + background: $aquamarine; + color: white; + margin-left: 4px; } diff --git a/web/src/app/shared/components/workbasket-filter/workbasket-filter.component.ts b/web/src/app/shared/components/workbasket-filter/workbasket-filter.component.ts new file mode 100644 index 000000000..02062aa1a --- /dev/null +++ b/web/src/app/shared/components/workbasket-filter/workbasket-filter.component.ts @@ -0,0 +1,42 @@ +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { ALL_TYPES, WorkbasketType } from '../../models/workbasket-type'; +import { WorkbasketQueryFilterParameter } from '../../models/workbasket-query-parameters'; +import { Pair } from '../../models/pair'; + +@Component({ + selector: 'taskana-shared-workbasket-filter', + templateUrl: './workbasket-filter.component.html', + styleUrls: ['./workbasket-filter.component.scss'] +}) +export class WorkbasketFilterComponent implements OnInit { + allTypes: Map = ALL_TYPES; + + @Input() component: string; + @Input() isExpanded: boolean; + + @Output() performFilter = new EventEmitter>(); + + filter: WorkbasketQueryFilterParameter; + + ngOnInit(): void { + this.clear(); + } + + clear() { + this.filter = { + 'name-like': [], + 'key-like': [], + 'description-like': [], + 'owner-like': [], + type: [] + }; + } + + selectType(type: WorkbasketType) { + this.filter.type = type ? [type] : []; + } + + search() { + this.performFilter.emit({ left: this.component, right: this.filter }); + } +} diff --git a/web/src/app/shared/models/classification-query-filter-parameters.ts b/web/src/app/shared/models/classification-query-filter-parameters.ts new file mode 100644 index 000000000..3bbb0268f --- /dev/null +++ b/web/src/app/shared/models/classification-query-filter-parameters.ts @@ -0,0 +1,16 @@ +export interface ClassificationQueryFilterParameters { + name?: string[]; + 'name-like'?: string[]; + key?: string[]; + category?: string[]; + domain?: string[]; + type?: string[]; + 'custom-1-like'?: string[]; + 'custom-2-like'?: string[]; + 'custom-3-like'?: string[]; + 'custom-4-like'?: string[]; + 'custom-5-like'?: string[]; + 'custom-6-like'?: string[]; + 'custom-7-like'?: string[]; + 'custom-8-like'?: string[]; +} diff --git a/web/src/app/shared/models/error-model.ts b/web/src/app/shared/models/error-model.ts index 6ed7091d7..9081c5c29 100644 --- a/web/src/app/shared/models/error-model.ts +++ b/web/src/app/shared/models/error-model.ts @@ -7,8 +7,8 @@ export class ErrorModel { public readonly message: string; constructor(key: NOTIFICATION_TYPES, passedError?: HttpErrorResponse, addition?: Map) { - this.title = notifications.get(key).name; - let messageTemp = notifications.get(key).text; + this.title = notifications.get(key).left; + let messageTemp = notifications.get(key).right; this.errObj = passedError; if (addition) { addition.forEach((value: string, replacementKey: string) => { diff --git a/web/src/app/shared/models/filter.ts b/web/src/app/shared/models/filter.ts deleted file mode 100644 index 24adca95a..000000000 --- a/web/src/app/shared/models/filter.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class Filter { - filterParams: any; - - constructor(filterParams?: any) { - this.filterParams = filterParams; - } -} diff --git a/web/src/app/shared/models/icon-types.ts b/web/src/app/shared/models/icon-types.ts deleted file mode 100644 index 9a4d2b032..000000000 --- a/web/src/app/shared/models/icon-types.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum ICONTYPES { - ALL = 'ALL', - PERSONAL = 'PERSONAL', - GROUP = 'GROUP', - CLEARANCE = 'CLEARANCE', - TOPIC = 'TOPIC' -} diff --git a/web/src/app/shared/models/notifications.ts b/web/src/app/shared/models/notifications.ts index 08dfdb22d..e9937e4ed 100644 --- a/web/src/app/shared/models/notifications.ts +++ b/web/src/app/shared/models/notifications.ts @@ -59,163 +59,194 @@ export enum NOTIFICATION_TYPES { WARNING_CANT_COPY } -export const notifications = new Map([ +export const notifications = new Map>([ // access-items-management.component.ts - [NOTIFICATION_TYPES.FETCH_ERR, new Pair('There was an error while retrieving your access ids with groups.', '')], + [ + NOTIFICATION_TYPES.FETCH_ERR, + { left: 'There was an error while retrieving your access ids with groups.', right: '' } + ], // access-items-management.component.ts - [NOTIFICATION_TYPES.FETCH_ERR_2, new Pair('There was an error while retrieving your access items ', '')], + [NOTIFICATION_TYPES.FETCH_ERR_2, { left: 'There was an error while retrieving your access items ', right: '' }], // access-items-management.component.ts - [NOTIFICATION_TYPES.DELETE_ERR, new Pair("You can't delete a group", '')], + [NOTIFICATION_TYPES.DELETE_ERR, { left: "You can't delete a group", right: '' }], // classification-details.component - [NOTIFICATION_TYPES.CREATE_ERR, new Pair('There was an error while creating this classification', '')], + [NOTIFICATION_TYPES.CREATE_ERR, { left: 'There was an error while creating this classification', right: '' }], // classification-details.component - [NOTIFICATION_TYPES.REMOVE_ERR, new Pair('There was an error while removing your classification', '')], + [NOTIFICATION_TYPES.REMOVE_ERR, { left: 'There was an error while removing your classification', right: '' }], // classification-details.component - [NOTIFICATION_TYPES.SAVE_ERR, new Pair('There was an error while saving your classification', '')], + [NOTIFICATION_TYPES.SAVE_ERR, { left: 'There was an error while saving your classification', right: '' }], // classification-details.component [ NOTIFICATION_TYPES.SELECT_ERR, - new Pair('There is no classification selected', 'Please check if you are creating a classification') + { left: 'There is no classification selected', right: 'Please check if you are creating a classification' } ], // import-export.component - [NOTIFICATION_TYPES.FILE_ERR, new Pair('Wrong format', 'This file format is not allowed! Please use a .json file.')], + [ + NOTIFICATION_TYPES.FILE_ERR, + { left: 'Wrong format', right: 'This file format is not allowed! Please use a .json file.' } + ], // import-export.component [ NOTIFICATION_TYPES.IMPORT_ERR_1, - new Pair('Import was not successful', 'Import was not successful, you have no access to apply this operation.') + { + left: 'Import was not successful', + right: 'Import was not successful, you have no access to apply this operation.' + } ], // import-export.component [ NOTIFICATION_TYPES.IMPORT_ERR_2, - new Pair('Import was not successful', 'Import was not successful, operation was not found.') + { left: 'Import was not successful', right: 'Import was not successful, operation was not found.' } ], // import-export.component [ NOTIFICATION_TYPES.IMPORT_ERR_3, - new Pair('Import was not successful', 'Import was not successful, operation has some conflicts.') + { left: 'Import was not successful', right: 'Import was not successful, operation has some conflicts.' } ], // import-export.component [ NOTIFICATION_TYPES.IMPORT_ERR_4, - new Pair('Import was not successful', 'Import was not successful, maximum file size exceeded.') + { left: 'Import was not successful', right: 'Import was not successful, maximum file size exceeded.' } ], // import-export.component [ NOTIFICATION_TYPES.UPLOAD_ERR, - new Pair( - 'Upload failed', - `The upload didn't proceed sucessfully. + { + left: 'Upload failed', + right: `The upload didn't proceed sucessfully. \n The uploaded file probably exceeded the maximum file size of 10 MB.` - ) + } ], // taskdetails.component - [NOTIFICATION_TYPES.FETCH_ERR_3, new Pair('', 'An error occurred while fetching the task')], + [NOTIFICATION_TYPES.FETCH_ERR_3, { left: '', right: 'An error occurred while fetching the task' }], // workbasket-details.component - [NOTIFICATION_TYPES.FETCH_ERR_4, new Pair('', 'An error occurred while fetching the workbasket')], + [NOTIFICATION_TYPES.FETCH_ERR_4, { left: '', right: 'An error occurred while fetching the workbasket' }], // access-items.component - [NOTIFICATION_TYPES.SAVE_ERR_2, new Pair("There was an error while saving your workbasket's access items", '')], + [ + NOTIFICATION_TYPES.SAVE_ERR_2, + { left: "There was an error while saving your workbasket's access items", right: '' } + ], // workbaskets-distribution-targets.component [ NOTIFICATION_TYPES.SAVE_ERR_3, - new Pair("There was an error while saving your workbasket's distribution targets", '') + { left: "There was an error while saving your workbasket's distribution targets", right: '' } ], // workbasket-information.component [ NOTIFICATION_TYPES.REMOVE_ERR_2, - new Pair('There was an error removing distribution target for {workbasketId}.', '') + { left: 'There was an error removing distribution target for {workbasketId}.', right: '' } ], // workbasket-information.component - [NOTIFICATION_TYPES.SAVE_ERR_4, new Pair('There was an error while saving your workbasket', '')], + [NOTIFICATION_TYPES.SAVE_ERR_4, { left: 'There was an error while saving your workbasket', right: '' }], // workbasket-information.component - [NOTIFICATION_TYPES.CREATE_ERR_2, new Pair('There was an error while creating this workbasket', '')], + [NOTIFICATION_TYPES.CREATE_ERR_2, { left: 'There was an error while creating this workbasket', right: '' }], // workbasket-information.component [ NOTIFICATION_TYPES.MARK_ERR, - new Pair( - 'Workbasket was marked for deletion.', - 'The Workbasket {workbasketId} still contains completed tasks and could not be deleted.' + + { + left: 'Workbasket was marked for deletion.', + right: + 'The Workbasket {workbasketId} still contains completed tasks and could not be deleted.' + ' Instead is was marked for deletion and will be deleted automatically ' + 'as soon as the completed tasks are cleared from the database.' - ) + } ], // domain.guard [ NOTIFICATION_TYPES.FETCH_ERR_5, - new Pair('There was an error, please contact your administrator', 'There was an error getting Domains') + { left: 'There was an error, please contact your administrator', right: 'There was an error getting Domains' } ], // history.guard [ NOTIFICATION_TYPES.FETCH_ERR_6, - new Pair('There was an error, please contact your administrator', 'There was an error getting history provider') + { + left: 'There was an error, please contact your administrator', + right: 'There was an error getting history provider' + } ], // http-client-interceptor.service - [NOTIFICATION_TYPES.ACCESS_ERR, new Pair('You have no access to this resource', '')], + [NOTIFICATION_TYPES.ACCESS_ERR, { left: 'You have no access to this resource', right: '' }], // http-client-interceptor.service - [NOTIFICATION_TYPES.GENERAL_ERR, new Pair('There was an error, please contact your administrator', '')], + [NOTIFICATION_TYPES.GENERAL_ERR, { left: 'There was an error, please contact your administrator', right: '' }], // spinner.component [ NOTIFICATION_TYPES.TIMEOUT_ERR, - new Pair( - 'There was an error with your request, please make sure you have internet connection', - 'Request time exceeded' - ) + { + left: 'There was an error with your request, please make sure you have internet connection', + right: 'Request time exceeded' + } ], // taskdetails.component - [NOTIFICATION_TYPES.FETCH_ERR_7, new Pair('An error occurred while fetching the task', '')], + [NOTIFICATION_TYPES.FETCH_ERR_7, { left: 'An error occurred while fetching the task', right: '' }], // taskdetails.component - [NOTIFICATION_TYPES.DELETE_ERR_2, new Pair('An error occurred while deleting the task', '')], + [NOTIFICATION_TYPES.DELETE_ERR_2, { left: 'An error occurred while deleting the task', right: '' }], // ALERTS // access-items-management.component - [NOTIFICATION_TYPES.SUCCESS_ALERT, new Pair('', '{accessId} was removed successfully')], + [NOTIFICATION_TYPES.SUCCESS_ALERT, { left: '', right: '{accessId} was removed successfully' }], // classification-details.component - [NOTIFICATION_TYPES.SUCCESS_ALERT_2, new Pair('', 'Classification {classificationKey} was created successfully')], + [ + NOTIFICATION_TYPES.SUCCESS_ALERT_2, + { left: '', right: 'Classification {classificationKey} was created successfully' } + ], // classification-details.component - [NOTIFICATION_TYPES.SUCCESS_ALERT_3, new Pair('', 'Classification {classificationKey} was saved successfully')], + [ + NOTIFICATION_TYPES.SUCCESS_ALERT_3, + { left: '', right: 'Classification {classificationKey} was saved successfully' } + ], // classification-details.component // access-items.component // workbasket.distribution-targets.component // workbasket-information.component // taskdetails.component - [NOTIFICATION_TYPES.INFO_ALERT, new Pair('', 'Information restored')], + [NOTIFICATION_TYPES.INFO_ALERT, { left: '', right: 'Information restored' }], // classification-details.component - [NOTIFICATION_TYPES.SUCCESS_ALERT_4, new Pair('', 'Classification {classificationKey} was removed successfully')], + [ + NOTIFICATION_TYPES.SUCCESS_ALERT_4, + { left: '', right: 'Classification {classificationKey} was removed successfully' } + ], // classification-list.component - [NOTIFICATION_TYPES.SUCCESS_ALERT_5, new Pair('', 'Classification {classificationKey} was moved successfully')], + [ + NOTIFICATION_TYPES.SUCCESS_ALERT_5, + { left: '', right: 'Classification {classificationKey} was moved successfully' } + ], // import-export.component - [NOTIFICATION_TYPES.SUCCESS_ALERT_6, new Pair('', 'Import was successful')], + [NOTIFICATION_TYPES.SUCCESS_ALERT_6, { left: '', right: 'Import was successful' }], // access-items.component - [NOTIFICATION_TYPES.SUCCESS_ALERT_7, new Pair('', 'Workbasket {workbasketKey} Access items were saved successfully')], + [ + NOTIFICATION_TYPES.SUCCESS_ALERT_7, + { left: '', right: 'Workbasket {workbasketKey} Access items were saved successfully' } + ], // workbasket.distribution-targets.component [ NOTIFICATION_TYPES.SUCCESS_ALERT_8, - new Pair('', 'Workbasket {workbasketName} Distribution targets were saved successfully') + { left: '', right: 'Workbasket {workbasketName} Distribution targets were saved successfully' } ], // workbasket-information.component [ NOTIFICATION_TYPES.SUCCESS_ALERT_9, - new Pair('', 'DistributionTargets for workbasketID {workbasketId} was removed successfully') + { left: '', right: 'DistributionTargets for workbasketID {workbasketId} was removed successfully' } ], // workbasket-information.component - [NOTIFICATION_TYPES.SUCCESS_ALERT_10, new Pair('', 'Workbasket {workbasketKey} was saved successfully')], + [NOTIFICATION_TYPES.SUCCESS_ALERT_10, { left: '', right: 'Workbasket {workbasketKey} was saved successfully' }], // workbasket-information.component - [NOTIFICATION_TYPES.SUCCESS_ALERT_11, new Pair('', 'Workbasket {workbasketKey} was created successfully')], + [NOTIFICATION_TYPES.SUCCESS_ALERT_11, { left: '', right: 'Workbasket {workbasketKey} was created successfully' }], // workbasket-information.component - [NOTIFICATION_TYPES.SUCCESS_ALERT_12, new Pair('', 'The Workbasket {workbasketId} has been deleted.')], + [NOTIFICATION_TYPES.SUCCESS_ALERT_12, { left: '', right: 'The Workbasket {workbasketId} has been deleted.' }], // forms-validator.service - [NOTIFICATION_TYPES.WARNING_ALERT, new Pair('', 'There are some empty fields which are required.')], + [NOTIFICATION_TYPES.WARNING_ALERT, { left: '', right: 'There are some empty fields which are required.' }], // forms-validator.service x2 - [NOTIFICATION_TYPES.WARNING_ALERT_2, new Pair('', 'The {owner} introduced is not valid.')], + [NOTIFICATION_TYPES.WARNING_ALERT_2, { left: '', right: 'The {owner} introduced is not valid.' }], // taskdetails.component - [NOTIFICATION_TYPES.DANGER_ALERT, new Pair('', 'There was an error while updating.')], + [NOTIFICATION_TYPES.DANGER_ALERT, { left: '', right: 'There was an error while updating.' }], // taskdetails.component - [NOTIFICATION_TYPES.SUCCESS_ALERT_13, new Pair('', 'Task {taskId} was created successfully.')], + [NOTIFICATION_TYPES.SUCCESS_ALERT_13, { left: '', right: 'Task {taskId} was created successfully.' }], // taskdetails.component - [NOTIFICATION_TYPES.SUCCESS_ALERT_14, new Pair('', 'Updating was successful.')], + [NOTIFICATION_TYPES.SUCCESS_ALERT_14, { left: '', right: 'Updating was successful.' }], // taskdetails.component - [NOTIFICATION_TYPES.DANGER_ALERT_2, new Pair('', 'There was an error while creating a new task.')], + [NOTIFICATION_TYPES.DANGER_ALERT_2, { left: '', right: 'There was an error while creating a new task.' }], // task-master.component - [NOTIFICATION_TYPES.INFO_ALERT_2, new Pair('', 'The selected Workbasket is empty!')], - [NOTIFICATION_TYPES.WARNING_CANT_COPY, new Pair('', "Can't copy a not created classification")] + [NOTIFICATION_TYPES.INFO_ALERT_2, { left: '', right: 'The selected Workbasket is empty!' }], + [NOTIFICATION_TYPES.WARNING_CANT_COPY, { left: '', right: "Can't copy a not created classification" }] ]); diff --git a/web/src/app/shared/models/pair.ts b/web/src/app/shared/models/pair.ts index 22b30d4d7..fff314f55 100644 --- a/web/src/app/shared/models/pair.ts +++ b/web/src/app/shared/models/pair.ts @@ -1,3 +1,4 @@ -export class Pair { - constructor(public name?: string, public text?: string) {} +export interface Pair { + left: L; + right: R; } diff --git a/web/src/app/shared/models/query-paging-parameter.ts b/web/src/app/shared/models/query-paging-parameter.ts new file mode 100644 index 000000000..369546568 --- /dev/null +++ b/web/src/app/shared/models/query-paging-parameter.ts @@ -0,0 +1,4 @@ +export interface QueryPagingParameter { + page?: number; + 'page-size'?: number; +} diff --git a/web/src/app/shared/models/sorting.ts b/web/src/app/shared/models/sorting.ts index 309ed7e82..1c6dc7b6a 100644 --- a/web/src/app/shared/models/sorting.ts +++ b/web/src/app/shared/models/sorting.ts @@ -1,13 +1,87 @@ export enum Direction { - ASC = 'asc', - DESC = 'desc' + ASC = 'ASCENDING', + DESC = 'DESCENDING' } -export class Sorting { - sortBy: string; - sortDirection: string; - constructor(sortBy: string = 'key', sortDirection: Direction = Direction.ASC) { - this.sortBy = sortBy; - this.sortDirection = sortDirection; - } +export interface Sorting { + 'sort-by': T; + order: Direction; +} + +export enum TaskQuerySortParameter { + CLASSIFICATION_KEY = 'CLASSIFICATION_KEY', + POR_TYPE = 'POR_TYPE', + POR_VALUE = 'POR_VALUE', + STATE = 'STATE', + NAME = 'NAME', + DUE = 'DUE', + PLANNED = 'PLANNED', + PRIORITY = 'PRIORITY' +} + +export const TASK_SORT_PARAMETER_NAMING: Map = new Map([ + [TaskQuerySortParameter.NAME, 'Name'], + [TaskQuerySortParameter.PRIORITY, 'Priority'], + [TaskQuerySortParameter.DUE, 'Due'], + [TaskQuerySortParameter.PLANNED, 'Planned'] +]); + +export enum WorkbasketQuerySortParameter { + NAME = 'NAME', + KEY = 'KEY', + OWNER = 'OWNER', + TYPE = 'TYPE', + DESCRIPTION = 'DESCRIPTION' +} + +export const WORKBASKET_SORT_PARAMETER_NAMING: Map = new Map([ + [WorkbasketQuerySortParameter.NAME, 'Name'], + [WorkbasketQuerySortParameter.KEY, 'Key'], + [WorkbasketQuerySortParameter.DESCRIPTION, 'Description'], + [WorkbasketQuerySortParameter.OWNER, 'Owner'], + [WorkbasketQuerySortParameter.TYPE, 'Type'] +]); + +export enum WorkbasketAccessItemQuerySortParameter { + WORKBASKET_KEY = 'WORKBASKET_KEY', + ACCESS_ID = 'ACCESS_ID' +} + +export const WORKBASKET_ACCESS_ITEM_SORT_PARAMETER_NAMING: Map< + WorkbasketAccessItemQuerySortParameter, + string +> = new Map([ + [WorkbasketAccessItemQuerySortParameter.ACCESS_ID, 'Access id'], + [WorkbasketAccessItemQuerySortParameter.WORKBASKET_KEY, 'Workbasket Key'] +]); + +export enum ClassificationQuerySortParameter { + DOMAIN = 'DOMAIN', + KEY = 'KEY', + CATEGORY = 'CATEGORY', + NAME = 'NAME' +} + +export enum TaskHistoryQuerySortParameter { + TASK_HISTORY_EVENT_ID = 'TASK_HISTORY_EVENT_ID', + BUSINESS_PROCESS_ID = 'BUSINESS_PROCESS_ID', + PARENT_BUSINESS_PROCESS_ID = 'PARENT_BUSINESS_PROCESS_ID', + TASK_ID = 'TASK_ID', + EVENT_TYPE = 'EVENT_TYPE', + CREATED = 'CREATED', + USER_ID = 'USER_ID', + DOMAIN = 'DOMAIN', + WORKBASKET_KEY = 'WORKBASKET_KEY', + POR_COMPANY = 'POR_COMPANY', + POR_SYSTEM = 'POR_SYSTEM', + POR_INSTANCE = 'POR_INSTANCE', + POR_TYPE = 'POR_TYPE', + POR_VALUE = 'POR_VALUE', + TASK_CLASSIFICATION_KEY = 'TASK_CLASSIFICATION_KEY', + TASK_CLASSIFICATION_CATEGORY = 'TASK_CLASSIFICATION_CATEGORY', + ATTACHMENT_CLASSIFICATION_KEY = 'ATTACHMENT_CLASSIFICATION_KEY', + CUSTOM_1 = 'CUSTOM_1', + CUSTOM_2 = 'CUSTOM_2', + CUSTOM_3 = 'CUSTOM_3', + CUSTOM_4 = 'CUSTOM_4' } diff --git a/web/src/app/shared/models/task-history-query-filter-parameter.ts b/web/src/app/shared/models/task-history-query-filter-parameter.ts new file mode 100644 index 000000000..adac2ddda --- /dev/null +++ b/web/src/app/shared/models/task-history-query-filter-parameter.ts @@ -0,0 +1,37 @@ +export interface TaskHistoryQueryFilterParameter { + 'event-type': string[]; + 'event-type-like': string[]; + 'user-id': string[]; + 'user-id-like': string[]; + created: string[]; + domain: string[]; + 'task-id': string[]; + 'task-id-like': string[]; + 'business-process-id': string[]; + 'business-process-id-like': string[]; + 'parent-business-process-id': string[]; + 'parent-business-process-id-like': string[]; + 'task-classification-key': string[]; + 'task-classification-key-like': string[]; + 'task-classification-category': string[]; + 'task-classification-category-like': string[]; + 'attachment-classification-key': string[]; + 'attachment-classification-key-like': string[]; + 'workbasket-key-like': string[]; + 'por-company': string[]; + 'por-company-like': string[]; + 'por-system': string[]; + 'por-system-like': string[]; + 'por-instance': string[]; + 'por-instance-like': string[]; + 'por-value': string[]; + 'por-value-like': string[]; + 'custom-1': string[]; + 'custom-1-like': string[]; + 'custom-2': string[]; + 'custom-2-like': string[]; + 'custom-3': string[]; + 'custom-3-like': string[]; + 'custom-4': string[]; + 'custom-4-like': string[]; +} diff --git a/web/src/app/shared/models/task-query-filter-parameter.ts b/web/src/app/shared/models/task-query-filter-parameter.ts new file mode 100644 index 000000000..d3a76bb76 --- /dev/null +++ b/web/src/app/shared/models/task-query-filter-parameter.ts @@ -0,0 +1,45 @@ +import { TaskState } from './task-state'; + +export interface TaskQueryFilterParameter { + name?: string[]; + 'name-like'?: string[]; + priority?: number[]; + state?: TaskState[]; + 'classification.key'?: string[]; + 'task-id'?: string[]; + 'workbasket-id'?: string[]; + 'workbasket-key'?: string[]; + domain?: string[]; + owner?: string[]; + 'owner-like'?: string[]; + 'por.company'?: string[]; + 'por.system'?: string[]; + 'por.instance'?: string[]; + 'por.type'?: string[]; + 'por.value'?: string[]; + planned?: string[]; + 'planned-from'?: string[]; + 'planned-until'?: string[]; + due?: string[]; + 'due-from'?: string[]; + 'due-until'?: string[]; + 'wildcard-search-fields'?: string[]; + 'wildcard-search-value'?: string[]; + 'external-id'?: string[]; + 'custom-1'?: string[]; + 'custom-2'?: string[]; + 'custom-3'?: string[]; + 'custom-4'?: string[]; + 'custom-5'?: string[]; + 'custom-6'?: string[]; + 'custom-7'?: string[]; + 'custom-8'?: string[]; + 'custom-9'?: string[]; + 'custom-10'?: string[]; + 'custom-11'?: string[]; + 'custom-12'?: string[]; + 'custom-13'?: string[]; + 'custom-14'?: string[]; + 'custom-15'?: string[]; + 'custom-16'?: string[]; +} diff --git a/web/src/app/shared/models/task-state.ts b/web/src/app/shared/models/task-state.ts new file mode 100644 index 000000000..a96fa41c2 --- /dev/null +++ b/web/src/app/shared/models/task-state.ts @@ -0,0 +1,16 @@ +export enum TaskState { + READY = 'READY', + CLAIMED = 'CLAIMED', + COMPLETED = 'COMPLETED', + CANCELLED = 'CANCELLED', + TERMINATED = 'TERMINATED' +} + +export const ALL_STATES: Map = new Map([ + [undefined, 'All'], + [TaskState.READY, 'Ready'], + [TaskState.CLAIMED, 'Claimed'], + [TaskState.COMPLETED, 'Completed'], + [TaskState.CANCELLED, 'Cancelled'], + [TaskState.TERMINATED, 'Terminated'] +]); diff --git a/web/src/app/shared/models/workbasket-access-item-query-filter-parameter.ts b/web/src/app/shared/models/workbasket-access-item-query-filter-parameter.ts new file mode 100644 index 000000000..850b307a7 --- /dev/null +++ b/web/src/app/shared/models/workbasket-access-item-query-filter-parameter.ts @@ -0,0 +1,6 @@ +export interface WorkbasketAccessItemQueryFilterParameter { + 'workbasket-key'?: string[]; + 'workbasket-key-like'?: string[]; + 'access-id'?: string[]; + 'access-id-like'?: string[]; +} diff --git a/web/src/app/shared/models/workbasket-permission.ts b/web/src/app/shared/models/workbasket-permission.ts new file mode 100644 index 000000000..d9ed38d28 --- /dev/null +++ b/web/src/app/shared/models/workbasket-permission.ts @@ -0,0 +1,19 @@ +export enum WorkbasketPermission { + READ = 'READ', + OPEN = 'OPEN', + APPEND = 'APPEND', + TRANSFER = 'TRANSFER', + DISTRIBUTE = 'DISTRIBUTE', + CUSTOM_1 = 'CUSTOM_1', + CUSTOM_2 = 'CUSTOM_2', + CUSTOM_3 = 'CUSTOM_3', + CUSTOM_4 = 'CUSTOM_4', + CUSTOM_5 = 'CUSTOM_5', + CUSTOM_6 = 'CUSTOM_6', + CUSTOM_7 = 'CUSTOM_7', + CUSTOM_8 = 'CUSTOM_8', + CUSTOM_9 = 'CUSTOM_9', + CUSTOM_10 = 'CUSTOM_10', + CUSTOM_11 = 'CUSTOM_11', + CUSTOM_12 = 'CUSTOM_12' +} diff --git a/web/src/app/shared/models/workbasket-query-parameters.ts b/web/src/app/shared/models/workbasket-query-parameters.ts new file mode 100644 index 000000000..3a289764d --- /dev/null +++ b/web/src/app/shared/models/workbasket-query-parameters.ts @@ -0,0 +1,15 @@ +import { WorkbasketType } from './workbasket-type'; +import { WorkbasketPermission } from './workbasket-permission'; + +export interface WorkbasketQueryFilterParameter { + name?: string[]; + 'name-like'?: string[]; + key?: string[]; + 'key-like'?: string[]; + owner?: string[]; + 'owner-like'?: string[]; + 'description-like'?: string[]; + domain?: string[]; + type?: WorkbasketType[]; + 'required-permission'?: WorkbasketPermission[]; +} diff --git a/web/src/app/shared/models/workbasket-summary.ts b/web/src/app/shared/models/workbasket-summary.ts index 3fc77902f..4d21d154d 100644 --- a/web/src/app/shared/models/workbasket-summary.ts +++ b/web/src/app/shared/models/workbasket-summary.ts @@ -1,11 +1,11 @@ -import { ICONTYPES } from './icon-types'; +import { WorkbasketType } from './workbasket-type'; export interface WorkbasketSummary { workbasketId?: string; key?: string; name?: string; domain?: string; - type?: ICONTYPES; + type?: WorkbasketType; description?: string; owner?: string; custom1?: string; diff --git a/web/src/app/shared/models/workbasket-type.ts b/web/src/app/shared/models/workbasket-type.ts new file mode 100644 index 000000000..9994eb17d --- /dev/null +++ b/web/src/app/shared/models/workbasket-type.ts @@ -0,0 +1,14 @@ +export enum WorkbasketType { + PERSONAL = 'PERSONAL', + GROUP = 'GROUP', + CLEARANCE = 'CLEARANCE', + TOPIC = 'TOPIC' +} + +export const ALL_TYPES: Map = new Map([ + [undefined, 'All'], + [WorkbasketType.PERSONAL, 'Personal'], + [WorkbasketType.GROUP, 'Group'], + [WorkbasketType.CLEARANCE, 'Clearance'], + [WorkbasketType.TOPIC, 'Topic'] +]); diff --git a/web/src/app/shared/models/workbasket.ts b/web/src/app/shared/models/workbasket.ts index 4f878b93c..54116ab81 100644 --- a/web/src/app/shared/models/workbasket.ts +++ b/web/src/app/shared/models/workbasket.ts @@ -1,12 +1,12 @@ import { Links } from './links'; -import { ICONTYPES } from './icon-types'; +import { WorkbasketType } from './workbasket-type'; export interface Workbasket { workbasketId?: string; key?: string; name?: string; domain?: string; - type?: ICONTYPES; + type?: WorkbasketType; description?: string; owner?: string; custom1?: string; @@ -21,6 +21,8 @@ export interface Workbasket { created?: string; modified?: string; _links?: Links; + // this is not part of the API, but needed for frontend + selected?: boolean; } export const customFieldCount: number = 4; diff --git a/web/src/app/shared/pipes/select-workbaskets.pipe.ts b/web/src/app/shared/pipes/select-workbaskets.pipe.ts index 3d011d073..623d03808 100644 --- a/web/src/app/shared/pipes/select-workbaskets.pipe.ts +++ b/web/src/app/shared/pipes/select-workbaskets.pipe.ts @@ -1,31 +1,21 @@ import { Pipe, PipeTransform } from '@angular/core'; -import { TaskanaQueryParameters } from 'app/shared/util/query-parameters'; import { WorkbasketSummary } from '../models/workbasket-summary'; +import { Side } from '../../administration/components/workbasket-distribution-targets/workbasket-distribution-targets.component'; @Pipe({ name: 'selectWorkbaskets' }) export class SelectWorkBasketPipe implements PipeTransform { - transform(originArray: any, selectionArray: any, arg1: any): WorkbasketSummary[] { - let returnArray = []; - if (!originArray || !selectionArray) { - return returnArray; + transform( + allWorkbaskets: WorkbasketSummary[], + selectedWorkbaskets: WorkbasketSummary[], + side: Side + ): WorkbasketSummary[] { + if (!allWorkbaskets || !selectedWorkbaskets) { + return []; } - for (let index = originArray.length - 1; index >= 0; index--) { - if ( - (arg1 && - !selectionArray.some( - (elementToRemove) => originArray[index].workbasketId === elementToRemove.workbasketId - )) || - (!arg1 && - selectionArray.some((elementToRemove) => originArray[index].workbasketId === elementToRemove.workbasketId)) - ) { - originArray.splice(index, 1); - } + if (side === Side.SELECTED) { + return selectedWorkbaskets; } - - if (originArray.length > TaskanaQueryParameters.pageSize) { - originArray.slice(0, TaskanaQueryParameters.pageSize); - } - returnArray = originArray; - return returnArray; + const isNotSelectedWorkbasket = (wb) => !selectedWorkbaskets.some((sWb) => sWb.workbasketId === wb.workbasketId); + return allWorkbaskets.filter(isNotSelectedWorkbasket); } } diff --git a/web/src/app/shared/services/access-ids/access-ids.service.ts b/web/src/app/shared/services/access-ids/access-ids.service.ts index 895ef1068..9b7582a6a 100644 --- a/web/src/app/shared/services/access-ids/access-ids.service.ts +++ b/web/src/app/shared/services/access-ids/access-ids.service.ts @@ -4,10 +4,11 @@ import { environment } from 'environments/environment'; import { AccessIdDefinition } from 'app/shared/models/access-id'; import { Observable, of } from 'rxjs'; import { WorkbasketAccessItemsRepresentation } from 'app/shared/models/workbasket-access-items-representation'; -import { TaskanaQueryParameters } from 'app/shared/util/query-parameters'; -import { Sorting } from 'app/shared/models/sorting'; -import { QueryParameters } from 'app/shared/models/query-parameters'; +import { Sorting, WorkbasketAccessItemQuerySortParameter } from 'app/shared/models/sorting'; import { StartupService } from '../startup/startup.service'; +import { WorkbasketAccessItemQueryFilterParameter } from '../../models/workbasket-access-item-query-filter-parameter'; +import { QueryPagingParameter } from '../../models/query-paging-parameter'; +import { asUrlQueryString } from '../../util/query-parameters-v2'; @Injectable({ providedIn: 'root' @@ -34,16 +35,17 @@ export class AccessIdsService { } getAccessItems( - accessIds: AccessIdDefinition[], - accessIdLike?: string, - workbasketKeyLike?: string, - sortModel: Sorting = new Sorting('workbasket-key') + filterParameter?: WorkbasketAccessItemQueryFilterParameter, + sortParameter?: Sorting, + pagingParameter?: QueryPagingParameter ): Observable { return this.httpClient.get( encodeURI( - `${environment.taskanaRestUrl}/v1/workbasket-access-items/${TaskanaQueryParameters.getQueryParameters( - AccessIdsService.accessIdsParameters(sortModel, accessIds, accessIdLike, workbasketKeyLike) - )}` + `${environment.taskanaRestUrl}/v1/workbasket-access-items/${asUrlQueryString({ + ...filterParameter, + ...sortParameter, + ...pagingParameter + })}` ) ); } @@ -53,22 +55,4 @@ export class AccessIdsService { `${environment.taskanaRestUrl}/v1/workbasket-access-items/?access-id=${accessId}` ); } - - private static accessIdsParameters( - sortModel: Sorting, - accessIds: AccessIdDefinition[], - accessIdLike?: string, - workbasketKeyLike?: string - ): QueryParameters { - // TODO extend this query for support of multiple sortbys - const parameters = new QueryParameters(); - parameters.SORTBY = sortModel.sortBy; - parameters.SORTDIRECTION = sortModel.sortDirection; - parameters.ACCESSIDS = accessIds.map((values: AccessIdDefinition) => values.accessId).join('|'); - parameters.ACCESSIDLIKE = accessIdLike; - parameters.WORKBASKETKEYLIKE = workbasketKeyLike; - delete TaskanaQueryParameters.page; - delete TaskanaQueryParameters.pageSize; - return parameters; - } } diff --git a/web/src/app/shared/services/classifications/classifications.service.ts b/web/src/app/shared/services/classifications/classifications.service.ts index 2805428d5..5c442f145 100644 --- a/web/src/app/shared/services/classifications/classifications.service.ts +++ b/web/src/app/shared/services/classifications/classifications.service.ts @@ -1,22 +1,19 @@ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; -import { mergeMap, tap } from 'rxjs/operators'; import { Classification } from 'app/shared/models/classification'; import { ClassificationPagingList } from 'app/shared/models/classification-paging-list'; import { DomainService } from 'app/shared/services/domain/domain.service'; -import { TaskanaQueryParameters } from 'app/shared/util/query-parameters'; -import { Direction } from 'app/shared/models/sorting'; -import { QueryParameters } from 'app/shared/models/query-parameters'; +import { ClassificationQuerySortParameter, Sorting } from 'app/shared/models/sorting'; import { StartupService } from '../startup/startup.service'; +import { asUrlQueryString } from '../../util/query-parameters-v2'; +import { ClassificationQueryFilterParameters } from '../../models/classification-query-filter-parameters'; +import { QueryPagingParameter } from '../../models/query-paging-parameter'; @Injectable() export class ClassificationsService { - private classificationResourcePromise: Promise; - private lastDomain: string; - constructor( private httpClient: HttpClient, private domainService: DomainService, @@ -27,47 +24,17 @@ export class ClassificationsService { return this.startupService.getTaskanaRestUrl() + '/v1/classifications/'; } - private static classificationParameters(domain: string, type?: string): QueryParameters { - const parameters = new QueryParameters(); - parameters.SORTBY = TaskanaQueryParameters.parameters.KEY; - parameters.SORTDIRECTION = Direction.ASC; - parameters.DOMAIN = domain; - parameters.TYPE = type; - delete TaskanaQueryParameters.page; - delete TaskanaQueryParameters.pageSize; - - return parameters; - } - // GET - getClassifications(classificationType?: string): Observable { - return this.domainService.getSelectedDomain().pipe( - mergeMap((domain) => - this.httpClient.get( - `${this.url}${TaskanaQueryParameters.getQueryParameters( - ClassificationsService.classificationParameters(domain, classificationType) - )}` - ) - ), - tap(() => this.domainService.domainChangedComplete()) + getClassifications( + filterParameter?: ClassificationQueryFilterParameters, + sortParameter?: Sorting, + pagingParameter?: QueryPagingParameter + ): Observable { + return this.httpClient.get( + `${this.url}${asUrlQueryString({ ...filterParameter, ...sortParameter, ...pagingParameter })}` ); } - // GET - getClassificationsByDomain(domain: string, forceRefresh = false): Promise { - if (this.lastDomain !== domain || !this.classificationResourcePromise || forceRefresh) { - this.lastDomain = domain; - this.classificationResourcePromise = this.httpClient - .get( - `${this.url}${TaskanaQueryParameters.getQueryParameters( - ClassificationsService.classificationParameters(domain) - )}` - ) - .toPromise(); - } - return this.classificationResourcePromise; - } - // GET getClassification(id: string): Observable { return this.httpClient.get(`${this.url}${id}`); diff --git a/web/src/app/shared/services/orientation/orientation.service.ts b/web/src/app/shared/services/orientation/orientation.service.ts index 625ca4b0d..1cb8179df 100644 --- a/web/src/app/shared/services/orientation/orientation.service.ts +++ b/web/src/app/shared/services/orientation/orientation.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@angular/core'; import { Orientation } from 'app/shared/models/orientation'; import { BehaviorSubject, Observable } from 'rxjs'; -import { TaskanaQueryParameters } from 'app/shared/util/query-parameters'; @Injectable() export class OrientationService { @@ -9,8 +8,15 @@ export class OrientationService { private currentOrientation; public orientation = new BehaviorSubject(this.currentOrientation); + private static detectOrientation(): Orientation { + if (window.innerHeight > window.innerWidth) { + return Orientation.portrait; + } + return Orientation.landscape; + } + onResize() { - const orientation = this.detectOrientation(); + const orientation = OrientationService.detectOrientation(); if (orientation !== this.currentOrientation) { this.currentOrientation = orientation; if (!this.lock) { @@ -41,14 +47,6 @@ export class OrientationService { if (doubleList && window.innerWidth < 992) { cards = Math.floor(cards / 2); } - TaskanaQueryParameters.pageSize = cards > 0 ? cards : 1; return cards; } - - private detectOrientation(): Orientation { - if (window.innerHeight > window.innerWidth) { - return Orientation.portrait; - } - return Orientation.landscape; - } } diff --git a/web/src/app/shared/services/workbasket/workbasket.service.ts b/web/src/app/shared/services/workbasket/workbasket.service.ts index 13a47e689..4ec3f1c9d 100644 --- a/web/src/app/shared/services/workbasket/workbasket.service.ts +++ b/web/src/app/shared/services/workbasket/workbasket.service.ts @@ -7,13 +7,14 @@ import { WorkbasketAccessItems } from 'app/shared/models/workbasket-access-items import { WorkbasketSummaryRepresentation } from 'app/shared/models/workbasket-summary-representation'; import { WorkbasketAccessItemsRepresentation } from 'app/shared/models/workbasket-access-items-representation'; import { WorkbasketDistributionTargets } from 'app/shared/models/workbasket-distribution-targets'; -import { Direction } from 'app/shared/models/sorting'; +import { Sorting, WorkbasketQuerySortParameter } from 'app/shared/models/sorting'; import { DomainService } from 'app/shared/services/domain/domain.service'; -import { TaskanaQueryParameters } from 'app/shared/util/query-parameters'; import { mergeMap, tap, catchError } from 'rxjs/operators'; -import { QueryParameters } from 'app/shared/models/query-parameters'; import { WorkbasketRepresentation } from '../../models/workbasket-representation'; +import { WorkbasketQueryFilterParameter } from '../../models/workbasket-query-parameters'; +import { QueryPagingParameter } from '../../models/query-paging-parameter'; +import { asUrlQueryString } from '../../util/query-parameters-v2'; @Injectable() export class WorkbasketService { @@ -28,18 +29,9 @@ export class WorkbasketService { // GET getWorkBasketsSummary( forceRequest: boolean = false, - sortBy: string = TaskanaQueryParameters.parameters.KEY, - order: string = Direction.ASC, - name?: string, - nameLike?: string, - descLike?: string, - owner?: string, - ownerLike?: string, - type?: string, - key?: string, - keyLike?: string, - requiredPermission?: string, - allPages: boolean = false + filterParameter?: WorkbasketQueryFilterParameter, + sortParameter?: Sorting, + pagingParameter?: QueryPagingParameter ) { if (this.workbasketSummaryRef && !forceRequest) { return this.workbasketSummaryRef; @@ -48,23 +40,11 @@ export class WorkbasketService { return this.domainService.getSelectedDomain().pipe( mergeMap((domain) => { this.workbasketSummaryRef = this.httpClient.get( - `${environment.taskanaRestUrl}/v1/workbaskets/${TaskanaQueryParameters.getQueryParameters( - this.workbasketParameters( - sortBy, - order, - name, - nameLike, - descLike, - owner, - ownerLike, - type, - key, - keyLike, - requiredPermission, - allPages, - domain - ) - )}` + `${environment.taskanaRestUrl}/v1/workbaskets/${asUrlQueryString({ + ...filterParameter, + ...sortParameter, + ...pagingParameter + })}` ); return this.workbasketSummaryRef; }), @@ -179,40 +159,5 @@ export class WorkbasketService { return observableThrowError(errMsg); } - private workbasketParameters( - sortBy: string = TaskanaQueryParameters.parameters.KEY, - order: string = Direction.ASC, - name?: string, - nameLike?: string, - descLike?: string, - owner?: string, - ownerLike?: string, - type?: string, - key?: string, - keyLike?: string, - requiredPermission?: string, - allPages?: boolean, - domain?: string - ): QueryParameters { - const parameters = new QueryParameters(); - parameters.SORTBY = sortBy; - parameters.SORTDIRECTION = order; - parameters.NAME = name; - parameters.NAMELIKE = nameLike; - parameters.DESCLIKE = descLike; - parameters.OWNER = owner; - parameters.OWNERLIKE = ownerLike; - parameters.TYPE = type; - parameters.KEY = key; - parameters.KEYLIKE = keyLike; - parameters.REQUIREDPERMISSION = requiredPermission; - parameters.DOMAIN = domain; - if (allPages) { - delete TaskanaQueryParameters.page; - delete TaskanaQueryParameters.pageSize; - } - return parameters; - } - // #endregion } diff --git a/web/src/app/shared/shared.module.ts b/web/src/app/shared/shared.module.ts index 1617ac884..f59174cff 100644 --- a/web/src/app/shared/shared.module.ts +++ b/web/src/app/shared/shared.module.ts @@ -19,7 +19,6 @@ import { SpinnerComponent } from 'app/shared/components/spinner/spinner.componen import { MasterAndDetailComponent } from 'app/shared/components/master-and-detail/master-and-detail.component'; import { TaskanaTreeComponent } from 'app/administration/components/tree/tree.component'; import { TypeAheadComponent } from 'app/shared/components/type-ahead/type-ahead.component'; -import { FilterComponent } from 'app/shared/components/filter/filter.component'; import { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component'; import { FieldErrorDisplayComponent } from 'app/shared/components/field-error-display/field-error-display.component'; import { MatSnackBarModule } from '@angular/material/snack-bar'; @@ -61,6 +60,8 @@ import { MatPaginatorModule } from '@angular/material/paginator'; import { MatSelectModule } from '@angular/material/select'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; +import { WorkbasketFilterComponent } from './components/workbasket-filter/workbasket-filter.component'; +import { TaskFilterComponent } from './components/task-filter/task-filter.component'; const MODULES = [ CommonModule, @@ -93,7 +94,6 @@ const DECLARATIONS = [ OrderBy, MapToIterable, SortComponent, - FilterComponent, IconTypeComponent, FieldErrorDisplayComponent, PaginationComponent, @@ -102,7 +102,9 @@ const DECLARATIONS = [ DatePickerComponent, DropdownComponent, ToastComponent, - DialogPopUpComponent + DialogPopUpComponent, + WorkbasketFilterComponent, + TaskFilterComponent ]; @NgModule({ diff --git a/web/src/app/shared/store/access-items-management-store/access-items-management.actions.ts b/web/src/app/shared/store/access-items-management-store/access-items-management.actions.ts index e243a8bd9..46eb19d66 100644 --- a/web/src/app/shared/store/access-items-management-store/access-items-management.actions.ts +++ b/web/src/app/shared/store/access-items-management-store/access-items-management.actions.ts @@ -1,5 +1,7 @@ import { AccessIdDefinition } from '../../models/access-id'; -import { Sorting } from '../../models/sorting'; +import { Sorting, WorkbasketAccessItemQuerySortParameter } from '../../models/sorting'; +import { WorkbasketAccessItemQueryFilterParameter } from '../../models/workbasket-access-item-query-filter-parameter'; +import { QueryPagingParameter } from '../../models/query-paging-parameter'; export class SelectAccessId { static readonly type = '[Access Items Management] Select access ID'; @@ -14,10 +16,9 @@ export class GetGroupsByAccessId { export class GetAccessItems { static readonly type = '[Access Items Management] Get access items'; constructor( - public accessIds: AccessIdDefinition[], - public accessIdLike?: string, - public workbasketKeyLike?: string, - public sortModel: Sorting = new Sorting('workbasket-key') + public filterParameter?: WorkbasketAccessItemQueryFilterParameter, + public sortParameter?: Sorting, + public pagingParameter?: QueryPagingParameter ) {} } diff --git a/web/src/app/shared/store/access-items-management-store/access-items-management.state.ts b/web/src/app/shared/store/access-items-management-store/access-items-management.state.ts index ef8b79cda..879bf52da 100644 --- a/web/src/app/shared/store/access-items-management-store/access-items-management.state.ts +++ b/web/src/app/shared/store/access-items-management-store/access-items-management.state.ts @@ -60,7 +60,7 @@ export class AccessItemsManagementState implements NgxsAfterBootstrap { getAccessItems(ctx: StateContext, action: GetAccessItems): Observable { this.requestInProgressService.setRequestInProgress(true); return this.accessIdsService - .getAccessItems(action.accessIds, action.accessIdLike, action.workbasketKeyLike, action.sortModel) + .getAccessItems(action.filterParameter, action.sortParameter, action.pagingParameter) .pipe( take(1), tap( diff --git a/web/src/app/shared/store/classification-store/classification.state.ts b/web/src/app/shared/store/classification-store/classification.state.ts index d4f87db74..b54ad107f 100644 --- a/web/src/app/shared/store/classification-store/classification.state.ts +++ b/web/src/app/shared/store/classification-store/classification.state.ts @@ -1,19 +1,19 @@ import { Action, NgxsAfterBootstrap, State, StateContext } from '@ngxs/store'; import { Observable, of } from 'rxjs'; -import { take, tap } from 'rxjs/operators'; +import { mergeMap, take, tap } from 'rxjs/operators'; import { TaskanaDate } from 'app/shared/util/taskana.date'; import { CategoriesResponse, ClassificationCategoriesService } from '../../services/classification-categories/classification-categories.service'; import { - SaveCreatedClassification, - DeselectClassification, - GetClassifications, CopyClassification, CreateClassification, + DeselectClassification, + GetClassifications, RemoveSelectedClassification, RestoreSelectedClassification, + SaveCreatedClassification, SaveModifiedClassification, SelectClassification, SetSelectedClassificationType, @@ -23,6 +23,8 @@ import { ClassificationsService } from '../../services/classifications/classific import { DomainService } from '../../services/domain/domain.service'; import { Classification } from '../../models/classification'; import { ClassificationSummary } from '../../models/classification-summary'; +import { ClassificationQueryFilterParameters } from '../../models/classification-query-filter-parameters'; +import { ClassificationQuerySortParameter, Direction, Sorting } from '../../models/sorting'; class InitializeStore { static readonly type = '[ClassificationState] Initializing state'; @@ -94,13 +96,22 @@ export class ClassificationState implements NgxsAfterBootstrap { @Action(GetClassifications) getClassifications(ctx: StateContext): Observable { const { selectedClassificationType } = ctx.getState(); - return this.classificationsService.getClassifications(selectedClassificationType).pipe( - take(1), - tap((list) => - ctx.patchState({ - classifications: list.classifications - }) - ) + return this.domainService.getSelectedDomain().pipe( + mergeMap((domain) => { + const filter: ClassificationQueryFilterParameters = { + domain: [domain], + type: [selectedClassificationType] + }; + const sort: Sorting = { + 'sort-by': ClassificationQuerySortParameter.KEY, + order: Direction.ASC + }; + return this.classificationsService.getClassifications(filter, sort).pipe( + take(1), + tap((list) => ctx.patchState({ classifications: list.classifications })) + ); + }), + tap(() => this.domainService.domainChangedComplete()) ); } diff --git a/web/src/app/shared/store/mock-data/mock-store.ts b/web/src/app/shared/store/mock-data/mock-store.ts index a234e5a41..d10eb8c8a 100644 --- a/web/src/app/shared/store/mock-data/mock-store.ts +++ b/web/src/app/shared/store/mock-data/mock-store.ts @@ -1,5 +1,5 @@ import { Workbasket } from '../../models/workbasket'; -import { ICONTYPES } from '../../models/icon-types'; +import { WorkbasketType } from '../../models/workbasket-type'; import { ACTION } from '../../models/action'; import { WorkbasketAccessItemsRepresentation } from '../../models/workbasket-access-items-representation'; @@ -93,7 +93,7 @@ export const selectedWorkbasketMock: Workbasket = { key: 'sOrt003', name: 'bAsxet2', domain: 'DOMAIN_A', - type: ICONTYPES.TOPIC, + type: WorkbasketType.TOPIC, description: 'Lorem ipsum dolor sit amet.', owner: 'Max', custom1: '', @@ -194,7 +194,7 @@ export const workbasketReadStateMock = { key: 'USER-2-1', name: 'PPK User 1 KSC 2', domain: 'DOMAIN_A', - type: ICONTYPES.PERSONAL, + type: WorkbasketType.PERSONAL, description: 'PPK User 1 KSC 2', owner: '', custom1: '', @@ -212,7 +212,7 @@ export const workbasketReadStateMock = { key: 'USER-1-2', name: 'PPK User 2 KSC 1', domain: 'DOMAIN_A', - type: ICONTYPES.PERSONAL, + type: WorkbasketType.PERSONAL, description: 'PPK User 2 KSC 1', owner: 'Peter Maier', custom1: 'custom1', @@ -230,7 +230,7 @@ export const workbasketReadStateMock = { key: 'USER-2-2', name: 'PPK User 2 KSC 2', domain: 'DOMAIN_A', - type: ICONTYPES.PERSONAL, + type: WorkbasketType.PERSONAL, description: 'PPK User 2 KSC 2', owner: '', custom1: '', @@ -248,7 +248,7 @@ export const workbasketReadStateMock = { key: 'TPK_VIP', name: 'Themenpostkorb VIP', domain: 'DOMAIN_A', - type: ICONTYPES.TOPIC, + type: WorkbasketType.TOPIC, description: 'Themenpostkorb VIP', owner: '', custom1: '', @@ -266,7 +266,7 @@ export const workbasketReadStateMock = { key: 'TPK_VIP_2', name: 'Themenpostkorb VIP 2', domain: 'DOMAIN_A', - type: ICONTYPES.TOPIC, + type: WorkbasketType.TOPIC, description: 'Themenpostkorb VIP', owner: '', custom1: '', diff --git a/web/src/app/shared/store/workbasket-store/workbasket.actions.ts b/web/src/app/shared/store/workbasket-store/workbasket.actions.ts index 90cd71088..342bc329c 100644 --- a/web/src/app/shared/store/workbasket-store/workbasket.actions.ts +++ b/web/src/app/shared/store/workbasket-store/workbasket.actions.ts @@ -1,10 +1,11 @@ import { Workbasket } from '../../models/workbasket'; -import { TaskanaQueryParameters } from '../../util/query-parameters'; -import { Direction } from '../../models/sorting'; +import { Sorting, WorkbasketQuerySortParameter } from '../../models/sorting'; import { ACTION } from '../../models/action'; import { WorkbasketAccessItems } from '../../models/workbasket-access-items'; import { WorkbasketComponent } from '../../../administration/models/workbasket-component'; import { ButtonAction } from '../../../administration/models/button-action'; +import { QueryPagingParameter } from '../../models/query-paging-parameter'; +import { WorkbasketQueryFilterParameter } from '../../models/workbasket-query-parameters'; // Workbasket List export class GetWorkbasketsSummary { @@ -12,18 +13,9 @@ export class GetWorkbasketsSummary { constructor( public forceRequest: boolean = false, - public sortBy: string = TaskanaQueryParameters.parameters.KEY, - public order: string = Direction.ASC, - public name?: string, - public nameLike?: string, - public descLike?: string, - public owner?: string, - public ownerLike?: string, - public type?: string, - public key?: string, - public keyLike?: string, - public requiredPermission?: string, - public allPages: boolean = false + public filterParameter: WorkbasketQueryFilterParameter, + public sortParameter: Sorting, + public pageParameter: QueryPagingParameter ) {} } diff --git a/web/src/app/shared/store/workbasket-store/workbasket.state.ts b/web/src/app/shared/store/workbasket-store/workbasket.state.ts index 4083848ba..e3691effd 100644 --- a/web/src/app/shared/store/workbasket-store/workbasket.state.ts +++ b/web/src/app/shared/store/workbasket-store/workbasket.state.ts @@ -81,21 +81,7 @@ export class WorkbasketState implements NgxsAfterBootstrap { paginatedWorkbasketsSummary: undefined }); return this.workbasketService - .getWorkBasketsSummary( - action.forceRequest, - action.sortBy, - action.order, - action.name, - action.nameLike, - action.descLike, - action.owner, - action.ownerLike, - action.type, - action.key, - action.keyLike, - action.requiredPermission, - action.allPages - ) + .getWorkBasketsSummary(action.forceRequest, action.filterParameter, action.sortParameter, action.pageParameter) .pipe( take(1), tap((paginatedWorkbasketsSummary) => { diff --git a/web/src/app/shared/util/query-parameters-v2.spec.ts b/web/src/app/shared/util/query-parameters-v2.spec.ts new file mode 100644 index 000000000..ae6e4d99b --- /dev/null +++ b/web/src/app/shared/util/query-parameters-v2.spec.ts @@ -0,0 +1,27 @@ +import { asUrlQueryString } from './query-parameters-v2'; + +describe('asUrlQueryString', () => { + it('should create a empty query', () => { + expect(asUrlQueryString({})).toBe(''); + }); + + it('should create a query string with one argument', () => { + expect(asUrlQueryString({ foo: 'bar' })).toBe('?foo=bar'); + }); + + it('should create a query string with multiple argument', () => { + expect(asUrlQueryString({ foo1: 'bar1', foo2: 'bar2' })).toBe('?foo1=bar1&foo2=bar2'); + }); + + it('should expand any array argument', () => { + expect(asUrlQueryString({ foo: ['bar1', 'bar2'] })).toBe('?foo=bar1&foo=bar2'); + }); + + it('should skip undefined values', () => { + expect(asUrlQueryString({ foo: 'bar', foo1: undefined })).toBe('?foo=bar'); + }); + + it('should skip undefined values in array', () => { + expect(asUrlQueryString({ foo: ['bar1', undefined, 'bar2'] })).toBe('?foo=bar1&foo=bar2'); + }); +}); diff --git a/web/src/app/shared/util/query-parameters-v2.ts b/web/src/app/shared/util/query-parameters-v2.ts new file mode 100644 index 000000000..02b9ffcb8 --- /dev/null +++ b/web/src/app/shared/util/query-parameters-v2.ts @@ -0,0 +1,11 @@ +export function asUrlQueryString(params: Object): string { + let query = ''; + + for (const [key, value] of Object.entries(params)) { + if (value) { + let values: any[] = value instanceof Array ? value : [value]; + values.filter((v) => v !== undefined).forEach((v) => (query += (query ? '&' : '?') + `${key}=${v}`)); + } + } + return query; +} diff --git a/web/src/app/shared/util/query-parameters.spec.ts b/web/src/app/shared/util/query-parameters.spec.ts deleted file mode 100644 index 8b76a8a0c..000000000 --- a/web/src/app/shared/util/query-parameters.spec.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Direction } from 'app/shared/models/sorting'; -import { QueryParameters } from 'app/shared/models/query-parameters'; -import { TaskanaQueryParameters } from './query-parameters'; - -describe('TaskanaQueryParameters', () => { - beforeAll(() => { - TaskanaQueryParameters.page = 1; - TaskanaQueryParameters.pageSize = 9; - }); - - it('should create a empty query', () => { - delete TaskanaQueryParameters.page; - delete TaskanaQueryParameters.pageSize; - expect(TaskanaQueryParameters.getQueryParameters(new QueryParameters())).toBe('?'); - TaskanaQueryParameters.page = 1; - TaskanaQueryParameters.pageSize = 9; - }); - - it('should create a query with pagin information', () => { - expect(TaskanaQueryParameters.getQueryParameters(new QueryParameters())).toBe('?page=1&page-size=9'); - }); - - it('should create a query separated with &', () => { - const parameters = new QueryParameters(); - parameters.SORTBY = TaskanaQueryParameters.parameters.KEY; - parameters.SORTDIRECTION = Direction.ASC; - expect(TaskanaQueryParameters.getQueryParameters(parameters).split('&').length).toBe(4); - }); - - it('should remove last & from query', () => { - const parameters = new QueryParameters(); - parameters.SORTBY = TaskanaQueryParameters.parameters.KEY; - parameters.SORTDIRECTION = Direction.ASC; - expect(TaskanaQueryParameters.getQueryParameters(parameters).endsWith('?')).toBeFalsy(); - }); -}); diff --git a/web/src/app/shared/util/query-parameters.ts b/web/src/app/shared/util/query-parameters.ts index 570db9955..f8ebb13cb 100644 --- a/web/src/app/shared/util/query-parameters.ts +++ b/web/src/app/shared/util/query-parameters.ts @@ -1,5 +1,3 @@ -import { QueryParameters } from 'app/shared/models/query-parameters'; - export class TaskanaQueryParameters { static parameters = { // Sorting @@ -56,26 +54,4 @@ export class TaskanaQueryParameters { static page = 1; static pageSize = 9; - - public static getQueryParameters(queryParametersModel: QueryParameters): string { - let query = '?'; - - Object.keys(queryParametersModel).forEach((key) => { - const value = queryParametersModel[key]; - query += value ? `${TaskanaQueryParameters.parameters[key]}=${value}&` : ''; - }); - - query += this.page ? `${TaskanaQueryParameters.parameters.PAGE}=${this.page}&` : ''; - query += this.pageSize ? `${TaskanaQueryParameters.parameters.PAGESIZE}=${this.pageSize}&` : ''; - - query = TaskanaQueryParameters.removeLastChar(query); - return query; - } - - private static removeLastChar(query: string): string { - if (query.lastIndexOf('&') === query.length - 1) { - return query.slice(0, query.lastIndexOf('&')); - } - return query; - } } diff --git a/web/src/app/workplace/components/task-list-toolbar/task-list-toolbar.component.html b/web/src/app/workplace/components/task-list-toolbar/task-list-toolbar.component.html index 21ec0d7f5..4fe081f5c 100644 --- a/web/src/app/workplace/components/task-list-toolbar/task-list-toolbar.component.html +++ b/web/src/app/workplace/components/task-list-toolbar/task-list-toolbar.component.html @@ -21,7 +21,8 @@ placeholder="Search by value ..." />
- + @@ -61,7 +62,6 @@
- - +
diff --git a/web/src/app/workplace/components/task-list-toolbar/task-list-toolbar.component.ts b/web/src/app/workplace/components/task-list-toolbar/task-list-toolbar.component.ts index 83ced86ec..710d4fd6b 100644 --- a/web/src/app/workplace/components/task-list-toolbar/task-list-toolbar.component.ts +++ b/web/src/app/workplace/components/task-list-toolbar/task-list-toolbar.component.ts @@ -3,13 +3,12 @@ import { Task } from 'app/workplace/models/task'; import { Workbasket } from 'app/shared/models/workbasket'; import { TaskService } from 'app/workplace/services/task.service'; import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service'; -import { Sorting } from 'app/shared/models/sorting'; -import { Filter } from 'app/shared/models/filter'; -import { TaskanaType } from 'app/shared/models/taskana-type'; +import { Sorting, TASK_SORT_PARAMETER_NAMING, TaskQuerySortParameter } from 'app/shared/models/sorting'; import { expandDown } from 'app/shared/animations/expand.animation'; import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'; import { WorkplaceService } from 'app/workplace/services/workplace.service'; import { ObjectReference } from 'app/workplace/models/object-reference'; +import { TaskQueryFilterParameter } from '../../../shared/models/task-query-filter-parameter'; export enum Search { byWorkbasket = 'workbasket', @@ -23,20 +22,14 @@ export enum Search { styleUrls: ['./task-list-toolbar.component.scss'] }) export class TaskListToolbarComponent implements OnInit { - @Input() taskDefaultSortBy: string; - @Output() performSorting = new EventEmitter(); - @Output() performFilter = new EventEmitter(); + @Input() taskDefaultSortBy: TaskQuerySortParameter; + @Output() performSorting = new EventEmitter>(); + @Output() performFilter = new EventEmitter(); @Output() selectSearchType = new EventEmitter(); - sortingFields = new Map([ - ['name', 'Name'], - ['priority', 'Priority'], - ['due', 'Due'], - ['planned', 'Planned'] - ]); - filterParams = { name: '', key: '', owner: '', priority: '', state: '' }; - tasks: Task[] = []; + sortingFields: Map = TASK_SORT_PARAMETER_NAMING; + tasks: Task[] = []; workbasketNames: string[] = []; resultName = ''; resultId = ''; @@ -44,7 +37,6 @@ export class TaskListToolbarComponent implements OnInit { currentBasket: Workbasket; workbasketSelected = false; toolbarState = false; - filterType = TaskanaType.TASKS; searched = false; search = Search; @@ -116,11 +108,11 @@ export class TaskListToolbarComponent implements OnInit { this.router.navigate(['']); } - sorting(sort: Sorting) { + sorting(sort: Sorting) { this.performSorting.emit(sort); } - filtering(filterBy: Filter) { + filtering(filterBy: TaskQueryFilterParameter) { this.performFilter.emit(filterBy); } diff --git a/web/src/app/workplace/components/task-master/task-master.component.ts b/web/src/app/workplace/components/task-master/task-master.component.ts index 23c5b0bb9..397633d98 100644 --- a/web/src/app/workplace/components/task-master/task-master.component.ts +++ b/web/src/app/workplace/components/task-master/task-master.component.ts @@ -2,11 +2,9 @@ import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/co import { Task } from 'app/workplace/models/task'; import { TaskService } from 'app/workplace/services/task.service'; import { Subject } from 'rxjs'; -import { Sorting } from 'app/shared/models/sorting'; +import { Direction, Sorting, TaskQuerySortParameter } from 'app/shared/models/sorting'; import { Workbasket } from 'app/shared/models/workbasket'; -import { Filter } from 'app/shared/models/filter'; import { WorkplaceService } from 'app/workplace/services/workplace.service'; -import { TaskanaQueryParameters } from 'app/shared/util/query-parameters'; import { OrientationService } from 'app/shared/services/orientation/orientation.service'; import { Page } from 'app/shared/models/page'; import { takeUntil } from 'rxjs/operators'; @@ -14,6 +12,8 @@ import { ObjectReference } from '../../models/object-reference'; import { Search } from '../task-list-toolbar/task-list-toolbar.component'; import { NotificationService } from '../../../shared/services/notifications/notification.service'; import { NOTIFICATION_TYPES } from '../../../shared/models/notifications'; +import { QueryPagingParameter } from '../../../shared/models/query-paging-parameter'; +import { TaskQueryFilterParameter } from '../../../shared/models/task-query-filter-parameter'; @Component({ selector: 'taskana-task-master', @@ -26,17 +26,16 @@ export class TaskMasterComponent implements OnInit, OnDestroy { type = 'tasks'; currentBasket: Workbasket; selectedId = ''; - taskDefaultSortBy: string = 'priority'; - sort: Sorting = new Sorting(this.taskDefaultSortBy); - filterBy: Filter = new Filter({ - name: '', - owner: '', - priority: '', - state: '', - classificationKey: '', - workbasketId: '', - workbasketKey: '' - }); + taskDefaultSortBy: TaskQuerySortParameter = TaskQuerySortParameter.PRIORITY; + sort: Sorting = { + 'sort-by': this.taskDefaultSortBy, + order: Direction.ASC + }; + paging: QueryPagingParameter = { + page: 1, + 'page-size': 9 + }; + filterBy: TaskQueryFilterParameter = undefined; requestInProgress = false; selectedSearchType: Search = Search.byWorkbasket; @@ -79,27 +78,33 @@ export class TaskMasterComponent implements OnInit, OnDestroy { this.refreshWorkbasketList(); }); - this.workplaceService.workbasketSelectedStream.pipe(takeUntil(this.destroy$)).subscribe((workbasket) => { - this.currentBasket = workbasket; - if (this.selectedSearchType === Search.byWorkbasket) { - this.getTasks(); - } - }); + this.workplaceService + .getSelectedWorkbasket() + .pipe(takeUntil(this.destroy$)) + .subscribe((workbasket) => { + this.currentBasket = workbasket; + if (this.selectedSearchType === Search.byWorkbasket) { + this.getTasks(); + } + }); - this.workplaceService.objectReferenceSelectedStream.pipe(takeUntil(this.destroy$)).subscribe((objectReference) => { - if (objectReference) { - delete this.currentBasket; - this.getTasks(objectReference); - } - }); + this.workplaceService + .getSelectedObjectReference() + .pipe(takeUntil(this.destroy$)) + .subscribe((objectReference) => { + if (objectReference) { + delete this.currentBasket; + this.getTasks(objectReference); + } + }); } - performSorting(sort: Sorting) { + performSorting(sort: Sorting) { this.sort = sort; this.getTasks(); } - performFilter(filterBy: Filter) { + performFilter(filterBy: TaskQueryFilterParameter) { this.filterBy = filterBy; this.getTasks(); } @@ -110,7 +115,7 @@ export class TaskMasterComponent implements OnInit, OnDestroy { } changePage(page) { - TaskanaQueryParameters.page = page; + this.paging.page = page; this.getTasks(); } @@ -126,8 +131,7 @@ export class TaskMasterComponent implements OnInit, OnDestroy { const unusedHeight = 150; const totalHeight = window.innerHeight; const cards = Math.round((totalHeight - (unusedHeight + toolbarSize)) / cardHeight); - TaskanaQueryParameters.page = TaskanaQueryParameters.page ? TaskanaQueryParameters.page : 1; - TaskanaQueryParameters.pageSize = cards > 0 ? cards : 1; + this.paging['page-size'] = cards > 0 ? cards : 1; } } @@ -139,17 +143,7 @@ export class TaskMasterComponent implements OnInit, OnDestroy { } else { this.calculateHeightCard(); this.taskService - .findTasksWithWorkbasket( - this.currentBasket ? this.currentBasket.workbasketId : '', - this.sort.sortBy, - this.sort.sortDirection, - this.filterBy.filterParams.name, - this.filterBy.filterParams.owner, - this.filterBy.filterParams.priority, - this.filterBy.filterParams.state, - objectReference ? objectReference.type : '', - objectReference ? objectReference.value : '' - ) + .findTasksWithWorkbasket(this.filterBy, this.sort, this.paging) .pipe(takeUntil(this.destroy$)) .subscribe((taskResource) => { this.requestInProgress = false; diff --git a/web/src/app/workplace/components/taskdetails-general/general-fields.component.ts b/web/src/app/workplace/components/taskdetails-general/general-fields.component.ts index 6081df054..a1993115f 100644 --- a/web/src/app/workplace/components/taskdetails-general/general-fields.component.ts +++ b/web/src/app/workplace/components/taskdetails-general/general-fields.component.ts @@ -102,14 +102,15 @@ export class TaskdetailsGeneralFieldsComponent implements OnInit, OnChanges, OnD }); } + // TODO: this is currently called for every selected task and is only necessary when we switch the workbasket -> can be optimized. private getClassificationByDomain() { this.requestInProgress = true; this.classificationService - .getClassificationsByDomain(this.domainService.getSelectedDomainValue()) - .then((classificationPagingList) => { + .getClassifications({ domain: [this.task.workbasketSummary.domain] }) + .subscribe((classificationPagingList) => { this.classifications = classificationPagingList.classifications; + this.requestInProgress = false; }); - this.requestInProgress = false; } ngOnDestroy() { diff --git a/web/src/app/workplace/components/taskdetails/taskdetails.component.ts b/web/src/app/workplace/components/taskdetails/taskdetails.component.ts index d0ecf4e19..f975ccf4e 100644 --- a/web/src/app/workplace/components/taskdetails/taskdetails.component.ts +++ b/web/src/app/workplace/components/taskdetails/taskdetails.component.ts @@ -45,7 +45,6 @@ export class TaskdetailsComponent implements OnInit, OnDestroy { ) {} ngOnInit() { - this.currentWorkbasket = this.workplaceService.currentWorkbasket; this.workbasketSubscription = this.workplaceService.getSelectedWorkbasket().subscribe((workbasket) => { this.currentWorkbasket = workbasket; }); diff --git a/web/src/app/workplace/services/task.service.ts b/web/src/app/workplace/services/task.service.ts index d656cbbc4..eb2783274 100644 --- a/web/src/app/workplace/services/task.service.ts +++ b/web/src/app/workplace/services/task.service.ts @@ -3,10 +3,11 @@ import { Observable, Subject } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { TaskResource } from 'app/workplace/models/task-resource'; -import { Direction } from 'app/shared/models/sorting'; -import { TaskanaQueryParameters } from 'app/shared/util/query-parameters'; -import { QueryParameters } from 'app/shared/models/query-parameters'; +import { Sorting, TaskQuerySortParameter } from 'app/shared/models/sorting'; import { StartupService } from '../../shared/services/startup/startup.service'; +import { asUrlQueryString } from '../../shared/util/query-parameters-v2'; +import { TaskQueryFilterParameter } from '../../shared/models/task-query-filter-parameter'; +import { QueryPagingParameter } from '../../shared/models/query-paging-parameter'; @Injectable() export class TaskService { @@ -40,31 +41,11 @@ export class TaskService { } findTasksWithWorkbasket( - basketId: string, - sortBy: string, - sortDirection: string, - nameLike: string, - ownerLike: string, - priority: string, - state: string, - objRefTypeLike: string, - objRefValueLike: string, - allPages: boolean = false + filterParameter: TaskQueryFilterParameter, + sortParameter: Sorting, + pagingParameter: QueryPagingParameter ): Observable { - const url = `${this.url}${TaskanaQueryParameters.getQueryParameters( - TaskService.accessIdsParameters( - basketId, - sortBy, - sortDirection, - nameLike, - ownerLike, - priority, - state, - objRefTypeLike, - objRefValueLike, - allPages - ) - )}`; + const url = `${this.url}${asUrlQueryString({ ...filterParameter, ...sortParameter, ...pagingParameter })}`; return this.httpClient.get(url); } @@ -110,34 +91,4 @@ export class TaskService { }); return task; } - - private static accessIdsParameters( - basketId: string, - sortBy = 'priority', - sortDirection: string = Direction.ASC, - nameLike: string, - ownerLike: string, - priority: string, - state: string, - objRefTypeLike: string, - objRefValueLike: string, - allPages: boolean = false - ): QueryParameters { - const parameters = new QueryParameters(); - parameters.WORKBASKET_ID = basketId; - parameters.SORTBY = sortBy; - parameters.SORTDIRECTION = sortDirection; - parameters.NAMELIKE = nameLike; - parameters.OWNERLIKE = ownerLike; - parameters.PRIORITY = priority; - parameters.STATE = state; - parameters.TASK_PRIMARY_OBJ_REF_TYPE_LIKE = objRefTypeLike; - parameters.TASK_PRIMARY_OBJ_REF_VALUE_LIKE = objRefValueLike; - if (allPages) { - delete TaskanaQueryParameters.page; - delete TaskanaQueryParameters.pageSize; - } - - return parameters; - } } diff --git a/web/src/app/workplace/services/workplace.service.ts b/web/src/app/workplace/services/workplace.service.ts index b4b3eeecb..5aadfa120 100644 --- a/web/src/app/workplace/services/workplace.service.ts +++ b/web/src/app/workplace/services/workplace.service.ts @@ -1,37 +1,26 @@ import { Injectable } from '@angular/core'; -import { Observable, Subject } from 'rxjs'; +import { BehaviorSubject, Observable, Subject } from 'rxjs'; import { Workbasket } from 'app/shared/models/workbasket'; import { ObjectReference } from '../models/object-reference'; @Injectable() export class WorkplaceService { - // necessary because the TaskdetailsComponent is not always initialized when the first workbasket was selected. - currentWorkbasket: Workbasket; - objectReference: ObjectReference; - private workbasketSelectedSource = new Subject(); - workbasketSelectedStream = this.workbasketSelectedSource.asObservable(); - private objectReferenceSource = new Subject(); - objectReferenceSelectedStream = this.objectReferenceSource.asObservable(); + private workbasketSelected = new BehaviorSubject(undefined); + private objectReferenceSelected = new BehaviorSubject(undefined); - selectWorkbasket(workbasket?: Workbasket) { - this.currentWorkbasket = workbasket; - this.workbasketSelectedSource.next(workbasket); + selectWorkbasket(workbasket?: Workbasket): void { + this.workbasketSelected.next(workbasket); } getSelectedWorkbasket(): Observable { - return this.workbasketSelectedStream; + return this.workbasketSelected.asObservable(); } - selectObjectReference(objectReference?: ObjectReference) { - this.objectReference = new ObjectReference(); - if (objectReference) { - this.objectReference.type = objectReference.type; - this.objectReference.value = objectReference.value; - } - this.objectReferenceSource.next(objectReference); + selectObjectReference(objectReference?: ObjectReference): void { + this.objectReferenceSelected.next(objectReference); } - getObjectReference() { - return this.objectReferenceSelectedStream; + getSelectedObjectReference(): Observable { + return this.objectReferenceSelected.asObservable(); } }