TSK-1436: adapted breaking changes for frontend

This commit is contained in:
Mustapha Zorgati 2020-12-15 08:04:46 +01:00
parent 41d956c633
commit 3172f9f1bc
99 changed files with 1139 additions and 1128 deletions

View File

@ -1,8 +1,5 @@
package pro.taskana.rest; 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.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean; 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.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@Configuration @Configuration
public class ExampleWebSecurityConfig { public class ExampleWebSecurityConfig {
@ -51,7 +52,7 @@ public class ExampleWebSecurityConfig {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration(); CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); config.setAllowCredentials(true);
config.addAllowedOrigin("*"); config.addAllowedOriginPattern("*");
config.addAllowedHeader("*"); config.addAllowedHeader("*");
config.addAllowedMethod("*"); config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config); source.registerCorsConfiguration("/**", config);

View File

@ -3,8 +3,11 @@ package pro.taskana.common.rest.assembler;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.RepresentationModel; import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.server.RepresentationModelAssembler; 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; import pro.taskana.common.rest.models.CollectionRepresentationModel;
@ -17,6 +20,16 @@ public interface CollectionRepresentationModelAssembler<
default C toTaskanaCollectionModel(Iterable<T> entities) { default C toTaskanaCollectionModel(Iterable<T> entities) {
return StreamSupport.stream(entities.spliterator(), false) return StreamSupport.stream(entities.spliterator(), false)
.map(this::toModel) .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;
} }
} }

View File

@ -21,6 +21,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Links; import org.springframework.hateoas.Links;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@ -102,7 +103,7 @@ class ClassificationDefinitionControllerIntTest {
ClassificationDefinitionCollectionRepresentationModel.class)); ClassificationDefinitionCollectionRepresentationModel.class));
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull(); assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().getLinks()).isEmpty(); assertThat(response.getBody().getLink(IanaLinkRelations.SELF)).isPresent();
assertThat(response.getBody().getContent()) assertThat(response.getBody().getContent())
.extracting(ClassificationDefinitionRepresentationModel::getClassification) .extracting(ClassificationDefinitionRepresentationModel::getClassification)
.extracting(ClassificationRepresentationModel::getLinks) .extracting(ClassificationRepresentationModel::getLinks)

View File

@ -34,7 +34,7 @@ class ClassificationDefinitionControllerRestDocTest extends BaseRestDocTest {
classification.setServiceLevel("P1D"); classification.setServiceLevel("P1D");
ClassificationCollectionRepresentationModel importCollection = ClassificationCollectionRepresentationModel importCollection =
assembler.toTaskanaCollectionModel(List.of(classification)); new ClassificationCollectionRepresentationModel(List.of(assembler.toModel(classification)));
this.mockMvc this.mockMvc
.perform( .perform(

View File

@ -103,7 +103,8 @@ class WorkbasketControllerRestDocTest extends BaseRestDocTest {
accessItem.setPermission(WorkbasketPermission.OPEN, true); accessItem.setPermission(WorkbasketPermission.OPEN, true);
WorkbasketAccessItemCollectionRepresentationModel repModel = WorkbasketAccessItemCollectionRepresentationModel repModel =
accessItemAssembler.toTaskanaCollectionModel(List.of(accessItem)); new WorkbasketAccessItemCollectionRepresentationModel(
List.of(accessItemAssembler.toModel(accessItem)));
mockMvc mockMvc
.perform( .perform(

View File

@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.ParameterizedTypeReference; import org.springframework.core.ParameterizedTypeReference;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.hateoas.IanaLinkRelations;
import org.springframework.hateoas.Links; import org.springframework.hateoas.Links;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
@ -102,7 +103,7 @@ class WorkbasketDefinitionControllerIntTest {
executeExportRequestForDomain("DOMAIN_A"); executeExportRequestForDomain("DOMAIN_A");
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).isNotNull(); assertThat(response.getBody()).isNotNull();
assertThat(response.getBody().getLinks()).isEmpty(); assertThat(response.getBody().getLink(IanaLinkRelations.SELF)).isPresent();
assertThat(response.getBody().getContent()) assertThat(response.getBody().getContent())
.extracting(WorkbasketDefinitionRepresentationModel::getWorkbasket) .extracting(WorkbasketDefinitionRepresentationModel::getWorkbasket)
.extracting(WorkbasketRepresentationModel::getLinks) .extracting(WorkbasketRepresentationModel::getLinks)

View File

@ -20,7 +20,10 @@
"main": "src/main.ts", "main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json", "tsConfig": "src/tsconfig.app.json",
"polyfills": "src/polyfills.ts", "polyfills": "src/polyfills.ts",
"assets": ["src/assets", "src/environments/data-sources"], "assets": [
"src/assets",
"src/environments/data-sources"
],
"styles": [ "styles": [
"./node_modules/@angular/material/prebuilt-themes/indigo-pink.css", "./node_modules/@angular/material/prebuilt-themes/indigo-pink.css",
"./node_modules/bootstrap/dist/css/bootstrap.min.css", "./node_modules/bootstrap/dist/css/bootstrap.min.css",
@ -80,7 +83,10 @@
"lint": { "lint": {
"builder": "@angular-devkit/build-angular:tslint", "builder": "@angular-devkit/build-angular:tslint",
"options": { "options": {
"tsConfig": ["src/tsconfig.app.json", "src/tsconfig.spec.json"], "tsConfig": [
"src/tsconfig.app.json",
"src/tsconfig.spec.json"
],
"exclude": [] "exclude": []
} }
} }
@ -95,11 +101,14 @@
"defaultProject": "taskana-web", "defaultProject": "taskana-web",
"schematics": { "schematics": {
"@schematics/angular:component": { "@schematics/angular:component": {
"prefix": "app", "prefix": "taskana",
"style": "scss" "style": "scss"
}, },
"@schematics/angular:directive": { "@schematics/angular:directive": {
"prefix": "app" "prefix": "taskana"
} }
},
"cli": {
"analytics": false
} }
} }

View File

@ -51,7 +51,7 @@
<tr> <tr>
<th class="align-left"> <th class="align-left">
<taskana-shared-sort [sortingFields]="sortingFields" (performSorting)="sorting($event)" <taskana-shared-sort [sortingFields]="sortingFields" (performSorting)="sorting($event)"
menuPosition="left" defaultSortBy="access-id"> menuPosition="left" [defaultSortBy]="defaultSortBy">
</taskana-shared-sort> </taskana-shared-sort>
</th> </th>
<th> <th>
@ -124,4 +124,4 @@
</ng-form> </ng-form>
</div> </div>
</mat-expansion-panel> </mat-expansion-panel>
</div> </div>

View File

@ -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 { AccessItemsManagementComponent } from './access-items-management.component';
import { FormsValidatorService } from '../../../shared/services/forms-validator/forms-validator.service'; import { FormsValidatorService } from '../../../shared/services/forms-validator/forms-validator.service';
import { Actions, NgxsModule, ofActionDispatched, Store } from '@ngxs/store'; 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 { Observable } from 'rxjs';
import { GetAccessItems } from '../../../shared/store/access-items-management-store/access-items-management.actions'; import { GetAccessItems } from '../../../shared/store/access-items-management-store/access-items-management.actions';
import { MatSnackBarModule } from '@angular/material/snack-bar'; 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 { TypeAheadComponent } from '../../../shared/components/type-ahead/type-ahead.component';
import { TypeaheadModule } from 'ngx-bootstrap'; import { TypeaheadModule } from 'ngx-bootstrap';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 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 { StartupService } from '../../../shared/services/startup/startup.service';
import { TaskanaEngineService } from '../../../shared/services/taskana-engine/taskana-engine.service'; import { TaskanaEngineService } from '../../../shared/services/taskana-engine/taskana-engine.service';
import { WindowRefService } from '../../../shared/services/window/window.service'; import { WindowRefService } from '../../../shared/services/window/window.service';
@ -62,8 +62,9 @@ describe('AccessItemsManagementComponent', () => {
@Component({ selector: 'taskana-shared-sort', template: '' }) @Component({ selector: 'taskana-shared-sort', template: '' })
class TaskanaSharedSortStub { class TaskanaSharedSortStub {
@Input() sortingFields: Map<string, string>; @Input() sortingFields: Map<WorkbasketAccessItemQuerySortParameter, string>;
@Output() performSorting = new EventEmitter<Sorting>(); @Input() defaultSortBy: WorkbasketAccessItemQuerySortParameter;
@Output() performSorting = new EventEmitter<Sorting<WorkbasketAccessItemQuerySortParameter>>();
} }
beforeEach(async(() => { beforeEach(async(() => {
@ -159,7 +160,7 @@ describe('AccessItemsManagementComponent', () => {
{ accessId: '1', name: 'users' }, { accessId: '1', name: 'users' },
{ accessId: '2', 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(); app.searchForAccessItemsWorkbaskets();
fixture.detectChanges(); fixture.detectChanges();
let actionDispatched = false; let actionDispatched = false;
@ -188,7 +189,10 @@ describe('AccessItemsManagementComponent', () => {
}); });
it('should invoke sorting function correctly', () => { it('should invoke sorting function correctly', () => {
const newSort = new Sorting('access-id', Direction.DESC); const newSort: Sorting<WorkbasketAccessItemQuerySortParameter> = {
'sort-by': WorkbasketAccessItemQuerySortParameter.ACCESS_ID,
order: Direction.DESC
};
app.accessId = { accessId: '1', name: 'max' }; app.accessId = { accessId: '1', name: 'max' };
app.groups = [{ accessId: '1', name: 'users' }]; app.groups = [{ accessId: '1', name: 'users' }];
app.sorting(newSort); app.sorting(newSort);

View File

@ -4,7 +4,12 @@ import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@ang
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service'; import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service';
import { WorkbasketAccessItems } from 'app/shared/models/workbasket-access-items'; 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 { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { AccessIdDefinition } from '../../../shared/models/access-id'; import { AccessIdDefinition } from '../../../shared/models/access-id';
@ -18,6 +23,7 @@ import {
} from '../../../shared/store/access-items-management-store/access-items-management.actions'; } 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 { AccessItemsManagementSelector } from '../../../shared/store/access-items-management-store/access-items-management.selector';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { WorkbasketAccessItemQueryFilterParameter } from '../../../shared/models/workbasket-access-item-query-filter-parameter';
@Component({ @Component({
selector: 'taskana-administration-access-items-management', selector: 'taskana-administration-access-items-management',
@ -31,20 +37,19 @@ export class AccessItemsManagementComponent implements OnInit {
accessIdName: string; accessIdName: string;
panelState: boolean = false; panelState: boolean = false;
accessItemsForm: FormGroup; accessItemsForm: FormGroup;
toggleValidationAccessIdMap = new Map<number, boolean>();
accessId: AccessIdDefinition; accessId: AccessIdDefinition;
groups: AccessIdDefinition[]; groups: AccessIdDefinition[];
sortingFields = new Map([ defaultSortBy: WorkbasketAccessItemQuerySortParameter = WorkbasketAccessItemQuerySortParameter.ACCESS_ID;
['access-id', 'Access id'], sortingFields: Map<WorkbasketAccessItemQuerySortParameter, string> = WORKBASKET_ACCESS_ITEM_SORT_PARAMETER_NAMING;
['workbasket-key', 'Workbasket Key'] sortModel: Sorting<WorkbasketAccessItemQuerySortParameter> = {
]); 'sort-by': this.defaultSortBy,
sortModel: Sorting = new Sorting('access-id', Direction.DESC); order: Direction.DESC
};
accessItems: WorkbasketAccessItems[]; accessItems: WorkbasketAccessItems[];
isGroup: boolean = false; isGroup: boolean = false;
@Select(EngineConfigurationSelectors.accessItemsCustomisation) accessItemsCustomization$: Observable< @Select(EngineConfigurationSelectors.accessItemsCustomisation)
AccessItemsCustomisation accessItemsCustomization$: Observable<AccessItemsCustomisation>;
>;
@Select(AccessItemsManagementSelector.groups) groups$: Observable<AccessIdDefinition[]>; @Select(AccessItemsManagementSelector.groups) groups$: Observable<AccessIdDefinition[]>;
customFields$: Observable<CustomField[]>; customFields$: Observable<CustomField[]>;
destroy$ = new Subject<void>(); destroy$ = new Subject<void>();
@ -81,15 +86,16 @@ export class AccessItemsManagementComponent implements OnInit {
searchForAccessItemsWorkbaskets() { searchForAccessItemsWorkbaskets() {
this.removeFocus(); this.removeFocus();
this.store const filterParameter: WorkbasketAccessItemQueryFilterParameter = {
.dispatch(new GetAccessItems([this.accessId, ...this.groups], '', '', this.sortModel)) 'access-id': [this.accessId, ...this.groups].map((a) => a.accessId)
.subscribe((state) => { };
this.setAccessItemsGroups( this.store.dispatch(new GetAccessItems(filterParameter, this.sortModel)).subscribe((state) => {
state['accessItemsManagement'].accessItemsResource this.setAccessItemsGroups(
? state['accessItemsManagement'].accessItemsResource.accessItems state['accessItemsManagement'].accessItemsResource
: [] ? state['accessItemsManagement'].accessItemsResource.accessItems
); : []
}); );
});
} }
setAccessItemsGroups(accessItems: Array<WorkbasketAccessItems>) { setAccessItemsGroups(accessItems: Array<WorkbasketAccessItems>) {
@ -150,7 +156,7 @@ export class AccessItemsManagementComponent implements OnInit {
return this.formsValidatorService.isFieldValid(this.accessItemsGroups[index], field); return this.formsValidatorService.isFieldValid(this.accessItemsGroups[index], field);
} }
sorting(sort: Sorting) { sorting(sort: Sorting<WorkbasketAccessItemQuerySortParameter>) {
this.sortModel = sort; this.sortModel = sort;
this.searchForAccessItemsWorkbaskets(); this.searchForAccessItemsWorkbaskets();
} }

View File

@ -177,12 +177,12 @@
<mat-select required [(value)]="this.classification.category"> <mat-select required [(value)]="this.classification.category">
<mat-select-trigger> <mat-select-trigger>
<svg-icon <svg-icon
class="detailed-fields__category-icon" [src]="(getCategoryIcon(this.classification.category) | async)?.name"> class="detailed-fields__category-icon" [src]="(getCategoryIcon(this.classification.category) | async)?.left">
</svg-icon> </svg-icon>
{{this.classification.category}} {{this.classification.category}}
</mat-select-trigger> </mat-select-trigger>
<mat-option *ngFor="let category of categories$ | async" value="{{category}}"> <mat-option *ngFor="let category of categories$ | async" value="{{category}}">
<svg-icon class="detailed-fields__category-icon" [src]="(getCategoryIcon(category) | async)?.name"></svg-icon> <svg-icon class="detailed-fields__category-icon" [src]="(getCategoryIcon(category) | async)?.left"></svg-icon>
{{category}} {{category}}
</mat-option> </mat-option>
</mat-select> </mat-select>

View File

@ -185,8 +185,8 @@ describe('ClassificationDetailsComponent', () => {
it('should return icon for category when getCategoryIcon() is called and category exists', (done) => { it('should return icon for category when getCategoryIcon() is called and category exists', (done) => {
const categoryIcon = component.getCategoryIcon('AUTOMATIC'); const categoryIcon = component.getCategoryIcon('AUTOMATIC');
categoryIcon.subscribe((iconPair) => { categoryIcon.subscribe((iconPair) => {
expect(iconPair.name).toBe('assets/icons/categories/automatic.svg'); expect(iconPair.left).toBe('assets/icons/categories/automatic.svg');
expect(iconPair.text).toBe('AUTOMATIC'); expect(iconPair.right).toBe('AUTOMATIC');
done(); done();
}); });
}); });
@ -194,7 +194,7 @@ describe('ClassificationDetailsComponent', () => {
it('should return icon when getCategoryIcon() is called and category does not exist', (done) => { it('should return icon when getCategoryIcon() is called and category does not exist', (done) => {
const categoryIcon = component.getCategoryIcon('WATER'); const categoryIcon = component.getCategoryIcon('WATER');
categoryIcon.subscribe((iconPair) => { categoryIcon.subscribe((iconPair) => {
expect(iconPair.name).toBe('assets/icons/categories/missing-icon.svg'); expect(iconPair.left).toBe('assets/icons/categories/missing-icon.svg');
done(); done();
}); });
}); });

View File

@ -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 { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
import { DomainService } from 'app/shared/services/domain/domain.service'; import { DomainService } from 'app/shared/services/domain/domain.service';
import { Pair } from 'app/shared/models/pair';
import { NgForm } from '@angular/forms'; import { NgForm } from '@angular/forms';
import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service'; import { FormsValidatorService } from 'app/shared/services/forms-validator/forms-validator.service';
import { ImportExportService } from 'app/administration/services/import-export.service'; import { ImportExportService } from 'app/administration/services/import-export.service';
@ -30,6 +29,7 @@ import {
CopyClassification, CopyClassification,
DeselectClassification DeselectClassification
} from '../../../shared/store/classification-store/classification.actions'; } from '../../../shared/store/classification-store/classification.actions';
import { Pair } from '../../../shared/models/pair';
@Component({ @Component({
selector: 'taskana-administration-classification-details', selector: 'taskana-administration-classification-details',
@ -137,12 +137,12 @@ export class ClassificationDetailsComponent implements OnInit, OnDestroy {
this.store.dispatch(new DeselectClassification()); this.store.dispatch(new DeselectClassification());
} }
getCategoryIcon(category: string): Observable<Pair> { getCategoryIcon(category: string): Observable<Pair<string, string>> {
return this.categoryIcons$.pipe( return this.categoryIcons$.pipe(
map((iconMap) => map((iconMap) =>
iconMap[category] iconMap[category]
? new Pair(iconMap[category], category) ? { left: iconMap[category], right: category }
: new Pair(iconMap.missing, 'Category does not match with the configuration') : { left: iconMap.missing, right: 'Category does not match with the configuration' }
) )
); );
} }

View File

@ -26,21 +26,21 @@
matTooltip="Filter Category"> matTooltip="Filter Category">
<mat-icon *ngIf="selectedCategory == ''">filter_list</mat-icon> <mat-icon *ngIf="selectedCategory == ''">filter_list</mat-icon>
<svg-icon class="classification-list__icons" [src]="(getCategoryIcon(selectedCategory) | async)?.name" <svg-icon class="classification-list__icons" [src]="(getCategoryIcon(selectedCategory) | async)?.left"
[title]="(getCategoryIcon(selectedCategory) | async)?.text" [title]="(getCategoryIcon(selectedCategory) | async)?.right"
*ngIf="selectedCategory != ''"> *ngIf="selectedCategory != ''">
</svg-icon> </svg-icon>
</button> </button>
<mat-menu #menu="matMenu"> <mat-menu #menu="matMenu">
<button class="classification-list__all-button" mat-menu-item (click)="selectCategory('')"> <button class="classification-list__all-button" mat-menu-item (click)="selectCategory('')">
<svg-icon class="classification-list__categories" src="./assets/icons/asterisk.svg" <svg-icon class="classification-list__categories" [src]="(getCategoryIcon('all') | async)?.left"
[title]="(getCategoryIcon('all') | async)?.text"></svg-icon> [title]="(getCategoryIcon('all') | async)?.right"></svg-icon>
<span>All</span> <span>All</span>
</button> </button>
<button mat-menu-item *ngFor="let category of categories$ | async" (click)="selectCategory(category)"> <button mat-menu-item *ngFor="let category of categories$ | async" (click)="selectCategory(category)">
<svg-icon class="classification-list__categories" [src]="(getCategoryIcon(category) | async)?.name" <svg-icon class="classification-list__categories" [src]="(getCategoryIcon(category) | async)?.left"
[title]="(getCategoryIcon(category) | async)?.text"></svg-icon> [title]="(getCategoryIcon(category) | async)?.right"></svg-icon>
<span> {{category}} </span> <span> {{category}} </span>
</button> </button>
</mat-menu> </mat-menu>

View File

@ -200,8 +200,8 @@ describe('ClassificationListComponent', () => {
it('should return icon for category when getCategoryIcon is called and category exists', (done) => { it('should return icon for category when getCategoryIcon is called and category exists', (done) => {
const categoryIcon = component.getCategoryIcon('MANUAL'); const categoryIcon = component.getCategoryIcon('MANUAL');
categoryIcon.subscribe((iconPair) => { categoryIcon.subscribe((iconPair) => {
expect(iconPair.name).toBe('assets/icons/categories/manual.svg'); expect(iconPair.left).toBe('assets/icons/categories/manual.svg');
expect(iconPair.text).toBe('MANUAL'); expect(iconPair.right).toBe('MANUAL');
done(); done();
}); });
}); });
@ -209,7 +209,7 @@ describe('ClassificationListComponent', () => {
it('should return a special icon when getCategoryIcon is called and category does not exist', (done) => { it('should return a special icon when getCategoryIcon is called and category does not exist', (done) => {
const categoryIcon = component.getCategoryIcon('CLOUD'); const categoryIcon = component.getCategoryIcon('CLOUD');
categoryIcon.subscribe((iconPair) => { categoryIcon.subscribe((iconPair) => {
expect(iconPair.name).toBe('assets/icons/categories/missing-icon.svg'); expect(iconPair.left).toBe('assets/icons/categories/missing-icon.svg');
done(); done();
}); });
}); });

View File

@ -6,7 +6,6 @@ import { Actions, ofActionCompleted, ofActionDispatched, Select, Store } from '@
import { ImportExportService } from 'app/administration/services/import-export.service'; import { ImportExportService } from 'app/administration/services/import-export.service';
import { TaskanaType } from 'app/shared/models/taskana-type'; 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 { EngineConfigurationSelectors } from 'app/shared/store/engine-configuration-store/engine-configuration.selectors';
import { ClassificationSelectors } from 'app/shared/store/classification-store/classification.selectors'; import { ClassificationSelectors } from 'app/shared/store/classification-store/classification.selectors';
import { Location } from '@angular/common'; import { Location } from '@angular/common';
@ -19,6 +18,7 @@ import {
import { DomainService } from '../../../shared/services/domain/domain.service'; import { DomainService } from '../../../shared/services/domain/domain.service';
import { ClassificationSummary } from '../../../shared/models/classification-summary'; import { ClassificationSummary } from '../../../shared/models/classification-summary';
import { RequestInProgressService } from '../../../shared/services/request-in-progress/request-in-progress.service'; import { RequestInProgressService } from '../../../shared/services/request-in-progress/request-in-progress.service';
import { Pair } from '../../../shared/models/pair';
@Component({ @Component({
selector: 'taskana-administration-classification-list', 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')); this.location.go(this.location.path().replace(/(classifications).*/g, 'classifications/new-classification'));
} }
getCategoryIcon(category: string): Observable<Pair> { getCategoryIcon(category: string): Observable<Pair<string, string>> {
return this.categoryIcons$.pipe( return this.categoryIcons$.pipe(
map((iconMap) => { map((iconMap) => {
if (category === '') { if (category === '') {
return new Pair(iconMap['all'], 'All'); return { left: iconMap['all'], right: 'All' };
} }
return iconMap[category] return iconMap[category]
? new Pair(iconMap[category], category) ? { left: iconMap[category], right: category }
: new Pair(iconMap.missing, 'Category does not match with the configuration'); : { left: iconMap.missing, right: 'Category does not match with the configuration' };
}) })
); );
} }

View File

@ -2,8 +2,8 @@
(moveNode)="onMoveNode($event)" (treeDrop)="onDrop($event)"> (moveNode)="onMoveNode($event)" (treeDrop)="onDrop($event)">
<ng-template #treeNodeTemplate let-node let-index="index"> <ng-template #treeNodeTemplate let-node let-index="index">
<span class="text-top"> <span class="text-top">
<svg-icon *ngIf="node.data.category" class="fa-fw pr-1" [src]="(getCategoryIcon(node.data.category) | async)?.name" data-toggle="tooltip" <svg-icon *ngIf="node.data.category" class="fa-fw pr-1" [src]="(getCategoryIcon(node.data.category) | async)?.left" data-toggle="tooltip"
[title]="(getCategoryIcon(node.data.category) | async)?.text"></svg-icon> [title]="(getCategoryIcon(node.data.category) | async)?.right"></svg-icon>
</span> </span>
<span> <span>
<strong>{{ node.data.key }}</strong> <strong>{{ node.data.key }}</strong>

View File

@ -13,7 +13,6 @@ import {
import { TreeNodeModel } from 'app/administration/models/tree-node'; import { TreeNodeModel } from 'app/administration/models/tree-node';
import { ITreeOptions, KEYS, TREE_ACTIONS, TreeComponent } from 'angular-tree-component'; import { ITreeOptions, KEYS, TREE_ACTIONS, TreeComponent } from 'angular-tree-component';
import { Pair } from 'app/shared/models/pair';
import { combineLatest, Observable, Subject } from 'rxjs'; import { combineLatest, Observable, Subject } from 'rxjs';
import { filter, map, takeUntil } from 'rxjs/operators'; import { filter, map, takeUntil } from 'rxjs/operators';
import { Select, Store } from '@ngxs/store'; import { Select, Store } from '@ngxs/store';
@ -32,6 +31,7 @@ import {
UpdateClassification UpdateClassification
} from '../../../shared/store/classification-store/classification.actions'; } from '../../../shared/store/classification-store/classification.actions';
import { ClassificationTreeService } from '../../services/classification-tree.service'; import { ClassificationTreeService } from '../../services/classification-tree.service';
import { Pair } from '../../../shared/models/pair';
@Component({ @Component({
selector: 'taskana-administration-tree', selector: 'taskana-administration-tree',
@ -168,12 +168,12 @@ export class TaskanaTreeComponent implements OnInit, AfterViewChecked, OnDestroy
} }
} }
getCategoryIcon(category: string): Observable<Pair> { getCategoryIcon(category: string): Observable<Pair<string, string>> {
return this.categoryIcons$.pipe( return this.categoryIcons$.pipe(
map((iconMap) => map((iconMap) =>
iconMap[category] iconMap[category]
? new Pair(iconMap[category], category) ? { left: iconMap[category], right: category }
: new Pair(iconMap.missing, 'Category does not match with the configuration') : { left: iconMap.missing, right: 'Category does not match with the configuration' }
) )
); );
} }

View File

@ -2,6 +2,7 @@ import { Component, DebugElement, Input } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { IconTypeComponent } from './icon-type.component'; import { IconTypeComponent } from './icon-type.component';
import { SvgIconComponent, SvgIconRegistryService } from 'angular-svg-icon'; import { SvgIconComponent, SvgIconRegistryService } from 'angular-svg-icon';
import { WorkbasketType } from '../../../shared/models/workbasket-type';
@Component({ selector: 'svg-icon', template: '' }) @Component({ selector: 'svg-icon', template: '' })
class SvgIconStub { class SvgIconStub {
@ -31,11 +32,11 @@ describe('IconTypeComponent', () => {
}); });
it('should return icon path dependent on the type when calling getIconPath', () => { it('should return icon path dependent on the type when calling getIconPath', () => {
expect(component.getIconPath('PERSONAL')).toBe('user.svg'); expect(component.getIconPath(WorkbasketType.PERSONAL)).toBe('user.svg');
expect(component.getIconPath('GROUP')).toBe('users.svg'); expect(component.getIconPath(WorkbasketType.GROUP)).toBe('users.svg');
expect(component.getIconPath('TOPIC')).toBe('topic.svg'); expect(component.getIconPath(WorkbasketType.TOPIC)).toBe('topic.svg');
expect(component.getIconPath('CLEARANCE')).toBe('clearance.svg'); expect(component.getIconPath(WorkbasketType.CLEARANCE)).toBe('clearance.svg');
expect(component.getIconPath('CLOUD')).toBe('asterisk.svg'); expect(component.getIconPath(undefined)).toBe('asterisk.svg');
}); });
it('should display svg-icon', () => { it('should display svg-icon', () => {

View File

@ -1,5 +1,5 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { ICONTYPES } from 'app/shared/models/icon-types'; import { WorkbasketType } from 'app/shared/models/workbasket-type';
@Component({ @Component({
selector: 'taskana-administration-icon-type', selector: 'taskana-administration-icon-type',
@ -8,7 +8,7 @@ import { ICONTYPES } from 'app/shared/models/icon-types';
}) })
export class IconTypeComponent { export class IconTypeComponent {
@Input() @Input()
type: ICONTYPES = ICONTYPES.ALL; type: WorkbasketType;
@Input() @Input()
selected = false; selected = false;
@ -22,24 +22,15 @@ export class IconTypeComponent {
@Input() @Input()
size = 'small'; size = 'small';
public static get allTypes(): Map<string, string> { getIconPath(type: WorkbasketType) {
return new Map([
['PERSONAL', 'Personal'],
['GROUP', 'Group'],
['CLEARANCE', 'Clearance'],
['TOPIC', 'Topic']
]);
}
getIconPath(type: string) {
switch (type) { switch (type) {
case 'PERSONAL': case WorkbasketType.PERSONAL:
return 'user.svg'; return 'user.svg';
case 'GROUP': case WorkbasketType.GROUP:
return 'users.svg'; return 'users.svg';
case 'TOPIC': case WorkbasketType.TOPIC:
return 'topic.svg'; return 'topic.svg';
case 'CLEARANCE': case WorkbasketType.CLEARANCE:
return 'clearance.svg'; return 'clearance.svg';
default: default:
return 'asterisk.svg'; return 'asterisk.svg';

View File

@ -10,7 +10,7 @@ import { takeUntil } from 'rxjs/operators';
import { WorkbasketAndAction, WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors'; import { WorkbasketAndAction, WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors';
import { TaskanaDate } from '../../../shared/util/taskana.date'; import { TaskanaDate } from '../../../shared/util/taskana.date';
import { Location } from '@angular/common'; import { Location } from '@angular/common';
import { ICONTYPES } from '../../../shared/models/icon-types'; import { WorkbasketType } from '../../../shared/models/workbasket-type';
import { import {
DeselectWorkbasket, DeselectWorkbasket,
OnButtonPressed, OnButtonPressed,
@ -94,7 +94,7 @@ export class WorkbasketDetailsComponent implements OnInit, OnDestroy, OnChanges
initWorkbasket() { initWorkbasket() {
const emptyWorkbasket: Workbasket = {}; const emptyWorkbasket: Workbasket = {};
emptyWorkbasket.domain = this.domainService.getSelectedDomainValue(); emptyWorkbasket.domain = this.domainService.getSelectedDomainValue();
emptyWorkbasket.type = ICONTYPES.PERSONAL; emptyWorkbasket.type = WorkbasketType.PERSONAL;
this.addDateToWorkbasket(emptyWorkbasket); this.addDateToWorkbasket(emptyWorkbasket);
this.workbasket = emptyWorkbasket; this.workbasket = emptyWorkbasket;
} }

View File

@ -21,8 +21,8 @@
<mat-icon class="button-icon" *ngIf="allSelected" matTooltip="Select all items">check_box_outline_blank</mat-icon> <mat-icon class="button-icon" *ngIf="allSelected" matTooltip="Select all items">check_box_outline_blank</mat-icon>
</button> </button>
</mat-toolbar> </mat-toolbar>
<taskana-shared-filter *ngIf="toolbarState" (performFilter)="performAvailableFilter($event)" <taskana-shared-workbasket-filter *ngIf="toolbarState" (performFilter)="performAvailableFilter($event)"
component="distribution-target" (inputComponent)="setComponent($event)"></taskana-shared-filter> isExpanded="true" component="distribution-target"></taskana-shared-workbasket-filter>
<!-- WORKBASKET LIST --> <!-- WORKBASKET LIST -->
<div class="distribution-targets-list__list" infiniteScroll [infiniteScrollDistance]="1" [infiniteScrollThrottle]="50" (scrolled)="onScroll()" <div class="distribution-targets-list__list" infiniteScroll [infiniteScrollDistance]="1" [infiniteScrollThrottle]="50" (scrolled)="onScroll()"
@ -30,7 +30,7 @@
<mat-selection-list #workbasket [multiple]="true"> <mat-selection-list #workbasket [multiple]="true">
<mat-list-option class="workbasket-distribution-targets__workbaskets-item" <mat-list-option class="workbasket-distribution-targets__workbaskets-item"
*ngFor="let workbasket of distributionTargets | selectWorkbaskets: distributionTargetsSelected: side" *ngFor="let workbasket of distributionTargets | selectWorkbaskets: distributionTargetsSelected: side"
[selected]="workbasket.selected" type="text" [selected]="workbasket.selected"
(click)="workbasket.selected = !workbasket.selected" (click)="workbasket.selected = !workbasket.selected"
[value]="workbasket.workbasketId"> [value]="workbasket.workbasketId">
<div class="distribution-targets-list__item-wrapper"> <div class="distribution-targets-list__item-wrapper">

View File

@ -1,9 +1,8 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Component, DebugElement, EventEmitter, Input, Output } from '@angular/core'; import { Component, DebugElement, EventEmitter, Input, Output } from '@angular/core';
import { WorkbasketDistributionTargetsListComponent } from './workbasket-distribution-targets-list.component'; import { WorkbasketDistributionTargetsListComponent } from './workbasket-distribution-targets-list.component';
import { Filter } from '../../../shared/models/filter';
import { InfiniteScrollModule } from 'ngx-infinite-scroll'; 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 { SelectWorkBasketPipe } from '../../../shared/pipes/select-workbaskets.pipe';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { workbasketReadStateMock } from '../../../shared/store/mock-data/mock-store'; 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 { MatIconModule } from '@angular/material/icon';
import { MatToolbarModule } from '@angular/material/toolbar'; import { MatToolbarModule } from '@angular/material/toolbar';
import { MatListModule } from '@angular/material/list'; 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 { class FilterStub {
@Output() performFilter = new EventEmitter<Filter>(); @Output() performFilter = new EventEmitter<WorkbasketQueryFilterParameter>();
} }
@Component({ selector: 'taskana-shared-spinner', template: '' }) @Component({ selector: 'taskana-shared-spinner', template: '' })
@ -24,7 +24,7 @@ class SpinnerStub {
@Component({ selector: 'taskana-administration-icon-type', template: '' }) @Component({ selector: 'taskana-administration-icon-type', template: '' })
class IconTypeStub { class IconTypeStub {
@Input() type: ICONTYPES = ICONTYPES.ALL; @Input() type: WorkbasketType;
@Input() text: string; @Input() text: string;
} }

View File

@ -9,10 +9,11 @@ import {
ViewChild ViewChild
} from '@angular/core'; } from '@angular/core';
import { WorkbasketSummary } from 'app/shared/models/workbasket-summary'; import { WorkbasketSummary } from 'app/shared/models/workbasket-summary';
import { Filter } from 'app/shared/models/filter';
import { expandDown } from 'app/shared/animations/expand.animation'; import { expandDown } from 'app/shared/animations/expand.animation';
import { Side } from '../workbasket-distribution-targets/workbasket-distribution-targets.component'; import { Side } from '../workbasket-distribution-targets/workbasket-distribution-targets.component';
import { MatSelectionList } from '@angular/material/list'; import { MatSelectionList } from '@angular/material/list';
import { Pair } from '../../../shared/models/pair';
import { WorkbasketQueryFilterParameter } from '../../../shared/models/workbasket-query-parameters';
@Component({ @Component({
selector: 'taskana-administration-workbasket-distribution-targets-list', selector: 'taskana-administration-workbasket-distribution-targets-list',
@ -23,7 +24,7 @@ import { MatSelectionList } from '@angular/material/list';
export class WorkbasketDistributionTargetsListComponent implements OnInit, AfterContentChecked { export class WorkbasketDistributionTargetsListComponent implements OnInit, AfterContentChecked {
@Input() distributionTargets: WorkbasketSummary[]; @Input() distributionTargets: WorkbasketSummary[];
@Input() distributionTargetsSelected: WorkbasketSummary[]; @Input() distributionTargetsSelected: WorkbasketSummary[];
@Output() performDualListFilter = new EventEmitter<{ filterBy: Filter; side: Side }>(); @Output() performDualListFilter = new EventEmitter<Pair<Side, WorkbasketQueryFilterParameter>>();
@Input() requestInProgress = false; @Input() requestInProgress = false;
@Input() loadingItems? = false; @Input() loadingItems? = false;
@Input() side: Side; @Input() side: Side;
@ -33,7 +34,6 @@ export class WorkbasketDistributionTargetsListComponent implements OnInit, After
@Output() allSelectedChange = new EventEmitter<boolean>(); @Output() allSelectedChange = new EventEmitter<boolean>();
toolbarState = false; toolbarState = false;
component = '';
@ViewChild('workbasket') distributionTargetsList: MatSelectionList; @ViewChild('workbasket') distributionTargetsList: MatSelectionList;
constructor(private changeDetector: ChangeDetectorRef) {} constructor(private changeDetector: ChangeDetectorRef) {}
@ -55,17 +55,13 @@ export class WorkbasketDistributionTargetsListComponent implements OnInit, After
this.allSelectedChange.emit(this.allSelected); this.allSelectedChange.emit(this.allSelected);
} }
setComponent(component: string) {
this.component = component;
}
onScroll() { onScroll() {
this.scrolling.emit(this.side); this.scrolling.emit(this.side);
} }
performAvailableFilter(filterModel: Filter) { performAvailableFilter(pair: Pair<string, WorkbasketQueryFilterParameter>) {
if (this.component === 'distribution-target') { if (pair.left === 'distribution-target') {
this.performDualListFilter.emit({ filterBy: filterModel, side: this.side }); this.performDualListFilter.emit({ left: this.side, right: pair.right });
} }
} }

View File

@ -2,7 +2,6 @@ import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Component, DebugElement, EventEmitter, Input, Output } from '@angular/core'; import { Component, DebugElement, EventEmitter, Input, Output } from '@angular/core';
import { Side, WorkbasketDistributionTargetsComponent } from './workbasket-distribution-targets.component'; import { Side, WorkbasketDistributionTargetsComponent } from './workbasket-distribution-targets.component';
import { WorkbasketSummary } from '../../../shared/models/workbasket-summary'; import { WorkbasketSummary } from '../../../shared/models/workbasket-summary';
import { Filter } from '../../../shared/models/filter';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
import { MatToolbarModule } from '@angular/material/toolbar'; import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button'; import { MatButtonModule } from '@angular/material/button';
@ -20,6 +19,8 @@ import {
selectedWorkbasketMock, selectedWorkbasketMock,
workbasketReadStateMock workbasketReadStateMock
} from '../../../shared/store/mock-data/mock-store'; } 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 routeParamsMock = { id: 'workbasket' };
const activatedRouteMock = { const activatedRouteMock = {
@ -31,7 +32,7 @@ const activatedRouteMock = {
class WorkbasketDistributionTargetsListStub { class WorkbasketDistributionTargetsListStub {
@Input() distributionTargets: WorkbasketSummary[]; @Input() distributionTargets: WorkbasketSummary[];
@Input() distributionTargetsSelected: WorkbasketSummary[]; @Input() distributionTargetsSelected: WorkbasketSummary[];
@Output() performDualListFilter = new EventEmitter<{ filterBy: Filter; side: Side }>(); @Output() performDualListFilter = new EventEmitter<Pair<Side, WorkbasketQueryFilterParameter>>();
@Input() requestInProgress = false; @Input() requestInProgress = false;
@Input() loadingItems? = false; @Input() loadingItems? = false;
@Input() side: Side; @Input() side: Side;
@ -128,11 +129,14 @@ describe('WorkbasketDistributionTargetsComponent', () => {
expect(component.distributionTargetsSelected).toHaveLength(3); //mock-data has 3 entries 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', () => { it('should emit filter model and side when performing filter', () => {
const performDualListFilterSpy = jest.spyOn(component, 'performFilter'); const performDualListFilterSpy = jest.spyOn(component, 'performFilter');
const filterModelMock: Filter = { filterParams: { name: '', description: '', owner: '', type: '', key: '' } }; const filterModelMock: WorkbasketQueryFilterParameter = { domain: ['DOMAIN_A'] };
component.performFilter({ filterBy: filterModelMock, side: component.side });
expect(performDualListFilterSpy).toHaveBeenCalledWith({ filterBy: filterModelMock, side: component.side }); component.performFilter({ left: Side.AVAILABLE, right: filterModelMock });
expect(performDualListFilterSpy).toHaveBeenCalledWith({ right: filterModelMock, left: Side.AVAILABLE });
}); });
it('should move distribution targets to selected list', () => { it('should move distribution targets to selected list', () => {

View File

@ -8,7 +8,6 @@ import { WorkbasketDistributionTargets } from 'app/shared/models/workbasket-dist
import { ACTION } from 'app/shared/models/action'; import { ACTION } from 'app/shared/models/action';
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service'; import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
import { SavingWorkbasketService, SavingInformation } from 'app/administration/services/saving-workbaskets.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 { Page } from 'app/shared/models/page';
import { Select, Store } from '@ngxs/store'; import { Select, Store } from '@ngxs/store';
import { filter, takeUntil } from 'rxjs/operators'; import { filter, takeUntil } from 'rxjs/operators';
@ -20,8 +19,10 @@ import {
UpdateWorkbasketDistributionTargets UpdateWorkbasketDistributionTargets
} from '../../../shared/store/workbasket-store/workbasket.actions'; } from '../../../shared/store/workbasket-store/workbasket.actions';
import { WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors'; import { WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors';
import { MatDialog } from '@angular/material/dialog';
import { ButtonAction } from '../../models/button-action'; 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 { export enum Side {
AVAILABLE, AVAILABLE,
@ -54,7 +55,11 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy
loadingItems = false; loadingItems = false;
side = Side; side = Side;
private initialized = false; private initialized = false;
page: Page; currentPage: Page;
pageParameter: QueryPagingParameter = {
page: 1,
'page-size': 9
};
cards: number; cards: number;
selectAllLeft = false; selectAllLeft = false;
selectAllRight = false; selectAllRight = false;
@ -77,8 +82,7 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy
private workbasketService: WorkbasketService, private workbasketService: WorkbasketService,
private savingWorkbaskets: SavingWorkbasketService, private savingWorkbaskets: SavingWorkbasketService,
private notificationsService: NotificationService, private notificationsService: NotificationService,
private store: Store, private store: Store
public matDialog: MatDialog
) {} ) {}
/** /**
@ -100,7 +104,7 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.pipe(filter((availableDistributionTargets) => typeof availableDistributionTargets !== 'undefined')) .pipe(filter((availableDistributionTargets) => typeof availableDistributionTargets !== 'undefined'))
.subscribe((availableDistributionTargets) => { .subscribe((availableDistributionTargets) => {
this.availableDistributionTargets = [...availableDistributionTargets]; this.availableDistributionTargets = availableDistributionTargets.map((wb) => ({ ...wb }));
}); });
this.savingWorkbaskets this.savingWorkbaskets
@ -118,7 +122,7 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy
this.distributionTargetsSelectedResource = { ...workbasketDistributionTargets }; this.distributionTargetsSelectedResource = { ...workbasketDistributionTargets };
this.distributionTargetsSelected = this.distributionTargetsSelectedResource.distributionTargets; this.distributionTargetsSelected = this.distributionTargetsSelectedResource.distributionTargets;
this.distributionTargetsSelectedClone = { ...this.distributionTargetsSelected }; this.distributionTargetsSelectedClone = { ...this.distributionTargetsSelected };
TaskanaQueryParameters.page = 1; this.pageParameter.page = 1;
this.getWorkbaskets(); this.getWorkbaskets();
} }
}); });
@ -140,7 +144,7 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy
} }
onScroll() { onScroll() {
if (this.page.totalPages > TaskanaQueryParameters.page) { if (this.currentPage && this.currentPage.totalPages > this.pageParameter.page) {
this.loadingItems = true; this.loadingItems = true;
this.getNextPage(); this.getNextPage();
} }
@ -157,20 +161,20 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy
getWorkbaskets(side?: Side) { getWorkbaskets(side?: Side) {
if (this.distributionTargetsSelected && !this.initialized) { if (this.distributionTargetsSelected && !this.initialized) {
this.initialized = true; this.initialized = true;
TaskanaQueryParameters.pageSize = this.cards + this.distributionTargetsSelected.length; this.pageParameter['page-size'] = this.cards + this.distributionTargetsSelected.length;
} }
this.workbasketService this.workbasketService
.getWorkBasketsSummary(true) .getWorkBasketsSummary(true, undefined, undefined, this.pageParameter)
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe((distributionTargetsAvailable: WorkbasketSummaryRepresentation) => { .subscribe((distributionTargetsAvailable: WorkbasketSummaryRepresentation) => {
if (TaskanaQueryParameters.page === 1) { if (this.pageParameter === 1) {
this.availableDistributionTargets = []; this.availableDistributionTargets = [];
this.page = distributionTargetsAvailable.page; this.currentPage = distributionTargetsAvailable.page;
} }
if (side === this.side.AVAILABLE) { if (side === Side.AVAILABLE) {
this.availableDistributionTargets.push(...distributionTargetsAvailable.workbaskets); this.availableDistributionTargets.push(...distributionTargetsAvailable.workbaskets);
} else if (side === this.side.SELECTED) { } else if (side === Side.SELECTED) {
this.distributionTargetsLeft = Object.assign([], distributionTargetsAvailable.workbaskets); this.distributionTargetsLeft = Object.assign([], distributionTargetsAvailable.workbaskets);
} else { } else {
this.availableDistributionTargets.push(...distributionTargetsAvailable.workbaskets); this.availableDistributionTargets.push(...distributionTargetsAvailable.workbaskets);
@ -181,38 +185,24 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy
} }
getNextPage(side?: Side) { getNextPage(side?: Side) {
TaskanaQueryParameters.page += 1; this.pageParameter.page += 1;
this.getWorkbaskets(side); this.getWorkbaskets(side);
} }
performFilter(dualListFilter: any) { performFilter({ left: side, right: filter }: Pair<Side, WorkbasketQueryFilterParameter>) {
this.workbasketService this.workbasketService
.getWorkBasketsSummary( .getWorkBasketsSummary(true, filter)
true,
'',
'',
'',
dualListFilter.filterBy.filterParams.name,
dualListFilter.filterBy.filterParams.description,
'',
dualListFilter.filterBy.filterParams.owner,
dualListFilter.filterBy.filterParams.type,
'',
dualListFilter.filterBy.filterParams.key,
'',
true
)
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe((distributionTargetsAvailable: WorkbasketSummaryRepresentation) => { .subscribe((distributionTargetsAvailable: WorkbasketSummaryRepresentation) => {
this.fillDistributionTargets(dualListFilter.side, []); this.fillDistributionTargets(side, []);
if (TaskanaQueryParameters.page === 1) { if (this.pageParameter === 1) {
this.availableDistributionTargets = []; 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); this.availableDistributionTargets.push(...distributionTargetsAvailable.workbaskets);
} else if (dualListFilter.side === this.side.SELECTED) { } else if (side === Side.SELECTED) {
this.distributionTargetsLeft = Object.assign([], distributionTargetsAvailable.workbaskets); this.distributionTargetsLeft = Object.assign([], distributionTargetsAvailable.workbaskets);
} else { } else {
this.availableDistributionTargets.push(...distributionTargetsAvailable.workbaskets); this.availableDistributionTargets.push(...distributionTargetsAvailable.workbaskets);
@ -240,8 +230,8 @@ export class WorkbasketDistributionTargetsComponent implements OnInit, OnDestroy
this.distributionTargetsSelected = [...this.distributionTargetsSelected, ...itemsSelected]; this.distributionTargetsSelected = [...this.distributionTargetsSelected, ...itemsSelected];
this.distributionTargetsLeft = this.distributionTargetsLeft.concat(itemsSelected); this.distributionTargetsLeft = this.distributionTargetsLeft.concat(itemsSelected);
if ( if (
itemsLeft - itemsSelected.length <= TaskanaQueryParameters.pageSize && itemsLeft - itemsSelected.length <= this.pageParameter['page-size'] &&
itemsLeft + itemsRight < this.page.totalElements itemsLeft + itemsRight < this.currentPage.totalElements
) { ) {
this.getNextPage(side); this.getNextPage(side);
} }

View File

@ -4,7 +4,7 @@ import { Component, DebugElement, Input } from '@angular/core';
import { Actions, NgxsModule, ofActionDispatched, Store } from '@ngxs/store'; import { Actions, NgxsModule, ofActionDispatched, Store } from '@ngxs/store';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 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 { MapValuesPipe } from '../../../shared/pipes/map-values.pipe';
import { RemoveNoneTypePipe } from '../../../shared/pipes/remove-empty-type.pipe'; import { RemoveNoneTypePipe } from '../../../shared/pipes/remove-empty-type.pipe';
import { WorkbasketService } from '../../../shared/services/workbasket/workbasket.service'; import { WorkbasketService } from '../../../shared/services/workbasket/workbasket.service';
@ -50,7 +50,7 @@ class FieldErrorDisplayStub {
@Component({ selector: 'taskana-administration-icon-type', template: '' }) @Component({ selector: 'taskana-administration-icon-type', template: '' })
class IconTypeStub { class IconTypeStub {
@Input() type: ICONTYPES = ICONTYPES.ALL; @Input() type: WorkbasketType;
@Input() text: string; @Input() text: string;
} }

View File

@ -29,10 +29,9 @@
</button> </button>
<div class="filter__filter-component-wrapper"> <div class="filter__filter-component-wrapper">
<taskana-shared-filter [isExpanded]="isExpanded" <taskana-shared-workbasket-filter [isExpanded]="isExpanded"
(performFilter)="filtering($event)" (performFilter)="filtering($event)"
component="workbasket-list" component="workbasket-list"></taskana-shared-workbasket-filter>
(inputComponent)="setComponent($event)"></taskana-shared-filter>
</div> </div>
</div> </div>
</div> </div>

View File

@ -9,8 +9,7 @@ import { WorkbasketService } from '../../../shared/services/workbasket/workbaske
import { DomainService } from '../../../shared/services/domain/domain.service'; import { DomainService } from '../../../shared/services/domain/domain.service';
import { CreateWorkbasket } from '../../../shared/store/workbasket-store/workbasket.actions'; import { CreateWorkbasket } from '../../../shared/store/workbasket-store/workbasket.actions';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { Filter } from '../../../shared/models/filter'; import { Direction, Sorting, WorkbasketQuerySortParameter } from '../../../shared/models/sorting';
import { Sorting } from '../../../shared/models/sorting';
import { ACTION } from '../../../shared/models/action'; import { ACTION } from '../../../shared/models/action';
import { TaskanaType } from '../../../shared/models/taskana-type'; import { TaskanaType } from '../../../shared/models/taskana-type';
import { MatIconModule } from '@angular/material/icon'; import { MatIconModule } from '@angular/material/icon';
@ -18,6 +17,8 @@ import { MatSnackBarModule } from '@angular/material/snack-bar';
import { MatDialogModule } from '@angular/material/dialog'; import { MatDialogModule } from '@angular/material/dialog';
import { RouterTestingModule } from '@angular/router/testing'; import { RouterTestingModule } from '@angular/router/testing';
import { RequestInProgressService } from '../../../shared/services/request-in-progress/request-in-progress.service'; 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 getDomainFn = jest.fn().mockReturnValue(true);
const domainServiceMock = jest.fn().mockImplementation( const domainServiceMock = jest.fn().mockImplementation(
@ -34,15 +35,15 @@ class ImportExportStub {
@Component({ selector: 'taskana-shared-sort', template: '' }) @Component({ selector: 'taskana-shared-sort', template: '' })
class SortStub { class SortStub {
@Input() sortingFields: Map<string, string>; @Input() sortingFields: Map<WorkbasketQuerySortParameter, string>;
@Input() defaultSortBy = 'key'; @Input() defaultSortBy: WorkbasketQuerySortParameter;
@Output() performSorting = new EventEmitter<Sorting>(); @Output() performSorting = new EventEmitter<Sorting<WorkbasketQuerySortParameter>>();
} }
@Component({ selector: 'taskana-shared-filter', template: '' }) @Component({ selector: 'taskana-shared-workbasket-filter', template: '' })
class FilterStub { class FilterStub {
@Input() isExpanded = false; @Input() isExpanded = false;
@Output() performFilter = new EventEmitter<Filter>(); @Output() performFilter = new EventEmitter<WorkbasketQueryFilterParameter>();
} }
const requestInProgressServiceSpy = jest.fn().mockImplementation( const requestInProgressServiceSpy = jest.fn().mockImplementation(
@ -109,25 +110,35 @@ describe('WorkbasketListToolbarComponent', () => {
})); }));
it('should emit value when sorting is called', (done) => { it('should emit value when sorting is called', (done) => {
const mockSort: Sorting = { sortBy: '123', sortDirection: 'asc' }; const mockSort: Sorting<WorkbasketQuerySortParameter> = {
let sort: Sorting = { sortBy: '123', sortDirection: 'asc' }; 'sort-by': WorkbasketQuerySortParameter.KEY,
component.performSorting.subscribe((sortBy: Sorting) => { order: Direction.ASC
};
let sort: Sorting<WorkbasketQuerySortParameter> = undefined;
component.performSorting.subscribe((sortBy: Sorting<WorkbasketQuerySortParameter>) => {
sort = sortBy; sort = sortBy;
done(); done();
}); });
component.sorting(sort); component.sorting(mockSort);
expect(sort).toMatchObject(mockSort); expect(sort).toMatchObject(mockSort);
}); });
it('should emit value when filtering is called', async((done) => { it('should NOT emit value when filtering is called with wrong component', async((done) => {
const mockFilter: Filter = { filterParams: 'abc' }; const mockFilter: Pair<string, WorkbasketQueryFilterParameter> = { left: 'foo', right: { domain: ['DOMAIN_A'] } };
let filterBy: Filter = { filterParams: 'abc' }; const performFilterSpy = jest.spyOn(component.performFilter, 'emit');
component.performFilter.subscribe((filter: Filter) => { component.filtering(mockFilter);
filterBy = filter; expect(performFilterSpy).toBeCalledTimes(0);
done(); }));
});
component.filtering(filterBy); it('should emit value when filtering is called with correct component', async((done) => {
expect(filterBy).toMatchObject(mockFilter); const mockFilter: Pair<string, WorkbasketQueryFilterParameter> = {
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 */ /* HTML */
@ -151,7 +162,7 @@ describe('WorkbasketListToolbarComponent', () => {
}); });
it('should display filter component', () => { 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', () => { it('should show expanded filter component only when filter button is clicked', () => {

View File

@ -1,6 +1,5 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { Sorting } from 'app/shared/models/sorting'; import { Sorting, WORKBASKET_SORT_PARAMETER_NAMING, WorkbasketQuerySortParameter } from 'app/shared/models/sorting';
import { Filter } from 'app/shared/models/filter';
import { WorkbasketSummary } from 'app/shared/models/workbasket-summary'; import { WorkbasketSummary } from 'app/shared/models/workbasket-summary';
import { TaskanaType } from 'app/shared/models/taskana-type'; import { TaskanaType } from 'app/shared/models/taskana-type';
import { expandDown } from 'app/shared/animations/expand.animation'; 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 { CreateWorkbasket } from '../../../shared/store/workbasket-store/workbasket.actions';
import { WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors'; import { WorkbasketSelectors } from '../../../shared/store/workbasket-store/workbasket.selectors';
import { WorkbasketService } from '../../../shared/services/workbasket/workbasket.service'; import { WorkbasketService } from '../../../shared/services/workbasket/workbasket.service';
import { WorkbasketQueryFilterParameter } from '../../../shared/models/workbasket-query-parameters';
import { Pair } from '../../../shared/models/pair';
@Component({ @Component({
selector: 'taskana-administration-workbasket-list-toolbar', selector: 'taskana-administration-workbasket-list-toolbar',
@ -21,31 +22,15 @@ import { WorkbasketService } from '../../../shared/services/workbasket/workbaske
export class WorkbasketListToolbarComponent implements OnInit { export class WorkbasketListToolbarComponent implements OnInit {
@Input() workbasketListExpanded: boolean = true; @Input() workbasketListExpanded: boolean = true;
@Input() workbaskets: Array<WorkbasketSummary>; @Input() workbaskets: Array<WorkbasketSummary>;
@Input() workbasketDefaultSortBy: string; @Input() workbasketDefaultSortBy: WorkbasketQuerySortParameter;
@Output() performSorting = new EventEmitter<Sorting>(); @Output() performSorting = new EventEmitter<Sorting<WorkbasketQuerySortParameter>>();
@Output() performFilter = new EventEmitter<Filter>(); @Output() performFilter = new EventEmitter<WorkbasketQueryFilterParameter>();
selectionToImport = TaskanaType.WORKBASKETS; selectionToImport = TaskanaType.WORKBASKETS;
sortingFields = new Map([ sortingFields: Map<WorkbasketQuerySortParameter, string> = WORKBASKET_SORT_PARAMETER_NAMING;
['name', 'Name'],
['key', 'Key'],
['description', 'Description'],
['owner', 'Owner'],
['type', 'Type']
]);
filteringTypes = new Map([
['ALL', 'All'],
['PERSONAL', 'Personal'],
['GROUP', 'Group'],
['CLEARANCE', 'Clearance'],
['TOPIC', 'Topic']
]);
filterParams = { name: '', key: '', type: '', description: '', owner: '' };
filterType = TaskanaType.WORKBASKETS;
isExpanded = false; isExpanded = false;
showFilter = false; showFilter = false;
component = '';
@Select(WorkbasketSelectors.workbasketActiveAction) @Select(WorkbasketSelectors.workbasketActiveAction)
workbasketActiveAction$: Observable<ACTION>; workbasketActiveAction$: Observable<ACTION>;
@ -61,20 +46,16 @@ export class WorkbasketListToolbarComponent implements OnInit {
}); });
} }
sorting(sort: Sorting) { sorting(sort: Sorting<WorkbasketQuerySortParameter>) {
this.performSorting.emit(sort); this.performSorting.emit(sort);
} }
filtering(filterBy: Filter) { filtering({ left: component, right: filter }: Pair<string, WorkbasketQueryFilterParameter>) {
if (this.component === 'workbasket-list') { if (component === 'workbasket-list') {
this.performFilter.emit(filterBy); this.performFilter.emit(filter);
} }
} }
setComponent(component: string) {
this.component = component;
}
addWorkbasket() { addWorkbasket() {
if (this.action !== ACTION.CREATE) { if (this.action !== ACTION.CREATE) {
this.store.dispatch(new CreateWorkbasket()); this.store.dispatch(new CreateWorkbasket());

View File

@ -10,11 +10,9 @@ import { MatDialogModule } from '@angular/material/dialog';
import { OrientationService } from '../../../shared/services/orientation/orientation.service'; import { OrientationService } from '../../../shared/services/orientation/orientation.service';
import { ImportExportService } from '../../services/import-export.service'; import { ImportExportService } from '../../services/import-export.service';
import { DeselectWorkbasket, SelectWorkbasket } from '../../../shared/store/workbasket-store/workbasket.actions'; 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 { WorkbasketSummary } from '../../../shared/models/workbasket-summary';
import { Sorting } from '../../../shared/models/sorting'; import { Direction, Sorting, WorkbasketQuerySortParameter } from '../../../shared/models/sorting';
import { Filter } from '../../../shared/models/filter'; import { WorkbasketType } from '../../../shared/models/workbasket-type';
import { ICONTYPES } from '../../../shared/models/icon-types';
import { Page } from '../../../shared/models/page'; import { Page } from '../../../shared/models/page';
import { MatProgressBarModule } from '@angular/material/progress-bar'; import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatSelectModule } from '@angular/material/select'; 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 { RouterTestingModule } from '@angular/router/testing';
import { RequestInProgressService } from '../../../shared/services/request-in-progress/request-in-progress.service'; import { RequestInProgressService } from '../../../shared/services/request-in-progress/request-in-progress.service';
import { selectedWorkbasketMock } from '../../../shared/store/mock-data/mock-store'; 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 workbasketSavedTriggeredFn = jest.fn().mockReturnValue(of(1));
const workbasketSummaryFn = jest.fn().mockReturnValue(of({})); const workbasketSummaryFn = jest.fn().mockReturnValue(of({}));
@ -74,13 +73,13 @@ class WorkbasketListToolbarStub {
@Input() workbaskets: Array<WorkbasketSummary>; @Input() workbaskets: Array<WorkbasketSummary>;
@Input() workbasketDefaultSortBy: string; @Input() workbasketDefaultSortBy: string;
@Input() workbasketListExpanded: boolean; @Input() workbasketListExpanded: boolean;
@Output() performSorting = new EventEmitter<Sorting>(); @Output() performSorting = new EventEmitter<Sorting<WorkbasketQuerySortParameter>>();
@Output() performFilter = new EventEmitter<Filter>(); @Output() performFilter = new EventEmitter<WorkbasketQueryFilterParameter>();
} }
@Component({ selector: 'taskana-administration-icon-type', template: '' }) @Component({ selector: 'taskana-administration-icon-type', template: '' })
class IconTypeStub { class IconTypeStub {
@Input() type: ICONTYPES = ICONTYPES.ALL; @Input() type: WorkbasketType;
@Input() selected = false; @Input() selected = false;
} }
@ -158,13 +157,16 @@ describe('WorkbasketListComponent', () => {
})); }));
it('should set sort value when performSorting is called', () => { it('should set sort value when performSorting is called', () => {
const sort = { sortBy: '1', sortDirection: 'asc' }; const sort: Sorting<WorkbasketQuerySortParameter> = {
'sort-by': WorkbasketQuerySortParameter.TYPE,
order: Direction.ASC
};
component.performSorting(sort); component.performSorting(sort);
expect(component.sort).toMatchObject(sort); expect(component.sort).toMatchObject(sort);
}); });
it('should set filter value when performFilter is called', () => { it('should set filter value when performFilter is called', () => {
const filter = { filterParams: '123' }; const filter: WorkbasketQueryFilterParameter = { domain: ['123'] };
component.performFilter(filter); component.performFilter(filter);
expect(component.filterBy).toMatchObject(filter); expect(component.filterBy).toMatchObject(filter);
}); });
@ -172,6 +174,6 @@ describe('WorkbasketListComponent', () => {
it('should change page value when change page function is called ', () => { it('should change page value when change page function is called ', () => {
const page = 2; const page = 2;
component.changePage(page); component.changePage(page);
expect(TaskanaQueryParameters.page).toBe(page); expect(component.pageParameter.page).toBe(page);
}); });
}); });

View File

@ -3,13 +3,11 @@ import { Observable, Subject } from 'rxjs';
import { WorkbasketSummaryRepresentation } from 'app/shared/models/workbasket-summary-representation'; import { WorkbasketSummaryRepresentation } from 'app/shared/models/workbasket-summary-representation';
import { WorkbasketSummary } from 'app/shared/models/workbasket-summary'; import { WorkbasketSummary } from 'app/shared/models/workbasket-summary';
import { Filter } from 'app/shared/models/filter'; import { Direction, Sorting, WorkbasketQuerySortParameter } from 'app/shared/models/sorting';
import { Sorting } from 'app/shared/models/sorting';
import { Orientation } from 'app/shared/models/orientation'; import { Orientation } from 'app/shared/models/orientation';
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service'; import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
import { OrientationService } from 'app/shared/services/orientation/orientation.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 { ImportExportService } from 'app/administration/services/import-export.service';
import { Actions, ofActionCompleted, ofActionDispatched, Select, Store } from '@ngxs/store'; import { Actions, ofActionCompleted, ofActionDispatched, Select, Store } from '@ngxs/store';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
@ -23,6 +21,8 @@ import { Workbasket } from '../../../shared/models/workbasket';
import { MatSelectionList } from '@angular/material/list'; import { MatSelectionList } from '@angular/material/list';
import { DomainService } from '../../../shared/services/domain/domain.service'; import { DomainService } from '../../../shared/services/domain/domain.service';
import { RequestInProgressService } from '../../../shared/services/request-in-progress/request-in-progress.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({ @Component({
selector: 'taskana-administration-workbasket-list', selector: 'taskana-administration-workbasket-list',
@ -31,13 +31,17 @@ import { RequestInProgressService } from '../../../shared/services/request-in-pr
}) })
export class WorkbasketListComponent implements OnInit, OnDestroy { export class WorkbasketListComponent implements OnInit, OnDestroy {
selectedId = ''; selectedId = '';
pageSelected = 1;
pageSize = 9;
type = 'workbaskets'; type = 'workbaskets';
cards: number = this.pageSize; workbasketDefaultSortBy: WorkbasketQuerySortParameter = WorkbasketQuerySortParameter.NAME;
workbasketDefaultSortBy: string = 'name'; sort: Sorting<WorkbasketQuerySortParameter> = {
sort: Sorting = new Sorting(this.workbasketDefaultSortBy); 'sort-by': this.workbasketDefaultSortBy,
filterBy: Filter = new Filter({ name: '', owner: '', type: '', description: '', key: '' }); order: Direction.ASC
};
filterBy: WorkbasketQueryFilterParameter = {};
pageParameter: QueryPagingParameter = {
page: 1,
'page-size': 9
};
requestInProgress: boolean; requestInProgress: boolean;
requestInProgressLocal = false; requestInProgressLocal = false;
@Input() expanded: boolean; @Input() expanded: boolean;
@ -88,9 +92,6 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
} }
}); });
TaskanaQueryParameters.page = this.pageSelected;
TaskanaQueryParameters.pageSize = this.pageSize;
this.workbasketService this.workbasketService
.workbasketSavedTriggered() .workbasketSavedTriggered()
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
@ -145,23 +146,23 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
} }
} }
performSorting(sort: Sorting) { performSorting(sort: Sorting<WorkbasketQuerySortParameter>) {
this.sort = sort; this.sort = sort;
this.performRequest(); this.performRequest();
} }
performFilter(filterBy: Filter) { performFilter(filterBy: WorkbasketQueryFilterParameter) {
this.filterBy = filterBy; this.filterBy = filterBy;
this.performRequest(); this.performRequest();
} }
changePage(page) { changePage(page) {
TaskanaQueryParameters.page = page; this.pageParameter.page = page;
this.performRequest(); this.performRequest();
} }
refreshWorkbasketList() { refreshWorkbasketList() {
this.cards = this.orientationService.calculateNumberItemsList( this.pageParameter['page-size'] = this.orientationService.calculateNumberItemsList(
window.innerHeight, window.innerHeight,
92, 92,
200 + this.toolbarElement.nativeElement.offsetHeight, 200 + this.toolbarElement.nativeElement.offsetHeight,
@ -171,27 +172,9 @@ export class WorkbasketListComponent implements OnInit, OnDestroy {
} }
performRequest() { performRequest() {
TaskanaQueryParameters.pageSize = this.cards; this.store.dispatch(new GetWorkbasketsSummary(true, this.filterBy, this.sort, this.pageParameter)).subscribe(() => {
this.store this.requestInProgressService.setRequestInProgress(false);
.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);
});
} }
ngOnDestroy() { ngOnDestroy() {

View File

@ -1,11 +1,11 @@
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router'; 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 = [ const routes: Routes = [
{ {
path: '', path: '',
component: TaskQueryComponent component: TaskHistoryQueryComponent
}, },
{ {
path: '**', path: '**',

View File

@ -4,10 +4,10 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { SharedModule } from 'app/shared/shared.module'; import { SharedModule } from 'app/shared/shared.module';
import { HistoryRoutingModule } from './history-routing.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({ @NgModule({
imports: [CommonModule, HistoryRoutingModule, SharedModule, FormsModule, ReactiveFormsModule], imports: [CommonModule, HistoryRoutingModule, SharedModule, FormsModule, ReactiveFormsModule],
declarations: [TaskQueryComponent] declarations: [TaskHistoryQueryComponent]
}) })
export class HistoryModule {} export class HistoryModule {}

View File

@ -1,56 +1,36 @@
import { Injectable } from '@angular/core'; 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 { TaskHistoryEventResourceData } from 'app/shared/models/task-history-event-resource';
import { QueryParameters } from 'app/shared/models/query-parameters'; import { QueryParameters } from 'app/shared/models/query-parameters';
import { TaskanaQueryParameters } from 'app/shared/util/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 { Observable, of } from 'rxjs';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { environment } from 'environments/environment';
import { StartupService } from '../../../shared/services/startup/startup.service'; 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({ @Injectable({
providedIn: 'root' providedIn: 'root'
}) })
export class TaskQueryService { export class TaskHistoryQueryService {
constructor(private httpClient: HttpClient, private startupService: StartupService) {} constructor(private httpClient: HttpClient, private startupService: StartupService) {}
get url(): string { get url(): string {
return this.startupService.getTaskanaRestUrl(); return this.startupService.getTaskanaRestUrl();
} }
queryTask( getTaskHistoryEvents(
orderBy: string = 'created', filterParameter?: TaskHistoryQueryFilterParameter,
sortDirection: string = Direction.ASC, sortParameter?: Sorting<TaskHistoryQuerySortParameter>,
searchForValues: TaskHistoryEventData, pagingParameter?: QueryPagingParameter
allPages: boolean = false
): Observable<TaskHistoryEventResourceData> { ): Observable<TaskHistoryEventResourceData> {
return this.httpClient.get<TaskHistoryEventResourceData>( return this.httpClient.get<TaskHistoryEventResourceData>(
`${this.url}/v1/task-history-event${this.getQueryParameters( `${this.url}/v1/task-history-event${asUrlQueryString({
orderBy, ...filterParameter,
sortDirection, ...sortParameter,
searchForValues.taskId, ...pagingParameter
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
)}`
); );
} }
@ -78,7 +58,7 @@ export class TaskQueryService {
custom4: string, custom4: string,
created: string, created: string,
allPages: boolean = false allPages: boolean = false
): string { ): void {
const parameters = new QueryParameters(); const parameters = new QueryParameters();
parameters.SORTBY = orderBy; parameters.SORTBY = orderBy;
parameters.SORTDIRECTION = sortDirection; parameters.SORTDIRECTION = sortDirection;
@ -107,7 +87,5 @@ export class TaskQueryService {
delete TaskanaQueryParameters.page; delete TaskanaQueryParameters.page;
delete TaskanaQueryParameters.pageSize; delete TaskanaQueryParameters.pageSize;
} }
return TaskanaQueryParameters.getQueryParameters(parameters);
} }
} }

View File

@ -22,7 +22,7 @@
<span class="icon-space"> <span class="icon-space">
{{getHeaderFieldDescription(taskHeader.key)}} {{getHeaderFieldDescription(taskHeader.key)}}
</span> </span>
<span *ngIf="orderBy.sortBy === taskHeader.key" <span *ngIf="orderBy['sort-by'] === taskHeader.key"
[ngClass]="{'flip': orderBy.sortDirection === 'desc'}" [ngClass]="{'flip': orderBy.sortDirection === 'desc'}"
class="material-icons md-20 blue pull-right">sort</span> class="material-icons md-20 blue pull-right">sort</span>
</div> </div>

View File

@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core'; 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 { OrientationService } from 'app/shared/services/orientation/orientation.service';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { Orientation } from 'app/shared/models/orientation'; 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 { TaskHistoryEventResourceData } from 'app/shared/models/task-history-event-resource';
import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service'; import { RequestInProgressService } from 'app/shared/services/request-in-progress/request-in-progress.service';
import { TaskHistoryEventData } from '../../shared/models/task-history-event'; import { TaskHistoryEventData } from '../../shared/models/task-history-event';
import { TaskQueryService } from '../services/task-query/task-query.service'; import { TaskHistoryQueryService } from '../services/task-history-query/task-history-query.service';
import { NotificationService } from '../../shared/services/notifications/notification.service';
@Component({ @Component({
selector: 'taskana-task-query', selector: 'taskana-task-query',
templateUrl: './task-query.component.html', templateUrl: './task-history-query.component.html',
styleUrls: ['./task-query.component.scss'] styleUrls: ['./task-history-query.component.scss']
}) })
export class TaskQueryComponent implements OnInit { export class TaskHistoryQueryComponent implements OnInit {
taskQueryResource: TaskHistoryEventResourceData; taskQueryResource: TaskHistoryEventResourceData;
taskQuery: Array<TaskHistoryEventData>; taskQuery: Array<TaskHistoryEventData>;
taskQueryHeader = new TaskHistoryEventData(); taskQueryHeader = new TaskHistoryEventData();
orderBy = new Sorting(TaskanaQueryParameters.parameters.CREATED); orderBy: Sorting<TaskHistoryQuerySortParameter> = {
'sort-by': TaskHistoryQuerySortParameter.CREATED,
order: Direction.ASC
};
orientationSubscription: Subscription; orientationSubscription: Subscription;
taskQuerySubscription: Subscription; taskQuerySubscription: Subscription;
taskQueryForm = new FormGroup({}); taskQueryForm = new FormGroup({});
constructor( constructor(
private taskQueryService: TaskQueryService, private taskQueryService: TaskHistoryQueryService,
private orientationService: OrientationService, private orientationService: OrientationService,
private requestInProgressService: RequestInProgressService, private requestInProgressService: RequestInProgressService
private errorsService: NotificationService
) {} ) {}
ngOnInit() { ngOnInit() {
@ -146,11 +147,12 @@ export class TaskQueryComponent implements OnInit {
} }
changeOrderBy(key: string) { changeOrderBy(key: string) {
console.log(key);
if (this.filterFieldsToAllowQuerying(key)) { if (this.filterFieldsToAllowQuerying(key)) {
if (this.orderBy.sortBy === key) { // if (this.orderBy.sortBy === key) {
this.orderBy.sortDirection = this.toggleSortDirection(this.orderBy.sortDirection); // this.orderBy.sortDirection = this.toggleSortDirection(this.orderBy.sortDirection);
} // }
this.orderBy.sortBy = key; // this.orderBy.sortBy = key;
} }
} }
@ -193,12 +195,12 @@ export class TaskQueryComponent implements OnInit {
this.requestInProgressService.setRequestInProgress(true); this.requestInProgressService.setRequestInProgress(true);
this.calculateQueryPages(); this.calculateQueryPages();
this.taskQuerySubscription = this.taskQueryService this.taskQuerySubscription = this.taskQueryService
.queryTask( .getTaskHistoryEvents
this.orderBy.sortBy.replace(/([A-Z])|([0-9])/g, (g) => `-${g[0].toLowerCase()}`), // this.orderBy.sortBy.replace(/([A-Z])|([0-9])/g, (g) => `-${g[0].toLowerCase()}`),
this.orderBy.sortDirection, // this.orderBy.sortDirection,
new TaskHistoryEventData(this.taskQueryForm.value), // new TaskHistoryEventData(this.taskQueryForm.value),
false // false
) ()
.subscribe((taskQueryResource) => { .subscribe((taskQueryResource) => {
this.requestInProgressService.setRequestInProgress(false); this.requestInProgressService.setRequestInProgress(false);
this.taskQueryResource = taskQueryResource.taskHistoryEvents ? taskQueryResource : null; this.taskQueryResource = taskQueryResource.taskHistoryEvents ? taskQueryResource : null;

View File

@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core'; 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 { ChartData } from 'app/monitor/models/chart-data';
import { ReportData } from '../../models/report-data'; import { ReportData } from '../../models/report-data';
import { ChartColorsDefinition } from '../../models/chart-colors'; import { ChartColorsDefinition } from '../../models/chart-colors';
@ -22,7 +22,7 @@ export class ClassificationReportComponent implements OnInit {
lineChartColors = ChartColorsDefinition.getColors(); lineChartColors = ChartColorsDefinition.getColors();
constructor(private restConnectorService: RestConnectorService) {} constructor(private restConnectorService: MonitorService) {}
async ngOnInit() { async ngOnInit() {
this.reportData = await this.restConnectorService.getClassificationTasksReport().toPromise(); this.reportData = await this.restConnectorService.getClassificationTasksReport().toPromise();

View File

@ -8,7 +8,7 @@
</ng-container> </ng-container>
<div *ngFor="let header of reportData.meta.header" <div *ngFor="let header of reportData.meta.header"
class="table-cell table-cell--bold">{{header}}</div> class="table-cell table-cell--bold">{{header}}</div>
<div class="table-cell table-cell--bold table-cell--border-left">{{reportData.meta.totalDesc}}</div> <div class="table-cell table-cell--bold table-cell--border-left">{{reportData.meta.sumRowDesc}}</div>
</div> </div>
</div> </div>
<div class="table-body"> <div class="table-body">

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ReportData } from 'app/monitor/models/report-data'; import { ReportData } from 'app/monitor/models/report-data';
import { RestConnectorService } from '../../services/rest-connector.service'; import { MonitorService } from '../../services/monitor.service';
@Component({ @Component({
selector: 'taskana-monitor-task-report', selector: 'taskana-monitor-task-report',
@ -13,7 +13,7 @@ export class TaskReportComponent implements OnInit {
pieChartType = 'pie'; pieChartType = 'pie';
reportData: ReportData; reportData: ReportData;
constructor(private restConnectorService: RestConnectorService) {} constructor(private restConnectorService: MonitorService) {}
async ngOnInit() { async ngOnInit() {
this.reportData = await this.restConnectorService.getTaskStatusReport().toPromise(); this.reportData = await this.restConnectorService.getTaskStatusReport().toPromise();

View File

@ -1,6 +1,6 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { ReportData } from '../../models/report-data'; import { ReportData } from '../../models/report-data';
import { RestConnectorService } from '../../services/rest-connector.service'; import { MonitorService } from '../../services/monitor.service';
@Component({ @Component({
selector: 'taskana-monitor-timestamp-report', selector: 'taskana-monitor-timestamp-report',
@ -10,7 +10,7 @@ import { RestConnectorService } from '../../services/rest-connector.service';
export class TimestampReportComponent implements OnInit { export class TimestampReportComponent implements OnInit {
reportData: ReportData; reportData: ReportData;
constructor(private restConnectorService: RestConnectorService) {} constructor(private restConnectorService: MonitorService) {}
ngOnInit() { ngOnInit() {
this.restConnectorService.getDailyEntryExitReport().subscribe((data: ReportData) => { this.restConnectorService.getDailyEntryExitReport().subscribe((data: ReportData) => {

View File

@ -2,7 +2,7 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { ReportData } from '../../models/report-data'; import { ReportData } from '../../models/report-data';
import { ChartData } from '../../models/chart-data'; import { ChartData } from '../../models/chart-data';
import { ChartColorsDefinition } from '../../models/chart-colors'; 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'; import { MetaInfoData } from '../../models/meta-info-data';
@Component({ @Component({
@ -26,7 +26,7 @@ export class WorkbasketReportDueDateComponent implements OnInit {
lineChartColors = ChartColorsDefinition.getColors(); lineChartColors = ChartColorsDefinition.getColors();
constructor(private restConnectorService: RestConnectorService) {} constructor(private restConnectorService: MonitorService) {}
async ngOnInit() { async ngOnInit() {
this.reportData = await this.restConnectorService.getWorkbasketStatisticsQueryingByDueDate().toPromise(); this.reportData = await this.restConnectorService.getWorkbasketStatisticsQueryingByDueDate().toPromise();

View File

@ -2,7 +2,7 @@ import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { ReportData } from '../../models/report-data'; import { ReportData } from '../../models/report-data';
import { ChartData } from '../../models/chart-data'; import { ChartData } from '../../models/chart-data';
import { ChartColorsDefinition } from '../../models/chart-colors'; 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'; import { MetaInfoData } from '../../models/meta-info-data';
@Component({ @Component({
@ -27,7 +27,7 @@ export class WorkbasketReportPlannedDateComponent implements OnInit {
lineChartColors = ChartColorsDefinition.getColors(); lineChartColors = ChartColorsDefinition.getColors();
constructor(private restConnectorService: RestConnectorService) {} constructor(private restConnectorService: MonitorService) {}
async ngOnInit() { async ngOnInit() {
this.reportData = await this.restConnectorService.getWorkbasketStatisticsQueryingByPlannedDate().toPromise(); this.reportData = await this.restConnectorService.getWorkbasketStatisticsQueryingByPlannedDate().toPromise();

View File

@ -3,5 +3,5 @@ export class MetaInfoData {
date: string; date: string;
header: Array<string>; header: Array<string>;
rowDesc: Array<string>; rowDesc: Array<string>;
totalDesc: string; sumRowDesc: string;
} }

View File

@ -17,7 +17,7 @@ import { TaskReportComponent } from './components/task-report/task-report.compon
import { ClassificationReportComponent } from './components/classification-report/classification-report.component'; import { ClassificationReportComponent } from './components/classification-report/classification-report.component';
import { TimestampReportComponent } from './components/timestamp-report/timestamp-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 { WorkbasketReportComponent } from './components/workbasket-report/workbasket-report.component';
import { WorkbasketReportPlannedDateComponent } from './components/workbasket-report-planned-date/workbasket-report-planned-date.component'; import { WorkbasketReportPlannedDateComponent } from './components/workbasket-report-planned-date/workbasket-report-planned-date.component';
@ -51,6 +51,6 @@ const DECLARATIONS = [
@NgModule({ @NgModule({
declarations: DECLARATIONS, declarations: DECLARATIONS,
imports: MODULES, imports: MODULES,
providers: [RestConnectorService, MapToIterable] providers: [MonitorService, MapToIterable]
}) })
export class MonitorModule {} export class MonitorModule {}

View File

@ -4,28 +4,40 @@ import { environment } from 'environments/environment';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { ChartData } from 'app/monitor/models/chart-data'; import { ChartData } from 'app/monitor/models/chart-data';
import { ReportData } from '../models/report-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/'; const monitorUrl = '/v1/monitor/';
@Injectable() @Injectable()
export class RestConnectorService { export class MonitorService {
constructor(private httpClient: HttpClient) {} constructor(private httpClient: HttpClient) {}
getTaskStatusReport(): Observable<ReportData> { getTaskStatusReport(): Observable<ReportData> {
const queryParams = {
states: [TaskState.READY, TaskState.CLAIMED, TaskState.COMPLETED]
};
return this.httpClient.get<ReportData>( return this.httpClient.get<ReportData>(
`${environment.taskanaRestUrl + monitorUrl}tasks-status-report?states=READY,CLAIMED,COMPLETED` `${environment.taskanaRestUrl + monitorUrl}tasks-status-report${asUrlQueryString(queryParams)}`
); );
} }
getWorkbasketStatisticsQueryingByDueDate(): Observable<ReportData> { getWorkbasketStatisticsQueryingByDueDate(): Observable<ReportData> {
const queryParams = {
states: [TaskState.READY, TaskState.CLAIMED, TaskState.COMPLETED]
};
return this.httpClient.get<ReportData>( return this.httpClient.get<ReportData>(
`${environment.taskanaRestUrl + monitorUrl}tasks-workbasket-report?states=READY,CLAIMED,COMPLETED` `${environment.taskanaRestUrl + monitorUrl}tasks-workbasket-report${asUrlQueryString(queryParams)}`
); );
} }
getWorkbasketStatisticsQueryingByPlannedDate(): Observable<ReportData> { getWorkbasketStatisticsQueryingByPlannedDate(): Observable<ReportData> {
const queryParams = {
daysInPast: 7,
states: [TaskState.READY, TaskState.CLAIMED, TaskState.COMPLETED]
};
return this.httpClient.get<ReportData>( return this.httpClient.get<ReportData>(
`${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)}`
); );
} }

View File

@ -1,127 +0,0 @@
<!-- WORKBASKET FILTER -->
<div *ngIf="filterTypeIsWorkbasket(); else taskType">
<!-- COLLAPSED WORKBASKET FILTER -->
<div class="filter__collapsed-filter" *ngIf="!isExpanded">
<!-- TEXT INPUT -->
<mat-form-field appearance="legacy" floatLabel="auto" class="collapsed-filter_input-field">
<mat-label>Filter by name</mat-label>
<input matInput [(ngModel)]="filter.filterParams.name" matTooltip="Type to filter by name" (keyup.enter)="search()">
</mat-form-field>
<!-- CLEAR BUTTON -->
<button mat-stroked-button (click)="clear(); search()" matTooltip="Clear workbasket filter" class="filter__undo-button">
<mat-icon style="color: #555">undo</mat-icon>
</button>
<!-- SEARCH BUTTON -->
<button mat-stroked-button (click)="search()" matTooltip="Search by given filter" class="filter__search-button">
<mat-icon>search</mat-icon>
</button>
</div>
<!-- EXPANDED WORKBASKET FILTER -->
<div class="filter" *ngIf="isExpanded">
<!-- TEXT INPUT -->
<div class="filter__text-input">
<div class="filter__name-and-key-input">
<mat-form-field appearance="legacy" floatLabel="auto" class="filter__input-field">
<mat-label>Filter by name</mat-label>
<input matInput [(ngModel)]="filter.filterParams.name" matTooltip="Type to filter by name" (keyup.enter)="search()">
</mat-form-field>
<mat-form-field appearance="legacy" floatLabel="auto" class="filter__input-field">
<mat-label>Filter by key</mat-label>
<input matInput [(ngModel)]="filter.filterParams.key" matTooltip="Type to filter by key" (keyup.enter)="search()">
</mat-form-field>
</div>
<div class="filter__name-and-key-input">
<mat-form-field appearance="legacy" floatLabel="auto" class="filter__input-field">
<mat-label>Filter by description</mat-label>
<input matInput [(ngModel)]="filter.filterParams.description" matTooltip="Type to filter by description" (keyup.enter)="search()">
</mat-form-field>
<mat-form-field appearance="legacy" floatLabel="auto" class="filter__input-field">
<mat-label>Filter by owner</mat-label>
<input matInput [(ngModel)]="filter.filterParams.owner" matTooltip="Type to filter by owner" (keyup.enter)="search()">
</mat-form-field>
</div>
</div>
<!-- SEARCH AND CLEAR BUTTON -->
<div class="filter__action-buttons">
<!-- TYPE FILTER -->
<button mat-stroked-button [matMenuTriggerFor]="menu" matTooltip="Filter workbaskets by type">
Filter by type
<mat-icon *ngIf="filter.filterParams?.type == ''" style="color: #555">filter_list</mat-icon>
<taskana-administration-icon-type *ngIf="filter.filterParams?.type != ''" [type]="filter.filterParams?.type"> </taskana-administration-icon-type>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item *ngFor="let type of allTypes | mapValues" (click)="selectType(type.key); search()">
<taskana-administration-icon-type [type]='type.key' [text]="type.value"></taskana-administration-icon-type>
</button>
</mat-menu>
<!-- CLEAR BUTTON -->
<button mat-stroked-button (click)="clear(); search()" matTooltip="Clear workbasket filter">
Reset
<mat-icon style="color: #555">undo</mat-icon>
</button>
<!-- SEARCH BUTTON -->
<button mat-stroked-button (click)="search()" matTooltip="Search by given filter" class="filter__search-button">
Apply
<mat-icon>search</mat-icon>
</button>
</div>
</div>
</div>
<!-- TASK FILTER -->
<ng-template #taskType>
<div class="row">
<div class="col-xs-2">
<taskana-shared-number-picker [(ngModel)]="filter.filterParams.priority" (keyup.enter)="search()" title="priority" id="display-priority-filter"></taskana-shared-number-picker>
</div>
<div class="col-xs-4">
<input type="text" [(ngModel)]="filter.filterParams.name" (keyup.enter)="search()" class="form-control" id="display-name-filter"
placeholder="Filter name">
</div>
<div class="col-xs-4">
<input type="text" [(ngModel)]="filter.filterParams.owner" (keyup.enter)="search()" class="form-control" id="display-owner-filter"
placeholder="Filter owner">
</div>
<button (click)="clear(); search()" class="btn btn-default pull-right margin-right" type="button" data-toggle="tooltip"
title="Clear">
<span class="material-icons md-20 blue">clear</span>
</button>
</div>
<div class="row">
<div class="dropdown col-xs-2 col-xs-offset-2">
<button class="btn btn-default" data-toggle="dropdown" type="button" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="true" title="State: {{filter.filterParams.state ? filter.filterParams?.state : 'All'}}">
<span>{{filter.filterParams.state ? filter.filterParams?.state : 'All'}}</span>
</button>
<ul class="dropdown-menu dropdown-menu-users" role="menu">
<li>
<a *ngFor="let state of allStates | mapValues" type="button" (click)="selectState(state.key); search()"
data-toggle="tooltip" [title]="state.value">
<label class="blue">{{state.value}}</label>
</a>
</li>
</ul>
</div>
<button (click)="search()" type="button" class="btn btn-default pull-right margin-right" data-toggle="tooltip"
title="Search">
<span class="material-icons md-20 blue">search</span>
</button>
</div>
</ng-template>

View File

@ -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<ICONTYPES, string> = new Map([
[ICONTYPES.ALL, 'All'],
[ICONTYPES.PERSONAL, 'Personal'],
[ICONTYPES.GROUP, 'Group'],
[ICONTYPES.CLEARANCE, 'Clearance'],
[ICONTYPES.TOPIC, 'Topic']
]);
@Input() allStates: Map<string, string> = 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<Filter>();
@Output() inputComponent = new EventEmitter<string>();
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);
}
}

View File

@ -29,9 +29,9 @@ export class DialogPopUpComponent implements OnInit {
} }
initError() { initError() {
this.title = notifications.get(this.data.key).name || ''; this.title = notifications.get(this.data.key).left || '';
this.message = 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 ? this.data.passedError.error.message
: ''; : '';
if (this.data.additions) { if (this.data.additions) {

View File

@ -10,8 +10,8 @@
<!-- SORT DIRECTION --> <!-- SORT DIRECTION -->
<mat-menu #sortDirection="matMenu"> <mat-menu #sortDirection="matMenu">
<!-- ASCENDING ORDER BUTTON --> <!-- ASCENDING ORDER BUTTON -->
<button mat-menu-item (click)="changeOrder('asc')"> <button mat-menu-item (click)="changeOrder(sortDirectionEnum.ASC)">
<span *ngIf="sort.sortDirection === 'asc'; else coloredAsc"> <span *ngIf="sort.order === 'asc'; else coloredAsc">
<mat-icon class="sort__selected-value"> arrow_upward </mat-icon> <mat-icon class="sort__selected-value"> arrow_upward </mat-icon>
<span class="sort__selected-value"> Ascending </span> <span class="sort__selected-value"> Ascending </span>
</span> </span>
@ -22,8 +22,8 @@
</button> </button>
<!-- DESCENDING ORDER BUTTON --> <!-- DESCENDING ORDER BUTTON -->
<button mat-menu-item (click)="changeOrder('desc')"> <button mat-menu-item (click)="changeOrder(sortDirectionEnum.DESC)">
<span *ngIf="sort.sortDirection === 'desc'; else coloredDesc"> <span *ngIf="sort.order === 'desc'; else coloredDesc">
<mat-icon class="sort__selected-value"> arrow_downward </mat-icon> <mat-icon class="sort__selected-value"> arrow_downward </mat-icon>
<span class="sort__selected-value"> Descending </span> <span class="sort__selected-value"> Descending </span>
</span> </span>
@ -38,7 +38,7 @@
<mat-menu #sortValue="matMenu"> <mat-menu #sortValue="matMenu">
<button mat-menu-item *ngFor="let sortingField of sortingFields | mapValues" <button mat-menu-item *ngFor="let sortingField of sortingFields | mapValues"
(click)="changeSortBy(sortingField.key)"> (click)="changeSortBy(sortingField.key)">
<span class="{{sortingField.key === sort.sortBy ? 'sort__selected-value' : ''}}"> <span class="{{sortingField.key === sort['sort-by'] ? 'sort__selected-value' : ''}}">
{{sortingField.value}} {{sortingField.value}}
</span> </span>
<ng-template #coloredValue> <ng-template #coloredValue>

View File

@ -6,26 +6,32 @@ import { Direction, Sorting } from 'app/shared/models/sorting';
templateUrl: './sort.component.html', templateUrl: './sort.component.html',
styleUrls: ['./sort.component.scss'] styleUrls: ['./sort.component.scss']
}) })
export class SortComponent implements OnInit { export class SortComponent<T> implements OnInit {
@Input() sortingFields: Map<string, string>; @Input() sortingFields: Map<T, string>;
@Input() menuPosition = 'right'; @Input() menuPosition = 'right';
@Input() defaultSortBy = 'key'; @Input() defaultSortBy: T;
@Output() performSorting = new EventEmitter<Sorting>(); @Output() performSorting = new EventEmitter<Sorting<T>>();
sort: Sorting = new Sorting(); sort: Sorting<T> = {
'sort-by': undefined,
order: Direction.ASC
};
// this allows the html template to use the Direction enum.
sortDirectionEnum = Direction;
ngOnInit() { ngOnInit() {
this.sort.sortBy = this.defaultSortBy; this.sort['sort-by'] = this.defaultSortBy;
} }
changeOrder(sortDirection: string) { changeOrder(sortDirection: Direction) {
this.sort.sortDirection = sortDirection === Direction.ASC ? Direction.ASC : Direction.DESC; this.sort.order = sortDirection;
this.search(); this.search();
} }
changeSortBy(sortBy: string) { changeSortBy(sortBy: T) {
this.sort.sortBy = sortBy; this.sort['sort-by'] = sortBy;
this.search(); this.search();
} }

View File

@ -0,0 +1,46 @@
<div class="row">
<div class="col-xs-2">
<taskana-shared-number-picker [(ngModel)]="filter.priority[0]"
(keyup.enter)="search()" title="priority"
id="display-priority-filter"></taskana-shared-number-picker>
</div>
<div class="col-xs-4">
<input type="text" [(ngModel)]="filter['name-like'][0]" (keyup.enter)="search()"
class="form-control" id="display-name-filter"
placeholder="Filter name">
</div>
<div class="col-xs-4">
<input type="text" [(ngModel)]="filter['owner-like'][0]" (keyup.enter)="search()"
class="form-control" id="display-owner-filter"
placeholder="Filter owner">
</div>
<button (click)="clear(); search()" class="btn btn-default pull-right margin-right" type="button"
data-toggle="tooltip"
title="Clear">
<span class="material-icons md-20 blue">clear</span>
</button>
</div>
<div class="row">
<div class="dropdown col-xs-2 col-xs-offset-2">
<button class="btn btn-default" type="button" data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="true"
title="State: {{filter.state && filter.state[0] ? filter.state[0] : 'All'}}">
<span>{{filter.state ? filter.state[0] : 'All'}}</span>
</button>
<ul class="dropdown-menu dropdown-menu-users" role="menu">
<li>
<a *ngFor="let state of allStates | mapValues" type="button"
(click)="selectState(state.key); search()"
data-toggle="tooltip" [title]="state.value">
<label class="blue">{{state.value}}</label>
</a>
</li>
</ul>
</div>
<button (click)="search()" type="button" class="btn btn-default pull-right margin-right"
data-toggle="tooltip"
title="Search">
<span class="material-icons md-20 blue">search</span>
</button>
</div>

View File

@ -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<TaskQueryFilterParameter>();
allStates: Map<TaskState, string> = 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': []
};
}
}

View File

@ -15,7 +15,7 @@ export class ToastComponent implements OnInit {
ngOnInit(): void { ngOnInit(): void {
if (this.data) { if (this.data) {
this.message = notifications.get(this.data.key).text; this.message = notifications.get(this.data.key).right;
if (this.data.additions) { if (this.data.additions) {
this.data.additions.forEach((value: string, replacementKey: string) => { this.data.additions.forEach((value: string, replacementKey: string) => {
this.message = this.message.replace(`{${replacementKey}}`, value); this.message = this.message.replace(`{${replacementKey}}`, value);

View File

@ -0,0 +1,89 @@
<div>
<!-- COLLAPSED WORKBASKET FILTER -->
<div class="filter__collapsed-filter" *ngIf="!isExpanded">
<!-- TEXT INPUT -->
<mat-form-field appearance="legacy" floatLabel="auto" class="collapsed-filter_input-field">
<mat-label>Filter by name</mat-label>
<input matInput [(ngModel)]="filter['name-like'][0]" matTooltip="Type to filter by name"
(keyup.enter)="search()">
</mat-form-field>
<!-- CLEAR BUTTON -->
<button mat-stroked-button (click)="clear(); search()" matTooltip="Clear workbasket filter"
class="filter__undo-button">
<mat-icon style="color: #555">undo</mat-icon>
</button>
<!-- SEARCH BUTTON -->
<button mat-stroked-button (click)="search()" matTooltip="Search by given filter" class="filter__search-button">
<mat-icon>search</mat-icon>
</button>
</div>
<!-- EXPANDED WORKBASKET FILTER -->
<div class="filter" *ngIf="isExpanded">
<!-- TEXT INPUT -->
<div class="filter__text-input">
<div class="filter__name-and-key-input">
<mat-form-field appearance="legacy" floatLabel="auto" class="filter__input-field">
<mat-label>Filter by name</mat-label>
<input matInput [(ngModel)]="filter['name-like'][0]" matTooltip="Type to filter by name"
(keyup.enter)="search()">
</mat-form-field>
<mat-form-field appearance="legacy" floatLabel="auto" class="filter__input-field">
<mat-label>Filter by key</mat-label>
<input matInput [(ngModel)]="filter['key-like'][0]" matTooltip="Type to filter by key"
(keyup.enter)="search()">
</mat-form-field>
</div>
<div class="filter__name-and-key-input">
<mat-form-field appearance="legacy" floatLabel="auto" class="filter__input-field">
<mat-label>Filter by description</mat-label>
<input matInput [(ngModel)]="filter['description-like']" matTooltip="Type to filter by description"
(keyup.enter)="search()">
</mat-form-field>
<mat-form-field appearance="legacy" floatLabel="auto" class="filter__input-field">
<mat-label>Filter by owner</mat-label>
<input matInput [(ngModel)]="filter['owner-like'][0]" matTooltip="Type to filter by owner"
(keyup.enter)="search()">
</mat-form-field>
</div>
</div>
<!-- SEARCH AND CLEAR BUTTON -->
<div class="filter__action-buttons">
<!-- TYPE FILTER -->
<button mat-stroked-button [matMenuTriggerFor]="menu" matTooltip="Filter workbaskets by type">
Filter by type
<mat-icon *ngIf="filter.type" style="color: #555">filter_list</mat-icon>
<taskana-administration-icon-type *ngIf="filter.type[0]"
[type]="filter.type[0]"></taskana-administration-icon-type>
</button>
<mat-menu #menu="matMenu">
<button mat-menu-item *ngFor="let type of allTypes | mapValues" (click)="selectType(type.key); search()">
<taskana-administration-icon-type [type]='type.key' [text]="type.value"></taskana-administration-icon-type>
</button>
</mat-menu>
<!-- CLEAR BUTTON -->
<button mat-stroked-button (click)="clear(); search()" matTooltip="Clear workbasket filter">
Reset
<mat-icon style="color: #555">undo</mat-icon>
</button>
<!-- SEARCH BUTTON -->
<button mat-stroked-button (click)="search()" matTooltip="Search by given filter" class="filter__search-button">
Apply
<mat-icon>search</mat-icon>
</button>
</div>
</div>
</div>

View File

@ -18,15 +18,10 @@
margin: 0 8px 0 8px; margin: 0 8px 0 8px;
} }
.filter__action-buttons {
width: 100%;
padding: 10px 0;
display: flex;
justify-content: space-between;
}
.filter__undo-buttons { .filter__undo-buttons {
margin-left: 4px; margin-left: 4px;
} }
.filter__search-button { .filter__search-button {
background: $aquamarine; background: $aquamarine;
color: white; color: white;
@ -38,37 +33,15 @@
justify-content: space-between; justify-content: space-between;
} }
.dropdown-menu-users { .filter__action-buttons {
& > li { width: 100%;
margin-bottom: 5px; padding: 10px 0;
} display: flex;
margin-left: 15px; justify-content: space-between;
} }
.list-group-search { .filter__search-button {
padding: 0 15px; background: $aquamarine;
border-top: 1px solid #ddd; color: white;
} margin-left: 4px;
.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;
} }

View File

@ -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<WorkbasketType, string> = ALL_TYPES;
@Input() component: string;
@Input() isExpanded: boolean;
@Output() performFilter = new EventEmitter<Pair<string, WorkbasketQueryFilterParameter>>();
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 });
}
}

View File

@ -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[];
}

View File

@ -7,8 +7,8 @@ export class ErrorModel {
public readonly message: string; public readonly message: string;
constructor(key: NOTIFICATION_TYPES, passedError?: HttpErrorResponse, addition?: Map<String, String>) { constructor(key: NOTIFICATION_TYPES, passedError?: HttpErrorResponse, addition?: Map<String, String>) {
this.title = notifications.get(key).name; this.title = notifications.get(key).left;
let messageTemp = notifications.get(key).text; let messageTemp = notifications.get(key).right;
this.errObj = passedError; this.errObj = passedError;
if (addition) { if (addition) {
addition.forEach((value: string, replacementKey: string) => { addition.forEach((value: string, replacementKey: string) => {

View File

@ -1,7 +0,0 @@
export class Filter {
filterParams: any;
constructor(filterParams?: any) {
this.filterParams = filterParams;
}
}

View File

@ -1,7 +0,0 @@
export enum ICONTYPES {
ALL = 'ALL',
PERSONAL = 'PERSONAL',
GROUP = 'GROUP',
CLEARANCE = 'CLEARANCE',
TOPIC = 'TOPIC'
}

View File

@ -59,163 +59,194 @@ export enum NOTIFICATION_TYPES {
WARNING_CANT_COPY WARNING_CANT_COPY
} }
export const notifications = new Map<NOTIFICATION_TYPES, Pair>([ export const notifications = new Map<NOTIFICATION_TYPES, Pair<string, string>>([
// access-items-management.component.ts // 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 // 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 // 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 // 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 // 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 // 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 // classification-details.component
[ [
NOTIFICATION_TYPES.SELECT_ERR, 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 // 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 // import-export.component
[ [
NOTIFICATION_TYPES.IMPORT_ERR_1, 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 // import-export.component
[ [
NOTIFICATION_TYPES.IMPORT_ERR_2, 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 // import-export.component
[ [
NOTIFICATION_TYPES.IMPORT_ERR_3, 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 // import-export.component
[ [
NOTIFICATION_TYPES.IMPORT_ERR_4, 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 // import-export.component
[ [
NOTIFICATION_TYPES.UPLOAD_ERR, NOTIFICATION_TYPES.UPLOAD_ERR,
new Pair( {
'Upload failed', left: 'Upload failed',
`The upload didn't proceed sucessfully. right: `The upload didn't proceed sucessfully.
\n The uploaded file probably exceeded the maximum file size of 10 MB.` \n The uploaded file probably exceeded the maximum file size of 10 MB.`
) }
], ],
// taskdetails.component // 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 // 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 // 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 // workbaskets-distribution-targets.component
[ [
NOTIFICATION_TYPES.SAVE_ERR_3, 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 // workbasket-information.component
[ [
NOTIFICATION_TYPES.REMOVE_ERR_2, 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 // 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 // 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 // workbasket-information.component
[ [
NOTIFICATION_TYPES.MARK_ERR, NOTIFICATION_TYPES.MARK_ERR,
new Pair( {
'Workbasket was marked for deletion.', left: 'Workbasket was marked for deletion.',
'The Workbasket {workbasketId} still contains completed tasks and could not be deleted.' + right:
'The Workbasket {workbasketId} still contains completed tasks and could not be deleted.' +
' Instead is was marked for deletion and will be deleted automatically ' + ' Instead is was marked for deletion and will be deleted automatically ' +
'as soon as the completed tasks are cleared from the database.' 'as soon as the completed tasks are cleared from the database.'
) }
], ],
// domain.guard // domain.guard
[ [
NOTIFICATION_TYPES.FETCH_ERR_5, 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 // history.guard
[ [
NOTIFICATION_TYPES.FETCH_ERR_6, 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 // 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 // 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 // spinner.component
[ [
NOTIFICATION_TYPES.TIMEOUT_ERR, NOTIFICATION_TYPES.TIMEOUT_ERR,
new Pair( {
'There was an error with your request, please make sure you have internet connection', left: 'There was an error with your request, please make sure you have internet connection',
'Request time exceeded' right: 'Request time exceeded'
) }
], ],
// taskdetails.component // 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 // 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 // ALERTS
// access-items-management.component // 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 // 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 // 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 // classification-details.component
// access-items.component // access-items.component
// workbasket.distribution-targets.component // workbasket.distribution-targets.component
// workbasket-information.component // workbasket-information.component
// taskdetails.component // taskdetails.component
[NOTIFICATION_TYPES.INFO_ALERT, new Pair('', 'Information restored')], [NOTIFICATION_TYPES.INFO_ALERT, { left: '', right: 'Information restored' }],
// classification-details.component // 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 // 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 // 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 // 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 // workbasket.distribution-targets.component
[ [
NOTIFICATION_TYPES.SUCCESS_ALERT_8, 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 // workbasket-information.component
[ [
NOTIFICATION_TYPES.SUCCESS_ALERT_9, 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 // 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 // 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 // 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 // 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 // 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 // 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 // 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 // taskdetails.component
[NOTIFICATION_TYPES.SUCCESS_ALERT_14, new Pair('', 'Updating was successful.')], [NOTIFICATION_TYPES.SUCCESS_ALERT_14, { left: '', right: 'Updating was successful.' }],
// taskdetails.component // 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 // task-master.component
[NOTIFICATION_TYPES.INFO_ALERT_2, new Pair('', 'The selected Workbasket is empty!')], [NOTIFICATION_TYPES.INFO_ALERT_2, { left: '', right: 'The selected Workbasket is empty!' }],
[NOTIFICATION_TYPES.WARNING_CANT_COPY, new Pair('', "Can't copy a not created classification")] [NOTIFICATION_TYPES.WARNING_CANT_COPY, { left: '', right: "Can't copy a not created classification" }]
]); ]);

View File

@ -1,3 +1,4 @@
export class Pair { export interface Pair<L, R> {
constructor(public name?: string, public text?: string) {} left: L;
right: R;
} }

View File

@ -0,0 +1,4 @@
export interface QueryPagingParameter {
page?: number;
'page-size'?: number;
}

View File

@ -1,13 +1,87 @@
export enum Direction { export enum Direction {
ASC = 'asc', ASC = 'ASCENDING',
DESC = 'desc' DESC = 'DESCENDING'
} }
export class Sorting { export interface Sorting<T> {
sortBy: string; 'sort-by': T;
sortDirection: string; order: Direction;
constructor(sortBy: string = 'key', sortDirection: Direction = Direction.ASC) { }
this.sortBy = sortBy;
this.sortDirection = sortDirection; 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<TaskQuerySortParameter, string> = 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<WorkbasketQuerySortParameter, string> = 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'
} }

View File

@ -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[];
}

View File

@ -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[];
}

View File

@ -0,0 +1,16 @@
export enum TaskState {
READY = 'READY',
CLAIMED = 'CLAIMED',
COMPLETED = 'COMPLETED',
CANCELLED = 'CANCELLED',
TERMINATED = 'TERMINATED'
}
export const ALL_STATES: Map<TaskState, string> = new Map([
[undefined, 'All'],
[TaskState.READY, 'Ready'],
[TaskState.CLAIMED, 'Claimed'],
[TaskState.COMPLETED, 'Completed'],
[TaskState.CANCELLED, 'Cancelled'],
[TaskState.TERMINATED, 'Terminated']
]);

View File

@ -0,0 +1,6 @@
export interface WorkbasketAccessItemQueryFilterParameter {
'workbasket-key'?: string[];
'workbasket-key-like'?: string[];
'access-id'?: string[];
'access-id-like'?: string[];
}

View File

@ -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'
}

View File

@ -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[];
}

View File

@ -1,11 +1,11 @@
import { ICONTYPES } from './icon-types'; import { WorkbasketType } from './workbasket-type';
export interface WorkbasketSummary { export interface WorkbasketSummary {
workbasketId?: string; workbasketId?: string;
key?: string; key?: string;
name?: string; name?: string;
domain?: string; domain?: string;
type?: ICONTYPES; type?: WorkbasketType;
description?: string; description?: string;
owner?: string; owner?: string;
custom1?: string; custom1?: string;

View File

@ -0,0 +1,14 @@
export enum WorkbasketType {
PERSONAL = 'PERSONAL',
GROUP = 'GROUP',
CLEARANCE = 'CLEARANCE',
TOPIC = 'TOPIC'
}
export const ALL_TYPES: Map<WorkbasketType, string> = new Map([
[undefined, 'All'],
[WorkbasketType.PERSONAL, 'Personal'],
[WorkbasketType.GROUP, 'Group'],
[WorkbasketType.CLEARANCE, 'Clearance'],
[WorkbasketType.TOPIC, 'Topic']
]);

View File

@ -1,12 +1,12 @@
import { Links } from './links'; import { Links } from './links';
import { ICONTYPES } from './icon-types'; import { WorkbasketType } from './workbasket-type';
export interface Workbasket { export interface Workbasket {
workbasketId?: string; workbasketId?: string;
key?: string; key?: string;
name?: string; name?: string;
domain?: string; domain?: string;
type?: ICONTYPES; type?: WorkbasketType;
description?: string; description?: string;
owner?: string; owner?: string;
custom1?: string; custom1?: string;
@ -21,6 +21,8 @@ export interface Workbasket {
created?: string; created?: string;
modified?: string; modified?: string;
_links?: Links; _links?: Links;
// this is not part of the API, but needed for frontend
selected?: boolean;
} }
export const customFieldCount: number = 4; export const customFieldCount: number = 4;

View File

@ -1,31 +1,21 @@
import { Pipe, PipeTransform } from '@angular/core'; import { Pipe, PipeTransform } from '@angular/core';
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters';
import { WorkbasketSummary } from '../models/workbasket-summary'; import { WorkbasketSummary } from '../models/workbasket-summary';
import { Side } from '../../administration/components/workbasket-distribution-targets/workbasket-distribution-targets.component';
@Pipe({ name: 'selectWorkbaskets' }) @Pipe({ name: 'selectWorkbaskets' })
export class SelectWorkBasketPipe implements PipeTransform { export class SelectWorkBasketPipe implements PipeTransform {
transform(originArray: any, selectionArray: any, arg1: any): WorkbasketSummary[] { transform(
let returnArray = []; allWorkbaskets: WorkbasketSummary[],
if (!originArray || !selectionArray) { selectedWorkbaskets: WorkbasketSummary[],
return returnArray; side: Side
): WorkbasketSummary[] {
if (!allWorkbaskets || !selectedWorkbaskets) {
return [];
} }
for (let index = originArray.length - 1; index >= 0; index--) { if (side === Side.SELECTED) {
if ( return selectedWorkbaskets;
(arg1 &&
!selectionArray.some(
(elementToRemove) => originArray[index].workbasketId === elementToRemove.workbasketId
)) ||
(!arg1 &&
selectionArray.some((elementToRemove) => originArray[index].workbasketId === elementToRemove.workbasketId))
) {
originArray.splice(index, 1);
}
} }
const isNotSelectedWorkbasket = (wb) => !selectedWorkbaskets.some((sWb) => sWb.workbasketId === wb.workbasketId);
if (originArray.length > TaskanaQueryParameters.pageSize) { return allWorkbaskets.filter(isNotSelectedWorkbasket);
originArray.slice(0, TaskanaQueryParameters.pageSize);
}
returnArray = originArray;
return returnArray;
} }
} }

View File

@ -4,10 +4,11 @@ import { environment } from 'environments/environment';
import { AccessIdDefinition } from 'app/shared/models/access-id'; import { AccessIdDefinition } from 'app/shared/models/access-id';
import { Observable, of } from 'rxjs'; import { Observable, of } from 'rxjs';
import { WorkbasketAccessItemsRepresentation } from 'app/shared/models/workbasket-access-items-representation'; import { WorkbasketAccessItemsRepresentation } from 'app/shared/models/workbasket-access-items-representation';
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters'; import { Sorting, WorkbasketAccessItemQuerySortParameter } from 'app/shared/models/sorting';
import { Sorting } from 'app/shared/models/sorting';
import { QueryParameters } from 'app/shared/models/query-parameters';
import { StartupService } from '../startup/startup.service'; 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({ @Injectable({
providedIn: 'root' providedIn: 'root'
@ -34,16 +35,17 @@ export class AccessIdsService {
} }
getAccessItems( getAccessItems(
accessIds: AccessIdDefinition[], filterParameter?: WorkbasketAccessItemQueryFilterParameter,
accessIdLike?: string, sortParameter?: Sorting<WorkbasketAccessItemQuerySortParameter>,
workbasketKeyLike?: string, pagingParameter?: QueryPagingParameter
sortModel: Sorting = new Sorting('workbasket-key')
): Observable<WorkbasketAccessItemsRepresentation> { ): Observable<WorkbasketAccessItemsRepresentation> {
return this.httpClient.get<WorkbasketAccessItemsRepresentation>( return this.httpClient.get<WorkbasketAccessItemsRepresentation>(
encodeURI( encodeURI(
`${environment.taskanaRestUrl}/v1/workbasket-access-items/${TaskanaQueryParameters.getQueryParameters( `${environment.taskanaRestUrl}/v1/workbasket-access-items/${asUrlQueryString({
AccessIdsService.accessIdsParameters(sortModel, accessIds, accessIdLike, workbasketKeyLike) ...filterParameter,
)}` ...sortParameter,
...pagingParameter
})}`
) )
); );
} }
@ -53,22 +55,4 @@ export class AccessIdsService {
`${environment.taskanaRestUrl}/v1/workbasket-access-items/?access-id=${accessId}` `${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;
}
} }

View File

@ -1,22 +1,19 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { mergeMap, tap } from 'rxjs/operators';
import { Classification } from 'app/shared/models/classification'; import { Classification } from 'app/shared/models/classification';
import { ClassificationPagingList } from 'app/shared/models/classification-paging-list'; import { ClassificationPagingList } from 'app/shared/models/classification-paging-list';
import { DomainService } from 'app/shared/services/domain/domain.service'; import { DomainService } from 'app/shared/services/domain/domain.service';
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters'; import { ClassificationQuerySortParameter, Sorting } from 'app/shared/models/sorting';
import { Direction } from 'app/shared/models/sorting';
import { QueryParameters } from 'app/shared/models/query-parameters';
import { StartupService } from '../startup/startup.service'; 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() @Injectable()
export class ClassificationsService { export class ClassificationsService {
private classificationResourcePromise: Promise<ClassificationPagingList>;
private lastDomain: string;
constructor( constructor(
private httpClient: HttpClient, private httpClient: HttpClient,
private domainService: DomainService, private domainService: DomainService,
@ -27,47 +24,17 @@ export class ClassificationsService {
return this.startupService.getTaskanaRestUrl() + '/v1/classifications/'; 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 // GET
getClassifications(classificationType?: string): Observable<ClassificationPagingList> { getClassifications(
return this.domainService.getSelectedDomain().pipe( filterParameter?: ClassificationQueryFilterParameters,
mergeMap((domain) => sortParameter?: Sorting<ClassificationQuerySortParameter>,
this.httpClient.get<ClassificationPagingList>( pagingParameter?: QueryPagingParameter
`${this.url}${TaskanaQueryParameters.getQueryParameters( ): Observable<ClassificationPagingList> {
ClassificationsService.classificationParameters(domain, classificationType) return this.httpClient.get<ClassificationPagingList>(
)}` `${this.url}${asUrlQueryString({ ...filterParameter, ...sortParameter, ...pagingParameter })}`
)
),
tap(() => this.domainService.domainChangedComplete())
); );
} }
// GET
getClassificationsByDomain(domain: string, forceRefresh = false): Promise<ClassificationPagingList> {
if (this.lastDomain !== domain || !this.classificationResourcePromise || forceRefresh) {
this.lastDomain = domain;
this.classificationResourcePromise = this.httpClient
.get<ClassificationPagingList>(
`${this.url}${TaskanaQueryParameters.getQueryParameters(
ClassificationsService.classificationParameters(domain)
)}`
)
.toPromise();
}
return this.classificationResourcePromise;
}
// GET // GET
getClassification(id: string): Observable<Classification> { getClassification(id: string): Observable<Classification> {
return this.httpClient.get<Classification>(`${this.url}${id}`); return this.httpClient.get<Classification>(`${this.url}${id}`);

View File

@ -1,7 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Orientation } from 'app/shared/models/orientation'; import { Orientation } from 'app/shared/models/orientation';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters';
@Injectable() @Injectable()
export class OrientationService { export class OrientationService {
@ -9,8 +8,15 @@ export class OrientationService {
private currentOrientation; private currentOrientation;
public orientation = new BehaviorSubject<Orientation>(this.currentOrientation); public orientation = new BehaviorSubject<Orientation>(this.currentOrientation);
private static detectOrientation(): Orientation {
if (window.innerHeight > window.innerWidth) {
return Orientation.portrait;
}
return Orientation.landscape;
}
onResize() { onResize() {
const orientation = this.detectOrientation(); const orientation = OrientationService.detectOrientation();
if (orientation !== this.currentOrientation) { if (orientation !== this.currentOrientation) {
this.currentOrientation = orientation; this.currentOrientation = orientation;
if (!this.lock) { if (!this.lock) {
@ -41,14 +47,6 @@ export class OrientationService {
if (doubleList && window.innerWidth < 992) { if (doubleList && window.innerWidth < 992) {
cards = Math.floor(cards / 2); cards = Math.floor(cards / 2);
} }
TaskanaQueryParameters.pageSize = cards > 0 ? cards : 1;
return cards; return cards;
} }
private detectOrientation(): Orientation {
if (window.innerHeight > window.innerWidth) {
return Orientation.portrait;
}
return Orientation.landscape;
}
} }

View File

@ -7,13 +7,14 @@ import { WorkbasketAccessItems } from 'app/shared/models/workbasket-access-items
import { WorkbasketSummaryRepresentation } from 'app/shared/models/workbasket-summary-representation'; import { WorkbasketSummaryRepresentation } from 'app/shared/models/workbasket-summary-representation';
import { WorkbasketAccessItemsRepresentation } from 'app/shared/models/workbasket-access-items-representation'; import { WorkbasketAccessItemsRepresentation } from 'app/shared/models/workbasket-access-items-representation';
import { WorkbasketDistributionTargets } from 'app/shared/models/workbasket-distribution-targets'; 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 { DomainService } from 'app/shared/services/domain/domain.service';
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters';
import { mergeMap, tap, catchError } from 'rxjs/operators'; import { mergeMap, tap, catchError } from 'rxjs/operators';
import { QueryParameters } from 'app/shared/models/query-parameters';
import { WorkbasketRepresentation } from '../../models/workbasket-representation'; 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() @Injectable()
export class WorkbasketService { export class WorkbasketService {
@ -28,18 +29,9 @@ export class WorkbasketService {
// GET // GET
getWorkBasketsSummary( getWorkBasketsSummary(
forceRequest: boolean = false, forceRequest: boolean = false,
sortBy: string = TaskanaQueryParameters.parameters.KEY, filterParameter?: WorkbasketQueryFilterParameter,
order: string = Direction.ASC, sortParameter?: Sorting<WorkbasketQuerySortParameter>,
name?: string, pagingParameter?: QueryPagingParameter
nameLike?: string,
descLike?: string,
owner?: string,
ownerLike?: string,
type?: string,
key?: string,
keyLike?: string,
requiredPermission?: string,
allPages: boolean = false
) { ) {
if (this.workbasketSummaryRef && !forceRequest) { if (this.workbasketSummaryRef && !forceRequest) {
return this.workbasketSummaryRef; return this.workbasketSummaryRef;
@ -48,23 +40,11 @@ export class WorkbasketService {
return this.domainService.getSelectedDomain().pipe( return this.domainService.getSelectedDomain().pipe(
mergeMap((domain) => { mergeMap((domain) => {
this.workbasketSummaryRef = this.httpClient.get<WorkbasketSummaryRepresentation>( this.workbasketSummaryRef = this.httpClient.get<WorkbasketSummaryRepresentation>(
`${environment.taskanaRestUrl}/v1/workbaskets/${TaskanaQueryParameters.getQueryParameters( `${environment.taskanaRestUrl}/v1/workbaskets/${asUrlQueryString({
this.workbasketParameters( ...filterParameter,
sortBy, ...sortParameter,
order, ...pagingParameter
name, })}`
nameLike,
descLike,
owner,
ownerLike,
type,
key,
keyLike,
requiredPermission,
allPages,
domain
)
)}`
); );
return this.workbasketSummaryRef; return this.workbasketSummaryRef;
}), }),
@ -179,40 +159,5 @@ export class WorkbasketService {
return observableThrowError(errMsg); 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 // #endregion
} }

View File

@ -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 { MasterAndDetailComponent } from 'app/shared/components/master-and-detail/master-and-detail.component';
import { TaskanaTreeComponent } from 'app/administration/components/tree/tree.component'; import { TaskanaTreeComponent } from 'app/administration/components/tree/tree.component';
import { TypeAheadComponent } from 'app/shared/components/type-ahead/type-ahead.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 { IconTypeComponent } from 'app/administration/components/type-icon/icon-type.component';
import { FieldErrorDisplayComponent } from 'app/shared/components/field-error-display/field-error-display.component'; import { FieldErrorDisplayComponent } from 'app/shared/components/field-error-display/field-error-display.component';
import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatSnackBarModule } from '@angular/material/snack-bar';
@ -61,6 +60,8 @@ import { MatPaginatorModule } from '@angular/material/paginator';
import { MatSelectModule } from '@angular/material/select'; import { MatSelectModule } from '@angular/material/select';
import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatAutocompleteModule } from '@angular/material/autocomplete';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; 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 = [ const MODULES = [
CommonModule, CommonModule,
@ -93,7 +94,6 @@ const DECLARATIONS = [
OrderBy, OrderBy,
MapToIterable, MapToIterable,
SortComponent, SortComponent,
FilterComponent,
IconTypeComponent, IconTypeComponent,
FieldErrorDisplayComponent, FieldErrorDisplayComponent,
PaginationComponent, PaginationComponent,
@ -102,7 +102,9 @@ const DECLARATIONS = [
DatePickerComponent, DatePickerComponent,
DropdownComponent, DropdownComponent,
ToastComponent, ToastComponent,
DialogPopUpComponent DialogPopUpComponent,
WorkbasketFilterComponent,
TaskFilterComponent
]; ];
@NgModule({ @NgModule({

View File

@ -1,5 +1,7 @@
import { AccessIdDefinition } from '../../models/access-id'; 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 { export class SelectAccessId {
static readonly type = '[Access Items Management] Select access ID'; static readonly type = '[Access Items Management] Select access ID';
@ -14,10 +16,9 @@ export class GetGroupsByAccessId {
export class GetAccessItems { export class GetAccessItems {
static readonly type = '[Access Items Management] Get access items'; static readonly type = '[Access Items Management] Get access items';
constructor( constructor(
public accessIds: AccessIdDefinition[], public filterParameter?: WorkbasketAccessItemQueryFilterParameter,
public accessIdLike?: string, public sortParameter?: Sorting<WorkbasketAccessItemQuerySortParameter>,
public workbasketKeyLike?: string, public pagingParameter?: QueryPagingParameter
public sortModel: Sorting = new Sorting('workbasket-key')
) {} ) {}
} }

View File

@ -60,7 +60,7 @@ export class AccessItemsManagementState implements NgxsAfterBootstrap {
getAccessItems(ctx: StateContext<AccessItemsManagementStateModel>, action: GetAccessItems): Observable<any> { getAccessItems(ctx: StateContext<AccessItemsManagementStateModel>, action: GetAccessItems): Observable<any> {
this.requestInProgressService.setRequestInProgress(true); this.requestInProgressService.setRequestInProgress(true);
return this.accessIdsService return this.accessIdsService
.getAccessItems(action.accessIds, action.accessIdLike, action.workbasketKeyLike, action.sortModel) .getAccessItems(action.filterParameter, action.sortParameter, action.pagingParameter)
.pipe( .pipe(
take(1), take(1),
tap( tap(

View File

@ -1,19 +1,19 @@
import { Action, NgxsAfterBootstrap, State, StateContext } from '@ngxs/store'; import { Action, NgxsAfterBootstrap, State, StateContext } from '@ngxs/store';
import { Observable, of } from 'rxjs'; 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 { TaskanaDate } from 'app/shared/util/taskana.date';
import { import {
CategoriesResponse, CategoriesResponse,
ClassificationCategoriesService ClassificationCategoriesService
} from '../../services/classification-categories/classification-categories.service'; } from '../../services/classification-categories/classification-categories.service';
import { import {
SaveCreatedClassification,
DeselectClassification,
GetClassifications,
CopyClassification, CopyClassification,
CreateClassification, CreateClassification,
DeselectClassification,
GetClassifications,
RemoveSelectedClassification, RemoveSelectedClassification,
RestoreSelectedClassification, RestoreSelectedClassification,
SaveCreatedClassification,
SaveModifiedClassification, SaveModifiedClassification,
SelectClassification, SelectClassification,
SetSelectedClassificationType, SetSelectedClassificationType,
@ -23,6 +23,8 @@ import { ClassificationsService } from '../../services/classifications/classific
import { DomainService } from '../../services/domain/domain.service'; import { DomainService } from '../../services/domain/domain.service';
import { Classification } from '../../models/classification'; import { Classification } from '../../models/classification';
import { ClassificationSummary } from '../../models/classification-summary'; import { ClassificationSummary } from '../../models/classification-summary';
import { ClassificationQueryFilterParameters } from '../../models/classification-query-filter-parameters';
import { ClassificationQuerySortParameter, Direction, Sorting } from '../../models/sorting';
class InitializeStore { class InitializeStore {
static readonly type = '[ClassificationState] Initializing state'; static readonly type = '[ClassificationState] Initializing state';
@ -94,13 +96,22 @@ export class ClassificationState implements NgxsAfterBootstrap {
@Action(GetClassifications) @Action(GetClassifications)
getClassifications(ctx: StateContext<ClassificationStateModel>): Observable<any> { getClassifications(ctx: StateContext<ClassificationStateModel>): Observable<any> {
const { selectedClassificationType } = ctx.getState(); const { selectedClassificationType } = ctx.getState();
return this.classificationsService.getClassifications(selectedClassificationType).pipe( return this.domainService.getSelectedDomain().pipe(
take(1), mergeMap((domain) => {
tap((list) => const filter: ClassificationQueryFilterParameters = {
ctx.patchState({ domain: [domain],
classifications: list.classifications type: [selectedClassificationType]
}) };
) const sort: Sorting<ClassificationQuerySortParameter> = {
'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())
); );
} }

View File

@ -1,5 +1,5 @@
import { Workbasket } from '../../models/workbasket'; import { Workbasket } from '../../models/workbasket';
import { ICONTYPES } from '../../models/icon-types'; import { WorkbasketType } from '../../models/workbasket-type';
import { ACTION } from '../../models/action'; import { ACTION } from '../../models/action';
import { WorkbasketAccessItemsRepresentation } from '../../models/workbasket-access-items-representation'; import { WorkbasketAccessItemsRepresentation } from '../../models/workbasket-access-items-representation';
@ -93,7 +93,7 @@ export const selectedWorkbasketMock: Workbasket = {
key: 'sOrt003', key: 'sOrt003',
name: 'bAsxet2', name: 'bAsxet2',
domain: 'DOMAIN_A', domain: 'DOMAIN_A',
type: ICONTYPES.TOPIC, type: WorkbasketType.TOPIC,
description: 'Lorem ipsum dolor sit amet.', description: 'Lorem ipsum dolor sit amet.',
owner: 'Max', owner: 'Max',
custom1: '', custom1: '',
@ -194,7 +194,7 @@ export const workbasketReadStateMock = {
key: 'USER-2-1', key: 'USER-2-1',
name: 'PPK User 1 KSC 2', name: 'PPK User 1 KSC 2',
domain: 'DOMAIN_A', domain: 'DOMAIN_A',
type: ICONTYPES.PERSONAL, type: WorkbasketType.PERSONAL,
description: 'PPK User 1 KSC 2', description: 'PPK User 1 KSC 2',
owner: '', owner: '',
custom1: '', custom1: '',
@ -212,7 +212,7 @@ export const workbasketReadStateMock = {
key: 'USER-1-2', key: 'USER-1-2',
name: 'PPK User 2 KSC 1', name: 'PPK User 2 KSC 1',
domain: 'DOMAIN_A', domain: 'DOMAIN_A',
type: ICONTYPES.PERSONAL, type: WorkbasketType.PERSONAL,
description: 'PPK User 2 KSC 1', description: 'PPK User 2 KSC 1',
owner: 'Peter Maier', owner: 'Peter Maier',
custom1: 'custom1', custom1: 'custom1',
@ -230,7 +230,7 @@ export const workbasketReadStateMock = {
key: 'USER-2-2', key: 'USER-2-2',
name: 'PPK User 2 KSC 2', name: 'PPK User 2 KSC 2',
domain: 'DOMAIN_A', domain: 'DOMAIN_A',
type: ICONTYPES.PERSONAL, type: WorkbasketType.PERSONAL,
description: 'PPK User 2 KSC 2', description: 'PPK User 2 KSC 2',
owner: '', owner: '',
custom1: '', custom1: '',
@ -248,7 +248,7 @@ export const workbasketReadStateMock = {
key: 'TPK_VIP', key: 'TPK_VIP',
name: 'Themenpostkorb VIP', name: 'Themenpostkorb VIP',
domain: 'DOMAIN_A', domain: 'DOMAIN_A',
type: ICONTYPES.TOPIC, type: WorkbasketType.TOPIC,
description: 'Themenpostkorb VIP', description: 'Themenpostkorb VIP',
owner: '', owner: '',
custom1: '', custom1: '',
@ -266,7 +266,7 @@ export const workbasketReadStateMock = {
key: 'TPK_VIP_2', key: 'TPK_VIP_2',
name: 'Themenpostkorb VIP 2', name: 'Themenpostkorb VIP 2',
domain: 'DOMAIN_A', domain: 'DOMAIN_A',
type: ICONTYPES.TOPIC, type: WorkbasketType.TOPIC,
description: 'Themenpostkorb VIP', description: 'Themenpostkorb VIP',
owner: '', owner: '',
custom1: '', custom1: '',

View File

@ -1,10 +1,11 @@
import { Workbasket } from '../../models/workbasket'; import { Workbasket } from '../../models/workbasket';
import { TaskanaQueryParameters } from '../../util/query-parameters'; import { Sorting, WorkbasketQuerySortParameter } from '../../models/sorting';
import { Direction } from '../../models/sorting';
import { ACTION } from '../../models/action'; import { ACTION } from '../../models/action';
import { WorkbasketAccessItems } from '../../models/workbasket-access-items'; import { WorkbasketAccessItems } from '../../models/workbasket-access-items';
import { WorkbasketComponent } from '../../../administration/models/workbasket-component'; import { WorkbasketComponent } from '../../../administration/models/workbasket-component';
import { ButtonAction } from '../../../administration/models/button-action'; import { ButtonAction } from '../../../administration/models/button-action';
import { QueryPagingParameter } from '../../models/query-paging-parameter';
import { WorkbasketQueryFilterParameter } from '../../models/workbasket-query-parameters';
// Workbasket List // Workbasket List
export class GetWorkbasketsSummary { export class GetWorkbasketsSummary {
@ -12,18 +13,9 @@ export class GetWorkbasketsSummary {
constructor( constructor(
public forceRequest: boolean = false, public forceRequest: boolean = false,
public sortBy: string = TaskanaQueryParameters.parameters.KEY, public filterParameter: WorkbasketQueryFilterParameter,
public order: string = Direction.ASC, public sortParameter: Sorting<WorkbasketQuerySortParameter>,
public name?: string, public pageParameter: QueryPagingParameter
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
) {} ) {}
} }

View File

@ -81,21 +81,7 @@ export class WorkbasketState implements NgxsAfterBootstrap {
paginatedWorkbasketsSummary: undefined paginatedWorkbasketsSummary: undefined
}); });
return this.workbasketService return this.workbasketService
.getWorkBasketsSummary( .getWorkBasketsSummary(action.forceRequest, action.filterParameter, action.sortParameter, action.pageParameter)
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
)
.pipe( .pipe(
take(1), take(1),
tap((paginatedWorkbasketsSummary) => { tap((paginatedWorkbasketsSummary) => {

View File

@ -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');
});
});

View File

@ -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;
}

View File

@ -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();
});
});

View File

@ -1,5 +1,3 @@
import { QueryParameters } from 'app/shared/models/query-parameters';
export class TaskanaQueryParameters { export class TaskanaQueryParameters {
static parameters = { static parameters = {
// Sorting // Sorting
@ -56,26 +54,4 @@ export class TaskanaQueryParameters {
static page = 1; static page = 1;
static pageSize = 9; 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;
}
} }

View File

@ -21,7 +21,8 @@
placeholder="Search by value ..." /> placeholder="Search by value ..." />
</div> </div>
<div *ngIf="searched" class="pull-right margin-right btn-group"> <div *ngIf="searched" class="pull-right margin-right btn-group">
<taskana-shared-sort [sortingFields]="sortingFields" (performSorting)="sorting($event)" class="btn-group" [defaultSortBy] = "taskDefaultSortBy"> </taskana-shared-sort> <taskana-shared-sort class="btn-group" [sortingFields]="sortingFields" [defaultSortBy]="taskDefaultSortBy"
(performSorting)="sorting($event)"> </taskana-shared-sort>
<button class="btn btn-default collapsed" type="button" id="collapsedMenufilterWb" aria-expanded="false" (click)="toolbarState=!toolbarState"> <button class="btn btn-default collapsed" type="button" id="collapsedMenufilterWb" aria-expanded="false" (click)="toolbarState=!toolbarState">
<span class="material-icons md-20 blue">search</span> <span class="material-icons md-20 blue">search</span>
</button> </button>
@ -61,7 +62,6 @@
</div> </div>
</div> </div>
<div [@toggleDown]="toolbarState" class="row no-overflow"> <div [@toggleDown]="toolbarState" class="row no-overflow">
<taskana-shared-filter [filterParams]="filterParams" [filterType]="filterType" (performFilter)="filtering($event)"> <taskana-shared-task-filter (performFilter)="filtering($event)"></taskana-shared-task-filter>
</taskana-shared-filter>
</div> </div>
</li> </li>

View File

@ -3,13 +3,12 @@ import { Task } from 'app/workplace/models/task';
import { Workbasket } from 'app/shared/models/workbasket'; import { Workbasket } from 'app/shared/models/workbasket';
import { TaskService } from 'app/workplace/services/task.service'; import { TaskService } from 'app/workplace/services/task.service';
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service'; import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
import { Sorting } from 'app/shared/models/sorting'; import { Sorting, TASK_SORT_PARAMETER_NAMING, TaskQuerySortParameter } from 'app/shared/models/sorting';
import { Filter } from 'app/shared/models/filter';
import { TaskanaType } from 'app/shared/models/taskana-type';
import { expandDown } from 'app/shared/animations/expand.animation'; import { expandDown } from 'app/shared/animations/expand.animation';
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router'; import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
import { WorkplaceService } from 'app/workplace/services/workplace.service'; import { WorkplaceService } from 'app/workplace/services/workplace.service';
import { ObjectReference } from 'app/workplace/models/object-reference'; import { ObjectReference } from 'app/workplace/models/object-reference';
import { TaskQueryFilterParameter } from '../../../shared/models/task-query-filter-parameter';
export enum Search { export enum Search {
byWorkbasket = 'workbasket', byWorkbasket = 'workbasket',
@ -23,20 +22,14 @@ export enum Search {
styleUrls: ['./task-list-toolbar.component.scss'] styleUrls: ['./task-list-toolbar.component.scss']
}) })
export class TaskListToolbarComponent implements OnInit { export class TaskListToolbarComponent implements OnInit {
@Input() taskDefaultSortBy: string; @Input() taskDefaultSortBy: TaskQuerySortParameter;
@Output() performSorting = new EventEmitter<Sorting>(); @Output() performSorting = new EventEmitter<Sorting<TaskQuerySortParameter>>();
@Output() performFilter = new EventEmitter<Filter>(); @Output() performFilter = new EventEmitter<TaskQueryFilterParameter>();
@Output() selectSearchType = new EventEmitter(); @Output() selectSearchType = new EventEmitter();
sortingFields = new Map([ sortingFields: Map<TaskQuerySortParameter, string> = TASK_SORT_PARAMETER_NAMING;
['name', 'Name'],
['priority', 'Priority'],
['due', 'Due'],
['planned', 'Planned']
]);
filterParams = { name: '', key: '', owner: '', priority: '', state: '' };
tasks: Task[] = [];
tasks: Task[] = [];
workbasketNames: string[] = []; workbasketNames: string[] = [];
resultName = ''; resultName = '';
resultId = ''; resultId = '';
@ -44,7 +37,6 @@ export class TaskListToolbarComponent implements OnInit {
currentBasket: Workbasket; currentBasket: Workbasket;
workbasketSelected = false; workbasketSelected = false;
toolbarState = false; toolbarState = false;
filterType = TaskanaType.TASKS;
searched = false; searched = false;
search = Search; search = Search;
@ -116,11 +108,11 @@ export class TaskListToolbarComponent implements OnInit {
this.router.navigate(['']); this.router.navigate(['']);
} }
sorting(sort: Sorting) { sorting(sort: Sorting<TaskQuerySortParameter>) {
this.performSorting.emit(sort); this.performSorting.emit(sort);
} }
filtering(filterBy: Filter) { filtering(filterBy: TaskQueryFilterParameter) {
this.performFilter.emit(filterBy); this.performFilter.emit(filterBy);
} }

View File

@ -2,11 +2,9 @@ import { Component, ElementRef, OnDestroy, OnInit, ViewChild } from '@angular/co
import { Task } from 'app/workplace/models/task'; import { Task } from 'app/workplace/models/task';
import { TaskService } from 'app/workplace/services/task.service'; import { TaskService } from 'app/workplace/services/task.service';
import { Subject } from 'rxjs'; 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 { Workbasket } from 'app/shared/models/workbasket';
import { Filter } from 'app/shared/models/filter';
import { WorkplaceService } from 'app/workplace/services/workplace.service'; 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 { OrientationService } from 'app/shared/services/orientation/orientation.service';
import { Page } from 'app/shared/models/page'; import { Page } from 'app/shared/models/page';
import { takeUntil } from 'rxjs/operators'; 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 { Search } from '../task-list-toolbar/task-list-toolbar.component';
import { NotificationService } from '../../../shared/services/notifications/notification.service'; import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications'; 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({ @Component({
selector: 'taskana-task-master', selector: 'taskana-task-master',
@ -26,17 +26,16 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
type = 'tasks'; type = 'tasks';
currentBasket: Workbasket; currentBasket: Workbasket;
selectedId = ''; selectedId = '';
taskDefaultSortBy: string = 'priority'; taskDefaultSortBy: TaskQuerySortParameter = TaskQuerySortParameter.PRIORITY;
sort: Sorting = new Sorting(this.taskDefaultSortBy); sort: Sorting<TaskQuerySortParameter> = {
filterBy: Filter = new Filter({ 'sort-by': this.taskDefaultSortBy,
name: '', order: Direction.ASC
owner: '', };
priority: '', paging: QueryPagingParameter = {
state: '', page: 1,
classificationKey: '', 'page-size': 9
workbasketId: '', };
workbasketKey: '' filterBy: TaskQueryFilterParameter = undefined;
});
requestInProgress = false; requestInProgress = false;
selectedSearchType: Search = Search.byWorkbasket; selectedSearchType: Search = Search.byWorkbasket;
@ -79,27 +78,33 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
this.refreshWorkbasketList(); this.refreshWorkbasketList();
}); });
this.workplaceService.workbasketSelectedStream.pipe(takeUntil(this.destroy$)).subscribe((workbasket) => { this.workplaceService
this.currentBasket = workbasket; .getSelectedWorkbasket()
if (this.selectedSearchType === Search.byWorkbasket) { .pipe(takeUntil(this.destroy$))
this.getTasks(); .subscribe((workbasket) => {
} this.currentBasket = workbasket;
}); if (this.selectedSearchType === Search.byWorkbasket) {
this.getTasks();
}
});
this.workplaceService.objectReferenceSelectedStream.pipe(takeUntil(this.destroy$)).subscribe((objectReference) => { this.workplaceService
if (objectReference) { .getSelectedObjectReference()
delete this.currentBasket; .pipe(takeUntil(this.destroy$))
this.getTasks(objectReference); .subscribe((objectReference) => {
} if (objectReference) {
}); delete this.currentBasket;
this.getTasks(objectReference);
}
});
} }
performSorting(sort: Sorting) { performSorting(sort: Sorting<TaskQuerySortParameter>) {
this.sort = sort; this.sort = sort;
this.getTasks(); this.getTasks();
} }
performFilter(filterBy: Filter) { performFilter(filterBy: TaskQueryFilterParameter) {
this.filterBy = filterBy; this.filterBy = filterBy;
this.getTasks(); this.getTasks();
} }
@ -110,7 +115,7 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
} }
changePage(page) { changePage(page) {
TaskanaQueryParameters.page = page; this.paging.page = page;
this.getTasks(); this.getTasks();
} }
@ -126,8 +131,7 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
const unusedHeight = 150; const unusedHeight = 150;
const totalHeight = window.innerHeight; const totalHeight = window.innerHeight;
const cards = Math.round((totalHeight - (unusedHeight + toolbarSize)) / cardHeight); const cards = Math.round((totalHeight - (unusedHeight + toolbarSize)) / cardHeight);
TaskanaQueryParameters.page = TaskanaQueryParameters.page ? TaskanaQueryParameters.page : 1; this.paging['page-size'] = cards > 0 ? cards : 1;
TaskanaQueryParameters.pageSize = cards > 0 ? cards : 1;
} }
} }
@ -139,17 +143,7 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
} else { } else {
this.calculateHeightCard(); this.calculateHeightCard();
this.taskService this.taskService
.findTasksWithWorkbasket( .findTasksWithWorkbasket(this.filterBy, this.sort, this.paging)
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 : ''
)
.pipe(takeUntil(this.destroy$)) .pipe(takeUntil(this.destroy$))
.subscribe((taskResource) => { .subscribe((taskResource) => {
this.requestInProgress = false; this.requestInProgress = false;

View File

@ -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() { private getClassificationByDomain() {
this.requestInProgress = true; this.requestInProgress = true;
this.classificationService this.classificationService
.getClassificationsByDomain(this.domainService.getSelectedDomainValue()) .getClassifications({ domain: [this.task.workbasketSummary.domain] })
.then((classificationPagingList) => { .subscribe((classificationPagingList) => {
this.classifications = classificationPagingList.classifications; this.classifications = classificationPagingList.classifications;
this.requestInProgress = false;
}); });
this.requestInProgress = false;
} }
ngOnDestroy() { ngOnDestroy() {

View File

@ -45,7 +45,6 @@ export class TaskdetailsComponent implements OnInit, OnDestroy {
) {} ) {}
ngOnInit() { ngOnInit() {
this.currentWorkbasket = this.workplaceService.currentWorkbasket;
this.workbasketSubscription = this.workplaceService.getSelectedWorkbasket().subscribe((workbasket) => { this.workbasketSubscription = this.workplaceService.getSelectedWorkbasket().subscribe((workbasket) => {
this.currentWorkbasket = workbasket; this.currentWorkbasket = workbasket;
}); });

View File

@ -3,10 +3,11 @@ import { Observable, Subject } from 'rxjs';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TaskResource } from 'app/workplace/models/task-resource'; import { TaskResource } from 'app/workplace/models/task-resource';
import { Direction } from 'app/shared/models/sorting'; import { Sorting, TaskQuerySortParameter } from 'app/shared/models/sorting';
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters';
import { QueryParameters } from 'app/shared/models/query-parameters';
import { StartupService } from '../../shared/services/startup/startup.service'; 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() @Injectable()
export class TaskService { export class TaskService {
@ -40,31 +41,11 @@ export class TaskService {
} }
findTasksWithWorkbasket( findTasksWithWorkbasket(
basketId: string, filterParameter: TaskQueryFilterParameter,
sortBy: string, sortParameter: Sorting<TaskQuerySortParameter>,
sortDirection: string, pagingParameter: QueryPagingParameter
nameLike: string,
ownerLike: string,
priority: string,
state: string,
objRefTypeLike: string,
objRefValueLike: string,
allPages: boolean = false
): Observable<TaskResource> { ): Observable<TaskResource> {
const url = `${this.url}${TaskanaQueryParameters.getQueryParameters( const url = `${this.url}${asUrlQueryString({ ...filterParameter, ...sortParameter, ...pagingParameter })}`;
TaskService.accessIdsParameters(
basketId,
sortBy,
sortDirection,
nameLike,
ownerLike,
priority,
state,
objRefTypeLike,
objRefValueLike,
allPages
)
)}`;
return this.httpClient.get<TaskResource>(url); return this.httpClient.get<TaskResource>(url);
} }
@ -110,34 +91,4 @@ export class TaskService {
}); });
return task; 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;
}
} }

View File

@ -1,37 +1,26 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs'; import { BehaviorSubject, Observable, Subject } from 'rxjs';
import { Workbasket } from 'app/shared/models/workbasket'; import { Workbasket } from 'app/shared/models/workbasket';
import { ObjectReference } from '../models/object-reference'; import { ObjectReference } from '../models/object-reference';
@Injectable() @Injectable()
export class WorkplaceService { export class WorkplaceService {
// necessary because the TaskdetailsComponent is not always initialized when the first workbasket was selected. private workbasketSelected = new BehaviorSubject<Workbasket>(undefined);
currentWorkbasket: Workbasket; private objectReferenceSelected = new BehaviorSubject<ObjectReference>(undefined);
objectReference: ObjectReference;
private workbasketSelectedSource = new Subject<Workbasket>();
workbasketSelectedStream = this.workbasketSelectedSource.asObservable();
private objectReferenceSource = new Subject<ObjectReference>();
objectReferenceSelectedStream = this.objectReferenceSource.asObservable();
selectWorkbasket(workbasket?: Workbasket) { selectWorkbasket(workbasket?: Workbasket): void {
this.currentWorkbasket = workbasket; this.workbasketSelected.next(workbasket);
this.workbasketSelectedSource.next(workbasket);
} }
getSelectedWorkbasket(): Observable<Workbasket> { getSelectedWorkbasket(): Observable<Workbasket> {
return this.workbasketSelectedStream; return this.workbasketSelected.asObservable();
} }
selectObjectReference(objectReference?: ObjectReference) { selectObjectReference(objectReference?: ObjectReference): void {
this.objectReference = new ObjectReference(); this.objectReferenceSelected.next(objectReference);
if (objectReference) {
this.objectReference.type = objectReference.type;
this.objectReference.value = objectReference.value;
}
this.objectReferenceSource.next(objectReference);
} }
getObjectReference() { getSelectedObjectReference(): Observable<ObjectReference> {
return this.objectReferenceSelectedStream; return this.objectReferenceSelected.asObservable();
} }
} }