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

View File

@ -3,8 +3,11 @@ package pro.taskana.common.rest.assembler;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import org.springframework.hateoas.Link;
import org.springframework.hateoas.RepresentationModel;
import org.springframework.hateoas.server.RepresentationModelAssembler;
import org.springframework.web.servlet.support.ServletUriComponentsBuilder;
import org.springframework.web.util.UriComponentsBuilder;
import pro.taskana.common.rest.models.CollectionRepresentationModel;
@ -17,6 +20,16 @@ public interface CollectionRepresentationModelAssembler<
default C toTaskanaCollectionModel(Iterable<T> entities) {
return StreamSupport.stream(entities.spliterator(), false)
.map(this::toModel)
.collect(Collectors.collectingAndThen(Collectors.toList(), this::buildCollectionEntity));
.collect(
Collectors.collectingAndThen(
Collectors.toList(),
content -> addLinksToCollectionModel(buildCollectionEntity(content))));
}
default C addLinksToCollectionModel(C model) {
final UriComponentsBuilder original = ServletUriComponentsBuilder.fromCurrentRequest();
model.add(Link.of(original.toUriString()).withSelfRel());
return model;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -177,12 +177,12 @@
<mat-select required [(value)]="this.classification.category">
<mat-select-trigger>
<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>
{{this.classification.category}}
</mat-select-trigger>
<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}}
</mat-option>
</mat-select>

View File

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

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

View File

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

View File

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

View File

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

View File

@ -2,8 +2,8 @@
(moveNode)="onMoveNode($event)" (treeDrop)="onDrop($event)">
<ng-template #treeNodeTemplate let-node let-index="index">
<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"
[title]="(getCategoryIcon(node.data.category) | async)?.text"></svg-icon>
<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)?.right"></svg-icon>
</span>
<span>
<strong>{{ node.data.key }}</strong>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,10 +9,11 @@ import {
ViewChild
} from '@angular/core';
import { WorkbasketSummary } from 'app/shared/models/workbasket-summary';
import { Filter } from 'app/shared/models/filter';
import { expandDown } from 'app/shared/animations/expand.animation';
import { Side } from '../workbasket-distribution-targets/workbasket-distribution-targets.component';
import { MatSelectionList } from '@angular/material/list';
import { Pair } from '../../../shared/models/pair';
import { WorkbasketQueryFilterParameter } from '../../../shared/models/workbasket-query-parameters';
@Component({
selector: 'taskana-administration-workbasket-distribution-targets-list',
@ -23,7 +24,7 @@ import { MatSelectionList } from '@angular/material/list';
export class WorkbasketDistributionTargetsListComponent implements OnInit, AfterContentChecked {
@Input() distributionTargets: WorkbasketSummary[];
@Input() distributionTargetsSelected: WorkbasketSummary[];
@Output() performDualListFilter = new EventEmitter<{ filterBy: Filter; side: Side }>();
@Output() performDualListFilter = new EventEmitter<Pair<Side, WorkbasketQueryFilterParameter>>();
@Input() requestInProgress = false;
@Input() loadingItems? = false;
@Input() side: Side;
@ -33,7 +34,6 @@ export class WorkbasketDistributionTargetsListComponent implements OnInit, After
@Output() allSelectedChange = new EventEmitter<boolean>();
toolbarState = false;
component = '';
@ViewChild('workbasket') distributionTargetsList: MatSelectionList;
constructor(private changeDetector: ChangeDetectorRef) {}
@ -55,17 +55,13 @@ export class WorkbasketDistributionTargetsListComponent implements OnInit, After
this.allSelectedChange.emit(this.allSelected);
}
setComponent(component: string) {
this.component = component;
}
onScroll() {
this.scrolling.emit(this.side);
}
performAvailableFilter(filterModel: Filter) {
if (this.component === 'distribution-target') {
this.performDualListFilter.emit({ filterBy: filterModel, side: this.side });
performAvailableFilter(pair: Pair<string, WorkbasketQueryFilterParameter>) {
if (pair.left === 'distribution-target') {
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 { Side, WorkbasketDistributionTargetsComponent } from './workbasket-distribution-targets.component';
import { WorkbasketSummary } from '../../../shared/models/workbasket-summary';
import { Filter } from '../../../shared/models/filter';
import { MatIconModule } from '@angular/material/icon';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatButtonModule } from '@angular/material/button';
@ -20,6 +19,8 @@ import {
selectedWorkbasketMock,
workbasketReadStateMock
} from '../../../shared/store/mock-data/mock-store';
import { WorkbasketQueryFilterParameter } from '../../../shared/models/workbasket-query-parameters';
import { Pair } from '../../../shared/models/pair';
const routeParamsMock = { id: 'workbasket' };
const activatedRouteMock = {
@ -31,7 +32,7 @@ const activatedRouteMock = {
class WorkbasketDistributionTargetsListStub {
@Input() distributionTargets: WorkbasketSummary[];
@Input() distributionTargetsSelected: WorkbasketSummary[];
@Output() performDualListFilter = new EventEmitter<{ filterBy: Filter; side: Side }>();
@Output() performDualListFilter = new EventEmitter<Pair<Side, WorkbasketQueryFilterParameter>>();
@Input() requestInProgress = false;
@Input() loadingItems? = false;
@Input() side: Side;
@ -128,11 +129,14 @@ describe('WorkbasketDistributionTargetsComponent', () => {
expect(component.distributionTargetsSelected).toHaveLength(3); //mock-data has 3 entries
});
// TODO: was ist das für ein test?
it('should emit filter model and side when performing filter', () => {
const performDualListFilterSpy = jest.spyOn(component, 'performFilter');
const filterModelMock: Filter = { filterParams: { name: '', description: '', owner: '', type: '', key: '' } };
component.performFilter({ filterBy: filterModelMock, side: component.side });
expect(performDualListFilterSpy).toHaveBeenCalledWith({ filterBy: filterModelMock, side: component.side });
const filterModelMock: WorkbasketQueryFilterParameter = { domain: ['DOMAIN_A'] };
component.performFilter({ left: Side.AVAILABLE, right: filterModelMock });
expect(performDualListFilterSpy).toHaveBeenCalledWith({ right: filterModelMock, left: Side.AVAILABLE });
});
it('should move distribution targets to selected list', () => {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,11 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { TaskQueryComponent } from './task-query/task-query.component';
import { TaskHistoryQueryComponent } from './task-history-query/task-history-query.component';
const routes: Routes = [
{
path: '',
component: TaskQueryComponent
component: TaskHistoryQueryComponent
},
{
path: '**',

View File

@ -4,10 +4,10 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { SharedModule } from 'app/shared/shared.module';
import { HistoryRoutingModule } from './history-routing.module';
import { TaskQueryComponent } from './task-query/task-query.component';
import { TaskHistoryQueryComponent } from './task-history-query/task-history-query.component';
@NgModule({
imports: [CommonModule, HistoryRoutingModule, SharedModule, FormsModule, ReactiveFormsModule],
declarations: [TaskQueryComponent]
declarations: [TaskHistoryQueryComponent]
})
export class HistoryModule {}

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@
</ng-container>
<div *ngFor="let header of reportData.meta.header"
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 class="table-body">

View File

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

View File

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

View File

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

View File

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

View File

@ -3,5 +3,5 @@ export class MetaInfoData {
date: string;
header: 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 { TimestampReportComponent } from './components/timestamp-report/timestamp-report.component';
import { RestConnectorService } from './services/rest-connector.service';
import { MonitorService } from './services/monitor.service';
import { WorkbasketReportComponent } from './components/workbasket-report/workbasket-report.component';
import { WorkbasketReportPlannedDateComponent } from './components/workbasket-report-planned-date/workbasket-report-planned-date.component';
@ -51,6 +51,6 @@ const DECLARATIONS = [
@NgModule({
declarations: DECLARATIONS,
imports: MODULES,
providers: [RestConnectorService, MapToIterable]
providers: [MonitorService, MapToIterable]
})
export class MonitorModule {}

View File

@ -4,28 +4,40 @@ import { environment } from 'environments/environment';
import { Observable } from 'rxjs';
import { ChartData } from 'app/monitor/models/chart-data';
import { ReportData } from '../models/report-data';
import { asUrlQueryString } from '../../shared/util/query-parameters-v2';
import { TaskState } from '../../shared/models/task-state';
const monitorUrl = '/v1/monitor/';
@Injectable()
export class RestConnectorService {
export class MonitorService {
constructor(private httpClient: HttpClient) {}
getTaskStatusReport(): Observable<ReportData> {
const queryParams = {
states: [TaskState.READY, TaskState.CLAIMED, TaskState.COMPLETED]
};
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> {
const queryParams = {
states: [TaskState.READY, TaskState.CLAIMED, TaskState.COMPLETED]
};
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> {
const queryParams = {
daysInPast: 7,
states: [TaskState.READY, TaskState.CLAIMED, TaskState.COMPLETED]
};
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() {
this.title = notifications.get(this.data.key).name || '';
this.title = notifications.get(this.data.key).left || '';
this.message =
notifications.get(this.data.key).text || (this.data && this.data.passedError && this.data.passedError.error)
notifications.get(this.data.key).right || (this.data && this.data.passedError && this.data.passedError.error)
? this.data.passedError.error.message
: '';
if (this.data.additions) {

View File

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

View File

@ -6,26 +6,32 @@ import { Direction, Sorting } from 'app/shared/models/sorting';
templateUrl: './sort.component.html',
styleUrls: ['./sort.component.scss']
})
export class SortComponent implements OnInit {
@Input() sortingFields: Map<string, string>;
export class SortComponent<T> implements OnInit {
@Input() sortingFields: Map<T, string>;
@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() {
this.sort.sortBy = this.defaultSortBy;
this.sort['sort-by'] = this.defaultSortBy;
}
changeOrder(sortDirection: string) {
this.sort.sortDirection = sortDirection === Direction.ASC ? Direction.ASC : Direction.DESC;
changeOrder(sortDirection: Direction) {
this.sort.order = sortDirection;
this.search();
}
changeSortBy(sortBy: string) {
this.sort.sortBy = sortBy;
changeSortBy(sortBy: T) {
this.sort['sort-by'] = sortBy;
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 {
if (this.data) {
this.message = notifications.get(this.data.key).text;
this.message = notifications.get(this.data.key).right;
if (this.data.additions) {
this.data.additions.forEach((value: string, replacementKey: string) => {
this.message = this.message.replace(`{${replacementKey}}`, value);

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

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;
constructor(key: NOTIFICATION_TYPES, passedError?: HttpErrorResponse, addition?: Map<String, String>) {
this.title = notifications.get(key).name;
let messageTemp = notifications.get(key).text;
this.title = notifications.get(key).left;
let messageTemp = notifications.get(key).right;
this.errObj = passedError;
if (addition) {
addition.forEach((value: string, replacementKey: string) => {

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
}
export const notifications = new Map<NOTIFICATION_TYPES, Pair>([
export const notifications = new Map<NOTIFICATION_TYPES, Pair<string, string>>([
// access-items-management.component.ts
[NOTIFICATION_TYPES.FETCH_ERR, new Pair('There was an error while retrieving your access ids with groups.', '')],
[
NOTIFICATION_TYPES.FETCH_ERR,
{ left: 'There was an error while retrieving your access ids with groups.', right: '' }
],
// access-items-management.component.ts
[NOTIFICATION_TYPES.FETCH_ERR_2, new Pair('There was an error while retrieving your access items ', '')],
[NOTIFICATION_TYPES.FETCH_ERR_2, { left: 'There was an error while retrieving your access items ', right: '' }],
// access-items-management.component.ts
[NOTIFICATION_TYPES.DELETE_ERR, new Pair("You can't delete a group", '')],
[NOTIFICATION_TYPES.DELETE_ERR, { left: "You can't delete a group", right: '' }],
// classification-details.component
[NOTIFICATION_TYPES.CREATE_ERR, new Pair('There was an error while creating this classification', '')],
[NOTIFICATION_TYPES.CREATE_ERR, { left: 'There was an error while creating this classification', right: '' }],
// classification-details.component
[NOTIFICATION_TYPES.REMOVE_ERR, new Pair('There was an error while removing your classification', '')],
[NOTIFICATION_TYPES.REMOVE_ERR, { left: 'There was an error while removing your classification', right: '' }],
// classification-details.component
[NOTIFICATION_TYPES.SAVE_ERR, new Pair('There was an error while saving your classification', '')],
[NOTIFICATION_TYPES.SAVE_ERR, { left: 'There was an error while saving your classification', right: '' }],
// classification-details.component
[
NOTIFICATION_TYPES.SELECT_ERR,
new Pair('There is no classification selected', 'Please check if you are creating a classification')
{ left: 'There is no classification selected', right: 'Please check if you are creating a classification' }
],
// import-export.component
[NOTIFICATION_TYPES.FILE_ERR, new Pair('Wrong format', 'This file format is not allowed! Please use a .json file.')],
[
NOTIFICATION_TYPES.FILE_ERR,
{ left: 'Wrong format', right: 'This file format is not allowed! Please use a .json file.' }
],
// import-export.component
[
NOTIFICATION_TYPES.IMPORT_ERR_1,
new Pair('Import was not successful', 'Import was not successful, you have no access to apply this operation.')
{
left: 'Import was not successful',
right: 'Import was not successful, you have no access to apply this operation.'
}
],
// import-export.component
[
NOTIFICATION_TYPES.IMPORT_ERR_2,
new Pair('Import was not successful', 'Import was not successful, operation was not found.')
{ left: 'Import was not successful', right: 'Import was not successful, operation was not found.' }
],
// import-export.component
[
NOTIFICATION_TYPES.IMPORT_ERR_3,
new Pair('Import was not successful', 'Import was not successful, operation has some conflicts.')
{ left: 'Import was not successful', right: 'Import was not successful, operation has some conflicts.' }
],
// import-export.component
[
NOTIFICATION_TYPES.IMPORT_ERR_4,
new Pair('Import was not successful', 'Import was not successful, maximum file size exceeded.')
{ left: 'Import was not successful', right: 'Import was not successful, maximum file size exceeded.' }
],
// import-export.component
[
NOTIFICATION_TYPES.UPLOAD_ERR,
new Pair(
'Upload failed',
`The upload didn't proceed sucessfully.
{
left: 'Upload failed',
right: `The upload didn't proceed sucessfully.
\n The uploaded file probably exceeded the maximum file size of 10 MB.`
)
}
],
// taskdetails.component
[NOTIFICATION_TYPES.FETCH_ERR_3, new Pair('', 'An error occurred while fetching the task')],
[NOTIFICATION_TYPES.FETCH_ERR_3, { left: '', right: 'An error occurred while fetching the task' }],
// workbasket-details.component
[NOTIFICATION_TYPES.FETCH_ERR_4, new Pair('', 'An error occurred while fetching the workbasket')],
[NOTIFICATION_TYPES.FETCH_ERR_4, { left: '', right: 'An error occurred while fetching the workbasket' }],
// access-items.component
[NOTIFICATION_TYPES.SAVE_ERR_2, new Pair("There was an error while saving your workbasket's access items", '')],
[
NOTIFICATION_TYPES.SAVE_ERR_2,
{ left: "There was an error while saving your workbasket's access items", right: '' }
],
// workbaskets-distribution-targets.component
[
NOTIFICATION_TYPES.SAVE_ERR_3,
new Pair("There was an error while saving your workbasket's distribution targets", '')
{ left: "There was an error while saving your workbasket's distribution targets", right: '' }
],
// workbasket-information.component
[
NOTIFICATION_TYPES.REMOVE_ERR_2,
new Pair('There was an error removing distribution target for {workbasketId}.', '')
{ left: 'There was an error removing distribution target for {workbasketId}.', right: '' }
],
// workbasket-information.component
[NOTIFICATION_TYPES.SAVE_ERR_4, new Pair('There was an error while saving your workbasket', '')],
[NOTIFICATION_TYPES.SAVE_ERR_4, { left: 'There was an error while saving your workbasket', right: '' }],
// workbasket-information.component
[NOTIFICATION_TYPES.CREATE_ERR_2, new Pair('There was an error while creating this workbasket', '')],
[NOTIFICATION_TYPES.CREATE_ERR_2, { left: 'There was an error while creating this workbasket', right: '' }],
// workbasket-information.component
[
NOTIFICATION_TYPES.MARK_ERR,
new Pair(
'Workbasket was marked for deletion.',
'The Workbasket {workbasketId} still contains completed tasks and could not be deleted.' +
{
left: 'Workbasket was marked for deletion.',
right:
'The Workbasket {workbasketId} still contains completed tasks and could not be deleted.' +
' Instead is was marked for deletion and will be deleted automatically ' +
'as soon as the completed tasks are cleared from the database.'
)
}
],
// domain.guard
[
NOTIFICATION_TYPES.FETCH_ERR_5,
new Pair('There was an error, please contact your administrator', 'There was an error getting Domains')
{ left: 'There was an error, please contact your administrator', right: 'There was an error getting Domains' }
],
// history.guard
[
NOTIFICATION_TYPES.FETCH_ERR_6,
new Pair('There was an error, please contact your administrator', 'There was an error getting history provider')
{
left: 'There was an error, please contact your administrator',
right: 'There was an error getting history provider'
}
],
// http-client-interceptor.service
[NOTIFICATION_TYPES.ACCESS_ERR, new Pair('You have no access to this resource', '')],
[NOTIFICATION_TYPES.ACCESS_ERR, { left: 'You have no access to this resource', right: '' }],
// http-client-interceptor.service
[NOTIFICATION_TYPES.GENERAL_ERR, new Pair('There was an error, please contact your administrator', '')],
[NOTIFICATION_TYPES.GENERAL_ERR, { left: 'There was an error, please contact your administrator', right: '' }],
// spinner.component
[
NOTIFICATION_TYPES.TIMEOUT_ERR,
new Pair(
'There was an error with your request, please make sure you have internet connection',
'Request time exceeded'
)
{
left: 'There was an error with your request, please make sure you have internet connection',
right: 'Request time exceeded'
}
],
// taskdetails.component
[NOTIFICATION_TYPES.FETCH_ERR_7, new Pair('An error occurred while fetching the task', '')],
[NOTIFICATION_TYPES.FETCH_ERR_7, { left: 'An error occurred while fetching the task', right: '' }],
// taskdetails.component
[NOTIFICATION_TYPES.DELETE_ERR_2, new Pair('An error occurred while deleting the task', '')],
[NOTIFICATION_TYPES.DELETE_ERR_2, { left: 'An error occurred while deleting the task', right: '' }],
// ALERTS
// access-items-management.component
[NOTIFICATION_TYPES.SUCCESS_ALERT, new Pair('', '{accessId} was removed successfully')],
[NOTIFICATION_TYPES.SUCCESS_ALERT, { left: '', right: '{accessId} was removed successfully' }],
// classification-details.component
[NOTIFICATION_TYPES.SUCCESS_ALERT_2, new Pair('', 'Classification {classificationKey} was created successfully')],
[
NOTIFICATION_TYPES.SUCCESS_ALERT_2,
{ left: '', right: 'Classification {classificationKey} was created successfully' }
],
// classification-details.component
[NOTIFICATION_TYPES.SUCCESS_ALERT_3, new Pair('', 'Classification {classificationKey} was saved successfully')],
[
NOTIFICATION_TYPES.SUCCESS_ALERT_3,
{ left: '', right: 'Classification {classificationKey} was saved successfully' }
],
// classification-details.component
// access-items.component
// workbasket.distribution-targets.component
// workbasket-information.component
// taskdetails.component
[NOTIFICATION_TYPES.INFO_ALERT, new Pair('', 'Information restored')],
[NOTIFICATION_TYPES.INFO_ALERT, { left: '', right: 'Information restored' }],
// classification-details.component
[NOTIFICATION_TYPES.SUCCESS_ALERT_4, new Pair('', 'Classification {classificationKey} was removed successfully')],
[
NOTIFICATION_TYPES.SUCCESS_ALERT_4,
{ left: '', right: 'Classification {classificationKey} was removed successfully' }
],
// classification-list.component
[NOTIFICATION_TYPES.SUCCESS_ALERT_5, new Pair('', 'Classification {classificationKey} was moved successfully')],
[
NOTIFICATION_TYPES.SUCCESS_ALERT_5,
{ left: '', right: 'Classification {classificationKey} was moved successfully' }
],
// import-export.component
[NOTIFICATION_TYPES.SUCCESS_ALERT_6, new Pair('', 'Import was successful')],
[NOTIFICATION_TYPES.SUCCESS_ALERT_6, { left: '', right: 'Import was successful' }],
// access-items.component
[NOTIFICATION_TYPES.SUCCESS_ALERT_7, new Pair('', 'Workbasket {workbasketKey} Access items were saved successfully')],
[
NOTIFICATION_TYPES.SUCCESS_ALERT_7,
{ left: '', right: 'Workbasket {workbasketKey} Access items were saved successfully' }
],
// workbasket.distribution-targets.component
[
NOTIFICATION_TYPES.SUCCESS_ALERT_8,
new Pair('', 'Workbasket {workbasketName} Distribution targets were saved successfully')
{ left: '', right: 'Workbasket {workbasketName} Distribution targets were saved successfully' }
],
// workbasket-information.component
[
NOTIFICATION_TYPES.SUCCESS_ALERT_9,
new Pair('', 'DistributionTargets for workbasketID {workbasketId} was removed successfully')
{ left: '', right: 'DistributionTargets for workbasketID {workbasketId} was removed successfully' }
],
// workbasket-information.component
[NOTIFICATION_TYPES.SUCCESS_ALERT_10, new Pair('', 'Workbasket {workbasketKey} was saved successfully')],
[NOTIFICATION_TYPES.SUCCESS_ALERT_10, { left: '', right: 'Workbasket {workbasketKey} was saved successfully' }],
// workbasket-information.component
[NOTIFICATION_TYPES.SUCCESS_ALERT_11, new Pair('', 'Workbasket {workbasketKey} was created successfully')],
[NOTIFICATION_TYPES.SUCCESS_ALERT_11, { left: '', right: 'Workbasket {workbasketKey} was created successfully' }],
// workbasket-information.component
[NOTIFICATION_TYPES.SUCCESS_ALERT_12, new Pair('', 'The Workbasket {workbasketId} has been deleted.')],
[NOTIFICATION_TYPES.SUCCESS_ALERT_12, { left: '', right: 'The Workbasket {workbasketId} has been deleted.' }],
// forms-validator.service
[NOTIFICATION_TYPES.WARNING_ALERT, new Pair('', 'There are some empty fields which are required.')],
[NOTIFICATION_TYPES.WARNING_ALERT, { left: '', right: 'There are some empty fields which are required.' }],
// forms-validator.service x2
[NOTIFICATION_TYPES.WARNING_ALERT_2, new Pair('', 'The {owner} introduced is not valid.')],
[NOTIFICATION_TYPES.WARNING_ALERT_2, { left: '', right: 'The {owner} introduced is not valid.' }],
// taskdetails.component
[NOTIFICATION_TYPES.DANGER_ALERT, new Pair('', 'There was an error while updating.')],
[NOTIFICATION_TYPES.DANGER_ALERT, { left: '', right: 'There was an error while updating.' }],
// taskdetails.component
[NOTIFICATION_TYPES.SUCCESS_ALERT_13, new Pair('', 'Task {taskId} was created successfully.')],
[NOTIFICATION_TYPES.SUCCESS_ALERT_13, { left: '', right: 'Task {taskId} was created successfully.' }],
// taskdetails.component
[NOTIFICATION_TYPES.SUCCESS_ALERT_14, new Pair('', 'Updating was successful.')],
[NOTIFICATION_TYPES.SUCCESS_ALERT_14, { left: '', right: 'Updating was successful.' }],
// taskdetails.component
[NOTIFICATION_TYPES.DANGER_ALERT_2, new Pair('', 'There was an error while creating a new task.')],
[NOTIFICATION_TYPES.DANGER_ALERT_2, { left: '', right: 'There was an error while creating a new task.' }],
// task-master.component
[NOTIFICATION_TYPES.INFO_ALERT_2, new Pair('', 'The selected Workbasket is empty!')],
[NOTIFICATION_TYPES.WARNING_CANT_COPY, new Pair('', "Can't copy a not created classification")]
[NOTIFICATION_TYPES.INFO_ALERT_2, { left: '', right: 'The selected Workbasket is empty!' }],
[NOTIFICATION_TYPES.WARNING_CANT_COPY, { left: '', right: "Can't copy a not created classification" }]
]);

View File

@ -1,3 +1,4 @@
export class Pair {
constructor(public name?: string, public text?: string) {}
export interface Pair<L, R> {
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 {
ASC = 'asc',
DESC = 'desc'
ASC = 'ASCENDING',
DESC = 'DESCENDING'
}
export class Sorting {
sortBy: string;
sortDirection: string;
constructor(sortBy: string = 'key', sortDirection: Direction = Direction.ASC) {
this.sortBy = sortBy;
this.sortDirection = sortDirection;
}
export interface Sorting<T> {
'sort-by': T;
order: Direction;
}
export enum TaskQuerySortParameter {
CLASSIFICATION_KEY = 'CLASSIFICATION_KEY',
POR_TYPE = 'POR_TYPE',
POR_VALUE = 'POR_VALUE',
STATE = 'STATE',
NAME = 'NAME',
DUE = 'DUE',
PLANNED = 'PLANNED',
PRIORITY = 'PRIORITY'
}
export const TASK_SORT_PARAMETER_NAMING: Map<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 {
workbasketId?: string;
key?: string;
name?: string;
domain?: string;
type?: ICONTYPES;
type?: WorkbasketType;
description?: string;
owner?: 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 { ICONTYPES } from './icon-types';
import { WorkbasketType } from './workbasket-type';
export interface Workbasket {
workbasketId?: string;
key?: string;
name?: string;
domain?: string;
type?: ICONTYPES;
type?: WorkbasketType;
description?: string;
owner?: string;
custom1?: string;
@ -21,6 +21,8 @@ export interface Workbasket {
created?: string;
modified?: string;
_links?: Links;
// this is not part of the API, but needed for frontend
selected?: boolean;
}
export const customFieldCount: number = 4;

View File

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

View File

@ -4,10 +4,11 @@ import { environment } from 'environments/environment';
import { AccessIdDefinition } from 'app/shared/models/access-id';
import { Observable, of } from 'rxjs';
import { WorkbasketAccessItemsRepresentation } from 'app/shared/models/workbasket-access-items-representation';
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters';
import { Sorting } from 'app/shared/models/sorting';
import { QueryParameters } from 'app/shared/models/query-parameters';
import { Sorting, WorkbasketAccessItemQuerySortParameter } from 'app/shared/models/sorting';
import { StartupService } from '../startup/startup.service';
import { WorkbasketAccessItemQueryFilterParameter } from '../../models/workbasket-access-item-query-filter-parameter';
import { QueryPagingParameter } from '../../models/query-paging-parameter';
import { asUrlQueryString } from '../../util/query-parameters-v2';
@Injectable({
providedIn: 'root'
@ -34,16 +35,17 @@ export class AccessIdsService {
}
getAccessItems(
accessIds: AccessIdDefinition[],
accessIdLike?: string,
workbasketKeyLike?: string,
sortModel: Sorting = new Sorting('workbasket-key')
filterParameter?: WorkbasketAccessItemQueryFilterParameter,
sortParameter?: Sorting<WorkbasketAccessItemQuerySortParameter>,
pagingParameter?: QueryPagingParameter
): Observable<WorkbasketAccessItemsRepresentation> {
return this.httpClient.get<WorkbasketAccessItemsRepresentation>(
encodeURI(
`${environment.taskanaRestUrl}/v1/workbasket-access-items/${TaskanaQueryParameters.getQueryParameters(
AccessIdsService.accessIdsParameters(sortModel, accessIds, accessIdLike, workbasketKeyLike)
)}`
`${environment.taskanaRestUrl}/v1/workbasket-access-items/${asUrlQueryString({
...filterParameter,
...sortParameter,
...pagingParameter
})}`
)
);
}
@ -53,22 +55,4 @@ export class AccessIdsService {
`${environment.taskanaRestUrl}/v1/workbasket-access-items/?access-id=${accessId}`
);
}
private static accessIdsParameters(
sortModel: Sorting,
accessIds: AccessIdDefinition[],
accessIdLike?: string,
workbasketKeyLike?: string
): QueryParameters {
// TODO extend this query for support of multiple sortbys
const parameters = new QueryParameters();
parameters.SORTBY = sortModel.sortBy;
parameters.SORTDIRECTION = sortModel.sortDirection;
parameters.ACCESSIDS = accessIds.map((values: AccessIdDefinition) => values.accessId).join('|');
parameters.ACCESSIDLIKE = accessIdLike;
parameters.WORKBASKETKEYLIKE = workbasketKeyLike;
delete TaskanaQueryParameters.page;
delete TaskanaQueryParameters.pageSize;
return parameters;
}
}

View File

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

View File

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

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

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

View File

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

View File

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

View File

@ -1,19 +1,19 @@
import { Action, NgxsAfterBootstrap, State, StateContext } from '@ngxs/store';
import { Observable, of } from 'rxjs';
import { take, tap } from 'rxjs/operators';
import { mergeMap, take, tap } from 'rxjs/operators';
import { TaskanaDate } from 'app/shared/util/taskana.date';
import {
CategoriesResponse,
ClassificationCategoriesService
} from '../../services/classification-categories/classification-categories.service';
import {
SaveCreatedClassification,
DeselectClassification,
GetClassifications,
CopyClassification,
CreateClassification,
DeselectClassification,
GetClassifications,
RemoveSelectedClassification,
RestoreSelectedClassification,
SaveCreatedClassification,
SaveModifiedClassification,
SelectClassification,
SetSelectedClassificationType,
@ -23,6 +23,8 @@ import { ClassificationsService } from '../../services/classifications/classific
import { DomainService } from '../../services/domain/domain.service';
import { Classification } from '../../models/classification';
import { ClassificationSummary } from '../../models/classification-summary';
import { ClassificationQueryFilterParameters } from '../../models/classification-query-filter-parameters';
import { ClassificationQuerySortParameter, Direction, Sorting } from '../../models/sorting';
class InitializeStore {
static readonly type = '[ClassificationState] Initializing state';
@ -94,13 +96,22 @@ export class ClassificationState implements NgxsAfterBootstrap {
@Action(GetClassifications)
getClassifications(ctx: StateContext<ClassificationStateModel>): Observable<any> {
const { selectedClassificationType } = ctx.getState();
return this.classificationsService.getClassifications(selectedClassificationType).pipe(
take(1),
tap((list) =>
ctx.patchState({
classifications: list.classifications
})
)
return this.domainService.getSelectedDomain().pipe(
mergeMap((domain) => {
const filter: ClassificationQueryFilterParameters = {
domain: [domain],
type: [selectedClassificationType]
};
const sort: Sorting<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 { ICONTYPES } from '../../models/icon-types';
import { WorkbasketType } from '../../models/workbasket-type';
import { ACTION } from '../../models/action';
import { WorkbasketAccessItemsRepresentation } from '../../models/workbasket-access-items-representation';
@ -93,7 +93,7 @@ export const selectedWorkbasketMock: Workbasket = {
key: 'sOrt003',
name: 'bAsxet2',
domain: 'DOMAIN_A',
type: ICONTYPES.TOPIC,
type: WorkbasketType.TOPIC,
description: 'Lorem ipsum dolor sit amet.',
owner: 'Max',
custom1: '',
@ -194,7 +194,7 @@ export const workbasketReadStateMock = {
key: 'USER-2-1',
name: 'PPK User 1 KSC 2',
domain: 'DOMAIN_A',
type: ICONTYPES.PERSONAL,
type: WorkbasketType.PERSONAL,
description: 'PPK User 1 KSC 2',
owner: '',
custom1: '',
@ -212,7 +212,7 @@ export const workbasketReadStateMock = {
key: 'USER-1-2',
name: 'PPK User 2 KSC 1',
domain: 'DOMAIN_A',
type: ICONTYPES.PERSONAL,
type: WorkbasketType.PERSONAL,
description: 'PPK User 2 KSC 1',
owner: 'Peter Maier',
custom1: 'custom1',
@ -230,7 +230,7 @@ export const workbasketReadStateMock = {
key: 'USER-2-2',
name: 'PPK User 2 KSC 2',
domain: 'DOMAIN_A',
type: ICONTYPES.PERSONAL,
type: WorkbasketType.PERSONAL,
description: 'PPK User 2 KSC 2',
owner: '',
custom1: '',
@ -248,7 +248,7 @@ export const workbasketReadStateMock = {
key: 'TPK_VIP',
name: 'Themenpostkorb VIP',
domain: 'DOMAIN_A',
type: ICONTYPES.TOPIC,
type: WorkbasketType.TOPIC,
description: 'Themenpostkorb VIP',
owner: '',
custom1: '',
@ -266,7 +266,7 @@ export const workbasketReadStateMock = {
key: 'TPK_VIP_2',
name: 'Themenpostkorb VIP 2',
domain: 'DOMAIN_A',
type: ICONTYPES.TOPIC,
type: WorkbasketType.TOPIC,
description: 'Themenpostkorb VIP',
owner: '',
custom1: '',

View File

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

View File

@ -81,21 +81,7 @@ export class WorkbasketState implements NgxsAfterBootstrap {
paginatedWorkbasketsSummary: undefined
});
return this.workbasketService
.getWorkBasketsSummary(
action.forceRequest,
action.sortBy,
action.order,
action.name,
action.nameLike,
action.descLike,
action.owner,
action.ownerLike,
action.type,
action.key,
action.keyLike,
action.requiredPermission,
action.allPages
)
.getWorkBasketsSummary(action.forceRequest, action.filterParameter, action.sortParameter, action.pageParameter)
.pipe(
take(1),
tap((paginatedWorkbasketsSummary) => {

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 {
static parameters = {
// Sorting
@ -56,26 +54,4 @@ export class TaskanaQueryParameters {
static page = 1;
static pageSize = 9;
public static getQueryParameters(queryParametersModel: QueryParameters): string {
let query = '?';
Object.keys(queryParametersModel).forEach((key) => {
const value = queryParametersModel[key];
query += value ? `${TaskanaQueryParameters.parameters[key]}=${value}&` : '';
});
query += this.page ? `${TaskanaQueryParameters.parameters.PAGE}=${this.page}&` : '';
query += this.pageSize ? `${TaskanaQueryParameters.parameters.PAGESIZE}=${this.pageSize}&` : '';
query = TaskanaQueryParameters.removeLastChar(query);
return query;
}
private static removeLastChar(query: string): string {
if (query.lastIndexOf('&') === query.length - 1) {
return query.slice(0, query.lastIndexOf('&'));
}
return query;
}
}

View File

@ -21,7 +21,8 @@
placeholder="Search by value ..." />
</div>
<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">
<span class="material-icons md-20 blue">search</span>
</button>
@ -61,7 +62,6 @@
</div>
</div>
<div [@toggleDown]="toolbarState" class="row no-overflow">
<taskana-shared-filter [filterParams]="filterParams" [filterType]="filterType" (performFilter)="filtering($event)">
</taskana-shared-filter>
<taskana-shared-task-filter (performFilter)="filtering($event)"></taskana-shared-task-filter>
</div>
</li>

View File

@ -3,13 +3,12 @@ import { Task } from 'app/workplace/models/task';
import { Workbasket } from 'app/shared/models/workbasket';
import { TaskService } from 'app/workplace/services/task.service';
import { WorkbasketService } from 'app/shared/services/workbasket/workbasket.service';
import { Sorting } from 'app/shared/models/sorting';
import { Filter } from 'app/shared/models/filter';
import { TaskanaType } from 'app/shared/models/taskana-type';
import { Sorting, TASK_SORT_PARAMETER_NAMING, TaskQuerySortParameter } from 'app/shared/models/sorting';
import { expandDown } from 'app/shared/animations/expand.animation';
import { ActivatedRoute, NavigationExtras, Router } from '@angular/router';
import { WorkplaceService } from 'app/workplace/services/workplace.service';
import { ObjectReference } from 'app/workplace/models/object-reference';
import { TaskQueryFilterParameter } from '../../../shared/models/task-query-filter-parameter';
export enum Search {
byWorkbasket = 'workbasket',
@ -23,20 +22,14 @@ export enum Search {
styleUrls: ['./task-list-toolbar.component.scss']
})
export class TaskListToolbarComponent implements OnInit {
@Input() taskDefaultSortBy: string;
@Output() performSorting = new EventEmitter<Sorting>();
@Output() performFilter = new EventEmitter<Filter>();
@Input() taskDefaultSortBy: TaskQuerySortParameter;
@Output() performSorting = new EventEmitter<Sorting<TaskQuerySortParameter>>();
@Output() performFilter = new EventEmitter<TaskQueryFilterParameter>();
@Output() selectSearchType = new EventEmitter();
sortingFields = new Map([
['name', 'Name'],
['priority', 'Priority'],
['due', 'Due'],
['planned', 'Planned']
]);
filterParams = { name: '', key: '', owner: '', priority: '', state: '' };
tasks: Task[] = [];
sortingFields: Map<TaskQuerySortParameter, string> = TASK_SORT_PARAMETER_NAMING;
tasks: Task[] = [];
workbasketNames: string[] = [];
resultName = '';
resultId = '';
@ -44,7 +37,6 @@ export class TaskListToolbarComponent implements OnInit {
currentBasket: Workbasket;
workbasketSelected = false;
toolbarState = false;
filterType = TaskanaType.TASKS;
searched = false;
search = Search;
@ -116,11 +108,11 @@ export class TaskListToolbarComponent implements OnInit {
this.router.navigate(['']);
}
sorting(sort: Sorting) {
sorting(sort: Sorting<TaskQuerySortParameter>) {
this.performSorting.emit(sort);
}
filtering(filterBy: Filter) {
filtering(filterBy: TaskQueryFilterParameter) {
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 { TaskService } from 'app/workplace/services/task.service';
import { Subject } from 'rxjs';
import { Sorting } from 'app/shared/models/sorting';
import { Direction, Sorting, TaskQuerySortParameter } from 'app/shared/models/sorting';
import { Workbasket } from 'app/shared/models/workbasket';
import { Filter } from 'app/shared/models/filter';
import { WorkplaceService } from 'app/workplace/services/workplace.service';
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters';
import { OrientationService } from 'app/shared/services/orientation/orientation.service';
import { Page } from 'app/shared/models/page';
import { takeUntil } from 'rxjs/operators';
@ -14,6 +12,8 @@ import { ObjectReference } from '../../models/object-reference';
import { Search } from '../task-list-toolbar/task-list-toolbar.component';
import { NotificationService } from '../../../shared/services/notifications/notification.service';
import { NOTIFICATION_TYPES } from '../../../shared/models/notifications';
import { QueryPagingParameter } from '../../../shared/models/query-paging-parameter';
import { TaskQueryFilterParameter } from '../../../shared/models/task-query-filter-parameter';
@Component({
selector: 'taskana-task-master',
@ -26,17 +26,16 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
type = 'tasks';
currentBasket: Workbasket;
selectedId = '';
taskDefaultSortBy: string = 'priority';
sort: Sorting = new Sorting(this.taskDefaultSortBy);
filterBy: Filter = new Filter({
name: '',
owner: '',
priority: '',
state: '',
classificationKey: '',
workbasketId: '',
workbasketKey: ''
});
taskDefaultSortBy: TaskQuerySortParameter = TaskQuerySortParameter.PRIORITY;
sort: Sorting<TaskQuerySortParameter> = {
'sort-by': this.taskDefaultSortBy,
order: Direction.ASC
};
paging: QueryPagingParameter = {
page: 1,
'page-size': 9
};
filterBy: TaskQueryFilterParameter = undefined;
requestInProgress = false;
selectedSearchType: Search = Search.byWorkbasket;
@ -79,27 +78,33 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
this.refreshWorkbasketList();
});
this.workplaceService.workbasketSelectedStream.pipe(takeUntil(this.destroy$)).subscribe((workbasket) => {
this.currentBasket = workbasket;
if (this.selectedSearchType === Search.byWorkbasket) {
this.getTasks();
}
});
this.workplaceService
.getSelectedWorkbasket()
.pipe(takeUntil(this.destroy$))
.subscribe((workbasket) => {
this.currentBasket = workbasket;
if (this.selectedSearchType === Search.byWorkbasket) {
this.getTasks();
}
});
this.workplaceService.objectReferenceSelectedStream.pipe(takeUntil(this.destroy$)).subscribe((objectReference) => {
if (objectReference) {
delete this.currentBasket;
this.getTasks(objectReference);
}
});
this.workplaceService
.getSelectedObjectReference()
.pipe(takeUntil(this.destroy$))
.subscribe((objectReference) => {
if (objectReference) {
delete this.currentBasket;
this.getTasks(objectReference);
}
});
}
performSorting(sort: Sorting) {
performSorting(sort: Sorting<TaskQuerySortParameter>) {
this.sort = sort;
this.getTasks();
}
performFilter(filterBy: Filter) {
performFilter(filterBy: TaskQueryFilterParameter) {
this.filterBy = filterBy;
this.getTasks();
}
@ -110,7 +115,7 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
}
changePage(page) {
TaskanaQueryParameters.page = page;
this.paging.page = page;
this.getTasks();
}
@ -126,8 +131,7 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
const unusedHeight = 150;
const totalHeight = window.innerHeight;
const cards = Math.round((totalHeight - (unusedHeight + toolbarSize)) / cardHeight);
TaskanaQueryParameters.page = TaskanaQueryParameters.page ? TaskanaQueryParameters.page : 1;
TaskanaQueryParameters.pageSize = cards > 0 ? cards : 1;
this.paging['page-size'] = cards > 0 ? cards : 1;
}
}
@ -139,17 +143,7 @@ export class TaskMasterComponent implements OnInit, OnDestroy {
} else {
this.calculateHeightCard();
this.taskService
.findTasksWithWorkbasket(
this.currentBasket ? this.currentBasket.workbasketId : '',
this.sort.sortBy,
this.sort.sortDirection,
this.filterBy.filterParams.name,
this.filterBy.filterParams.owner,
this.filterBy.filterParams.priority,
this.filterBy.filterParams.state,
objectReference ? objectReference.type : '',
objectReference ? objectReference.value : ''
)
.findTasksWithWorkbasket(this.filterBy, this.sort, this.paging)
.pipe(takeUntil(this.destroy$))
.subscribe((taskResource) => {
this.requestInProgress = false;

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

View File

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

View File

@ -3,10 +3,11 @@ import { Observable, Subject } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { TaskResource } from 'app/workplace/models/task-resource';
import { Direction } from 'app/shared/models/sorting';
import { TaskanaQueryParameters } from 'app/shared/util/query-parameters';
import { QueryParameters } from 'app/shared/models/query-parameters';
import { Sorting, TaskQuerySortParameter } from 'app/shared/models/sorting';
import { StartupService } from '../../shared/services/startup/startup.service';
import { asUrlQueryString } from '../../shared/util/query-parameters-v2';
import { TaskQueryFilterParameter } from '../../shared/models/task-query-filter-parameter';
import { QueryPagingParameter } from '../../shared/models/query-paging-parameter';
@Injectable()
export class TaskService {
@ -40,31 +41,11 @@ export class TaskService {
}
findTasksWithWorkbasket(
basketId: string,
sortBy: string,
sortDirection: string,
nameLike: string,
ownerLike: string,
priority: string,
state: string,
objRefTypeLike: string,
objRefValueLike: string,
allPages: boolean = false
filterParameter: TaskQueryFilterParameter,
sortParameter: Sorting<TaskQuerySortParameter>,
pagingParameter: QueryPagingParameter
): Observable<TaskResource> {
const url = `${this.url}${TaskanaQueryParameters.getQueryParameters(
TaskService.accessIdsParameters(
basketId,
sortBy,
sortDirection,
nameLike,
ownerLike,
priority,
state,
objRefTypeLike,
objRefValueLike,
allPages
)
)}`;
const url = `${this.url}${asUrlQueryString({ ...filterParameter, ...sortParameter, ...pagingParameter })}`;
return this.httpClient.get<TaskResource>(url);
}
@ -110,34 +91,4 @@ export class TaskService {
});
return task;
}
private static accessIdsParameters(
basketId: string,
sortBy = 'priority',
sortDirection: string = Direction.ASC,
nameLike: string,
ownerLike: string,
priority: string,
state: string,
objRefTypeLike: string,
objRefValueLike: string,
allPages: boolean = false
): QueryParameters {
const parameters = new QueryParameters();
parameters.WORKBASKET_ID = basketId;
parameters.SORTBY = sortBy;
parameters.SORTDIRECTION = sortDirection;
parameters.NAMELIKE = nameLike;
parameters.OWNERLIKE = ownerLike;
parameters.PRIORITY = priority;
parameters.STATE = state;
parameters.TASK_PRIMARY_OBJ_REF_TYPE_LIKE = objRefTypeLike;
parameters.TASK_PRIMARY_OBJ_REF_VALUE_LIKE = objRefValueLike;
if (allPages) {
delete TaskanaQueryParameters.page;
delete TaskanaQueryParameters.pageSize;
}
return parameters;
}
}

View File

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